I have a problem with a role to create user in jenkins with credentials plugin using private key in body.
I can get the crumb however when I make the request to create the credential I get the invalid crumb error.
My role:
- name: Get Jenkins Crumb
uri:
url: "{{ master_url }}/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,\":\",//crumb)"
user: '{{ master_username }}'
password: "my_token"
method: GET
return_content: yes
force_basic_auth: yes
body_format: form-urlencoded
register: crumb
- debug:
msg: "{{ crumb.content.split(':')[1]}}"
- name: Add credential to Node
uri:
url: "{{ master_url }}/credentials/store/system/domain/_/createCredentials"
user: "{{ master_username }}"
password: "my_token"
method: POST
status_code: 302
body_format: form-urlencoded
headers:
Jenkins-Crumb: "{{ crumb.content.split(':')[1]}}"
Cookie: "{{ crumb.set_cookie }}"
body: |
json={
"": "0",
"credentials": {
"scope": "GLOBAL",
"id": "jenkins_linux_slave1_auth",
"username": "jenkins",
"password": "jenkins",
"privateKeySource": {
"stapler-class": "com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey$DirectEntryPrivateKeySource",
"privateKey": "{{ private_key_file['content'] | b64decode | to_json}}"
},
"description": "Jenkins Linux Slave1 Authentication",
"stapler-class": "com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey"
}
}
Out \ Error:
TASK [jenkins : Get Jenkins Crumb] *********************************************
ok: [protheus]
TASK [jenkins : debug] *********************************************************
ok: [protheus] => {
"msg": "f87df3f83a7ecfb5f5b2e7af0c0beb218dd239ec460fba832ff36f0a90e42287"
}
TASK [jenkins : Add credential to Node] ****************************************
fatal: [protheus]: FAILED! => {"cache_control": "must-revalidate,no-cache,no-store", "changed": false, "connection": "close", "content": "<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\n<title>Error 403 No valid crumb was included in the request</title>\n</head>\n<body><h2>HTTP ERROR 403 No valid crumb was included in the request</h2>\n<table>\n<tr><th>URI:</th><td>/credentials/store/system/domain/_/createCredentials</td></tr>\n<tr><th>STATUS:</th><td>403</td></tr>\n<tr><th>MESSAGE:</th><td>No valid crumb was included in the request</td></tr>\n<tr><th>SERVLET:</th><td>Stapler</td></tr>\n</table>\n<hr>Powered by Jetty:// 9.4.27.v20200227<hr/>\n\n</body>\n</html>\n", "content_length": "593", "content_type": "text/html;charset=iso-8859-1", "elapsed": 0, "msg": "Status code was 403 and not [302]: HTTP Error 403: Forbidden", "redirected": false, "server": "Jetty(9.4.27.v20200227)", "status": 403, "url": "http://192.168.0.120:9080/credentials/store/system/domain/_/createCredentials", "x_content_type_options": "nosniff"}
PLAY RECAP *********************************************************************
protheus : ok=9 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Ansible failed to complete successfully. Any error output should be
visible above. Please fix these errors and try again.
Does anyone know what's wrong?
Thks!
Since you're not calling the API, you can remove the Jenkins-Crumb header and use the username and the username Token.
Also add the option force_basic_auth: true
So your task will be like this:
- name: Add credential to Node
uri:
url: "{{ master_url }}/credentials/store/system/domain/_/createCredentials"
user: "{{ master_username }}"
password: "my_token"
method: POST
status_code: 302
body_format: form-urlencoded
force_basic_auth: true
body: |
json={
"": "0",
"credentials": {
"scope": "GLOBAL",
"id": "jenkins_linux_slave1_auth",
"username": "jenkins",
"password": "jenkins",
"privateKeySource": {
"stapler-class": "com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey$DirectEntryPrivateKeySource",
"privateKey": "{{ private_key_file['content'] | b64decode | to_json}}"
},
"description": "Jenkins Linux Slave1 Authentication",
"stapler-class": "com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey"
}
}
Related
See below first call which works. This is an example of how I create a template on my system. Notice I have to use {% raw %} ... {% endraw %} so ansible doesn't try to interpret the variables in my template.
- name: Create a template : OK
uri:
url: https://{{ ip }}/api/v1/templates
method: POST
headers:
Authorization: "{{ token }}"
Content-Type: application/json
body: |
{
"name": "{{ template_name }}",
"type": "v1",
"description": "{{ template_name }}",
"content": "template: | {% raw %}\n {\n \"class\": \"ABC\",\n \"param\": {{param1::integer}}\n }{% endraw %}"
}
body_format: json
timeout: 60
status_code: 202
validate_certs: false
register: json_response
Output OK:
ok: [notahost] => {
"invocation": {
"module_args": {
"body": {
"content": "template: | \n {\n \"class\": \"ABC\",\n \"param\": {{param1::integer}}\n }",
"description": "test1",
"name": "test1",
"type": "v1"
},
"body_format": "json",
Now, I am trying to move the content of the template outside in a file called template1.j2.
template1.j2:
{% raw %}
{
"class": "ABC",
"param": {{param1::integer}}
}
{% endraw %}
I insert the template template1.j2 into my JSON body (noticed I added the {% raw %} ... {% endraw %} inside the template).
- name: Create a template NOK
uri:
url: https://{{ ip }}/api/v1/templates
method: POST
headers:
Authorization: "{{ token }}"
Content-Type: application/json
body: |
{
"name": "{{ template_name }}",
"type": "v1",
"description": "{{ template_name }}",
"content": "template: | {{ lookup('file','template1.j2') }}"
}
body_format: json
timeout: 60
status_code: 202
validate_certs: false
register: json_response
Output NOK:
fatal: [notahost]: FAILED! => {
"content": "{\"message\":\"request body has an error: failed to decode request body: invalid character '\\\\n' in string literal\"}\n",
"invocation": {
"module_args": {
"body": "{\n \"name\": \"test2\",\n \"type\": \"v1\",\n \"description\": \"test2\",\n \"content\": \"template: | \n{\n \"class\": \"ABC\",\n \"param\": {{param1::integer}}\n} \n\"\n}\n",
"body_format": "json",
For some reason, it looks like the way I am doing this doesn't work, ansible still try to interpret the variable in my template at creation.
Any idea on how to get his work with my template outside the ansible task?
PS: I have tried to load the template file using the shell ansible module and that did not help.
Thanks & Regards,
Romain
Use lookup 'file' instead of 'template', e.g. the template (that you actually don't want to use as a template in this task)
shell> cat template1.j2
param: {{param1.integer}}
and the play
- hosts: localhost
vars:
param1:
integer: 99
tasks:
- debug:
msg: |
{{ lookup('template', 'template1.j2') }}
{{ lookup('file', 'template1.j2') }}
gives
msg: |-
param: 99
param: {{param1.integer}}
Given the template
shell> cat template1.j2
{
"class": "ABC",
"param": {{param1::integer}}
}
The play below shows how to create a body with and without the template
- hosts: localhost
vars:
template_name: test1
body1: |
{
"name": "{{ template_name }}",
"type": "v1",
"description": "{{ template_name }}",
"content": "template: | {% raw %}\n {\n \"class\": \"ABC\",\n \"param\": {{param1::integer}}\n }{% endraw %}"
}
body2:
{
"name": "{{ template_name }}",
"type": "v1",
"description": "{{ template_name }}",
"content": "template: | \n{{ lookup('file','template1.j2') }}"
}
tasks:
- debug:
var: body1|type_debug
- debug:
var: body1
- debug:
var: body2|type_debug
- debug:
var: body2
gives
ok: [localhost] =>
body1|type_debug: dict
ok: [localhost] =>
body1:
content: |-
template: |
{
"class": "ABC",
"param": {{param1::integer}}
}
description: test1
name: test1
type: v1
ok: [localhost] =>
body2|type_debug: dict
ok: [localhost] =>
body2:
content: |-
template: |
{
"class": "ABC",
"param": {{param1::integer}}
}
description: test1
name: test1
type: v1
Question
I need to get the ID from the GET because it's needed in the URL in the PUT task to edit a specific "input" entry. I'm using the Ansible URI to talk to a REST API to manage this.
playbook
*host_vars/host.yml
*
---
inputs:
- title: "test_input_api"
type: "org.graylog2.inputs.syslog.udp.SyslogUDPInput"
global: false
configuration:
allow_override_date: false
bind_address: "0.0.0.0"
expand_structured_data: false
force_rdns: false
number_worker_threads: 8
override_source: null
port: 5999
recv_buffer_size: null
store_full_message: true
- title: "test_input_api_2"
type: "org.graylog2.inputs.syslog.udp.SyslogUDPInput"
global: false
configuration:
allow_override_date: false
bind_address: "0.0.0.0"
expand_structured_data: false
force_rdns: false
number_worker_threads: 8
override_source: null
port: 5998
recv_buffer_size: null
store_full_message: true
playbook.yml
---
- name: Configure system
hosts: graylog
connection: local
gather_facts: no
roles:
- graylog/inputs
roles/graylog/inputs/tasks/main.yml
---
- include_tasks: get_inputs.yml
- include_tasks: put_inputs.yml
roles/graylog/inputs/tasks/get_inputs.yml
---
- name: "API GET System Inputs"
uri:
url: http://{{ ansible_host }}:9000/api/system/inputs
url_username : "{{ system.users.triple_admin.api_token }}"
url_password: token
method: GET
return_content: yes
register: get_graylog_inputs
- name: Set Fact
set_fact:
get_input_id: "{{ get_graylog_inputs.content | from_json | json_query('inputs[?title == `{}`] | [0].id '.format(input.title)) }}"
loop: "{{ inputs }}"
loop_control:
loop_var: input
The registered var from the get show's the following
{
"json": {
"inputs": [
{
"attributes": {
"allow_override_date": "False",
"bind_address": "0.0.0.0",
"expand_structured_data": "False",
"force_rdns": "False",
"number_worker_threads": 8,
"override_source": "",
"port": 5999,
"recv_buffer_size": "",
"store_full_message": "True"
},
"content_pack": null,
"created_at": "2021-07-30T15:21:47.590Z",
"creator_user_id": "triple_admin",
"global": false,
"id": "6104170beca15547502665d6",
"name": "Syslog UDP",
"node": "ba52ad48-0b13-419d-b957-d47d8911b413",
"static_fields": {},
"title": "test_input_api",
"type": "org.graylog2.inputs.syslog.udp.SyslogUDPInput"
},
roles/graylog/inputs/tasks/put_inputs.yml
---
- name: "API PUT System Inputs"
uri:
url: http://{{ ansible_host }}:9000/api/system/inputs/{{ get_input_id }}
url_username : "{{ system.users.triple_admin.api_token }}"
url_password: token
headers:
X-Requested-By: X-Ansible
method: PUT
body_format: json
body: "{{ lookup('template', 'templates/post_template.j2') }}"
status_code: 201
return_content: yes
loop: "{{ inputs }}"
loop_control:
loop_var: input
"ansible_facts": {
"get_input_id": "61015085eca1554750236084",
"get_input_titles": "test_input_api"
},
"ansible_facts": {
"get_input_id": "610282d0eca155475024ac91",
"get_input_titles": "test_input_api_2"
Results of running the play
loop 1 - this needs to be matched to the title and therefor get id "61015085eca1554750236084"
"title": "test_input_api",
"url": "http://192.168.21.82:9000/api/system/inputs/610282d0eca155475024ac91",
loop 2
"title": "test_input_api_2",
"url": "http://192.168.21.82:9000/api/system/inputs/610282d0eca155475024ac91",
All help is welcome !
(not related) You don't need to json_decode the result get_graylog_inputs.content. If the server on the over side sends the correct Content-type: application/json header, you should have a get_graylog_inputs.json entry containing the already decoded json result.
You don't need to loop twice. Remove the set_fact loop (which is not correct anyway) in your first file and use the value from your register directly in the second loop.
You did not show any example of your input data so I have to guess a bit here from your jmespath expression... but you basically don't need json_query at all and can stick to generic core ansible filters.
Here is how I see the solution in the second file once you cleaned-up the first:
---
- name: "API PUT System Inputs"
vars:
get_input_id: "{{ get_graylog_inputs.json.inputs | selectattr('title', '==', input.title) | map(attribute='id') | first }}"
uri:
url: http://{{ ansible_host }}:9000/api/system/inputs/{{ get_input_id }}
url_username : "{{ system.users.triple_admin.api_token }}"
url_password: token
headers:
X-Requested-By: X-Ansible
method: PUT
body_format: json
body: "{{ lookup('template', 'templates/post_template.j2') }}"
status_code: 201
return_content: yes
loop: "{{ inputs }}"
loop_control:
loop_var: input
You will probably have to debug and tune the expression to get the input id as I could not do it myself against an example data structure.
ill have a role playbook which get json from gitlab
---
- name: Make an API call
uri:
method: GET
url: "https://gitlab.example.com/api/v4/projects/***/repository/files/Dev/raw?ref=master"
headers:
PRIVATE-TOKEN: **************
register: json_var
- name: show json
debug:
msg: "{{json_var}}"
- name: test
debug:
var: json_var.json.plannedrelease
register: release
- name: debug
debug:
msg: "{{ release }}"
but cant get json value to variable, i need only version "1.0" in variable release (from "plannedrelease" : "1.0"), how can i filter it?
Playbook output is:
PLAY [127.0.0.1] ***************************************************************
TASK [Gathering Facts] *********************************************************
ok: [127.0.0.1]
TASK [get_contour_version : Make an API call] **********************************
ok: [127.0.0.1]
TASK [get_contour_version : show json] *****************************************
ok: [127.0.0.1] => {
"msg": {
"cache_control": "max-age=0, private, must-revalidate, no-store, no-cache",
"changed": false,
"connection": "close",
"content_disposition": "inline; filename=\"Dev\"; filename*=UTF-8''Dev",
"content_length": "402",
"content_type": "text/plain; charset=utf-8",
"cookies": {},
"cookies_string": "",
"date": "Tue, 19 Jan 2021 19:42:33 GMT",
"elapsed": 0,
"expires": "Fri, 01 Jan 1990 00:00:00 GMT",
"failed": false,
"json": {
"Created": "12/06/2020 10:11",
"Key": "123",
"Updated": "01/12/2020 11:51",
"contour": "Dev",
"plannedrelease": "1.0",
"…
TASK [get_contour_version : test] **********************************************
ok: [127.0.0.1] => {
"json_var.json.plannedrelease": "1.0"
}
TASK [get_contour_version : debug] *********************************************
ok: [127.0.0.1] => {
"msg": {
"changed": false,
"failed": false,
"json_var.json.plannedrelease": "1.0"
}
}
PLAY RECAP *********************************************************************
127.0.0.1 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
probably i used wrong method for filtering
Have you tried https://docs.ansible.com/ansible/latest/collections/ansible/builtin/set_fact_module.html ? Reading the documentation the following might work:
- name: Save release var
set_fact:
release: "{{ json_var.json.plannedrelease }}"
I am new to Ansible. I am trying to fetch some API info from a centralized server in the json format, parse the json and set "certificate" to "yes" for each and every server and make a PUT operation back. GET and PUT operations in my code are working fine.
I am not able to find correct syntax to parse listofservers.json.
What is the syntax to display a specific json element??
Can you help me on how to parse the json to change "Certificate" value to Yes for all the servers?
My script
- hosts: serverlist
vars_prompt:
- name: "USER"
prompt: "Enter Admin Username"
private: no
- name: "PASS"
prompt: "Enter Admin Password"
private: yes
tasks:
- name: "Get the current values of all Servers"
delegate_to: localhost
register: listofservers
check_mode: no
uri:
validate_certs: "no"
user: "{{USER}}"
password: "{{PASS}}"
url: "https://console.exmaple.com/adminapi/serverlist"
method: "GET"
body_format: "json"
return_content: "no"
status_code: "200"
- name: Print json dictionary
debug:
var: listofservers.json
Output
TASK [Print json dictionary] *****************************************************************************************
ok: [centalServer.example.com] =>
{
"listofservers.json": {
"serverlist": [
{
"id": 1,
"servername": "redhat.example.com",
"apachestatus": "running",
"certificate": "no"
},
{
"id": 2,
"servername": "solaris.example.com",
"apachestatus": "down",
"certificate": "yes"
} ] } }
Q: "Change "certificate" value to "yes" for all servers."
A: The task below does the job
- set_fact:
listofservers: "{{ {'json':
{'serverlist': serverlist}} }}"
vars:
serverlist: "{{ listofservers.json.serverlist|
map('combine', {'certificate': 'yes'})|
list }}"
- debug:
var: listofservers.json
give
{
"listofservers.json": {
"serverlist": [
{
"apachestatus": "running",
"certificate": "yes",
"id": 1,
"servername": "redhat.example.com"
},
{
"apachestatus": "down",
"certificate": "yes",
"id": 2,
"servername": "solaris.example.com"
}
]
}
}
Q: "What is the syntax to display a specific JSON element?"
A: There are two main options 1) Ansible filters and 2) json_query, and many variations which depend on the structure of the data and requested query
For example, display first servername where id is equal to 1
- debug:
msg: "{{ listofservers.json.serverlist|
selectattr('id', 'eq', 1)|
map(attribute='servername')|
first }}"
gives
"msg": "redhat.example.com"
The same result will give json_query below
- debug:
msg: "{{ listofservers.json.serverlist|
json_query('[?id == `1`].servername')|
first }}"
I have a task which performs a GET request to a page. The response's body is a JSON like the following.
{
"ips": [
{
"organization": "1233124121",
"reverse": null,
"id": "1321411312",
"server": {
"id": "1321411",
"name": "name1"
},
"address": "x.x.x.x"
},
{
"organization": "2398479823",
"reverse": null,
"id": "2418209841",
"server": {
"id": "234979823",
"name": "name2"
},
"address": "x.x.x.x"
}
]
}
I want to extract the fields id and address, and tried (for id field):
tasks:
- name: get request
uri:
url: "https://myurl.com/ips"
method: GET
return_content: yes
status_code: 200
headers:
Content-Type: "application/json"
X-Auth-Token: "0010101010"
body_format: json
register: json_response
- name: copy ip_json content into a file
copy: content={{json_response.json.ips.id}} dest="/dest_path/json_response.txt"
but I get this error:
the field 'args' has an invalid value, which appears to include a variable
that is undefined. The error was: 'list object' has no attribute 'id'..
Where is the problem?
The error was: 'list object' has no attribute 'id'
json_response.json.ips is a list.
You either need to choose one element (first?): json_response.json.ips[0].id.
Or process this list for example with map or json_query filters if you need all ids.
Ansible command to copy to file:
copy:
content: "{{ output.stdout[0] }}"
dest: "~/ansible/local/facts/{{ inventory_hostname }}.txt"