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?
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!
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!