win_uri: Add integration tests, new functionality... (#25373)
This is a cleanup of the win_uri module to make it feature-complete. This PR includes: - Added check-mode support - Add as many options from the uri module as possible - Added creates - Added follow_redirects - Added maximum_redirection - Added password - Added removes - Added return_content - Added status_code - Added timeout - Added user - Added validate_certs - Fixed list-handling for comma-separated strings - Added basic integration tests (should come from uri module)pull/4420/head
parent
a4ebde1516
commit
0aba04fdad
|
@ -222,7 +222,7 @@ Function Get-AnsibleParam($obj, $name, $default = $null, $resultobj = @{}, $fail
|
||||||
# Nothing to do
|
# Nothing to do
|
||||||
} 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 ","
|
$value = $value.Split(",").Trim()
|
||||||
} 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."
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# This file is part of Ansible
|
# This file is part of Ansible
|
||||||
#
|
#
|
||||||
# Copyright 2015, Corwin Brown <corwin@corwinbrown.com>
|
# Copyright 2015, Corwin Brown <corwin@corwinbrown.com>
|
||||||
|
# Copyright 2017, Dag Wieers <dag@wieers.com>
|
||||||
#
|
#
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -20,80 +21,137 @@
|
||||||
# POWERSHELL_COMMON
|
# POWERSHELL_COMMON
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
$safe_methods = @("GET", "HEAD")
|
||||||
# Functions ###############################################
|
$content_keys = @("Content", "Images", "InputFields", "Links", "RawContent")
|
||||||
|
|
||||||
Function ConvertTo-SnakeCase($input_string) {
|
Function ConvertTo-SnakeCase($input_string) {
|
||||||
$snake_case = $input_string -csplit "(?<!^)(?=[A-Z])" -join "_"
|
$snake_case = $input_string -csplit "(?<!^)(?=[A-Z])" -join "_"
|
||||||
return $snake_case.ToLower()
|
return $snake_case.ToLower()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build Arguments
|
|
||||||
$params = Parse-Args $args -supports_check_mode $true
|
$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
|
$url = Get-AnsibleParam -obj $params -name "url" -type "str" -failifempty $true
|
||||||
$method = Get-AnsibleParam -obj $params "method" -type "str" -default "GET" -validateset "GET","POST","PUT","HEAD","DELETE","OPTIONS","PATCH","TRACE","CONNECT","REFRESH"
|
$method = Get-AnsibleParam -obj $params "method" -type "str" -default "GET" -validateset "CONNECT","DELETE","GET","HEAD","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"
|
||||||
# TODO: Why is this not a normal dictionary ?
|
$headers = Get-AnsibleParam -obj $params -name "headers" -type="dict"
|
||||||
$headers = Get-AnsibleParam -obj $params -name "headers" -type "str"
|
$body = Get-AnsibleParam -obj $params -name "body" -type "dict"
|
||||||
# TODO: Why is this not a normal dictionary ?
|
|
||||||
$body = Get-AnsibleParam -obj $params -name "body" -type "str"
|
|
||||||
$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"
|
||||||
|
$password = Get-AnsibleParam -obj $params -name "password" -type "str"
|
||||||
|
|
||||||
|
$creates = Get-AnsibleParam -obj $params -name "creates" -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"
|
||||||
|
$maximum_redirection = Get-AnsibleParam -obj $params -name "maximum_redirection" -type "int" -default 5
|
||||||
|
$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)
|
||||||
|
$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" -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"
|
||||||
|
|
||||||
|
if ($creates -and (Test-Path -Path $creates)) {
|
||||||
|
$result.skipped = $true
|
||||||
|
Exit-Json $result "The 'creates' file or directory ($creates) already exists."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($removes -and -not (Test-Path -Path $removes)) {
|
||||||
|
$result.skipped = $true
|
||||||
|
Exit-Json $result "The 'removes' file or directory ($removes) does not exist."
|
||||||
|
}
|
||||||
|
|
||||||
$result = @{
|
$result = @{
|
||||||
changed = $false
|
changed = $false
|
||||||
win_uri = @{
|
|
||||||
content_type = $content_type
|
content_type = $content_type
|
||||||
method = $method
|
method = $method
|
||||||
url = $url
|
url = $url
|
||||||
use_basic_parsing = $use_basic_parsing
|
use_basic_parsing = $use_basic_parsing
|
||||||
|
}
|
||||||
|
|
||||||
|
# Disable redirection if requested
|
||||||
|
switch($follow_redirects) {
|
||||||
|
"none" {
|
||||||
|
$maximum_redirection = 0
|
||||||
|
}
|
||||||
|
"safe" {
|
||||||
|
if ($safe_methods -notcontains $method) {
|
||||||
|
$maximum_redirection = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$webrequest_opts = @{
|
$webrequest_opts = @{
|
||||||
ContentType = $content_type
|
ContentType = $content_type
|
||||||
|
ErrorAction = "SilentlyContinue"
|
||||||
|
MaximumRedirection = $maximum_redirection
|
||||||
Method = $method
|
Method = $method
|
||||||
|
TimeoutSec = $timeout
|
||||||
Uri = $url
|
Uri = $url
|
||||||
UseBasicParsing = $use_basic_parsing
|
UseBasicParsing = $use_basic_parsing
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($headers -ne $null) {
|
if (-not $validate_certs) {
|
||||||
|
$PSDefaultParameterValues.Add("Invoke-WebRequest:SkipCertificateCheck", $true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($headers) {
|
||||||
$req_headers = @{}
|
$req_headers = @{}
|
||||||
ForEach ($header in $headers.psobject.properties) {
|
ForEach ($header in $headers.psobject.properties) {
|
||||||
$req_headers.Add($header.Name, $header.Value)
|
$req_headers.Add($header.Name, $header.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
$webrequest_opts.Headers = $req_headers
|
$webrequest_opts.Headers = $req_headers
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($body -ne $null) {
|
if ($client_cert) {
|
||||||
$webrequest_opts.Body = $body
|
Try {
|
||||||
$result.win_uri.body = $body
|
$webrequest_opts.Certificate = Get-PfxCertificate -FilePath $client_cert
|
||||||
}
|
} Catch {
|
||||||
|
Fail-Json $result "Failed to read client certificate '$client_cert'"
|
||||||
if ($dest -ne $null) {
|
|
||||||
$webrequest_opts.OutFile = $dest
|
|
||||||
$result.win_uri.dest = $dest
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO: When writing to a file, this is not idempotent !
|
|
||||||
if ($check_mode -ne $true -or $dest -eq $null) {
|
|
||||||
try {
|
|
||||||
$response = Invoke-WebRequest @webrequest_opts
|
|
||||||
} catch {
|
|
||||||
Fail-Json $result $_.Exception.Message
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Assume a change when we are writing to a file
|
if ($body) {
|
||||||
if ($dest -ne $null) {
|
$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) {
|
||||||
|
$webrequest_opts.Credential = New-Object System.Management.Automation.PSCredential($user, $($password | ConvertTo-SecureString -AsPlainText -Force))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = Invoke-WebRequest @webrequest_opts
|
||||||
|
} catch {
|
||||||
|
Fail-Json $result $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: When writing to a file, this is not idempotent !
|
||||||
|
# FIXME: Assume a change when we are writing to a file
|
||||||
|
# FIXME: Implement diff-mode
|
||||||
|
if ($dest) {
|
||||||
$result.changed = $true
|
$result.changed = $true
|
||||||
}
|
}
|
||||||
|
|
||||||
ForEach ($prop in $response.psobject.properties) {
|
ForEach ($prop in $response.psobject.properties) {
|
||||||
|
if ($content_keys -contains $prop.Name -and -not $return_content) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
$result_key = ConvertTo-SnakeCase $prop.Name
|
$result_key = ConvertTo-SnakeCase $prop.Name
|
||||||
$result.$result_key = $prop.Value
|
$result.$result_key = $prop.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($status_code -notcontains $response.StatusCode) {
|
||||||
|
Fail-Json $result "Status code of request '$($response.StatusCode)' is not in list of valid status codes $status_code."
|
||||||
|
}
|
||||||
|
|
||||||
Exit-Json $result
|
Exit-Json $result
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# (c) 2015, Corwin Brown <corwin@corwinbrown.com>
|
# (c) 2015, Corwin Brown <corwin@corwinbrown.com>
|
||||||
|
# (c) 2017, Dag Wieers <dag@wieers.com>
|
||||||
#
|
#
|
||||||
# This file is part of Ansible
|
# This file is part of Ansible
|
||||||
#
|
#
|
||||||
|
@ -25,57 +26,121 @@ ANSIBLE_METADATA = {'metadata_version': '1.0',
|
||||||
'status': ['preview'],
|
'status': ['preview'],
|
||||||
'supported_by': 'community'}
|
'supported_by': 'community'}
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = r'''
|
DOCUMENTATION = r'''
|
||||||
---
|
---
|
||||||
module: win_uri
|
module: win_uri
|
||||||
version_added: "2.1"
|
version_added: '2.1'
|
||||||
short_description: Interacts with webservices.
|
short_description: Interacts with webservices
|
||||||
description:
|
description:
|
||||||
- Interacts with HTTP and HTTPS web services and supports Digest, Basic and WSSE HTTP authentication mechanisms.
|
- Interacts with FTP, HTTP and HTTPS web services.
|
||||||
|
- Supports Digest, Basic and WSSE HTTP authentication mechanisms.
|
||||||
options:
|
options:
|
||||||
url:
|
url:
|
||||||
description:
|
description:
|
||||||
- HTTP or HTTPS URL in the form of (http|https)://host.domain:port/path
|
- Supports FTP, HTTP or HTTPS URLs in the form of (ftp|http|https)://host.domain:port/path.
|
||||||
required: True
|
- Also supports file:/// URLs through Invoke-WebRequest.
|
||||||
|
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 ]
|
||||||
default: GET
|
default: GET
|
||||||
choices:
|
|
||||||
- GET
|
|
||||||
- POST
|
|
||||||
- PUT
|
|
||||||
- HEAD
|
|
||||||
- DELETE
|
|
||||||
- OPTIONS
|
|
||||||
- PATCH
|
|
||||||
- TRACE
|
|
||||||
- CONNECT
|
|
||||||
- REFRESH
|
|
||||||
content_type:
|
content_type:
|
||||||
description:
|
description:
|
||||||
- Sets the "Content-Type" header.
|
- Sets the "Content-Type" header.
|
||||||
body:
|
body:
|
||||||
description:
|
description:
|
||||||
- The body of the HTTP request/response to the web service.
|
- The body of the HTTP request/response to the web service.
|
||||||
|
user:
|
||||||
|
description:
|
||||||
|
- Username to use for authentication.
|
||||||
|
version_added: '2.4'
|
||||||
|
password:
|
||||||
|
description:
|
||||||
|
- Password to use for authentication.
|
||||||
|
version_added: '2.4'
|
||||||
dest:
|
dest:
|
||||||
version_added: "2.3"
|
|
||||||
description:
|
description:
|
||||||
- Output the response body to a file.
|
- Output the response body to a file.
|
||||||
|
version_added: '2.3'
|
||||||
headers:
|
headers:
|
||||||
description:
|
description:
|
||||||
- 'Key Value pairs for headers. Example "Host: www.somesite.com"'
|
- 'Key Value pairs for headers. Example "Host: www.somesite.com"'
|
||||||
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. There's an edge-case where if a
|
- This module relies upon 'Invoke-WebRequest', which by default uses the Internet Explorer Engine to parse a webpage.
|
||||||
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
|
- There's an edge-case where if a user hasn't run IE before, this will fail.
|
||||||
powershell script. That isn't useful for Ansible, so by default we toggle 'UseBasicParsing'. However, you can toggle that off here.
|
- The only advantage to using the Internet Explorer praser is that you can traverse the DOM in a powershell script.
|
||||||
choices:
|
- That isn't useful for Ansible, so by default we toggle 'UseBasicParsing'. However, you can toggle that off here.
|
||||||
- True
|
type: bool
|
||||||
- False
|
default: 'yes'
|
||||||
default: True
|
creates:
|
||||||
author: Corwin Brown (@blakfeld)
|
description:
|
||||||
|
- A filename, when it already exists, this step will be skipped.
|
||||||
|
version_added: '2.4'
|
||||||
|
removes:
|
||||||
|
description:
|
||||||
|
- A filename, when it does not exist, this step will be skipped.
|
||||||
|
version_added: '2.4'
|
||||||
|
return_content:
|
||||||
|
description:
|
||||||
|
- Whether or not to return the body of the request as a "content" key in
|
||||||
|
the dictionary result. If the reported Content-type is
|
||||||
|
"application/json", then the JSON is additionally loaded into a key
|
||||||
|
called C(json) in the dictionary results.
|
||||||
|
type: bool
|
||||||
|
default: 'no'
|
||||||
|
version_added: '2.4'
|
||||||
|
status_code:
|
||||||
|
description:
|
||||||
|
- A valid, numeric, HTTP status code that signifies success of the request.
|
||||||
|
- Can also be comma separated list of status codes.
|
||||||
|
default: 200
|
||||||
|
version_added: '2.4'
|
||||||
|
timeout:
|
||||||
|
description:
|
||||||
|
- Specifies how long the request can be pending before it times out (in seconds).
|
||||||
|
- The value 0 (zero) specifies an indefinite time-out.
|
||||||
|
- A Domain Name System (DNS) query can take up to 15 seconds to return or time out.
|
||||||
|
If your request contains a host name that requires resolution, and you set
|
||||||
|
C(timeout) to a value greater than zero, but less than 15 seconds, it can
|
||||||
|
take 15 seconds or more before your request times out.
|
||||||
|
default: 30
|
||||||
|
version_added: '2.4'
|
||||||
|
follow_redirects:
|
||||||
|
description:
|
||||||
|
- Whether or not the C(win_uri) module should follow redirects.
|
||||||
|
- C(all) will follow all redirects.
|
||||||
|
- C(none) will not follow any redirects.
|
||||||
|
- C(safe) will follow only "safe" redirects, where "safe" means that the client is only
|
||||||
|
doing a C(GET) or C(HEAD) on the URI to which it is being redirected.
|
||||||
|
choices: [ all, none, safe ]
|
||||||
|
default: safe
|
||||||
|
version_added: '2.4'
|
||||||
|
maximum_redirection:
|
||||||
|
description:
|
||||||
|
- Specifies how many times C(win_uri) redirects a connection to an alternate
|
||||||
|
Uniform Resource Identifier (URI) before the connection fails.
|
||||||
|
- If C(maximum_redirection) is set to 0 (zero)
|
||||||
|
or C(follow_redirects) is set to C(none),
|
||||||
|
or set to C(safe) when not doing C(GET) or C(HEAD) it prevents all redirection.
|
||||||
|
default: 5
|
||||||
|
version_added: '2.4'
|
||||||
|
validate_certs:
|
||||||
|
description:
|
||||||
|
- If C(no), SSL certificates will not be validated. This should only
|
||||||
|
set to C(no) used on personally controlled sites using self-signed
|
||||||
|
certificates.
|
||||||
|
type: bool
|
||||||
|
default: 'yes'
|
||||||
|
version_added: '2.4'
|
||||||
|
client_cert:
|
||||||
|
description:
|
||||||
|
- Specifies the client certificate(.pfx) that is used for a secure web request.
|
||||||
|
version_added: '2.4'
|
||||||
|
author:
|
||||||
|
- Corwin Brown (@blakfeld)
|
||||||
|
- Dag Wieers (@dagwieers)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = r'''
|
EXAMPLES = r'''
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
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()
|
||||||
|
}
|
Loading…
Reference in New Issue