1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Merge pull request #7861 from cchurch/devel

Windows Remote Support
This commit is contained in:
Michael DeHaan 2014-06-19 22:10:41 -05:00
commit 5156794738
58 changed files with 2758 additions and 128 deletions

View file

@ -3,6 +3,10 @@ Ansible Changes By Release
## 1.7 "Summer Nights" - Active Development
Major new features:
* Windows support (alpha) using native PowerShell remoting
New inventory scripts:
* SoftLayer
@ -14,6 +18,13 @@ New Modules:
* cloud: rax_meta
* cloud: rax_scaling_group
* cloud: rax_scaling_policy
* windows: version of setup module
* windows: version of slurp module
* windows: win_feature
* windows: win_get_url
* windows: win_msi
* windows: win_ping
* windows: win_user
## 1.6.3 "And the Cradle Will Rock" - Jun 09, 2014

View file

@ -3,6 +3,7 @@ include examples/hosts
include examples/ansible.cfg
graft examples/playbooks
include packaging/distutils/setup.py
include lib/ansible/module_common/*.ps1
recursive-include docs *
recursive-include library *
include Makefile

View file

@ -13,5 +13,5 @@ Before we dive into the really fun parts -- playbooks, configuration management,
intro_patterns
intro_adhoc
intro_configuration
intro_windows

View file

@ -0,0 +1,270 @@
Windows Support
===============
.. contents:: Topics
.. _windows_how_does_it_work:
Windows: How Does It Work
`````````````````````````
As you may have already read, Ansible manages Linux/Unix machines using SSH by default.
Starting in version 1.7, Ansible also contains support for managing Windows machines. This uses
native powershell remoting, rather than SSH.
Ansible will still be run from a Linux control machine, and uses the "winrm" Python module to talk to remote hosts.
No additional software needs to be installed on the remote machines for Ansible to manage them, it still maintains the agentless properties that make it popular on Linux/Unix.
Note that it is expected you have a basic understanding of Ansible prior to jumping into this section, so if you haven't written a Linux playbook first, it might be worthwhile to dig in there first.
.. _windows_installing:
Installing on the Control Machine
``````````````````````````````````
On a Linux control machine::
pip install http://github.com/diyan/pywinrm/archive/master.zip#egg=pywinrm
.. _windows_inventory:
Inventory
`````````
Ansible's windows support relies on a few standard variables to indicate the username, password, and connection type (windows) of the remote hosts. These variables are most easily set up in inventory. This is used instead of SSH-keys or passwords as normally fed into Ansible::
[windows]
winserver1.example.com
winserver2.example.com
In group_vars/windows.yml, define the following inventory variables::
# it is suggested that these be encrypted with ansible-vault:
# ansible-vault edit group_vars/windows.yml
ansible_ssh_user: Administrator
ansible_ssh_pass: SekritPasswordGoesHere
ansible_ssh_port: 5986
ansible_connection: winrm
Notice that the ssh_port is not actually for SSH, but this is a holdover variable name from how Ansible is mostly an SSH-oriented system. Again, Windows management will not happen over SSH.
When using your playbook, don't forget to specify --ask-vault-pass to provide the password to unlock the file.
Test your configuration like so, by trying to contact your Windows nodes. Note this is not an ICMP ping, but tests the Ansible
communication channel that leverages Windows remoting::
ansible windows [-i inventory] -m win_ping --ask-vault-pass
If you haven't done anything to prep your systems yet, this won't work yet. This is covered in a later
section about how to enable powershell remoting - and if neccessary - how to upgrade powershell to
a version that is 3 or higher.
You'll run this command again later though, to make sure everything is working.
.. _windows_system_prep:
Windows System Prep
```````````````````
In order for Ansible to manage your windows machines, you will have to enable Powershell remoting first, which also enables WinRM.
From the Windows host, launch the Powershell Client. For information on Powershell, visit `Microsoft's Using Powershell article <http://technet.microsoft.com/en-us/library/dn425048.aspx>`_.
In the powershell session, run the following to enable PS Remoting and set the execution policy
.. code-block:: bash
$ Enable-PSRemoting -Force
$ Set-ExecutionPolicy RemoteSigned
If your Windows firewall is enabled, you must also run the following command to allow firewall access to the public firewall profile:
.. code-block:: bash
# Windows 2012 / 2012R2
$ Set-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" -RemoteAddress Any
# Windows 2008 / 2008R2
$ NetSH ADVFirewall Set AllProfiles Settings remotemanagement Enable
By default, Powershell remoting enables an HTTP listener. The following commands enable an HTTPS listener, which secures communication between the Control Machine and windows.
An SSL certificate for server authentication is required to create the HTTPS listener. The existence of an existing certificate in the computer account can be verified by using the MMC snap-in.
A best practice for SSL certificates is generating them from an internal or external certificate authority. An existing certificate could be located in the computer account certificate store `using the following article <http://technet.microsoft.com/en-us/library/cc754431.aspx#BKMK_computer>`_.
Alternatively, a self-signed SSL certificate can be generated in powershell using `the following technet article <http://social.technet.microsoft.com/wiki/contents/articles/4714.how-to-generate-a-self-signed-certificate-using-powershell.aspx>`_. At a minimum, the subject name should match the hostname, and Server Authentication is required. Once the self signed certificate is obtained, the certificate thumbprint can be identified using `How to: Retrieve the Thumbprint of a Certificate <http://msdn.microsoft.com/en-us/library/ms734695%28v=vs.110%29.aspx>`_.
.. code-block:: bash
# Create the https listener
$ winrm create winrm/config/Listener?Address=*+Transport=HTTPS  @{Hostname="host_name";CertificateThumbprint="certificate_thumbprint"}
# Delete the http listener
$ WinRM delete winrm/config/listener?Address=*+Transport=HTTP
Again, if your Windows firewall is enabled, the following command to allow firewall access to the HTTPS listener:
.. code-block:: bash
# Windows 2008 / 2008R2 / 2012 / 2012R2
$ netsh advfirewall firewall add rule Profile=public name="Allow WinRM HTTPS" dir=in localport=5986 protocol=TCP action=allow
It's time to verify things are working::
ansible windows [-i inventory] -m win_ping --ask-vault-pass
However, if you are still running Powershell 2.0 on remote systems, it's time to use Ansible to upgrade powershell
before proceeding further, as some of the Ansible modules will require Powershell 3.0.
In the future, Ansible may provide a shortcut installer that automates these steps for prepping a Windows machine.
.. _getting_to_powershell_three_or_higher:
Getting to Powershell 3.0 or higher
```````````````````````````````````
Powershell 3.0 or higher is needed for most provided Ansible modules for Windows.
Looking at an ansible checkout, copy the `examples/scripts/upgrade_to_ps3.ps1 <https://github.com/cchurch/ansible/blob/devel/examples/scripts/upgrade_to_ps3.ps1>`_ script onto the remote host and run a powershell console as an administrator. You will now be running Powershell 3 and can try connectivity again using the win_ping technique referenced above.
.. _what_windows_modules_are_available:
What modules are available
``````````````````````````
Most of the Ansible modules in core Ansible are written for a combination of Linux/Unix machines and arbitrary web services, though there are various
Windows modules as listed in the `"windows" subcategory of the Ansible module index <http://docs.ansible.com/list_of_windows_modules.html>`_.
Browse this index to see what is available.
In many cases, it may not be neccessary to even write or use an Ansible module.
In particular, the "script" module can be used to run arbitrary powershell scripts, allowing Windows administrators familiar with powershell a very native way to do things, as in the following playbook::
- hosts: windows
tasks:
- script: foo.ps1 --argument --other-argument
Note there are a few other Ansible modules that don't start with "win" that also function, including "slurp", "raw", and "setup" (which is how fact gathering works).
.. _developers_developers_developers:
Developers: Supported modules and how it works
``````````````````````````````````````````````
Developing ansible modules are covered in a `later section of the documentation <http://developing_modules.html>`_, with a focus on Linux/Unix.
What if you want to write Windows modules for ansible though?
For Windows, ansible modules are implemented in Powershell. Skim those Linux/Unix module development chapters before proceeding.
Windows modules live in a "windows/" subfolder in the Ansible "library/" subtree. For example, if a module is named
"library/windows/win_ping", there will be embedded documentation in the "win_ping" file, and the actual powershell code will live in a "win_ping.ps1" file. Take a look at the sources and this will make more sense.
Modules (ps1 files) should start as follows::
#!powershell
# <license>
# WANT_JSON
# POWERSHELL_COMMON
# code goes here, reading in stdin as JSON and outputting JSON
The above magic is neccessary to tell Ansible to mix in some common code and also know how to push modules out. The common code contains some nice wrappers around working with hash data structures and emitting JSON results, and possibly a few mpmore useful things. Regular Ansible has this same concept for reusing Python code - this is just the windows equivalent.
What modules you see in windows/ are just a start. Additional modules may be submitted as pull requests to github.
.. _windows_and_linux_control_machine:
Reminder: You Must Have a Linux Control Machine
```````````````````````````````````````````````
Note running Ansible from a Windows control machine is NOT a goal of the project. Refrain from asking for this feature,
as it limits what technologies, features, and code we can use in the main project in the future. A Linux control machine
will be required to manage Windows hosts.
Cygwin is not supported, so please do not ask questions about Ansible running from Cygwin.
.. _windows_facts:
Windows Facts
`````````````
Just as with Linux/Unix, facts can be gathered for windows hosts, which will return things such as the operating system version. To see what variables are available about a windows host, run the following::
ansible winhost.example.com -m setup
Note that this command invocation is exactly the same as the Linux/Unix equivalent.
.. _windows_playbook_example:
Windows Playbook Examples
`````````````````````````
Look to the list of windows modules for most of what is possible, though also some modules like "raw" and "script" also work on Windows, as do "fetch" and "slurp".
Here is an example of pushing and running a powershell script::
- name: test script module
hosts: windows
tasks:
- name: run test script
script: files/test_script.ps1
Running individual commands uses the 'raw' module, as opposed to the shell or command module as is common on Linux/Unix operating systems::
- name: test raw module
hosts: windows
tasks:
- name: run ipconfig
raw: ipconfig
register: ipconfig
- debug: var=ipconfig
And for a final example, here's how to use the win_stat module to test for file existance. Note that the data returned byt he win_stat module is slightly different than what is provided by the Linux equivalent::
- name: test stat module
hosts: windows
tasks:
- name: test stat module on file
win_stat: path="C:/Windows/win.ini"
register: stat_file
- debug: var=stat_file
- name: check stat_file result
assert:
that:
- "stat_file.stat.exists"
- "not stat_file.stat.isdir"
- "stat_file.stat.size > 0"
- "stat_file.stat.md5"
Again, recall that the Windows modules are all listed in the Windows category of modules, with the exception that the "raw", "script", and "fetch" modules are also available. These modules do not start with a "win" prefix.
.. _windows_contributions:
Windows Contributions
`````````````````````
Windows support in Ansible is still very new, and contributions are quite welcome, whether this is in the
form of new modules, tweaks to existing modules, documentation, or something else. Please stop by the ansible-devel mailing list if you would like to get involved and say hi.
.. seealso::
:doc:`developing_modules`
How to write modules
:doc:`playbooks`
Learning ansible's configuration management language
`List of Windows Modules <http://docs.ansible.com/list_of_windows_modules.html>`_
Windows specific module list, all implemented in powershell
`Mailing List <http://groups.google.com/group/ansible-project>`_
Questions? Help? Ideas? Stop by the list on Google Groups
`irc.freenode.net <http://irc.freenode.net>`_
#ansible IRC chat channel

View file

@ -216,14 +216,13 @@ Version Comparison Filters
.. versionadded:: 1.6
To compare a version number, such as checking if the ``ansible_distribution_version``
version is greater than or equal to '12.04', you can use the ``version_compare`` filter::
version is greater than or equal to '12.04', you can use the ``version_compare`` filter.
The ``version_compare`` filter can also be used to evaluate the ``ansible_distribution_version``::
{{ ansible_distribution_version | version_compare('12.04', '>=') }}
If ``ansible_distribution_version`` is greater than or equal to 12, this filter will return True, otherwise
it will return False.
If ``ansible_distribution_version`` is greater than or equal to 12, this filter will return True, otherwise it will return False.
The ``version_compare`` filter accepts the following operators::
@ -234,10 +233,10 @@ be used. The default is ``False``, and if set as ``True`` will use more strict
{{ sample_version_var | version_compare('1.0', operator='lt', strict=True) }}
.. _random_filter
.. _random_filter:
Random Number Filter
--------------------------
--------------------
.. versionadded:: 1.6

View file

@ -0,0 +1,82 @@
# Powershell script to upgrade a PowerShell 2.0 system to PowerShell 3.0
# based on http://occasionalutility.blogspot.com/2013/11/everyday-powershell-part-7-powershell.html
#
# some Ansible modules that may use Powershell 3 features, so systems may need
# to be upgraded. This may be used by a sample playbook. Refer to the windows
# documentation on docs.ansible.com for details.
#
# - hosts: windows
# tasks:
# - script: upgrade_to_ps3.ps1
# Get version of OS
# 6.0 is 2008
# 6.1 is 2008 R2
# 6.2 is 2012
# 6.3 is 2012 R2
if ($PSVersionTable.psversion.Major -ge 3)
{
write-host "Powershell 3 Installed already; You don't need this"
Exit
}
$powershellpath = "C:\powershell"
function download-file
{
param ([string]$path, [string]$local)
$client = new-object system.net.WebClient
$client.Headers.Add("user-agent", "PowerShell")
$client.downloadfile($path, $local)
}
if (!(test-path $powershellpath))
{
New-Item -ItemType directory -Path $powershellpath
}
# .NET Framework 4.0 is necessary.
#if (($PSVersionTable.CLRVersion.Major) -lt 2)
#{
# $DownloadUrl = "http://download.microsoft.com/download/B/A/4/BA4A7E71-2906-4B2D-A0E1-80CF16844F5F/dotNetFx45_Full_x86_x64.exe"
# $FileName = $DownLoadUrl.Split('/')[-1]
# download-file $downloadurl "$powershellpath\$filename"
# ."$powershellpath\$filename" /quiet /norestart
#}
#You may need to reboot after the .NET install if so just run the script again.
# If the Operating System is above 6.2, then you already have PowerShell Version > 3
if ([Environment]::OSVersion.Version.Major -gt 6)
{
write-host "OS is new; upgrade not needed."
Exit
}
$osminor = [environment]::OSVersion.Version.Minor
if ($osminor -eq 1)
{
$DownloadUrl = "http://download.microsoft.com/download/E/7/6/E76850B8-DA6E-4FF5-8CCE-A24FC513FD16/Windows6.1-KB2506143-x64.msu"
}
elseif ($osminor -eq 0)
{
$DownloadUrl = "http://download.microsoft.com/download/E/7/6/E76850B8-DA6E-4FF5-8CCE-A24FC513FD16/Windows6.0-KB2506146-x64.msu"
}
else
{
# Nothing to do; In theory this point will never be reached.
Exit
}
$FileName = $DownLoadUrl.Split('/')[-1]
download-file $downloadurl "$powershellpath\$filename"
Start-Process -FilePath ".$powershellpath\$filename" -ArgumentList /quiet

View file

@ -123,6 +123,12 @@ def list_modules(module_dir):
if os.path.isdir(d):
files2 = glob.glob("%s/*" % d)
for f in files2:
if f.endswith(".ps1"):
# windows powershell modules have documentation stubs in python docstring
# format (they are not executed) so skip the ps1 format files
continue
tokens = f.split("/")
module = tokens[-1]
category = tokens[-2]

View file

@ -29,6 +29,7 @@ from ansible import constants as C
REPLACER = "#<<INCLUDE_ANSIBLE_MODULE_COMMON>>"
REPLACER_ARGS = "\"<<INCLUDE_ANSIBLE_MODULE_ARGS>>\""
REPLACER_COMPLEX = "\"<<INCLUDE_ANSIBLE_MODULE_COMPLEX_ARGS>>\""
REPLACER_WINDOWS = "# POWERSHELL_COMMON"
class ModuleReplacer(object):
@ -46,14 +47,17 @@ class ModuleReplacer(object):
from ansible.module_utils.basic import *
will result in a template evaluation of
{{ include 'basic.py' }}
... will result in the insertion basic.py into the module
from the module_utils/ directory in the source tree.
All modules are required to import at least basic, though there will also
be other snippets.
# POWERSHELL_COMMON
Also results in the inclusion of the common code in powershell.ps1
"""
# ******************************************************************************
@ -97,6 +101,10 @@ class ModuleReplacer(object):
if REPLACER in line:
output.write(self.slurp(os.path.join(self.snippet_path, "basic.py")))
snippet_names.append('basic')
if REPLACER_WINDOWS in line:
ps_data = self.slurp(os.path.join(self.snippet_path, "powershell.ps1"))
output.write(ps_data)
snippet_names.append('powershell')
elif line.startswith('from ansible.module_utils.'):
tokens=line.split(".")
import_error = False
@ -116,8 +124,14 @@ class ModuleReplacer(object):
output.write(line)
output.write("\n")
if len(snippet_names) > 0 and not 'basic' in snippet_names:
raise errors.AnsibleError("missing required import in %s: from ansible.module_utils.basic import *" % module_path)
if not module_path.endswith(".ps1"):
# Unixy modules
if len(snippet_names) > 0 and not 'basic' in snippet_names:
raise errors.AnsibleError("missing required import in %s: from ansible.module_utils.basic import *" % module_path)
else:
# Windows modules
if len(snippet_names) > 0 and not 'powershell' in snippet_names:
raise errors.AnsibleError("missing required import in %s: # POWERSHELL_COMMON" % module_path)
return (output.getvalue(), module_style)

View file

@ -0,0 +1,138 @@
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2014, and others
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Helper function to parse Ansible JSON arguments from a file passed as
# the single argument to the module
# Example: $params = Parse-Args $args
Function Parse-Args($arguments)
{
$parameters = New-Object psobject;
If ($arguments.Length -gt 0)
{
$parameters = Get-Content $arguments[0] | ConvertFrom-Json;
}
$parameters;
}
# Helper function to set an "attribute" on a psobject instance in powershell.
# This is a convenience to make adding Members to the object easier and
# slightly more pythonic
# Example: Set-Attr $result "changed" $true
Function Set-Attr($obj, $name, $value)
{
# If the provided $obj is undefined, define one to be nice
If (-not $obj.GetType)
{
$obj = New-Object psobject
}
$obj | Add-Member -Force -MemberType NoteProperty -Name $name -Value $value
}
# Helper function to get an "attribute" from a psobject instance in powershell.
# This is a convenience to make getting Members from an object easier and
# slightly more pythonic
# Example: $attr = Get-Attr $response "code" -default "1"
Function Get-Attr($obj, $name, $default = $null)
{
# Check if the provided Member $name exists in $obj and return it or the
# default
If ($obj.$name.GetType)
{
$obj.$name
}
Else
{
$default
}
return
}
# Helper function to convert a powershell object to JSON to echo it, exiting
# the script
# Example: Exit-Json $result
Function Exit-Json($obj)
{
# If the provided $obj is undefined, define one to be nice
If (-not $obj.GetType)
{
$obj = New-Object psobject
}
echo $obj | ConvertTo-Json
Exit
}
# Helper function to add the "msg" property and "failed" property, convert the
# powershell object to JSON and echo it, exiting the script
# Example: Fail-Json $result "This is the failure message"
Function Fail-Json($obj, $message = $null)
{
# If we weren't given 2 args, and the only arg was a string, create a new
# psobject and use the arg as the failure message
If ($message -eq $null -and $obj.GetType().Name -eq "String")
{
$message = $obj
$obj = New-Object psobject
}
# If the first args is undefined or not an object, make it an object
ElseIf (-not $obj.GetType -or $obj.GetType().Name -ne "PSCustomObject")
{
$obj = New-Object psobject
}
Set-Attr $obj "msg" $message
Set-Attr $obj "failed" $true
echo $obj | ConvertTo-Json
Exit 1
}
# Helper filter/pipeline function to convert a value to boolean following current
# Ansible practices
# Example: $is_true = "true" | ConvertTo-Bool
Function ConvertTo-Bool
{
param(
[parameter(valuefrompipeline=$true)]
$obj
)
$boolean_strings = "yes", "on", "1", "true", 1
$obj_string = [string]$obj
if (($obj.GetType().Name -eq "Boolean" -and $obj) -or $boolean_strings -contains $obj_string.ToLower())
{
$true
}
Else
{
$false
}
return
}

View file

@ -167,7 +167,7 @@ class Runner(object):
self.module_vars = utils.default(module_vars, lambda: {})
self.default_vars = utils.default(default_vars, lambda: {})
self.always_run = None
self.connector = connection.Connection(self)
self.connector = connection.Connector(self)
self.conditional = conditional
self.module_name = module_name
self.forks = int(forks)
@ -275,7 +275,7 @@ class Runner(object):
afo.flush()
afo.close()
remote = os.path.join(tmp, name)
remote = conn.shell.join_path(tmp, name)
try:
conn.put_file(afile, remote)
finally:
@ -284,32 +284,17 @@ class Runner(object):
# *****************************************************
def _compute_environment_string(self, inject=None):
def _compute_environment_string(self, conn, inject=None):
''' what environment variables to use when running the command? '''
shell_type = inject.get('ansible_shell_type')
if not shell_type:
shell_type = os.path.basename(C.DEFAULT_EXECUTABLE)
default_environment = dict(
LANG = C.DEFAULT_MODULE_LANG,
LC_CTYPE = C.DEFAULT_MODULE_LANG,
)
enviro = {}
if self.environment:
enviro = template.template(self.basedir, self.environment, inject, convert_bare=True)
enviro = utils.safe_eval(enviro)
if type(enviro) != dict:
raise errors.AnsibleError("environment must be a dictionary, received %s" % enviro)
default_environment.update(enviro)
result = ""
for (k,v) in default_environment.iteritems():
if shell_type in ('csh', 'fish'):
result = "env %s=%s %s" % (k, pipes.quote(unicode(v)), result)
else:
result = "%s=%s %s" % (k, pipes.quote(unicode(v)), result)
return result
return conn.shell.env_prefix(**enviro)
# *****************************************************
@ -425,7 +410,7 @@ class Runner(object):
if self._late_needs_tmp_path(conn, tmp, module_style):
tmp = self._make_tmp_path(conn)
remote_module_path = os.path.join(tmp, module_name)
remote_module_path = conn.shell.join_path(tmp, module_name)
if (module_style != 'new'
or async_jid is not None
@ -435,12 +420,11 @@ class Runner(object):
or self.su):
self._transfer_str(conn, tmp, module_name, module_data)
environment_string = self._compute_environment_string(inject)
environment_string = self._compute_environment_string(conn, inject)
if "tmp" in tmp and ((self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root')):
# deal with possible umask issues once sudo'ed to other user
cmd_chmod = "chmod a+r %s" % remote_module_path
self._low_level_exec_command(conn, cmd_chmod, tmp, sudoable=False)
self._remote_chmod(conn, 'a+r', remote_module_path)
cmd = ""
in_data = None
@ -468,8 +452,7 @@ class Runner(object):
if (self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root'):
# deal with possible umask issues once sudo'ed to other user
cmd_args_chmod = "chmod a+r %s" % argsfile
self._low_level_exec_command(conn, cmd_args_chmod, tmp, sudoable=False)
self._remote_chmod(conn, 'a+r', argsfile)
if async_jid is None:
cmd = "%s %s" % (remote_module_path, argsfile)
@ -487,14 +470,14 @@ class Runner(object):
if not shebang:
raise errors.AnsibleError("module is missing interpreter line")
cmd = " ".join([environment_string.strip(), shebang.replace("#!","").strip(), cmd])
cmd = cmd.strip()
rm_tmp = None
if "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp:
if not self.sudo or self.su or self.sudo_user == 'root' or self.su_user == 'root':
# not sudoing or sudoing to root, so can cleanup files in the same step
cmd = cmd + "; rm -rf %s >/dev/null 2>&1" % tmp
rm_tmp = tmp
cmd = conn.shell.build_module_command(environment_string, shebang, cmd, rm_tmp)
cmd = cmd.strip()
sudoable = True
if module_name == "accelerate":
@ -511,7 +494,7 @@ class Runner(object):
if (self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root'):
# not sudoing to root, so maybe can't delete files as that other user
# have to clean up temp files as original user in a second step
cmd2 = "rm -rf %s >/dev/null 2>&1" % tmp
cmd2 = conn.shell.remove(tmp, recurse=True)
self._low_level_exec_command(conn, cmd2, tmp, sudoable=False)
data = utils.parse_json(res['stdout'])
@ -776,8 +759,7 @@ class Runner(object):
if not self.accelerate_port:
self.accelerate_port = C.ACCELERATE_PORT
if actual_transport in [ 'paramiko', 'ssh', 'accelerate' ]:
actual_port = inject.get('ansible_ssh_port', port)
actual_port = inject.get('ansible_ssh_port', port)
# the delegated host may have different SSH port configured, etc
# and we need to transfer those, and only those, variables
@ -818,6 +800,18 @@ class Runner(object):
if delegate_to or host != actual_host:
conn.delegate = host
default_shell = getattr(conn, 'default_shell', '')
shell_type = inject.get('ansible_shell_type')
if not shell_type:
if default_shell:
shell_type = default_shell
else:
shell_type = os.path.basename(C.DEFAULT_EXECUTABLE)
shell_plugin = utils.plugins.shell_loader.get(shell_type)
if shell_plugin is None:
shell_plugin = utils.plugins.shell_loader.get('sh')
conn.shell = shell_plugin
except errors.AnsibleConnectionFailed, e:
result = dict(failed=True, msg="FAILED: %s" % str(e))
@ -947,6 +941,10 @@ class Runner(object):
executable=None, su=False, in_data=None):
''' execute a command string over SSH, return the output '''
if not cmd:
# this can happen with powershell modules when there is no analog to a Windows command (like chmod)
return dict(stdout='', stderr='')
if executable is None:
executable = C.DEFAULT_EXECUTABLE
@ -954,16 +952,11 @@ class Runner(object):
su_user = self.su_user
# compare connection user to (su|sudo)_user and disable if the same
if hasattr(conn, 'user'):
if (not su and conn.user == sudo_user) or (su and conn.user == su_user):
sudoable = False
su = False
else:
# assume connection type is local if no user attribute
this_user = getpass.getuser()
if (not su and this_user == sudo_user) or (su and this_user == su_user):
sudoable = False
su = False
# assume connection type is local if no user attribute
this_user = getattr(conn, 'user', getpass.getuser())
if (not su and this_user == sudo_user) or (su and this_user == su_user):
sudoable = False
su = False
if su:
rc, stdin, stdout, stderr = conn.exec_command(cmd,
@ -997,26 +990,16 @@ class Runner(object):
# *****************************************************
def _remote_chmod(self, conn, mode, path, tmp, sudoable=False, su=False):
''' issue a remote chmod command '''
cmd = conn.shell.chmod(mode, path)
return self._low_level_exec_command(conn, cmd, tmp, sudoable=sudoable, su=su)
# *****************************************************
def _remote_md5(self, conn, tmp, path):
''' takes a remote md5sum without requiring python, and returns 1 if no file '''
path = pipes.quote(path)
# The following test needs to be SH-compliant. BASH-isms will
# not work if /bin/sh points to a non-BASH shell.
test = "rc=0; [ -r \"%s\" ] || rc=2; [ -f \"%s\" ] || rc=1; [ -d \"%s\" ] && echo 3 && exit 0" % ((path,) * 3)
md5s = [
"(/usr/bin/md5sum %s 2>/dev/null)" % path, # Linux
"(/sbin/md5sum -q %s 2>/dev/null)" % path, # ?
"(/usr/bin/digest -a md5 %s 2>/dev/null)" % path, # Solaris 10+
"(/sbin/md5 -q %s 2>/dev/null)" % path, # Freebsd
"(/usr/bin/md5 -n %s 2>/dev/null)" % path, # Netbsd
"(/bin/md5 -q %s 2>/dev/null)" % path, # Openbsd
"(/usr/bin/csum -h MD5 %s 2>/dev/null)" % path, # AIX
"(/bin/csum -h MD5 %s 2>/dev/null)" % path # AIX also
]
cmd = " || ".join(md5s)
cmd = "%s; %s || (echo \"${rc} %s\")" % (test, cmd, path)
cmd = conn.shell.md5(path)
data = self._low_level_exec_command(conn, cmd, tmp, sudoable=True)
data2 = utils.last_non_blank_line(data['stdout'])
try:
@ -1039,17 +1022,16 @@ class Runner(object):
def _make_tmp_path(self, conn):
''' make and return a temporary path on a remote box '''
basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48))
basetmp = os.path.join(C.DEFAULT_REMOTE_TMP, basefile)
if (self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root') and basetmp.startswith('$HOME'):
basetmp = os.path.join('/tmp', basefile)
use_system_tmp = False
if (self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root'):
use_system_tmp = True
cmd = 'mkdir -p %s' % basetmp
tmp_mode = None
if self.remote_user != 'root' or ((self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root')):
cmd += ' && chmod a+rx %s' % basetmp
cmd += ' && echo %s' % basetmp
tmp_mode = 'a+rx'
cmd = conn.shell.mkdtemp(basefile, use_system_tmp, tmp_mode)
result = self._low_level_exec_command(conn, cmd, None, sudoable=False)
# error handling on this seems a little aggressive?
@ -1067,7 +1049,7 @@ class Runner(object):
output = output + ": %s" % result['stdout']
raise errors.AnsibleError(output)
rc = utils.last_non_blank_line(result['stdout']).strip() + '/'
rc = conn.shell.join_path(utils.last_non_blank_line(result['stdout']).strip(), '')
# Catch failure conditions, files should never be
# written to locations in /.
if rc == '/':
@ -1078,9 +1060,8 @@ class Runner(object):
def _remove_tmp_path(self, conn, tmp_path):
''' Remove a tmp_path. '''
if "-tmp-" in tmp_path:
cmd = "rm -rf %s >/dev/null 2>&1" % tmp_path
cmd = conn.shell.remove(tmp_path, recurse=True)
self._low_level_exec_command(conn, cmd, None, sudoable=False)
# If we have gotten here we have a working ssh configuration.
# If ssh breaks we could leave tmp directories out on the remote system.
@ -1094,7 +1075,7 @@ class Runner(object):
module_shebang,
module_data
) = self._configure_module(conn, module_name, module_args, inject, complex_args)
module_remote_path = os.path.join(tmp, module_name)
module_remote_path = conn.shell.join_path(tmp, module_name)
self._transfer_str(conn, tmp, module_name, module_data)
@ -1106,7 +1087,8 @@ class Runner(object):
''' find module and configure it '''
# Search module path(s) for named module.
module_path = utils.plugins.module_finder.find_plugin(module_name)
module_suffixes = getattr(conn, 'default_suffixes', None)
module_path = utils.plugins.module_finder.find_plugin(module_name, module_suffixes)
if module_path is None:
raise errors.AnsibleFileNotFound("module %s not found in %s" % (module_name, utils.plugins.module_finder.print_paths()))

View file

@ -119,7 +119,7 @@ class ActionModule(object):
# fix file permissions when the copy is done as a different user
if self.runner.sudo and self.runner.sudo_user != 'root':
self.runner._low_level_exec_command(conn, "chmod a+r %s" % xfered, tmp)
self.runner._remote_chmod(conn, 'a+r', xfered, tmp)
# run the copy module
module_args = "%s src=%s dest=%s original_basename=%s" % (module_args, pipes.quote(xfered), pipes.quote(dest), pipes.quote(os.path.basename(src)))

View file

@ -37,7 +37,7 @@ class ActionModule(object):
tmp = self.runner._make_tmp_path(conn)
(module_path, is_new_style, shebang) = self.runner._copy_module(conn, tmp, module_name, module_args, inject, complex_args=complex_args)
self.runner._low_level_exec_command(conn, "chmod a+rx %s" % module_path, tmp)
self.runner._remote_chmod(conn, 'a+rx', module_path, tmp)
return self.runner._execute_module(conn, tmp, 'async_wrapper', module_args,
async_module=module_path,

View file

@ -136,8 +136,8 @@ class ActionModule(object):
# If it's recursive copy, destination is always a dir,
# explicitly mark it so (note - copy module relies on this).
if not dest.endswith("/"):
dest += "/"
if not conn.shell.path_has_trailing_slash(dest):
dest = conn.shell.join_path(dest, '')
else:
source_files.append((source, os.path.basename(source)))
@ -169,10 +169,10 @@ class ActionModule(object):
# This is kind of optimization - if user told us destination is
# dir, do path manipulation right away, otherwise we still check
# for dest being a dir via remote call below.
if dest.endswith("/"):
dest_file = os.path.join(dest, source_rel)
if conn.shell.path_has_trailing_slash(dest):
dest_file = conn.shell.join_path(dest, source_rel)
else:
dest_file = dest
dest_file = conn.shell.join_path(dest)
# Attempt to get the remote MD5 Hash.
remote_md5 = self.runner._remote_md5(conn, tmp_path, dest_file)
@ -186,7 +186,7 @@ class ActionModule(object):
return ReturnData(conn=conn, result=result)
else:
# Append the relative source location to the destination and retry remote_md5.
dest_file = os.path.join(dest, source_rel)
dest_file = conn.shell.join_path(dest, source_rel)
remote_md5 = self.runner._remote_md5(conn, tmp_path, dest_file)
if remote_md5 != '1' and not force:
@ -228,7 +228,7 @@ class ActionModule(object):
# fix file permissions when the copy is done as a different user
if self.runner.sudo and self.runner.sudo_user != 'root' and not raw:
self.runner._low_level_exec_command(conn, "chmod a+r %s" % tmp_src, tmp_path)
self.runner._remote_chmod(conn, 'a+r', tmp_src, tmp_path)
if raw:
# Continue to next iteration if raw is defined.

View file

@ -57,19 +57,24 @@ class ActionModule(object):
return ReturnData(conn=conn, result=results)
source = os.path.expanduser(source)
source = conn.shell.join_path(source)
if os.path.sep not in conn.shell.join_path('a', ''):
source_local = source.replace('\\', '/')
else:
source_local = source
if flat:
if dest.endswith("/"):
# if the path ends with "/", we'll use the source filename as the
# destination filename
base = os.path.basename(source)
base = os.path.basename(source_local)
dest = os.path.join(dest, base)
if not dest.startswith("/"):
# if dest does not start with "/", we'll assume a relative path
dest = utils.path_dwim(self.runner.basedir, dest)
else:
# files are saved in dest dir, with a subdir for each host, then the filename
dest = "%s/%s/%s" % (utils.path_dwim(self.runner.basedir, dest), conn.host, source)
dest = "%s/%s/%s" % (utils.path_dwim(self.runner.basedir, dest), conn.host, source_local)
dest = os.path.expanduser(dest.replace("//","/"))

View file

@ -106,7 +106,7 @@ class ActionModule(object):
# transfer the file to a remote tmp location
source = source.replace('\x00', '') # why does this happen here?
args = args.replace('\x00', '') # why does this happen here?
tmp_src = os.path.join(tmp, os.path.basename(source))
tmp_src = conn.shell.join_path(tmp, os.path.basename(source))
tmp_src = tmp_src.replace('\x00', '')
conn.put_file(source, tmp_src)
@ -115,22 +115,22 @@ class ActionModule(object):
# set file permissions, more permisive when the copy is done as a different user
if ((self.runner.sudo and self.runner.sudo_user != 'root') or
(self.runner.su and self.runner.su_user != 'root')):
cmd_args_chmod = "chmod a+rx %s" % tmp_src
chmod_mode = 'a+rx'
sudoable = False
else:
cmd_args_chmod = "chmod +rx %s" % tmp_src
self.runner._low_level_exec_command(conn, cmd_args_chmod, tmp, sudoable=sudoable, su=self.runner.su)
chmod_mode = '+rx'
self.runner._remote_chmod(conn, chmod_mode, tmp_src, tmp, sudoable=sudoable, su=self.runner.su)
# add preparation steps to one ssh roundtrip executing the script
env_string = self.runner._compute_environment_string(inject)
module_args = env_string + tmp_src + ' ' + args
env_string = self.runner._compute_environment_string(conn, inject)
module_args = ' '.join([env_string, tmp_src, args])
handler = utils.plugins.action_loader.get('raw', self.runner)
result = handler.run(conn, tmp, 'raw', module_args, inject)
# clean up after
if "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES:
self.runner._low_level_exec_command(conn, 'rm -rf %s >/dev/null 2>&1' % tmp, tmp)
self.runner._remove_tmp_path(conn, tmp)
result.result['changed'] = True

View file

@ -79,7 +79,7 @@ class ActionModule(object):
source = utils.path_dwim(self.runner.basedir, source)
if dest.endswith("/"):
if dest.endswith("/"): # CCTODO: Fix path for Windows hosts.
base = os.path.basename(source)
dest = os.path.join(dest, base)
@ -114,7 +114,7 @@ class ActionModule(object):
# fix file permissions when the copy is done as a different user
if self.runner.sudo and self.runner.sudo_user != 'root':
self.runner._low_level_exec_command(conn, "chmod a+r %s" % xfered, tmp)
self.runner._remote_chmod(conn, 'a+r', xfered, tmp)
# run the copy module
module_args = "%s src=%s dest=%s original_basename=%s" % (module_args, pipes.quote(xfered), pipes.quote(dest), pipes.quote(os.path.basename(source)))

View file

@ -54,7 +54,7 @@ class ActionModule(object):
result = dict(failed=True, msg="src (or content) and dest are required")
return ReturnData(conn=conn, result=result)
dest = os.path.expanduser(dest)
dest = os.path.expanduser(dest) # CCTODO: Fix path for Windows hosts.
source = template.template(self.runner.basedir, os.path.expanduser(source), inject)
if copy:
if '_original_file' in inject:
@ -77,7 +77,7 @@ class ActionModule(object):
# fix file permissions when the copy is done as a different user
if copy:
if self.runner.sudo and self.runner.sudo_user != 'root':
self.runner._low_level_exec_command(conn, "chmod a+r %s" % tmp_src, tmp)
self.runner._remote_chmod(conn, 'a+r', tmp_src, tmp)
module_args = "%s src=%s original_basename=%s" % (module_args, pipes.quote(tmp_src), pipes.quote(os.path.basename(source)))
else:
module_args = "%s original_basename=%s" % (module_args, pipes.quote(os.path.basename(source)))

View file

@ -20,23 +20,16 @@
from ansible import utils
from ansible.errors import AnsibleError
import ansible.constants as C
import os
import os.path
class Connection(object):
class Connector(object):
''' Handles abstract connections to remote hosts '''
def __init__(self, runner):
self.runner = runner
def connect(self, host, port, user, password, transport, private_key_file):
conn = None
conn = utils.plugins.connection_loader.get(transport, self.runner, host, port, user=user, password=password, private_key_file=private_key_file)
if conn is None:
raise AnsibleError("unsupported connection type: %s" % transport)
self.active = conn.connect()
return self.active

View file

@ -0,0 +1,256 @@
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
#
# This file is part of Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import
import base64
import hashlib
import imp
import os
import re
import shlex
import traceback
import urlparse
from ansible import errors
from ansible import utils
from ansible.callbacks import vvv, vvvv, verbose
from ansible.runner.shell_plugins import powershell
try:
from winrm import Response
from winrm.exceptions import WinRMTransportError
from winrm.protocol import Protocol
except ImportError:
raise errors.AnsibleError("winrm is not installed")
_winrm_cache = {
# 'user:pwhash@host:port': <protocol instance>
}
def vvvvv(msg, host=None):
verbose(msg, host=host, caplevel=4)
class Connection(object):
'''WinRM connections over HTTP/HTTPS.'''
def __init__(self, runner, host, port, user, password, *args, **kwargs):
self.runner = runner
self.host = host
self.port = port
self.user = user
self.password = password
self.has_pipelining = False
self.default_shell = 'powershell'
self.default_suffixes = ['.ps1', '']
self.protocol = None
self.shell_id = None
self.delegate = None
def _winrm_connect(self):
'''
Establish a WinRM connection over HTTP/HTTPS.
'''
port = self.port or 5986
vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % \
(self.user, port, self.host), host=self.host)
netloc = '%s:%d' % (self.host, port)
cache_key = '%s:%s@%s:%d' % (self.user, hashlib.md5(self.password).hexdigest(), self.host, port)
if cache_key in _winrm_cache:
vvvv('WINRM REUSE EXISTING CONNECTION: %s' % cache_key, host=self.host)
return _winrm_cache[cache_key]
transport_schemes = [('plaintext', 'https'), ('plaintext', 'http')] # FIXME: ssl/kerberos
if port == 5985:
transport_schemes = reversed(transport_schemes)
exc = None
for transport, scheme in transport_schemes:
endpoint = urlparse.urlunsplit((scheme, netloc, '/wsman', '', ''))
vvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint),
host=self.host)
protocol = Protocol(endpoint, transport=transport,
username=self.user, password=self.password)
try:
protocol.send_message('')
_winrm_cache[cache_key] = protocol
return protocol
except WinRMTransportError, exc:
err_msg = str(exc.args[0])
if re.search(r'Operation\s+?timed\s+?out', err_msg, re.I):
raise
m = re.search(r'Code\s+?(\d{3})', err_msg)
if m:
code = int(m.groups()[0])
if code == 411:
_winrm_cache[cache_key] = protocol
return protocol
vvvv('WINRM CONNECTION ERROR: %s' % err_msg, host=self.host)
continue
if exc:
raise exc
def _winrm_exec(self, command, args=(), from_exec=False):
if from_exec:
vvvv("WINRM EXEC %r %r" % (command, args), host=self.host)
else:
vvvvv("WINRM EXEC %r %r" % (command, args), host=self.host)
if not self.protocol:
self.protocol = self._winrm_connect()
if not self.shell_id:
self.shell_id = self.protocol.open_shell()
command_id = None
try:
command_id = self.protocol.run_command(self.shell_id, command, args)
response = Response(self.protocol.get_command_output(self.shell_id, command_id))
if from_exec:
vvvv('WINRM RESULT %r' % response, host=self.host)
else:
vvvvv('WINRM RESULT %r' % response, host=self.host)
vvvvv('WINRM STDOUT %s' % response.std_out, host=self.host)
vvvvv('WINRM STDERR %s' % response.std_err, host=self.host)
return response
finally:
if command_id:
self.protocol.cleanup_command(self.shell_id, command_id)
def connect(self):
if not self.protocol:
self.protocol = self._winrm_connect()
return self
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable=None, in_data=None, su=None, su_user=None):
cmd = cmd.encode('utf-8')
cmd_parts = shlex.split(cmd, posix=False)
if '-EncodedCommand' in cmd_parts:
encoded_cmd = cmd_parts[cmd_parts.index('-EncodedCommand') + 1]
decoded_cmd = base64.b64decode(encoded_cmd)
vvv("EXEC %s" % decoded_cmd, host=self.host)
else:
vvv("EXEC %s" % cmd, host=self.host)
# For script/raw support.
if cmd_parts and cmd_parts[0].lower().endswith('.ps1'):
script = powershell._build_file_cmd(cmd_parts)
cmd_parts = powershell._encode_script(script, as_list=True)
try:
result = self._winrm_exec(cmd_parts[0], cmd_parts[1:], from_exec=True)
except Exception, e:
traceback.print_exc()
raise errors.AnsibleError("failed to exec cmd %s" % cmd)
return (result.status_code, '', result.std_out.encode('utf-8'), result.std_err.encode('utf-8'))
def put_file(self, in_path, out_path):
vvv("PUT %s TO %s" % (in_path, out_path), host=self.host)
if not os.path.exists(in_path):
raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path)
with open(in_path) as in_file:
in_size = os.path.getsize(in_path)
script_template = '''
$s = [System.IO.File]::OpenWrite("%s");
[void]$s.Seek(%d, [System.IO.SeekOrigin]::Begin);
$b = [System.Convert]::FromBase64String("%s");
[void]$s.Write($b, 0, $b.length);
[void]$s.SetLength(%d);
[void]$s.Close();
'''
# Determine max size of data we can pass per command.
script = script_template % (powershell._escape(out_path), in_size, '', in_size)
cmd = powershell._encode_script(script)
# Encode script with no data, subtract its length from 8190 (max
# windows command length), divide by 2.67 (UTF16LE base64 command
# encoding), then by 1.35 again (data base64 encoding).
buffer_size = int(((8190 - len(cmd)) / 2.67) / 1.35)
for offset in xrange(0, in_size, buffer_size):
try:
out_data = in_file.read(buffer_size)
if offset == 0:
if out_data.lower().startswith('#!powershell') and not out_path.lower().endswith('.ps1'):
out_path = out_path + '.ps1'
b64_data = base64.b64encode(out_data)
script = script_template % (powershell._escape(out_path), offset, b64_data, in_size)
vvvv("WINRM PUT %s to %s (offset=%d size=%d)" % (in_path, out_path, offset, len(out_data)), host=self.host)
cmd_parts = powershell._encode_script(script, as_list=True)
result = self._winrm_exec(cmd_parts[0], cmd_parts[1:])
if result.status_code != 0:
raise IOError(result.std_err.encode('utf-8'))
except Exception:
traceback.print_exc()
raise errors.AnsibleError("failed to transfer file to %s" % out_path)
def fetch_file(self, in_path, out_path):
out_path = out_path.replace('\\', '/')
vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host)
buffer_size = 2**20 # 1MB chunks
if not os.path.exists(os.path.dirname(out_path)):
os.makedirs(os.path.dirname(out_path))
out_file = None
try:
offset = 0
while True:
try:
script = '''
If (Test-Path -PathType Leaf "%(path)s")
{
$stream = [System.IO.File]::OpenRead("%(path)s");
$stream.Seek(%(offset)d, [System.IO.SeekOrigin]::Begin) | Out-Null;
$buffer = New-Object Byte[] %(buffer_size)d;
$bytesRead = $stream.Read($buffer, 0, %(buffer_size)d);
$bytes = $buffer[0..($bytesRead-1)];
[System.Convert]::ToBase64String($bytes);
$stream.Close() | Out-Null;
}
ElseIf (Test-Path -PathType Container "%(path)s")
{
Write-Host "[DIR]";
}
Else
{
Write-Error "%(path)s does not exist";
Exit 1;
}
''' % dict(buffer_size=buffer_size, path=powershell._escape(in_path), offset=offset)
vvvv("WINRM FETCH %s to %s (offset=%d)" % (in_path, out_path, offset), host=self.host)
cmd_parts = powershell._encode_script(script, as_list=True)
result = self._winrm_exec(cmd_parts[0], cmd_parts[1:])
if result.status_code != 0:
raise IOError(result.std_err.encode('utf-8'))
if result.std_out.strip() == '[DIR]':
data = None
else:
data = base64.b64decode(result.std_out.strip())
if data is None:
if not os.path.exists(out_path):
os.makedirs(out_path)
break
else:
if not out_file:
# If out_path is a directory and we're expecting a file, bail out now.
if os.path.isdir(out_path):
break
out_file = open(out_path, 'wb')
out_file.write(data)
if len(data) < buffer_size:
break
offset += len(data)
except Exception:
traceback.print_exc()
raise errors.AnsibleError("failed to transfer file to %s" % out_path)
finally:
if out_file:
out_file.close()
def close(self):
if self.protocol and self.shell_id:
self.protocol.close_shell(self.shell_id)
self.shell_id = None

View file

@ -0,0 +1,23 @@
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
#
# This file is part of Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from ansible.runner.shell_plugins.sh import ShellModule as ShModule
class ShellModule(ShModule):
def env_prefix(self, **kwargs):
return 'env %s' % super(ShellModule, self).env_prefix(**kwargs)

View file

@ -0,0 +1,23 @@
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
#
# This file is part of Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from ansible.runner.shell_plugins.sh import ShellModule as ShModule
class ShellModule(ShModule):
def env_prefix(self, **kwargs):
return 'env %s' % super(ShellModule, self).env_prefix(**kwargs)

View file

@ -0,0 +1,113 @@
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
#
# This file is part of Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import base64
import os
import re
import random
import shlex
import time
_common_args = ['PowerShell', '-NoProfile', '-NonInteractive']
# Primarily for testing, allow explicitly specifying PowerShell version via
# an environment variable.
_powershell_version = os.environ.get('POWERSHELL_VERSION', None)
if _powershell_version:
_common_args = ['PowerShell', '-Version', _powershell_version] + _common_args[1:]
def _escape(value, include_vars=False):
'''Return value escaped for use in PowerShell command.'''
# http://www.techotopia.com/index.php/Windows_PowerShell_1.0_String_Quoting_and_Escape_Sequences
# http://stackoverflow.com/questions/764360/a-list-of-string-replacements-in-python
subs = [('\n', '`n'), ('\r', '`r'), ('\t', '`t'), ('\a', '`a'),
('\b', '`b'), ('\f', '`f'), ('\v', '`v'), ('"', '`"'),
('\'', '`\''), ('`', '``'), ('\x00', '`0')]
if include_vars:
subs.append(('$', '`$'))
pattern = '|'.join('(%s)' % re.escape(p) for p, s in subs)
substs = [s for p, s in subs]
replace = lambda m: substs[m.lastindex - 1]
return re.sub(pattern, replace, value)
def _encode_script(script, as_list=False):
'''Convert a PowerShell script to a single base64-encoded command.'''
script = '\n'.join([x.strip() for x in script.splitlines() if x.strip()])
encoded_script = base64.b64encode(script.encode('utf-16-le'))
cmd_parts = _common_args + ['-EncodedCommand', encoded_script]
if as_list:
return cmd_parts
return ' '.join(cmd_parts)
def _build_file_cmd(cmd_parts):
'''Build command line to run a file, given list of file name plus args.'''
return ' '.join(_common_args + ['-ExecutionPolicy', 'Unrestricted', '-File'] + ['"%s"' % x for x in cmd_parts])
class ShellModule(object):
def env_prefix(self, **kwargs):
return ''
def join_path(self, *args):
return os.path.join(*args).replace('/', '\\')
def path_has_trailing_slash(self, path):
# Allow Windows paths to be specified using either slash.
return path.endswith('/') or path.endswith('\\')
def chmod(self, mode, path):
return ''
def remove(self, path, recurse=False):
path = _escape(path)
if recurse:
return _encode_script('''Remove-Item "%s" -Force -Recurse;''' % path)
else:
return _encode_script('''Remove-Item "%s" -Force;''' % path)
def mkdtemp(self, basefile, system=False, mode=None):
basefile = _escape(basefile)
# FIXME: Support system temp path!
return _encode_script('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName | Write-Host -Separator '';''' % basefile)
def md5(self, path):
path = _escape(path)
script = '''
If (Test-Path -PathType Leaf "%(path)s")
{
(Get-FileHash -Path "%(path)s" -Algorithm MD5).Hash.ToLower();
}
ElseIf (Test-Path -PathType Container "%(path)s")
{
Write-Host "3";
}
Else
{
Write-Host "1";
}
''' % dict(path=path)
return _encode_script(script)
def build_module_command(self, env_string, shebang, cmd, rm_tmp=None):
cmd_parts = shlex.split(cmd, posix=False)
if not cmd_parts[0].lower().endswith('.ps1'):
cmd_parts[0] = '%s.ps1' % cmd_parts[0]
script = _build_file_cmd(cmd_parts)
if rm_tmp:
rm_tmp = _escape(rm_tmp)
script = '%s; Remove-Item "%s" -Force -Recurse;' % (script, rm_tmp)
return _encode_script(script)

View file

@ -0,0 +1,87 @@
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
#
# This file is part of Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import os
import pipes
import ansible.constants as C
class ShellModule(object):
def env_prefix(self, **kwargs):
'''Build command prefix with environment variables.'''
env = dict(
LANG = C.DEFAULT_MODULE_LANG,
LC_CTYPE = C.DEFAULT_MODULE_LANG,
)
env.update(kwargs)
return ' '.join(['%s=%s' % (k, pipes.quote(unicode(v))) for k,v in env.items()])
def join_path(self, *args):
return os.path.join(*args)
def path_has_trailing_slash(self, path):
return path.endswith('/')
def chmod(self, mode, path):
path = pipes.quote(path)
return 'chmod %s %s' % (mode, path)
def remove(self, path, recurse=False):
path = pipes.quote(path)
if recurse:
return "rm -rf %s >/dev/null 2>&1" % path
else:
return "rm -f %s >/dev/null 2>&1" % path
def mkdtemp(self, basefile=None, system=False, mode=None):
if not basefile:
basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48))
basetmp = self.join_path(C.DEFAULT_REMOTE_TMP, basefile)
if system and basetmp.startswith('$HOME'):
basetmp = self.join_path('/tmp', basefile)
cmd = 'mkdir -p %s' % basetmp
if mode:
cmd += ' && chmod %s %s' % (mode, basetmp)
cmd += ' && echo %s' % basetmp
return cmd
def md5(self, path):
path = pipes.quote(path)
# The following test needs to be SH-compliant. BASH-isms will
# not work if /bin/sh points to a non-BASH shell.
test = "rc=0; [ -r \"%s\" ] || rc=2; [ -f \"%s\" ] || rc=1; [ -d \"%s\" ] && echo 3 && exit 0" % ((path,) * 3)
md5s = [
"(/usr/bin/md5sum %s 2>/dev/null)" % path, # Linux
"(/sbin/md5sum -q %s 2>/dev/null)" % path, # ?
"(/usr/bin/digest -a md5 %s 2>/dev/null)" % path, # Solaris 10+
"(/sbin/md5 -q %s 2>/dev/null)" % path, # Freebsd
"(/usr/bin/md5 -n %s 2>/dev/null)" % path, # Netbsd
"(/bin/md5 -q %s 2>/dev/null)" % path, # Openbsd
"(/usr/bin/csum -h MD5 %s 2>/dev/null)" % path, # AIX
"(/bin/csum -h MD5 %s 2>/dev/null)" % path # AIX also
]
cmd = " || ".join(md5s)
cmd = "%s; %s || (echo \"${rc} %s\")" % (test, cmd, path)
return cmd
def build_module_command(self, env_string, shebang, cmd, rm_tmp=None):
cmd_parts = [env_string.strip(), shebang.replace("#!", "").strip(), cmd]
new_cmd = " ".join(cmd_parts)
if rm_tmp:
new_cmd = '%s; rm -rf %s >/dev/null 2>&1' % (new_cmd, rm_tmp)
return new_cmd

View file

@ -608,9 +608,9 @@ def md5s(data):
return digest.hexdigest()
def md5(filename):
''' Return MD5 hex digest of local file, or None if file is not present. '''
''' Return MD5 hex digest of local file, None if file is not present or a directory. '''
if not os.path.exists(filename):
if not os.path.exists(filename) or os.path.isdir(filename):
return None
digest = _md5()
blocksize = 64 * 1024

View file

@ -139,21 +139,25 @@ class PluginLoader(object):
if directory not in self._extra_dirs:
self._extra_dirs.append(directory)
def find_plugin(self, name):
def find_plugin(self, name, suffixes=None):
''' Find a plugin named name '''
if name in self._plugin_path_cache:
return self._plugin_path_cache[name]
if not suffixes:
if self.class_name:
suffixes = ['.py']
else:
suffixes = ['', '.ps1']
suffix = ".py"
if not self.class_name:
suffix = ""
for suffix in suffixes:
full_name = '%s%s' % (name, suffix)
if full_name in self._plugin_path_cache:
return self._plugin_path_cache[full_name]
for i in self._get_paths():
path = os.path.join(i, "%s%s" % (name, suffix))
if os.path.isfile(path):
self._plugin_path_cache[name] = path
return path
for i in self._get_paths():
path = os.path.join(i, full_name)
if os.path.isfile(path):
self._plugin_path_cache[full_name] = path
return path
return None
@ -212,6 +216,13 @@ connection_loader = PluginLoader(
aliases={'paramiko': 'paramiko_ssh'}
)
shell_loader = PluginLoader(
'ShellModule',
'ansible.runner.shell_plugins',
'shell_plugins',
'shell_plugins',
)
module_finder = PluginLoader(
'',
'',

View file

@ -54,6 +54,9 @@ notes:
install I(facter) and I(ohai) means you can avoid Ruby-dependencies on your
remote systems. (See also M(facter) and M(ohai).)
- The filter option filters only the first level subkey below ansible_facts.
- If the target host is Windows, you will not currently have the ability to use
C(fact_path) or C(filter) as this is provided by a simpler implementation of the module.
Different facts are returned for Windows hosts.
author: Michael DeHaan
'''

45
library/windows/setup.ps1 Normal file
View file

@ -0,0 +1,45 @@
#!powershell
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
# $params is not currently used in this module
# $params = Parse-Args $args;
$result = New-Object psobject @{
ansible_facts = New-Object psobject
changed = $false
};
$osversion = [Environment]::OSVersion
$memory = Get-WmiObject win32_Pysicalmemory
$netcfg = Get-WmiObject win32_NetworkAdapterConfiguration
Set-Attr $result.ansible_facts "ansible_hostname" $env:COMPUTERNAME;
Set-Attr $result.ansible_facts "ansible_fqdn" "$([System.Net.Dns]::GetHostByName((hostname)).HostName)"
Set-Attr $result.ansible_facts "ansible_system" $osversion.Platform.ToString()
Set-Attr $result.ansible_facts "ansible_os_family" "Windows"
Set-Attr $result.ansible_facts "ansible_distribution" $osversion.VersionString
Set-Attr $result.ansible_facts "ansible_distribution_version" $osversion.Version.ToString()
Set-Attr $result.ansible_facts "ansible_totalmem" $memory.Capacity.ToString()
$ips = @()
Foreach ($ip in $netcfg.IPAddress) { If ($ip) { $ips += $ip } }
Set-Attr $result.ansible_facts "ansible_ip_addresses" $ips
Exit-Json $result;

46
library/windows/slurp.ps1 Normal file
View file

@ -0,0 +1,46 @@
#!powershell
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
$params = Parse-Args $args;
$src = Get-Attr $params "src" (Get-Attr $params "path" $FALSE);
If (-not $src)
{
Fail-Json (New-Object psobject) "missing required argument: src";
}
If (Test-Path -PathType Leaf $src)
{
$bytes = [System.IO.File]::ReadAllBytes($src);
$content = [System.Convert]::ToBase64String($bytes);
$result = New-Object psobject @{
changed = $false
encoding = "base64"
content = $content
};
Exit-Json $result;
}
ElseIf (Test-Path -PathType Container $src)
{
Fail-Json (New-Object psobject) ("is a directory: " + $src);
}
Else
{
Fail-Json (New-Object psobject) ("file not found: " + $src);
}

View file

@ -0,0 +1,77 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2014, Paul Durivage <paul.durivage@rackspace.com>, and others
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
DOCUMENTATION = '''
---
module: win_feature
version_added: "1.7"
short_description: Fetches a file from a given URL
description:
- Fetches a file from a URL and saves to locally
options:
name:
description:
- Names of roles or features to install as a single feature or a comma-separated list of features
required: true
default: null
aliases: []
state:
description:
- State of the features or roles on the system
required: false
choices:
- present
- absent
default: present
aliases: []
restart:
description:
- Restarts the computer automatically when installation is complete, if restarting is required by the roles or features installed.
choices:
- yes
- no
default: null
aliases: []
author: Paul Durivage
'''
EXAMPLES = '''
# This installs IIS.
# The names of features available for install can be run by running the following Powershell Command:
# PS C:\Users\Administrator> Import-Module ServerManager; Get-WindowsFeature
$ ansible -i hosts -m win_feature -a "name=Web-Server" all
$ ansible -i hosts -m win_feature -a "name=Web-Server,Web-Common-Http" all
# Playbook example
---
- name: Install IIS
hosts: all
gather_facts: false
tasks:
- name: Install IIS
win_feature:
name: "Web-Server"
state: absent
restart: yes
'''

View file

@ -0,0 +1,100 @@
#!powershell
# This file is part of Ansible.
#
# Copyright 2014, Paul Durivage <paul.durivage@rackspace.com>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
Import-Module Servermanager;
$params = Parse-Args $args;
$result = New-Object psobject @{
changed = $false
}
If ($params.name) {
$name = $params.name
}
Else {
Fail-Json $result "mising required argument: name"
}
If ($params.state) {
$state = $params.state.ToString().ToLower()
If (($state -ne 'present') -and ($state -ne 'absent')) {
Fail-Json $result "state is '$state'; must be 'present' or 'absent'"
}
}
Elseif (!$params.state) {
$state = "present"
}
If ($params.restart) {
$restart = $params.restart | ConvertTo-Bool
}
If ($state -eq "present") {
try {
if ($restart) {
$featureresult = Add-WindowsFeature -Name $name -Restart
}
else {
$featureresult = Add-WindowsFeature -Name $name
}
}
catch {
Fail-Json $result $_.Exception.Message
}
}
Elseif ($state -eq "absent") {
try {
if ($restart) {
$featureresult = Remove-WindowsFeature -Name $name -Restart
}
else {
$featureresult = Remove-WindowsFeature -Name $name
}
}
catch {
Fail-Json $result $_.Exception.Message
}
}
# Loop through results and create a hash containing details about
# each role/feature that is installed/removed
$installed_features = @()
ForEach ($item in $featureresult.FeatureResult) {
$installed_features += New-Object psobject @{
id = $item.id.ToString()
display_name = $item.DisplayName
message = $item.Message.ToString()
restart_needed = $item.RestartNeeded.ToString()
skip_reason = $item.SkipReason.ToString()
success = $item.Success.ToString()
}
}
Set-Attr $result "feature_result" $installed_features
Set-Attr $result "feature_success" $featureresult.Success.ToString()
Set-Attr $result "feature_exitcode" $featureresult.ExitCode.ToString()
Set-Attr $result "feature_restart_needed" $featureresult.RestartNeeded.ToString()
If ($result.feature_result.Length -gt 0) {
$result.changed = $true
}
Exit-Json $result;

View file

@ -0,0 +1,57 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2014, Paul Durivage <paul.durivage@rackspace.com>, and others
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
DOCUMENTATION = '''
---
module: win_get_url
version_added: "1.7"
short_description: Fetches a file from a given URL
description:
- Fetches a file from a URL and saves to locally
options:
url:
description:
- The full URL of a file to download
required: true
default: null
aliases: []
dest:
description:
- The absolute path of the location to save the file at the URL. Be sure to include a filename and extension as appropriate.
required: false
default: yes
aliases: []
author: Paul Durivage
'''
EXAMPLES = '''
# Downloading a JPEG and saving it to a file with the ansible command.
# Note the "dest" is quoted rather instead of escaping the backslashes
$ ansible -i hosts -c winrm -m win_get_url -a "url=http://www.example.com/earthrise.jpg dest='C:\Users\Administrator\earthrise.jpg'" all
# Playbook example
- name: Download earthrise.jpg to 'C:\Users\RandomUser\earthrise.jpg'
win_get_url:
url: 'http://www.example.com/earthrise.jpg'
dest: 'C:\Users\RandomUser\earthrise.jpg'
'''

View file

@ -0,0 +1,56 @@
#!powershell
# This file is part of Ansible.
#
# Copyright 2014, Paul Durivage <paul.durivage@rackspace.com>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
$params = Parse-Args $args;
$result = New-Object psobject @{
win_get_url = New-Object psobject
changed = $false
}
If ($params.url) {
$url = $params.url
}
Else {
Fail-Json $result "mising required argument: url"
}
If ($params.dest) {
$dest = $params.dest
}
Else {
Fail-Json $result "missing required argument: dest"
}
$client = New-Object System.Net.WebClient
Try {
$client.DownloadFile($url, $dest)
$result.changed = $true
}
Catch {
Fail-Json $result "Error downloading $url to $dest"
}
Set-Attr $result.win_get_url "url" $url
Set-Attr $result.win_get_url "dest" $dest
Exit-Json $result;

58
library/windows/win_msi Normal file
View file

@ -0,0 +1,58 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2014, Matt Martz <matt@sivel.net>, and others
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
DOCUMENTATION = '''
---
module: win_msi
version_added: "1.7"
short_description: Installs and uninstalls Windows MSI files
description:
- Installs or uninstalls a Windows MSI file that is already located on the
target server
options:
path:
description:
- File system path to the MSI file to install
required: true
state:
description:
- Whether the MSI file should be installed or uninstalled
choices:
- present
- absent
default: present
creates:
description:
- Path to a file created by installing the MSI to prevent from
attempting to reinstall the package on every run
author: Matt Martz
'''
EXAMPLES = '''
# Install an MSI file
- win_msi: path=C:\\\\7z920-x64.msi
# Uninstall an MSI file
- win_msi: path=C:\\\\7z920-x64.msi state=absent
'''

View file

@ -0,0 +1,63 @@
#!powershell
# (c) 2014, Matt Martz <matt@sivel.net>, and others
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
$params = Parse-Args $args;
$result = New-Object psobject;
Set-Attr $result "changed" $false;
If (-not $params.path.GetType)
{
Fail-Json $result "missing required arguments: path"
}
$extra_args = ""
If ($params.extra_args.GetType)
{
$extra_args = $params.extra_args;
}
If ($params.creates.GetType -and $params.state.GetType -and $params.state -ne "absent")
{
If (Test-File $creates)
{
Exit-Json $result;
}
}
$logfile = [IO.Path]::GetTempFileName();
if ($params.state.GetType -and $params.state -eq "absent")
{
msiexec.exe /x $params.path /qb /l $logfile $extra_args;
}
Else
{
msiexec.exe /i $params.path /qb /l $logfile $extra_args;
}
Set-Attr $result "changed" $true;
$logcontents = Get-Content $logfile;
Remove-Item $logfile;
Set-Attr $result "log" $logcontents;
Exit-Json $result;

48
library/windows/win_ping Normal file
View file

@ -0,0 +1,48 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>, and others
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
DOCUMENTATION = '''
---
module: win_ping
version_added: "1.7"
short_description: A windows version of the classic ping module.
description:
- Checks management connectivity of a windows host
options:
data:
description:
- Alternate data to return instead of 'pong'
required: false
default: 'pong'
aliases: []
author: Chris Church
'''
EXAMPLES = '''
# Test connectivity to a windows host
ansible winserver -m win_ping
# Example from an Ansible Playbook
- action: win_ping
'''

View file

@ -0,0 +1,29 @@
#!powershell
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
$params = Parse-Args $args;
$data = Get-Attr $params "data" "pong";
$result = New-Object psobject @{
changed = $false
ping = $data
};
Exit-Json $result;

52
library/windows/win_stat Normal file
View file

@ -0,0 +1,52 @@
#!/usr/bin/python
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# this is a windows documentation stub, actual code lives in the .ps1
# file of the same name
DOCUMENTATION = '''
---
module: win_stat
version_added: "1.7"
short_description: returns information about a Windows file
description:
- Returns information about a Windows file
options:
path:
description:
- The full path of the file/object to get the facts of; both forward and
back slashes are accepted.
required: true
default: null
aliases: []
get_md5:
description:
- Whether to return the md5 sum of the file
required: false
default: yes
aliases: []
author: Chris Church
'''
EXAMPLES = '''
# Obtain information about a file
- win_stat: path=C:\\foo.ini
register: file_info
- debug: var=file_info
'''

View file

@ -0,0 +1,60 @@
#!powershell
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
$params = Parse-Args $args;
$path = Get-Attr $params "path" $FALSE;
If ($path -eq $FALSE)
{
Fail-Json (New-Object psobject) "missing required argument: path";
}
$get_md5 = Get-Attr $params "get_md5" $TRUE | ConvertTo-Bool;
$result = New-Object psobject @{
stat = New-Object psobject
changed = $false
};
If (Test-Path $path)
{
Set-Attr $result.stat "exists" $TRUE;
$info = Get-Item $path;
If ($info.Directory) # Only files have the .Directory attribute.
{
Set-Attr $result.stat "isdir" $FALSE;
Set-Attr $result.stat "size" $info.Length;
}
Else
{
Set-Attr $result.stat "isdir" $TRUE;
}
}
Else
{
Set-Attr $result.stat "exists" $FALSE;
}
If ($get_md5 -and $result.stat.exists -and -not $result.stat.isdir)
{
$path_md5 = (Get-FileHash -Path $path -Algorithm MD5).Hash.ToLower();
Set-Attr $result.stat "md5" $path_md5;
}
Exit-Json $result;

71
library/windows/win_user Normal file
View file

@ -0,0 +1,71 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2014, Matt Martz <matt@sivel.net>, and others
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
DOCUMENTATION = '''
---
module: win_user
version_added: "1.7"
short_description: Manages local Windows user accounts
description:
- Manages local Windows user accounts
options:
name:
description:
- Username of the user to manage
required: true
default: null
aliases: []
password:
description:
- Password for the user (plain text)
required: true
default: null
aliases: []
state:
description:
- Whether to create or delete a user
required: false
choices:
- present
- absent
default: present
aliases: []
author: Paul Durivage
'''
EXAMPLES = '''
# Ad-hoc example
$ ansible -i hosts -m win_user -a "name=bob password=Password12345" all
$ ansible -i hosts -m win_user -a "name=bob password=Password12345 state=absent" all
# Playbook example
---
- name: Add a user
hosts: all
gather_facts: false
tasks:
- name: Add User
win_user:
name: ansible
password: "@ns1bl3"
'''

View file

@ -0,0 +1,116 @@
#!powershell
# This file is part of Ansible
#
# Copyright 2014, Paul Durivage <paul.durivage@rackspace.com>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
########
$adsi = [ADSI]"WinNT://$env:COMPUTERNAME"
function Get-User($user) {
$adsi.Children | where {$_.SchemaClassName -eq 'user' -and $_.Name -eq $user }
return
}
function Create-User([string]$user, [string]$passwd) {
$adsiuser = $adsi.Create("User", $user)
$adsiuser.SetPassword($passwd)
$adsiuser.SetInfo()
$adsiuser
return
}
function Update-Password($user, [string]$passwd) {
$user.SetPassword($passwd)
$user.SetInfo()
}
function Delete-User($user) {
$adsi.delete("user", $user.Name.Value)
}
########
$params = Parse-Args $args;
$result = New-Object psobject @{
changed = $false
};
If (-not $params.name.GetType)
{
Fail-Json $result "missing required arguments: name"
}
If ($params.state) {
$state = $params.state.ToString().ToLower()
If (($state -ne 'present') -and ($state -ne 'absent')) {
Fail-Json $result "state is '$state'; must be 'present' or 'absent'"
}
}
Elseif (!$params.state) {
$state = "present"
}
If ((-not $params.password.GetType) -and ($state -eq 'present'))
{
Fail-Json $result "missing required arguments: password"
}
$username = Get-Attr $params "name"
$password = Get-Attr $params "password"
$user_obj = Get-User $username
if ($state -eq 'present') {
# Add or update user
try {
if ($user_obj.GetType) {
Update-Password $user_obj $password
}
else {
Create-User $username $password
}
$result.changed = $true
$user_obj = Get-User $username
}
catch {
Fail-Json $result $_.Exception.Message
}
}
else {
# Remove user
try {
if ($user_obj.GetType) {
Delete-User $user_obj
$result.changed = $true
}
else {
Set-Attr $result "msg" "User '$username' was not found"
}
}
catch {
Fail-Json $result $_.Exception.Message
}
}
# Set-Attr $result "user" $user_obj
Set-Attr $result "user_name" $user_obj.Name
Set-Attr $result "user_fullname" $user_obj.FullName
Set-Attr $result "user_path" $user_obj.Path
Exit-Json $result;

View file

@ -46,6 +46,9 @@ test_vault:
ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE) --syntax-check
ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE)
test_winrm:
ansible-playbook test_winrm.yml -i inventory.winrm -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
cloud: amazon rackspace
cloud_cleanup: amazon_cleanup rackspace_cleanup

View file

@ -70,3 +70,23 @@ resources. Running these tests may result in additional fees associated with
your cloud account. Care is taken to ensure that created resources are
removed. However, it is advisable to inspect your AWS console to ensure no
unexpected resources are running.
Windows Tests
=============
These tests exercise the winrm connection plugin and Windows modules. You'll
need to define an inventory with a remote Windows 2008 or 2012 Server to use
for testing, and enable PowerShell Remoting to continue.
Running these tests may result in changes to your Windows host, so don't run
them against a production/critical Windows environment.
Enable PowerShell Remoting (run on the Windows host via Remote Desktop):
Enable-PSRemoting -Force
Define Windows inventory:
cp inventory.winrm.template inventory.winrm
${EDITOR:-vi} inventory.winrm
Run the tests:
make test_winrm

View file

@ -0,0 +1,7 @@
[windows]
server ansible_ssh_host=10.10.10.10 ansible_ssh_user=Administrator ansible_ssh_pass=ShhhDontTellAnyone
[windows:vars]
ansible_connection=winrm
# HTTPS uses 5986, HTTP uses 5985
ansible_ssh_port=5985

View file

@ -0,0 +1,168 @@
# test code for the fetch module when using winrm connection
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: clean out the test directory
local_action: file name={{ output_dir|mandatory }} state=absent
tags: me
- name: create the test directory
local_action: file name={{ output_dir }} state=directory
tags: me
- name: fetch a small file
fetch: src="C:/Windows/win.ini" dest={{ output_dir }}
register: fetch_small
- name: check fetch small result
assert:
that:
- "fetch_small.changed"
- name: check file created by fetch small
local_action: stat path={{ fetch_small.dest }}
register: fetch_small_stat
- name: verify fetched small file exists locally
assert:
that:
- "fetch_small_stat.stat.exists"
- "fetch_small_stat.stat.isreg"
- "fetch_small_stat.stat.md5 == fetch_small.md5sum"
- name: fetch the same small file
fetch: src="C:/Windows/win.ini" dest={{ output_dir }}
register: fetch_small_again
- name: check fetch small result again
assert:
that:
- "not fetch_small_again.changed"
- name: fetch a small file to flat namespace
fetch: src="C:/Windows/win.ini" dest="{{ output_dir }}/" flat=yes
register: fetch_flat
- name: check fetch flat result
assert:
that:
- "fetch_flat.changed"
- name: check file created by fetch flat
local_action: stat path="{{ output_dir }}/win.ini"
register: fetch_flat_stat
- name: verify fetched file exists locally in output_dir
assert:
that:
- "fetch_flat_stat.stat.exists"
- "fetch_flat_stat.stat.isreg"
- "fetch_flat_stat.stat.md5 == fetch_flat.md5sum"
- name: fetch a small file to flat directory (without trailing slash)
fetch: src="C:/Windows/win.ini" dest="{{ output_dir }}" flat=yes
register: fetch_flat_dir
ignore_errors: true
- name: check fetch flat to directory result
assert:
that:
- "fetch_flat_dir|failed"
- "fetch_flat_dir.msg"
- name: fetch a large binary file
fetch: src="C:/Windows/explorer.exe" dest={{ output_dir }}
register: fetch_large
- name: check fetch large binary file result
assert:
that:
- "fetch_large.changed"
- name: check file created by fetch large binary
local_action: stat path={{ fetch_large.dest }}
register: fetch_large_stat
- name: verify fetched large file exists locally
assert:
that:
- "fetch_large_stat.stat.exists"
- "fetch_large_stat.stat.isreg"
- "fetch_large_stat.stat.md5 == fetch_large.md5sum"
- name: fetch a large binary file again
fetch: src="C:/Windows/explorer.exe" dest={{ output_dir }}
register: fetch_large_again
- name: check fetch large binary file result again
assert:
that:
- "not fetch_large_again.changed"
- name: fetch a small file using backslashes in src path
fetch: src="C:\Windows\system.ini" dest={{ output_dir }}
register: fetch_small_bs
- name: check fetch small result with backslashes
assert:
that:
- "fetch_small_bs.changed"
- name: check file created by fetch small with backslashes
local_action: stat path={{ fetch_small_bs.dest }}
register: fetch_small_bs_stat
- name: verify fetched small file with backslashes exists locally
assert:
that:
- "fetch_small_bs_stat.stat.exists"
- "fetch_small_bs_stat.stat.isreg"
- "fetch_small_bs_stat.stat.md5 == fetch_small_bs.md5sum"
- name: attempt to fetch a non-existent file - do not fail on missing
fetch: src="C:/this_file_should_not_exist.txt" dest={{ output_dir }}
register: fetch_missing_nofail
- name: check fetch missing no fail result
assert:
that:
- "not fetch_missing_nofail|failed"
- "fetch_missing_nofail.msg"
- "not fetch_missing_nofail|changed"
- name: attempt to fetch a non-existent file - fail on missing
fetch: src="C:/this_file_should_not_exist.txt" dest={{ output_dir }} fail_on_missing=yes
register: fetch_missing
ignore_errors: true
- name: check fetch missing with failure
assert:
that:
- "fetch_missing|failed"
- "fetch_missing.msg"
- "not fetch_missing|changed"
- name: attempt to fetch a directory
fetch: src="C:\Windows" dest={{ output_dir }}
register: fetch_dir
ignore_errors: true
- name: check fetch directory result
assert:
that:
- "fetch_dir|failed"
- "fetch_dir.msg"

View file

@ -0,0 +1,35 @@
# test code for the win_get_url module
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: remove test file if it exists
raw: PowerShell -Command {Remove-Item "C:\Users\Administrator\win_get_url.jpg" -Force}
- name: test win_get_url module
win_get_url: url=http://placehold.it/10x10.jpg dest='C:\Users\Administrator\win_get_url.jpg'
register: win_get_url_result
- name: check win_get_url result
assert:
that:
- "not win_get_url_result|failed"
- "win_get_url_result|changed"
# FIXME:
# - Test invalid url
# - Test invalid dest, when dest is directory
# - Test idempotence when downloading same url/dest (not yet implemented)

View file

@ -0,0 +1,41 @@
# test code for the win_msi module
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: use win_get_url module to download msi
win_get_url: url=http://downloads.sourceforge.net/project/sevenzip/7-Zip/9.22/7z922-x64.msi dest='C:\7z922-x64.msi'
register: win_get_url_result
- name: install 7zip msi
win_msi: path="{{ win_get_url_result.win_get_url.dest }}"
register: win_msi_install_result
- name: check win_msi install result
assert:
that:
- "not win_msi_install_result|failed"
- "win_msi_install_result|changed"
- name: uninstall 7zip msi
win_msi: path="{{ win_get_url_result.win_get_url.dest }}" state=absent
register: win_msi_uninstall_result
- name: check win_msi uninstall result
assert:
that:
- "not win_msi_uninstall_result|failed"
- "win_msi_uninstall_result|changed"

View file

@ -0,0 +1,72 @@
# test code for the win_ping module
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: test win_ping
action: win_ping
register: win_ping_result
- name: check win_ping result
assert:
that:
- "not win_ping_result|failed"
- "not win_ping_result|changed"
- "win_ping_result.ping == 'pong'"
- name: test win_ping with data
win_ping: data=blah
register: win_ping_with_data_result
- name: check win_ping result with data
assert:
that:
- "not win_ping_with_data_result|failed"
- "not win_ping_with_data_result|changed"
- "win_ping_with_data_result.ping == 'blah'"
#- name: test local ping (should use default ping)
# local_action: ping
# register: local_ping_result
#- name: check local ping result
# assert:
# that:
# - "not local_ping_result|failed"
# - "not local_ping_result|changed"
# - "local_ping_result.ping == 'pong'"
- name: test win_ping.ps1 with data
win_ping.ps1: data=bleep
register: win_ping_ps1_result
- name: check win_ping.ps1 result with data
assert:
that:
- "not win_ping_ps1_result|failed"
- "not win_ping_ps1_result|changed"
- "win_ping_ps1_result.ping == 'bleep'"
#- name: test win_ping with invalid args
# win_ping: arg=invalid
# register: win_ping_ps1_invalid_args_result
#- name: check that win_ping.ps1 with invalid args fails
# assert:
# that:
# - "win_ping_ps1_invalid_args_result|failed"
# - "win_ping_ps1_invalid_args_result.msg"

View file

@ -0,0 +1,72 @@
# test code for the raw module when using winrm connection
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: run getmac
raw: getmac
register: getmac_result
- name: assert that getmac ran
assert:
that:
- "getmac_result.rc == 0"
- "getmac_result.stdout"
- "not getmac_result.stderr"
- "not getmac_result|failed"
- "not getmac_result|changed"
- name: run ipconfig with /all argument
raw: ipconfig /all
register: ipconfig_result
- name: assert that ipconfig ran with /all argument
assert:
that:
- "ipconfig_result.rc == 0"
- "ipconfig_result.stdout"
- "'Physical Address' in ipconfig_result.stdout"
- "not ipconfig_result.stderr"
- "not ipconfig_result|failed"
- "not ipconfig_result|changed"
- name: run ipconfig with invalid argument
raw: ipconfig /badswitch
register: ipconfig_invalid_result
ignore_errors: true
- name: assert that ipconfig with invalid argument failed
assert:
that:
- "ipconfig_invalid_result.rc != 0"
- "ipconfig_invalid_result.stdout" # ipconfig displays errors on stdout.
- "not ipconfig_invalid_result.stderr"
- "ipconfig_invalid_result|failed"
- "not ipconfig_invalid_result|changed"
- name: run an unknown command
raw: uname -a
register: unknown_result
ignore_errors: true
- name: assert that an unknown command failed
assert:
that:
- "unknown_result.rc != 0"
- "not unknown_result.stdout"
- "unknown_result.stderr" # An unknown command displays error on stderr.
- "unknown_result|failed"
- "not unknown_result|changed"

View file

@ -0,0 +1,2 @@
@ECHO OFF
ECHO We can even run a batch file!

View file

@ -0,0 +1,2 @@
# Test script to make sure the Ansible script module works.
Write-Host "Woohoo! We can run a PowerShell script via Ansible!"

View file

@ -0,0 +1,7 @@
# Test script to make sure the Ansible script module works when arguments are
# passed to the script.
foreach ($i in $args)
{
Write-Host $i;
}

View file

@ -0,0 +1,9 @@
# Test script to make sure we handle non-zero exit codes.
trap
{
Write-Error -ErrorRecord $_
exit 1;
}
throw "Oh noes I has an error"

View file

@ -0,0 +1,75 @@
# test code for the script module when using winrm connection
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: run simple test script
script: test_script.ps1
register: test_script_result
- name: check that script ran
assert:
that:
- "test_script_result.rc == 0"
- "test_script_result.stdout"
- "'Woohoo' in test_script_result.stdout"
- "not test_script_result.stderr"
- "not test_script_result|failed"
- "test_script_result|changed"
- name: run test script that takes arguments
script: test_script_with_args.ps1 /this /that /other
register: test_script_with_args_result
- name: check that script ran and received arguments
assert:
that:
- "test_script_with_args_result.rc == 0"
- "test_script_with_args_result.stdout"
- "test_script_with_args_result.stdout_lines[0] == '/this'"
- "test_script_with_args_result.stdout_lines[1] == '/that'"
- "test_script_with_args_result.stdout_lines[2] == '/other'"
- "not test_script_with_args_result.stderr"
- "not test_script_with_args_result|failed"
- "test_script_with_args_result|changed"
- name: run test script that has errors
script: test_script_with_errors.ps1
register: test_script_with_errors_result
ignore_errors: true
- name: check that script ran but failed with errors
assert:
that:
- "test_script_with_errors_result.rc != 0"
- "not test_script_with_errors_result.stdout"
- "test_script_with_errors_result.stderr"
- "test_script_with_errors_result|failed"
- "test_script_with_errors_result|changed"
- name: run simple batch file
script: test_script.bat
register: test_batch_result
- name: check that batch file ran
assert:
that:
- "test_batch_result.rc == 0"
- "test_batch_result.stdout"
- "'batch' in test_batch_result.stdout"
- "not test_batch_result.stderr"
- "not test_batch_result|failed"
- "test_batch_result|changed"

View file

@ -0,0 +1,35 @@
# test code for the setup module when using winrm connection
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: test setup module
action: setup
register: setup_result
- name: check setup result
assert:
that:
- "not setup_result|failed"
- "not setup_result|changed"
- "setup_result.ansible_facts"
- "setup_result.ansible_facts.ansible_os_family == 'Windows'"
- "setup_result.ansible_facts.ansible_distribution"
- "setup_result.ansible_facts.ansible_distribution_version"
- "setup_result.ansible_facts.ansible_fqdn"
- "setup_result.ansible_facts.ansible_hostname"
- "setup_result.ansible_facts.ansible_ip_addresses"
- "setup_result.ansible_facts.ansible_system"

View file

@ -0,0 +1,77 @@
# test code for the slurp module when using winrm connection
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: test slurping an existing file
slurp: src="C:/Windows/win.ini"
register: slurp_existing
- name: check slurp existing result
assert:
that:
- "slurp_existing.content"
- "slurp_existing.encoding == 'base64'"
- "not slurp_existing|changed"
- "not slurp_existing|failed"
- name: test slurping a large binary file with path param and backslashes
slurp: path="C:\Windows\explorer.exe"
register: slurp_path_backslashes
- name: check slurp result with path param and backslashes
assert:
that:
- "slurp_path_backslashes.content"
- "slurp_path_backslashes.encoding == 'base64'"
- "not slurp_path_backslashes|changed"
- "not slurp_path_backslashes|failed"
- name: test slurping a non-existent file
slurp: src="C:/this_file_should_not_exist.txt"
register: slurp_missing
ignore_errors: true
- name: check slurp missing result
assert:
that:
- "slurp_missing|failed"
- "slurp_missing.msg"
- "not slurp_missing|changed"
- name: test slurping a directory
slurp: src="C:/Windows"
register: slurp_dir
ignore_errors: true
- name: check slurp directory result
assert:
that:
- "slurp_dir|failed"
- "slurp_dir.msg"
- "not slurp_dir|changed"
- name: test slurp with missing argument
action: slurp
register: slurp_no_args
ignore_errors: true
- name: check slurp with missing argument result
assert:
that:
- "slurp_no_args|failed"
- "slurp_no_args.msg"
- "not slurp_no_args|changed"

View file

@ -0,0 +1,80 @@
# test code for the win_stat module
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: test win_stat module on file
win_stat: path="C:/Windows/win.ini"
register: win_stat_file
- name: check win_stat file result
assert:
that:
- "win_stat_file.stat.exists"
- "not win_stat_file.stat.isdir"
- "win_stat_file.stat.size > 0"
- "win_stat_file.stat.md5"
- "not win_stat_file|failed"
- "not win_stat_file|changed"
- name: test win_stat module on file without md5 and backslashes
win_stat: path="C:\Windows\win.ini" get_md5=no
register: win_stat_file_no_md5
- name: check win_stat file result without md
assert:
that:
- "win_stat_file_no_md5.stat.exists"
- "not win_stat_file_no_md5.stat.isdir"
- "win_stat_file_no_md5.stat.size > 0"
- "not win_stat_file_no_md5.stat.md5|default('')"
- "not win_stat_file_no_md5|failed"
- "not win_stat_file_no_md5|changed"
- name: test win_stat module on directory
win_stat: path="C:\\Windows"
register: win_stat_dir
- name: check win_stat dir result
assert:
that:
- "win_stat_dir.stat.exists"
- "win_stat_dir.stat.isdir"
- "not win_stat_dir|failed"
- "not win_stat_dir|changed"
- name: test win_stat module non-existent path
win_stat: path="C:/this_file_should_not_exist.txt"
register: win_stat_missing
- name: check win_stat missing result
assert:
that:
- "not win_stat_missing.stat.exists"
- "not win_stat_missing|failed"
- "not win_stat_missing|changed"
- name: test win_stat module without path argument
action: win_stat
register: win_stat_no_args
ignore_errors: true
- name: check win_stat result witn no path argument
assert:
that:
- "win_stat_no_args|failed"
- "win_stat_no_args.msg"
- "not win_stat_no_args|changed"

View file

@ -0,0 +1,30 @@
# test code powershell modules and winrm connection plugin
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- hosts: windows
gather_facts: false
roles:
- { role: test_win_raw, tags: test_win_raw }
- { role: test_win_script, tags: test_win_script }
- { role: test_win_ping, tags: test_win_ping }
- { role: test_win_setup, tags: test_win_setup }
- { role: test_win_slurp, tags: test_win_slurp }
- { role: test_win_fetch, tags: test_win_fetch }
- { role: test_win_stat, tags: test_win_stat }
- { role: test_win_get_url, tags: test_win_get_url }
- { role: test_win_msi, tags: test_win_msi }