See the example of a complete playbook below. The idea is to split the play into two parts. In the first block get the disk info and write it to a file at the controller. In the second block create the partition. The reason is to make the code safer, more robust, and more flexible.
- The first part is safe. There are no changes on the remote hosts. You can run it once on all hosts and display the debug to check the details. For example, let's test it on a 4GB flash disk
shell> parted -l
Model: Generic Flash Disk (scsi)
Disk /dev/sdb: 4089MB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:
Number Start End Size Type File system Flags
1 512B 34.1MB 34.1MB primary fat32 esp
Set the variables
disk: sdb
part_size: 10
unit: MB
and run the playbook
shell> ansible-playbook pb.yml -e debug=true -e get_info=true
PLAY [localhost] *****************************************************************************
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: |-
disk: /dev/sdb
part_unit: MB
cache_update: False
cache_path: /tmp/localhost-sdb-part_info.json
TASK [get partition info] ********************************************************************
ok: [localhost]
TASK [debug] *********************************************************************************
ok: [localhost] =>
part_info:
changed: false
disk:
dev: /dev/sdb
logical_block: 512
model: Generic Flash Disk
physical_block: 512
size: 4089.0
table: msdos
unit: mb
failed: false
partitions:
- begin: 0.0
end: 34.1
flags:
- esp
fstype: fat32
name: ''
num: 1
size: 34.1
unit: mb
script: unit 'MB' print
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: |-
part_size: 10
last_number: 1
last_end: 34.1
next_number: 2
next_part_start: 34.1MB
next_part_end: 44.1MB
TASK [copy] **********************************************************************************
changed: [localhost]
TASK [set_fact] ******************************************************************************
skipping: [localhost]
TASK [debug] *********************************************************************************
skipping: [localhost]
TASK [debug] *********************************************************************************
skipping: [localhost]
TASK [create new partition] ******************************************************************
skipping: [localhost]
TASK [debug] *********************************************************************************
skipping: [localhost]
PLAY RECAP ***********************************************************************************
localhost: ok=5 changed=1 unreachable=0 failed=0 skipped=5 rescued=0 ignored=0
You can see that the task copy is changed. This means the file was written on a controller. Take a look
shell> cat /tmp/localhost-sdb-part_info.json
{"changed": false, "disk": {"dev": "/dev/sdb", "size": 4089.0, "unit": "mb", "table": "msdos", "model": "Generic Flash Disk", "logical_block": 512, "physical_block": 512}, "partitions": [{"num": 1, "begin": 0.0, "end": 34.1, "size": 34.1, "fstype": "fat32", "name": "", "flags": ["esp"], "unit": "mb"}], "script": "unit 'MB' print", "failed": false}
The copy parameter force is set to false. This means the file won't be replaced. The file will only be transferred if the destination does not exist. This is what you want to make the play idempotent. You don't want to replace the cache with the parted info after you created new partition. But, if you for whatever reason want to update the cache set cache_update=true
and run the playbook
shell> ansible-playbook pb.yml -e debug=true -e get_info=true -e cache_update=true
- New partition will be created in the second part. Run it in
--check
mode first and see the debug output
shell> ansible-playbook pb6.yml -e debug=true -e create_part=true -C
gives (abridged)
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: |-
part_size: 10
last_number: 1
last_end: 34.1
unit: MB
next_number: 2
next_part_start: 34.1MB
next_part_end: 44.1MB
This is correct. We want to create partition number 2 of the size 10MB. Let's create it
shell> ansible-playbook pb.yml -e debug=true -e create_part=true
PLAY [localhost] ********************************************************************************
TASK [debug] ************************************************************************************
skipping: [localhost]
TASK [get partition info] ***********************************************************************
skipping: [localhost]
TASK [debug] ************************************************************************************
skipping: [localhost]
TASK [debug] ************************************************************************************
skipping: [localhost]
TASK [copy] *************************************************************************************
skipping: [localhost]
TASK [set_fact] *********************************************************************************
ok: [localhost]
TASK [debug] ************************************************************************************
ok: [localhost] =>
part_info:
changed: false
disk:
dev: /dev/sdb
logical_block: 512
model: Generic Flash Disk
physical_block: 512
size: 4089.0
table: msdos
unit: mb
failed: false
partitions:
- begin: 0.0
end: 34.1
flags:
- esp
fstype: fat32
name: ''
num: 1
size: 34.1
unit: mb
script: unit 'MB' print
TASK [debug] ************************************************************************************
ok: [localhost] =>
msg: |-
part_size: 10
last_number: 1
last_end: 34.1
unit: MB
next_number: 2
next_part_start: 34.1MB
next_part_end: 44.1MB
TASK [create new partition] *********************************************************************
changed: [localhost]
TASK [debug] ************************************************************************************
ok: [localhost] =>
result:
changed: true
disk:
dev: /dev/sdb
logical_block: 512
model: Generic Flash Disk
physical_block: 512
size: 4089.0
table: msdos
unit: mb
failed: false
partitions:
- begin: 0.0
end: 34.1
flags:
- esp
fstype: fat32
name: ''
num: 1
size: 34.1
unit: mb
- begin: 34.1
end: 44.1
flags: []
fstype: ''
name: ''
num: 2
size: 10.0
unit: mb
script: unit MB mkpart primary 34.1MB 44.1MB
PLAY RECAP **************************************************************************************
localhost: ok=5 changed=1 unreachable=0 failed=0 skipped=5 rescued=0 ignored=0
You can see the new partition was created. The playbook is idempotent. You can run it repeatedly to make sure the partition is present. There will be no changes reported when all is right.
shell> parted -l
Model: Generic Flash Disk (scsi)
Disk /dev/sdb: 4089MB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:
Number Start End Size Type File system Flags
1 512B 34.1MB 34.1MB primary fat32 esp
2 34.1MB 44.1MB 10.0MB primary
- It might be a good idea to split the remote hosts into smaller groups when you run the second part on a large number of remote hosts for the first time. When the partition is created you can run it all in one for the purpose of audit.
Example of a complete playbook
---
# All rights reserved (c) 2022, Vladimir Botka <vbotka@gmail.com>
# Simplified BSD License, https://opensource.org/licenses/BSD-2-Clause
- hosts: localhost
gather_facts: false
become: true
vars:
disk: sda # change this
part_size: 3 # change this
unit: GiB # change this
cache_update: false
cache_path: "/tmp/{{ inventory_hostname }}-{{ disk }}-part_info.json"
# next variables are calculated from part_info
last_number: "{{ part_info.partitions|map(attribute='num')|max }}"
last_end: "{{ part_info.partitions|map(attribute='end')|max }}"
next_number: "{{ last_number|int + 1 }}"
next_part_start: "{{ last_end }}{{ unit }}"
next_part_end: "{{ last_end|float + part_size|float }}{{ unit }}"
tasks:
- name: Get info and write part_info to cache_path
block:
- debug:
msg: |-
disk: /dev/{{ disk }}
unit: {{ unit }}
cache_update: {{ cache_update }}
cache_path: {{ cache_path }}
when: debug|d(false)|bool
- name: get partition info
parted:
device: "/dev/{{ disk }}"
unit: "{{ unit }}"
register: part_info
- debug:
var: part_info
when: debug|d(false)|bool
- debug:
msg: |-
part_size: {{ part_size }}
last_number: {{ last_number }}
last_end: {{ last_end }}
next_number: {{ next_number }}
next_part_start: {{ next_part_start }}
next_part_end: {{ next_part_end }}
when: debug|d(false)|bool
- copy:
dest: "{{ cache_path }}"
force: "{{ cache_update|bool }}"
content: |
{{ part_info|to_json }}
delegate_to: localhost
when: get_info|d(false)|bool
- name: Read part_info from cache_path and create partition
block:
- set_fact:
part_info: "{{ lookup('file', cache_path)|from_yaml }}"
- debug:
var: part_info
when: debug|d(false)|bool
- debug:
msg: |-
part_size: {{ part_size }}
last_number: {{ last_number }}
last_end: {{ last_end }}
unit: {{ unit }}
next_number: {{ next_number }}
next_part_start: {{ next_part_start }}
next_part_end: {{ next_part_end }}
when: debug|d(false)|bool
- name: create new partition
parted:
device: "/dev/{{ disk }}"
number: "{{ next_number }}"
part_start: "{{ next_part_start }}"
part_end: "{{ next_part_end }}"
align: optimal
unit: "{{ unit }}"
state: present
register: result
- debug:
var: result
when: debug|d(false)|bool
when: create_part|d(false)|bool