apenella / go-ansible

Go-ansible is a Go package that enables the execution of ansible-playbook or ansible commands directly from Golang applications. It supports a wide range of options for each command, enabling smooth integration of Ansible functionality into your projects.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Help with StdoutCallbacks

rhugga opened this issue · comments

Im writing a large automation that will be running several playbooks and many adhoc commands. Is there a way to get stdout/stderr from ansible commands using the shell/command modules in json or some structured output that can be easily parse?

I can only seem to get output like this:

msg="running ansible command: ansible all  --args uname -r --extra-vars '{"ansible_sudo_pass":"mysecret"}' --forks 5 --inventory myhost,  --timeout 30  --become-method sudo --become-user root"
msg="── myhost | CHANGED | rc=0 >>"
msg="── 3.10.0-1160.31.1.el7.x86_64"
── myhost | CHANGED | rc=0 >>
── 3.10.0-1160.31.1.el7.x86_64

My transformer:

func parseKernelVersion() results.TransformerFunc {
    return func(s string) string {
        log.Debugf("%s", s)
        return s
    }
}

Then how I set it up:

    command.Exec = execute.NewDefaultExecute(execute.WithTransformers(parseKernelVersion()))
    // I've tried setting this to "" and few other options
    command.StdoutCallback = "oneline"
    //command.StdoutCallback = "json"

Is there a way to get stdout/stderr back in a json structure of some sort here?

Hi @rhugga
The way to get the ansible command output as a json is using the json's stdout callback. It seems that you tried but it did not convince you.
One drawback of using json stdout callback is that the result is shown once the playbook finishes, and another one is that you will need to walk through all task till you achieve the shell/command task. Fortunately, you could use JSONParse function, that returns an AnsiblePlaybookJSONResults with the stdout already parsed, and you only need to pick the messages.

Another option is to use diy stdout callback, that lets you customize the ansible output (I have never used it) and parse the output with go-ansible transformers.

As you could see, go-ansible does not solve what you are facing. You need to find something between ansible and go-ansible.

Let me know If It helps you!
Thanks!

I'm using the json callback for playbook commands, I'm talking about adhoc commands here where the structure will be random and tied directly to whatever shell command you're running. I'll look into the diy callback, maybe that's what I'm looking for.

So for example, we have an in-house utility for managing kernel upgrades that lives on every host. It's an old command, it's a mess of perl code that no one wants to dive into and update and it outputs a bunch of unstructured text. So I need to massage the hell out of it with regexp and strings package to get it into a programmable form. Would I use transformers to do this or is their a better place for this code?

Hi @rhugga
I would recommend to keep transformer as simple as you can. It is executed on each output line received from ansible command.
In that case, I would recommend processing it once the playbook finishes.

I have realized that to get the output from the shell/command task, it would be better to include ansible's stdout attribute to AnsiblePlaybookJSONResultsPlayTaskHostsItem struct.
I am going to work on it today or tomorrow.

I have written a dummy example, that achieve the shell's task output, which is a json, unmarshal that json to an struct and finally prints its content. It uses the stdout attribute, which is not already available.

  • The playbook:
- hosts: all
  gather_facts: False

  tasks:
    - name: json-stdout-ansibleplaybook
      ansible.builtin.shell: |
        printf '{"host": "%s", "message": "%s"}' "{{ inventory_hostname }}" "that is just an example"
      args:
        executable: /bin/bash

  • The golang code:
func main() {

	var err error
	var res *results.AnsiblePlaybookJSONResults

	buff := new(bytes.Buffer)

	ansiblePlaybookConnectionOptions := &options.AnsibleConnectionOptions{
		Connection: "local",
		User:       "apenella",
	}

	ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
		Inventory: "127.0.0.1,",
	}

	execute := execute.NewDefaultExecute(
		execute.WithWrite(io.Writer(buff)),
	)

	playbook := &playbook.AnsiblePlaybookCmd{
		Playbooks:         []string{"site.yml"},
		Exec:              execute,
		ConnectionOptions: ansiblePlaybookConnectionOptions,
		Options:           ansiblePlaybookOptions,
		StdoutCallback:    "json",
	}

	err = playbook.Run(context.TODO())
	if err != nil {
		fmt.Println(err.Error())
	}

	res, err = results.JSONParse(buff.Bytes())
	if err != nil {
		panic(err)
	}

	msgOutput := struct {
		Host    string `json:"host"`
		Message string `json:"message"`
	}{}

	for _, play := range res.Plays {
		for _, task := range play.Tasks {
                        // only prints task json-stdout-ansibleplaybook
			if task.Task.Name == "json-stdout-ansibleplaybook" {
				for _, content := range task.Hosts {
					err = json.Unmarshal([]byte(content.Stdout), &msgOutput)
					if err != nil {
						panic(err)
					}
					fmt.Println(msgOutput.Host, ": ", msgOutput.Message)
				}
			}
		}
	}
}

The output:

❯ go run test-json.go 
127.0.0.1 :  that is just an example

Although it is a dummy example, could it helps you?

related to #68

Thx I'll give it a shot today.

Hi @rhugga
v1.1.2 has been released and includes the above example as walk-through-json-output-ansibleplaybook
Let me know if it helps you!

Hi @rhugga
regarding that issue, do you need any thing else?

Thanks!

That helped. I ended up making a further mod to include more fields, I can't recall now haven't looked at this code in over a month. Thx!