Skip to content

feat(ansible): make ansible verbosity configurable #2087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### Added

- Added `Operator Version: X.Y.Z` information in the operator logs.([#1953](https://github.com/operator-framework/operator-sdk/pull/1953))
- Make Ansible verbosity configurable via the `ansible-verbosity` flag. ([#2087](https://github.com/operator-framework/operator-sdk/pull/2087))

### Changed

Expand Down
43 changes: 42 additions & 1 deletion doc/ansible/dev/advanced_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Some features can be overridden per resource via an annotation on that CR. The o

| Feature | Yaml Key | Description| Annotation for override | default | Documentation |
|---------|----------|------------|-------------------------|---------|---------------|
| Reconcile Period | `reconcilePeriod` | time between reconcile runs for a particular CR | ansbile.operator-sdk/reconcile-period | 1m | |
| Reconcile Period | `reconcilePeriod` | time between reconcile runs for a particular CR | ansible.operator-sdk/reconcile-period | 1m | |
| Manage Status | `manageStatus` | Allows the ansible operator to manage the conditions section of each resource's status section. | | true | |
| Watching Dependent Resources | `watchDependentResources` | Allows the ansible operator to dynamically watch resources that are created by ansible | | true | [dependent_watches.md](dependent_watches.md) |
| Watching Cluster-Scoped Resources | `watchClusterScopedResources` | Allows the ansible operator to watch cluster-scoped resources that are created by ansible | | false | |
Expand Down Expand Up @@ -111,3 +111,44 @@ From this data, we can see that the environment variable will be
- name: WORKER_MEMCACHED_CACHE_EXAMPLE_COM
value: "6"
```

## Ansible Verbosity

Setting the verbosity at which `ansible-runner` is run controls how verbose the
output of `ansible-playbook` will be. The normal rules for verbosity apply
here, where higher values mean more output. Acceptable values range from 0
(only the most severe messages are output) to 7 (all debugging messages are
output).

There are two ways to configure the verbosity argument to the `ansible-runner`
command:

1. Operator **authors and admins** can set the Ansible verbosity by including
extra args to the operator container in the operator deployment.
1. Operator **admins** can set Ansible verbosity by setting an environment
variable in the format `ANSIBLE_VERBOSITY_<kind>_<group>`. This variable must
be all uppercase and all periods (e.g. in the group name) are replaced with
underscore.

### Example

For demonstration purposes, let us assume that we have a database operator that
supports two Kinds -- `MongoDB` and `PostgreSQL` -- in the `db.example.com`
Group. We have only recently implemented the support for the `MongoDB` Kind so
we want reconciles for this Kind to be more verbose. Our operator container's
spec in our `deploy/operator.yaml` might look something like:

```yaml
- name: operator
image: "quay.io/example/database-operator:v1.0.0"
imagePullPolicy: "Always"
args:
# This value applies to all GVKs specified in watches.yaml
# that are not overriden by environment variables.
- "--ansible-verbosity"
- "1"
env:
# Override the verbosity for the MongoDB kind
- name: ANSIBLE_VERBOSITY_MONGODB_DB_EXAMPLE_COM
value: "4"
```
12 changes: 10 additions & 2 deletions pkg/ansible/flags/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import (
// AnsibleOperatorFlags - Options to be used by an ansible operator
type AnsibleOperatorFlags struct {
watch.WatchFlags
InjectOwnerRef bool
MaxWorkers int
InjectOwnerRef bool
MaxWorkers int
AnsibleVerbosity int
}

// AddTo - Add the ansible operator flags to the the flagset
Expand All @@ -47,5 +48,12 @@ func AddTo(flagSet *pflag.FlagSet, helpTextPrefix ...string) *AnsibleOperatorFla
"Maximum number of workers to use. Overridden by environment variable."),
" "),
)
flagSet.IntVar(&aof.AnsibleVerbosity,
"ansible-verbosity",
2,
strings.Join(append(helpTextPrefix,
"Ansible verbosity. Overridden by environment variable."),
" "),
)
return aof
}
2 changes: 1 addition & 1 deletion pkg/ansible/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func Run(flags *aoflags.AnsibleOperatorFlags) error {

var gvks []schema.GroupVersionKind
cMap := controllermap.NewControllerMap()
watches, err := watches.Load(flags.WatchesFile, flags.MaxWorkers)
watches, err := watches.Load(flags.WatchesFile, flags.MaxWorkers, flags.AnsibleVerbosity)
if err != nil {
log.Error(err, "Failed to load watches.")
return err
Expand Down
55 changes: 29 additions & 26 deletions pkg/ansible/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,62 +52,65 @@ type Runner interface {
GetFinalizer() (string, bool)
}

func ansibleVerbosityString(verbosity int) string {
if verbosity > 0 {
return fmt.Sprintf("-%v", strings.Repeat("v", verbosity))
}
return ""
}

type cmdFuncType func(ident, inputDirPath string, maxArtifacts int) *exec.Cmd

func playbookCmdFunc(verbosity, path string) cmdFuncType {
return func(ident, inputDirPath string, maxArtifacts int) *exec.Cmd {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be worth creating an issue to make it possible to configure the ansibleVerbosity via an annotation on the Custom Resource. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that idea a lot actually

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return exec.Command("ansible-runner", verbosity, "--rotate-artifacts", fmt.Sprintf("%v", maxArtifacts), "-p", path, "-i", ident, "run", inputDirPath)
}
}

func roleCmdFunc(verbosity, path string) cmdFuncType {
rolePath, roleName := filepath.Split(path)
return func(ident, inputDirPath string, maxArtifacts int) *exec.Cmd {
return exec.Command("ansible-runner", verbosity, "--rotate-artifacts", fmt.Sprintf("%v", maxArtifacts), "--role", roleName, "--roles-path", rolePath, "--hosts", "localhost", "-i", ident, "run", inputDirPath)
}
}

// New - creates a Runner from a Watch struct
func New(watch watches.Watch) (Runner, error) {
// handle role or playbook
var path string
var cmdFunc func(ident, inputDirPath string, maxArtifacts int) *exec.Cmd
var cmdFunc, finalizerCmdFunc cmdFuncType

err := watch.Validate()
if err != nil {
log.Error(err, "Failed to validate watch")
return nil, err
}
verbosityString := ansibleVerbosityString(watch.AnsibleVerbosity)

switch {
case watch.Playbook != "":
path = watch.Playbook
cmdFunc = func(ident, inputDirPath string, maxArtifacts int) *exec.Cmd {
return exec.Command("ansible-runner", "-vv", "--rotate-artifacts", fmt.Sprintf("%v", maxArtifacts), "-p", path, "-i", ident, "run", inputDirPath)
}
cmdFunc = playbookCmdFunc(verbosityString, path)
case watch.Role != "":
path = watch.Role
cmdFunc = func(ident, inputDirPath string, maxArtifacts int) *exec.Cmd {
rolePath, roleName := filepath.Split(path)
return exec.Command("ansible-runner", "-vv", "--rotate-artifacts", fmt.Sprintf("%v", maxArtifacts), "--role", roleName, "--roles-path", rolePath, "--hosts", "localhost", "-i", ident, "run", inputDirPath)
}
default:
return nil, fmt.Errorf("must specify Role or Path")
cmdFunc = roleCmdFunc(verbosityString, path)
}

// handle finalizer
var finalizer *watches.Finalizer
var finalizerCmdFunc func(ident, inputDirPath string, maxArtifacts int) *exec.Cmd
switch {
case watch.Finalizer == nil:
finalizer = nil
finalizerCmdFunc = nil
case watch.Finalizer.Playbook != "":
finalizer = watch.Finalizer
finalizerCmdFunc = func(ident, inputDirPath string, maxArtifacts int) *exec.Cmd {
return exec.Command("ansible-runner", "-vv", "--rotate-artifacts", fmt.Sprintf("%v", maxArtifacts), "-p", finalizer.Playbook, "-i", ident, "run", inputDirPath)
}
finalizerCmdFunc = playbookCmdFunc(verbosityString, watch.Finalizer.Playbook)
case watch.Finalizer.Role != "":
finalizer = watch.Finalizer
finalizerCmdFunc = func(ident, inputDirPath string, maxArtifacts int) *exec.Cmd {
path := strings.TrimRight(finalizer.Role, "/")
rolePath, roleName := filepath.Split(path)
return exec.Command("ansible-runner", "-vv", "--rotate-artifacts", fmt.Sprintf("%v", maxArtifacts), "--role", roleName, "--roles-path", rolePath, "--hosts", "localhost", "-i", ident, "run", inputDirPath)
}
finalizerCmdFunc = roleCmdFunc(verbosityString, watch.Finalizer.Role)
case len(watch.Finalizer.Vars) != 0:
finalizer = watch.Finalizer
finalizerCmdFunc = cmdFunc
}

return &runner{
Path: path,
cmdFunc: cmdFunc,
Finalizer: finalizer,
Finalizer: watch.Finalizer,
finalizerCmdFunc: finalizerCmdFunc,
GVK: watch.GroupVersionKind,
maxRunnerArtifacts: watch.MaxRunnerArtifacts,
Expand Down
51 changes: 29 additions & 22 deletions pkg/ansible/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package runner

import (
"fmt"
"os"
"os/exec"
"path/filepath"
Expand All @@ -27,32 +26,20 @@ import (
"github.com/operator-framework/operator-sdk/pkg/ansible/watches"
)

func playbookCmd(path string, ident string, inputDirPath string, maxArtifacts int) *exec.Cmd {
return exec.Command("ansible-runner", "-vv", "--rotate-artifacts", fmt.Sprintf("%v", maxArtifacts), "-p", path, "-i", ident, "run", inputDirPath)
}

func roleCmd(path string, ident string, inputDirPath string, maxArtifacts int) *exec.Cmd {
rolePath, roleName := filepath.Split(path)
return exec.Command("ansible-runner", "-vv", "--rotate-artifacts", fmt.Sprintf("%v", maxArtifacts), "--role", roleName, "--roles-path", rolePath, "--hosts", "localhost", "-i", ident, "run", inputDirPath)
}

type cmdFuncType func(ident, inputDirPath string, maxArtifacts int) *exec.Cmd

func checkCmdFunc(t *testing.T, cmdFunc cmdFuncType, role, playbook string, watchesMaxArtifacts, runnerMaxArtifacts int) {
func checkCmdFunc(t *testing.T, cmdFunc cmdFuncType, playbook, role string, verbosity int) {
ident := "test"
inputDirPath := "/test/path"
var path string
maxArtifacts := 1
var expectedCmd, gotCmd *exec.Cmd
verbosityString := ansibleVerbosityString(verbosity)
switch {
case playbook != "":
path = playbook
expectedCmd = playbookCmd(path, ident, inputDirPath, watchesMaxArtifacts)
expectedCmd = playbookCmdFunc(verbosityString, playbook)(ident, inputDirPath, maxArtifacts)
case role != "":
path = role
expectedCmd = roleCmd(path, ident, inputDirPath, watchesMaxArtifacts)
expectedCmd = roleCmdFunc(verbosityString, role)(ident, inputDirPath, maxArtifacts)
}

gotCmd = cmdFunc(ident, inputDirPath, runnerMaxArtifacts)
gotCmd = cmdFunc(ident, inputDirPath, maxArtifacts)

if expectedCmd.Path != gotCmd.Path {
t.Fatalf("Unexpected cmd path %v expected cmd path %v", gotCmd.Path, expectedCmd.Path)
Expand Down Expand Up @@ -171,7 +158,7 @@ func TestNew(t *testing.T) {
}

// Check the cmdFunc
checkCmdFunc(t, testRunnerStruct.cmdFunc, testWatch.Role, testWatch.Playbook, testWatch.MaxRunnerArtifacts, testRunnerStruct.maxRunnerArtifacts)
checkCmdFunc(t, testRunnerStruct.cmdFunc, testWatch.Playbook, testWatch.Role, testWatch.AnsibleVerbosity)

// Check finalizer
if testRunnerStruct.Finalizer != testWatch.Finalizer {
Expand All @@ -184,12 +171,32 @@ func TestNew(t *testing.T) {
}

if len(testWatch.Finalizer.Vars) == 0 {
checkCmdFunc(t, testRunnerStruct.finalizerCmdFunc, testWatch.Finalizer.Role, testWatch.Finalizer.Playbook, testWatch.MaxRunnerArtifacts, testRunnerStruct.maxRunnerArtifacts)
checkCmdFunc(t, testRunnerStruct.cmdFunc, testWatch.Finalizer.Playbook, testWatch.Finalizer.Role, testWatch.AnsibleVerbosity)
} else {
// when finalizer vars is set the finalizerCmdFunc should be the same as the cmdFunc
checkCmdFunc(t, testRunnerStruct.finalizerCmdFunc, testWatch.Role, testWatch.Playbook, testWatch.MaxRunnerArtifacts, testRunnerStruct.maxRunnerArtifacts)
checkCmdFunc(t, testRunnerStruct.finalizerCmdFunc, testWatch.Playbook, testWatch.Role, testWatch.AnsibleVerbosity)
}
}
})
}
}

func TestAnsibleVerbosityString(t *testing.T) {
testCases := []struct {
verbosity int
expectedString string
}{
{verbosity: -1, expectedString: ""},
{verbosity: 0, expectedString: ""},
{verbosity: 1, expectedString: "-v"},
{verbosity: 2, expectedString: "-vv"},
{verbosity: 7, expectedString: "-vvvvvvv"},
}

for _, tc := range testCases {
gotString := ansibleVerbosityString(tc.verbosity)
if tc.expectedString != gotString {
t.Fatalf("Unexpected string %v expected %v", gotString, tc.expectedString)
}
}
}
13 changes: 13 additions & 0 deletions pkg/ansible/watches/testdata/valid.yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,16 @@
group: app.example.com
kind: MaxWorkersEnv
role: {{ .ValidRole }}
- version: v1alpha1
group: app.example.com
kind: AnsibleVerbosityDefault
role: {{ .ValidRole }}
- version: v1alpha1
group: app.example.com
kind: AnsibleVerbosityIgnored
role: {{ .ValidRole }}
ansibleVerbosity: 5
- version: v1alpha1
group: app.example.com
kind: AnsibleVerbosityEnv
role: {{ .ValidRole }}
40 changes: 36 additions & 4 deletions pkg/ansible/watches/watches.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ type Watch struct {
Finalizer *Finalizer `yaml:"finalizer"`

// Not configurable via watches.yaml
MaxWorkers int `yaml:"maxWorkers"`
MaxWorkers int `yaml:"maxWorkers"`
AnsibleVerbosity int `yaml:"ansibleVerbosity"`
}

// Finalizer - Expose finalizer to be used by a user.
Expand All @@ -68,7 +69,8 @@ var (
watchClusterScopedResourcesDefault = false

// these are overridden by cmdline flags
maxWorkersDefault = 1
maxWorkersDefault = 1
ansibleVerbosityDefault = 2
)

// UnmarshalYAML - implements the yaml.Unmarshaler interface for Watch.
Expand Down Expand Up @@ -130,6 +132,7 @@ func (w *Watch) UnmarshalYAML(unmarshal func(interface{}) error) error {
w.WatchDependentResources = tmp.WatchDependentResources
w.WatchClusterScopedResources = tmp.WatchClusterScopedResources
w.Finalizer = tmp.Finalizer
w.AnsibleVerbosity = getAnsibleVerbosity(gvk, ansibleVerbosityDefault)

return nil
}
Expand Down Expand Up @@ -176,12 +179,14 @@ func New(gvk schema.GroupVersionKind, role, playbook string, finalizer *Finalize
WatchDependentResources: watchDependentResourcesDefault,
WatchClusterScopedResources: watchClusterScopedResourcesDefault,
Finalizer: finalizer,
AnsibleVerbosity: ansibleVerbosityDefault,
}
}

// Load - loads a slice of Watches from the watches file from the CLI
func Load(path string, maxWorkers int) ([]Watch, error) {
func Load(path string, maxWorkers, ansibleVerbosity int) ([]Watch, error) {
maxWorkersDefault = maxWorkers
ansibleVerbosityDefault = ansibleVerbosity
b, err := ioutil.ReadFile(path)
if err != nil {
log.Error(err, "Failed to get config file")
Expand Down Expand Up @@ -251,7 +256,7 @@ func verifyAnsiblePath(playbook string, role string) error {
}

// if the WORKER_* environment variable is set, use that value.
// Otherwise, use the value from the CLI. This is definitely
// Otherwise, use defValue. This is definitely
// counter-intuitive but it allows the operator admin adjust the
// number of workers based on their cluster resources. While the
// author may use the CLI option to specify a suggested
Expand All @@ -276,3 +281,30 @@ func getMaxWorkers(gvk schema.GroupVersionKind, defValue int) int {
}
return maxWorkers
}

// if the ANSIBLE_VERBOSITY_* environment variable is set, use that value.
// Otherwise, use defValue.
func getAnsibleVerbosity(gvk schema.GroupVersionKind, defValue int) int {
envVar := strings.ToUpper(strings.Replace(
fmt.Sprintf("ANSIBLE_VERBOSITY_%s_%s", gvk.Kind, gvk.Group),
".",
"_",
-1,
))
ansibleVerbosity, err := strconv.Atoi(os.Getenv(envVar))
if err != nil {
log.Info("Failed to parse %v from environment. Using default %v", envVar, defValue)
return defValue
}

// Use default value when value doesn't make sense
if ansibleVerbosity < 0 {
log.Info("Value %v not valid. Using default %v", ansibleVerbosity, defValue)
return defValue
}
if ansibleVerbosity > 7 {
log.Info("Value %v not valid. Using default %v", ansibleVerbosity, defValue)
return defValue
}
return ansibleVerbosity
}
Loading