Ansible datetime timezone conversion - datetime

Is there a way to convert ansible date into a different timezone in a "debug" statement in my playbook ? I dont want a global timezone setting at the playbook level. I have this :
debug:
msg: "{{ '%Y-%m-%d %H:%M:%S' | strftime(ansible_date_time.epoch) }}"
This works fine but displays time in UTC. I need the time to be displayed in EDT without setting timezone at the global playbook level. How do I accomplish this ?

If you use a command task to run date rather than relying on the ansible_date_time variable, you can set the timezone via an environment variable. E.g. the following playbook:
- hosts: localhost
vars:
ansible_python_interpreter: /usr/bin/python
tasks:
- command: "date '+%Y-%m-%d %H:%M:%S'"
register: date_utc
environment:
TZ: UTC
- command: "date '+%Y-%m-%d %H:%M:%S'"
register: date_us_eastern
environment:
TZ: US/Eastern
- debug:
msg:
- "{{ date_utc.stdout }}"
- "{{ date_us_eastern.stdout }}"
Results in this output:
PLAY [localhost] *****************************************************************************
TASK [Gathering Facts] ***********************************************************************
ok: [localhost]
TASK [command] *******************************************************************************
changed: [localhost]
TASK [command] *******************************************************************************
changed: [localhost]
TASK [debug] *********************************************************************************
ok: [localhost] => {
"msg": [
"2020-05-12 15:21:05",
"2020-05-12 11:21:06"
]
}
PLAY RECAP ***********************************************************************************
localhost : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Related

Ensure a certain amount of time has elapsed between two tasks in ansible playbook, in real time

I will be notifying users that an event will happen in 15 minutes; I then perform tasks that take a variable amount of time which is less than 15 minutes, and I then need to wait the rest of the time and perform the said event at exactly 15 minutes from when I notified the users.
Can someone propose such a real-time timer in ansible? pause won't work, because it's static. Also, async doesn't work on a pause task, so we can't start a pause asynchronously with poll: 0, move on to other tasks, and then come back and ensure it has succeeded with async_status right before our waited-for task.
This is my best attempt, but the until conditional doesn't seem to be getting updated with the actual current time, because it never terminates:
- name: Ensure a certain amount of time has elapsed between two tasks
hosts: localhost
gather_facts: no
vars:
wait_time: 10
timer_delay_interval: 1
tasks:
- name: Debug start time
debug:
var: ansible_date_time
- name: Set current time
set_fact:
start_time: "{{ ansible_date_time.date }} {{ ansible_date_time.time }}"
- name: Other task
pause:
seconds: 2
- name: Timer
set_fact:
current_time: "{{ ansible_date_time.date }} {{ ansible_date_time.time }}"
until: ((current_time | to_datetime) - (start_time | to_datetime)).total_seconds() >= wait_time
retries: 1000000
delay: "{{ timer_delay_interval }}"
register: timer_task
- name: Waited for task
debug:
msg: |
The timer has completed with {{ timer_task.attempts }} attempts,
for a total of {{ timer_task.attempts*timer_delay_interval | int }} seconds.
The original wait time was {{ wait_time }}, which means that intervening
tasks took {{ wait_time - timer_task.attempts*timer_delay_interval | int }} seconds.
NOTE: The to_datetime filter requires datetimes to be formatted like %Y-%m-%d %H:%M:%S, which is why I'm formatting them that way.
There are more options.
Run tasks concurrently. Run the module wait_for asynchronously. Then, use async_status to wait for the remaining wait_time to elapse. The number of retries is the difference between wait_time and pause in 1 second delay to ensure the module will cover the remaining time. In practice, the number of retries will be smaller, of course. See comments below about offset
- name: Ensure a certain amount of time has elapsed between two tasks
hosts: localhost
gather_facts: false
vars:
wait_time: 10
pause: 5
offset: 2
tasks:
- debug:
msg: "start_time: {{ '%Y-%m-%d %H:%M:%S'|strftime }}"
- wait_for:
timeout: "{{ wait_time|int - offset|int }}"
async: 20
poll: 0
register: async_result
- pause:
seconds: "{{ pause }}"
- async_status:
jid: "{{ async_result.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: "{{ wait_time|int - pause|int }}"
delay: 1
- debug:
msg: "Something happened at {{ '%Y-%m-%d %H:%M:%S'|strftime }}"
gives (abridged)
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: 'start_time: 2022-05-12 09:43:11'
TASK [wait_for] ******************************************************************************
changed: [localhost]
TASK [pause] *********************************************************************************
Pausing for 5 seconds
(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
ok: [localhost]
TASK [async_status] **************************************************************************
FAILED - RETRYING: [localhost]: async_status (5 retries left).
FAILED - RETRYING: [localhost]: async_status (4 retries left).
ok: [localhost]
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: Something happened at 2022-05-12 09:43:21
The next option is the calculation of the remaining time
- name: Ensure a certain amount of time has elapsed between two tasks
hosts: localhost
gather_facts: false
vars:
wait_time: 10
pause: 5
offset: 2
tasks:
- set_fact:
start_time: "{{ '%Y-%m-%d %H:%M:%S'|strftime }}"
start_time_sec: "{{ '%s'|strftime }}"
- set_fact:
stop_time: "{{ '%Y-%m-%d %H:%M:%S'|strftime(start_time_sec|int + wait_time|int) }}"
stop_time_sec: "{{ start_time_sec|int + wait_time|int }}"
- debug:
msg: "start_time: {{ start_time }}"
- pause:
seconds: "{{ pause }}"
- set_fact:
wait_time: "{{ stop_time_sec|int - '%s'|strftime|int - offset|int }}"
- debug:
msg: |-
wait_time: {{ wait_time }}
when: debug|d(false)|bool
- wait_for:
timeout: "{{ wait_time|int }}"
- debug:
msg: "Something happened at {{ '%Y-%m-%d %H:%M:%S'|strftime }}"
gives (abridged)
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: 'start_time: 2022-05-12 09:55:08'
TASK [pause] *********************************************************************************
Pausing for 5 seconds
(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
ok: [localhost]
TASK [set_fact] ******************************************************************************
ok: [localhost]
TASK [debug] *********************************************************************************
skipping: [localhost]
TASK [wait_for] ******************************************************************************
ok: [localhost]
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: Something happened at 2022-05-12 09:55:18
Fit offset to your system.

When I am running playbook its showing Variable not defined

- hosts: switch
connection: network_cli
become_method: enable
gather_facts: no
vars_prompt:
- name: vlan_id
prompt: enter the vlan_id
private: no
vars:
cli:
username: admin
password: int123$%^
vlans:
100: "CORE"
200: "MONITORING"
300: "ACCESS"
400: "GUEST_WIFI"
ansible_buffer_read_timeout: 2
tasks:
- name: "creating the vlans"
ios_vlans:
config:
- vlan_id: "{{ vlan_id }}"
mtu: 700
state: active
shutdown: disabled
register: show_vlan
- debug:
var: show_vlan.stdout_lines
Output:
enter the vlan_id: 11
PLAY [switch] ****************************************************************************************************************************************************
TASK [creating the vlans] **************************************************************************************************************************************** changed: [172.16.1.252]
TASK [debug]
ok: [172.16.1.252] => show_vlan.stdout_lines: VARIABLE IS NOT DEFINED!
PLAY RECAP 172.16.1.252 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ios_vlans module does not have stdout_lines key in it's return values. Please check the documention here
so debug show_lan
- debug:
var: show_vlan

Ansible filter to extract specific keys from a dict into another dict

Using the nios lookup modules, I can get a list of dicts of records
- set_fact:
records: "{{ lookup('community.general.nios', 'record:a', filter={'name~': 'abc.com'}) }}"
This returns something like
- ref: record:a/someBase64:name/view
name: abc.com
ipv4addr: 1.2.3.4
view: default
- ref: record:a/someBase64:name/view
name: def.abc.com
ipv4addr: 1.2.3.5
view: default
- ref: record:a/someBase64:name/view
name: ghi.abc.com
ipv4addr: 1.2.3.6
view: default
I want to convert this into a dict of dicts of {name}: a: {ipv4addr}
abc.com:
a: 1.2.3.4
def.abc.com:
a: 1.2.3.5
ghi.abc.com:
a: 1.2.3.6
So that I can then run a similar lookup to get other record types (e.g. cname) and combine them into the same dict. The items2dict filter seems halfway there, but I want the added a: key underneath.
If you just wanted a dictionary that maps name to an ipv4 address, like:
{
"abc.com": "1.2.3.4",
...
}
You could use a simple json_query expression. Take a look at the
set_fact task in the following example:
- hosts: localhost
gather_facts: false
vars:
data:
- ref: record:a/someBase64:name/view
name: abc.com
ipv4addr: 1.2.3.4
view: default
- ref: record:a/someBase64:name/view
name: def.abc.com
ipv4addr: 1.2.3.5
view: default
- ref: record:a/someBase64:name/view
name: ghi.abc.com
ipv4addr: 1.2.3.6
view: default
tasks:
- set_fact:
name_map: "{{ dict(data|json_query('[].[name, ipv4addr]')) }}"
- debug:
var: name_map
Running that playbook will output:
PLAY [localhost] ***************************************************************
TASK [set_fact] ****************************************************************
ok: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
"name_map": {
"abc.com": "1.2.3.4",
"def.abc.com": "1.2.3.5",
"ghi.abc.com": "1.2.3.6"
}
}
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You could use a similar structure to extract other data (e.g. cname records). This would get you dictionary per type of data, rather than merging everything together in a single dictionary as you've requested, but this might end up being easier to work with.
To get exactly the structure you want, you can use set_fact in a loop, like this:
- hosts: localhost
vars:
data:
- ref: record:a/someBase64:name/view
name: abc.com
ipv4addr: 1.2.3.4
view: default
- ref: record:a/someBase64:name/view
name: def.abc.com
ipv4addr: 1.2.3.5
view: default
- ref: record:a/someBase64:name/view
name: ghi.abc.com
ipv4addr: 1.2.3.6
view: default
gather_facts: false
tasks:
- set_fact:
name_map: "{{ name_map|combine({item.name: {'a': item.ipv4addr}}) }}"
loop: "{{ data }}"
vars:
name_map: {}
- debug:
var: name_map
This will produce:
PLAY [localhost] ***************************************************************
TASK [set_fact] ****************************************************************
ok: [localhost] => (item={'ref': 'record:a/someBase64:name/view', 'name': 'abc.com', 'ipv4addr': '1.2.3.4', 'view': 'default'})
ok: [localhost] => (item={'ref': 'record:a/someBase64:name/view', 'name': 'def.abc.com', 'ipv4addr': '1.2.3.5', 'view': 'default'})
ok: [localhost] => (item={'ref': 'record:a/someBase64:name/view', 'name': 'ghi.abc.com', 'ipv4addr': '1.2.3.6', 'view': 'default'})
TASK [debug] *******************************************************************
ok: [localhost] => {
"name_map": {
"abc.com": {
"a": "1.2.3.4"
},
"def.abc.com": {
"a": "1.2.3.5"
},
"ghi.abc.com": {
"a": "1.2.3.6"
}
}
}
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

How to check if an encrypted variable is decrypted?

I have an Ansible encrypted variable. Now I'd like to be able to run my playbook even when I don't unlock the variable (with --ask-vault-pass) and just skip the tasks that depend on it. Ideally with a warning saying that the task was skipped.
Now when I run my playbook without --ask-vault-pass, it fails with an error:
fatal: [...]: FAILED! => {"changed": false, "msg": "AnsibleError: An unhandled exception occurred while templating '{{ (samba_passwords |
string | from_yaml)[samba_username] }}'. Error was a <class 'ansible.parsing.vault.AnsibleVaultError'>, original message: Attempting to decrypt bu
t no vault secrets found"}
Is there a way how to check in the when: clause that an encrypted variable is not decrypted and thus inaccessible?
Q: "Check if an encrypted variable is decrypted. Skip the tasks that depend on it. Ideally with a warning saying that the task was skipped."
A: For example, given the file with the variable
shell> cat vars-test.yml
test_var1: test var1
Encrypt the file
shell> ansible-vault encrypt vars-test.yml
New Vault password:
Confirm New Vault password:
Encryption successful
shell> cat vars-test.yml
$ANSIBLE_VAULT;1.1;AES256
61373230346437306135303463393166323063656561623863306333313837666561653466393835
3738666532303836376139613766343930346263633032330a323336643061373039613330653237
30666364376266396633613162626536383161306262613062373239343232663935376364383431
6335623366613834360a336531656537626662376166323766376433653232633139383636613963
64356632633863353534323636313231633866613635343962383463636565303032
Then the playbook
shell> cat pb.yml
- hosts: test_01
tasks:
- include_vars: vars-test.yml
ignore_errors: true
- set_fact:
test_var1: "{{ test_var1|default('default') }}"
- name: Execute tasks if test_var1 was decrypted
block:
- debug:
msg: Execute task1
- debug:
msg: Execute task2
when: test_var1 != 'default'
gives (abridged)
shell> ansible-playbook pb.yml --ask-vault-pass
TASK [include_vars] ****
ok: [test_01]
TASK [set_fact] ****
ok: [test_01]
TASK [debug] ****
ok: [test_01] =>
msg: Execute task1
TASK [debug] ****
ok: [test_01] =>
msg: Execute task2
If you don't provide the command with the password the playbook gives (abridged)
shell> ansible-playbook pb.yml
PLAY [test_01] ****
TASK [include_vars] ****
fatal: [test_01]: FAILED! => changed=false
ansible_facts: {}
ansible_included_var_files: []
message: Attempting to decrypt but no vault secrets found
...ignoring
TASK [set_fact] ****
ok: [test_01]
TASK [debug] ****
skipping: [test_01]
TASK [debug] ****
skipping: [test_01]
I've researched but I haven't found anything to do that. The easy way to solve this case would be used ignore_errors: yes in the task.

How to define an Ansible playbook environment with passed in environment dictionary

My problem is that I'm unable to set the environment for the entire playbook by passing in a dict to be set as the environment. Is that possible?
For example, here is my sample ansible playbook:
- hosts: localhost
vars:
env_vars: "{{ PLAY_ENVS }}"
environment: "{{ env_vars }}"
tasks:
- name: Here is what you passed in
debug: msg="env_vars == {{ env_vars }}"
- name: What is FAKE_ENV
debug: msg="FAKE_ENV == {{ lookup('env', 'FAKE_ENV') }}"
And I'm passing the command:
/bin/ansible-playbook sample_playbook.yml --extra-vars '{PLAY_ENVS: {"FAKE_ENV":"/path/to/fake/destination"}}'
The response I'm getting is the following:
PLAY [localhost] ***************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [Here is what you passed in] **********************************************
ok: [localhost] => {
"msg": "env_vars == {u'FAKE_ENV': u'/path/to/fake/destination'}"
}
TASK [What is FAKE_ENV] ********************************************************
ok: [localhost] => {
"msg": "FAKE_ENV == "
}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
As you can see 'FAKE_ENV' is not being set in the environment. What am I doing wrong?
Lookups in Ansible are executed in a context of parent ansible process.
You should check your environment with a spawned process, like this:
- hosts: localhost
vars:
env_vars:
FAKE_ENV: foobar
environment: "{{ env_vars }}"
tasks:
- name: Test with spawned process
shell: echo $FAKE_ENV
And get expected result: "stdout": "foobar",

Resources