Skip to content

Commit

Permalink
ansible: Support vars in watches
Browse files Browse the repository at this point in the history
This adds support for extra vars in top level watches for ansible, to allow for the same playbook to be used for multiple GVKs with different vars.

Fixes operator-framework#2138
  • Loading branch information
Jamstah committed Nov 2, 2019
1 parent 8daec8c commit 468e2c5
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Unreleased

### Added
- Support for vars in top level ansible watches. ([#2147](https://github.com/operator-framework/operator-sdk/pull/2147))

### Changed
- Upgrade minimal Ansible version in the init projects from `2.4` to `2.6`. ([#2107](https://github.com/operator-framework/operator-sdk/pull/2107))
Expand Down
9 changes: 7 additions & 2 deletions doc/ansible/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ expects this mapping file in a predefined location: `/opt/ansible/watches.yaml`
* **playbook**: This is the path to the playbook that you have added to the
container. This playbook is expected to be simply a way to call roles. This
field is mutually exclusive with the "role" field.
* **vars**: This is an arbitrary map of key-value pairs. The contents will be
passed as `extra_vars` to the playbook or role specified for this watch.
* **reconcilePeriod** (optional): The reconciliation interval, how often the
role/playbook is run, for a given CR.
* **manageStatus** (optional): When true (default), the operator will manage
Expand All @@ -80,13 +82,16 @@ An example Watches file:
playbook: /opt/ansible/playbook.yml

# More complex example for our Baz kind
# Here we will disable requeuing and be managing the CR status in the playbook
# Here we will disable requeuing and be managing the CR status in the playbook,
# and specify additional variables.
- version: v1alpha1
group: baz.example.com
kind: Baz
playbook: /opt/ansible/baz.yml
reconcilePeriod: 0
manageStatus: false
vars:
foo: bar
```
## Customize the operator logic
Expand Down Expand Up @@ -145,7 +150,7 @@ resource is modified.

Defining the spec for an Ansible Operator can be done entirely in Ansible. The
Ansible Operator will simply pass all key value pairs listed in the Custom
Resource spec field along to Ansible as
Resource spec field along to Ansible as extra
[variables](https://docs.ansible.com/ansible/2.5/user_guide/playbooks_variables.html#passing-variables-on-the-command-line).
The names of all variables in the spec field are converted to snake_case
by the operator before running ansible. For example, `serviceAccount` in
Expand Down
10 changes: 8 additions & 2 deletions pkg/ansible/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,14 @@ func New(watch watches.Watch) (Runner, error) {
finalizerCmdFunc = playbookCmdFunc(verbosityString, watch.Finalizer.Playbook)
case watch.Finalizer.Role != "":
finalizerCmdFunc = roleCmdFunc(verbosityString, watch.Finalizer.Role)
case len(watch.Finalizer.Vars) != 0:
default:
finalizerCmdFunc = cmdFunc
}

return &runner{
Path: path,
cmdFunc: cmdFunc,
Vars: watch.Vars,
Finalizer: watch.Finalizer,
finalizerCmdFunc: finalizerCmdFunc,
GVK: watch.GroupVersionKind,
Expand All @@ -123,6 +124,7 @@ type runner struct {
GVK schema.GroupVersionKind // GVK being watched that corresponds to the Path
Finalizer *watches.Finalizer
cmdFunc func(ident, inputDirPath string, maxArtifacts int) *exec.Cmd // returns a Cmd that runs ansible-runner
Vars map[string]interface{}
finalizerCmdFunc func(ident, inputDirPath string, maxArtifacts int) *exec.Cmd
maxRunnerArtifacts int
}
Expand Down Expand Up @@ -249,7 +251,8 @@ func (r *runner) isFinalizerRun(u *unstructured.Unstructured) bool {
// "namespace": <object_namespace>,
// },
// <cr_spec_fields_as_snake_case>,
// ...
// <watch vars>,
// <finalizer vars>,
// _<group_as_snake>_<kind>: {
// <cr_object> as is
// }
Expand All @@ -274,6 +277,9 @@ func (r *runner) makeParameters(u *unstructured.Unstructured) map[string]interfa
specKey := fmt.Sprintf("%s_spec", objKey)
parameters[specKey] = spec

for k, v := range r.Vars {
parameters[k] = v
}
if r.isFinalizerRun(u) {
for k, v := range r.Finalizer.Vars {
parameters[k] = v
Expand Down
21 changes: 20 additions & 1 deletion pkg/ansible/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func TestNew(t *testing.T) {
gvk schema.GroupVersionKind
playbook string
role string
vars map[string]interface{}
finalizer *watches.Finalizer
}{
{
Expand Down Expand Up @@ -123,11 +124,29 @@ func TestNew(t *testing.T) {
},
},
},
{
name: "basic runner with playbook, vars + finalizer vars",
gvk: schema.GroupVersionKind{
Group: "operator.example.com",
Version: "v1alpha1",
Kind: "Example",
},
playbook: validPlaybook,
vars: map[string]interface{}{
"type": "this",
},
finalizer: &watches.Finalizer{
Name: "example.finalizer.com",
Vars: map[string]interface{}{
"state": "absent",
},
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
testWatch := watches.New(tc.gvk, tc.role, tc.playbook, tc.finalizer)
testWatch := watches.New(tc.gvk, tc.role, tc.playbook, tc.vars, tc.finalizer)

testRunner, err := New(*testWatch)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions pkg/ansible/watches/testdata/valid.yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,9 @@
group: app.example.com
kind: AnsibleVerbosityEnv
role: {{ .ValidRole }}
- version: v1alpha1
group: app.example.com
kind: WatchWithVars
role: {{ .ValidRole }}
vars:
sentinel: reconciling
28 changes: 16 additions & 12 deletions pkg/ansible/watches/watches.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Watch struct {
GroupVersionKind schema.GroupVersionKind `yaml:",inline"`
Playbook string `yaml:"playbook"`
Role string `yaml:"role"`
Vars map[string]interface{} `yaml:"vars"`
MaxRunnerArtifacts int `yaml:"maxRunnerArtifacts"`
ReconcilePeriod time.Duration `yaml:"reconcilePeriod"`
ManageStatus bool `yaml:"manageStatus"`
Expand Down Expand Up @@ -80,17 +81,18 @@ var (
func (w *Watch) UnmarshalYAML(unmarshal func(interface{}) error) error {
// Use an alias struct to handle complex types
type alias struct {
Group string `yaml:"group"`
Version string `yaml:"version"`
Kind string `yaml:"kind"`
Playbook string `yaml:"playbook"`
Role string `yaml:"role"`
MaxRunnerArtifacts int `yaml:"maxRunnerArtifacts"`
ReconcilePeriod string `yaml:"reconcilePeriod"`
ManageStatus bool `yaml:"manageStatus"`
WatchDependentResources bool `yaml:"watchDependentResources"`
WatchClusterScopedResources bool `yaml:"watchClusterScopedResources"`
Finalizer *Finalizer `yaml:"finalizer"`
Group string `yaml:"group"`
Version string `yaml:"version"`
Kind string `yaml:"kind"`
Playbook string `yaml:"playbook"`
Role string `yaml:"role"`
Vars map[string]interface{} `yaml:"vars"`
MaxRunnerArtifacts int `yaml:"maxRunnerArtifacts"`
ReconcilePeriod string `yaml:"reconcilePeriod"`
ManageStatus bool `yaml:"manageStatus"`
WatchDependentResources bool `yaml:"watchDependentResources"`
WatchClusterScopedResources bool `yaml:"watchClusterScopedResources"`
Finalizer *Finalizer `yaml:"finalizer"`
}
var tmp alias

Expand Down Expand Up @@ -125,6 +127,7 @@ func (w *Watch) UnmarshalYAML(unmarshal func(interface{}) error) error {
w.GroupVersionKind = gvk
w.Playbook = tmp.Playbook
w.Role = tmp.Role
w.Vars = tmp.Vars
w.MaxRunnerArtifacts = tmp.MaxRunnerArtifacts
w.MaxWorkers = getMaxWorkers(gvk, maxWorkersDefault)
w.ReconcilePeriod = reconcilePeriod
Expand Down Expand Up @@ -166,12 +169,13 @@ func (w *Watch) Validate() error {
}

// New - returns a Watch with sensible defaults.
func New(gvk schema.GroupVersionKind, role, playbook string, finalizer *Finalizer) *Watch {
func New(gvk schema.GroupVersionKind, role, playbook string, vars map[string]interface{}, finalizer *Finalizer) *Watch {
reconcilePeriod, _ := time.ParseDuration(reconcilePeriodDefault)
return &Watch{
GroupVersionKind: gvk,
Playbook: playbook,
Role: role,
Vars: vars,
MaxRunnerArtifacts: maxRunnerArtifactsDefault,
MaxWorkers: maxWorkersDefault,
ReconcilePeriod: reconcilePeriod,
Expand Down
13 changes: 12 additions & 1 deletion pkg/ansible/watches/watches_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func TestNew(t *testing.T) {
gvk schema.GroupVersionKind
role string
playbook string
vars map[string]interface{}
finalizer *Finalizer
shouldValidate bool
}{
Expand All @@ -50,7 +51,7 @@ func TestNew(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
watch := New(tc.gvk, tc.role, tc.playbook, tc.finalizer)
watch := New(tc.gvk, tc.role, tc.playbook, tc.vars, tc.finalizer)
if watch.GroupVersionKind != tc.gvk {
t.Fatalf("Unexpected GVK %v expected %v", watch.GroupVersionKind, tc.gvk)
}
Expand Down Expand Up @@ -348,6 +349,16 @@ func TestLoad(t *testing.T) {
ManageStatus: true,
AnsibleVerbosity: 4,
},
Watch{
GroupVersionKind: schema.GroupVersionKind{
Version: "v1alpha1",
Group: "app.example.com",
Kind: "WatchWithVars",
},
Role: validTemplate.ValidRole,
ManageStatus: true,
Vars: map[string]interface{}{"sentinel": "reconciling"},
},
},
},
}
Expand Down

0 comments on commit 468e2c5

Please sign in to comment.