mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
win_uri: fixes (#35487)
* win_uri: moved away from Invoke-WebRequest and fixed multiple issues * fixes from review
This commit is contained in:
parent
63fdc3f08f
commit
a0178b79f1
9 changed files with 528 additions and 134 deletions
|
@ -214,6 +214,8 @@ Function Get-AnsibleParam($obj, $name, $default = $null, $resultobj = @{}, $fail
|
||||||
} elseif ($value -is [string]) {
|
} elseif ($value -is [string]) {
|
||||||
# Convert string type to real Powershell array
|
# Convert string type to real Powershell array
|
||||||
$value = $value.Split(",").Trim()
|
$value = $value.Split(",").Trim()
|
||||||
|
} elseif ($value -is [int]) {
|
||||||
|
$value = @($value)
|
||||||
} else {
|
} else {
|
||||||
Fail-Json -obj $resultobj -message "Get-AnsibleParam: Parameter '$name' is not a YAML list."
|
Fail-Json -obj $resultobj -message "Get-AnsibleParam: Parameter '$name' is not a YAML list."
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,141 +5,252 @@
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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.Legacy
|
||||||
|
#Requires -Module Ansible.ModuleUtils.CamelConversion
|
||||||
|
#Requires -Module Ansible.ModuleUtils.FileUtil
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
$safe_methods = @("GET", "HEAD")
|
|
||||||
$content_keys = @("Content", "Images", "InputFields", "Links", "RawContent")
|
|
||||||
|
|
||||||
Function ConvertTo-SnakeCase($input_string) {
|
|
||||||
$snake_case = $input_string -csplit "(?<!^)(?=[A-Z])" -join "_"
|
|
||||||
return $snake_case.ToLower()
|
|
||||||
}
|
|
||||||
|
|
||||||
$params = Parse-Args -arguments $args -supports_check_mode $true
|
$params = Parse-Args -arguments $args -supports_check_mode $true
|
||||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
$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
|
$url = Get-AnsibleParam -obj $params -name "url" -type "str" -failifempty $true
|
||||||
$method = Get-AnsibleParam -obj $params "method" -type "str" -default "GET" -validateset "CONNECT","DELETE","GET","HEAD","OPTIONS","PATCH","POST","PUT","REFRESH","TRACE"
|
$method = Get-AnsibleParam -obj $params "method" -type "str" -default "GET" -validateset "CONNECT","DELETE","GET","HEAD","MERGE","OPTIONS","PATCH","POST","PUT","REFRESH","TRACE"
|
||||||
$content_type = Get-AnsibleParam -obj $params -name "content_type" -type "str"
|
$content_type = Get-AnsibleParam -obj $params -name "content_type" -type "str"
|
||||||
$headers = Get-AnsibleParam -obj $params -name "headers" -type="dict"
|
$headers = Get-AnsibleParam -obj $params -name "headers"
|
||||||
$body = Get-AnsibleParam -obj $params -name "body" -type "dict"
|
$body = Get-AnsibleParam -obj $params -name "body"
|
||||||
$dest = Get-AnsibleParam -obj $params -name "dest" -type "path"
|
$dest = Get-AnsibleParam -obj $params -name "dest" -type "path"
|
||||||
|
|
||||||
$user = Get-AnsibleParam -obj $params -name "user" -type "str"
|
$user = Get-AnsibleParam -obj $params -name "user" -type "str"
|
||||||
$password = Get-AnsibleParam -obj $params -name "password" -type "str"
|
$password = Get-AnsibleParam -obj $params -name "password" -type "str"
|
||||||
|
$force_basic_auth = Get-AnsibleParam -obj $params -name "force_basic_auth" -type "bool" -default $false
|
||||||
|
|
||||||
$creates = Get-AnsibleParam -obj $params -name "creates" -type "path"
|
$creates = Get-AnsibleParam -obj $params -name "creates" -type "path"
|
||||||
$removes = Get-AnsibleParam -obj $params -name "removes" -type "path"
|
$removes = Get-AnsibleParam -obj $params -name "removes" -type "path"
|
||||||
|
|
||||||
$follow_redirects = Get-AnsibleParam -obj $params -name "follow_redirects" -type "str" -default "safe" -validateset "all","none","safe"
|
$follow_redirects = Get-AnsibleParam -obj $params -name "follow_redirects" -type "str" -default "safe" -validateset "all","none","safe"
|
||||||
$maximum_redirection = Get-AnsibleParam -obj $params -name "maximum_redirection" -type "int" -default 5
|
$maximum_redirection = Get-AnsibleParam -obj $params -name "maximum_redirection" -type "int" -default 50
|
||||||
$return_content = Get-AnsibleParam -obj $params -name "return_content" -type "bool" -default $false
|
$return_content = Get-AnsibleParam -obj $params -name "return_content" -type "bool" -default $false
|
||||||
$status_code = Get-AnsibleParam -obj $params -name "status_code" -type "list" -default @(200)
|
$status_code = Get-AnsibleParam -obj $params -name "status_code" -type "list" -default @(200)
|
||||||
$timeout = Get-AnsibleParam -obj $params -name "timeout" -type "int" -default 30
|
$timeout = Get-AnsibleParam -obj $params -name "timeout" -type "int" -default 30
|
||||||
$use_basic_parsing = Get-AnsibleParam -obj $params -name "use_basic_parsing" -type "bool" -default $true
|
$use_basic_parsing = Get-AnsibleParam -obj $params -name "use_basic_parsing" -type "bool"
|
||||||
$validate_certs = Get-AnsibleParam -obj $params -name "validate_certs" -type "bool" -default $true
|
$validate_certs = Get-AnsibleParam -obj $params -name "validate_certs" -type "bool" -default $true
|
||||||
$client_cert = Get-AnsibleParam -obj $params -name "client_cert" -type "path"
|
$client_cert = Get-AnsibleParam -obj $params -name "client_cert" -type "path"
|
||||||
|
$client_cert_password = Get-AnsibleParam -obj $params -name "client_cert_password" -type "str"
|
||||||
|
|
||||||
if ($creates -and (Test-Path -Path $creates)) {
|
if ($creates -and (Test-AnsiblePath -Path $creates)) {
|
||||||
$result.skipped = $true
|
$result.skipped = $true
|
||||||
Exit-Json -obj $result -message "The 'creates' file or directory ($creates) already exists."
|
Exit-Json -obj $result -message "The 'creates' file or directory ($creates) already exists."
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($removes -and -not (Test-Path -Path $removes)) {
|
if ($removes -and -not (Test-AnsiblePath -Path $removes)) {
|
||||||
$result.skipped = $true
|
$result.skipped = $true
|
||||||
Exit-Json -obj $result -message "The 'removes' file or directory ($removes) does not exist."
|
Exit-Json -obj $result -message "The 'removes' file or directory ($removes) does not exist."
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = @{
|
$result = @{
|
||||||
changed = $false
|
changed = $false
|
||||||
content_type = $content_type
|
|
||||||
method = $method
|
|
||||||
url = $url
|
url = $url
|
||||||
use_basic_parsing = $use_basic_parsing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($use_basic_parsing) {
|
||||||
|
Add-DeprecationWarning -obj $result -message "Since Ansible 2.5, use_basic_parsing does not change any behaviour, this option will be removed" -version 2.7
|
||||||
|
}
|
||||||
|
|
||||||
|
$client = [System.Net.WebRequest]::Create($url)
|
||||||
|
$client.Method = $method
|
||||||
|
$client.Timeout = $timeout * 1000
|
||||||
|
|
||||||
# Disable redirection if requested
|
# Disable redirection if requested
|
||||||
switch($follow_redirects) {
|
switch($follow_redirects) {
|
||||||
"none" {
|
"none" {
|
||||||
$maximum_redirection = 0
|
$client.AllowAutoRedirect = $false
|
||||||
}
|
}
|
||||||
"safe" {
|
"safe" {
|
||||||
if ($safe_methods -notcontains $method) {
|
if (@("GET", "HEAD") -notcontains $method) {
|
||||||
$maximum_redirection = 0
|
$client.AllowAutoRedirect = $false
|
||||||
|
} else {
|
||||||
|
$client.AllowAutoRedirect = $true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
default {
|
||||||
|
$client.AllowAutoRedirect = $true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if ($maximum_redirection -eq 0) {
|
||||||
$webrequest_opts = @{
|
# 0 is not a valid option, need to disable redirection through AllowAutoRedirect
|
||||||
ContentType = $content_type
|
$client.AllowAutoRedirect = $false
|
||||||
ErrorAction = "SilentlyContinue"
|
} else {
|
||||||
MaximumRedirection = $maximum_redirection
|
$client.MaximumAutomaticRedirections = $maximum_redirection
|
||||||
Method = $method
|
|
||||||
TimeoutSec = $timeout
|
|
||||||
Uri = $url
|
|
||||||
UseBasicParsing = $use_basic_parsing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $validate_certs) {
|
if (-not $validate_certs) {
|
||||||
$PSDefaultParameterValues.Add("Invoke-WebRequest:SkipCertificateCheck", $true)
|
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enable TLS1.1/TLS1.2 if they're available but disabled (eg. .NET 4.5)
|
||||||
|
$security_protcols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault
|
||||||
|
if ([Net.SecurityProtocolType].GetMember("Tls11").Count -gt 0) {
|
||||||
|
$security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls11
|
||||||
|
}
|
||||||
|
if ([Net.SecurityProtocolType].GetMember("Tls12").Count -gt 0) {
|
||||||
|
$security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls12
|
||||||
|
}
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = $security_protcols
|
||||||
|
|
||||||
|
|
||||||
|
if ($null -ne $content_type) {
|
||||||
|
$client.ContentType = $content_type
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($headers) {
|
if ($headers) {
|
||||||
$req_headers = @{}
|
$req_headers = New-Object -TypeName System.Net.WebHeaderCollection
|
||||||
ForEach ($header in $headers.psobject.properties) {
|
foreach ($header in $headers.GetEnumerator()) {
|
||||||
$req_headers.Add($header.Name, $header.Value)
|
# some headers need to be set on the property itself
|
||||||
|
switch ($header.Name) {
|
||||||
|
Accept { $client.Accept = $header.Value }
|
||||||
|
Connection { $client.Connection = $header.Value }
|
||||||
|
Content-Length { $client.ContentLength = $header.Value }
|
||||||
|
Content-Type { $client.ContentType = $header.Value }
|
||||||
|
Expect { $client.Expect = $header.Value }
|
||||||
|
Date { $client.Date = $header.Value }
|
||||||
|
Host { $client.Host = $header.Value }
|
||||||
|
If-Modified-Since { $client.IfModifiedSince = $header.Value }
|
||||||
|
Range { $client.AddRange($header.Value) }
|
||||||
|
Referer { $client.Referer = $header.Value }
|
||||||
|
Transfer-Encoding {
|
||||||
|
$client.SendChunked = $true
|
||||||
|
$client.TransferEncoding = $header.Value
|
||||||
|
}
|
||||||
|
User-Agent { $client.UserAgent = $header.Value }
|
||||||
|
default { $req_headers.Add($header.Name, $header.Value) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$webrequest_opts.Headers = $req_headers
|
$client.Headers = $req_headers
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($client_cert) {
|
if ($client_cert) {
|
||||||
Try {
|
if (-not (Test-AnsiblePath -Path $client_cert)) {
|
||||||
$webrequest_opts.Certificate = Get-PfxCertificate -FilePath $client_cert
|
Fail-Json -obj $result -message "Client certificate '$client_cert' does not exist"
|
||||||
} Catch {
|
}
|
||||||
Fail-Json -obj $result -message "Failed to read client certificate '$client_cert'"
|
try {
|
||||||
|
$certs = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2Collection -ArgumentList $client_cert, $client_cert_password
|
||||||
|
$client.ClientCertificates = $certs
|
||||||
|
} catch [System.Security.Cryptography.CryptographicException] {
|
||||||
|
Fail-Json -obj $result -message "Failed to read client certificate '$client_cert': $($_.Exception.Message)"
|
||||||
|
} catch {
|
||||||
|
Fail-Json -obj $result -message "Unhandled exception when reading client certificate at '$client_cert': $($_.Exception.Message)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($body) {
|
|
||||||
$webrequest_opts.Body = $body
|
|
||||||
$result.body = $body
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($dest -and -not $check_mode) {
|
|
||||||
$webrequest_opts.OutFile = $dest
|
|
||||||
$webrequest_opts.PassThru = $true
|
|
||||||
$result.dest = $dest
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($user -and $password) {
|
if ($user -and $password) {
|
||||||
$webrequest_opts.Credential = New-Object System.Management.Automation.PSCredential($user, $($password | ConvertTo-SecureString -AsPlainText -Force))
|
if ($force_basic_auth) {
|
||||||
|
$basic_value = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("$($user):$($password)"))
|
||||||
|
$client.Headers.Add("Authorization", "Basic $basic_value")
|
||||||
|
} else {
|
||||||
|
$sec_password = ConvertTo-SecureString -String $password -AsPlainText -Force
|
||||||
|
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $sec_password
|
||||||
|
$client.Credentials = $credential
|
||||||
|
}
|
||||||
} elseif ($user -or $password) {
|
} elseif ($user -or $password) {
|
||||||
Add-Warning -obj $result -message "Both 'user' and 'password' parameters are required together, skipping authentication"
|
Add-Warning -obj $result -message "Both 'user' and 'password' parameters are required together, skipping authentication"
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if ($null -ne $body) {
|
||||||
$response = Invoke-WebRequest @webrequest_opts
|
if ($body -is [Hashtable]) {
|
||||||
} catch {
|
$body_string = ConvertTo-Json -InputObject $body -Compress
|
||||||
Fail-Json $result $_.Exception.Message
|
} elseif ($body -isnot [String]) {
|
||||||
|
$body_string = $body.ToString()
|
||||||
|
} else {
|
||||||
|
$body_string = $body
|
||||||
|
}
|
||||||
|
$buffer = [System.Text.Encoding]::UTF8.GetBytes($body_string)
|
||||||
|
|
||||||
|
$req_st = $client.GetRequestStream()
|
||||||
|
try {
|
||||||
|
$req_st.Write($buffer, 0, $buffer.Length)
|
||||||
|
} finally {
|
||||||
|
$req_st.Flush()
|
||||||
|
$req_st.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: When writing to a file, this is not idempotent !
|
try {
|
||||||
# FIXME: Assume a change when we are writing to a file
|
$response = $client.GetResponse()
|
||||||
if ($dest) {
|
} catch [System.Net.ProtocolViolationException] {
|
||||||
$result.changed = $true
|
Fail-Json -obj $result -message "ProtocolViolationException when sending web request: $($_.Exception.Message)"
|
||||||
|
} catch [System.Net.WebException] {
|
||||||
|
Fail-Json -obj $result -message "WebException occurred when sending web request: $($_.Exception.Message)"
|
||||||
|
} catch {
|
||||||
|
Fail-Json -obj $result -message "Unhandled exception occured when sending web request. Exception: $($_.Exception.Message)"
|
||||||
}
|
}
|
||||||
|
|
||||||
ForEach ($prop in $response.psobject.properties) {
|
ForEach ($prop in $response.psobject.properties) {
|
||||||
if ($content_keys -contains $prop.Name -and -not $return_content) {
|
$result_key = Convert-StringToSnakeCase -string $prop.Name
|
||||||
continue
|
|
||||||
}
|
|
||||||
$result_key = ConvertTo-SnakeCase $prop.Name
|
|
||||||
$result.$result_key = $prop.Value
|
$result.$result_key = $prop.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# manually get the headers as not all of them are in the response properties
|
||||||
|
foreach ($header_key in $response.Headers.GetEnumerator()) {
|
||||||
|
$header_value = $response.Headers[$header_key]
|
||||||
|
$header_key = $header_key.Replace("-", "") # replace - with _ for snake conversion
|
||||||
|
$header_key = Convert-StringToSnakeCase -string $header_key
|
||||||
|
$result.$header_key = $header_value
|
||||||
|
}
|
||||||
|
|
||||||
if ($status_code -notcontains $response.StatusCode) {
|
if ($status_code -notcontains $response.StatusCode) {
|
||||||
Fail-Json -obj $result -message "Status code of request '$($response.StatusCode)' is not in list of valid status codes $status_code."
|
Fail-Json -obj $result -message "Status code of request '$($response.StatusCode)' is not in list of valid status codes $status_code."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# we only care about the return body if we need to return the content or create a file
|
||||||
|
if ($return_content -or $dest) {
|
||||||
|
$resp_st = $response.GetResponseStream()
|
||||||
|
|
||||||
|
# copy to a MemoryStream so we can read it multiple times
|
||||||
|
$memory_st = New-Object -TypeName System.IO.MemoryStream
|
||||||
|
try {
|
||||||
|
$resp_st.CopyTo($memory_st)
|
||||||
|
$resp_st.Close()
|
||||||
|
|
||||||
|
if ($return_content) {
|
||||||
|
$memory_st.Seek(0, [System.IO.SeekOrigin]::Begin)
|
||||||
|
$content_bytes = $memory_st.ToArray()
|
||||||
|
$result.content = [System.Text.Encoding]::UTF8.GetString($content_bytes)
|
||||||
|
if ($result.ContainsKey("content_type") -and $result.content_type -in @("application/json", "application/javascript")) {
|
||||||
|
$result.json = ConvertFrom-Json -InputObject $result.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dest) {
|
||||||
|
$memory_st.Seek(0, [System.IO.SeekOrigin]::Begin)
|
||||||
|
$changed = $true
|
||||||
|
|
||||||
|
if (Test-AnsiblePath -Path $dest) {
|
||||||
|
$actual_checksum = Get-FileChecksum -path $dest -algorithm "sha1"
|
||||||
|
|
||||||
|
$sp = New-Object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider
|
||||||
|
$content_checksum = [System.BitConverter]::ToString($sp.ComputeHash($memory_st)).Replace("-", "").ToLower()
|
||||||
|
|
||||||
|
if ($actual_checksum -eq $content_checksum) {
|
||||||
|
$changed = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result.changed = $changed
|
||||||
|
if ($changed -and (-not $check_mode)) {
|
||||||
|
$memory_st.Seek(0, [System.IO.SeekOrigin]::Begin)
|
||||||
|
$file_stream = [System.IO.File]::Create($dest)
|
||||||
|
try {
|
||||||
|
$memory_st.CopyTo($file_stream)
|
||||||
|
} finally {
|
||||||
|
$file_stream.Flush()
|
||||||
|
$file_stream.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$memory_st.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Exit-Json -obj $result
|
Exit-Json -obj $result
|
||||||
|
|
||||||
|
|
|
@ -22,12 +22,11 @@ options:
|
||||||
url:
|
url:
|
||||||
description:
|
description:
|
||||||
- Supports FTP, HTTP or HTTPS URLs in the form of (ftp|http|https)://host.domain:port/path.
|
- Supports FTP, HTTP or HTTPS URLs in the form of (ftp|http|https)://host.domain:port/path.
|
||||||
- Also supports file:/// URLs through Invoke-WebRequest.
|
|
||||||
required: yes
|
required: yes
|
||||||
method:
|
method:
|
||||||
description:
|
description:
|
||||||
- The HTTP Method of the request or response.
|
- The HTTP Method of the request or response.
|
||||||
choices: [ CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, REFRESH, TRACE ]
|
choices: [ CONNECT, DELETE, GET, HEAD, MERGE, OPTIONS, PATCH, POST, PUT, REFRESH, TRACE ]
|
||||||
default: GET
|
default: GET
|
||||||
content_type:
|
content_type:
|
||||||
description:
|
description:
|
||||||
|
@ -43,16 +42,30 @@ options:
|
||||||
description:
|
description:
|
||||||
- Password to use for authentication.
|
- Password to use for authentication.
|
||||||
version_added: '2.4'
|
version_added: '2.4'
|
||||||
|
force_basic_auth:
|
||||||
|
description:
|
||||||
|
- By default the authentication information is only sent when a webservice
|
||||||
|
responds to an initial request with a 401 status. Since some basic auth
|
||||||
|
services do not properly send a 401, logins will fail.
|
||||||
|
- This option forces the sending of the Basic authentication header upon
|
||||||
|
the initial request.
|
||||||
|
type: bool
|
||||||
|
default: 'no'
|
||||||
|
version_added: '2.5'
|
||||||
dest:
|
dest:
|
||||||
description:
|
description:
|
||||||
- Output the response body to a file.
|
- Output the response body to a file.
|
||||||
version_added: '2.3'
|
version_added: '2.3'
|
||||||
headers:
|
headers:
|
||||||
description:
|
description:
|
||||||
- 'Key Value pairs for headers. Example "Host: www.somesite.com"'
|
- Extra headers to set on the request, see the examples for more details on
|
||||||
|
how to set this.
|
||||||
use_basic_parsing:
|
use_basic_parsing:
|
||||||
description:
|
description:
|
||||||
- This module relies upon 'Invoke-WebRequest', which by default uses the Internet Explorer Engine to parse a webpage.
|
- As of Ansible 2.5, this option is no longer valid and cannot be changed from C(yes), this option will be removed
|
||||||
|
in Ansible 2.7.
|
||||||
|
- Before Ansible 2.5, this module relies upon 'Invoke-WebRequest', which by default uses the Internet Explorer Engine
|
||||||
|
to parse a webpage.
|
||||||
- There's an edge-case where if a user hasn't run IE before, this will fail.
|
- There's an edge-case where if a user hasn't run IE before, this will fail.
|
||||||
- The only advantage to using the Internet Explorer praser is that you can traverse the DOM in a powershell script.
|
- The only advantage to using the Internet Explorer praser is that you can traverse the DOM in a powershell script.
|
||||||
- That isn't useful for Ansible, so by default we toggle 'UseBasicParsing'. However, you can toggle that off here.
|
- That isn't useful for Ansible, so by default we toggle 'UseBasicParsing'. However, you can toggle that off here.
|
||||||
|
@ -120,8 +133,17 @@ options:
|
||||||
version_added: '2.4'
|
version_added: '2.4'
|
||||||
client_cert:
|
client_cert:
|
||||||
description:
|
description:
|
||||||
- Specifies the client certificate(.pfx) that is used for a secure web request.
|
- Specifies the client certificate (.pfx) that is used for a secure web request.
|
||||||
|
- The WinRM connection must be authenticated with C(CredSSP) if the
|
||||||
|
certificate file is not password protected.
|
||||||
|
- Other authentication types can set I(client_cert_password) when the cert
|
||||||
|
is password protected.
|
||||||
version_added: '2.4'
|
version_added: '2.4'
|
||||||
|
client_cert_password:
|
||||||
|
description:
|
||||||
|
- The password for the client certificate (.pfx) file that is used for a
|
||||||
|
secure web request.
|
||||||
|
version_added: '2.5'
|
||||||
notes:
|
notes:
|
||||||
- For non-Windows targets, use the M(uri) module instead.
|
- For non-Windows targets, use the M(uri) module instead.
|
||||||
author:
|
author:
|
||||||
|
@ -161,26 +183,6 @@ url:
|
||||||
returned: always
|
returned: always
|
||||||
type: string
|
type: string
|
||||||
sample: https://www.ansible.com
|
sample: https://www.ansible.com
|
||||||
method:
|
|
||||||
description: The HTTP method used.
|
|
||||||
returned: always
|
|
||||||
type: string
|
|
||||||
sample: GET
|
|
||||||
content_type:
|
|
||||||
description: The "content-type" header used.
|
|
||||||
returned: always
|
|
||||||
type: string
|
|
||||||
sample: application/json
|
|
||||||
use_basic_parsing:
|
|
||||||
description: The state of the "use_basic_parsing" flag.
|
|
||||||
returned: always
|
|
||||||
type: bool
|
|
||||||
sample: True
|
|
||||||
body:
|
|
||||||
description: The content of the body used
|
|
||||||
returned: when body is specified
|
|
||||||
type: string
|
|
||||||
sample: '{"id":1}'
|
|
||||||
status_code:
|
status_code:
|
||||||
description: The HTTP Status Code of the response.
|
description: The HTTP Status Code of the response.
|
||||||
returned: success
|
returned: success
|
||||||
|
@ -191,19 +193,19 @@ status_description:
|
||||||
returned: success
|
returned: success
|
||||||
type: string
|
type: string
|
||||||
sample: OK
|
sample: OK
|
||||||
raw_content:
|
content:
|
||||||
description: The raw content of the HTTP response.
|
description: The raw content of the HTTP response.
|
||||||
returned: success
|
returned: success and return_content is True
|
||||||
type: string
|
type: string
|
||||||
sample: 'HTTP/1.1 200 OK\nX-XSS-Protection: 1; mode=block\nAlternate-Protocol: 443:quic,p=1\nAlt-Svc: quic="www.google.com:443";'
|
sample: '{"foo": "bar"}'
|
||||||
headers:
|
content_length:
|
||||||
description: The Headers of the response.
|
|
||||||
returned: success
|
|
||||||
type: dict
|
|
||||||
sample: {"Content-Type": "application/json"}
|
|
||||||
raw_content_length:
|
|
||||||
description: The byte size of the response.
|
description: The byte size of the response.
|
||||||
returned: success
|
returned: success
|
||||||
type: int
|
type: int
|
||||||
sample: 54447
|
sample: 54447
|
||||||
|
json:
|
||||||
|
description: The json structure returned under content as a dictionary
|
||||||
|
returned: success and Content-Type is "application/json" or "application/javascript" and return_content is True
|
||||||
|
type: dict
|
||||||
|
sample: {"this-is-dependent": "on the actual return content"}
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
param (
|
|
||||||
[int]$port = 8000
|
|
||||||
)
|
|
||||||
|
|
||||||
$listener = New-Object Net.HttpListener
|
|
||||||
$listener.Prefixes.Add("http://+:$port/")
|
|
||||||
$listener.Start()
|
|
||||||
|
|
||||||
try {
|
|
||||||
while ($listener.IsListening) {
|
|
||||||
# process received request
|
|
||||||
$context = $listener.GetContext()
|
|
||||||
$Request = $context.Request
|
|
||||||
$Response = $context.Response
|
|
||||||
#$Response.Headers.Add("Content-Type","text/plain")
|
|
||||||
|
|
||||||
$received = '{0} {1}' -f $Request.httpmethod, $Request.url.localpath
|
|
||||||
|
|
||||||
# is there HTML content for this URL?
|
|
||||||
$html = $htmlcontents[$received]
|
|
||||||
if ($html -eq $null) {
|
|
||||||
$Response.statuscode = 404
|
|
||||||
$html = 'Oops, the page is not available!'
|
|
||||||
}
|
|
||||||
|
|
||||||
# return the HTML to the caller
|
|
||||||
$buffer = [Text.Encoding]::UTF8.GetBytes($html)
|
|
||||||
$Response.ContentLength64 = $buffer.length
|
|
||||||
$Response.OutputStream.Write($buffer, 0, $buffer.length)
|
|
||||||
|
|
||||||
$Response.Close()
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
$listener.Stop()
|
|
||||||
$listener.Close()
|
|
||||||
}
|
|
1
test/integration/targets/win_uri/aliases
Normal file
1
test/integration/targets/win_uri/aliases
Normal file
|
@ -0,0 +1 @@
|
||||||
|
windows/ci/group4
|
3
test/integration/targets/win_uri/defaults/main.yml
Normal file
3
test/integration/targets/win_uri/defaults/main.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
test_uri_path: C:\ansible\win_uri
|
||||||
|
httpbin_host: httpbin.org
|
14
test/integration/targets/win_uri/tasks/main.yml
Normal file
14
test/integration/targets/win_uri/tasks/main.yml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
- name: create test directory
|
||||||
|
win_file:
|
||||||
|
path: '{{test_uri_path}}'
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- include_tasks: test.yml
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: cleanup test directory
|
||||||
|
win_file:
|
||||||
|
path: '{{test_uri_path}}'
|
||||||
|
state: absent
|
298
test/integration/targets/win_uri/tasks/test.yml
Normal file
298
test/integration/targets/win_uri/tasks/test.yml
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
---
|
||||||
|
# get with mismatch https
|
||||||
|
# get with mismatch https and ignore validation
|
||||||
|
|
||||||
|
- name: get request without return_content
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/get
|
||||||
|
return_content: no
|
||||||
|
register: get_request_without_content
|
||||||
|
|
||||||
|
- name: assert get request without return_content
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not get_request_without_content.changed
|
||||||
|
- get_request_without_content.content is not defined
|
||||||
|
- get_request_without_content.json is not defined
|
||||||
|
- get_request_without_content.status_code == 200
|
||||||
|
|
||||||
|
- name: get request with xml content
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/xml
|
||||||
|
return_content: yes
|
||||||
|
register: get_request_with_xml_content
|
||||||
|
|
||||||
|
- name: assert get request with xml content
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not get_request_with_xml_content.changed
|
||||||
|
- get_request_with_xml_content.content is defined
|
||||||
|
- get_request_with_xml_content.json is not defined
|
||||||
|
- get_request_with_xml_content.status_code == 200
|
||||||
|
|
||||||
|
- name: get request with binary content
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/image/png
|
||||||
|
return_content: yes
|
||||||
|
register: get_request_with_binary_content
|
||||||
|
|
||||||
|
- name: assert get request with binary content
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not get_request_with_binary_content.changed
|
||||||
|
- get_request_with_binary_content.content is defined
|
||||||
|
- get_request_with_binary_content.json is not defined
|
||||||
|
- get_request_with_xml_content.status_code == 200
|
||||||
|
|
||||||
|
- name: get request with return_content and dest (check mode)
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/get
|
||||||
|
return_content: yes
|
||||||
|
dest: '{{test_uri_path}}\get.json'
|
||||||
|
register: get_request_with_dest_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: get stat of downloaded file (check mode)
|
||||||
|
win_stat:
|
||||||
|
path: '{{test_uri_path}}\get.json'
|
||||||
|
register: get_request_with_dest_actual_check
|
||||||
|
|
||||||
|
- name: assert get request with return_content and dest (check mode)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- get_request_with_dest_check.changed
|
||||||
|
- get_request_with_dest_check.content is defined
|
||||||
|
- get_request_with_dest_check.json is defined
|
||||||
|
- get_request_with_dest_actual_check.stat.exists == False
|
||||||
|
|
||||||
|
- name: get request with return_content and dest
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/get
|
||||||
|
return_content: yes
|
||||||
|
dest: '{{test_uri_path}}\get.json'
|
||||||
|
register: get_request_with_dest
|
||||||
|
|
||||||
|
- name: get stat of downloaded file
|
||||||
|
win_stat:
|
||||||
|
path: '{{test_uri_path}}\get.json'
|
||||||
|
checksum_algorithm: sha1
|
||||||
|
get_checksum: yes
|
||||||
|
register: get_request_with_dest_actual
|
||||||
|
|
||||||
|
- name: assert get request with return_content and dest
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- get_request_with_dest.changed
|
||||||
|
- get_request_with_dest.content is defined
|
||||||
|
- get_request_with_dest.json is defined
|
||||||
|
- get_request_with_dest_actual.stat.exists == True
|
||||||
|
- get_request_with_dest_actual.stat.checksum == get_request_with_dest.content|hash('sha1')
|
||||||
|
|
||||||
|
- name: get request with return_content and dest (idempotent)
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/get
|
||||||
|
return_content: yes
|
||||||
|
dest: '{{test_uri_path}}\get.json'
|
||||||
|
register: get_request_with_dest_again
|
||||||
|
|
||||||
|
- name: assert get request with return_content and dest (idempotent)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not get_request_with_dest_again.changed
|
||||||
|
|
||||||
|
- name: post request with return_content, dest and different content
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/post
|
||||||
|
method: POST
|
||||||
|
content_type: application/json
|
||||||
|
body: '{"foo": "bar"}'
|
||||||
|
return_content: yes
|
||||||
|
dest: '{{test_uri_path}}\get.json'
|
||||||
|
register: post_request_with_different_content
|
||||||
|
|
||||||
|
- name: get stat of downloaded file
|
||||||
|
win_stat:
|
||||||
|
path: '{{test_uri_path}}\get.json'
|
||||||
|
checksum_algorithm: sha1
|
||||||
|
get_checksum: yes
|
||||||
|
register: post_request_with_different_content_actual
|
||||||
|
|
||||||
|
- name: assert post request with return_content, dest and different content
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- post_request_with_different_content.changed
|
||||||
|
- post_request_with_different_content_actual.stat.exists == True
|
||||||
|
- post_request_with_different_content_actual.stat.checksum == post_request_with_different_content.content|hash('sha1')
|
||||||
|
|
||||||
|
- name: test redirect without follow_redirects
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/redirect/2
|
||||||
|
follow_redirects: none
|
||||||
|
status_code: 302
|
||||||
|
register: redirect_without_follow
|
||||||
|
|
||||||
|
- name: assert redirect without follow_redirects
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not redirect_without_follow.changed
|
||||||
|
- redirect_without_follow.location|default("") == '/relative-redirect/1'
|
||||||
|
- redirect_without_follow.status_code == 302
|
||||||
|
|
||||||
|
- name: test redirect with follow_redirects
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/redirect/2
|
||||||
|
follow_redirects: all
|
||||||
|
register: redirect_with_follow
|
||||||
|
|
||||||
|
- name: assert redirect with follow_redirects
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not redirect_with_follow.changed
|
||||||
|
- redirect_with_follow.location is not defined
|
||||||
|
- redirect_with_follow.status_code == 200
|
||||||
|
- redirect_with_follow.response_uri == 'http://{{httpbin_host}}/get'
|
||||||
|
|
||||||
|
- name: get request with redirect of TLS
|
||||||
|
win_uri:
|
||||||
|
url: https://{{httpbin_host}}/redirect/2
|
||||||
|
follow_redirects: all
|
||||||
|
register: redirect_with_follow_tls
|
||||||
|
|
||||||
|
- name: assert redirect with redirect of TLS
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not redirect_with_follow_tls.changed
|
||||||
|
- redirect_with_follow_tls.location is not defined
|
||||||
|
- redirect_with_follow_tls.status_code == 200
|
||||||
|
- redirect_with_follow_tls.response_uri == 'https://{{httpbin_host}}/get'
|
||||||
|
|
||||||
|
- name: test basic auth
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/basic-auth/user/passwd
|
||||||
|
user: user
|
||||||
|
password: passwd
|
||||||
|
register: basic_auth
|
||||||
|
|
||||||
|
- name: assert test basic auth
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not basic_auth.changed
|
||||||
|
- basic_auth.status_code == 200
|
||||||
|
|
||||||
|
- name: test basic auth with force auth
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/hidden-basic-auth/user/passwd
|
||||||
|
user: user
|
||||||
|
password: passwd
|
||||||
|
force_basic_auth: yes
|
||||||
|
register: basic_auth_forced
|
||||||
|
|
||||||
|
- name: assert test basic auth with forced auth
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not basic_auth_forced.changed
|
||||||
|
- basic_auth_forced.status_code == 200
|
||||||
|
|
||||||
|
- name: test PUT
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/put
|
||||||
|
method: PUT
|
||||||
|
body: foo=bar
|
||||||
|
return_content: yes
|
||||||
|
register: put_request
|
||||||
|
|
||||||
|
- name: assert test PUT
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not put_request.changed
|
||||||
|
- put_request.status_code == 200
|
||||||
|
- put_request.json.data == 'foo=bar'
|
||||||
|
|
||||||
|
- name: test OPTIONS
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/
|
||||||
|
method: OPTIONS
|
||||||
|
register: option_request
|
||||||
|
|
||||||
|
- name: assert test OPTIONS
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not option_request.changed
|
||||||
|
- option_request.status_code == 200
|
||||||
|
- 'option_request.allow.split(", ")|sort == ["GET", "HEAD", "OPTIONS"]'
|
||||||
|
|
||||||
|
# SNI Tests
|
||||||
|
|
||||||
|
- name: validate status_codes are correct
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/status/202
|
||||||
|
status_code:
|
||||||
|
- 202
|
||||||
|
method: POST
|
||||||
|
body: foo
|
||||||
|
register: status_code_check
|
||||||
|
|
||||||
|
- name: assert validate status_codes are correct
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not status_code_check.changed
|
||||||
|
- status_code_check.status_code == 202
|
||||||
|
|
||||||
|
- name: send JSON body with dict type
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/post
|
||||||
|
method: POST
|
||||||
|
body:
|
||||||
|
foo: bar
|
||||||
|
list:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
dict:
|
||||||
|
foo: bar
|
||||||
|
headers:
|
||||||
|
'Content-Type': 'text/json'
|
||||||
|
return_content: yes
|
||||||
|
register: json_as_dict
|
||||||
|
|
||||||
|
- name: set fact of expected json dict
|
||||||
|
set_fact:
|
||||||
|
json_as_dict_value:
|
||||||
|
foo: bar
|
||||||
|
list:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
dict:
|
||||||
|
foo: bar
|
||||||
|
|
||||||
|
- name: assert send JSON body with dict type
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not json_as_dict.changed
|
||||||
|
- json_as_dict.json.json == json_as_dict_value
|
||||||
|
- json_as_dict.status_code == 200
|
||||||
|
|
||||||
|
- name: get request with custom headers
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/get
|
||||||
|
headers:
|
||||||
|
Test-Header: hello
|
||||||
|
Another-Header: world
|
||||||
|
return_content: yes
|
||||||
|
register: get_custom_header
|
||||||
|
|
||||||
|
- name: assert request with custom headers
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not get_custom_header.changed
|
||||||
|
- get_custom_header.status_code == 200
|
||||||
|
- get_custom_header.json.headers['Test-Header'] == 'hello'
|
||||||
|
- get_custom_header.json.headers['Another-Header'] == 'world'
|
||||||
|
|
||||||
|
# client cert auth tests
|
||||||
|
|
||||||
|
- name: get request with timeout
|
||||||
|
win_uri:
|
||||||
|
url: http://{{httpbin_host}}/delay/10
|
||||||
|
timeout: 5
|
||||||
|
register: get_with_timeout_fail
|
||||||
|
failed_when: '"The operation has timed out" not in get_with_timeout_fail.msg'
|
|
@ -160,7 +160,6 @@ lib/ansible/modules/windows/win_wakeonlan.ps1 PSAvoidUsingCmdletAliases
|
||||||
lib/ansible/modules/windows/win_webpicmd.ps1 PSAvoidUsingInvokeExpression
|
lib/ansible/modules/windows/win_webpicmd.ps1 PSAvoidUsingInvokeExpression
|
||||||
lib/ansible/modules/windows/win_webpicmd.ps1 PSPossibleIncorrectComparisonWithNull
|
lib/ansible/modules/windows/win_webpicmd.ps1 PSPossibleIncorrectComparisonWithNull
|
||||||
lib/ansible/modules/windows/win_webpicmd.ps1 PSUseOutputTypeCorrectly
|
lib/ansible/modules/windows/win_webpicmd.ps1 PSUseOutputTypeCorrectly
|
||||||
test/integration/targets/uri/files/testserver.ps1 PSPossibleIncorrectComparisonWithNull
|
|
||||||
test/integration/targets/win_audit_rule/library/test_get_audit_rule.ps1 PSAvoidUsingCmdletAliases
|
test/integration/targets/win_audit_rule/library/test_get_audit_rule.ps1 PSAvoidUsingCmdletAliases
|
||||||
test/integration/targets/win_dsc/library/test_win_dsc_iis_info.ps1 PSPossibleIncorrectComparisonWithNull
|
test/integration/targets/win_dsc/library/test_win_dsc_iis_info.ps1 PSPossibleIncorrectComparisonWithNull
|
||||||
test/integration/targets/win_dsc/templates/ANSIBLE_xTestResource.psm1 PSAvoidDefaultValueForMandatoryParameter
|
test/integration/targets/win_dsc/templates/ANSIBLE_xTestResource.psm1 PSAvoidDefaultValueForMandatoryParameter
|
||||||
|
|
Loading…
Reference in a new issue