Skip to content
This repository has been archived by the owner on May 30, 2022. It is now read-only.

Ansible runner improvements - Run meta playbook #165

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
105 changes: 69 additions & 36 deletions runner/ansiblerunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,60 +22,58 @@ const (
AraApiServer = "ARA_API_SERVER"
)

//go:generate mockery --name=CustomCommand

type CustomCommand func(name string, arg ...string) *exec.Cmd

var customExecCommand CustomCommand = exec.Command

type AnsibleRunner struct {
Playbook string
Inventory string
Envs map[string]string
Check bool
}

func runPlaybook(playbook, inventory string, envs map[string]string) error {
log.Infof("Running ansible playbook %s, with inventory %s...", playbook, inventory)

cmd := exec.Command("ansible-playbook", playbook, fmt.Sprintf("--inventory=%s", inventory))

cmd.Env = os.Environ()
for key, value := range envs {
newEnv := fmt.Sprintf("%s=%s", key, value)
log.Debugf("New environment variable: %s", newEnv)
cmd.Env = append(cmd.Env, newEnv)
func DefaultAnsibleRunner() *AnsibleRunner {
return &AnsibleRunner{
Playbook: "main.yml",
Envs: make(map[string]string),
Check: false,
}
}

output, err := cmd.CombinedOutput()

log.Debugf("Ansible output:\n%s:", output)

if err != nil {
log.Errorf("An error occurred while running ansible: %s", err)
return err
func DefaultAnsibleRunnerWithAra() (*AnsibleRunner, error) {
a := DefaultAnsibleRunner()
if err := a.LoadAraPlugins(); err != nil {
return a, err
}

log.Info("Ansible playbook execution finished successfully")

return nil
return a, nil
}

func NewAnsibleRunner(playbook, inventory string) (*AnsibleRunner, error) {
r := &AnsibleRunner{Envs: make(map[string]string)}
func (a *AnsibleRunner) setEnv(name, value string) {
a.Envs[name] = value
}

func (a *AnsibleRunner) SetPlaybook(playbook string) error {
if _, err := os.Stat(playbook); os.IsNotExist(err) {
log.Errorf("Playbook file %s does not exist", playbook)
return r, err
return err
}

r.Playbook = playbook
a.Playbook = playbook
return nil
}

func (a *AnsibleRunner) SetInventory(inventory string) error {
if _, err := os.Stat(inventory); os.IsNotExist(err) {
log.Errorf("Inventory file %s does not exist", inventory)
return r, err
return err
}

r.Inventory = inventory

return r, nil
}

func (a *AnsibleRunner) setEnv(name, value string) {
a.Envs[name] = value
a.Inventory = inventory
return nil
}

// ARA_API_CLIENT is always set to "http" to ensure the usage of the REST API
Expand All @@ -89,7 +87,7 @@ func (a *AnsibleRunner) SetAraServer(host string) {
func (a *AnsibleRunner) LoadAraPlugins() error {
log.Info("Loading ARA plugins...")

araCallback := exec.Command("python3", "-m", "ara.setup.callback_plugins")
araCallback := customExecCommand("python3", "-m", "ara.setup.callback_plugins")
araCallbackPath, err := araCallback.Output()
if err != nil {
log.Errorf("An error occurred getting the ARA callback plugin path: %s", err)
Expand All @@ -100,7 +98,7 @@ func (a *AnsibleRunner) LoadAraPlugins() error {

a.setEnv(AnsibleCallbackPlugins, araCallbackPathStr)

araAction := exec.Command("python3", "-m", "ara.setup.action_plugins")
araAction := customExecCommand("python3", "-m", "ara.setup.action_plugins")
araActionPath, err := araAction.Output()
if err != nil {
log.Errorf("An error occurred getting the ARA actions plugin path: %s", err)
Expand All @@ -116,7 +114,7 @@ func (a *AnsibleRunner) LoadAraPlugins() error {
return nil
}

func (a *AnsibleRunner) isAraServerUp() bool {
func (a *AnsibleRunner) IsAraServerUp() bool {
server, ok := a.Envs[AraApiServer]
if !ok {
log.Warn("ARA server usage not configured")
Expand All @@ -143,5 +141,40 @@ func (a *AnsibleRunner) isAraServerUp() bool {
}

func (a *AnsibleRunner) RunPlaybook() error {
return runPlaybook(a.Playbook, a.Inventory, a.Envs)
var cmdItems []string

log.Infof("Ansible playbook %s", a.Playbook)
cmdItems = append(cmdItems, a.Playbook)

if a.Inventory != "" {
log.Infof("Inventory %s", a.Inventory)
cmdItems = append(cmdItems, fmt.Sprintf("--inventory=%s", a.Inventory))
}

if a.Check {
log.Info("Running in check mode")
cmdItems = append(cmdItems, "--check")
}

cmd := customExecCommand("ansible-playbook", cmdItems...)

cmd.Env = os.Environ()
for key, value := range a.Envs {
newEnv := fmt.Sprintf("%s=%s", key, value)
log.Debugf("New environment variable: %s", newEnv)
cmd.Env = append(cmd.Env, newEnv)
}

output, err := cmd.CombinedOutput()

log.Debugf("Ansible output:\n%s:", output)

if err != nil {
log.Errorf("An error occurred while running ansible: %s", err)
return err
}

log.Info("Ansible playbook execution finished successfully")

return nil
}
121 changes: 121 additions & 0 deletions runner/ansiblerunner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package runner

import (
"os"
"os/exec"
"testing"

"github.com/stretchr/testify/assert"
"github.com/trento-project/trento/runner/mocks"
)

func TestLoadAraPlugins(t *testing.T) {

a := DefaultAnsibleRunner()

cmdCallback := exec.Command("echo", "callback")
cmdAction := exec.Command("echo", "action")

mockCommand := new(mocks.CustomCommand)
customExecCommand = mockCommand.Execute
mockCommand.On("Execute", "python3", "-m", "ara.setup.callback_plugins").Return(
cmdCallback,
)
mockCommand.On("Execute", "python3", "-m", "ara.setup.action_plugins").Return(
cmdAction,
)

err := a.LoadAraPlugins()
a.SetAraServer("127.0.0.1")

expectedMetaRunner := &AnsibleRunner{
Playbook: "main.yml",
Envs: map[string]string{
"ANSIBLE_CALLBACK_PLUGINS": "callback",
"ANSIBLE_ACTION_PLUGINS": "action",
"ARA_API_CLIENT": "http",
"ARA_API_SERVER": "127.0.0.1",
},
Check: false,
}

assert.NoError(t, err)
assert.Equal(t, expectedMetaRunner, a)

mockCommand.AssertExpectations(t)
}

func TestRunPlaybookSimple(t *testing.T) {

runnerInst := &AnsibleRunner{
Playbook: "superplay.yml",
}

cmd := exec.Command("echo", "stdout", "&&", ">&2", "echo", "stderr")

mockCommand := new(mocks.CustomCommand)
customExecCommand = mockCommand.Execute
mockCommand.On("Execute", "ansible-playbook", "superplay.yml").Return(
cmd,
)

err := runnerInst.RunPlaybook()

assert.Equal(t, os.Environ(), cmd.Env)
assert.NoError(t, err)

mockCommand.AssertExpectations(t)
}

func TestRunPlaybookError(t *testing.T) {

runnerInst := &AnsibleRunner{
Playbook: "superplay.yml",
}

cmd := exec.Command("error")

mockCommand := new(mocks.CustomCommand)
customExecCommand = mockCommand.Execute
mockCommand.On("Execute", "ansible-playbook", "superplay.yml").Return(
cmd,
)

err := runnerInst.RunPlaybook()

assert.Equal(t, os.Environ(), cmd.Env)
assert.EqualError(t, err, "exec: \"error\": executable file not found in $PATH")

mockCommand.AssertExpectations(t)
}

func TestRunPlaybookComplex(t *testing.T) {

runnerInst := &AnsibleRunner{
Playbook: "superplay.yml",
Inventory: "inventory.yml",
Envs: map[string]string{
"env1": "value1",
"env2": "value2",
},
Check: true,
}

cmd := exec.Command("echo", "stdout", "&&", ">&2", "echo", "stderr")

mockCommand := new(mocks.CustomCommand)
customExecCommand = mockCommand.Execute
mockCommand.On(
"Execute", "ansible-playbook", "superplay.yml",
"--inventory=inventory.yml", "--check").Return(
cmd,
)

err := runnerInst.RunPlaybook()

assert.Contains(t, cmd.Env, "env1=value1")
assert.Contains(t, cmd.Env, "env2=value2")
assert.NoError(t, err)

mockCommand.AssertExpectations(t)
}
37 changes: 37 additions & 0 deletions runner/mocks/CustomCommand.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.