Skip to content

Commit 9023d4d

Browse files
authored
filesystem: extend support for FreeBSD (ansible-collections#2902)
* extend support for FreeBSD * Check if FS exists with `fstyp` if `blkid` fails to find FS signature (fix a potential data loss) * Add support for FreeBSD special devices (character devices). * Add support for FreeBSD native fstype (UFS). * Update DOCUMENTATION accordingly. * add/update integration tests * Add tests for `fstype=ufs` on FreeBSD. * Run `remove_fs` tests (`state=absent`) on FreeBSD. * Run `overwrite_another_fs` tests on FreeBSD. * add a changelog fragment * fix indentation * restrict new tests to regular files * fix typo * fix searching of providersize (block count) * add '-y' option to growfs command * remove references to versions older than the collection itself * bump version adding new feats to 3.4.0 * reformat *collection* and *version added* for better DOCUMENTATION parsing * skip tests for FreeBSD < 12.2 * run tests for FreeBSD >= 12.2 * re-enable tests for FreeBSD < 12.2 and give it a try with group1 * util-linux not available on FreeBSD < 12.2
1 parent 4ae392e commit 9023d4d

File tree

10 files changed

+162
-46
lines changed

10 files changed

+162
-46
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
minor_changes:
3+
- filesystem - extend support for FreeBSD. Avoid potential data loss by checking
4+
existence of a filesystem with ``fstyp`` (native command) if ``blkid`` (foreign
5+
command) doesn't find one. Add support for character devices and ``ufs`` filesystem
6+
type (https://github.com/ansible-collections/community.general/pull/2902).

plugins/modules/system/filesystem.py

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/python
22
# -*- coding: utf-8 -*-
33

4+
# Copyright: (c) 2021, quidame <quidame@poivron.org>
45
# Copyright: (c) 2013, Alexander Bulimov <lazywolf0@gmail.com>
56
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
67

@@ -12,6 +13,7 @@
1213
---
1314
author:
1415
- Alexander Bulimov (@abulimov)
16+
- quidame (@quidame)
1517
module: filesystem
1618
short_description: Makes a filesystem
1719
description:
@@ -30,25 +32,22 @@
3032
default: present
3133
version_added: 1.3.0
3234
fstype:
33-
choices: [ btrfs, ext2, ext3, ext4, ext4dev, f2fs, lvm, ocfs2, reiserfs, xfs, vfat, swap ]
35+
choices: [ btrfs, ext2, ext3, ext4, ext4dev, f2fs, lvm, ocfs2, reiserfs, xfs, vfat, swap, ufs ]
3436
description:
3537
- Filesystem type to be created. This option is required with
3638
C(state=present) (or if I(state) is omitted).
37-
- reiserfs support was added in 2.2.
38-
- lvm support was added in 2.5.
39-
- since 2.5, I(dev) can be an image file.
40-
- vfat support was added in 2.5
41-
- ocfs2 support was added in 2.6
42-
- f2fs support was added in 2.7
43-
- swap support was added in 2.8
39+
- ufs support has been added in community.general 3.4.0.
4440
type: str
4541
aliases: [type]
4642
dev:
4743
description:
48-
- Target path to block device or regular file.
49-
- On systems not using block devices but character devices instead (as
50-
FreeBSD), this module only works when applying to regular files, aka
51-
disk images.
44+
- Target path to block device (Linux) or character device (FreeBSD) or
45+
regular file (both).
46+
- When setting Linux-specific filesystem types on FreeBSD, this module
47+
only works when applying to regular files, aka disk images.
48+
- Currently C(lvm) (Linux-only) and C(ufs) (FreeBSD-only) don't support
49+
a regular file as their target I(dev).
50+
- Support for character devices on FreeBSD has been added in community.general 3.4.0.
5251
type: path
5352
required: yes
5453
aliases: [device]
@@ -60,7 +59,7 @@
6059
resizefs:
6160
description:
6261
- If C(yes), if the block device and filesystem size differ, grow the filesystem into the space.
63-
- Supported for C(ext2), C(ext3), C(ext4), C(ext4dev), C(f2fs), C(lvm), C(xfs) and C(vfat) filesystems.
62+
- Supported for C(ext2), C(ext3), C(ext4), C(ext4dev), C(f2fs), C(lvm), C(xfs), C(ufs) and C(vfat) filesystems.
6463
Attempts to resize other filesystem types will fail.
6564
- XFS Will only grow if mounted. Currently, the module is based on commands
6665
from C(util-linux) package to perform operations, so resizing of XFS is
@@ -73,16 +72,24 @@
7372
- List of options to be passed to mkfs command.
7473
type: str
7574
requirements:
76-
- Uses tools related to the I(fstype) (C(mkfs)) and the C(blkid) command.
77-
- When I(resizefs) is enabled, C(blockdev) command is required too.
75+
- Uses specific tools related to the I(fstype) for creating or resizing a
76+
filesystem (from packages e2fsprogs, xfsprogs, dosfstools, and so on).
77+
- Uses generic tools mostly related to the Operating System (Linux or
78+
FreeBSD) or available on both, as C(blkid).
79+
- On FreeBSD, either C(util-linux) or C(e2fsprogs) package is required.
7880
notes:
79-
- Potential filesystem on I(dev) are checked using C(blkid). In case C(blkid)
80-
isn't able to detect an existing filesystem, this filesystem is overwritten
81-
even if I(force) is C(no).
82-
- On FreeBSD systems, either C(e2fsprogs) or C(util-linux) packages provide
83-
a C(blkid) command that is compatible with this module, when applied to
84-
regular files.
81+
- Potential filesystems on I(dev) are checked using C(blkid). In case C(blkid)
82+
is unable to detect a filesystem (and in case C(fstyp) on FreeBSD is also
83+
unable to detect a filesystem), this filesystem is overwritten even if
84+
I(force) is C(no).
85+
- On FreeBSD systems, both C(e2fsprogs) and C(util-linux) packages provide
86+
a C(blkid) command that is compatible with this module. However, these
87+
packages conflict with each other, and only the C(util-linux) package
88+
provides the command required to not fail when I(state=absent).
8589
- This module supports I(check_mode).
90+
seealso:
91+
- module: community.general.filesize
92+
- module: ansible.posix.mount
8693
'''
8794

8895
EXAMPLES = '''
@@ -101,6 +108,11 @@
101108
community.general.filesystem:
102109
dev: /dev/sdb1
103110
state: absent
111+
112+
- name: Create a filesystem on top of a regular file
113+
community.general.filesystem:
114+
dev: /path/to/disk.img
115+
fstype: vfat
104116
'''
105117

106118
from distutils.version import LooseVersion
@@ -125,6 +137,10 @@ def size(self):
125137
blockdev_cmd = self.module.get_bin_path("blockdev", required=True)
126138
dummy, out, dummy = self.module.run_command([blockdev_cmd, "--getsize64", self.path], check_rc=True)
127139
devsize_in_bytes = int(out)
140+
elif stat.S_ISCHR(statinfo.st_mode) and platform.system() == 'FreeBSD':
141+
diskinfo_cmd = self.module.get_bin_path("diskinfo", required=True)
142+
dummy, out, dummy = self.module.run_command([diskinfo_cmd, self.path], check_rc=True)
143+
devsize_in_bytes = int(out.split()[2])
128144
elif os.path.isfile(self.path):
129145
devsize_in_bytes = os.path.getsize(self.path)
130146
else:
@@ -423,6 +439,31 @@ class Swap(Filesystem):
423439
MKFS_FORCE_FLAGS = ['-f']
424440

425441

442+
class UFS(Filesystem):
443+
MKFS = 'newfs'
444+
INFO = 'dumpfs'
445+
GROW = 'growfs'
446+
GROW_MAX_SPACE_FLAGS = ['-y']
447+
448+
def get_fs_size(self, dev):
449+
"""Get providersize and fragment size and return their product."""
450+
cmd = self.module.get_bin_path(self.INFO, required=True)
451+
dummy, out, dummy = self.module.run_command([cmd, str(dev)], check_rc=True, environ_update=self.LANG_ENV)
452+
453+
fragmentsize = providersize = None
454+
for line in out.splitlines():
455+
if line.startswith('fsize'):
456+
fragmentsize = int(line.split()[1])
457+
elif 'providersize' in line:
458+
providersize = int(line.split()[-1])
459+
if None not in (fragmentsize, providersize):
460+
break
461+
else:
462+
raise ValueError(out)
463+
464+
return fragmentsize * providersize
465+
466+
426467
FILESYSTEMS = {
427468
'ext2': Ext2,
428469
'ext3': Ext3,
@@ -436,6 +477,7 @@ class Swap(Filesystem):
436477
'ocfs2': Ocfs2,
437478
'LVM2_member': LVM,
438479
'swap': Swap,
480+
'ufs': UFS,
439481
}
440482

441483

@@ -484,11 +526,16 @@ def main():
484526

485527
dev = Device(module, dev)
486528

529+
# In case blkid/fstyp isn't able to identify an existing filesystem, device
530+
# is considered as empty, then this existing filesystem would be overwritten
531+
# even if force isn't enabled.
487532
cmd = module.get_bin_path('blkid', required=True)
488533
rc, raw_fs, err = module.run_command([cmd, '-c', os.devnull, '-o', 'value', '-s', 'TYPE', str(dev)])
489-
# In case blkid isn't able to identify an existing filesystem, device is considered as empty,
490-
# then this existing filesystem would be overwritten even if force isn't enabled.
491534
fs = raw_fs.strip()
535+
if not fs and platform.system() == 'FreeBSD':
536+
cmd = module.get_bin_path('fstyp', required=True)
537+
rc, raw_fs, err = module.run_command([cmd, str(dev)])
538+
fs = raw_fs.strip()
492539

493540
if state == "present":
494541
if fstype in friendly_names:
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
destructive
2-
shippable/posix/group3
2+
shippable/posix/group1
33
skip/aix
44
skip/osx
55
skip/macos

tests/integration/targets/filesystem/defaults/main.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,9 @@ tested_filesystems:
2323
f2fs: {fssize: '{{ f2fs_fssize|default(60) }}', grow: 'f2fs_version is version("1.10.0", ">=")'}
2424
lvm: {fssize: 20, grow: True}
2525
swap: {fssize: 10, grow: False} # grow not implemented
26+
ufs: {fssize: 10, grow: True}
27+
28+
29+
get_uuid_any: "blkid -c /dev/null -o value -s UUID {{ dev }}"
30+
get_uuid_ufs: "dumpfs {{ dev }} | awk -v sb=superblock -v id=id '$1 == sb && $4 == id {print $6$7}'"
31+
get_uuid_cmd: "{{ get_uuid_ufs if fstype == 'ufs' else get_uuid_any }}"

tests/integration/targets/filesystem/tasks/create_device.yml

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@
1919
ansible.builtin.set_fact:
2020
dev: "{{ loop_device_cmd.stdout }}"
2121

22+
- when: fstype == 'ufs'
23+
block:
24+
- name: 'Create a memory disk for UFS'
25+
ansible.builtin.command:
26+
cmd: 'mdconfig -a -f {{ dev }}'
27+
register: memory_disk_cmd
28+
29+
- name: 'Switch to memory disk target for further tasks'
30+
ansible.builtin.set_fact:
31+
dev: "/dev/{{ memory_disk_cmd.stdout }}"
32+
2233
- include_tasks: '{{ action }}.yml'
2334

2435
always:
@@ -28,10 +39,16 @@
2839
removes: '{{ dev }}'
2940
when: fstype == 'lvm'
3041

31-
- name: 'Clean correct device for LVM'
42+
- name: 'Detach memory disk used for UFS'
43+
ansible.builtin.command:
44+
cmd: 'mdconfig -d -u {{ dev }}'
45+
removes: '{{ dev }}'
46+
when: fstype == 'ufs'
47+
48+
- name: 'Clean correct device for LVM and UFS'
3249
ansible.builtin.set_fact:
3350
dev: '{{ image_file }}'
34-
when: fstype == 'lvm'
51+
when: fstype in ['lvm', 'ufs']
3552

3653
- name: 'Remove disk image file'
3754
ansible.builtin.file:

tests/integration/targets/filesystem/tasks/create_fs.yml

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
- 'fs_result is success'
1313

1414
- name: "Get UUID of created filesystem"
15-
ansible.builtin.command:
16-
cmd: 'blkid -c /dev/null -o value -s UUID {{ dev }}'
15+
ansible.builtin.shell:
16+
cmd: "{{ get_uuid_cmd }}"
1717
changed_when: false
1818
register: uuid
1919

@@ -24,8 +24,8 @@
2424
register: fs2_result
2525

2626
- name: "Get UUID of the filesystem"
27-
ansible.builtin.command:
28-
cmd: 'blkid -c /dev/null -o value -s UUID {{ dev }}'
27+
ansible.builtin.shell:
28+
cmd: "{{ get_uuid_cmd }}"
2929
changed_when: false
3030
register: uuid2
3131

@@ -44,8 +44,8 @@
4444
register: fs3_result
4545

4646
- name: "Get UUID of the new filesystem"
47-
ansible.builtin.command:
48-
cmd: 'blkid -c /dev/null -o value -s UUID {{ dev }}'
47+
ansible.builtin.shell:
48+
cmd: "{{ get_uuid_cmd }}"
4949
changed_when: false
5050
register: uuid3
5151

@@ -71,6 +71,11 @@
7171
cmd: 'losetup -c {{ dev }}'
7272
when: fstype == 'lvm'
7373

74+
- name: "Resize memory disk for UFS"
75+
ansible.builtin.command:
76+
cmd: 'mdconfig -r -u {{ dev }} -s {{ fssize | int + 1 }}M'
77+
when: fstype == 'ufs'
78+
7479
- name: "Expand filesystem"
7580
community.general.filesystem:
7681
dev: '{{ dev }}'
@@ -79,8 +84,8 @@
7984
register: fs4_result
8085

8186
- name: "Get UUID of the filesystem"
82-
ansible.builtin.command:
83-
cmd: 'blkid -c /dev/null -o value -s UUID {{ dev }}'
87+
ansible.builtin.shell:
88+
cmd: "{{ get_uuid_cmd }}"
8489
changed_when: false
8590
register: uuid4
8691

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
- name: "Uninstall e2fsprogs"
3+
ansible.builtin.package:
4+
name: e2fsprogs
5+
state: absent
6+
7+
- name: "Install util-linux"
8+
ansible.builtin.package:
9+
name: util-linux
10+
state: present

tests/integration/targets/filesystem/tasks/main.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
# Available on FreeBSD but not on testbed (util-linux conflicts with e2fsprogs): wipefs, mkfs.minix
3636
- 'not (ansible_system == "FreeBSD" and item.1 in ["overwrite_another_fs", "remove_fs"])'
3737

38+
# Linux limited support
39+
# Not available: ufs (this is FreeBSD's native fs)
40+
- 'not (ansible_system == "Linux" and item.0.key == "ufs")'
41+
3842
# Other limitations and corner cases
3943

4044
# f2fs-tools and reiserfs-utils packages not available with RHEL/CentOS on CI
@@ -59,3 +63,24 @@
5963
item.0.key == "xfs" and ansible_python.version.major == 2)'
6064

6165
loop: "{{ query('dict', tested_filesystems)|product(['create_fs', 'overwrite_another_fs', 'remove_fs'])|list }}"
66+
67+
68+
# With FreeBSD extended support (util-linux is not available before 12.2)
69+
70+
- include_tasks: freebsd_setup.yml
71+
when:
72+
- 'ansible_system == "FreeBSD"'
73+
- 'ansible_distribution_version is version("12.2", ">=")'
74+
75+
- include_tasks: create_device.yml
76+
vars:
77+
image_file: '{{ remote_tmp_dir }}/img'
78+
fstype: '{{ item.0.key }}'
79+
fssize: '{{ item.0.value.fssize }}'
80+
grow: '{{ item.0.value.grow }}'
81+
action: '{{ item.1 }}'
82+
when:
83+
- 'ansible_system == "FreeBSD"'
84+
- 'ansible_distribution_version is version("12.2", ">=")'
85+
- 'item.0.key in ["xfs", "vfat"]'
86+
loop: "{{ query('dict', tested_filesystems)|product(['create_fs', 'overwrite_another_fs', 'remove_fs'])|list }}"

tests/integration/targets/filesystem/tasks/overwrite_another_fs.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
cmd: 'mkfs.minix {{ dev }}'
1111

1212
- name: 'Get UUID of the new filesystem'
13-
ansible.builtin.command:
14-
cmd: 'blkid -c /dev/null -o value -s UUID {{ dev }}'
13+
ansible.builtin.shell:
14+
cmd: "{{ get_uuid_cmd }}"
1515
changed_when: false
1616
register: uuid
1717

@@ -23,8 +23,8 @@
2323
ignore_errors: True
2424

2525
- name: 'Get UUID of the filesystem'
26-
ansible.builtin.command:
27-
cmd: 'blkid -c /dev/null -o value -s UUID {{ dev }}'
26+
ansible.builtin.shell:
27+
cmd: "{{ get_uuid_cmd }}"
2828
changed_when: false
2929
register: uuid2
3030

@@ -42,8 +42,8 @@
4242
register: fs_result2
4343

4444
- name: 'Get UUID of the new filesystem'
45-
ansible.builtin.command:
46-
cmd: 'blkid -c /dev/null -o value -s UUID {{ dev }}'
45+
ansible.builtin.shell:
46+
cmd: "{{ get_uuid_cmd }}"
4747
changed_when: false
4848
register: uuid3
4949

0 commit comments

Comments
 (0)