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) +# 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) - } - $xmlorig.Save($dest) - } else { - $result.msg += " check mode" + 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) + } + $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) - $xmlorig.Save($dest) - $result.msg = "text changed" - } else { - $result.msg = "text changed check mode" + 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.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) - $xmlorig.Save($dest) - $result.msg = "text changed" - } else { - $result.msg = "text changed check mode" + 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.changed = $true + $result.msg = "text changed" } elseif (!$add -And ($state -eq "absent")) { - $result.changed = $true - if (-Not $check_mode) { - if ($backup) { - $result.backup = BackupFile($dest) - } - $node.RemoveAttribute($attribute) - $xmlorig.Save($dest) - $result.msg = "text changed" + 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.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) +# 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"