mirror of https://github.com/ansible/ansible.git
Add win_format (#53925)
* Add win_format * Some doc changes were missed * Fixes for ansible-test, additional assertion for check mode * Fix -WhatIf issues * Support for idempotency and changes to integration tests * Fix trailing whitespace * Fixes from review, and added check for non-empty volumes * Remove an extra line * Structural changes * Minor fixes for CIpull/55012/head
parent
6761fc1475
commit
4651bcf561
@ -0,0 +1,184 @@
|
|||||||
|
#!powershell
|
||||||
|
|
||||||
|
# Copyright: (c) 2019, Varun Chopra (@chopraaa) <v@chopraaa.com>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||||
|
#AnsibleRequires -OSVersion 6.2
|
||||||
|
|
||||||
|
Set-StrictMode -Version 2
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$spec = @{
|
||||||
|
options = @{
|
||||||
|
drive_letter = @{ type = "str" }
|
||||||
|
path = @{ type = "str" }
|
||||||
|
label = @{ type = "str" }
|
||||||
|
new_label = @{ type = "str" }
|
||||||
|
file_system = @{ type = "str"; choices = "ntfs", "refs", "exfat", "fat32", "fat" }
|
||||||
|
allocation_unit_size = @{ type = "int" }
|
||||||
|
large_frs = @{ type = "bool" }
|
||||||
|
full = @{ type = "bool"; default = $false }
|
||||||
|
compress = @{ type = "bool" }
|
||||||
|
integrity_streams = @{ type = "bool" }
|
||||||
|
force = @{ type = "bool"; default = $false }
|
||||||
|
}
|
||||||
|
mutually_exclusive = @(
|
||||||
|
,@('drive_letter', 'path', 'label')
|
||||||
|
)
|
||||||
|
required_one_of = @(
|
||||||
|
,@('drive_letter', 'path', 'label')
|
||||||
|
)
|
||||||
|
supports_check_mode = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||||
|
|
||||||
|
$drive_letter = $module.Params.drive_letter
|
||||||
|
$path = $module.Params.path
|
||||||
|
$label = $module.Params.label
|
||||||
|
$new_label = $module.Params.new_label
|
||||||
|
$file_system = $module.Params.file_system
|
||||||
|
$allocation_unit_size = $module.Params.allocation_unit_size
|
||||||
|
$large_frs = $module.Params.large_frs
|
||||||
|
$full_format = $module.Params.full
|
||||||
|
$compress_volume = $module.Params.compress
|
||||||
|
$integrity_streams = $module.Params.integrity_streams
|
||||||
|
$force_format = $module.Params.force
|
||||||
|
|
||||||
|
# Some pre-checks
|
||||||
|
if ($null -ne $drive_letter -and $drive_letter -notmatch "^[a-zA-Z]$") {
|
||||||
|
$module.FailJson("The parameter drive_letter should be a single character A-Z")
|
||||||
|
}
|
||||||
|
if ($integrity_streams -eq $true -and $file_system -ne "refs") {
|
||||||
|
$module.FailJson("Integrity streams can be enabled only on ReFS volumes. You specified: $($file_system)")
|
||||||
|
}
|
||||||
|
if ($compress_volume -eq $true) {
|
||||||
|
if ($file_system -eq "ntfs") {
|
||||||
|
if ($null -ne $allocation_unit_size -and $allocation_unit_size -gt 4096) {
|
||||||
|
$module.FailJson("NTFS compression is not supported for allocation unit sizes above 4096")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$module.FailJson("Compression can be enabled only on NTFS volumes. You specified: $($file_system)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-AnsibleVolume {
|
||||||
|
param(
|
||||||
|
$DriveLetter,
|
||||||
|
$Path,
|
||||||
|
$Label
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($null -ne $DriveLetter) {
|
||||||
|
try {
|
||||||
|
$volume = Get-Volume -DriveLetter $DriveLetter
|
||||||
|
} catch {
|
||||||
|
$module.FailJson("There was an error retrieving the volume using drive_letter $($DriveLetter): $($_.Exception.Message)", $_)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($null -ne $Path) {
|
||||||
|
try {
|
||||||
|
$volume = Get-Volume -Path $Path
|
||||||
|
} catch {
|
||||||
|
$module.FailJson("There was an error retrieving the volume using path $($Path): $($_.Exception.Message)", $_)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($null -ne $Label) {
|
||||||
|
try {
|
||||||
|
$volume = Get-Volume -FileSystemLabel $Label
|
||||||
|
} catch {
|
||||||
|
$module.FailJson("There was an error retrieving the volume using label $($Label): $($_.Exception.Message)", $_)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$module.FailJson("Unable to locate volume: drive_letter, path and label were not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
return $volume
|
||||||
|
}
|
||||||
|
|
||||||
|
function Format-AnsibleVolume {
|
||||||
|
param(
|
||||||
|
$Path,
|
||||||
|
$Label,
|
||||||
|
$FileSystem,
|
||||||
|
$Full,
|
||||||
|
$UseLargeFRS,
|
||||||
|
$Compress,
|
||||||
|
$SetIntegrityStreams
|
||||||
|
)
|
||||||
|
$parameters = @{
|
||||||
|
Path = $Path
|
||||||
|
Full = $Full
|
||||||
|
}
|
||||||
|
if ($null -ne $UseLargeFRS) {
|
||||||
|
$parameters.Add("UseLargeFRS", $UseLargeFRS)
|
||||||
|
}
|
||||||
|
if ($null -ne $SetIntegrityStreams) {
|
||||||
|
$parameters.Add("SetIntegrityStreams", $SetIntegrityStreams)
|
||||||
|
}
|
||||||
|
if ($null -ne $Compress){
|
||||||
|
$parameters.Add("Compress", $Compress)
|
||||||
|
}
|
||||||
|
if ($null -ne $Label) {
|
||||||
|
$parameters.Add("NewFileSystemLabel", $Label)
|
||||||
|
}
|
||||||
|
if ($null -ne $FileSystem) {
|
||||||
|
$parameters.Add("FileSystem", $FileSystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
Format-Volume @parameters -Confirm:$false | Out-Null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$ansible_volume = Get-AnsibleVolume -DriveLetter $drive_letter -Path $path -Label $label
|
||||||
|
$ansible_file_system = $ansible_volume.FileSystem
|
||||||
|
$ansible_volume_size = $ansible_volume.Size
|
||||||
|
|
||||||
|
$ansible_partition = Get-Partition -Volume $ansible_volume
|
||||||
|
|
||||||
|
foreach ($access_path in $ansible_partition.AccessPaths) {
|
||||||
|
if ($access_path -ne $Path) {
|
||||||
|
$files_in_volume = (Get-ChildItem -LiteralPath $access_path -ErrorAction SilentlyContinue | Measure-Object).Count
|
||||||
|
|
||||||
|
if (-not $force_format -and $files_in_volume -gt 0) {
|
||||||
|
$module.FailJson("Force format must be specified to format non-pristine volumes")
|
||||||
|
} else {
|
||||||
|
if (-not $force_format -and
|
||||||
|
-not $null -eq $file_system -and
|
||||||
|
-not [string]::IsNullOrEmpty($ansible_file_system) -and
|
||||||
|
$file_system -ne $ansible_file_system) {
|
||||||
|
$module.FailJson("Force format must be specified since target file system: $($file_system) is different from the current file system of the volume: $($ansible_file_system.ToLower())")
|
||||||
|
} else {
|
||||||
|
$pristine = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($force_format) {
|
||||||
|
if (-not $module.CheckMode) {
|
||||||
|
Format-AnsibleVolume -Path $ansible_volume.Path -Full $full_format -Label $new_label -FileSystem $file_system -SetIntegrityStreams $integrity_streams -UseLargeFRS $large_frs -Compress $compress_volume
|
||||||
|
}
|
||||||
|
$module.Result.changed = $true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ($pristine) {
|
||||||
|
if ($null -eq $new_label) {
|
||||||
|
$new_label = $ansible_volume.FileSystemLabel
|
||||||
|
}
|
||||||
|
# Conditions for formatting
|
||||||
|
if ($ansible_volume_size -eq 0 -or
|
||||||
|
$ansible_volume.FileSystemLabel -ne $new_label) {
|
||||||
|
if (-not $module.CheckMode) {
|
||||||
|
Format-AnsibleVolume -Path $ansible_volume.Path -Full $full_format -Label $new_label -FileSystem $file_system -SetIntegrityStreams $integrity_streams -UseLargeFRS $large_frs -Compress $compress_volume
|
||||||
|
}
|
||||||
|
$module.Result.changed = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$module.ExitJson()
|
@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright: (c) 2019, Varun Chopra (@chopraaa) <v@chopraaa.com>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {
|
||||||
|
'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
module: win_format
|
||||||
|
version_added: '2.8'
|
||||||
|
short_description: Formats an existing volume or a new volume on an existing partition on Windows
|
||||||
|
description:
|
||||||
|
- The M(win_format) module formats an existing volume or a new volume on an existing partition on Windows
|
||||||
|
options:
|
||||||
|
drive_letter:
|
||||||
|
description:
|
||||||
|
- Used to specify the drive letter of the volume to be formatted.
|
||||||
|
type: str
|
||||||
|
path:
|
||||||
|
description:
|
||||||
|
- Used to specify the path to the volume to be formatted.
|
||||||
|
type: str
|
||||||
|
label:
|
||||||
|
description:
|
||||||
|
- Used to specify the label of the volume to be formatted.
|
||||||
|
type: str
|
||||||
|
new_label:
|
||||||
|
description:
|
||||||
|
- Used to specify the new file system label of the formatted volume.
|
||||||
|
type: str
|
||||||
|
file_system:
|
||||||
|
description:
|
||||||
|
- Used to specify the file system to be used when formatting the target volume.
|
||||||
|
type: str
|
||||||
|
choices: [ ntfs, refs, exfat, fat32, fat ]
|
||||||
|
allocation_unit_size:
|
||||||
|
description:
|
||||||
|
- Specifies the cluster size to use when formatting the volume.
|
||||||
|
- If no cluster size is specified when you format a partition, defaults are selected based on
|
||||||
|
the size of the partition.
|
||||||
|
type: int
|
||||||
|
large_frs:
|
||||||
|
description:
|
||||||
|
- Specifies that large File Record System (FRS) should be used.
|
||||||
|
type: bool
|
||||||
|
compress:
|
||||||
|
description:
|
||||||
|
- Enable compression on the resulting NTFS volume.
|
||||||
|
- NTFS compression is not supported where I(allocation_unit_size) is more than 4096.
|
||||||
|
type: bool
|
||||||
|
integrity_streams:
|
||||||
|
description:
|
||||||
|
- Enable integrity streams on the resulting ReFS volume.
|
||||||
|
type: bool
|
||||||
|
full:
|
||||||
|
description:
|
||||||
|
- A full format writes to every sector of the disk, takes much longer to perform than the
|
||||||
|
default (quick) format, and is not recommended on storage that is thinly provisioned.
|
||||||
|
- Specify C(true) for full format.
|
||||||
|
type: bool
|
||||||
|
force:
|
||||||
|
description:
|
||||||
|
- Specify if formatting should be forced for volumes that are not created from new partitions
|
||||||
|
or if the source and target file system are different.
|
||||||
|
type: bool
|
||||||
|
notes:
|
||||||
|
- One of three parameters (I(drive_letter), I(path) and I(label)) are mandatory to identify the target
|
||||||
|
volume but more than one cannot be specified at the same time.
|
||||||
|
- This module is idempotent if I(force) is not specified and file system labels remain preserved.
|
||||||
|
- For more information, see U(https://docs.microsoft.com/en-us/previous-versions/windows/desktop/stormgmt/format-msft-volume)
|
||||||
|
seealso:
|
||||||
|
- module: win_disk_facts
|
||||||
|
- module: win_partition
|
||||||
|
author:
|
||||||
|
- Varun Chopra (@chopraaa) <v@chopraaa.com>
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
- name: Create a partition with drive letter D and size 5 GiB
|
||||||
|
win_partition:
|
||||||
|
drive_letter: D
|
||||||
|
partition_size: 5 GiB
|
||||||
|
disk_number: 1
|
||||||
|
|
||||||
|
- name: Full format the newly created partition as NTFS and label it
|
||||||
|
win_format:
|
||||||
|
drive_letter: D
|
||||||
|
file_system: NTFS
|
||||||
|
new_label: Formatted
|
||||||
|
full: True
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
#
|
||||||
|
'''
|
@ -0,0 +1,3 @@
|
|||||||
|
shippable/windows/group4
|
||||||
|
skip/windows/2008
|
||||||
|
skip/windows/2008-R2
|
@ -0,0 +1,2 @@
|
|||||||
|
dependencies:
|
||||||
|
- setup_remote_tmp_dir
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
- name: Check if Format-Volume is supported
|
||||||
|
win_shell: if (Get-Command -Name Format-Volume -ErrorAction SilentlyContinue) { $true } else { $false }
|
||||||
|
register: module_present
|
||||||
|
|
||||||
|
- include: pre_test.yml
|
||||||
|
when: module_present.stdout | trim | bool
|
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
- set_fact:
|
||||||
|
AnsibleVhdx: '{{ remote_tmp_dir }}\AnsiblePart.vhdx'
|
||||||
|
|
||||||
|
- name: Copy VHDX scripts
|
||||||
|
win_template:
|
||||||
|
src: "{{ item.src }}"
|
||||||
|
dest: '{{ remote_tmp_dir }}\{{ item.dest }}'
|
||||||
|
loop:
|
||||||
|
- { src: partition_creation_script.j2, dest: partition_creation_script.txt }
|
||||||
|
- { src: partition_deletion_script.j2, dest: partition_deletion_script.txt }
|
||||||
|
|
||||||
|
- name: Create partition
|
||||||
|
win_command: diskpart.exe /s {{ remote_tmp_dir }}\partition_creation_script.txt
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
block:
|
||||||
|
- include: tests.yml
|
||||||
|
always:
|
||||||
|
- name: Detach disk
|
||||||
|
win_command: diskpart.exe /s {{ remote_tmp_dir }}\partition_deletion_script.txt
|
@ -0,0 +1,138 @@
|
|||||||
|
---
|
||||||
|
- win_shell: $AnsiPart = Get-Partition -DriveLetter T; $AnsiVol = Get-Volume -DriveLetter T; "$($AnsiPart.Size),$($AnsiVol.Size)"
|
||||||
|
register: shell_result
|
||||||
|
|
||||||
|
- name: Assert volume size is 0 for pristine volume
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- shell_result.stdout | trim == "2096037888,0"
|
||||||
|
|
||||||
|
- name: Get partition access path
|
||||||
|
win_shell: (Get-Partition -DriveLetter T).AccessPaths[1]
|
||||||
|
register: shell_partition_result
|
||||||
|
|
||||||
|
- name: Try to format using mutually exclusive parameters
|
||||||
|
win_format:
|
||||||
|
drive_letter: T
|
||||||
|
path: "{{ shell_partition_result.stdout | trim }}"
|
||||||
|
register: format_mutex_result
|
||||||
|
ignore_errors: True
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- format_mutex_result is failed
|
||||||
|
- 'format_mutex_result.msg == "parameters are mutually exclusive: drive_letter, path, label"'
|
||||||
|
|
||||||
|
- name: Fully format volume and assign label (check)
|
||||||
|
win_format:
|
||||||
|
drive_letter: T
|
||||||
|
new_label: Formatted
|
||||||
|
full: True
|
||||||
|
register: format_result_check
|
||||||
|
check_mode: True
|
||||||
|
|
||||||
|
- win_shell: $AnsiPart = Get-Partition -DriveLetter T; $AnsiVol = Get-Volume -DriveLetter T; "$($AnsiPart.Size),$($AnsiVol.Size),$($AnsiVol.FileSystemLabel)"
|
||||||
|
register: formatted_value_result_check
|
||||||
|
|
||||||
|
- name: Fully format volume and assign label
|
||||||
|
win_format:
|
||||||
|
drive_letter: T
|
||||||
|
new_label: Formatted
|
||||||
|
full: True
|
||||||
|
register: format_result
|
||||||
|
|
||||||
|
- win_shell: $AnsiPart = Get-Partition -DriveLetter T; $AnsiVol = Get-Volume -DriveLetter T; "$($AnsiPart.Size),$($AnsiVol.Size),$($AnsiVol.FileSystemLabel)"
|
||||||
|
register: formatted_value_result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- format_result_check is changed
|
||||||
|
- format_result is changed
|
||||||
|
- formatted_value_result_check.stdout | trim == "2096037888,0,"
|
||||||
|
- formatted_value_result.stdout | trim == "2096037888,2096033792,Formatted"
|
||||||
|
|
||||||
|
- name: Format NTFS volume with integrity streams enabled
|
||||||
|
win_format:
|
||||||
|
path: "{{ shell_partition_result.stdout | trim }}"
|
||||||
|
file_system: ntfs
|
||||||
|
integrity_streams: True
|
||||||
|
ignore_errors: True
|
||||||
|
register: ntfs_integrity_streams
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- ntfs_integrity_streams is failed
|
||||||
|
- 'ntfs_integrity_streams.msg == "Integrity streams can be enabled only on ReFS volumes. You specified: ntfs"'
|
||||||
|
|
||||||
|
- name: Format volume (require force_format for specifying different file system)
|
||||||
|
win_format:
|
||||||
|
path: "{{ shell_partition_result.stdout | trim }}"
|
||||||
|
file_system: fat32
|
||||||
|
ignore_errors: True
|
||||||
|
register: require_force_format
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- require_force_format is failed
|
||||||
|
- 'require_force_format.msg == "Force format must be specified since target file system: fat32 is different from the current file system of the volume: ntfs"'
|
||||||
|
|
||||||
|
- name: Format volume (forced) (check)
|
||||||
|
win_format:
|
||||||
|
path: "{{ shell_partition_result.stdout | trim }}"
|
||||||
|
file_system: refs
|
||||||
|
force: True
|
||||||
|
check_mode: True
|
||||||
|
ignore_errors: True
|
||||||
|
register: not_pristine_forced_check
|
||||||
|
|
||||||
|
- name: Format volume (forced)
|
||||||
|
win_format:
|
||||||
|
path: "{{ shell_partition_result.stdout | trim }}"
|
||||||
|
file_system: refs
|
||||||
|
force: True
|
||||||
|
register: not_pristine_forced
|
||||||
|
|
||||||
|
- name: Format volume (forced) (idempotence will not work)
|
||||||
|
win_format:
|
||||||
|
path: "{{ shell_partition_result.stdout | trim }}"
|
||||||
|
file_system: refs
|
||||||
|
force: True
|
||||||
|
register: not_pristine_forced_idem_fails
|
||||||
|
|
||||||
|
- name: Format volume (idempotence)
|
||||||
|
win_format:
|
||||||
|
path: "{{ shell_partition_result.stdout | trim }}"
|
||||||
|
file_system: refs
|
||||||
|
register: not_pristine_forced_idem
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not_pristine_forced_check is changed
|
||||||
|
- not_pristine_forced is changed
|
||||||
|
- not_pristine_forced_idem_fails is changed
|
||||||
|
- not_pristine_forced_idem is not changed
|
||||||
|
|
||||||
|
- name: Add a file
|
||||||
|
win_file:
|
||||||
|
path: T:\path\to\directory
|
||||||
|
state: directory
|
||||||
|
register: add_file_to_volume
|
||||||
|
|
||||||
|
- name: Format volume with file inside without force
|
||||||
|
win_format:
|
||||||
|
path: "{{ shell_partition_result.stdout | trim }}"
|
||||||
|
register: format_volume_without_force
|
||||||
|
ignore_errors: True
|
||||||
|
|
||||||
|
- name: Format volume with file inside with force
|
||||||
|
win_format:
|
||||||
|
path: "{{ shell_partition_result.stdout | trim }}"
|
||||||
|
force: True
|
||||||
|
register: format_volume_with_force
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- add_file_to_volume is changed
|
||||||
|
- format_volume_without_force is failed
|
||||||
|
- 'format_volume_without_force.msg == "Force format must be specified to format non-pristine volumes"'
|
||||||
|
- format_volume_with_force is changed
|
@ -0,0 +1,11 @@
|
|||||||
|
create vdisk file="{{ AnsibleVhdx }}" maximum=2000 type=fixed
|
||||||
|
|
||||||
|
select vdisk file="{{ AnsibleVhdx }}"
|
||||||
|
|
||||||
|
attach vdisk
|
||||||
|
|
||||||
|
convert mbr
|
||||||
|
|
||||||
|
create partition primary
|
||||||
|
|
||||||
|
assign letter="T"
|
@ -0,0 +1,3 @@
|
|||||||
|
select vdisk file="{{ AnsibleVhdx }}"
|
||||||
|
|
||||||
|
detach vdisk
|
Loading…
Reference in New Issue