summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDag Wieers <dag@wieers.com>2019-02-25 02:37:25 +0100
committerJordan Borean <jborean93@gmail.com>2019-02-25 11:37:25 +1000
commit3d1dd0e5995e9808143debd50f1f790bceb2e6e0 (patch)
tree26737664d33d2c89906e642bf990f3db93545136
parent76b5a9fb52585d8018fca5d333ce4470757920e9 (diff)
downloadansible-3d1dd0e5995e9808143debd50f1f790bceb2e6e0.tar.gz
Windows: Add backup parameter to modules (#50033)
* Windows: Add backup parameter to modules This PR adds a backup infrastructure for modules. * Fixes based on review feedback * Various fixes to check-mode and backup * Add integration tests * Fix win_xml integration test * Add backup support to copy action plugin * Added integration tests * Improve test efficiencies and other minor impv
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Backup.psm133
-rw-r--r--lib/ansible/modules/windows/win_copy.ps110
-rw-r--r--lib/ansible/modules/windows/win_copy.py23
-rw-r--r--lib/ansible/modules/windows/win_lineinfile.ps126
-rw-r--r--lib/ansible/modules/windows/win_lineinfile.py27
-rw-r--r--lib/ansible/modules/windows/win_template.py22
-rw-r--r--lib/ansible/modules/windows/win_xml.ps183
-rw-r--r--lib/ansible/modules/windows/win_xml.py16
-rw-r--r--lib/ansible/plugins/action/win_copy.py29
-rw-r--r--test/integration/targets/win_copy/tasks/tests.yml18
-rw-r--r--test/integration/targets/win_lineinfile/tasks/main.yml11
-rw-r--r--test/integration/targets/win_module_utils/library/backup_file_test.ps189
-rw-r--r--test/integration/targets/win_module_utils/tasks/main.yml10
-rw-r--r--test/integration/targets/win_template/tasks/main.yml33
-rw-r--r--test/integration/targets/win_xml/tasks/main.yml22
15 files changed, 361 insertions, 91 deletions
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Backup.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Backup.psm1
new file mode 100644
index 0000000000..246341cb7a
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Backup.psm1
@@ -0,0 +1,33 @@
+# Copyright (c): 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+Function Backup-File {
+<#
+ .SYNOPSIS
+ Helper function to make a backup of a file.
+ .EXAMPLE
+ Backup-File -path $path -WhatIf:$check_mode
+#>
+ [CmdletBinding(SupportsShouldProcess=$true)]
+
+ Param (
+ [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
+ [string] $path
+ )
+
+ Process {
+ $backup_path = $null
+ if (Test-Path -LiteralPath $path -PathType Leaf) {
+ $backup_path = "$path.$pid." + [DateTime]::Now.ToString("yyyyMMdd-HHmmss") + ".bak";
+ Try {
+ Copy-Item -LiteralPath $path -Destination $backup_path
+ } Catch {
+ throw "Failed to create backup file '$backup_path' from '$path'. ($($_.Exception.Message))"
+ }
+ }
+ return $backup_path
+ }
+}
+
+# This line must stay at the bottom to ensure all defined module parts are exported
+Export-ModuleMember -Function Backup-File
diff --git a/lib/ansible/modules/windows/win_copy.ps1 b/lib/ansible/modules/windows/win_copy.ps1
index 50ae1c61aa..83151c830d 100644
--- a/lib/ansible/modules/windows/win_copy.ps1
+++ b/lib/ansible/modules/windows/win_copy.ps1
@@ -5,6 +5,7 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
+#Requires -Module Ansible.ModuleUtils.Backup
$ErrorActionPreference = 'Stop'
@@ -22,6 +23,7 @@ $copy_mode = Get-AnsibleParam -obj $params -name "_copy_mode" -type "str" -defau
# used in explode, remote and single mode
$src = Get-AnsibleParam -obj $params -name "src" -type "path" -failifempty ($copy_mode -in @("explode","process","single"))
$dest = Get-AnsibleParam -obj $params -name "dest" -type "path" -failifempty $true
+$backup = Get-AnsibleParam -obj $params -name "backup" -type "bool" -default $false
# used in single mode
$original_basename = Get-AnsibleParam -obj $params -name "_original_basename" -type "str"
@@ -74,6 +76,10 @@ Function Copy-File($source, $dest) {
$diff += "+$file_dir\`n"
}
+ if ($backup) {
+ $result.backup_file = Backup-File -path $dest -WhatIf:$check_mode
+ }
+
if (Test-Path -Path $dest -PathType Leaf) {
Remove-Item -Path $dest -Force -Recurse -WhatIf:$check_mode | Out-Null
$diff += "-$dest`n"
@@ -390,6 +396,10 @@ if ($copy_mode -eq "query") {
}
}
+ if ($backup) {
+ $result.backup_file = Backup-File -path $remote_dest -WhatIf:$check_mode
+ }
+
Copy-Item -Path $src -Destination $remote_dest -Force | Out-Null
$result.changed = $true
}
diff --git a/lib/ansible/modules/windows/win_copy.py b/lib/ansible/modules/windows/win_copy.py
index b70707945d..04549c4568 100644
--- a/lib/ansible/modules/windows/win_copy.py
+++ b/lib/ansible/modules/windows/win_copy.py
@@ -43,8 +43,18 @@ options:
with "/" or "\", or C(src) is a directory.
- If C(src) and C(dest) are files and if the parent directory of C(dest)
doesn't exist, then the task will fail.
- required: yes
type: path
+ required: yes
+ backup:
+ description:
+ - Determine whether a backup should be created.
+ - When set to C(yes), create a backup file including the timestamp information
+ so you can get the original file back if you somehow clobbered it incorrectly.
+ - No backup is taken when C(remote_src=False) and multiple files are being
+ copied.
+ type: bool
+ default: no
+ version_added: '2.8'
force:
description:
- If set to C(yes), the file will only be transferred if the content
@@ -107,6 +117,12 @@ EXAMPLES = r'''
src: /srv/myfiles/foo.conf
dest: C:\Temp\renamed-foo.conf
+- name: Copy a single file, but keep a backup
+ win_copy:
+ src: /srv/myfiles/foo.conf
+ dest: C:\Temp\renamed-foo.conf
+ backup: yes
+
- name: Copy a single file keeping the filename
win_copy:
src: /src/myfiles/foo.conf
@@ -141,6 +157,11 @@ EXAMPLES = r'''
'''
RETURN = r'''
+backup_file:
+ description: Name of the backup file that was created.
+ returned: if backup=yes
+ type: str
+ sample: C:\Path\To\File.txt.11540.20150212-220915.bak
dest:
description: Destination file/path.
returned: changed
diff --git a/lib/ansible/modules/windows/win_lineinfile.ps1 b/lib/ansible/modules/windows/win_lineinfile.ps1
index 2c2eb9a803..33b22f7bae 100644
--- a/lib/ansible/modules/windows/win_lineinfile.ps1
+++ b/lib/ansible/modules/windows/win_lineinfile.ps1
@@ -3,6 +3,7 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
+#Requires -Module Ansible.ModuleUtils.Backup
function WriteLines($outlines, $path, $linesep, $encodingobj, $validate, $check_mode) {
Try {
@@ -42,14 +43,14 @@ function WriteLines($outlines, $path, $linesep, $encodingobj, $validate, $check_
# Commit changes to the path
$cleanpath = $path.Replace("/", "\");
Try {
- Copy-Item $temppath $cleanpath -force -ErrorAction Stop -WhatIf:$check_mode;
+ Copy-Item -Path $temppath -Destination $cleanpath -Force -WhatIf:$check_mode;
}
Catch {
Fail-Json @{} "Cannot write to: $cleanpath ($($_.Exception.Message))";
}
Try {
- Remove-Item $temppath -force -ErrorAction Stop -WhatIf:$check_mode;
+ Remove-Item -Path $temppath -Force -WhatIf:$check_mode;
}
Catch {
Fail-Json @{} "Cannot remove temporary file: $temppath ($($_.Exception.Message))";
@@ -60,19 +61,6 @@ function WriteLines($outlines, $path, $linesep, $encodingobj, $validate, $check_
}
-# Backup the file specified with a date/time filename
-function BackupFile($path, $check_mode) {
- $backuppath = $path + "." + [DateTime]::Now.ToString("yyyyMMdd-HHmmss");
- Try {
- Copy-Item $path $backuppath -WhatIf:$check_mode;
- }
- Catch {
- Fail-Json @{} "Cannot copy backup file! ($($_.Exception.Message))";
- }
- return $backuppath;
-}
-
-
# Implement the functionality for state == 'present'
function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $backup, $backrefs, $validate, $encodingobj, $linesep, $check_mode, $diff_support) {
@@ -198,7 +186,9 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b
# Write backup file if backup == "yes"
If ($backup) {
- $result.backup = BackupFile $path $check_mode;
+ $result.backup_file = Backup-File -path $path -WhatIf:$check_mode
+ # Ensure backward compatibility (deprecate in future)
+ $result.backup = $result.backup_file
}
$after = WriteLines $lines $path $linesep $encodingobj $validate $check_mode;
@@ -278,7 +268,9 @@ function Absent($path, $regexp, $line, $backup, $validate, $encodingobj, $linese
# Write backup file if backup == "yes"
If ($backup) {
- $result.backup = BackupFile $path $check_mode;
+ $result.backup_file = Backup-File -path $path -WhatIf:$check_mode
+ # Ensure backward compatibility (deprecate in future)
+ $result.backup = $result.backup_file
}
$after = WriteLines $left $path $linesep $encodingobj $validate $check_mode;
diff --git a/lib/ansible/modules/windows/win_lineinfile.py b/lib/ansible/modules/windows/win_lineinfile.py
index 2cf7d8f1c8..bce908266f 100644
--- a/lib/ansible/modules/windows/win_lineinfile.py
+++ b/lib/ansible/modules/windows/win_lineinfile.py
@@ -24,6 +24,13 @@ options:
type: path
required: yes
aliases: [ dest, destfile, name ]
+ backup:
+ description:
+ - Determine whether a backup should be created.
+ - When set to C(yes), create a backup file including the timestamp information
+ so you can get the original file back if you somehow clobbered it incorrectly.
+ type: bool
+ default: no
regexp:
description:
- The regular expression to look for in every line of the file. For C(state=present), the pattern to replace if found; only the last line found
@@ -70,11 +77,6 @@ options:
- Used with C(state=present). If specified, the file will be created if it does not already exist. By default it will fail if the file is missing.
type: bool
default: no
- backup:
- description:
- - Create a backup file including the timestamp information so you can get the original file back if you somehow clobbered it incorrectly.
- type: bool
- default: no
validate:
description:
- Validation to run before copying into place. Use %s in the command to indicate the current file to validate.
@@ -160,3 +162,18 @@ EXAMPLES = r'''
regexp: '(^name=)'
line: '$1JohnDoe'
'''
+
+RETURN = r'''
+backup:
+ description:
+ - Name of the backup file that was created.
+ - This is now deprecated, use C(backup_file) instead.
+ returned: if backup=yes
+ type: str
+ sample: C:\Path\To\File.txt.11540.20150212-220915.bak
+backup_file:
+ description: Name of the backup file that was created.
+ returned: if backup=yes
+ type: str
+ sample: C:\Path\To\File.txt.11540.20150212-220915.bak
+'''
diff --git a/lib/ansible/modules/windows/win_template.py b/lib/ansible/modules/windows/win_template.py
index 6817d29915..b16bf2947b 100644
--- a/lib/ansible/modules/windows/win_template.py
+++ b/lib/ansible/modules/windows/win_template.py
@@ -40,8 +40,16 @@ options:
dest:
description:
- Location to render the template to on the remote machine.
- type: str
+ type: path
required: yes
+ backup:
+ description:
+ - Determine whether a backup should be created.
+ - When set to C(yes), create a backup file including the timestamp information
+ so you can get the original file back if you somehow clobbered it incorrectly.
+ type: bool
+ default: no
+ version_added: '2.8'
newline_sequence:
description:
- Specify the newline sequence to use for templating files.
@@ -99,6 +107,9 @@ notes:
which changes the variable interpolation markers to [% var %] instead of {{ var }}.
This is the best way to prevent evaluation of things that look like, but should not be Jinja2.
raw/endraw in Jinja2 will not work as you expect because templates in Ansible are recursively evaluated."
+ - You can use the M(win_copy) module with the C(content:) option if you prefer the template inline,
+ as part of the playbook.
+
seealso:
- module: template
- module: win_copy
@@ -117,4 +128,13 @@ EXAMPLES = r'''
src: unix/config.conf.j2
dest: C:\share\unix\config.conf
newline_sequence: '\n'
+ backup: yes
+'''
+
+RETURN = r'''
+backup_file:
+ description: Name of the backup file that was created.
+ returned: if backup=yes
+ type: str
+ sample: C:\Path\To\File.txt.11540.20150212-220915.bak
'''
diff --git a/lib/ansible/modules/windows/win_xml.ps1 b/lib/ansible/modules/windows/win_xml.ps1
index f722deb614..f2188b5cb0 100644
--- a/lib/ansible/modules/windows/win_xml.ps1
+++ b/lib/ansible/modules/windows/win_xml.ps1
@@ -4,6 +4,7 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
+#Requires -Module Ansible.ModuleUtils.Backup
Set-StrictMode -Version 2
@@ -79,12 +80,6 @@ function Compare-XmlDocs($actual, $expected) {
}
}
-function BackupFile($path) {
- $backuppath = $path + "." + [DateTime]::Now.ToString("yyyyMMdd-HHmmss");
- Copy-Item $path $backuppath;
- return $backuppath;
-}
-
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
@@ -94,7 +89,7 @@ $debug = $debug_level -gt 2
$dest = Get-AnsibleParam $params "path" -type "path" -FailIfEmpty $true -aliases "dest", "file"
$fragment = Get-AnsibleParam $params "fragment" -type "str" -FailIfEmpty $true -aliases "xmlstring"
$xpath = Get-AnsibleParam $params "xpath" -type "str" -FailIfEmpty $true
-$backup = Get-AnsibleParam $params "backup" -type "bool" -Default $false
+$backup = Get-AnsibleParam $params "backup" -type "bool" -default $false
$type = Get-AnsibleParam $params "type" -type "str" -Default "element" -ValidateSet "element", "attribute", "text"
$attribute = Get-AnsibleParam $params "attribute" -type "str" -FailIfEmpty ($type -eq "attribute")
$state = Get-AnsibleParam $params "state" -type "str" -Default "present"
@@ -174,15 +169,15 @@ if ($type -eq "element") {
}
if ($changed) {
- $result.changed = $true
- if (!$check_mode) {
- if ($backup) {
- $result.backup = BackupFile($dest)
- }
+ if ($backup) {
+ $result.backup_file = Backup-File -path $dest -WhatIf:$check_mode
+ # Ensure backward compatibility (deprecate in future)
+ $result.backup = $result.backup_file
+ }
+ if (-not $check_mode) {
$xmlorig.Save($dest)
- } else {
- $result.msg += " check mode"
}
+ $result.changed = $true
} else {
$result.msg = "not changed"
}
@@ -190,17 +185,17 @@ if ($type -eq "element") {
$node = $xmlorig.SelectSingleNode($xpath, $namespaceMgr)
[bool]$add = ($node.get_InnerText() -ne $fragment)
if ($add) {
- $result.changed = $true
- if (-Not $check_mode) {
- if ($backup) {
- $result.backup = BackupFile($dest)
- }
- $node.set_InnerText($fragment)
+ if ($backup) {
+ $result.backup_file = Backup-File -path $dest -WhatIf:$check_mode
+ # Ensure backward compatibility (deprecate in future)
+ $result.backup = $result.backup_file
+ }
+ $node.set_InnerText($fragment)
+ if (-not $check_mode) {
$xmlorig.Save($dest)
- $result.msg = "text changed"
- } else {
- $result.msg = "text changed check mode"
}
+ $result.changed = $true
+ $result.msg = "text changed"
} else {
$result.msg = "not changed"
}
@@ -208,33 +203,35 @@ if ($type -eq "element") {
$node = $xmlorig.SelectSingleNode($xpath, $namespaceMgr)
[bool]$add = !$node.HasAttribute($attribute) -Or ($node.$attribute -ne $fragment)
if ($add -And ($state -eq "present")) {
- $result.changed = $true
- if (-Not $check_mode) {
- if ($backup) {
- $result.backup = BackupFile($dest)
- }
- if (!$node.HasAttribute($attribute)) {
- $node.SetAttributeNode($attribute, $xmlorig.get_DocumentElement().get_NamespaceURI())
- }
- $node.SetAttribute($attribute, $fragment)
+ if ($backup) {
+ $result.backup_file = Backup-File -path $dest -WhatIf:$check_mode
+ # Ensure backward compatibility (deprecate in future)
+ $result.backup = $result.backup_file
+ }
+ if (!$node.HasAttribute($attribute)) {
+ $node.SetAttributeNode($attribute, $xmlorig.get_DocumentElement().get_NamespaceURI())
+ }
+ $node.SetAttribute($attribute, $fragment)
+ if (-not $check_mode) {
$xmlorig.Save($dest)
- $result.msg = "text changed"
- } else {
- $result.msg = "text changed check mode"
}
- } elseif (!$add -And ($state -eq "absent")) {
$result.changed = $true
- if (-Not $check_mode) {
- if ($backup) {
- $result.backup = BackupFile($dest)
- }
- $node.RemoveAttribute($attribute)
+ $result.msg = "text changed"
+ } elseif (!$add -And ($state -eq "absent")) {
+ if ($backup) {
+ $result.backup_file = Backup-File -path $dest -WhatIf:$check_mode
+ # Ensure backward compatibility (deprecate in future)
+ $result.backup = $result.backup_file
+ }
+ $node.RemoveAttribute($attribute)
+ if (-not $check_mode) {
$xmlorig.Save($dest)
- $result.msg = "text changed"
}
+ $result.changed = $true
+ $result.msg = "text changed"
} else {
$result.msg = "not changed"
}
}
-Exit-Json $result \ No newline at end of file
+Exit-Json $result
diff --git a/lib/ansible/modules/windows/win_xml.py b/lib/ansible/modules/windows/win_xml.py
index 6af8dee0d4..3f24c09a58 100644
--- a/lib/ansible/modules/windows/win_xml.py
+++ b/lib/ansible/modules/windows/win_xml.py
@@ -23,7 +23,7 @@ options:
path:
description:
- The path of remote servers XML.
- type: str
+ type: path
required: true
aliases: [ dest, file ]
fragment:
@@ -39,7 +39,9 @@ options:
required: true
backup:
description:
- - Whether to backup the remote server's XML before applying the change.
+ - Determine whether a backup should be created.
+ - When set to C(yes), create a backup file including the timestamp information
+ so you can get the original file back if you somehow clobbered it incorrectly.
type: bool
default: no
type:
@@ -75,6 +77,11 @@ EXAMPLES = r'''
'''
RETURN = r'''
+backup_file:
+ description: Name of the backup file that was created.
+ returned: if backup=yes
+ type: str
+ sample: C:\Path\To\File.txt.11540.20150212-220915.bak
msg:
description: What was done.
returned: always
@@ -85,9 +92,4 @@ err:
returned: always, for type element and -vvv or more
type: list
sample: attribute mismatch for actual=string
-backup:
- description: Name of the backup file, if created.
- returned: changed
- type: str
- sample: C:\config.xml.19700101-000000
'''
diff --git a/lib/ansible/plugins/action/win_copy.py b/lib/ansible/plugins/action/win_copy.py
index df85e14a1a..adb918be29 100644
--- a/lib/ansible/plugins/action/win_copy.py
+++ b/lib/ansible/plugins/action/win_copy.py
@@ -260,7 +260,7 @@ class ActionModule(ActionBase):
if content is not None:
os.remove(content_tempfile)
- def _copy_single_file(self, local_file, dest, source_rel, task_vars, tmp):
+ def _copy_single_file(self, local_file, dest, source_rel, task_vars, tmp, backup):
if self._play_context.check_mode:
module_return = dict(changed=True)
return module_return
@@ -275,7 +275,8 @@ class ActionModule(ActionBase):
dest=dest,
src=tmp_src,
_original_basename=source_rel,
- _copy_mode="single"
+ _copy_mode="single",
+ backup=backup,
)
)
copy_args.pop('content', None)
@@ -286,7 +287,7 @@ class ActionModule(ActionBase):
return copy_result
- def _copy_zip_file(self, dest, files, directories, task_vars, tmp):
+ def _copy_zip_file(self, dest, files, directories, task_vars, tmp, backup):
# create local zip file containing all the files and directories that
# need to be copied to the server
if self._play_context.check_mode:
@@ -317,7 +318,8 @@ class ActionModule(ActionBase):
dict(
src=tmp_src,
dest=dest,
- _copy_mode="explode"
+ _copy_mode="explode",
+ backup=backup,
)
)
copy_args.pop('content', None)
@@ -342,6 +344,7 @@ class ActionModule(ActionBase):
local_follow = boolean(self._task.args.get('local_follow', False), strict=False)
force = boolean(self._task.args.get('force', True), strict=False)
decrypt = boolean(self._task.args.get('decrypt', True), strict=False)
+ backup = boolean(self._task.args.get('backup', False), strict=False)
result['src'] = source
result['dest'] = dest
@@ -383,7 +386,8 @@ class ActionModule(ActionBase):
_copy_mode="remote",
dest=dest,
src=source,
- force=force
+ force=force,
+ backup=backup,
)
)
new_module_args.pop('content', None)
@@ -472,7 +476,7 @@ class ActionModule(ActionBase):
force=force,
files=source_files['files'],
directories=source_files['directories'],
- symlinks=source_files['symlinks']
+ symlinks=source_files['symlinks'],
)
)
# src is not required for query, will fail path validation is src has unix allowed chars
@@ -493,20 +497,19 @@ class ActionModule(ActionBase):
# we only need to copy 1 file, don't mess around with zips
file_src = query_return['files'][0]['src']
file_dest = query_return['files'][0]['dest']
- copy_result = self._copy_single_file(file_src, dest, file_dest,
- task_vars, self._connection._shell.tmpdir)
-
+ result.update(self._copy_single_file(file_src, dest, file_dest,
+ task_vars, self._connection._shell.tmpdir, backup))
+ if result.get('failed') is True:
+ result['msg'] = "failed to copy file %s: %s" % (file_src, result['msg'])
result['changed'] = True
- if copy_result.get('failed') is True:
- result['failed'] = True
- result['msg'] = "failed to copy file %s: %s" % (file_src, copy_result['msg'])
+
elif len(query_return['files']) > 0 or len(query_return['directories']) > 0:
# either multiple files or directories need to be copied, compress
# to a zip and 'explode' the zip on the server
# TODO: handle symlinks
result.update(self._copy_zip_file(dest, source_files['files'],
source_files['directories'],
- task_vars, self._connection._shell.tmpdir))
+ task_vars, self._connection._shell.tmpdir, backup))
result['changed'] = True
else:
# no operations need to occur
diff --git a/test/integration/targets/win_copy/tasks/tests.yml b/test/integration/targets/win_copy/tasks/tests.yml
index 5e6726af44..7f286dbf27 100644
--- a/test/integration/targets/win_copy/tasks/tests.yml
+++ b/test/integration/targets/win_copy/tasks/tests.yml
@@ -172,6 +172,24 @@
that:
- copy_file_again is not changed
+- name: copy single file (backup)
+ win_copy:
+ content: "{{ lookup('file', 'foo.txt') }}\nfoo bar"
+ dest: '{{test_win_copy_path}}\foo-target.txt'
+ backup: yes
+ register: copy_file_backup
+
+- name: check backup_file
+ win_stat:
+ path: '{{ copy_file_backup.backup_file }}'
+ register: backup_file
+
+- name: assert copy single file (backup)
+ assert:
+ that:
+ - copy_file_backup is changed
+ - backup_file.stat.exists == true
+
- name: copy single file to folder (check mode)
win_copy:
src: foo.txt
diff --git a/test/integration/targets/win_lineinfile/tasks/main.yml b/test/integration/targets/win_lineinfile/tasks/main.yml
index a1d9622535..e5f047bec3 100644
--- a/test/integration/targets/win_lineinfile/tasks/main.yml
+++ b/test/integration/targets/win_lineinfile/tasks/main.yml
@@ -43,12 +43,17 @@
win_lineinfile: dest={{win_output_dir}}/test.txt state=present line="New line at the beginning" insertbefore="BOF" backup=yes
register: result
+- name: check backup_file
+ win_stat:
+ path: '{{ result.backup_file }}'
+ register: backup_file
+
- name: assert that the line was inserted at the head of the file
assert:
that:
- - "result.changed == true"
- - "result.msg == 'line added'"
- - "result.backup != ''"
+ - result.changed == true
+ - result.msg == 'line added'
+ - backup_file.stat.exists == true
- name: stat the backup file
win_stat: path={{result.backup}}
diff --git a/test/integration/targets/win_module_utils/library/backup_file_test.ps1 b/test/integration/targets/win_module_utils/library/backup_file_test.ps1
new file mode 100644
index 0000000000..5f920ab2bd
--- /dev/null
+++ b/test/integration/targets/win_module_utils/library/backup_file_test.ps1
@@ -0,0 +1,89 @@
+#!powershell
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#AnsibleRequires -CSharpUtil Ansible.Basic
+#Requires -Module Ansible.ModuleUtils.Backup
+
+$module = [Ansible.Basic.AnsibleModule]::Create($args, @{})
+
+Function Assert-Equals {
+ param(
+ [Parameter(Mandatory=$true, ValueFromPipeline=$true)][AllowNull()]$Actual,
+ [Parameter(Mandatory=$true, Position=0)][AllowNull()]$Expected
+ )
+
+ $matched = $false
+ if ($Actual -is [System.Collections.ArrayList] -or $Actual -is [Array]) {
+ $Actual.Count | Assert-Equals -Expected $Expected.Count
+ for ($i = 0; $i -lt $Actual.Count; $i++) {
+ $actual_value = $Actual[$i]
+ $expected_value = $Expected[$i]
+ Assert-Equals -Actual $actual_value -Expected $expected_value
+ }
+ $matched = $true
+ } else {
+ $matched = $Actual -ceq $Expected
+ }
+
+ if (-not $matched) {
+ if ($Actual -is [PSObject]) {
+ $Actual = $Actual.ToString()
+ }
+
+ $call_stack = (Get-PSCallStack)[1]
+ $module.Result.test = $test
+ $module.Result.actual = $Actual
+ $module.Result.expected = $Expected
+ $module.Result.line = $call_stack.ScriptLineNumber
+ $module.Result.method = $call_stack.Position.Text
+ $module.FailJson("AssertionError: actual != expected")
+ }
+}
+
+$tmp_dir = $module.Tmpdir
+
+$tests = @{
+ "Test backup file with missing file" = {
+ $actual = Backup-File -path (Join-Path -Path $tmp_dir -ChildPath "missing")
+ $actual | Assert-Equals -Expected $null
+ }
+
+ "Test backup file in check mode" = {
+ $orig_file = Join-Path -Path $tmp_dir -ChildPath "file-check.txt"
+ Set-Content -Path $orig_file -Value "abc"
+ $actual = Backup-File -path $orig_file -WhatIf
+
+ (Test-Path -LiteralPath $actual) | Assert-Equals -Expected $false
+
+ $parent_dir = Split-Path -Path $actual
+ $backup_file = Split-Path -Path $actual -Leaf
+ $parent_dir | Assert-Equals -Expected $tmp_dir
+ ($backup_file -match "^file-check\.txt\.$pid\.\d{8}-\d{6}\.bak$") | Assert-Equals -Expected $true
+ }
+
+ "Test backup file" = {
+ $content = "abc"
+ $orig_file = Join-Path -Path $tmp_dir -ChildPath "file.txt"
+ Set-Content -Path $orig_file -Value $content
+ $actual = Backup-File -path $orig_file
+
+ (Test-Path -LiteralPath $actual) | Assert-Equals -Expected $true
+
+ $parent_dir = Split-Path -Path $actual
+ $backup_file = Split-Path -Path $actual -Leaf
+ $parent_dir | Assert-Equals -Expected $tmp_dir
+ ($backup_file -match "^file\.txt\.$pid\.\d{8}-\d{6}\.bak$") | Assert-Equals -Expected $true
+ (Get-Content -Path $actual -Raw) | Assert-Equals -Expected "$content`r`n"
+ }
+}
+
+foreach ($test_impl in $tests.GetEnumerator()) {
+ $test = $test_impl.Key
+ &$test_impl.Value
+}
+
+$module.Result.res = 'success'
+
+$module.ExitJson()
diff --git a/test/integration/targets/win_module_utils/tasks/main.yml b/test/integration/targets/win_module_utils/tasks/main.yml
index 18fca76ccd..4e0735402e 100644
--- a/test/integration/targets/win_module_utils/tasks/main.yml
+++ b/test/integration/targets/win_module_utils/tasks/main.yml
@@ -163,3 +163,13 @@
that:
- not add_type_test is failed
- add_type_test.res == 'success'
+
+- name: call module with BackupFile tests
+ backup_file_test:
+ register: backup_file_test
+
+- name: assert call module with BackupFile tests
+ assert:
+ that:
+ - not backup_file_test is failed
+ - backup_file_test.res == 'success'
diff --git a/test/integration/targets/win_template/tasks/main.yml b/test/integration/targets/win_template/tasks/main.yml
index cd1829dfc4..2d34c1d05f 100644
--- a/test/integration/targets/win_template/tasks/main.yml
+++ b/test/integration/targets/win_template/tasks/main.yml
@@ -119,6 +119,39 @@
that:
- '"FC: no differences encountered" in diff_result.stdout'
+# TEST BACKUP
+- name: test backup (check_mode)
+ win_template:
+ src: foo.j2
+ dest: '{{ win_output_dir }}/foo.unix.templated'
+ backup: yes
+ register: cm_backup_result
+ check_mode: yes
+
+- name: verify that a backup_file was returned
+ assert:
+ that:
+ - cm_backup_result is changed
+ - cm_backup_result.backup_file is not none
+
+- name: test backup (normal mode)
+ win_template:
+ src: foo.j2
+ dest: '{{ win_output_dir }}/foo.unix.templated'
+ backup: yes
+ register: nm_backup_result
+
+- name: check backup_file
+ win_stat:
+ path: '{{ nm_backup_result.backup_file }}'
+ register: backup_file
+
+- name: verify that a backup_file was returned
+ assert:
+ that:
+ - nm_backup_result is changed
+ - backup_file.stat.exists == true
+
- name: create template dest directory
win_file:
path: '{{win_output_dir}}\directory'
diff --git a/test/integration/targets/win_xml/tasks/main.yml b/test/integration/targets/win_xml/tasks/main.yml
index 26c991a61e..77a9b9aa63 100644
--- a/test/integration/targets/win_xml/tasks/main.yml
+++ b/test/integration/targets/win_xml/tasks/main.yml
@@ -70,7 +70,7 @@
- name: check attribute change result
assert:
that:
- - not attribute_changed_result is changed
+ - attribute_changed_result is not changed
# This testing is for https://github.com/ansible/ansible/issues/48471
# The issue was that an .xml with no encoding declaration, but a UTF8 BOM
@@ -105,6 +105,26 @@
that:
- sha1_checksum.stat.checksum == 'e3e18c3066e1bfce9a5cf87c81353fa174440944'
+- name: change a text value in a file with UTF8 BOM and armenian characters in the description
+ win_xml:
+ path: "{{ win_output_dir }}\\plane-utf8-bom-armenian-characters.xml"
+ xpath: '/plane/year'
+ type: text
+ fragment: '1989'
+ backup: yes
+ register: test_backup
+
+- name: check backup_file
+ win_stat:
+ path: '{{ test_backup.backup_file }}'
+ register: backup_file
+
+- name: Check backup_file
+ assert:
+ that:
+ - test_backup is changed
+ - backup_file.stat.exists == true
+
- name: change a text value in a file with UTF-16 BE BOM and Chinese characters in the description
win_xml:
path: "{{ win_output_dir }}\\plane-utf16be-bom-chinese-characters.xml"