Ansible uri POST with body containing as key value JSON - ansible

I have a config_file with values in the format: key-value pair
e.g. a: "{\"type\": \"some_type\"}"
Everything is ok. Util I tried to pass this JSON value as value inside uri utility:
- name: 'Push config values'
uri:
url: '{{ uri }}/api/{{ item.key }}'
method: POST
body: '{"alias":"{{ item.key }}","key":"{{ item.key }}","value":"{{ item.value }}"}'
body_format: json
validate_certs: no
status_code: 204
headers:
X-Token: "{{ token }}"
with_dict: '{{ config_overrides_file }}'
register: response
I tried different way of escaping, also filter combination | from_json | to_json. But nothing is working. I got 'failed to parse JSON input: invalid character ''t'' after object key:value pair' in case of escaping
Probably, exist some standard approach to handle it?
I found just one way to achieve what I want: instead of a: "{\"type\": \"some_type\"}" this value in config file it could be a: {"type": "some_type"} and in uri it will be body: {"alias":"{{ item.key }}","key":"{{ item.key }}", "value": {{ item.value | to_json }}}. It's working approach but I would like not create additional task for json and non-json properties for such type of request.
P.S. When I hardcode this value everything is ok

You're getting bitten by ansible's "helpful" behavior of coercing any string it finds that starts with { back into a dict; the usual fix for that is to send it explicitly through |string to indicate to ansible you do not wish for that to happen. For sure any loop-ish behavior on a task (with_dict: being one of them) makes that problem much worse, because ansible gets really, really weird about coercion and recursive jinja2 templates in that circumstance
- name: test data for SO
set_fact:
config_values_file:
a: '{"type": "some_type"}'
- name: 'Push config values'
uri:
url: '{{ uri }}/api/{{ item.key }}'
method: POST
body: >-
{{ {"alias": item.key,
"key": item.key,
"value": item.value|string
} }}
body_format: json
validate_certs: no
status_code: 204
headers:
X-Token: "{{ token }}"
with_dict: '{{ config_overrides_file }}'
register: response
POSTs:
{"alias": "a", "key": "a", "value": "{\"type\": \"some_type\"}"} which I believe is what you were asking for

Related

Ansible template error while templating string : unexpected char '#' in email

I'm using Ansible to get a list of user emails from an API and I want to loop over them.
This is the json response I get from the API:
"users": [
{
"email": "email1#email.com",
"id": 1,
"is_admin": true
},
{
"email": "email2#email.com",
"id": 2,
"is_admin": false
},
]
edit:
the task after that which I need the email for:
- name: Send emails
register: result
uri:
url: http://api
method: GET
body_format: json
return_content: yes
body:
email: "{{ item.email }}"
scope: SCOPE
loop: "{{ users.json['users'] }}"
- name: Print result
debug:
var: result
the error I get:
fatal: [localhost]: FAILED! => {"msg": "template error while templating string: unexpected char '#' at 16. String: {{ email#email.com }}"}
If I use email: item.email the json request body will be "body": {"email": "item.email"} instead of the email value
How Can I get the full email of each user?
You need to remove the {{ and }} markers from your var: directive. The value of var is implicitly evaluated in a Jinja template context so you don't need those markers:
- name: Print returned json dictionary
debug:
var: item.email
loop: "{{ users.json['users'] }}"
(Updated based on edits to your question)
The example you've shown doesn't generate the error you're asking about. Here's a complete reproducer with just the uri changed:
- hosts: localhost
gather_facts: false
vars:
users:
json:
users:
- email: email1#email.com
id: 1
is_admin: true
- email: email2#email.com
id: 2
is_admin: false
tasks:
- name: Send emails
register: result
uri:
url: https://eny1tdcqj6ghp.x.pipedream.net
method: GET
body_format: json
return_content: true
body:
email: "{{ item.email }}"
scope: SCOPE
loop: "{{ users.json['users'] }}"
This runs without errors (although note that I'm suspicious about the
use of a GET request with a JSON body; typically you would expect
that to be a POST request).
You can see the result of running this playbook here, which shows that the values being sent in the request are exactly what we expect from the data.

Ansible TypeError: must be string or buffer, not list

Below task is failing with must be string or buffer, not list and when I use the same loop over shell and the output prints string, so not sure where it is going wrong. I have used loop also, it is also giving same output
- name: Provide Ambari cluster user role to users in file {{ cluster_user_group_file }}
uri:
url: "http://{{ ansible_fqdn }}:8080/api/v1/clusters/{{ cluster_name }}/privileges"
method: POST
force_basic_auth: yes
user: "{{ ambari_admin_user }}"
password: "{{ ambari_admin_password }}"
headers: '{"X-Requested-By":"ambari"}'
body: "[{\"PrivilegeInfo\":{\"permission_name\":\"CLUSTER.USER\",\"principal_name\":\"{{ item }}\",\"principal_type\":\"GROUP\"}}]"
status_code: 200,201,202,409
timeout: 60
return_content: no
with_items: "{{ lookup('file', '{{ cluster_user_group_file }}').split(',') }}"
This is resolved by adding to_json and setting body_format: raw
- name: Provide Ambari cluster user role to users in file {{ cluster_user_group_file }}
uri:
url: "http://{{ ansible_fqdn }}:8080/api/v1/clusters/{{ cluster_name }}/privileges"
method: POST
force_basic_auth: yes
user: "{{ ambari_admin_user }}"
password: "{{ ambari_admin_password }}"
headers: '{"X-Requested-By":"ambari"}'
body: "[{\"PrivilegeInfo\":{\"permission_name\":\"CLUSTER.USER\",\"principal_name\":\"{{ item }}\",\"principal_type\":\"GROUP\"}}]|to_json"
body_format: raw
status_code: 200,201,202,409
timeout: 60
return_content: no
with_items: "{{ lookup('file', '{{ cluster_user_group_file }}').split(',') }}"

Ansible Using large variable in playbook opts to fail while pasting the raw data in, or using other smaller variables will run

I want to push ios_facts to gitlab using ansibles uri module.
- name: get ios facts
ios_facts:
gather_subset: all
register: ios_facts
- name: commit to gitlab
delegate_to: localhost
uri:
url: http://gitlab/api/v4/projects/2/repository/commits
method: POST
body_format: json
status_code: 201
headers:
PRIVATE-TOKEN: "xxxxxxxxxxxxxx"
Content-Type: "application/json"
body: |
{
"branch": "master",
"commit_message": "{{ ansible_net_hostname }} update",
"actions": [
{
"action": "update",
"file_path": "conf/{{ ansible_net_hostname }}",
"content": "{{ ansible_net_config }}"
}
]
}
The Playbook works fine if I am using any other variable than ansible_net_config, or if I am pasting the raw content of ansible_net_config instead of using the jinja2 reference. The ansible_net_config is a large string using \n as new line and contains some special characters. I guess the problem occurs because I don't get valid json when the playbook parses.
I then get the HTTP Error 400: Bad Request
Is there any filter I can apply or any other thing I might be missing out?
I managed to solve the issue:
The API Call failed when the variable contains "\n". I could make it work by replacing "\n" with escaped "\\n":
...
"content": {{ ansible_net_config | replace('\n','\\n') }}
...

Ansible URI module passing integer from variable

I set a variable from a previous play that collects a value (integer), then call it in the following task using the uri module to pass it via API. But I always get a 422 error back saying "Value should be integer".
- name: Deploy Staging Blueprint
local_action:
module: uri
url: "https://{{ server_address }}/api/application/{{app_id}}/deploy"
method: PUT
status_code: 202
validate_certs: no
headers:
Content-Type: 'application/json'
Accept: 'application/json'
AUTHTOKEN: "{{ server_session.token }}"
body_format: json
body:
version: "{{ staging.json.version }}"
run_once: true
register: deploy
changed_when: deploy|succeeded
I first tried converting it to integer with "| int" but that didnt work. I then tried "| type_debug" and validated the variable is an integer.
But I still get this error. If I replace the variable with a raw integer it works just fine. Using verbose output it still appears the value is being passed as a string.
"invocation": {
"module_args": {
"attributes": null,
"backup": null,
"body": {
"version": "48"
},
"body_format": "json",
Any idea what Im missing here or how I can work around this? Im currently running Ansible 2.4.0 for this project.
The uri module can take a pre-formatted body in JSON format. Try:
- name: Deploy Staging Blueprint
local_action:
module: uri
url: "https://{{ server_address }}/api/application/{{app_id}}/deploy"
method: PUT
status_code: 202
validate_certs: no
headers:
Content-Type: 'application/json'
Accept: 'application/json'
AUTHTOKEN: "{{ server_session.token }}"
body_format: json
body: '{ "version": {{ staging.json.version }} }'
run_once: true
register: deploy
changed_when: deploy|succeeded
Yes, this is a really strange default behavior of Ansible. It renders every value as string regardless its original type. So you end up with staging.json.version being a string instead of a numeric type.
This behavior can be altered in recent Ansible releases using the jina2_native flag globally.
Example with
test_int: 50
Try using the Jinja variable without quotes in the body parameter i.e.
body: '{ "test": {{ test_int }} }'
It works for me in Ansible 2.9.13

Post Json to API via Ansible

I want to make a POST request to an API endpoint via Ansible where some of the items inside the post data are dynamic, here is what I try and fail:
My body_content.json:
{
apiKey: '{{ KEY_FROM_VARS }}',
data1: 'foo',
data2: 'bar'
}
And here is my Ansible task:
# Create an item via API
- uri: url="http://www.myapi.com/create"
method=POST return_content=yes HEADER_Content-Type="application/json"
body="{{ lookup('file','create_body.json') | to_json }}"
Sadly this doesn't work:
failed: [localhost] => {"failed": true}
msg: this module requires key=value arguments
....
FATAL: all hosts have already failed -- aborting
My ansible version is 1.9.1
You can't use newlines like this in yaml. Try this instead (the ">" indicates that the next lines are to be concatenated):
# Create an item via API
- uri: >
url="http://www.myapi.com/create"
method=POST return_content=yes HEADER_Content-Type="application/json"
body="{{ lookup('file','create_body.json') | to_json }}"
But I find this much better:
# Create an item via API
- uri:
url: "http://www.myapi.com/create"
method: POST
return_content: yes
HEADER_Content-Type: "application/json"
body: "{{ lookup('file','create_body.json') | to_json }}"
I'm posting below what I ended up using for my usecase (Ansible 2.0). This is useful if your json payload is stated inline (and not in a file).
this task expects 204 as its success return code.
And since the body_format is json, the header is inferred automatically
- name: add user to virtual host
uri:
url: http://0.0.0.0:15672/api/permissions/{{ rabbit_virtualhost }}/{{ rabbit_username }}
method: PUT
user: "{{ rabbit_username }}"
password: "{{ rabbit_password }}"
return_content: yes
body: {"configure":".*","write":".*","read":".*"}
body_format: json
status_code: 204
it is basically equivalent to:
curl -i -u user:pass -H "content-type:application/json" -XPUT http://0.0.0.0:15672/api/permissions/my_vhost/my_user -d '{"configure":".*","write":".*","read":".*"}'

Resources