mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-25 03:41:25 -07:00
psrp: Added new Windows connection plugin (#41729)
* psrp: Added new Windows connection plugin * Tweaks to connection options from review
This commit is contained in:
parent
07a011cd6f
commit
6982dfc756
7 changed files with 793 additions and 22 deletions
|
@ -182,6 +182,7 @@ Function Extract-Zip($src, $dest) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$archive.Dispose() # release the handle of the zip file
|
||||||
}
|
}
|
||||||
|
|
||||||
Function Extract-ZipLegacy($src, $dest) {
|
Function Extract-ZipLegacy($src, $dest) {
|
||||||
|
|
600
lib/ansible/plugins/connection/psrp.py
Normal file
600
lib/ansible/plugins/connection/psrp.py
Normal file
|
@ -0,0 +1,600 @@
|
||||||
|
# Copyright (c) 2018 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
author: Ansible Core Team
|
||||||
|
connection: psrp
|
||||||
|
short_description: Run tasks over Microsoft PowerShell Remoting Protocol
|
||||||
|
description:
|
||||||
|
- Run commands or put/fetch on a target via PSRP (WinRM plugin)
|
||||||
|
- This is similar to the I(winrm) connection plugin which uses the same
|
||||||
|
underlying transport but instead runs in a PowerShell interpreter.
|
||||||
|
version_added: "2.7"
|
||||||
|
requirements:
|
||||||
|
- pypsrp (Python library)
|
||||||
|
options:
|
||||||
|
# transport options
|
||||||
|
remote_addr:
|
||||||
|
description:
|
||||||
|
- The hostname or IP address of the remote host.
|
||||||
|
default: inventory_hostname
|
||||||
|
vars:
|
||||||
|
- name: ansible_host
|
||||||
|
- name: ansible_psrp_host
|
||||||
|
remote_user:
|
||||||
|
description:
|
||||||
|
- The user to log in as.
|
||||||
|
vars:
|
||||||
|
- name: ansible_user
|
||||||
|
- name: ansible_psrp_user
|
||||||
|
port:
|
||||||
|
description:
|
||||||
|
- The port for PSRP to connect on the remote target.
|
||||||
|
- Default is C(5986) if I(protocol) is not defined or is C(https),
|
||||||
|
otherwise the port is C(5985).
|
||||||
|
vars:
|
||||||
|
- name: ansible_port
|
||||||
|
- name: ansible_psrp_port
|
||||||
|
protocol:
|
||||||
|
description:
|
||||||
|
- Set the protocol to use for the connection.
|
||||||
|
- Default is C(https) if I(port) is not defined or I(port) is not C(5985).
|
||||||
|
choices:
|
||||||
|
- http
|
||||||
|
- https
|
||||||
|
vars:
|
||||||
|
- name: ansible_psrp_protocol
|
||||||
|
path:
|
||||||
|
description:
|
||||||
|
- The URI path to connect to.
|
||||||
|
vars:
|
||||||
|
- name: ansible_psrp_path
|
||||||
|
default: 'wsman'
|
||||||
|
auth:
|
||||||
|
description:
|
||||||
|
- The authentication protocol to use when authenticating the remote user.
|
||||||
|
- The default, C(negotiate), will attempt to use C(Kerberos) if it is
|
||||||
|
available and fall back to C(NTLM) if it isn't.
|
||||||
|
vars:
|
||||||
|
- name: ansible_psrp_auth
|
||||||
|
choices:
|
||||||
|
- basic
|
||||||
|
- certificate
|
||||||
|
- negotiate
|
||||||
|
- kerberos
|
||||||
|
- ntlm
|
||||||
|
- credssp
|
||||||
|
default: negotiate
|
||||||
|
cert_validation:
|
||||||
|
description:
|
||||||
|
- Whether to validate the remote server's certificate or not.
|
||||||
|
- Set to C(ignore) to not validate any certificates.
|
||||||
|
- I(cert_trust_path) can be set to the path of a PEM certificate chain to
|
||||||
|
use in the validation.
|
||||||
|
choices:
|
||||||
|
- validate
|
||||||
|
- ignore
|
||||||
|
default: validate
|
||||||
|
vars:
|
||||||
|
- name: ansible_psrp_cert_validation
|
||||||
|
cert_trust_path:
|
||||||
|
description:
|
||||||
|
- The path to a PEM certificate chain to use when validating the server's
|
||||||
|
certificate.
|
||||||
|
- This value is ignored if I(cert_validation) is set to C(ignore).
|
||||||
|
vars:
|
||||||
|
- name: ansible_psrp_cert_trust_path
|
||||||
|
connection_timeout:
|
||||||
|
description:
|
||||||
|
- The connection timeout for making the request to the remote host.
|
||||||
|
- This is measured in seconds.
|
||||||
|
vars:
|
||||||
|
- name: ansible_psrp_connection_timeout
|
||||||
|
default: 30
|
||||||
|
message_encryption:
|
||||||
|
description:
|
||||||
|
- Controls the message encryption settings, this is different from TLS
|
||||||
|
encryption when I(ansible_psrp_protocol) is C(https).
|
||||||
|
- Only the auth protocols C(negotiate), C(kerberos), C(ntlm), and
|
||||||
|
C(credssp) can do message encryption. The other authentication protocols
|
||||||
|
only support encryption when C(protocol) is set to C(https).
|
||||||
|
- C(auto) means means message encryption is only used when not using
|
||||||
|
TLS/HTTPS.
|
||||||
|
- C(always) is the same as C(auto) but message encryption is always used
|
||||||
|
even when running over TLS/HTTPS.
|
||||||
|
- C(never) disables any encryption checks that are in place when running
|
||||||
|
over HTTP and disables any authentication encryption processes.
|
||||||
|
vars:
|
||||||
|
- name: ansible_psrp_message_encryption
|
||||||
|
choices:
|
||||||
|
- auto
|
||||||
|
- always
|
||||||
|
- never
|
||||||
|
default: auto
|
||||||
|
proxy:
|
||||||
|
description:
|
||||||
|
- Set the proxy URL to use when connecting to the remote host.
|
||||||
|
vars:
|
||||||
|
- name: ansible_psrp_proxy
|
||||||
|
ignore_proxy:
|
||||||
|
description:
|
||||||
|
- Will disable any environment proxy settings and connect directly to the
|
||||||
|
remote host.
|
||||||
|
- This option is ignored if C(proxy) is set.
|
||||||
|
vars:
|
||||||
|
- name: ansible_psrp_ignore_proxy
|
||||||
|
type: bool
|
||||||
|
default: 'no'
|
||||||
|
|
||||||
|
# protocol options
|
||||||
|
operation_timeout:
|
||||||
|
description:
|
||||||
|
- Sets the WSMan timeout for each operation.
|
||||||
|
- This is measured in seconds.
|
||||||
|
- This should not exceed the value for C(connection_timeout).
|
||||||
|
vars:
|
||||||
|
- name: ansible_psrp_operation_timeout
|
||||||
|
default: 20
|
||||||
|
max_envelope_size:
|
||||||
|
description:
|
||||||
|
- Sets the maximum size of each WSMan message sent to the remote host.
|
||||||
|
- This is measured in bytes.
|
||||||
|
- Defaults to C(150KiB) for compatibility with older hosts.
|
||||||
|
vars:
|
||||||
|
- name: ansible_psrp_max_envelope_size
|
||||||
|
default: 153600
|
||||||
|
configuration_name:
|
||||||
|
description:
|
||||||
|
- The name of the PowerShell configuration endpoint to connect to.
|
||||||
|
vars:
|
||||||
|
- name: ansible_psrp_configuration_name
|
||||||
|
default: Microsoft.PowerShell
|
||||||
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleConnectionFailure, AnsibleError
|
||||||
|
from ansible.errors import AnsibleFileNotFound
|
||||||
|
from ansible.module_utils.parsing.convert_bool import boolean
|
||||||
|
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||||
|
from ansible.plugins.connection import ConnectionBase
|
||||||
|
from ansible.plugins.shell.powershell import _common_args
|
||||||
|
from ansible.utils.hashing import secure_hash
|
||||||
|
from ansible.utils.path import makedirs_safe
|
||||||
|
|
||||||
|
HAS_PYPSRP = True
|
||||||
|
PYPSRP_IMP_ERR = None
|
||||||
|
try:
|
||||||
|
from pypsrp.complex_objects import GenericComplexObject, RunspacePoolState
|
||||||
|
from pypsrp.exceptions import AuthenticationError, WinRMError
|
||||||
|
from pypsrp.host import PSHost, PSHostUserInterface
|
||||||
|
from pypsrp.powershell import PowerShell, RunspacePool
|
||||||
|
from pypsrp.shell import Process, SignalCode, WinRS
|
||||||
|
from pypsrp.wsman import WSMan, AUTH_KWARGS
|
||||||
|
except ImportError as err:
|
||||||
|
HAS_PYPSRP = False
|
||||||
|
PYPSRP_IMP_ERR = err
|
||||||
|
|
||||||
|
try:
|
||||||
|
from __main__ import display
|
||||||
|
except ImportError:
|
||||||
|
from ansible.utils.display import Display
|
||||||
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
|
class Connection(ConnectionBase):
|
||||||
|
|
||||||
|
transport = 'psrp'
|
||||||
|
module_implementation_preferences = ('.ps1', '.exe', '')
|
||||||
|
become_methods = ['runas']
|
||||||
|
allow_executable = False
|
||||||
|
has_pipelining = True
|
||||||
|
allow_extras = True
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.always_pipeline_modules = True
|
||||||
|
self.has_native_async = True
|
||||||
|
|
||||||
|
self.runspace = None
|
||||||
|
self.host = None
|
||||||
|
|
||||||
|
self._shell_type = 'powershell'
|
||||||
|
super(Connection, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
if not HAS_PYPSRP:
|
||||||
|
raise AnsibleError("pypsrp or dependencies are not installed: %s"
|
||||||
|
% to_native(PYPSRP_IMP_ERR))
|
||||||
|
super(Connection, self)._connect()
|
||||||
|
self._build_kwargs()
|
||||||
|
display.vvv("ESTABLISH PSRP CONNECTION FOR USER: %s ON PORT %s TO %s" %
|
||||||
|
(self._psrp_user, self._psrp_port, self._psrp_host),
|
||||||
|
host=self._psrp_host)
|
||||||
|
|
||||||
|
if not self.runspace:
|
||||||
|
connection = WSMan(**self._psrp_conn_kwargs)
|
||||||
|
|
||||||
|
# create our psuedo host to capture the exit code and host output
|
||||||
|
host_ui = PSHostUserInterface()
|
||||||
|
self.host = PSHost(None, None, False, "Ansible PSRP Host", None,
|
||||||
|
host_ui, None)
|
||||||
|
|
||||||
|
self.runspace = RunspacePool(
|
||||||
|
connection, host=self.host,
|
||||||
|
configuration_name=self._psrp_configuration_name
|
||||||
|
)
|
||||||
|
display.vvvvv(
|
||||||
|
"PSRP OPEN RUNSPACE: auth=%s configuration=%s endpoint=%s" %
|
||||||
|
(self._psrp_auth, self._psrp_configuration_name,
|
||||||
|
connection.transport.endpoint), host=self._psrp_host
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
self.runspace.open()
|
||||||
|
except AuthenticationError as e:
|
||||||
|
raise AnsibleConnectionFailure("failed to authenticate with "
|
||||||
|
"the server: %s" % to_native(e))
|
||||||
|
except WinRMError as e:
|
||||||
|
raise AnsibleConnectionFailure(
|
||||||
|
"psrp connection failure during runspace open: %s"
|
||||||
|
% to_native(e)
|
||||||
|
)
|
||||||
|
self._connected = True
|
||||||
|
return self
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
display.vvvvv("PSRP: Reset Connection", host=self._psrp_host)
|
||||||
|
self.runspace = None
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
def exec_command(self, cmd, in_data=None, sudoable=True):
|
||||||
|
super(Connection, self).exec_command(cmd, in_data=in_data,
|
||||||
|
sudoable=sudoable)
|
||||||
|
|
||||||
|
if cmd == "-" and not in_data.startswith(b"#!"):
|
||||||
|
# The powershell plugin sets cmd to '-' when we are executing a
|
||||||
|
# PowerShell script with in_data being the script to execute.
|
||||||
|
script = in_data
|
||||||
|
in_data = None
|
||||||
|
display.vvv("PSRP: EXEC (via pipeline wrapper)",
|
||||||
|
host=self._psrp_host)
|
||||||
|
elif cmd == "-":
|
||||||
|
# ANSIBALLZ wrapper, we need to get the interpreter and execute
|
||||||
|
# that as the script - note this won't work as basic.py relies
|
||||||
|
# on packages not available on Windows, once fixed we can enable
|
||||||
|
# this path
|
||||||
|
interpreter = to_native(in_data.splitlines()[0][2:])
|
||||||
|
# script = "$input | &'%s' -" % interpreter
|
||||||
|
# in_data = to_text(in_data)
|
||||||
|
raise AnsibleError("cannot run the interpreter '%s' on the psrp "
|
||||||
|
"connection plugin" % interpreter)
|
||||||
|
elif cmd.startswith(" ".join(_common_args) + " -EncodedCommand"):
|
||||||
|
# This is a PowerShell script encoded by the shell plugin, we will
|
||||||
|
# decode the script and execute it in the runspace instead of
|
||||||
|
# starting a new interpreter to save on time
|
||||||
|
b_command = base64.b64decode(cmd.split(" ")[-1])
|
||||||
|
script = to_text(b_command, 'utf-16-le')
|
||||||
|
display.vvv("PSRP: EXEC %s" % script, host=self._psrp_host)
|
||||||
|
else:
|
||||||
|
# in other cases we want to execute the cmd as the script
|
||||||
|
script = cmd
|
||||||
|
display.vvv("PSRP: EXEC %s" % script, host=self._psrp_host)
|
||||||
|
|
||||||
|
rc, stdout, stderr = self._exec_psrp_script(script, in_data)
|
||||||
|
return rc, stdout, stderr
|
||||||
|
|
||||||
|
def put_file(self, in_path, out_path):
|
||||||
|
super(Connection, self).put_file(in_path, out_path)
|
||||||
|
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._psrp_host)
|
||||||
|
|
||||||
|
out_path = self._shell._unquote(out_path)
|
||||||
|
script = u'''begin {
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$path = '%s'
|
||||||
|
$fd = [System.IO.File]::Create($path)
|
||||||
|
$algo = [System.Security.Cryptography.SHA1CryptoServiceProvider]::Create()
|
||||||
|
$bytes = @()
|
||||||
|
} process {
|
||||||
|
$bytes = [System.Convert]::FromBase64String($input)
|
||||||
|
$algo.TransformBlock($bytes, 0, $bytes.Length, $bytes, 0) > $null
|
||||||
|
$fd.Write($bytes, 0, $bytes.Length)
|
||||||
|
} end {
|
||||||
|
$fd.Close()
|
||||||
|
$algo.TransformFinalBlock($bytes, 0, 0) > $null
|
||||||
|
$hash = [System.BitConverter]::ToString($algo.Hash)
|
||||||
|
$hash = $hash.Replace("-", "").ToLowerInvariant()
|
||||||
|
|
||||||
|
Write-Output -InputObject "{`"sha1`":`"$hash`"}"
|
||||||
|
}''' % self._shell._escape(out_path)
|
||||||
|
|
||||||
|
cmd_parts = self._shell._encode_script(script, as_list=True,
|
||||||
|
strict_mode=False,
|
||||||
|
preserve_rc=False)
|
||||||
|
b_in_path = to_bytes(in_path, errors='surrogate_or_strict')
|
||||||
|
if not os.path.exists(b_in_path):
|
||||||
|
raise AnsibleFileNotFound('file or module does not exist: "%s"'
|
||||||
|
% to_native(in_path))
|
||||||
|
|
||||||
|
in_size = os.path.getsize(b_in_path)
|
||||||
|
buffer_size = int(self.runspace.connection.max_payload_size / 4 * 3)
|
||||||
|
|
||||||
|
# copying files is faster when using the raw WinRM shell and not PSRP
|
||||||
|
# we will create a WinRS shell just for this process
|
||||||
|
# TODO: speed this up as there is overhead creating a shell for this
|
||||||
|
with WinRS(self.runspace.connection, codepage=65001) as shell:
|
||||||
|
process = Process(shell, cmd_parts[0], cmd_parts[1:])
|
||||||
|
process.begin_invoke()
|
||||||
|
|
||||||
|
offset = 0
|
||||||
|
with open(b_in_path, 'rb') as src_file:
|
||||||
|
for data in iter((lambda: src_file.read(buffer_size)), b""):
|
||||||
|
offset += len(data)
|
||||||
|
display.vvvvv("PSRP PUT %s to %s (offset=%d, size=%d" %
|
||||||
|
(in_path, out_path, offset, len(data)),
|
||||||
|
host=self._psrp_host)
|
||||||
|
b64_data = base64.b64encode(data) + b"\r\n"
|
||||||
|
process.send(b64_data, end=(src_file.tell() == in_size))
|
||||||
|
|
||||||
|
# the file was empty, return empty buffer
|
||||||
|
if offset == 0:
|
||||||
|
process.send(b"", end=True)
|
||||||
|
|
||||||
|
process.end_invoke()
|
||||||
|
process.signal(SignalCode.CTRL_C)
|
||||||
|
|
||||||
|
if process.rc != 0:
|
||||||
|
raise AnsibleError(to_native(process.stderr))
|
||||||
|
|
||||||
|
put_output = json.loads(process.stdout)
|
||||||
|
remote_sha1 = put_output.get("sha1")
|
||||||
|
|
||||||
|
if not remote_sha1:
|
||||||
|
raise AnsibleError("Remote sha1 was not returned, stdout: '%s', "
|
||||||
|
"stderr: '%s'" % (to_native(process.stdout),
|
||||||
|
to_native(process.stderr)))
|
||||||
|
|
||||||
|
local_sha1 = secure_hash(in_path)
|
||||||
|
if not remote_sha1 == local_sha1:
|
||||||
|
raise AnsibleError("Remote sha1 hash %s does not match local hash "
|
||||||
|
"%s" % (to_native(remote_sha1),
|
||||||
|
to_native(local_sha1)))
|
||||||
|
|
||||||
|
def fetch_file(self, in_path, out_path):
|
||||||
|
super(Connection, self).fetch_file(in_path, out_path)
|
||||||
|
display.vvv("FETCH %s TO %s" % (in_path, out_path),
|
||||||
|
host=self._psrp_host)
|
||||||
|
|
||||||
|
in_path = self._shell._unquote(in_path)
|
||||||
|
out_path = out_path.replace('\\', '/')
|
||||||
|
|
||||||
|
# because we are dealing with base64 data we need to get the max size
|
||||||
|
# of the bytes that the base64 size would equal
|
||||||
|
max_b64_size = int(self.runspace.connection.max_payload_size -
|
||||||
|
(self.runspace.connection.max_payload_size / 4 * 3))
|
||||||
|
buffer_size = max_b64_size - (max_b64_size % 1024)
|
||||||
|
|
||||||
|
# setup the file stream with read only mode
|
||||||
|
setup_script = '''$ErrorActionPreference = "Stop"
|
||||||
|
$path = "%s"
|
||||||
|
|
||||||
|
if (Test-Path -Path $path -PathType Leaf) {
|
||||||
|
$fs = New-Object -TypeName System.IO.FileStream -ArgumentList @(
|
||||||
|
$path,
|
||||||
|
[System.IO.FileMode]::Open,
|
||||||
|
[System.IO.FileAccess]::Read,
|
||||||
|
[System.IO.FileShare]::Read
|
||||||
|
)
|
||||||
|
$buffer_size = %d
|
||||||
|
} elseif (Test-Path -Path $path -PathType Container) {
|
||||||
|
Write-Output -InputObject "[DIR]"
|
||||||
|
} else {
|
||||||
|
Write-Error -Message "$path does not exist"
|
||||||
|
$host.SetShouldExit(1)
|
||||||
|
}''' % (self._shell._escape(in_path), buffer_size)
|
||||||
|
|
||||||
|
# read the file stream at the offset and return the b64 string
|
||||||
|
read_script = '''$ErrorActionPreference = "Stop"
|
||||||
|
$fs.Seek(%d, [System.IO.SeekOrigin]::Begin) > $null
|
||||||
|
$buffer = New-Object -TypeName byte[] -ArgumentList $buffer_size
|
||||||
|
$bytes_read = $fs.Read($buffer, 0, $buffer_size)
|
||||||
|
|
||||||
|
if ($bytes_read -gt 0) {
|
||||||
|
$bytes = $buffer[0..($bytes_read - 1)]
|
||||||
|
Write-Output -InputObject ([System.Convert]::ToBase64String($bytes))
|
||||||
|
}'''
|
||||||
|
|
||||||
|
# need to run the setup script outside of the local scope so the
|
||||||
|
# file stream stays active between fetch operations
|
||||||
|
rc, stdout, stderr = self._exec_psrp_script(setup_script,
|
||||||
|
use_local_scope=False)
|
||||||
|
if rc != 0:
|
||||||
|
raise AnsibleError("failed to setup file stream for fetch '%s': %s"
|
||||||
|
% (out_path, to_native(stderr)))
|
||||||
|
elif stdout.strip() == '[DIR]':
|
||||||
|
# in_path was a dir so we need to create the dir locally
|
||||||
|
makedirs_safe(out_path)
|
||||||
|
return
|
||||||
|
|
||||||
|
b_out_path = to_bytes(out_path, errors='surrogate_or_strict')
|
||||||
|
makedirs_safe(os.path.dirname(b_out_path))
|
||||||
|
offset = 0
|
||||||
|
with open(b_out_path, 'wb') as out_file:
|
||||||
|
while True:
|
||||||
|
display.vvvvv("PSRP FETCH %s to %s (offset=%d" %
|
||||||
|
(in_path, out_path, offset), host=self._psrp_host)
|
||||||
|
rc, stdout, stderr = \
|
||||||
|
self._exec_psrp_script(read_script % offset)
|
||||||
|
if rc != 0:
|
||||||
|
raise AnsibleError("failed to transfer file to '%s': %s"
|
||||||
|
% (out_path, to_native(stderr)))
|
||||||
|
|
||||||
|
data = base64.b64decode(stdout.strip())
|
||||||
|
out_file.write(data)
|
||||||
|
if len(data) < buffer_size:
|
||||||
|
break
|
||||||
|
|
||||||
|
rc, stdout, stderr = self._exec_psrp_script("$fs.Close()")
|
||||||
|
if rc != 0:
|
||||||
|
display.warning("failed to close remote file stream of file "
|
||||||
|
"'%s': %s" % (in_path, to_native(stderr)))
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.runspace and self.runspace.state == RunspacePoolState.OPENED:
|
||||||
|
display.vvvvv("PSRP CLOSE RUNSPACE: %s" % (self.runspace.id),
|
||||||
|
host=self._psrp_host)
|
||||||
|
self.runspace.close()
|
||||||
|
self.runspace = None
|
||||||
|
self._connected = False
|
||||||
|
|
||||||
|
def _build_kwargs(self):
|
||||||
|
self._become_method = self._play_context.become_method
|
||||||
|
self._become_user = self._play_context.become_user
|
||||||
|
self._become_pass = self._play_context.become_pass
|
||||||
|
|
||||||
|
self._psrp_host = self.get_option('remote_addr')
|
||||||
|
self._psrp_user = self.get_option('remote_user')
|
||||||
|
self._psrp_pass = self._play_context.password
|
||||||
|
|
||||||
|
protocol = self.get_option('protocol')
|
||||||
|
port = self.get_option('port')
|
||||||
|
if protocol is None and port is None:
|
||||||
|
protocol = 'https'
|
||||||
|
port = 5986
|
||||||
|
elif protocol is None:
|
||||||
|
protocol = 'https' if int(port) != 5985 else 'http'
|
||||||
|
elif port is None:
|
||||||
|
port = 5986 if protocol == 'https' else 5985
|
||||||
|
|
||||||
|
self._psrp_protocol = protocol
|
||||||
|
self._psrp_port = int(port)
|
||||||
|
|
||||||
|
self._psrp_path = self.get_option('path')
|
||||||
|
self._psrp_auth = self.get_option('auth')
|
||||||
|
# cert validation can either be a bool or a path to the cert
|
||||||
|
cert_validation = self.get_option('cert_validation')
|
||||||
|
cert_trust_path = self.get_option('cert_trust_path')
|
||||||
|
if cert_validation == 'ignore':
|
||||||
|
self._psrp_cert_validation = False
|
||||||
|
elif cert_trust_path is not None:
|
||||||
|
self._psrp_cert_validation = cert_trust_path
|
||||||
|
else:
|
||||||
|
self._psrp_cert_validation = True
|
||||||
|
|
||||||
|
self._psrp_connection_timeout = int(self.get_option('connection_timeout'))
|
||||||
|
self._psrp_message_encryption = self.get_option('message_encryption')
|
||||||
|
self._psrp_proxy = self.get_option('proxy')
|
||||||
|
self._psrp_ignore_proxy = boolean(self.get_option('ignore_proxy'))
|
||||||
|
self._psrp_operation_timeout = int(self.get_option('operation_timeout'))
|
||||||
|
self._psrp_max_envelope_size = int(self.get_option('max_envelope_size'))
|
||||||
|
self._psrp_configuration_name = self.get_option('configuration_name')
|
||||||
|
|
||||||
|
supported_args = []
|
||||||
|
for auth_kwarg in AUTH_KWARGS.values():
|
||||||
|
supported_args.extend(auth_kwarg)
|
||||||
|
extra_args = set([v.replace('ansible_psrp_', '') for v in
|
||||||
|
self.get_option('_extras')])
|
||||||
|
unsupported_args = extra_args.difference(supported_args)
|
||||||
|
|
||||||
|
for arg in unsupported_args:
|
||||||
|
display.warning("ansible_psrp_%s is unsupported by the current "
|
||||||
|
"psrp version installed" % arg)
|
||||||
|
|
||||||
|
self._psrp_conn_kwargs = dict(
|
||||||
|
server=self._psrp_host, port=self._psrp_port,
|
||||||
|
username=self._psrp_user, password=self._psrp_pass,
|
||||||
|
ssl=self._psrp_protocol == 'https', path=self._psrp_path,
|
||||||
|
auth=self._psrp_auth, cert_validation=self._psrp_cert_validation,
|
||||||
|
connection_timeout=self._psrp_connection_timeout,
|
||||||
|
encryption=self._psrp_message_encryption, proxy=self._psrp_proxy,
|
||||||
|
no_proxy=self._psrp_ignore_proxy,
|
||||||
|
max_envelope_size=self._psrp_max_envelope_size,
|
||||||
|
operation_timeout=self._psrp_operation_timeout,
|
||||||
|
)
|
||||||
|
# add in the extra args that were set
|
||||||
|
for arg in extra_args.intersection(supported_args):
|
||||||
|
option = self.get_option('_extras')['ansible_psrp_%s' % arg]
|
||||||
|
self._psrp_conn_kwargs[arg] = option
|
||||||
|
|
||||||
|
def _exec_psrp_script(self, script, input_data=None, use_local_scope=True):
|
||||||
|
ps = PowerShell(self.runspace)
|
||||||
|
ps.add_script(script, use_local_scope=use_local_scope)
|
||||||
|
ps.invoke(input=input_data)
|
||||||
|
|
||||||
|
rc, stdout, stderr = self._parse_pipeline_result(ps)
|
||||||
|
return rc, stdout, stderr
|
||||||
|
|
||||||
|
def _parse_pipeline_result(self, pipeline):
|
||||||
|
"""
|
||||||
|
PSRP doesn't have the same concept as other protocols with its output.
|
||||||
|
We need some extra logic to convert the pipeline streams and host
|
||||||
|
output into the format that Ansible understands.
|
||||||
|
|
||||||
|
:param pipeline: The finished PowerShell pipeline that invoked our
|
||||||
|
commands
|
||||||
|
:return: rc, stdout, stderr based on the pipeline output
|
||||||
|
"""
|
||||||
|
# we try and get the rc from our host implementation, this is set if
|
||||||
|
# exit or $host.SetShouldExit() is called in our pipeline, if not we
|
||||||
|
# set to 0 if the pipeline had not errors and 1 if it did
|
||||||
|
rc = self.host.rc or (1 if pipeline.had_errors else 0)
|
||||||
|
|
||||||
|
# TODO: figure out a better way of merging this with the host output
|
||||||
|
stdout_list = []
|
||||||
|
for output in pipeline.output:
|
||||||
|
# not all pipeline outputs can be casted to a string, we will
|
||||||
|
# create our own output based on the properties if that is the
|
||||||
|
# case+
|
||||||
|
try:
|
||||||
|
output_msg = str(output)
|
||||||
|
except TypeError:
|
||||||
|
if isinstance(output, GenericComplexObject):
|
||||||
|
obj_lines = output.property_sets
|
||||||
|
for key, value in output.adapted_properties.items():
|
||||||
|
obj_lines.append("%s: %s" % (key, value))
|
||||||
|
for key, value in output.extended_properties.items():
|
||||||
|
obj_lines.append("%s: %s" % (key, value))
|
||||||
|
output_msg = "\n".join(obj_lines)
|
||||||
|
else:
|
||||||
|
output_msg = ""
|
||||||
|
stdout_list.append(output_msg)
|
||||||
|
|
||||||
|
stdout = "\r\n".join(stdout_list)
|
||||||
|
if len(self.host.ui.stdout) > 0:
|
||||||
|
stdout += "\r\n" + "".join(self.host.ui.stdout)
|
||||||
|
|
||||||
|
stderr_list = []
|
||||||
|
for error in pipeline.streams.error:
|
||||||
|
# the error record is not as fully fleshed out like we usually get
|
||||||
|
# in PS, we will manually create it here
|
||||||
|
error_msg = "%s : %s\r\n" \
|
||||||
|
"%s\r\n" \
|
||||||
|
" + CategoryInfo : %s\r\n" \
|
||||||
|
" + FullyQualifiedErrorId : %s" \
|
||||||
|
% (error.command_name, str(error),
|
||||||
|
error.invocation_position_message, error.message,
|
||||||
|
error.fq_error)
|
||||||
|
stacktrace = error.script_stacktrace
|
||||||
|
if self._play_context.verbosity >= 3 and stacktrace is not None:
|
||||||
|
error_msg += "\r\nStackTrace:\r\n%s" % stacktrace
|
||||||
|
stderr_list.append(error_msg)
|
||||||
|
|
||||||
|
stderr = "\r\n".join(stderr_list)
|
||||||
|
if len(self.host.ui.stderr) > 0:
|
||||||
|
stderr += "\r\n" + "".join(self.host.ui.stderr)
|
||||||
|
|
||||||
|
display.vvvvv("PSRP RC: %d" % rc, host=self._psrp_host)
|
||||||
|
display.vvvvv("PSRP STDOUT: %s" % stdout, host=self._psrp_host)
|
||||||
|
display.vvvvv("PSRP STDERR: %s" % stderr, host=self._psrp_host)
|
||||||
|
|
||||||
|
# reset the host back output back to defaults, needed if running
|
||||||
|
# multiple pipelines on the same RunspacePool
|
||||||
|
self.host.rc = 0
|
||||||
|
self.host.ui.stdout = []
|
||||||
|
self.host.ui.stderr = []
|
||||||
|
|
||||||
|
return rc, stdout, stderr
|
|
@ -171,8 +171,12 @@ Function Run($payload) {
|
||||||
$ps.AddCommand("Out-Null") | Out-Null
|
$ps.AddCommand("Out-Null") | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
# force input encoding to preamble-free UTF8 so PS sub-processes (eg, Start-Job) don't blow up
|
# force input encoding to preamble-free UTF8 so PS sub-processes (eg,
|
||||||
$ps.AddStatement().AddScript("[Console]::InputEncoding = New-Object Text.UTF8Encoding `$false") | Out-Null
|
# Start-Job) don't blow up. This is only required for WinRM, a PSRP
|
||||||
|
# runspace doesn't have a host console and this will bomb out
|
||||||
|
if ($host.Name -eq "ConsoleHost") {
|
||||||
|
$ps.AddStatement().AddScript("[Console]::InputEncoding = New-Object Text.UTF8Encoding `$false") | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
$ps.AddStatement().AddScript($entrypoint) | Out-Null
|
$ps.AddStatement().AddScript($entrypoint) | Out-Null
|
||||||
|
|
||||||
|
@ -182,7 +186,7 @@ Function Run($payload) {
|
||||||
|
|
||||||
# PS3 doesn't properly set HadErrors in many cases, inspect the error stream as a fallback
|
# PS3 doesn't properly set HadErrors in many cases, inspect the error stream as a fallback
|
||||||
If ($ps.HadErrors -or ($PSVersionTable.PSVersion.Major -lt 4 -and $ps.Streams.Error.Count -gt 0)) {
|
If ($ps.HadErrors -or ($PSVersionTable.PSVersion.Major -lt 4 -and $ps.Streams.Error.Count -gt 0)) {
|
||||||
[System.Console]::Error.WriteLine($($ps.Streams.Error | Out-String))
|
$host.UI.WriteErrorLine($($ps.Streams.Error | Out-String))
|
||||||
$exit_code = $ps.Runspace.SessionStateProxy.GetVariable("LASTEXITCODE")
|
$exit_code = $ps.Runspace.SessionStateProxy.GetVariable("LASTEXITCODE")
|
||||||
If(-not $exit_code) {
|
If(-not $exit_code) {
|
||||||
$exit_code = 1
|
$exit_code = 1
|
||||||
|
@ -210,7 +214,7 @@ using System.Security.Principal;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ansible
|
namespace AnsibleBecome
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public class SECURITY_ATTRIBUTES
|
public class SECURITY_ATTRIBUTES
|
||||||
|
@ -931,6 +935,7 @@ $become_exec_wrapper = {
|
||||||
}
|
}
|
||||||
|
|
||||||
$exec_wrapper = {
|
$exec_wrapper = {
|
||||||
|
&chcp.com 65001 > $null
|
||||||
Set-StrictMode -Version 2
|
Set-StrictMode -Version 2
|
||||||
$DebugPreference = "Continue"
|
$DebugPreference = "Continue"
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
@ -1011,8 +1016,8 @@ Function Parse-EnumValue($enum, $flag_type, $value, $prefix) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Function Parse-BecomeFlags($flags) {
|
Function Parse-BecomeFlags($flags) {
|
||||||
$logon_type = [Ansible.LogonType]::LOGON32_LOGON_INTERACTIVE
|
$logon_type = [AnsibleBecome.LogonType]::LOGON32_LOGON_INTERACTIVE
|
||||||
$logon_flags = [Ansible.LogonFlags]::LOGON_WITH_PROFILE
|
$logon_flags = [AnsibleBecome.LogonFlags]::LOGON_WITH_PROFILE
|
||||||
|
|
||||||
if ($flags -eq $null -or $flags -eq "") {
|
if ($flags -eq $null -or $flags -eq "") {
|
||||||
$flag_split = @()
|
$flag_split = @()
|
||||||
|
@ -1031,7 +1036,7 @@ Function Parse-BecomeFlags($flags) {
|
||||||
$flag_value = $split[1]
|
$flag_value = $split[1]
|
||||||
if ($flag_key -eq "logon_type") {
|
if ($flag_key -eq "logon_type") {
|
||||||
$enum_details = @{
|
$enum_details = @{
|
||||||
enum = [Ansible.LogonType]
|
enum = [AnsibleBecome.LogonType]
|
||||||
flag_type = $flag_key
|
flag_type = $flag_key
|
||||||
value = $flag_value
|
value = $flag_value
|
||||||
prefix = "LOGON32_LOGON_"
|
prefix = "LOGON32_LOGON_"
|
||||||
|
@ -1039,13 +1044,13 @@ Function Parse-BecomeFlags($flags) {
|
||||||
$logon_type = Parse-EnumValue @enum_details
|
$logon_type = Parse-EnumValue @enum_details
|
||||||
} elseif ($flag_key -eq "logon_flags") {
|
} elseif ($flag_key -eq "logon_flags") {
|
||||||
$logon_flag_values = $flag_value.Split(",")
|
$logon_flag_values = $flag_value.Split(",")
|
||||||
$logon_flags = 0 -as [Ansible.LogonFlags]
|
$logon_flags = 0 -as [AnsibleBecome.LogonFlags]
|
||||||
foreach ($logon_flag_value in $logon_flag_values) {
|
foreach ($logon_flag_value in $logon_flag_values) {
|
||||||
if ($logon_flag_value -eq "") {
|
if ($logon_flag_value -eq "") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
$enum_details = @{
|
$enum_details = @{
|
||||||
enum = [Ansible.LogonFlags]
|
enum = [AnsibleBecome.LogonFlags]
|
||||||
flag_type = $flag_key
|
flag_type = $flag_key
|
||||||
value = $logon_flag_value
|
value = $logon_flag_value
|
||||||
prefix = "LOGON_"
|
prefix = "LOGON_"
|
||||||
|
@ -1058,7 +1063,7 @@ Function Parse-BecomeFlags($flags) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $logon_type, [Ansible.LogonFlags]$logon_flags
|
return $logon_type, [AnsibleBecome.LogonFlags]$logon_flags
|
||||||
}
|
}
|
||||||
|
|
||||||
Function Run($payload) {
|
Function Run($payload) {
|
||||||
|
@ -1098,14 +1103,14 @@ Function Run($payload) {
|
||||||
$lp_current_directory = "$env:SystemRoot"
|
$lp_current_directory = "$env:SystemRoot"
|
||||||
|
|
||||||
Try {
|
Try {
|
||||||
$result = [Ansible.BecomeUtil]::RunAsUser($username, $password, $lp_command_line, $lp_current_directory, $exec_wrapper, $logon_flags, $logon_type)
|
$result = [AnsibleBecome.BecomeUtil]::RunAsUser($username, $password, $lp_command_line, $lp_current_directory, $exec_wrapper, $logon_flags, $logon_type)
|
||||||
$stdout = $result.StandardOut
|
$stdout = $result.StandardOut
|
||||||
$stdout = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($stdout.Trim()))
|
$stdout = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($stdout.Trim()))
|
||||||
$stderr = $result.StandardError
|
$stderr = $result.StandardError
|
||||||
$rc = $result.ExitCode
|
$rc = $result.ExitCode
|
||||||
|
|
||||||
[Console]::Out.WriteLine($stdout)
|
$host.UI.WriteLine($stdout)
|
||||||
[Console]::Error.WriteLine($stderr.Trim())
|
$host.UI.WriteErrorLine($stderr.Trim())
|
||||||
} Catch {
|
} Catch {
|
||||||
$excep = $_
|
$excep = $_
|
||||||
Dump-Error -excep $excep -msg "Failed to become user $username"
|
Dump-Error -excep $excep -msg "Failed to become user $username"
|
||||||
|
@ -1521,7 +1526,7 @@ class ShellModule(ShellBase):
|
||||||
script = '''
|
script = '''
|
||||||
$tmp_path = [System.Environment]::ExpandEnvironmentVariables('%s')
|
$tmp_path = [System.Environment]::ExpandEnvironmentVariables('%s')
|
||||||
$tmp = New-Item -Type Directory -Path $tmp_path -Name '%s'
|
$tmp = New-Item -Type Directory -Path $tmp_path -Name '%s'
|
||||||
$tmp.FullName | Write-Host -Separator ''
|
Write-Output -InputObject $tmp.FullName
|
||||||
''' % (basetmpdir, basefile)
|
''' % (basetmpdir, basefile)
|
||||||
return self._encode_script(script.strip())
|
return self._encode_script(script.strip())
|
||||||
|
|
||||||
|
@ -1531,11 +1536,11 @@ class ShellModule(ShellBase):
|
||||||
# in the user's home directory.
|
# in the user's home directory.
|
||||||
user_home_path = self._unquote(user_home_path)
|
user_home_path = self._unquote(user_home_path)
|
||||||
if user_home_path == '~':
|
if user_home_path == '~':
|
||||||
script = 'Write-Host (Get-Location).Path'
|
script = 'Write-Output (Get-Location).Path'
|
||||||
elif user_home_path.startswith('~\\'):
|
elif user_home_path.startswith('~\\'):
|
||||||
script = 'Write-Host ((Get-Location).Path + "%s")' % self._escape(user_home_path[1:])
|
script = 'Write-Output ((Get-Location).Path + "%s")' % self._escape(user_home_path[1:])
|
||||||
else:
|
else:
|
||||||
script = 'Write-Host "%s"' % self._escape(user_home_path)
|
script = 'Write-Output "%s"' % self._escape(user_home_path)
|
||||||
return self._encode_script(script)
|
return self._encode_script(script)
|
||||||
|
|
||||||
def exists(self, path):
|
def exists(self, path):
|
||||||
|
@ -1549,7 +1554,7 @@ class ShellModule(ShellBase):
|
||||||
{
|
{
|
||||||
$res = 1;
|
$res = 1;
|
||||||
}
|
}
|
||||||
Write-Host "$res";
|
Write-Output "$res";
|
||||||
Exit $res;
|
Exit $res;
|
||||||
''' % path
|
''' % path
|
||||||
return self._encode_script(script)
|
return self._encode_script(script)
|
||||||
|
@ -1566,11 +1571,11 @@ class ShellModule(ShellBase):
|
||||||
}
|
}
|
||||||
ElseIf (Test-Path -PathType Container "%(path)s")
|
ElseIf (Test-Path -PathType Container "%(path)s")
|
||||||
{
|
{
|
||||||
Write-Host "3";
|
Write-Output "3";
|
||||||
}
|
}
|
||||||
Else
|
Else
|
||||||
{
|
{
|
||||||
Write-Host "1";
|
Write-Output "1";
|
||||||
}
|
}
|
||||||
''' % dict(path=path)
|
''' % dict(path=path)
|
||||||
return self._encode_script(script)
|
return self._encode_script(script)
|
||||||
|
@ -1633,7 +1638,7 @@ class ShellModule(ShellBase):
|
||||||
return self._encode_script(script, preserve_rc=False)
|
return self._encode_script(script, preserve_rc=False)
|
||||||
|
|
||||||
def wrap_for_exec(self, cmd):
|
def wrap_for_exec(self, cmd):
|
||||||
return '& %s' % cmd
|
return '& %s; exit $LASTEXITCODE' % cmd
|
||||||
|
|
||||||
def _unquote(self, value):
|
def _unquote(self, value):
|
||||||
'''Remove any matching quotes that wrap the given value.'''
|
'''Remove any matching quotes that wrap the given value.'''
|
||||||
|
|
3
test/integration/targets/connection_psrp/aliases
Normal file
3
test/integration/targets/connection_psrp/aliases
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
windows
|
||||||
|
shippable/windows/group1
|
||||||
|
shippable/windows/smoketest
|
15
test/integration/targets/connection_psrp/runme.sh
Executable file
15
test/integration/targets/connection_psrp/runme.sh
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
pip install pypsrp
|
||||||
|
cd ../connection
|
||||||
|
|
||||||
|
INVENTORY=../../inventory.winrm ./test.sh \
|
||||||
|
-e target_hosts=winrm \
|
||||||
|
-e action_prefix=win_ \
|
||||||
|
-e local_tmp=/tmp/ansible-local \
|
||||||
|
-e remote_tmp=c:/windows/temp/ansible-remote \
|
||||||
|
-e ansible_psrp_cert_validation=False \
|
||||||
|
-c psrp \
|
||||||
|
"$@"
|
|
@ -395,7 +395,7 @@ class PathMapper(object):
|
||||||
|
|
||||||
# entire integration test commands depend on these connection plugins
|
# entire integration test commands depend on these connection plugins
|
||||||
|
|
||||||
if name == 'winrm':
|
if name in ['winrm', 'psrp']:
|
||||||
return {
|
return {
|
||||||
'windows-integration': self.integration_all_target,
|
'windows-integration': self.integration_all_target,
|
||||||
'units': units_path,
|
'units': units_path,
|
||||||
|
|
147
test/units/plugins/connection/test_psrp.py
Normal file
147
test/units/plugins/connection/test_psrp.py
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# (c) 2018, Jordan Borean <jborean@redhat.com>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
from ansible.playbook.play_context import PlayContext
|
||||||
|
from ansible.plugins.loader import connection_loader
|
||||||
|
|
||||||
|
pytest.importorskip("pypsrp")
|
||||||
|
|
||||||
|
|
||||||
|
class TestConnectionWinRM(object):
|
||||||
|
|
||||||
|
OPTIONS_DATA = (
|
||||||
|
# default options
|
||||||
|
(
|
||||||
|
{'_extras': {}},
|
||||||
|
{
|
||||||
|
'_psrp_auth': 'negotiate',
|
||||||
|
'_psrp_cert_validation': True,
|
||||||
|
'_psrp_configuration_name': 'Microsoft.PowerShell',
|
||||||
|
'_psrp_connection_timeout': 30,
|
||||||
|
'_psrp_message_encryption': 'auto',
|
||||||
|
'_psrp_host': 'inventory_hostname',
|
||||||
|
'_psrp_conn_kwargs': {
|
||||||
|
'server': 'inventory_hostname',
|
||||||
|
'port': 5986,
|
||||||
|
'username': None,
|
||||||
|
'password': '',
|
||||||
|
'ssl': True,
|
||||||
|
'path': 'wsman',
|
||||||
|
'auth': 'negotiate',
|
||||||
|
'cert_validation': True,
|
||||||
|
'connection_timeout': 30,
|
||||||
|
'encryption': 'auto',
|
||||||
|
'proxy': None,
|
||||||
|
'no_proxy': False,
|
||||||
|
'max_envelope_size': 153600,
|
||||||
|
'operation_timeout': 20,
|
||||||
|
},
|
||||||
|
'_psrp_max_envelope_size': 153600,
|
||||||
|
'_psrp_ignore_proxy': False,
|
||||||
|
'_psrp_operation_timeout': 20,
|
||||||
|
'_psrp_pass': '',
|
||||||
|
'_psrp_path': 'wsman',
|
||||||
|
'_psrp_port': 5986,
|
||||||
|
'_psrp_proxy': None,
|
||||||
|
'_psrp_protocol': 'https',
|
||||||
|
'_psrp_user': None
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# ssl=False when port defined to 5985
|
||||||
|
(
|
||||||
|
{'_extras': {}, 'ansible_port': '5985'},
|
||||||
|
{
|
||||||
|
'_psrp_port': 5985,
|
||||||
|
'_psrp_protocol': 'http'
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# ssl=True when port defined to not 5985
|
||||||
|
(
|
||||||
|
{'_extras': {}, 'ansible_port': 1234},
|
||||||
|
{
|
||||||
|
'_psrp_port': 1234,
|
||||||
|
'_psrp_protocol': 'https'
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# port 5986 when ssl=True
|
||||||
|
(
|
||||||
|
{'_extras': {}, 'ansible_psrp_protocol': 'https'},
|
||||||
|
{
|
||||||
|
'_psrp_port': 5986,
|
||||||
|
'_psrp_protocol': 'https'
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# port 5985 when ssl=False
|
||||||
|
(
|
||||||
|
{'_extras': {}, 'ansible_psrp_protocol': 'http'},
|
||||||
|
{
|
||||||
|
'_psrp_port': 5985,
|
||||||
|
'_psrp_protocol': 'http'
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# psrp extras
|
||||||
|
(
|
||||||
|
{'_extras': {'ansible_psrp_negotiate_delegate': True}},
|
||||||
|
{
|
||||||
|
'_psrp_conn_kwargs': {
|
||||||
|
'server': 'inventory_hostname',
|
||||||
|
'port': 5986,
|
||||||
|
'username': None,
|
||||||
|
'password': '',
|
||||||
|
'ssl': True,
|
||||||
|
'path': 'wsman',
|
||||||
|
'auth': 'negotiate',
|
||||||
|
'cert_validation': True,
|
||||||
|
'connection_timeout': 30,
|
||||||
|
'encryption': 'auto',
|
||||||
|
'proxy': None,
|
||||||
|
'no_proxy': False,
|
||||||
|
'max_envelope_size': 153600,
|
||||||
|
'operation_timeout': 20,
|
||||||
|
'negotiate_delegate': True,
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# cert validation through string repr of bool
|
||||||
|
(
|
||||||
|
{'_extras': {}, 'ansible_psrp_cert_validation': 'ignore'},
|
||||||
|
{
|
||||||
|
'_psrp_cert_validation': False
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# cert validation path
|
||||||
|
(
|
||||||
|
{'_extras': {}, 'ansible_psrp_cert_trust_path': '/path/cert.pem'},
|
||||||
|
{
|
||||||
|
'_psrp_cert_validation': '/path/cert.pem'
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# pylint bug: https://github.com/PyCQA/pylint/issues/511
|
||||||
|
# pylint: disable=undefined-variable
|
||||||
|
@pytest.mark.parametrize('options, expected',
|
||||||
|
((o, e) for o, e in OPTIONS_DATA))
|
||||||
|
def test_set_options(self, options, expected):
|
||||||
|
pc = PlayContext()
|
||||||
|
new_stdin = StringIO()
|
||||||
|
|
||||||
|
conn = connection_loader.get('psrp', pc, new_stdin)
|
||||||
|
conn.set_options(var_options=options)
|
||||||
|
conn._build_kwargs()
|
||||||
|
|
||||||
|
for attr, expected in expected.items():
|
||||||
|
actual = getattr(conn, attr)
|
||||||
|
assert actual == expected, \
|
||||||
|
"psrp attr '%s', actual '%s' != expected '%s'"\
|
||||||
|
% (attr, actual, expected)
|
Loading…
Add table
Add a link
Reference in a new issue