This blog starts with one patching automation workshop. The customer’s procedure is that the end user raises a patching request. The request includes the server list, patch list, and specified time for this patching task. The customer not only wants to automate the patching task itself but also wants the playbook to auto-schedule the patching task at a given time within the request.
But Ansible Automation Platform (AAP) is suitable to schedule the patching task in the fix time window, like the cron job in Linux. The scheduler tool is more suitable for this scenario. This blog is talking about how to use AAP to handle “scheduler” tasks.
In AAP web console, schedules can be created from a template, project or inventory source but not directly on the main Schedules screen itself. It looks like this.
This is good for static schedules that require manual configuration but not for dynamic schedules.
Recently, I came across a patch automation use case that caught my attention. The client wants scheduling to be automated. They want a playbook to execute the patch at a time specified inside their change request. The original schedule function from AAP cannot meet this requirement.
In order to make scheduling dynamic, I created 2 templates.
I use the REST API to add/delete schedules.
Below is my POC on dynamic scheduling
patch.yaml
This is to verify the content of the variables ‘report_file’ and ‘delete_schedule’, which should come from the schedule template.
This is is to get AAP token for following REST API authentication.
This is to collect all schedules’ information.
This is to figure out the schedule ID based on the given schedule name.
This is to delete the schedule based on the schedule ID.
# cat patch.yaml
- name: Patch
hosts: all
vars:
app_ip: 192.168.122.31
user_id: 1
job_template_id: 9
tasks:
- name: Report File
debug:
msg: "{{ report_file | default('Hello World!') }}"
- name: Schedule Name
debug:
msg: "{{ delete_schedule | default('Hello World!') }}"
- name: retrieve the AAP Token
uri:
url: https://{{ app_ip }}/api/v2/users/{{ user_id }}/personal_tokens/
user: admin
password: redhat
method: POST
force_basic_auth: yes
headers:
Content-Type: application/json
return_content: yes
validate_certs: no
status_code: [200, 201]
body_format: json
body:
extra_vars:
description: "Tower CLI"
application: null
scope: write
register: result
- set_fact:
token: "{{ result['json']['token'] }}"
- name: Collect all schedules' info
uri:
url: https://{{ app_ip }}/api/v2/schedules/
method: GET
headers:
Authorization: "Bearer {{ token }}"
return_content: yes
validate_certs: no
status_code: [200, 201]
body_format: json
register: result
- name: Extract schedule ID
register: result_01
args:
stdin: |
for schedule in {{ result.json.results }}:
if schedule['name'] == "{{ delete_schedule }}":
print(schedule['id'])
command: /usr/bin/python3
- name: Delete used schedule
uri:
url: https://{{ app_ip }}/api/v2/schedules/{{ result_01.stdout }}/
method: DELETE
headers:
Authorization: "Bearer {{ token }}"
return_content: yes
validate_certs: no
status_code: [200, 201, 204]
body_format: json
body:
extra_vars:
id: "{{ result_01.stdout }}"
register: result
patch
patch
– ParametersIn order to accept the parameters from adding a schedule REST API call, the parameters need to be defined in the Survey.
This is to use Python code to convert SGT time to UTC time.
This is to get AAP token for following REST API authentication.
This is to generate a unique schedule name. The playbook “scheduler.yaml” passes this parameter to the playbook “patch.yaml”. The playbook “patch.yaml” will delete the schedule based on this unique name once patching is finished.
This is to add a schedule to the job template patch.yaml, and pass the below parameters to the job template patch.yaml.
# cat scheduler.yaml
---
- hosts: localhost
connection: local
become: true
gather_facts: false
vars:
app_ip: 192.168.122.31
user_id: 1
job_template_id: 9
tasks:
- debug:
msg: "Singapore Time: {{ year }}-{{ month }}-{{ date }}T{{ hour }}:{{ min }}:{{ sec }}"
# it is easy to do time conversion in the python
# Below is to directly use python code for time conversion
# It requires tzdata packge and environment TZ is SGT
- name: python code converts SGT to UTC
register: results
args:
stdin: |
from datetime import datetime
import pytz
dt_str = "{{ year }}{{ month }}{{ date }}T{{ hour }}{{ min }}{{ sec }}"
format = "%Y%m%dT%H%M%S"
local_dt = datetime.strptime(dt_str, format)
dt_utc = local_dt.astimezone(pytz.UTC)
format = "%Y-%m-%dT%H:%M:%SZ"
dt_utc_str = dt_utc.strftime(format)
print(dt_utc_str)
command: /usr/bin/python3
- debug:
msg: "UTC Time: {{ results.stdout }}"
- set_fact:
utc_date: "{{ results.stdout }}"
- name: retrieve the Ansible Tower Token
uri:
url: https://{{ app_ip }}/api/v2/users/{{ user_id }}/personal_tokens/
user: admin
password: redhat
method: POST
force_basic_auth: yes
headers:
Content-Type: application/json
return_content: yes
validate_certs: no
status_code: [200, 201]
body_format: json
body:
extra_vars:
description: "Tower CLI"
application: null
scope: write
register: result
# the schedule name has to be unique
- name: Create unique schedule name
set_fact:
token: "{{ result['json']['token'] }}"
sgt_date: "{{ year }}{{ month }}{{ date }}T{{ hour }}{{ min }}{{ sec }}"
schedule_name: "schedule-{{ 99999999 | random | to_uuid }}"
# the schdule can have it's own extra_data pasing the extr varaibles' value
# to the job tempalte patch
# The REST API needs boht SGT and UTC times :-)
- name: Add the schdule to the job template patch.yaml
uri:
url: https://{{ app_ip }}/api/v2/job_templates/{{ job_template_id }}/schedules/
method: POST
headers:
Authorization: "Bearer {{ token }}"
return_content: yes
validate_certs: no
status_code: [200, 201]
body_format: json
body:
rrule: "DTSTART;TZID=Asia/Singapore:{{ sgt_date }} RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY"
name: "{{ schedule_name }}"
description: ""
extra_data:
report_file: "{{ report_fie_location }}"
delete_schedule: "{{ schedule_name }}"
inventory: null
scm_branch: ""
job_type: null
job_tags: ""
skip_tags: ""
limit: ""
diff_mode: null
verbosity: null
enabled: true
unified_job_template: 9
dtstart: "{{ utc_date }}"
dtend: "{{ utc_date }}"
next_run: null
timezone: "Asia/Singapore"
until: ""
register: result
“python code converts SGT to UTC” task directly calls the python code from the date conversion. Please refer to https://www.techbeatly.com/python-inside-ansible-playbook/
This works well when I run it on the local VM. But it does not work as expected we I put it into the container. And I realize that the customization has to be done on the container images I used.
I did the below customization for the container image
tzdata
in the image. Install the rpm, tzdata
.# cat bindep.txt
tzdata [platform:rpm]
Set TimeZone from the environment file
# cat execution-environment.yml
version: 1
ansible_config: 'ansible.cfg'
dependencies:
system: bindep.txt
additional_build_steps:
prepend: |
RUN cp /usr/share/zoneinfo/Asia/Singapore /etc/localtime && echo "Asia/Singapore" >/etc/timezone
# cat bindep.txt
tzdata [platform:rpm]
Clean up the ansible.cfg
# > ./ansible.cfg
Build the new image
# podman login -u=admin -p=redhat aap-hub-01.example.com --tls-verify=false
Login Succeeded!
# pip3 install ansible-builder
# ansible-builder build -t utc_ee_image_sgt
Running command:
podman build -f context/Containerfile -t utc_ee_image_sgt context
# podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/utc_ee_image_sgt latest f5bb2d1bd3ab 5 minutes ago 994 MB
# podman tag f5bb2d1bd3ab aap-hub-01.example.com/utc_ee_image_sgt
# podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/utc_ee_image latest f5bb2d1bd3ab 11 minutes ago 994 MB
aap-hub-01.example.com/utc_ee_image latest f5bb2d1bd3ab 11 minutes ago 994 MB
# podman push aap-hub-01.example.com/utc_ee_image_sgt:latest --tls-verify=false
# podman run -it --name test-03 --entrypoint /bin/bash localhost/utc_ee_image_sgt:latest
bash-4.4# date
Sat Aug 6 20:51:14 +08 2022
bash-4.4# python3
Python 3.8.13 (default, Jun 24 2022, 15:27:57)
[GCC 8.5.0 20210514 (Red Hat 8.5.0-13)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from datetime import datetime
>>> import pytz
>>> dt_str = "20220806T145000"
>>> format = "%Y%m%dT%H%M%S"
>>> local_dt = datetime.strptime(dt_str, format)
>>> print(local_dt)
2022-08-06 14:50:00
>>> dt_utc = local_dt.astimezone(pytz.UTC)
>>> dt_utc = local_dt.astimezone(pytz.UTC)
>>> format = "%Y-%m-%dT%H:%M:%SZ"
>>> dt_utc_str = dt_utc.strftime(format)
>>> print(dt_utc_str)
2022-08-06T06:50:00Z
>>> exit()
scheduler
schedule
– SurveyThe value of “report_fie_location” will be passed to the playbook “patch.yaml”. The other parameters will be consumed by the playbook “schedule.yaml”.
scheduler
patch
, which received from scheduler
Schedule Name: schedule-0d70dc58-d9d7-5adc-98d8-c7d7a4e006ff
The entire schedule (adding schedules, executing scheduled tasks and deleting schedule) cycle completes successfully. The input value can pass successfully from the beginning into the embeded “patch.yaml” playbook. The above POC codes can be used for real schedule tasks.
Disclaimer:
The views expressed and the content shared in all published articles on this website are solely those of the respective authors, and they do not necessarily reflect the views of the author’s employer or the techbeatly platform. We strive to ensure the accuracy and validity of the content published on our website. However, we cannot guarantee the absolute correctness or completeness of the information provided. It is the responsibility of the readers and users of this website to verify the accuracy and appropriateness of any information or opinions expressed within the articles. If you come across any content that you believe to be incorrect or invalid, please contact us immediately so that we can address the issue promptly.
Tags: Ansible · ansible playbook
Jin Zhang
I’m Jin, Red Hat ASEAN Senior Platform Consultant. My primary focus is Ansible Automation (Infrastructure as Code), OpenShift, and OpenStack.
This site uses Akismet to reduce spam. Learn how your comment data is processed.2 Responses
Leave a Reply Cancel reply
Good Article! Is there a way to get the current schedule name or id inside the playbook?
The schedule name is inside the playbook. You can use the “debug” module to explicitly show it.