win_get_url: Add use_proxy, headers and timeout (#26612)

* win_get_url: Add use_proxy, headers and timeout

This PR includes:
- Add use_proxy parameter
- Add timeout parameter
- Add headers parameter
- Simplify logic
- Create separate CheckModified-File
- Now use -LiteralPath instead of -Path
- Clean up documentation

* win_get_url: Add use_proxy, headers and timeout

This PR includes:
- Add use_proxy parameter
- Add timeout parameter
- Add headers parameter
- Simplify logic
- Create separate CheckModified-File
- Now use -LiteralPath instead of -Path
- Clean up documentation
pull/28747/head
Dag Wieers 7 years ago committed by Jordan Borean
parent b84a48caef
commit 61d2201a2d

@ -47,6 +47,8 @@ Ansible Changes By Release
* Those using ansible as a library should note that the `ansible.vars.unsafe_proxy`
module is deprecated and slated to go away in 2.8. The functionality has been
moved to `ansible.utils.unsafe_proxy` to avoid a circular import.
* The win_get_url module has the dictionary 'win_get_url' in its results deprecated,
its content is now also available directly in the resulting output, like other modules.
#### Deprecated Modules (to be removed in 2.8):
* ec2_facts: replaced by ec2_metadata_facts

@ -1,33 +1,126 @@
#!powershell
# This file is part of Ansible.
#
# (c)) 2015, Paul Durivage <paul.durivage@rackspace.com>, Tal Auslander <tal@cloudshare.com>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Copyright: (c) 2015, Paul Durivage <paul.durivage@rackspace.com>, Tal Auslander <tal@cloudshare.com>
# Copyright: (c) 2017, Dag Wieers <dag@wieers.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# WANT_JSON
# POWERSHELL_COMMON
$ErrorActionPreference = 'Stop'
Function CheckModified-File($url, $dest, $headers, $credentials, $timeout, $use_proxy, $proxy) {
$fileLastMod = ([System.IO.FileInfo]$dest).LastWriteTimeUtc
$webLastMod = $null
$webRequest = [System.Net.HttpWebRequest]::Create($url)
foreach ($header in $headers.GetEnumerator()) {
$webRequest.Headers.Add($header.Name, $header.Value)
}
if ($timeout) {
$webRequest.Timeout = $timeout * 1000
}
if (-not $use_proxy) {
# Ignore the system proxy settings
$webRequest.Proxy = $null
} elseif ($proxy) {
$webRequest.Proxy = $proxy
}
if ($credentials) {
$webRequest.Credentials = $credentials
}
$webRequest.Method = "HEAD"
Try {
[System.Net.HttpWebResponse]$webResponse = $webRequest.GetResponse()
$webLastMod = $webResponse.GetResponseHeader("Last-Modified")
} Catch [System.Net.WebException] {
$result.status_code = $_.Exception.Response.StatusCode
Fail-Json -obj $result -message "Error requesting '$url'. $($_.Exception.Message)"
} Catch {
Fail-Json -obj $result -message "Error when requesting 'Last-Modified' date from '$url'. $($_.Exception.Message)"
}
$result.status_code = $webResponse.StatusCode
$result.msg = $webResponse.StatusDescription
$webResponse.Close()
if ($webLastMod -and ((Get-Date -Date $webLastMod) -lt $fileLastMod)) {
return $false
} else {
return $true
}
}
Function Download-File($result, $url, $dest, $headers, $credentials, $timeout, $use_proxy, $proxy, $whatif) {
# Check $dest parent folder exists before attempting download, which avoids unhelpful generic error message.
$dest_parent = Split-Path -LiteralPath $dest
if (-not (Test-Path -LiteralPath $dest_parent -PathType Container)) {
Fail-Json -obj $result -message "The path '$dest_parent' does not exist for destination '$dest', or is not visible to the current user. Ensure download destination folder exists (perhaps using win_file state=directory) before win_get_url runs."
}
# TODO: Replace this with WebRequest
$webClient = New-Object System.Net.WebClient
foreach ($header in $headers.GetEnumerator()) {
$webClient.Headers.Add($header.Name, $header.Value)
}
# FIXME: WebClient has no Timeout property ? Should be replaced with WebRequest
# if ($timeout) {
# $webClient.Timeout = $timeout * 1000
# }
if (-not $use_proxy) {
# Ignore the system proxy settings
$webClient.Proxy = $null
} elseif ($proxy) {
$webClient.Proxy = $proxy
}
if ($credentials) {
$webClient.Credentials = $credentials
}
Try {
if (-not $whatif) {
$webClient.DownloadFile($url, $dest)
}
$result.changed = $true
} Catch [System.Net.WebException] {
$result.status_code = $_.Exception.Response.StatusCode
Fail-Json -obj $result -message "Error downloading '$url' to '$dest'. $($_.Exception.Message)"
} Catch {
Fail-Json -obj $result -message "Unknown error downloading '$url' to '$dest'. $($_.Exception.Message)"
}
# FIXME: Reimplement DownloadFile() using WebRequest so we get the real information
$result.status_code = 200
$result.msg = 'OK'
$result.dest = $dest
}
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$url = Get-AnsibleParam -obj $params -name "url" -type "str" -failifempty $true
$dest = Get-AnsibleParam -obj $params -name "dest" -type "path" -failifempty $true
$timeout = Get-AnsibleParam -obj $params -name "timeout" -type "int" -default 10
$headers = Get-AnsibleParam -obj $params -name "headers" -type "dict" -default @{}
$skip_certificate_validation = Get-AnsibleParam -obj $params -name "skip_certificate_validation" -type "bool"
$validate_certs = Get-AnsibleParam -obj $params -name "validate_certs" -type "bool" -default $true
$username = Get-AnsibleParam -obj $params -name "username" -type "str"
$password = Get-AnsibleParam -obj $params -name "password" -type "str"
$url_username = Get-AnsibleParam -obj $params -name "url_username" -type "str" -aliases "username"
$url_password = Get-AnsibleParam -obj $params -name "url_password" -type "str" -aliases "password"
$use_proxy = Get-AnsibleParam -obj $params -name "use_proxy" -type "bool" -default $true
$proxy_url = Get-AnsibleParam -obj $params -name "proxy_url" -type "str"
$proxy_username = Get-AnsibleParam -obj $params -name "proxy_username" -type "str"
$proxy_password = Get-AnsibleParam -obj $params -name "proxy_password" -type "str"
@ -35,63 +128,59 @@ $force = Get-AnsibleParam -obj $params -name "force" -type "bool" -default $true
$result = @{
changed = $false
dest = $dest
url = $url
# This is deprecated as of v2.4, remove in v2.8
win_get_url = @{
dest = $dest
url = $url
}
}
# If skip_certificate_validation was specified, use validate_certs
if ($skip_certificate_validation -ne $null) {
Add-DeprecationWarning -obj $result -message "The parameter 'skip_certificate_validation' is being replaced with 'validate_certs'" -version 2.8
$validate_certs = -not $skip_certificate_validation
}
if (-not $validate_certs) {
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
if (-not $use_proxy -and ($proxy_url -or $proxy_username -or $proxy_password)) {
Add-Warning -obj $result -msg "Not using a proxy on request, however a 'proxy_url', 'proxy_username' or 'proxy_password' was defined."
}
Function Download-File($result, $url, $dest, $username, $password, $proxy_url, $proxy_username, $proxy_password) {
# use last part of url for dest file name if a directory is supplied for $dest
If ( Test-Path -PathType Container $dest ) {
$url_basename = Split-Path -leaf $url
If ( $url_basename.Length -gt 0 ) {
$dest = Join-Path -Path $dest -ChildPath $url_basename
$result.win_get_url.actual_dest = $dest
}
}
# check $dest parent folder exists before attempting download, which avoids unhelpful generic error message.
$dest_parent = Split-Path -Path $dest
$result.win_get_url.dest_parent = $dest_parent
If ( -not (Test-Path -Path $dest_parent -PathType Container)) {
$result.changed = $false
Fail-Json $result "The path '$dest_parent' does not exist for destination '$dest', or is not visible to the current user. Ensure download destination folder exists (perhaps using win_file state=directory) before win_get_url runs."
}
$webClient = New-Object System.Net.WebClient
$proxy = $null
if ($proxy_url) {
$proxy_server = New-Object System.Net.WebProxy($proxy_url, $true)
$proxy = New-Object System.Net.WebProxy($proxy_url, $true)
if ($proxy_username -and $proxy_password) {
$proxy_credential = New-Object System.Net.NetworkCredential($proxy_username, $proxy_password)
$proxy_server.Credentials = $proxy_credential
$proxy.Credentials = $proxy_credential
}
$webClient.Proxy = $proxy_server
}
if($username -and $password){
$webClient.Credentials = New-Object System.Net.NetworkCredential($username, $password)
$credentials = $null
if ($url_username -and $url_password) {
$credentials = New-Object System.Net.NetworkCredential($url_username, $url_password)
}
Try {
if (-not $check_mode) {
$webClient.DownloadFile($url, $dest)
# If skip_certificate_validation was specified, use validate_certs
if ($skip_certificate_validation -ne $null) {
Add-DeprecationWarning -obj $result -message "The parameter 'skip_certificate_validation' is being replaced with 'validate_certs'" -version 2.8
$validate_certs = -not $skip_certificate_validation
}
$result.changed = $true
if (-not $validate_certs) {
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
}
Catch {
Fail-Json $result "Error downloading $url to $dest $($_.Exception.Message)"
# Use last part of url for dest file name if a directory is supplied for $dest
if (Test-Path -LiteralPath $dest -PathType Container) {
$uri = [System.Uri]$url
$basename = Split-Path -Path $uri.LocalPath -Leaf
if ($uri.LocalPath -and $uri.LocalPath -ne '/' -and $basename) {
$url_basename = Split-Path -Path $uri.LocalPath -Leaf
$dest = Join-Path -Path $dest -ChildPath $url_basename
} else {
$dest = Join-Path -Path $dest -ChildPath $uri.Host
}
} elseif (([System.IO.Path]::GetFileName($dest)) -eq '') {
# We have a trailing path separator
Fail-Json -obj $result -message "The destination path '$dest' does not exist, or is not visible to the current user. Ensure download destination folder exists (perhaps using win_file state=directory) before win_get_url runs."
}
$result.dest = $dest
$result.win_get_url.dest = $dest
# Enable TLS1.1/TLS1.2 if they're available but disabled (eg. .NET 4.5)
$security_protcols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault
@ -103,45 +192,24 @@ if ([Net.SecurityProtocolType].GetMember("Tls12").Count -gt 0) {
}
[Net.ServicePointManager]::SecurityProtocol = $security_protcols
If ($force -or -not (Test-Path -Path $dest)) {
if ($force -or -not (Test-Path -LiteralPath $dest)) {
Download-File -result $result -url $url -dest $dest -username $username -password $password -proxy_url $proxy_url -proxy_username $proxy_username -proxy_password $proxy_password
}
Else {
$fileLastMod = ([System.IO.FileInfo]$dest).LastWriteTimeUtc
$webLastMod = $null
Download-File -result $result -url $url -dest $dest -credentials $credentials `
-headers $headers -timeout $timeout -use_proxy $use_proxy -proxy $proxy `
-whatif $check_mode
Try {
$webRequest = [System.Net.HttpWebRequest]::Create($url)
if ($proxy_url) {
$proxy_server = New-Object System.Net.WebProxy($proxy_url, $true)
if ($proxy_username -and $proxy_password) {
$proxy_credential = New-Object System.Net.NetworkCredential($proxy_username, $proxy_password)
$proxy_server.Credentials = $proxy_credential
}
$webRequest.Proxy = $proxy_server
}
} else {
if($username -and $password){
$webRequest.Credentials = New-Object System.Net.NetworkCredential($username, $password)
}
$is_modified = CheckModified-File -result $result -url $url -dest $dest -credentials $credentials `
-headers $headers -timeout $timeout -use_proxy $use_proxy -proxy $proxy
$webRequest.Method = "HEAD"
[System.Net.HttpWebResponse]$webResponse = $webRequest.GetResponse()
if ($is_modified) {
$webLastMod = $webResponse.GetResponseHeader("Last-Modified")
$webResponse.Close()
}
Catch {
Fail-Json $result "Error when requesting Last-Modified date from $url $($_.Exception.Message)"
}
Download-File -result $result -url $url -dest $dest -credentials $credentials `
-headers $headers -timeout $timeout -use_proxy $use_proxy -proxy $proxy `
-whatif $check_mode
If (($webLastMod) -and ((Get-Date -Date $webLastMod ) -lt $fileLastMod)) {
$result.changed = $false
} Else {
Download-File -result $result -url $url -dest $dest -username $username -password $password -proxy_url $proxy_url -proxy_username $proxy_username -proxy_password $proxy_password
}
}
Exit-Json $result

@ -31,75 +31,84 @@ module: win_get_url
version_added: "1.7"
short_description: Fetches a file from a given URL
description:
- Fetches a file from a URL and saves to locally
- Fetches a file from a URL and saves it locally.
- For non-Windows targets, use the M(get_url) module instead.
author:
- "Paul Durivage (@angstwad)"
- "Takeshi Kuramochi (tksarah)"
- Paul Durivage (@angstwad)
- Takeshi Kuramochi (tksarah)
options:
url:
description:
- The full URL of a file to download
required: true
default: null
- The full URL of a file to download.
required: yes
dest:
description:
- The absolute path of the location to save the file at the URL. Be sure
to include a filename and extension as appropriate.
required: true
default: null
- The location to save the file at the URL.
- Be sure to include a filename and extension as appropriate.
required: yes
force:
description:
- If C(yes), will always download the file. If C(no), will only
download the file if it does not exist or the remote file has been
modified more recently than the local file. This works by sending
an http HEAD request to retrieve last modified time of the requested
resource, so for this to work, the remote web server must support
HEAD requests.
modified more recently than the local file.
- This works by sending an http HEAD request to retrieve last modified
time of the requested resource, so for this to work, the remote web
server must support HEAD requests.
type: bool
default: 'yes'
version_added: "2.0"
required: false
choices: [ "yes", "no" ]
default: yes
username:
headers:
description:
- Add custom HTTP headers to a request (as a dictionary).
version_added: '2.4'
url_username:
description:
- Basic authentication username
required: false
default: null
password:
- Basic authentication username.
aliases: [ username ]
url_password:
description:
- Basic authentication password
required: false
default: null
- Basic authentication password.
aliases: [ password ]
skip_certificate_validation:
description:
- This option is deprecated since v2.4, please use C(validate_certs) instead.
- If C(yes), SSL certificates will not be validated. This should only be used
on personally controlled sites using self-signed certificates.
default: 'no'
type: bool
default: 'no'
validate_certs:
description:
- If C(no), SSL certificates will not be validated. This should only be used
on personally controlled sites using self-signed certificates.
- If C(skip_certificate_validation) was set, it overrides this option.
default: 'yes'
type: bool
default: 'yes'
version_added: '2.4'
proxy_url:
description:
- The full URL of the proxy server to download through.
version_added: "2.0"
required: false
proxy_username:
description:
- Proxy authentication username
- Proxy authentication username.
version_added: "2.0"
required: false
proxy_password:
description:
- Proxy authentication password
- Proxy authentication password.
version_added: "2.0"
required: false
use_proxy:
description:
- If C(no), it will not use a proxy, even if one is defined in an environment
variable on the target hosts.
type: bool
default: 'yes'
version_added: '2.4'
# TODO: Once we have implemented DownloadFile() using $WebRequest, enable timeout again
# timeout:
# description:
# - Timeout in seconds for URL request.
# default: 10
# version_added : '2.5'
notes:
- For non-Windows targets, use the M(get_url) module instead.
'''
@ -126,14 +135,24 @@ EXAMPLES = r'''
'''
RETURN = r'''
dest:
description: destination file/path
returned: always
type: string
sample: C:\Users\RandomUser\earthrise.jpg
url:
description: requested url
returned: always
type: string
sample: http://www.example.com/earthrise.jpg
dest:
description: destination file/path
msg:
description: Error message, or HTTP status message from web-server
returned: always
type: string
sample: C:\Users\RandomUser\earthrise.jpg
sample: OK
status_code:
description: HTTP status code
returned: always
type: int
sample: 200
'''

@ -5,4 +5,4 @@ test_win_get_url_link: "https://{{test_win_get_url_host}}"
test_win_get_url_invalid_link: https://www.redhat.com/skynet_module.html
test_win_get_url_invalid_path: 'Q:\Filez\Cyberdyne.html'
test_win_get_url_invalid_path_dir: 'Q:\Filez\'
test_win_get_url_path: '{{ test_win_get_url_dir_path }}\docs_index.html'
test_win_get_url_path: '%TEMP%\docs_index.html'

@ -16,120 +16,128 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: get tempdir path
raw: $env:TEMP
register: tempdir
- setup:
- name: set output path dynamically
set_fact:
test_win_get_url_dir_path: "{{ tempdir.stdout_lines[0] }}"
- name: remove test file if it exists
raw: >
PowerShell -Command Remove-Item "{{test_win_get_url_path}}" -Force
ignore_errors: true
- name: Remove test file if it exists
win_file:
path: '{{ test_win_get_url_path }}'
state: absent
- name: test win_get_url module
- name: Test win_get_url module
win_get_url:
url: "{{test_win_get_url_link}}"
dest: "{{test_win_get_url_path}}"
url: '{{ test_win_get_url_link }}'
dest: '{{ test_win_get_url_path }}'
register: win_get_url_result
- name: check that url was downloaded
- name: Check that url was downloaded
assert:
that:
- "not win_get_url_result|failed"
- "win_get_url_result|changed"
- "win_get_url_result.win_get_url.url"
- "win_get_url_result.win_get_url.dest"
- not win_get_url_result|failed
- win_get_url_result|changed
- win_get_url_result.url
- win_get_url_result.dest
- name: test win_get_url module again (force should be yes by default)
- name: Test win_get_url module again (force should be yes by default)
win_get_url:
url: "{{test_win_get_url_link}}"
dest: "{{test_win_get_url_path}}"
url: '{{ test_win_get_url_link }}'
dest: '{{ test_win_get_url_path }}'
register: win_get_url_result_again
- name: check that url was downloaded again
- name: Check that url was downloaded again
assert:
that:
- "not win_get_url_result_again|failed"
- "win_get_url_result_again|changed"
- not win_get_url_result_again|failed
- win_get_url_result_again|changed
- name: test win_get_url module again with force=no
- name: Test win_get_url module again with force=no
win_get_url:
url: "{{test_win_get_url_link}}"
dest: "{{test_win_get_url_path}}"
url: '{{ test_win_get_url_link }}'
dest: '{{ test_win_get_url_path }}'
force: no
register: win_get_url_result_noforce
- name: check that url was not downloaded again
- name: Check that url was not downloaded again
assert:
that:
- "not win_get_url_result_noforce|failed"
- "not win_get_url_result_noforce|changed"
- not win_get_url_result_noforce|failed
- not win_get_url_result_noforce|changed
- name: test win_get_url module with url that returns a 404
- name: Test win_get_url module with url that returns a 404
win_get_url:
url: "{{test_win_get_url_invalid_link}}"
dest: "{{test_win_get_url_path}}"
url: '{{ test_win_get_url_invalid_link }}'
dest: '{{ test_win_get_url_path }}'
register: win_get_url_result_invalid_link
ignore_errors: true
- name: check that the download failed for an invalid url
- name: Check that the download failed for an invalid url
assert:
that:
- "win_get_url_result_invalid_link|failed"
- win_get_url_result_invalid_link|failed
- win_get_url_result_invalid_link.status_code == 404
- name: test win_get_url module with an invalid path
- name: Test win_get_url module with an invalid path
win_get_url:
url: "{{test_win_get_url_link}}"
dest: "{{test_win_get_url_invalid_path}}"
url: '{{ test_win_get_url_link }}'
dest: '{{ test_win_get_url_invalid_path }}'
register: win_get_url_result_invalid_path
ignore_errors: true
- name: check that the download failed for an invalid path
- name: Check that the download failed for an invalid path
assert:
that:
- "win_get_url_result_invalid_path|failed"
- win_get_url_result_invalid_path|failed
- name: test win_get_url module with a valid path that is a directory
- name: Test win_get_url module with a valid path that is a directory
win_get_url:
url: "{{test_win_get_url_link}}"
dest: "{{test_win_get_url_dir_path}}"
url: '{{ test_win_get_url_link }}'
dest: '%TEMP%'
register: win_get_url_result_dir_path
ignore_errors: true
- name: check that the download did NOT fail, even though dest was directory
- name: Check that the download did NOT fail, even though dest was directory
assert:
that:
- "win_get_url_result_dir_path|changed"
- win_get_url_result_dir_path|changed
- name: test win_get_url with a valid url path and a dest that is a directory (from 2.4 should use url path as filename)
- name: Test win_get_url with a valid url path and a dest that is a directory (from 2.4 should use url path as filename)
win_get_url:
url: "{{test_win_get_url_link}}"
dest: "{{test_win_get_url_dir_path}}"
url: '{{ test_win_get_url_link }}'
dest: '%TEMP%'
register: win_get_url_result_dir_path_urlpath
ignore_errors: true
- name: set expected destination path fact
- name: Set expected destination path fact
set_fact:
expected_dest_path: '{{test_win_get_url_dir_path}}\{{test_win_get_url_host}}'
expected_dest_path: '{{ ansible_env.TEMP }}\{{ test_win_get_url_host }}'
- name: check that the download succeeded (changed) and dest is as expected
- name: Check that the download succeeded (changed) and dest is as expected
assert:
that:
- "win_get_url_result_dir_path_urlpath|changed"
- "win_get_url_result_dir_path_urlpath.win_get_url.actual_dest==expected_dest_path"
#- name: since 2.4 check you get a helpful message if the parent folder of the dest doesnt exist
# win_get_url:
# url: "{{test_win_get_url_link}}"
# dest: "{{test_win_get_url_invalid_path_dir}}"
# register: win_get_url_result_invalid_dest
# ignore_errors: true
#
#- name: check if dest parent dir does not exist, module fails and you get a specific error message
# assert:
# that:
# - "win_get_url_result_invalid_dest|failed"
# - "win_get_url_result_invalid_dest.msg is search('does not exist')"
- win_get_url_result_dir_path_urlpath|changed
- win_get_url_result_dir_path_urlpath.dest == expected_dest_path
- name: Check you get a helpful message if the parent folder of the dest doesn't exist
win_get_url:
url: '{{ test_win_get_url_link }}'
dest: 'Q:\Filez\'
register: win_get_url_result_invalid_dest
ignore_errors: true
- name: Check if dest parent dir does not exist, module fails and you get a specific error message
assert:
that:
- win_get_url_result_invalid_dest|failed
- win_get_url_result_invalid_dest.msg is search('invalid path')
- name: Check you get a helpful message if the parent folder of the dest doesn't exist
win_get_url:
url: '{{ test_win_get_url_link }}'
dest: 'C:\Filez\'
register: win_get_url_result_invalid_dest2
ignore_errors: true
- name: Check if dest parent dir does not exist, module fails and you get a specific error message
assert:
that:
- win_get_url_result_invalid_dest2|failed
- win_get_url_result_invalid_dest2.msg is search('does not exist')

Loading…
Cancel
Save