Merge branch '2018.3' into 2018.3

This commit is contained in:
Gareth J. Greenaway 2019-03-05 17:42:18 -08:00 committed by GitHub
commit faa3471c05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 601 additions and 169 deletions

View File

@ -3,7 +3,7 @@
source 'https://rubygems.org'
gem 'test-kitchen', '~>1.23.3'
gem 'kitchen-salt', '~>0.4.1'
gem 'kitchen-salt', :git => 'https://github.com/s0undt3ch/kitchen-salt.git', :branch => 'features/nox'
gem 'kitchen-sync'
gem 'git'
@ -13,7 +13,8 @@ end
group :windows do
gem 'winrm', '~>2.0'
gem 'winrm-fs', '~>1.3.1'
# gem 'winrm-fs', '~>1.3.1'
gem 'winrm-fs', :git => 'https://github.com/s0undt3ch/winrm-fs.git', :branch => 'hotfix/saltstack-ci'
end
group :ec2 do

View File

@ -340,8 +340,17 @@ Building the documentation
4. A useful method of viewing the HTML documentation locally is to start
Python's built-in HTTP server:
Python 3:
.. code-block:: bash
cd /path/to/salt/doc/_build/html
python3 -m http.server
Python 2:
.. code-block:: bash
cd /path/to/salt/doc/_build/html
python -m SimpleHTTPServer

View File

@ -2,7 +2,7 @@
Salt Release Process
====================
The goal for Salt projects is to cut a new feature release every four to six
The goal for Salt projects is to cut a new feature release every six
months. This document outlines the process for these releases, and the
subsequent bug fix releases which follow.
@ -11,29 +11,66 @@ Feature Release Process
=======================
When a new release is ready to be cut, the person responsible for cutting the
release will follow the following steps (written using the 0.16 release as an
release will follow the following steps (written using the 2019.2.0 release as an
example):
#. All open issues on the release milestone should be moved to the next release
milestone. (e.g. from the ``0.16`` milestone to the ``0.17`` milestone)
#. Release notes should be created documenting the major new features and
bugfixes in the release.
#. Create an annotated tag with only the major and minor version numbers,
preceded by the letter ``v``. (e.g. ``v0.16``) This tag will reside on the
``develop`` branch.
#. Create a branch for the new release, using only the major and minor version
numbers. (e.g. ``0.16``)
#. On this new branch, create an annotated tag for the first revision release,
which is generally a release candidate. It should be preceded by the letter
``v``. (e.g. ``v0.16.0rc1``)
#. The release should be packaged from this annotated tag and uploaded to PyPI
as well as the GitHub releases page for this tag.
#. The packagers should be notified on the `salt-packagers`_ mailing list so
they can create packages for all the major operating systems. (note that
release candidates should go in the testing repositories)
#. After the packagers have been given a few days to compile the packages, the
release is announced on the `salt-users`_ mailing list.
#. Log into RTD and add the new release there. (Have to do it manually)
#. Create first public draft of release notes with major features.
#. Remove any deprecations for the upcoming release.
#. Notify salt-users and salt-announce google groups when the feature freeze
branch creation will occur so they can try to get their work merged.
#. Create QA test plan. Review features planned for the release and determine if
there is sufficient test coverage.
#. Ensure all required features are merged.
#. Complete one last merge forward from the previous branch.
#. Create feature release branch with the name of the release. (ex. fluorine)
#. Create issue to start the process of deprecating for the next feature release.
#. Create jenkins jobs to test the new feature release branch.
#. Inform salt-users and salt-announce google groups feature branch and
freeze is complete.
#. Add new feature branch to salt-jenkins repo and the kitchen yaml file.
#. Fix tests failing in jenkins test runs.
#. Finalize QA test plan and add all required tests.
#. Run through a manual test run based off of the head of the feature branch.
#. Convert the feature release branch to the version number. For example (v2019.2)
This is based off of the year and month that is planned to release.
#. Migrate both the jenkins test jobs and salt-jenkins repo to the new branch number.
#. Notify salt-users and salt-announce google groups of the new version branch
number and migrate any PRs to the new branch.
#. Delete old feature release branch name (ex. fluorine)
#. Update all name references to version number in the docs. For example
all fluorine references in the docs needs to be moved to v2019.2.0
#. Create RC release branch. (ex. 2019.2.0.rc)
#. Create new jenkins test jobs with new RC release branch
#. Notify salt-users and salt-announce google groups of the new RC branch.
#. Fix tests failing in jenkins test runs.
#. Review the release notes with major features.
#. Generate the new man pages for the release.
#. Create internal RC tag for testing.
#. Build latest windows, mac, ubuntu, debian and redhat packages.
#. Run manual and package tests against new RC packages.
#. Update release candidate docs with the new version. (ex. 2019.2.0rc1)
#. Push the internal tag live to salt's repo.
#. Publish release archive to pypi based off tag.
#. Push the RC packages live.
#. Announce new RC to salt-users and salt-announce google groups.
#. Triage incoming issues based on the new RC release.
#. Fix RC issues once they are categorized as a release blocker.
#. Depending on the issues found during the RC process make a decesion
on whether to release based off the RC or go through another RC process,
repeating the steps starting at ensuring the tests are not failing.
#. If a RC is categorized as stable, build all required packages.
#. Test all release packages.
#. Test links from `repo.saltstack.com`_.
#. Update installation instructions with new release number at `repo.saltstack.com`_.
#. Update and build docs to include new version (2019.2) as the latest.
#. Pre-announce on salt-users google group that we are about to update our repo.
#. Publish release (v2019.2.0) archive to pypi based off tag.
#. Publish all packages live to repo.
#. Publish the docs.
#. Create release at `github`_
#. Update win-repo-ng with new salt versions.
#. Announce release is live to irc, salt-users, salt-announce and release slack
community channel.
Maintenance and Bugfix Releases
@ -44,16 +81,34 @@ into a "feature freeze" state. The new release branch enters the ``merge-forward
chain and only bugfixes should be applied against the new branch. Once major bugs
have been fixed, a bugfix release can be cut:
#. On the release branch (i.e. ``0.16``), create an annotated tag for the
revision release. It should be preceded by the letter ``v``. (e.g.
``v0.16.2``) Release candidates are unnecessary for bugfix releases.
#. The release should be packaged from this annotated tag and uploaded to PyPI.
#. The packagers should be notified on the `salt-packagers`_ mailing list so
they can create packages for all the major operating systems.
#. After the packagers have been given a few days to compile the packages, the
release is announced on the `salt-users`_ mailing list.
#. Ensure all required bug fixes are merged.
#. Inform salt-users and salt-announce we are going to branch for the release.
#. Complete one last merge forward from the previous branch.
#. Create release branch with the version of the release. (ex. 2019.2.1)
#. Create jenkins jobs that test the new release branch.
#. Fix tests failing in jeknins test runs.
#. Run through a manual test run based off of the head of the branch.
#. Generate the new man pages for the release.
#. Create internal tag for testing.(ex v2019.2.1)
#. Build all release packages.
#. Run manual and package tests against new packages.
#. Update installation instructions with new release number at `repo.saltstack.com`_.
#. Update and build docs to include new version. (ex. 2019.2.1)
#. Pre-announce on salt-users google groups that we are about to update our repo.
#. Push the internal tag live to salt's repo.
#. Publish release archive to pypi based off tag.
#. Push the packages live.
#. Publish release (v2019.2.1) archive to pypi based off tag.
#. Publish all packages live to repo.
#. Publish the docs.
#. Create release at `github`_
#. Update win-repo-ng with new salt versions.
#. Announce release is live to irc, salt-users, salt-announce and release slack channel.
For more information about the difference between the ``develop`` branch and
bugfix release branches, please refer to the :ref:`Which Salt Branch?
<which-salt-branch>` section of Salt's :ref:`Contributing <contributing>`
documentation.
.. _`github`: https://github.com/saltstack/salt/releases
.. _`repo.saltstack.com`: https://repo.saltstack.com

View File

@ -68,11 +68,10 @@ related release branches.
For more information, please see the :ref:`Which Salt Branch? <which-salt-branch>`
section of Salt's :ref:`Contributing <contributing>` documentation.
Determining when a point release is going to be made is up to the project
leader (Thomas Hatch). Generally point releases are made every 2-4 weeks or
if there is a security fix they can be made sooner.
Generally point releases are made every 2 months or if there is a security fix
they can be made sooner.
The point release is only designated by tagging the commit on the release
branch with a release number using the existing convention (version 2015.8.1
is tagged with v2015.8.1). From the tag point a new source tarball is generated
and published to PyPI, and a release announcement is made.
The point release is designated by branching (ex 2019.2.1) and then tagging (v2019.2.1)
from that newly created release branch when its determined the release is stable.
From the tag point a new source tarball is generated and published to PyPI,
and a release announcement is made.

View File

@ -0,0 +1,6 @@
========================================
In Progress: Salt 2018.3.5 Release Notes
========================================
Version 2018.3.5 is an **unreleased** bugfix release for :ref:`2018.3.0 <release-2018-3-0>`.
This release is still in progress and has not been released yet.

View File

@ -23,6 +23,10 @@ import nox
REPO_ROOT = os.path.abspath(os.path.dirname(__file__))
SITECUSTOMIZE_DIR = os.path.join(REPO_ROOT, 'tests', 'support', 'coverage')
# We can't just import salt because if this is running under a frozen nox, there
# will be no salt to import
IS_WINDOWS = sys.platform.lower().startswith('win')
# Python versions to run against
_PYTHON_VERSIONS = ('2', '2.7', '3', '3.4', '3.5', '3.6')
@ -41,7 +45,9 @@ def _create_ci_directories():
def _install_requirements(session, *extra_requirements):
# Install requirements
_requirements_files = []
_requirements_files = [
os.path.join(REPO_ROOT, 'requirements', 'pytest.txt')
]
if sys.platform.startswith('linux'):
requirements_files = [
os.path.join(REPO_ROOT, 'requirements', 'tests.txt')
@ -49,7 +55,6 @@ def _install_requirements(session, *extra_requirements):
elif sys.platform.startswith('win'):
requirements_files = [
os.path.join(REPO_ROOT, 'pkg', 'windows', 'req.txt'),
os.path.join(REPO_ROOT, 'pkg', 'windows', 'req_testing.txt'),
]
elif sys.platform.startswith('darwin'):
requirements_files = [
@ -61,6 +66,10 @@ def _install_requirements(session, *extra_requirements):
if not requirements_files:
break
requirements_file = requirements_files.pop(0)
if requirements_file not in _requirements_files:
_requirements_files.append(requirements_file)
session.log('Processing {}'.format(requirements_file))
with open(requirements_file) as rfh: # pylint: disable=resource-leakage
for line in rfh:
@ -80,8 +89,14 @@ def _install_requirements(session, *extra_requirements):
if extra_requirements:
session.install(*extra_requirements)
if IS_WINDOWS:
# Windows hacks :/
nox_windows_setup = os.path.join(REPO_ROOT, 'tests', 'support', 'nox-windows-setup.py')
session.run('python', nox_windows_setup)
def _run_with_coverage(session, *test_cmd):
session.install('coverage')
session.run('coverage', 'erase')
python_path_env_var = os.environ.get('PYTHONPATH') or None
if python_path_env_var is None:

View File

@ -495,8 +495,6 @@ If Exist "%BinDir%\Lib\site-packages\salt\modules\xfs.py"^
del /Q "%BinDir%\Lib\site-packages\salt\modules\xfs.*" 1>nul
If Exist "%BinDir%\Lib\site-packages\salt\modules\yumpkg.py"^
del /Q "%BinDir%\Lib\site-packages\salt\modules\yum.*" 1>nul
If Exist "%BinDir%\Lib\site-packages\salt\modules\zabbix.py"^
del /Q "%BinDir%\Lib\site-packages\salt\modules\zabbix.*" 1>nul
If Exist "%BinDir%\Lib\site-packages\salt\modules\zfs.py"^
del /Q "%BinDir%\Lib\site-packages\salt\modules\zfs.*" 1>nul
If Exist "%BinDir%\Lib\site-packages\salt\modules\znc.py"^
@ -601,8 +599,6 @@ If Exist "%BinDir%\Lib\site-packages\salt\states\vbox_guest.py"^
del /Q "%BinDir%\Lib\site-packages\salt\states\vbox_guest.*" 1>nul
If Exist "%BinDir%\Lib\site-packages\salt\states\virt.py"^
del /Q "%BinDir%\Lib\site-packages\salt\states\virt.*" 1>nul
If Exist "%BinDir%\Lib\site-packages\salt\states\zabbix*"^
del /Q "%BinDir%\Lib\site-packages\salt\states\zabbix*" 1>nul
If Exist "%BinDir%\Lib\site-packages\salt\states\zfs.py"^
del /Q "%BinDir%\Lib\site-packages\salt\states\zfs.*" 1>nul
If Exist "%BinDir%\Lib\site-packages\salt\states\zpool.py"^

View File

@ -97,7 +97,13 @@ def beacon(config):
mount_re = '{0}$'.format(mount)
if salt.utils.platform.is_windows():
mount_re = re.sub('\\$', '\\\\', mount_re)
# mount_re comes in formatted with a $ at the end
# can be `C:\\$` or `C:\\\\$`
# re string must be like `C:\\\\` regardless of \\ or \\\\
# also, psutil returns uppercase
mount_re = re.sub(r':\\\$', r':\\\\', mount_re)
mount_re = re.sub(r':\\\\\$', r':\\\\', mount_re)
mount_re = mount_re.upper()
for part in parts:
if re.match(mount_re, part.mountpoint):

View File

@ -238,21 +238,21 @@ def beacon(config):
break
path = os.path.dirname(path)
for path in _config.get('files', {}):
excludes = _config['files'][path].get('exclude', '')
excludes = _config['files'][path].get('exclude', '')
if excludes and isinstance(excludes, list):
for exclude in excludes:
if isinstance(exclude, dict):
if exclude.values()[0].get('regex', False):
_exclude = next(iter(exclude))
if exclude[_exclude].get('regex', False):
try:
if re.search(list(exclude)[0], event.pathname):
if re.search(_exclude, event.pathname):
_append = False
except Exception:
log.warning('Failed to compile regex: %s',
list(exclude)[0])
_exclude)
else:
exclude = list(exclude)[0]
exclude = _exclude
elif '*' in exclude:
if fnmatch.fnmatch(event.pathname, exclude):
_append = False
@ -312,7 +312,7 @@ def beacon(config):
excl = []
for exclude in excludes:
if isinstance(exclude, dict):
excl.append(exclude.keys()[0])
excl.append(list(exclude)[0])
else:
excl.append(exclude)
excl = pyinotify.ExcludeFilter(excl)

View File

@ -1208,13 +1208,15 @@ def mount(name, device, mkmnt=False, fstype='', opts='defaults', user=None, util
lopts = ','.join(opts)
args = '-o {0}'.format(lopts)
# use of fstype on AIX differs from typical Linux use of -t functionality
# AIX uses -v vfsname, -t fstype mounts all with fstype in /etc/filesystems
if 'AIX' in __grains__['os']:
if fstype:
if fstype:
# use of fstype on AIX differs from typical Linux use of -t
# functionality AIX uses -v vfsname, -t fstype mounts all with
# fstype in /etc/filesystems
if 'AIX' in __grains__['os']:
args += ' -v {0}'.format(fstype)
else:
args += ' -t {0}'.format(fstype)
else:
args += ' -t {0}'.format(fstype)
cmd = 'mount {0} {1} {2} '.format(args, device, name)
out = __salt__['cmd.run_all'](cmd, runas=user, python_shell=False)
if out['retcode']:

View File

@ -1265,7 +1265,12 @@ def user_exists(user,
salt '*' mysql.user_exists 'username' password_column='authentication_string'
'''
run_verify = False
server_version = version(**connection_args)
server_version = salt.utils.data.decode(version(**connection_args))
if not server_version:
last_err = __context__['mysql.error']
err = 'MySQL Error: Unable to fetch current server version. Last error was: "{}"'.format(last_err)
log.error(err)
return False
compare_version = '10.2.0' if 'MariaDB' in server_version else '8.0.11'
dbc = _connect(**connection_args)
# Did we fail to connect with the user we are checking
@ -1404,7 +1409,12 @@ def user_create(user,
salt '*' mysql.user_create 'username' 'hostname' password_hash='hash'
salt '*' mysql.user_create 'username' 'hostname' allow_passwordless=True
'''
server_version = version(**connection_args)
server_version = salt.utils.data.decode(version(**connection_args))
if not server_version:
last_err = __context__['mysql.error']
err = 'MySQL Error: Unable to fetch current server version. Last error was: "{}"'.format(last_err)
log.error(err)
return False
compare_version = '10.2.0' if 'MariaDB' in server_version else '8.0.11'
if user_exists(user, host, **connection_args):
log.info('User \'%s\'@\'%s\' already exists', user, host)
@ -1509,7 +1519,12 @@ def user_chpass(user,
salt '*' mysql.user_chpass frank localhost password_hash='hash'
salt '*' mysql.user_chpass frank localhost allow_passwordless=True
'''
server_version = version(**connection_args)
server_version = salt.utils.data.decode(version(**connection_args))
if not server_version:
last_err = __context__['mysql.error']
err = 'MySQL Error: Unable to fetch current server version. Last error was: "{}"'.format(last_err)
log.error(err)
return False
compare_version = '10.2.0' if 'MariaDB' in server_version else '8.0.11'
args = {}
if password is not None:
@ -1865,7 +1880,12 @@ def grant_exists(grant,
'SELECT,INSERT,UPDATE,...' 'database.*' 'frank' 'localhost'
'''
server_version = version(**connection_args)
server_version = salt.utils.data.decode(version(**connection_args))
if not server_version:
last_err = __context__['mysql.error']
err = 'MySQL Error: Unable to fetch current server version. Last error was: "{}"'.format(last_err)
log.error(err)
return False
if 'ALL' in grant:
if salt.utils.versions.version_cmp(server_version, '8.0') >= 0 and \
'MariaDB' not in server_version:

View File

@ -13,11 +13,11 @@ from __future__ import absolute_import, unicode_literals, print_function
# Import Salt libs
import salt.utils.platform
import salt.utils.win_functions
import salt.utils.winapi
try:
import win32com.client
import pythoncom
import pywintypes
HAS_DEPENDENCIES = True
except ImportError:
@ -43,8 +43,8 @@ def _get_computer_object():
Returns:
object: Returns the computer object for the local machine
'''
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
with salt.utils.winapi.Com():
nt = win32com.client.Dispatch('AdsNameSpaces')
return nt.GetObject('', 'WinNT://.,computer')
@ -59,8 +59,8 @@ def _get_group_object(name):
Returns:
object: The specified group object
'''
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
with salt.utils.winapi.Com():
nt = win32com.client.Dispatch('AdsNameSpaces')
return nt.GetObject('', 'WinNT://./' + name + ',group')
@ -72,8 +72,8 @@ def _get_all_groups():
Returns:
iter: A list of objects for all groups on the machine
'''
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
with salt.utils.winapi.Com():
nt = win32com.client.Dispatch('AdsNameSpaces')
results = nt.GetObject('', 'WinNT://.')
results.Filter = ['group']
return results

View File

@ -3,7 +3,6 @@
Module for managing windows systems.
:depends:
- pythoncom
- pywintypes
- win32api
- win32con
@ -24,12 +23,12 @@ from datetime import datetime
import salt.utils.functools
import salt.utils.locales
import salt.utils.platform
import salt.utils.winapi
from salt.exceptions import CommandExecutionError
# Import 3rd-party Libs
from salt.ext import six
try:
import pythoncom
import wmi
import win32net
import win32api
@ -516,8 +515,8 @@ def get_system_info():
os_type = {1: 'Work Station',
2: 'Domain Controller',
3: 'Server'}
pythoncom.CoInitialize()
conn = wmi.WMI()
with salt.utils.winapi.Com():
conn = wmi.WMI()
system = conn.Win32_OperatingSystem()[0]
ret = {'name': get_computer_name(),
'description': system.Description,
@ -756,8 +755,8 @@ def _join_domain(domain,
if not account_exists:
join_options |= NETSETUP_ACCOUNT_CREATE
pythoncom.CoInitialize()
conn = wmi.WMI()
with salt.utils.winapi.Com():
conn = wmi.WMI()
comp = conn.Win32_ComputerSystem()[0]
# Return the results of the command as an error
@ -848,8 +847,8 @@ def unjoin_domain(username=None,
if disable:
unjoin_options |= NETSETUP_ACCT_DELETE
pythoncom.CoInitialize()
conn = wmi.WMI()
with salt.utils.winapi.Com():
conn = wmi.WMI()
comp = conn.Win32_ComputerSystem()[0]
err = comp.UnjoinDomainOrWorkgroup(Password=password,
UserName=username,
@ -892,8 +891,8 @@ def get_domain_workgroup():
salt 'minion-id' system.get_domain_workgroup
'''
pythoncom.CoInitialize()
conn = wmi.WMI()
with salt.utils.winapi.Com():
conn = wmi.WMI()
for computer in conn.Win32_ComputerSystem():
if computer.PartOfDomain:
return {'Domain': computer.Domain}

View File

@ -17,6 +17,7 @@ from datetime import datetime
# Import Salt libs
import salt.utils.platform
import salt.utils.winapi
# Import 3rd-party libraries
try:
@ -333,8 +334,8 @@ def list_tasks(location='\\'):
salt 'minion-id' task.list_tasks
'''
# Create the task service object
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# Get the folder to list tasks from
@ -366,8 +367,8 @@ def list_folders(location='\\'):
salt 'minion-id' task.list_folders
'''
# Create the task service object
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# Get the folder to list folders from
@ -401,8 +402,8 @@ def list_triggers(name, location='\\'):
salt 'minion-id' task.list_triggers <task_name>
'''
# Create the task service object
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# Get the folder to list folders from
@ -437,8 +438,8 @@ def list_actions(name, location='\\'):
salt 'minion-id' task.list_actions <task_name>
'''
# Create the task service object
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# Get the folder to list folders from
@ -499,8 +500,8 @@ def create_task(name,
return '{0} already exists'.format(name)
# connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# Create a new task definition
@ -584,8 +585,8 @@ def create_task_from_xml(name,
return 'Must specify either xml_text or xml_path'
# Create the task service object
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# Load xml from file, overrides xml_text
@ -664,8 +665,8 @@ def create_folder(name, location='\\'):
return '{0} already exists'.format(name)
# Create the task service object
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# Get the folder to list folders from
@ -879,8 +880,8 @@ def edit_task(name=None,
if name in list_tasks(location):
# Connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to create the task in
@ -1045,8 +1046,8 @@ def delete_task(name, location='\\'):
return '{0} not found in {1}'.format(name, location)
# connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to delete the task from
@ -1085,8 +1086,8 @@ def delete_folder(name, location='\\'):
return '{0} not found in {1}'.format(name, location)
# connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to delete the folder from
@ -1126,8 +1127,8 @@ def run(name, location='\\'):
return '{0} not found in {1}'.format(name, location)
# connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to delete the folder from
@ -1165,8 +1166,8 @@ def run_wait(name, location='\\'):
return '{0} not found in {1}'.format(name, location)
# connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to delete the folder from
@ -1222,8 +1223,8 @@ def stop(name, location='\\'):
return '{0} not found in {1}'.format(name, location)
# connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to delete the folder from
@ -1268,8 +1269,8 @@ def status(name, location='\\'):
return '{0} not found in {1}'.format(name, location)
# connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder where the task is defined
@ -1303,8 +1304,8 @@ def info(name, location='\\'):
return '{0} not found in {1}'.format(name, location)
# connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to delete the folder from
@ -1493,8 +1494,8 @@ def add_action(name=None,
if name in list_tasks(location):
# Connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to create the task in
@ -1600,8 +1601,8 @@ def _clear_actions(name, location='\\'):
return '{0} not found in {1}'.format(name, location)
# Create the task service object
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# Get the actions from the task
@ -1992,8 +1993,8 @@ def add_trigger(name=None,
if name in list_tasks(location):
# Connect to the task scheduler
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# get the folder to create the task in
@ -2169,8 +2170,8 @@ def clear_triggers(name, location='\\'):
return '{0} not found in {1}'.format(name, location)
# Create the task service object
pythoncom.CoInitialize()
task_service = win32com.client.Dispatch("Schedule.Service")
with salt.utils.winapi.Com():
task_service = win32com.client.Dispatch("Schedule.Service")
task_service.Connect()
# Get the triggers from the task

View File

@ -9,7 +9,6 @@ Module for managing Windows Users
<module-provider-override>`.
:depends:
- pythoncom
- pywintypes
- win32api
- win32con
@ -38,6 +37,7 @@ except Exception:
import salt.utils.args
import salt.utils.dateutils
import salt.utils.platform
import salt.utils.winapi
from salt.ext import six
from salt.ext.six import string_types
from salt.exceptions import CommandExecutionError
@ -47,7 +47,6 @@ log = logging.getLogger(__name__)
try:
import pywintypes
import wmi
import pythoncom
import win32api
import win32con
import win32net
@ -989,8 +988,8 @@ def rename(name, new_name):
# Rename the user account
# Connect to WMI
pythoncom.CoInitialize()
c = wmi.WMI(find_classes=0)
with salt.utils.winapi.Com():
c = wmi.WMI(find_classes=0)
# Get the user object
try:

View File

@ -63,12 +63,12 @@ import logging
import salt.utils.platform
import salt.utils.versions
import salt.utils.win_update
import salt.utils.winapi
from salt.exceptions import CommandExecutionError
# Import 3rd-party libs
from salt.ext import six
try:
import pythoncom
import win32com.client
HAS_PYWIN32 = True
except ImportError:
@ -1057,6 +1057,7 @@ def set_wu_settings(level=None,
# work on Windows 10 / Server 2016. It is called in throughout this function
# like this:
#
# with salt.utils.winapi.Com():
# obj_au = win32com.client.Dispatch('Microsoft.Update.AutoUpdate')
# obj_au_settings = obj_au.Settings
# obj_au_settings.Save()
@ -1077,10 +1078,10 @@ def set_wu_settings(level=None,
ret = {'Success': True}
# Initialize the PyCom system
pythoncom.CoInitialize()
with salt.utils.winapi.Com():
# Create an AutoUpdate object
obj_au = win32com.client.Dispatch('Microsoft.Update.AutoUpdate')
# Create an AutoUpdate object
obj_au = win32com.client.Dispatch('Microsoft.Update.AutoUpdate')
# Create an AutoUpdate Settings Object
obj_au_settings = obj_au.Settings
@ -1174,7 +1175,8 @@ def set_wu_settings(level=None,
if msupdate is not None:
# Microsoft Update requires special handling
# First load the MS Update Service Manager
obj_sm = win32com.client.Dispatch('Microsoft.Update.ServiceManager')
with salt.utils.winapi.Com():
obj_sm = win32com.client.Dispatch('Microsoft.Update.ServiceManager')
# Give it a bogus name
obj_sm.ClientApplicationID = "My App"
@ -1275,10 +1277,9 @@ def get_wu_settings():
'Saturday']
# Initialize the PyCom system
pythoncom.CoInitialize()
# Create an AutoUpdate object
obj_au = win32com.client.Dispatch('Microsoft.Update.AutoUpdate')
with salt.utils.winapi.Com():
# Create an AutoUpdate object
obj_au = win32com.client.Dispatch('Microsoft.Update.AutoUpdate')
# Create an AutoUpdate Settings Object
obj_au_settings = obj_au.Settings
@ -1312,8 +1313,10 @@ def _get_msupdate_status():
'''
# To get the status of Microsoft Update we actually have to check the
# Microsoft Update Service Manager
# Create a ServiceManager Object
obj_sm = win32com.client.Dispatch('Microsoft.Update.ServiceManager')
# Initialize the PyCom system
with salt.utils.winapi.Com():
# Create a ServiceManager Object
obj_sm = win32com.client.Dispatch('Microsoft.Update.ServiceManager')
# Return a collection of loaded Services
col_services = obj_sm.Services

View File

@ -298,6 +298,7 @@ from salt.state import get_accumulator_dir as _get_accumulator_dir
if salt.utils.platform.is_windows():
import salt.utils.win_dacl
import salt.utils.win_functions
import salt.utils.winapi
# Import 3rd-party libs
from salt.ext import six
@ -1207,7 +1208,8 @@ def _shortcut_check(name,
), pchanges
if os.path.isfile(name):
shell = win32com.client.Dispatch("WScript.Shell")
with salt.utils.winapi.Com():
shell = win32com.client.Dispatch("WScript.Shell")
scut = shell.CreateShortcut(name)
state_checks = [scut.TargetPath.lower() == target.lower()]
if arguments is not None:
@ -6851,7 +6853,8 @@ def shortcut(
# This will just load the shortcut if it already exists
# It won't create the file until calling scut.Save()
shell = win32com.client.Dispatch("WScript.Shell")
with salt.utils.winapi.Com():
shell = win32com.client.Dispatch("WScript.Shell")
scut = shell.CreateShortcut(name)
# The shortcut target will automatically be created with its

View File

@ -14,12 +14,17 @@ import re
# Import Salt libs
import salt.utils.path
import salt.modules.cmdmod
__salt__ = {
'cmd.run_all': salt.modules.cmdmod.run_all
}
log = logging.getLogger(__name__)
def get_certs_path():
icinga2_output = __salt__['cmd.run_all']([salt.utils.path.which('icinga2'), "--version"], python_shell=False)
icinga2_output = __salt__['cmd.run_all']([salt.utils.path.which('icinga2'),
"--version"], python_shell=False)
version = re.search(r'r\d+\.\d+', icinga2_output['stdout']).group(0)
# Return new certs path for icinga2 >= 2.8
if int(version.split('.')[1]) >= 8:

View File

@ -56,6 +56,13 @@ def _init_libcrypto():
'''
libcrypto = _load_libcrypto()
try:
libcrypto.OPENSSL_init_crypto()
except AttributeError:
# Support for OpenSSL < 1.1 (OPENSSL_API_COMPAT < 0x10100000L)
libcrypto.OPENSSL_no_config()
libcrypto.OPENSSL_add_all_algorithms_noconf()
libcrypto.RSA_new.argtypes = ()
libcrypto.RSA_new.restype = c_void_p
libcrypto.RSA_free.argtypes = (c_void_p, )
@ -70,13 +77,6 @@ def _init_libcrypto():
libcrypto.RSA_private_encrypt.argtypes = (c_int, c_char_p, c_char_p, c_void_p, c_int)
libcrypto.RSA_public_decrypt.argtypes = (c_int, c_char_p, c_char_p, c_void_p, c_int)
try:
libcrypto.OPENSSL_init_crypto()
except AttributeError:
# Support for OpenSSL < 1.1 (OPENSSL_API_COMPAT < 0x10100000L)
libcrypto.OPENSSL_no_config()
libcrypto.OPENSSL_add_all_algorithms_noconf()
return libcrypto

View File

@ -10,6 +10,7 @@ import subprocess
# Import Salt libs
import salt.utils.args
import salt.utils.data
import salt.utils.winapi
from salt.ext import six
from salt.ext.six.moves import range
from salt.exceptions import CommandExecutionError
@ -17,7 +18,6 @@ from salt.exceptions import CommandExecutionError
# Import 3rd-party libs
try:
import win32com.client
import pythoncom
import pywintypes
HAS_PYWIN32 = True
except ImportError:
@ -25,6 +25,16 @@ except ImportError:
log = logging.getLogger(__name__)
__virtualname__ = 'win_update'
def __virtual__():
if not salt.utils.platform.is_windows():
return False, 'win_update: Not available on Windows'
if not HAS_PYWIN32:
return False, 'win_update: Missing pywin32'
return __virtualname__
class Updates(object):
'''
@ -68,7 +78,8 @@ class Updates(object):
Initialize the updates collection. Can be accessed via
``Updates.updates``
'''
self.updates = win32com.client.Dispatch('Microsoft.Update.UpdateColl')
with salt.utils.winapi.Com():
self.updates = win32com.client.Dispatch('Microsoft.Update.UpdateColl')
def count(self):
'''
@ -274,13 +285,13 @@ class WindowsUpdateAgent(object):
Need to look at the possibility of loading this into ``__context__``
'''
# Initialize the PyCom system
pythoncom.CoInitialize()
with salt.utils.winapi.Com():
# Create a session with the Windows Update Agent
self._session = win32com.client.Dispatch('Microsoft.Update.Session')
# Create a session with the Windows Update Agent
self._session = win32com.client.Dispatch('Microsoft.Update.Session')
# Create Collection for Updates
self._updates = win32com.client.Dispatch('Microsoft.Update.UpdateColl')
# Create Collection for Updates
self._updates = win32com.client.Dispatch('Microsoft.Update.UpdateColl')
self.refresh()
@ -572,7 +583,8 @@ class WindowsUpdateAgent(object):
# Initialize the downloader object and list collection
downloader = self._session.CreateUpdateDownloader()
self._session.ClientApplicationID = 'Salt: Download Update'
download_list = win32com.client.Dispatch('Microsoft.Update.UpdateColl')
with salt.utils.winapi.Com():
download_list = win32com.client.Dispatch('Microsoft.Update.UpdateColl')
ret = {'Updates': {}}
@ -683,7 +695,8 @@ class WindowsUpdateAgent(object):
installer = self._session.CreateUpdateInstaller()
self._session.ClientApplicationID = 'Salt: Install Update'
install_list = win32com.client.Dispatch('Microsoft.Update.UpdateColl')
with salt.utils.winapi.Com():
install_list = win32com.client.Dispatch('Microsoft.Update.UpdateColl')
ret = {'Updates': {}}
@ -802,7 +815,8 @@ class WindowsUpdateAgent(object):
installer = self._session.CreateUpdateInstaller()
self._session.ClientApplicationID = 'Salt: Install Update'
uninstall_list = win32com.client.Dispatch('Microsoft.Update.UpdateColl')
with salt.utils.winapi.Com():
uninstall_list = win32com.client.Dispatch('Microsoft.Update.UpdateColl')
ret = {'Updates': {}}
@ -999,8 +1013,7 @@ def needs_reboot():
'''
# Initialize the PyCom system
pythoncom.CoInitialize()
# Create an AutoUpdate object
obj_sys = win32com.client.Dispatch('Microsoft.Update.SystemInfo')
with salt.utils.winapi.Com():
# Create an AutoUpdate object
obj_sys = win32com.client.Dispatch('Microsoft.Update.SystemInfo')
return salt.utils.data.is_true(obj_sys.RebootRequired)

View File

@ -651,14 +651,18 @@ def _fetch_events(q):
atexit.register(_clean_queue)
a_config = AdaptedConfigurationTestCaseMixin()
event = salt.utils.event.get_event('minion', sock_dir=a_config.get_config('minion')['sock_dir'], opts=a_config.get_config('minion'))
event = salt.utils.event.get_event(
'minion',
sock_dir=a_config.get_config('minion')['sock_dir'],
opts=a_config.get_config('minion'),
)
while True:
try:
events = event.get_event(full=False)
except Exception:
except Exception as exc:
# This is broad but we'll see all kinds of issues right now
# if we drop the proc out from under the socket while we're reading
pass
log.exception("Exception caught while getting events %r", exc)
q.put(events)

View File

@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
'''
tests.support.nox-windows-setup
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This script is meant to run under the nox virtualenv to take care of required
windows procedures
'''
# pylint: disable=resource-leakage
from __future__ import absolute_import, print_function, unicode_literals
import os
import re
import sys
import site
import shutil
try:
import site
SITE_PACKAGES = site.getsitepackages()
PYTHON_EXECUTABLE_DIRECTORY = os.path.dirname(sys.executable)
PYTHON_SCRIPTS_DIR = os.path.join(PYTHON_EXECUTABLE_DIRECTORY, 'Scripts')
except AttributeError:
# The site module does not have the getsitepackages function when running within a virtualenv
# But the site-packages directory WILL be on sys.path
SITE_PACKAGES = None
for entry in sys.path:
if 'site-packages' in entry:
SITE_PACKAGES = entry
break
# Under a virtualenv, the python "binary" is under Scripts already.
# Well, not the binary, but the Python DLLs
PYTHON_EXECUTABLE_DIRECTORY = PYTHON_SCRIPTS_DIR = os.path.dirname(sys.executable)
# Requests is a Salt dependency, it's safe to import, but...
try:
import requests
HAS_REQUESTS = True
except ImportError:
HAS_REQUESTS = False
IS_64_BITS = sys.maxsize > 2**32
SALT_REPO_URL = 'https://repo.saltstack.com/windows/dependencies/{}'.format(IS_64_BITS and 64 or 32)
DLLS = ("libeay32.dll", "ssleay32.dll", "OpenSSL_License.txt", "msvcr120.dll", "libsodium.dll")
for dll in DLLS:
outfile = os.path.join(PYTHON_EXECUTABLE_DIRECTORY, dll)
if os.path.exists(outfile):
continue
src_url = '{}/{}'.format(SALT_REPO_URL, dll)
if HAS_REQUESTS:
print('Downloading {} to {}'.format(src_url, outfile))
request = requests.get(src_url, allow_redirects=True)
with open(outfile, 'wb') as wfh:
wfh.write(request.content)
else:
print('ATTENTION: The python requests package is not installed, can\'t download {}'.format(src_url))
PYWIN32_SYSTEM32_DIR = os.path.join(SITE_PACKAGES, 'pywin32_system32')
if os.path.exists(PYWIN32_SYSTEM32_DIR):
for fname in os.listdir(PYWIN32_SYSTEM32_DIR):
if not fname.endswith('.dll'):
continue
spath = os.path.join(PYWIN32_SYSTEM32_DIR, fname)
dpath = spath.replace('pywin32_system32', 'win32')
print('Moving {} to {}'.format(spath, dpath))
shutil.move(spath, dpath)
print('Deleting {}'.format(PYWIN32_SYSTEM32_DIR))
shutil.rmtree(PYWIN32_SYSTEM32_DIR, ignore_errors=True)
if os.path.exists(PYTHON_SCRIPTS_DIR):
print('Searching for pywin32 scripts to delete')
for fname in os.listdir(PYTHON_SCRIPTS_DIR):
if not fname.startswith('pywin32_'):
continue
fpath = os.path.join(PYTHON_SCRIPTS_DIR, fname)
print('Deleting {}'.format(fpath))
os.unlink(fpath)
PYTHONWIN_DIR = os.path.join(SITE_PACKAGES, 'pythonwin')
if os.path.exists(PYTHONWIN_DIR):
print('Deleting {}'.format(PYTHONWIN_DIR))
shutil.rmtree(PYTHONWIN_DIR, ignore_errors=True)
PYCRPTO_NT_FILE = os.path.join(SITE_PACKAGES, 'Crypto', 'Random', 'OSRNG', 'nt.py')
if os.path.exists(PYCRPTO_NT_FILE):
with open(PYCRPTO_NT_FILE, 'r') as rfh:
contents = rfh.read()
new_contents = re.sub(
r'^import winrandom$',
'from Crypto.Random.OSRNG import winrandom',
contents,
count=1,
flags=re.MULTILINE
)
if contents != new_contents:
print('Patching {}'.format(PYCRPTO_NT_FILE))
with open(PYCRPTO_NT_FILE, 'w') as wfh:
wfh.write(new_contents)

View File

@ -120,7 +120,10 @@ class DiskUsageBeaconTestCase(TestCase, LoaderModuleMockMixin):
ret = diskusage.beacon(config)
self.assertEqual(ret, [{'diskusage': 50, 'mount': '/'}])
def test_diskusage_windows(self):
def test_diskusage_windows_single_slash(self):
r'''
This tests new behavior (C:\)
'''
disk_usage_mock = Mock(return_value=WINDOWS_STUB_DISK_USAGE)
with patch('salt.utils.platform.is_windows',
MagicMock(return_value=True)):
@ -136,6 +139,44 @@ class DiskUsageBeaconTestCase(TestCase, LoaderModuleMockMixin):
ret = diskusage.beacon(config)
self.assertEqual(ret, [{'diskusage': 50, 'mount': 'C:\\'}])
def test_diskusage_windows_double_slash(self):
'''
This tests original behavior (C:\\)
'''
disk_usage_mock = Mock(return_value=WINDOWS_STUB_DISK_USAGE)
with patch('salt.utils.platform.is_windows',
MagicMock(return_value=True)):
with patch('psutil.disk_partitions',
MagicMock(return_value=WINDOWS_STUB_DISK_PARTITION)), \
patch('psutil.disk_usage', disk_usage_mock):
config = [{'C:\\\\': '50%'}]
ret = diskusage.validate(config)
self.assertEqual(ret, (True, 'Valid beacon configuration'))
ret = diskusage.beacon(config)
self.assertEqual(ret, [{'diskusage': 50, 'mount': 'C:\\'}])
def test_diskusage_windows_lowercase(self):
r'''
This tests lowercase drive letter (c:\)
'''
disk_usage_mock = Mock(return_value=WINDOWS_STUB_DISK_USAGE)
with patch('salt.utils.platform.is_windows',
MagicMock(return_value=True)):
with patch('psutil.disk_partitions',
MagicMock(return_value=WINDOWS_STUB_DISK_PARTITION)), \
patch('psutil.disk_usage', disk_usage_mock):
config = [{'c:\\': '50%'}]
ret = diskusage.validate(config)
self.assertEqual(ret, (True, 'Valid beacon configuration'))
ret = diskusage.beacon(config)
self.assertEqual(ret, [{'diskusage': 50, 'mount': 'C:\\'}])
def test_diskusage_windows_match_regex(self):
disk_usage_mock = Mock(return_value=WINDOWS_STUB_DISK_USAGE)
with patch('salt.utils.platform.is_windows',

View File

@ -156,3 +156,43 @@ class INotifyBeaconTestCase(TestCase, LoaderModuleMockMixin):
self.assertEqual(len(ret), 1)
self.assertEqual(ret[0]['path'], fp)
self.assertEqual(ret[0]['change'], 'IN_DELETE')
def test_multi_files_exclude(self):
dp1 = os.path.join(self.tmpdir, 'subdir1')
dp2 = os.path.join(self.tmpdir, 'subdir2')
os.mkdir(dp1)
os.mkdir(dp2)
_exclude1 = '{0}/subdir1/*tmpfile*$'.format(self.tmpdir)
_exclude2 = '{0}/subdir2/*filetmp*$'.format(self.tmpdir)
config = [{'files': {dp1: {'mask': ['create', 'delete'],
'recurse': True,
'exclude': [{_exclude1: {'regex': True}}],
'auto_add': True}}},
{'files': {dp2: {'mask': ['create', 'delete'],
'recurse': True,
'exclude': [{_exclude2: {'regex': True}}],
'auto_add': True}}}]
ret = inotify.validate(config)
self.assertEqual(ret, (True, 'Valid beacon configuration'))
fp = os.path.join(dp1, 'tmpfile')
with salt.utils.files.fopen(fp, 'w') as f:
pass
ret = inotify.beacon(config)
self.assertEqual(len(ret), 0)
os.remove(fp)
ret = inotify.beacon(config)
self.assertEqual(len(ret), 0)
fp = os.path.join(dp2, 'tmpfile')
with salt.utils.files.fopen(fp, 'w') as f:
pass
ret = inotify.beacon(config)
self.assertEqual(len(ret), 1)
self.assertEqual(ret[0]['path'], fp)
self.assertEqual(ret[0]['change'], 'IN_CREATE')
os.remove(fp)
ret = inotify.beacon(config)
self.assertEqual(len(ret), 1)
self.assertEqual(ret[0]['path'], fp)
self.assertEqual(ret[0]['change'], 'IN_DELETE')

View File

@ -297,6 +297,13 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'stderr': True})
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device'))
mock.assert_called_with('mount device name ',
python_shell=False, runas=None)
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device', fstype='fstype'))
mock.assert_called_with('mount -t fstype device name ',
python_shell=False, runas=None)
mock = MagicMock(return_value={'retcode': False,
'stderr': False})
@ -312,6 +319,35 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'stderr': True})
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device'))
mock.assert_called_with('mount device name ',
python_shell=False, runas=None)
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device', fstype='fstype'))
mock.assert_called_with('mount -v fstype device name ',
python_shell=False, runas=None)
mock = MagicMock(return_value={'retcode': False,
'stderr': False})
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device'))
with patch.dict(mount.__grains__, {'os': 'Linux'}):
mock = MagicMock(return_value=True)
with patch.object(os.path, 'exists', mock):
mock = MagicMock(return_value=None)
with patch.dict(mount.__salt__, {'file.mkdir': None}):
mock = MagicMock(return_value={'retcode': True,
'stderr': True})
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device'))
mock.assert_called_with('mount -o defaults device name ',
python_shell=False, runas=None)
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device', fstype='fstype'))
mock.assert_called_with('mount -o defaults -t fstype device name ',
python_shell=False, runas=None)
mock = MagicMock(return_value={'retcode': False,
'stderr': False})

View File

@ -38,6 +38,11 @@ class WinSystemTestCase(TestCase, LoaderModuleMockMixin):
now.day, now.hour, now.minute,
now.second, now.microsecond])
modules_globals['win32api'] = win32api
win32net = types.ModuleType(str('win32net')) # future lint: disable=blacklisted-function
win32net.NetServerGetInfo = MagicMock()
win32net.NetServerSetInfo = MagicMock()
modules_globals['win32net'] = win32net
return {win_system: modules_globals}
def test_halt(self):
@ -177,14 +182,15 @@ class WinSystemTestCase(TestCase, LoaderModuleMockMixin):
'''
Test to set the Windows computer description
'''
mock = MagicMock(return_value=True)
with patch.dict(win_system.__salt__, {'cmd.run': mock}):
mock = MagicMock(return_value="Salt's comp")
with patch.object(win_system, 'get_computer_desc', mock):
self.assertDictEqual(win_system.set_computer_desc(
"Salt's comp"
),
{'Computer Description': "Salt's comp"})
mock = MagicMock()
mock_get_info = MagicMock(return_value={'comment': ''})
mock_get_desc = MagicMock(return_value="Salt's comp")
with patch('salt.modules.win_system.win32net.NetServerGetInfo', mock_get_info), \
patch('salt.modules.win_system.win32net.NetServerSetInfo', mock), \
patch.object(win_system, 'get_computer_desc', mock_get_desc):
self.assertDictEqual(
win_system.set_computer_desc("Salt's comp"),
{'Computer Description': "Salt's comp"})
@skipIf(not win_system.HAS_WIN32NET_MODS, 'this test needs the w32net library')
def test_get_computer_desc(self):

View File

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
'''
Unit tests for salt._compat
'''
# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals
import logging
import sys
# Import Salt Testing libs
from tests.support.unit import TestCase
# Import Salt libs
import salt._compat as compat
# Import 3rd Party libs
from salt.ext.six import binary_type, text_type
log = logging.getLogger(__name__)
PY3 = sys.version_info.major == 3
class CompatTestCase(TestCase):
def test_text(self):
ret = compat.text_('test string')
self.assertTrue(isinstance(ret, text_type))
def test_text_binary(self):
ret = compat.text_(b'test string')
self.assertTrue(isinstance(ret, text_type))
def test_bytes(self):
ret = compat.bytes_('test string')
self.assertTrue(isinstance(ret, binary_type))
def test_bytes_binary(self):
ret = compat.bytes_(b'test string')
self.assertTrue(isinstance(ret, binary_type))
def test_ascii_native(self):
ret = compat.ascii_native_('test string')
self.assertTrue(isinstance(ret, str))
def test_ascii_native_binary(self):
ret = compat.ascii_native_(b'test string')
self.assertTrue(isinstance(ret, str))
def test_native(self):
ret = compat.native_('test string')
self.assertTrue(isinstance(ret, str))
def test_native_binary(self):
ret = compat.native_(b'test string')
self.assertTrue(isinstance(ret, str))
def test_string_io(self):
ret = compat.string_io('test string')
if PY3:
expected = 'io.StringIO object'
else:
expected = 'cStringIO.StringI object'
self.assertTrue(expected in repr(ret))
def test_string_io_unicode(self):
ret = compat.string_io(u'test string \xf8')
if PY3:
expected = 'io.StringIO object'
else:
expected = 'StringIO.StringIO instance'
self.assertTrue(expected in repr(ret))