Merge remote-tracking branch 'upstream/2015.8' into merge-forward-develop

Conflicts:
    salt/minion.py
    salt/modules/rest_service.py
    salt/states/dockerng.py
    salt/utils/cloud.py
This commit is contained in:
Colton Myers 2015-09-29 18:13:33 -06:00
commit 41cad702e3
41 changed files with 720 additions and 580 deletions

View file

@ -123,8 +123,9 @@ MOCK_MODULES = [
for mod_name in MOCK_MODULES:
sys.modules[mod_name] = Mock()
# Define a fake version attribute for libcloud so docs build as supposed
# Define a fake version attribute for the following libs.
sys.modules['libcloud'].__version__ = '0.0.0'
sys.modules['pymongo'].version = '0.0.0'
# -- Add paths to PYTHONPATH ---------------------------------------------------

View file

@ -163,81 +163,126 @@ it can be verified with Salt:
GCE Specific Settings
=====================
Consult the sample profile below for more information about GCE specific
settings. Some of them are mandatory and are properly labeled below but
settings. Some of them are mandatory and are properly labeled below but
typically also include a hard-coded default.
Initial Profile
---------------
Set up an initial profile at ``/etc/salt/cloud.profiles`` or
``/etc/salt/cloud.profiles.d/gce.conf``:
.. code-block:: yaml
my-gce-profile:
# Image is used to define what Operating System image should be used
# to for the instance. Examples are Debian 7 (wheezy) and CentOS 6.
#
# MANDATORY
#
image: centos-6
# A 'size', in GCE terms, refers to the instance's 'machine type'. See
# the on-line documentation for a complete list of GCE machine types.
#
# MANDATORY
#
size: n1-standard-1
# A 'location', in GCE terms, refers to the instance's 'zone'. GCE
# has the notion of both Regions (e.g. us-central1, europe-west1, etc)
# and Zones (e.g. us-central1-a, us-central1-b, etc).
#
# MANDATORY
#
location: europe-west1-b
# Use this setting to define the network resource for the instance.
# All GCE projects contain a network named 'default' but it's possible
# to use this setting to create instances belonging to a different
# network resource.
#
network: default
# GCE supports instance/network tags and this setting allows you to
# set custom tags. It should be a list of strings and must be
# parse-able by the python ast.literal_eval() function to convert it
# to a python list.
#
tags: '["one", "two", "three"]'
# GCE supports instance metadata and this setting allows you to
# set custom metadata. It should be a hash of key/value strings and
# parse-able by the python ast.literal_eval() function to convert it
# to a python dictionary.
#
metadata: '{"one": "1", "2": "two"}'
# Use this setting to ensure that when new instances are created,
# they will use a persistent disk to preserve data between instance
# terminations and re-creations.
#
use_persistent_disk: True
# In the event that you wish the boot persistent disk to be permanently
# deleted when you destroy an instance, set delete_boot_pd to True.
#
delete_boot_pd: False
# Specify whether to use public or private IP for deploy script.
# Valid options are:
# private_ips - The salt-master is also hosted with GCE
# public_ips - The salt-master is hosted outside of GCE
ssh_interface: public_ips
# Per instance setting: Used a named fixed IP address to this host.
# Valid options are:
# ephemeral - The host will use a GCE ephemeral IP
# None - No external IP will be configured on this host.
# Optionally, pass the name of a GCE address to use a fixed IP address.
# If the address does not already exist, it will be created.
external_ip: "ephemeral"
image
-----
Image is used to define what Operating System image should be used
to for the instance. Examples are Debian 7 (wheezy) and CentOS 6. Required.
size
----
A 'size', in GCE terms, refers to the instance's 'machine type'. See
the on-line documentation for a complete list of GCE machine types. Required.
location
--------
A 'location', in GCE terms, refers to the instance's 'zone'. GCE
has the notion of both Regions (e.g. us-central1, europe-west1, etc)
and Zones (e.g. us-central1-a, us-central1-b, etc). Required.
network
-------
Use this setting to define the network resource for the instance.
All GCE projects contain a network named 'default' but it's possible
to use this setting to create instances belonging to a different
network resource.
tags
----
GCE supports instance/network tags and this setting allows you to
set custom tags. It should be a list of strings and must be
parse-able by the python ast.literal_eval() function to convert it
to a python list.
metadata
--------
GCE supports instance metadata and this setting allows you to
set custom metadata. It should be a hash of key/value strings and
parse-able by the python ast.literal_eval() function to convert it
to a python dictionary.
use_persistent_disk
-------------------
Use this setting to ensure that when new instances are created,
they will use a persistent disk to preserve data between instance
terminations and re-creations.
delete_boot_pd
--------------
In the event that you wish the boot persistent disk to be permanently
deleted when you destroy an instance, set delete_boot_pd to True.
ssh_interface
-------------
Specify whether to use public or private IP for deploy script.
Valid options are:
* private_ips: The salt-master is also hosted with GCE
* public_ips: The salt-master is hosted outside of GCE
external_ip
-----------
Per instance setting: Used a named fixed IP address to this host.
Valid options are:
* ephemeral - The host will use a GCE ephemeral IP
* None - No external IP will be configured on this host.
Optionally, pass the name of a GCE address to use a fixed IP address.
If the address does not already exist, it will be created.
ex_disk_type
------------
GCE supports two different disk types, ``pd-standard`` and ``pd-ssd``.
The default disk type setting is ``pd-standard``. To specify using an SSD
disk, set ``pd-ssd`` as the value.
.. versionadded:: 2014.7.0
ip_forwarding
-------------
GCE instances can be enabled to use IP Forwarding. When set to ``True``,
this options allows the instance to send/receive non-matching src/dst
packets. Default is ``False``.
.. versionadded:: 2015.8.1
SSH Remote Access
=================
GCE instances do not allow remote access to the root user by default.
Instead, another user must be used to run the deploy script using sudo.
Append something like this to ``/etc/salt/cloud.profiles`` or

View file

@ -288,6 +288,9 @@ with labels.
``Story``
The issue is used by a SaltStack engineer to track progress on multiple related issues in a single place.
``Stretch``
The issue is an optional goal for the current sprint but may not be delivered.
``ZD``
The issue is related to a Zendesk customer support ticket.

View file

@ -25,6 +25,8 @@ configuration is needed to pull from these repositories.
These packages are usually available within a few days of upstream release.
.. _freebsd-upstream:
SaltStack repo
==============

View file

@ -67,7 +67,7 @@ from the file /srv/pillar/p8000.sls (if you have not changed your default pillar
---------
proxy:
proxymodule: rest_sample
proxytype: rest_sample
url: http://<IP your REST listens on>:port
In other words, if your REST service is listening on port 8000 on 127.0.0.1

View file

@ -19,7 +19,7 @@ See the following links for instructions:
- :ref:`Red Hat / CentOS 5, 6, 7 <installation-rhel-repo>`
- :ref:`Debian 8 <installation-debian-repo>`
- :ref:`Windows <windows-installer>`
- FreeBSD
- :ref:`FreeBSD <freebsd-upstream>`
Send Event on State Completion
==============================

View file

@ -6,3 +6,11 @@ Version 2015.8.1 is a bugfix release for :doc:`2015.8.0
</topics/releases/2015.8.0>`.
Changes:
- Some issues with proxy minions were corrected.
Known Issues:
- Proxy minions currently cannot execute a highstate because of the way
the proxymodule is being loaded internally. This will be fixed in a
future release.

View file

@ -6,7 +6,7 @@ Extended changelog courtesy of Todd Stansell (https://github.com/tjstansell/salt
*Generated at: 2015-09-09T18:15:43Z*
This list includes all pull requests merged into the 2015.8 branch between the
forking of the branch from develop and the release of 12015.8.0.
forking of the branch from develop and the release of 2015.8.0.
Statistics:

View file

@ -42,6 +42,8 @@ The information which can be stored in a roster `target` is the following:
# Optional parameters
port: # The target system's ssh port number
sudo: # Boolean to run command via sudo
tty: # Boolean: Set this option to True if sudo is also set to
# True and requiretty is also set on the target system
priv: # File path to ssh private key, defaults to salt-ssh.rsa
timeout: # Number of seconds to wait for response when establishing
# an SSH connection

View file

@ -113,34 +113,34 @@ FunctionEnd
Function updateMinionConfig
ClearErrors
FileOpen $0 "$INSTDIR\conf\minion" "r" ; open target file for reading
GetTempFileName $R0 ; get new temp file name
FileOpen $1 $R0 "w" ; open temp file for writing
FileOpen $0 "$INSTDIR\conf\minion" "r" ; open target file for reading
GetTempFileName $R0 ; get new temp file name
FileOpen $1 $R0 "w" ; open temp file for writing
loop:
FileRead $0 $2 ; read line from target file
FileRead $0 $2 ; read line from target file
IfErrors done
${If} $MasterHost_State != ""
${AndIf} $MasterHost_State != "salt" ; check if end of file reached
StrCmp $2 "#master: salt$\r$\n" 0 +2 ; compare line with search string with CR/LF
StrCpy $2 "master: $MasterHost_State$\r$\n" ; change line
StrCmp $2 "#master: salt" 0 +2 ; compare line with search string without CR/LF (at the end of the file)
StrCpy $2 "master: $MasterHost_State" ; change line
${AndIf} $MasterHost_State != "salt" ; check if end of file reached
StrCmp $2 "#master: salt$\r$\n" 0 +2 ; compare line with search string with CR/LF
StrCpy $2 "master: $MasterHost_State$\r$\n" ; change line
StrCmp $2 "#master: salt" 0 +2 ; compare line with search string without CR/LF (at the end of the file)
StrCpy $2 "master: $MasterHost_State" ; change line
${EndIf}
${If} $MinionName_State != ""
${AndIf} $MinionName_State != "hostname"
StrCmp $2 "#id:$\r$\n" 0 +2 ; compare line with search string with CR/LF
StrCpy $2 "id: $MinionName_State$\r$\n" ; change line
StrCmp $2 "#id:" 0 +2 ; compare line with search string without CR/LF (at the end of the file)
StrCpy $2 "id: $MinionName_State" ; change line
StrCmp $2 "#id:$\r$\n" 0 +2 ; compare line with search string with CR/LF
StrCpy $2 "id: $MinionName_State$\r$\n" ; change line
StrCmp $2 "#id:" 0 +2 ; compare line with search string without CR/LF (at the end of the file)
StrCpy $2 "id: $MinionName_State" ; change line
${EndIf}
FileWrite $1 $2 ; write changed or unchanged line to temp file
FileWrite $1 $2 ; write changed or unchanged line to temp file
Goto loop
done:
FileClose $0 ; close target file
FileClose $1 ; close temp file
Delete "$INSTDIR\conf\minion" ; delete target file
CopyFiles /SILENT $R0 "$INSTDIR\conf\minion" ; copy temp file to target file
FileClose $0 ; close target file
FileClose $1 ; close temp file
Delete "$INSTDIR\conf\minion" ; delete target file
CopyFiles /SILENT $R0 "$INSTDIR\conf\minion" ; copy temp file to target file
Delete $R0
FunctionEnd
@ -156,40 +156,6 @@ ShowUnInstDetails show
Section "MainSection" SEC01
; Remove previous version of salt, but don't remove conf and key
; Service must be stopped to delete files that are in use
ExecWait "net stop salt-minion"
; Remove the service in case we're installing over an older version
; It will be recreated later
ExecWait "sc delete salt-minion"
; Delete everything except conf and var
ClearErrors
FindFirst $0 $1 $INSTDIR\*
loop:
IfFileExists "$INSTDIR\$1\*.*" IsDir IsFile
IsDir:
${IfNot} $1 == "."
${AndIfNot} $1 == ".."
${AndIfNot} $1 == "conf"
${AndIfNot} $1 == "var"
RMDir /r "$INSTDIR\$1"
${EndIf}
IsFile:
DELETE "$INSTDIR\$1"
FindNext $0 $1
IfErrors done
Goto loop
done:
FindClose $0
Sleep 3000
SetOutPath "$INSTDIR\"
SetOverwrite try
CreateDirectory $INSTDIR\conf\pki\minion
@ -239,6 +205,34 @@ FunctionEnd
Function .onInit
; Check for existing installation
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "UninstallString"
StrCmp $R0 "" confFind
; Found existing installation, prompt to uninstall
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "${PRODUCT_NAME} is already installed. $\n$\nClick `OK` to remove the existing installation." /SD IDOK IDOK uninst
Abort
uninst:
; Make sure we're in the right directory
${If} $INSTDIR == "c:\salt\bin\Scripts"
StrCpy $INSTDIR "C:\salt"
${EndIf}
; Stop and remove the salt-minion service
ExecWait "net stop salt-minion"
ExecWait "sc delete salt-minion"
; Remove salt binaries and batch files
Delete "$INSTDIR\uninst.exe"
Delete "$INSTDIR\nssm.exe"
Delete "$INSTDIR\salt*"
RMDir /r "$INSTDIR\bin"
; Remove registry entries
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}"
confFind:
IfFileExists "$INSTDIR\conf\minion" confFound confNotFound
@ -339,7 +333,7 @@ Section Uninstall
Delete "$INSTDIR\uninst.exe"
Delete "$INSTDIR\nssm.exe"
Delete "$INSTDIR\salt*"
Delete "$INSTDIR\bin"
RMDir /r "$INSTDIR\bin"
${If} $INSTDIR != 'Program Files'
${AndIf} $INSTDIR != 'Program Files (x86)'

View file

@ -1281,7 +1281,13 @@ def _modify_eni_properties(eni_id, properties=None):
while retries > 0:
retries = retries - 1
result = query(params, return_root=True)
result = aws.query(params,
return_root=True,
location=get_location(),
provider=get_provider(),
opts=__opts__,
sigver='4')
if isinstance(result, dict) and result.get('error'):
time.sleep(1)
continue

View file

@ -2030,8 +2030,6 @@ def request_instance(vm_):
if LIBCLOUD_VERSION_INFO > (0, 15, 1):
# This only exists in current trunk of libcloud and should be in next
# release
kwargs.update({
'ex_disk_type': config.get_cloud_config_value(
'ex_disk_type', vm_, __opts__, default='pd-standard'),
@ -2040,7 +2038,10 @@ def request_instance(vm_):
'ex_disks_gce_struct': config.get_cloud_config_value(
'ex_disks_gce_struct', vm_, __opts__, default=None),
'ex_service_accounts': config.get_cloud_config_value(
'ex_service_accounts', vm_, __opts__, default=None)
'ex_service_accounts', vm_, __opts__, default=None),
'ex_can_ip_forward': config.get_cloud_config_value(
'ip_forwarding', vm_, __opts__, default=False
)
})
if kwargs.get('ex_disk_type') not in ('pd-standard', 'pd-ssd'):
raise SaltCloudSystemExit(

View file

@ -220,7 +220,7 @@ def list_nodes(full=False, call=None):
for node in nodes:
ret[node] = {}
for item in 'id', 'image', 'size', 'public_ips', 'private_ips', 'state':
for item in ('id', 'image', 'size', 'public_ips', 'private_ips', 'state'):
ret[node][item] = nodes[node][item]
return ret

View file

@ -222,7 +222,7 @@ def list_nodes(conn=None, call=None):
nodes = list_nodes_full(conn, call)
for node in nodes:
ret[node] = {}
for prop in 'id', 'image', 'name', 'size', 'state', 'private_ips', 'public_ips':
for prop in ('id', 'image', 'name', 'size', 'state', 'private_ips', 'public_ips'):
ret[node][prop] = nodes[node][prop]
return ret

View file

@ -512,8 +512,7 @@ class Client(object):
ret.sort()
return ret
def get_url(self, url, dest, makedirs=False, saltenv='base',
env=None, no_cache=False):
def get_url(self, url, dest, makedirs=False, saltenv='base', env=None, no_cache=False):
'''
Get a single file from a URL.
'''
@ -598,78 +597,21 @@ class Client(object):
destfp = None
try:
if no_cache:
result = []
def on_chunk(chunk):
result.append(chunk)
else:
dest_tmp = "{0}.part".format(dest)
destfp = salt.utils.fopen(dest_tmp, 'wb')
def on_chunk(chunk):
destfp.write(chunk)
query = salt.utils.http.query(
fixed_url,
stream=True,
streaming_callback=on_chunk,
username=url_data.username,
password=url_data.password,
**get_kwargs
)
if 'handle' not in query:
raise MinionError('Error: {0}'.format(query['error']))
try:
content_length = int(query['handle'].headers['Content-Length'])
except (AttributeError, KeyError, ValueError):
# Shouldn't happen but don't let this raise an exception.
# Instead, just don't do content length checking below.
log.warning(
'No Content-Length header in HTTP response from fetch of '
'{0}, or Content-Length is non-numeric'.format(fixed_url)
)
content_length = None
if no_cache:
content = ''.join(result)
if content_length is not None \
and len(content) > content_length:
return content[-content_length:]
else:
return content
return query['handle'].body
else:
destfp.close()
destfp = None
dest_tmp_size = os.path.getsize(dest_tmp)
if content_length is not None \
and dest_tmp_size > content_length:
log.warning(
'Size of file downloaded from {0} ({1}) does not '
'match the Content-Length ({2}). This is probably due '
'to an upstream bug in tornado '
'(https://github.com/tornadoweb/tornado/issues/1518). '
'Re-writing the file to correct this.'.format(
fixed_url,
dest_tmp_size,
content_length
)
)
dest_tmp_bak = dest_tmp + '.bak'
salt.utils.files.rename(dest_tmp, dest_tmp_bak)
with salt.utils.fopen(dest_tmp_bak, 'rb') as fp_bak:
fp_bak.seek(dest_tmp_size - content_length)
with salt.utils.fopen(dest_tmp, 'wb') as fp_new:
while True:
chunk = fp_bak.read(
self.opts['file_buffer_size']
)
if not chunk:
break
fp_new.write(chunk)
os.remove(dest_tmp_bak)
dest_tmp = "{0}.part".format(dest)
with salt.utils.fopen(dest_tmp, 'wb') as destfp:
destfp.write(query['handle'].body)
salt.utils.files.rename(dest_tmp, dest)
return dest
except HTTPError as exc:

View file

@ -43,11 +43,11 @@ def defaults():
def facts():
log.debug('----------- Trying to get facts')
if 'proxymodule' in __opts__:
facts = __opts__['proxymodule']['junos.facts']()
facts['version_info'] = 'override'
return facts
if 'junos.facts' in __opts__['proxymodule']:
facts = __opts__['proxymodule']['junos.facts']()
facts['version_info'] = 'override'
return facts
return None

View file

@ -19,7 +19,7 @@ def kernel():
def os():
return {'os': 'proxy'}
return {'os': 'RestExampleOS'}
def location():

View file

@ -186,6 +186,8 @@ def minion_mods(
loaded_base_name=loaded_base_name,
static_modules=static_modules)
ret.pack['__salt__'] = ret
# Load any provider overrides from the configuration file providers option
# Note: Providers can be pkg, service, user or group - not to be confused
# with cloud providers.
@ -195,7 +197,7 @@ def minion_mods(
# sometimes providers opts is not to diverge modules but
# for other configuration
try:
funcs = raw_mod(opts, providers[mod], ret.items())
funcs = raw_mod(opts, providers[mod], ret)
except TypeError:
break
else:
@ -204,7 +206,6 @@ def minion_mods(
f_key = '{0}{1}'.format(mod, func[func.rindex('.'):])
ret[f_key] = funcs[func]
ret.pack['__salt__'] = ret
if notify:
evt = salt.utils.event.get_event('minion', opts=opts, listen=False)
evt.fire_event({'complete': True}, tag='/salt/minion/minion_mod_complete')
@ -254,6 +255,7 @@ def proxy(opts, functions, whitelist=None, loaded_base_name=None):
'''
Returns the proxy module for this salt-proxy-minion
'''
dirs = _module_dirs(opts, 'proxy', 'proxy')
return LazyLoader(_module_dirs(opts, 'proxy', 'proxy'),
opts,
tag='proxy',

View file

@ -803,7 +803,7 @@ class Minion(MinionBase):
just return the value of the return_retry_timer.
'''
msg = 'Minion return retry timer set to {0} seconds'
if self.opts['return_retry_random']:
if self.opts.get('return_retry_random'):
try:
random_retry = randint(1, self.opts['return_retry_timer'])
except ValueError:
@ -818,8 +818,8 @@ class Minion(MinionBase):
log.debug(msg.format(random_retry) + ' (randomized)')
return random_retry
else:
log.debug(msg.format(self.opts['return_retry_timer']))
return self.opts['return_retry_timer']
log.debug(msg.format(self.opts.get('return_retry_timer')))
return self.opts.get('return_retry_timer')
def _prep_mod_opts(self):
'''
@ -2548,9 +2548,18 @@ class ProxyMinion(Minion):
self.opts['proxymodule'] = salt.loader.proxy(self.opts, None, loaded_base_name=fq_proxyname)
self.functions, self.returners, self.function_errors, self.executors = self._load_modules()
proxy_fn = self.opts['proxymodule'].loaded_base_name + '.init'
self.opts['proxymodule'][proxy_fn](self.opts)
if ('{0}.init'.format(fq_proxyname) not in self.opts['proxymodule']
or '{0}.shutdown'.format(fq_proxyname) not in self.opts['proxymodule']):
log.error('Proxymodule {0} is missing an init() or a shutdown() or both.'.format(fq_proxyname))
log.error('Check your proxymodule. Salt-proxy aborted.')
self._running = False
raise SaltSystemExit(code=-1)
proxy_fn = self.opts['proxymodule'].loaded_base_name + '.init'
self.opts['proxymodule'][proxy_fn](self.opts)
# reload ?!?
self.serial = salt.payload.Serial(self.opts)
self.mod_opts = self._prep_mod_opts()
self.matcher = Matcher(self.opts, self.functions)

View file

@ -52,9 +52,28 @@ def _get_build_env(env):
return env_override
def _get_repo_env(env):
def _get_repo_options_env(env):
'''
Get repo environment overrides dictionary to use in repo process
Get repo environment overrides dictionary to use in repo options process
env
A dictionary of variables to define the repository options
Example:
.. code-block:: yaml
- env:
- OPTIONS : 'ask-passphrase'
.. warning::
The above illustrates a common PyYAML pitfall, that **yes**,
**no**, **on**, **off**, **true**, and **false** are all loaded as
boolean ``True`` and ``False`` values, and must be enclosed in
quotes to be used as strings. More info on this (and other) PyYAML
idiosyncrasies can be found :doc:`here
</topics/troubleshooting/yaml_idiosyncrasies>`.
'''
env_options = ''
if env is None:
@ -64,10 +83,96 @@ def _get_repo_env(env):
'\'env\' must be a Python dictionary'
)
for key, value in env.items():
env_options += '{0}\n'.format(value)
if key == 'OPTIONS':
env_options += '{0}\n'.format(value)
return env_options
def _get_repo_dists_env(env):
'''
Get repo environment overrides dictionary to use in repo distributions process
env
A dictionary of variables to define the repository distributions
Example:
.. code-block:: yaml
- env:
- ORIGIN : 'jessie'
- LABEL : 'salt debian'
- SUITE : 'main'
- VERSION : '8.1'
- CODENAME : 'jessie'
- ARCHS : 'amd64 i386 source'
- COMPONENTS : 'main'
- DESCRIPTION : 'SaltStack Debian package repo'
.. warning::
The above illustrates a common PyYAML pitfall, that **yes**,
**no**, **on**, **off**, **true**, and **false** are all loaded as
boolean ``True`` and ``False`` values, and must be enclosed in
quotes to be used as strings. More info on this (and other) PyYAML
idiosyncrasies can be found :doc:`here
</topics/troubleshooting/yaml_idiosyncrasies>`.
'''
# env key with tuple of control information for handling input env dictionary
# 0 | M - Mandatory, O - Optional, I - Ignore
# 1 | 'text string for repo field'
# 2 | 'default value'
dflts_dict = {
'OPTIONS': ('I', '', 'processed by _get_repo_options_env'),
'ORIGIN': ('O', 'Origin', 'SaltStack'),
'LABEL': ('O', 'Label', 'salt_debian'),
'SUITE': ('O', 'Suite', 'stable'),
'VERSION': ('O', 'Version', '8.1'),
'CODENAME': ('M', 'Codename', 'jessie'),
'ARCHS': ('M', 'Architectures', 'i386 amd64 source'),
'COMPONENTS': ('M', 'Components', 'main'),
'DESCRIPTION': ('O', 'Description', 'SaltStack debian package repo'),
}
env_dists = ''
codename = ''
dflts_keys = list(dflts_dict.keys())
if env is None:
for key, value in dflts_dict.items():
if dflts_dict[key][0] == 'M':
env_dists += '{0}: {1}\n'.format(dflts_dict[key][1], dflts_dict[key][2])
if key == 'CODENAME':
codename = dflts_dict[key][2]
return (codename, env_dists)
if not isinstance(env, dict):
raise SaltInvocationError(
'\'env\' must be a Python dictionary'
)
env_man_seen = []
for key, value in env.items():
if key in dflts_keys:
if dflts_dict[key][0] == 'M':
env_man_seen.append(key)
if key == 'CODENAME':
codename = value
if dflts_dict[key][0] != 'I':
env_dists += '{0}: {1}\n'.format(dflts_dict[key][1], value)
else:
env_dists += '{0}: {1}\n'.format(key, value)
## ensure mandatories are included
env_keys = list(env.keys())
for key in env_keys:
if key in dflts_keys and dflts_dict[key][0] == 'M' and key not in env_man_seen:
env_dists += '{0}: {1}\n'.format(dflts_dict[key][1], dflts_dict[key][2])
if key == 'CODENAME':
codename = value
return (codename, env_dists)
def _create_pbuilders(env):
'''
Create the .pbuilder family of files in user's home directory
@ -90,72 +195,13 @@ def _create_pbuilders(env):
idiosyncrasies can be found :doc:`here
</topics/troubleshooting/yaml_idiosyncrasies>`.
'''
hook_text = '''#!/bin/sh
set -e
cat > "/etc/apt/preferences" << EOF
Package: python-alabaster
Pin: release a=testing
Pin-Priority: 950
Package: python-sphinx
Pin: release a=testing
Pin-Priority: 900
Package: sphinx-common
Pin: release a=testing
Pin-Priority: 900
Package: *
Pin: release a=jessie-backports
Pin-Priority: 750
Package: *
Pin: release a=stable
Pin-Priority: 700
Package: *
Pin: release a=testing
Pin-Priority: 650
Package: *
Pin: release a=unstable
Pin-Priority: 600
Package: *
Pin: release a=experimental
Pin-Priority: 550
EOF
'''
pbldrc_text = '''DIST="jessie"
if [ -n "${DIST}" ]; then
TMPDIR=/tmp
BASETGZ="`dirname $BASETGZ`/$DIST-base.tgz"
DISTRIBUTION=$DIST
APTCACHE="/var/cache/pbuilder/$DIST/aptcache"
fi
HOOKDIR="${HOME}/.pbuilder-hooks"
OTHERMIRROR="deb http://ftp.us.debian.org/debian/ stable main contrib non-free | deb http://ftp.us.debian.org/debian/ testing main contrib non-free | deb http://ftp.us.debian.org/debian/ unstable main contrib non-free"
'''
home = os.path.expanduser('~')
pbuilder_hooksdir = os.path.join(home, '.pbuilder-hooks')
if not os.path.isdir(pbuilder_hooksdir):
os.makedirs(pbuilder_hooksdir)
g05hook = os.path.join(pbuilder_hooksdir, 'G05apt-preferences')
with salt.utils.fopen(g05hook, 'w') as fow:
fow.write('{0}'.format(hook_text))
cmd = 'chmod 755 {0}'.format(g05hook)
__salt__['cmd.run'](cmd)
pbuilderrc = os.path.join(home, '.pbuilderrc')
with salt.utils.fopen(pbuilderrc, 'w') as fow:
fow.write('{0}'.format(pbldrc_text))
if not os.path.isfile(pbuilderrc):
raise SaltInvocationError(
'pbuilderrc environment is incorrectly setup'
)
env_overrides = _get_build_env(env)
if env_overrides and not env_overrides.isspace():
@ -342,37 +388,29 @@ def make_repo(repodir, keyid=None, env=None):
salt '*' pkgbuild.make_repo /var/www/html/
'''
repocfg_text = '''Origin: SaltStack
Label: salt_debian
Suite: unstable
Codename: jessie
Architectures: i386 amd64 source
Components: contrib
Description: SaltStack debian package repo
Pull: jessie
'''
repoconf = os.path.join(repodir, 'conf')
if not os.path.isdir(repoconf):
os.makedirs(repoconf)
codename, repocfg_dists = _get_repo_dists_env(env)
repoconfdist = os.path.join(repoconf, 'distributions')
with salt.utils.fopen(repoconfdist, 'w') as fow:
fow.write('{0}'.format(repocfg_text))
fow.write('{0}'.format(repocfg_dists))
if keyid is not None:
with salt.utils.fopen(repoconfdist, 'a') as fow:
fow.write('SignWith: {0}\n'.format(keyid))
repocfg_opts = _get_repo_env(env)
repocfg_opts = _get_repo_options_env(env)
repoconfopts = os.path.join(repoconf, 'options')
with salt.utils.fopen(repoconfopts, 'w') as fow:
fow.write('{0}'.format(repocfg_opts))
for debfile in os.listdir(repodir):
if debfile.endswith('.changes'):
cmd = 'reprepro -Vb . include jessie {0}'.format(os.path.join(repodir, debfile))
cmd = 'reprepro -Vb . include {0} {1}'.format(codename, os.path.join(repodir, debfile))
__salt__['cmd.run'](cmd, cwd=repodir)
if debfile.endswith('.deb'):
cmd = 'reprepro -Vb . includedeb jessie {0}'.format(os.path.join(repodir, debfile))
cmd = 'reprepro -Vb . includedeb {0} {1}'.format(codename, os.path.join(repodir, debfile))
__salt__['cmd.run'](cmd, cwd=repodir)

View file

@ -5,7 +5,7 @@ Management of Docker Containers
.. versionadded:: 2014.1.0
.. deprecated:: 2015.8.0
Future feature development will be done only in :mod:`docker-ng
Future feature development will be done only in :mod:`dockerng
<salt.modules.dockerng>`. See the documentation for this module for
information on the deprecation path.

View file

@ -5,8 +5,8 @@ Management of Docker Containers
.. versionadded:: 2015.8.0
Why Make a Second Docker Module?
--------------------------------
Why Make a Second Docker Execution Module?
------------------------------------------
We have received a lot of feedback on our Docker support. In the process of
implementing recommended improvements, it became obvious that major changes
@ -19,7 +19,7 @@ option. This will give users a couple release cycles to modify their scripts,
SLS files, etc. to use the new functionality, rather than forcing users to
change everything immediately.
In the **Carbon** release of Salt (due early 2016), this execution module will
In the **Carbon** release of Salt (due in 2016), this execution module will
take the place of the default Docker execution module, and backwards-compatible
naming will be maintained for a couple releases after that to allow users time
to replace references to ``dockerng`` with ``docker``.
@ -28,13 +28,13 @@ to replace references to ``dockerng`` with ``docker``.
Installation Prerequisites
--------------------------
This execution module requires at least version 1.0.0 of both docker-py_ and
This execution module requires at least version 1.4.0 of both docker-py_ and
Docker_. docker-py can easily be installed using :py:func:`pip.install
<salt.modules.pip.install>`:
.. code-block:: bash
salt myminion pip.install docker-py
salt myminion pip.install docker-py>=1.4.0
.. _docker-py: https://pypi.python.org/pypi/docker-py
.. _Docker: https://www.docker.com/
@ -234,6 +234,7 @@ import distutils.version # pylint: disable=import-error,no-name-in-module,unuse
import fnmatch
import functools
import gzip
import inspect as inspect_module
import json
import logging
import os
@ -256,6 +257,7 @@ import salt.ext.six as six
# pylint: disable=import-error
try:
import docker
import docker.utils
HAS_DOCKER_PY = True
except ImportError:
HAS_DOCKER_PY = False
@ -287,7 +289,7 @@ __func_alias__ = {
# Minimum supported versions
MIN_DOCKER = (1, 0, 0)
MIN_DOCKER_PY = (1, 0, 0)
MIN_DOCKER_PY = (1, 4, 0)
VERSION_RE = r'([\d.]+)'
@ -343,7 +345,7 @@ argument name:
'''
VALID_CREATE_OPTS = {
'cmd': {
'command': {
'path': 'Config:Cmd',
},
'hostname': {
@ -1104,25 +1106,25 @@ def _validate_input(action,
raise SaltInvocationError(key + ' must be a list of strings')
# Custom validation functions for container creation options
def _valid_cmd(): # pylint: disable=unused-variable
def _valid_command(): # pylint: disable=unused-variable
'''
Must be either a string or a list of strings. Value will be translated
to a list of strings
'''
if kwargs.get('cmd') is None:
if kwargs.get('command') is None:
# No need to validate
return
if isinstance(kwargs['cmd'], six.string_types):
if isinstance(kwargs['command'], six.string_types):
# Translate command into a list of strings
try:
kwargs['cmd'] = shlex.split(kwargs['cmd'])
kwargs['command'] = shlex.split(kwargs['command'])
except AttributeError:
pass
try:
_valid_stringlist('cmd')
_valid_stringlist('command')
except SaltInvocationError:
raise SaltInvocationError(
'cmd must be a string or list of strings'
'command/cmd must be a string or list of strings'
)
def _valid_user(): # pylint: disable=unused-variable
@ -2515,10 +2517,10 @@ def create(image,
image
Image from which to create the container
cmd or command
command or cmd
Command to run in the container
Example: ``cmd=bash`` or ``command=bash``
Example: ``command=bash`` or ``cmd=bash``
.. versionchanged:: 2015.8.1
``cmd`` is now also accepted
@ -2654,13 +2656,13 @@ def create(image,
# Create a CentOS 7 container that will stay running once started
salt myminion dockerng.create centos:7 name=mycent7 interactive=True tty=True command=bash
'''
if 'command' in kwargs:
if 'cmd' in kwargs:
if 'cmd' in kwargs:
if 'command' in kwargs:
raise SaltInvocationError(
'Only one of \'cmd\' and \'command\' can be used. Both '
'Only one of \'command\' and \'cmd\' can be used. Both '
'arguments are equivalent.'
)
kwargs['cmd'] = kwargs.pop('command')
kwargs['command'] = kwargs.pop('cmd')
try:
# Try to inspect the image, if it fails then we know we need to pull it
@ -2689,14 +2691,11 @@ def create(image,
# Added to manage api change in 1.19.
# mem_limit and memswap_limit must be provided in host_config object
if salt.utils.version_cmp(version()['ApiVersion'], '1.18') == 1:
create_kwargs['host_config'] = docker.utils.create_host_config(
mem_limit=create_kwargs.get('mem_limit'),
memswap_limit=create_kwargs.get('memswap_limit')
client = __context__['docker.client']
host_config_args = inspect_module.getargspec(docker.utils.create_host_config).args
create_kwargs['host_config'] = client.create_host_config(
**dict((arg, create_kwargs.pop(arg, None)) for arg in host_config_args if arg != 'version')
)
if 'mem_limit' in create_kwargs:
del create_kwargs['mem_limit']
if 'memswap_limit' in create_kwargs:
del create_kwargs['memswap_limit']
log.debug(
'dockerng.create is using the following kwargs to create '

View file

@ -80,11 +80,14 @@ def _config_getter(get_opt,
def _expand_path(cwd, user):
'''
Expand home directory
'''
try:
to_expand = '~' + user if user else '~'
except TypeError:
# Users should never be numeric but if we don't account for this then
# we're going to get a traceback
# we're going to get a traceback if someone passes this invalid input.
to_expand = '~' + str(user) if user else '~'
try:
return os.path.join(os.path.expanduser(to_expand), cwd)
@ -246,6 +249,9 @@ def _get_toplevel(path, user=None):
def _git_config(cwd, user):
'''
Helper to retrive git config options
'''
contextkey = 'git.config.' + cwd
if contextkey not in __context__:
git_dir = rev_parse(cwd,
@ -958,7 +964,6 @@ config_get_regex = config_get_regexp
def config_set(key,
value=None,
add=False,
multivar=None,
cwd=None,
user=None,
@ -1029,6 +1034,7 @@ def config_set(key,
salt myminion git.config_set user.email foo@bar.com global=True
'''
kwargs = salt.utils.clean_kwargs(**kwargs)
add_ = kwargs.pop('add', False)
global_ = kwargs.pop('global', False)
is_global = kwargs.pop('is_global', False)
if kwargs:
@ -1080,7 +1086,7 @@ def config_set(key,
if value is not None:
command = copy.copy(command_prefix)
if add:
if add_:
command.append('--add')
else:
command.append('--replace-all')
@ -1830,8 +1836,8 @@ def merge(cwd,
rev=None,
opts='',
user=None,
branch=None,
ignore_retcode=False):
ignore_retcode=False,
**kwargs):
'''
Interface to `git-merge(1)`_
@ -1883,14 +1889,19 @@ def merge(cwd,
# .. or merge another rev
salt myminion git.merge /path/to/repo rev=upstream/foo
'''
kwargs = salt.utils.clean_kwargs(**kwargs)
branch_ = kwargs.pop('branch', None)
if kwargs:
salt.utils.invalid_kwargs(kwargs)
cwd = _expand_path(cwd, user)
if branch:
if branch_:
salt.utils.warn_until(
'Nitrogen',
'The \'branch\' argument to git.merge has been deprecated, please '
'use \'rev\' instead.'
)
rev = branch
rev = branch_
command = ['git', 'merge']
command.extend(_format_opts(opts))
if rev:
@ -2201,7 +2212,7 @@ def push(cwd,
user=None,
identity=None,
ignore_retcode=False,
branch=None):
**kwargs):
'''
Interface to `git-push(1)`_
@ -2270,14 +2281,19 @@ def push(cwd,
# Delete remote branch 'upstream/temp'
salt myminion git.push /path/to/repo upstream :temp
'''
kwargs = salt.utils.clean_kwargs(**kwargs)
branch_ = kwargs.pop('branch', None)
if kwargs:
salt.utils.invalid_kwargs(kwargs)
cwd = _expand_path(cwd, user)
if branch:
if branch_:
salt.utils.warn_until(
'Nitrogen',
'The \'branch\' argument to git.push has been deprecated, please '
'use \'ref\' instead.'
)
ref = branch
ref = branch_
command = ['git', 'push']
command.extend(_format_opts(opts))
if not isinstance(remote, six.string_types):
@ -2393,7 +2409,8 @@ def remote_get(cwd,
cwd = _expand_path(cwd, user)
all_remotes = remotes(cwd,
user=user,
redact_auth=redact_auth)
redact_auth=redact_auth,
ignore_retcode=ignore_retcode)
if remote not in all_remotes:
raise CommandExecutionError(
'Remote \'{0}\' not present in git checkout located at {1}'
@ -2959,8 +2976,8 @@ def submodule(cwd,
opts='',
user=None,
identity=None,
init=False,
ignore_retcode=False):
ignore_retcode=False,
**kwargs):
'''
.. versionchanged:: 2015.8.0
Added the ``command`` argument to allow for operations other than
@ -3038,8 +3055,13 @@ def submodule(cwd,
# Unregister submodule (2015.8.0 and later)
salt myminion git.submodule /path/to/repo/sub/repo deinit
'''
kwargs = salt.utils.clean_kwargs(**kwargs)
init_ = kwargs.pop('init', False)
if kwargs:
salt.utils.invalid_kwargs(kwargs)
cwd = _expand_path(cwd, user)
if init:
if init_:
raise SaltInvocationError(
'The \'init\' argument is no longer supported. Either set '
'\'command\' to \'init\', or include \'--init\' in the \'opts\' '
@ -3178,14 +3200,14 @@ def version(versioninfo=False):
def worktree_add(cwd,
worktree_path,
branch=None,
ref=None,
reset_branch=None,
force=None,
detach=False,
opts='',
user=None,
ignore_retcode=False):
ignore_retcode=False,
**kwargs):
'''
.. versionadded:: 2015.8.0
@ -3254,8 +3276,13 @@ def worktree_add(cwd,
salt myminion git.worktree_add /path/to/repo/main ../hotfix ref=origin/master
salt myminion git.worktree_add /path/to/repo/main ../hotfix branch=hotfix21 ref=v2.1.9.3
'''
kwargs = salt.utils.clean_kwargs(**kwargs)
branch_ = kwargs.pop('branch', None)
if kwargs:
salt.utils.invalid_kwargs(kwargs)
cwd = _expand_path(cwd, user)
if branch and detach:
if branch_ and detach:
raise SaltInvocationError(
'Only one of \'branch\' and \'detach\' is allowed'
)
@ -3269,9 +3296,9 @@ def worktree_add(cwd,
)
command.append('--detach')
else:
if not branch:
branch = os.path.basename(worktree_path)
command.extend(['-B' if reset_branch else '-b', branch])
if not branch_:
branch_ = os.path.basename(worktree_path)
command.extend(['-B' if reset_branch else '-b', branch_])
if force:
command.append('--force')
command.extend(_format_opts(opts))

View file

@ -16,6 +16,9 @@ except ImportError:
# Import salt libs
import salt.utils
# Import 3rd-party libs
import salt.ext.six as six
__func_alias__ = {
'list_': 'list'
}
@ -67,6 +70,8 @@ def install(pecls, defaults=False, force=False, preferred_state='stable'):
salt '*' pecl.install fuse
'''
if isinstance(pecls, six.string_types):
pecls = [pecls]
preferred_state = '-d preferred_state={0}'.format(_cmd_quote(preferred_state))
if force:
return _pecl('{0} install -f {1}'.format(preferred_state, _cmd_quote(' '.join(pecls))),
@ -108,6 +113,8 @@ def uninstall(pecls):
salt '*' pecl.uninstall fuse
'''
if isinstance(pecls, six.string_types):
pecls = [pecls]
return _pecl('uninstall {0}'.format(_cmd_quote(' '.join(pecls))))
@ -124,6 +131,8 @@ def update(pecls):
salt '*' pecl.update fuse
'''
if isinstance(pecls, six.string_types):
pecls = [pecls]
return _pecl('install -U {0}'.format(_cmd_quote(' '.join(pecls))))

View file

@ -1,41 +1,69 @@
# -*- coding: utf-8 -*-
'''
Service support for the REST example
Provide the service module for the proxy-minion REST sample
'''
from __future__ import absolute_import
# Import python libs
from __future__ import absolute_import
import logging
__proxyenabled__ = ['rest_sample']
log = logging.getLogger(__name__)
__proxyenabled__ = ['rest_sample']
# Define the module's virtual name
__virtualname__ = 'service'
# Don't shadow built-ins.
__func_alias__ = {
'list_': 'list'
}
# Define the module's virtual name
__virtualname__ = 'service'
def __virtual__():
'''
Only work on RestExampleOS
Only work on systems that are a proxy minion
'''
# Enable on these platforms only.
enable = set((
'RestExampleOS',
'proxy',
))
if __grains__['os'] in enable:
if __grains__['os'] == 'proxy':
return __virtualname__
return False
def start(name):
def get_all():
'''
Start the specified service
Return a list of all available services
.. versionadded:: 2015.8.0
CLI Example:
.. code-block:: bash
salt '*' service.get_all
'''
proxy_fn = 'rest_sample'+ '.service_list'
return __opts__['proxymodule'][proxy_fn]()
def list_():
'''
Return a list of all available services.
.. versionadded: 2015.8.1
CLI Example:
.. code-block:: bash
salt '*' service.list
'''
return get_all()
def start(name, sig=None):
'''
Start the specified service on the rest_sample
.. versionadded:: 2015.8.0
CLI Example:
@ -43,12 +71,16 @@ def start(name):
salt '*' service.start <service name>
'''
return __opts__['proxymodule']['rest_sample.service_start'](name)
proxy_fn = 'rest_sample'+ '.service_start'
return __opts__['proxymodule'][proxy_fn](name)
def stop(name):
def stop(name, sig=None):
'''
Stop the specified service
Stop the specified service on the rest_sample
.. versionadded:: 2015.8.0
CLI Example:
@ -56,12 +88,15 @@ def stop(name):
salt '*' service.stop <service name>
'''
return __opts__['proxymodule']['rest_sample.service_stop'](name)
proxy_fn = 'rest_sample'+ '.service_stop'
return __opts__['proxymodule'][proxy_fn](name)
def restart(name):
def restart(name, sig=None):
'''
Restart the named service
Restart the specified service with rest_sample
.. versionadded:: 2015.8.0
CLI Example:
@ -70,13 +105,16 @@ def restart(name):
salt '*' service.restart <service name>
'''
return __opts__['proxymodule']['rest_sample.service_restart'](name)
proxy_fn = 'rest_sample'+ '.service_restart'
return __opts__['proxymodule'][proxy_fn](name)
def status(name):
def status(name, sig=None):
'''
Return the status for a service, returns a bool whether the service is
running.
Return the status for a service via rest_sample, returns a bool
whether the service is running.
.. versionadded:: 2015.8.0
CLI Example:
@ -84,17 +122,30 @@ def status(name):
salt '*' service.status <service name>
'''
return __opts__['proxymodule']['rest_sample.service_status'](name)
proxy_fn = 'rest_sample' + '.service_status'
resp = __opts__['proxymodule'][proxy_fn](name)
if resp['comment'] == 'stopped':
return False
if resp['comment'] == 'running':
return True
def list_():
def running(name, sig=None):
'''
List services.
Return whether this service is running.
CLI Example:
.. versionadded:: 2015.8.0
.. code-block:: bash
salt '*' service.list <service name>
'''
return __opts__['proxymodule']['rest_sample.service_list']()
return status(name).get(name, False)
def enabled(name, sig=None):
'''
Only the 'redbull' service is 'enabled' in the test
.. versionadded:: 2015.8.1
'''
return name == 'redbull'

View file

@ -1,105 +0,0 @@
# -*- coding: utf-8 -*-
'''
Provide the service module for the proxy-minion REST sample
'''
# Import python libs
from __future__ import absolute_import
import logging
__proxyenabled__ = ['rest_sample']
log = logging.getLogger(__name__)
__func_alias__ = {
'reload_': 'reload'
}
# Define the module's virtual name
__virtualname__ = 'service'
def __virtual__():
'''
Only work on systems that are a proxy minion
'''
if __grains__['kernel'] == 'proxy':
return __virtualname__
return False
def get_all():
'''
Return a list of all available services
CLI Example:
.. code-block:: bash
salt '*' service.get_all
'''
proxy_fn = 'rest_sample'+ '.service_list'
return __opts__['proxymodule'][proxy_fn]()
def start(name):
'''
Start the specified service on the rest_sample
CLI Example:
.. code-block:: bash
salt '*' service.start <service name>
'''
proxy_fn = 'rest_sample'+ '.service_start'
return __opts__['proxymodule'][proxy_fn](name)
def stop(name):
'''
Stop the specified service on the rest_sample
CLI Example:
.. code-block:: bash
salt '*' service.stop <service name>
'''
proxy_fn = 'rest_sample'+ '.service_stop'
return __opts__['proxymodule'][proxy_fn](name)
def restart(name):
'''
Restart the specified service with rest_sample
CLI Example:
.. code-block:: bash
salt '*' service.restart <service name>
'''
proxy_fn = 'rest_sample'+ '.service_restart'
return __opts__['proxymodule'][proxy_fn](name)
def status(name, sig):
'''
Return the status for a service via rest_sample, returns a bool
whether the service is running.
CLI Example:
.. code-block:: bash
salt '*' service.status <service name>
'''
proxy_fn = 'rest_sample' + '.service_status'
resp = __opts__['proxymodule'][proxy_fn](name)
if resp['comment'] == 'stopped':
return {name: False}
if resp['comment'] == 'running':
return {name: True}

View file

@ -408,8 +408,7 @@ def present(
for cfg in launch_config:
args.update(cfg)
if not __opts__['test']:
lc_ret = __salt__['state.single']('boto_lc.present', **args)
lc_ret = next(six.itervalues(lc_ret))
lc_ret = __states__['boto_lc.present'](**args)
if lc_ret['result'] is True and lc_ret['changes']:
if 'launch_config' not in ret['changes']:
ret['changes']['launch_config'] = {}
@ -612,8 +611,7 @@ def _alarms_present(name, alarms, alarms_from_pillar, region, key, keyid, profil
'keyid': keyid,
'profile': profile,
}
ret = __salt__['state.single']('boto_cloudwatch_alarm.present', **kwargs)
results = next(six.itervalues(ret))
results = __states__['boto_cloudwatch_alarm.present'](**kwargs)
if not results['result']:
merged_return_value['result'] = False
if results.get('changes', {}) != {}:

View file

@ -335,8 +335,7 @@ def present(
name, region, key, keyid, profile
)
for cname in cnames:
_ret = __salt__['state.single'](
'boto_route53.present',
_ret = __states__['boto_route53.present'](
name=cname.get('name'),
value=lb['dns_name'],
zone=cname.get('zone'),
@ -348,7 +347,6 @@ def present(
keyid=keyid,
profile=profile
)
_ret = _ret.values()[0]
ret['changes'] = dictupdate.update(ret['changes'], _ret['changes'])
ret['comment'] = ' '.join([ret['comment'], _ret['comment']])
if not _ret['result']:
@ -382,7 +380,6 @@ def register_instances(name, instances, region=None, key=None, keyid=None,
- instance-id2
'''
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
ret['name'] = name
lb = __salt__['boto_elb.exists'](name, region, key, keyid, profile)
if lb:
health = __salt__['boto_elb.get_instance_health'](name,
@ -912,9 +909,8 @@ def _alarms_present(name, alarms, alarms_from_pillar, region, key, keyid, profil
"keyid": keyid,
"profile": profile,
}
ret = __salt__["state.single"]('boto_cloudwatch_alarm.present', **kwargs)
results = next(six.itervalues(ret))
if not results["result"]:
results = __states__['boto_cloudwatch_alarm.present'](**kwargs)
if not results.get('result'):
merged_return_value["result"] = results["result"]
if results.get("changes", {}) != {}:
merged_return_value["changes"][info["name"]] = results["changes"]

View file

@ -3,6 +3,11 @@
Manage Docker containers
========================
.. deprecated:: 2015.8.0
Future feature development will be done only in :mod:`dockerng
<salt.states.dockerng>`. See the documentation for this module for
information on the deprecation path.
`Docker <https://www.docker.io>`_
is a lightweight, portable, self-sufficient software container
wrapper. The base supported wrapper type is

View file

@ -8,6 +8,27 @@ Management of Docker containers
This is the state module to accompany the :mod:`dockerng
<salt.modules.dockerng>` execution module.
Why Make a Second Docker State Module?
--------------------------------------
We have received a lot of feedback on our Docker support. In the process of
implementing recommended improvements, it became obvious that major changes
needed to be made to the functions and return data. In the end, a complete
rewrite was done.
The changes being too significant, it was decided that making a separate
execution module and state module (called ``dockerng``) would be the best
option. This will give users a couple release cycles to modify their scripts,
SLS files, etc. to use the new functionality, rather than forcing users to
change everything immediately.
In the **Carbon** release of Salt (due in 2016), this execution module will
take the place of the default Docker execution module, and backwards-compatible
naming will be maintained for a couple releases after that to allow users time
to replace references to ``dockerng`` with ``docker``.
.. note::
To pull from a Docker registry, authentication must be configured. See
@ -47,7 +68,7 @@ NOTSET = object()
def __virtual__():
'''
Only load if the dockerio execution module is available
Only load if the dockerng execution module is available
'''
if 'dockerng.version' in __salt__:
global _validate_input # pylint: disable=global-statement
@ -331,8 +352,8 @@ def _compare(actual, create_kwargs, runtime_kwargs):
continue
elif isinstance(data, list):
# Compare two sorted lists of items. Won't work for "cmd" or
# "entrypoint" because those are both shell commands and the
# Compare two sorted lists of items. Won't work for "command"
# or "entrypoint" because those are both shell commands and the
# original order matters. It will, however, work for "volumes"
# because even though "volumes" is a sub-dict nested within the
# "actual" dict sorted(somedict) still just gives you a sorted
@ -425,17 +446,18 @@ def image_present(name,
image = ':'.join(_get_repo_tag(name))
all_tags = __salt__['dockerng.list_tags']()
if image in all_tags and not force:
ret['result'] = True
ret['comment'] = 'Image \'{0}\' already present'.format(name)
return ret
elif image in all_tags and force:
try:
image_info = __salt__['dockerng.inspect_image'](name)
except Exception as exc:
ret['comment'] = \
'Unable to get info for image \'{0}\': {1}'.format(name, exc)
if image in all_tags:
if not force:
ret['result'] = True
ret['comment'] = 'Image \'{0}\' already present'.format(name)
return ret
else:
try:
image_info = __salt__['dockerng.inspect_image'](name)
except Exception as exc:
ret['comment'] = \
'Unable to get info for image \'{0}\': {1}'.format(name, exc)
return ret
else:
image_info = None
@ -732,7 +754,7 @@ def running(name,
**CONTAINER CONFIGURATION PARAMETERS**
cmd or command
command or cmd
Command to run in the container
.. code-block:: yaml
@ -740,7 +762,7 @@ def running(name,
foo:
dockerng.running:
- image: bar/baz:latest
- cmd: bash
- command: bash
OR
@ -749,7 +771,7 @@ def running(name,
foo:
dockerng.running:
- image: bar/baz:latest
- command: bash
- cmd: bash
.. versionchanged:: 2015.8.1
``cmd`` is now also accepted
@ -1344,15 +1366,15 @@ def running(name,
ret['comment'] = 'The \'image\' argument is required'
return ret
if 'command' in kwargs:
if 'cmd' in kwargs:
if 'cmd' in kwargs:
if 'command' in kwargs:
ret['comment'] = (
'Only one of \'cmd\' and \'command\' can be used. Both '
'Only one of \'command\' and \'cmd\' can be used. Both '
'arguments are equivalent.'
)
ret['result'] = False
return ret
kwargs['cmd'] = kwargs.pop('command')
kwargs['command'] = kwargs.pop('cmd')
try:
image = ':'.join(_get_repo_tag(image))

View file

@ -1584,10 +1584,11 @@ def config_unset(name,
all : False
If ``True``, unset all matches
repo : None
An optional location of a git repository for local operations
repo
Location of the git repository for which the config value should be
set. Required unless ``global`` is set to ``True``.
user : None
user
Optional name of a user as whom `git config` will be run
global : False
@ -1659,11 +1660,12 @@ def config_unset(name,
# Get matching keys/values
pre_matches = __salt__['git.config_get_regexp'](
cwd='global' if global_ else repo,
cwd=repo,
key=key,
value_regex=value_regex,
user=user,
ignore_retcode=True
ignore_retcode=True,
**{'global': global_}
)
if not pre_matches:
@ -1706,11 +1708,12 @@ def config_unset(name,
# Get all keys matching the key expression, so we can accurately report
# on changes made.
pre = __salt__['git.config_get_regexp'](
cwd='global' if global_ else repo,
cwd=repo,
key=key,
value_regex=None,
user=user,
ignore_retcode=True
ignore_retcode=True,
**{'global': global_}
)
failed = []
@ -1719,11 +1722,12 @@ def config_unset(name,
for key_name in pre_matches:
try:
__salt__['git.config_unset'](
cwd='global' if global_ else repo,
cwd=repo,
key=name,
value_regex=value_regex,
all=all_,
user=user
user=user,
**{'global': global_}
)
except CommandExecutionError as exc:
msg = 'Failed to unset \'{0}\''.format(key_name)
@ -1741,11 +1745,12 @@ def config_unset(name,
)
post = __salt__['git.config_get_regexp'](
cwd='global' if global_ else repo,
cwd=repo,
key=key,
value_regex=None,
user=user,
ignore_retcode=True
ignore_retcode=True,
**{'global': global_}
)
for key_name, values in six.iteritems(pre):
@ -1759,11 +1764,12 @@ def config_unset(name,
post_matches = post
else:
post_matches = __salt__['git.config_get_regexp'](
cwd='global' if global_ else repo,
cwd=repo,
key=key,
value_regex=value_regex,
user=user,
ignore_retcode=True
ignore_retcode=True,
**{'global': global_}
)
if post_matches:
@ -1809,10 +1815,11 @@ def config_set(name,
.. versionadded:: 2015.8.0
repo : None
An optional location of a git repository for local operations
repo
Location of the git repository for which the config value should be
set. Required unless ``global`` is set to ``True``.
user : None
user
Optional name of a user as whom `git config` will be run
global : False
@ -1920,11 +1927,11 @@ def config_set(name,
# Get current value
pre = __salt__['git.config_get'](
cwd='global' if global_ else repo,
cwd=repo,
key=name,
user=user,
ignore_retcode=True,
**{'all': True}
**{'all': True, 'global': global_}
)
if desired == pre:
@ -1948,11 +1955,12 @@ def config_set(name,
try:
# Set/update config value
post = __salt__['git.config_set'](
cwd='global' if global_ else repo,
cwd=repo,
key=name,
value=value,
multivar=multivar,
user=user
user=user,
**{'global': global_}
)
except CommandExecutionError as exc:
return _fail(

View file

@ -318,7 +318,8 @@ class AsyncZeroMQPubChannel(salt.transport.mixins.auth.AESPubClientMixin, salt.t
# TODO: Optionally call stream.close() on newer pyzmq? Its broken on some
self._stream.io_loop.remove_handler(self._stream.socket)
self._stream.socket.close(0)
self.context.term()
if hasattr(self, 'context'):
self.context.term()
def __del__(self):
self.destroy()

View file

@ -86,6 +86,6 @@ class SyncWrapper(object):
'''
On deletion of the async wrapper, make sure to clean up the async stuff
'''
self.io_loop.close()
if hasattr(self, 'async'):
del self.async
self.io_loop.close()

View file

@ -93,14 +93,22 @@ def copyfile(source, dest, backup_mode='', cachedir=''):
def rename(src, dst):
'''
On Windows, os.rename() will fail with a WindowsError exception if a file
exists at the destination path. This function removes the destination path
first before attempting the os.rename().
exists at the destination path. This function checks for this error and if
found, it deletes the destination path first.
'''
try:
os.remove(dst)
os.rename(src, dst)
except OSError as exc:
if exc.errno != errno.ENOENT:
raise MinionError(
'Error: Unable to remove {0}: {1}'.format(dst, exc.strerror)
)
os.rename(src, dst)
if exc.errno != errno.EEXIST:
raise
try:
os.remove(dst)
except OSError as exc:
if exc.errno != errno.ENOENT:
raise MinionError(
'Error: Unable to remove {0}: {1}'.format(
dst,
exc.strerror
)
)
os.rename(src, dst)

View file

@ -742,7 +742,7 @@ class Pygit2(GitProvider):
for line in subprocess.Popen(
'git ls-remote origin',
shell=True,
close_fds=True,
close_fds=not salt.utils.is_windows(),
cwd=self.repo.workdir,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT).communicate()[0].splitlines():
@ -1508,7 +1508,7 @@ class GitBase(object):
self.cache_root = os.path.join(self.opts['cachedir'], self.role)
self.env_cache = os.path.join(self.cache_root, 'envs.p')
self.hash_cachedir = os.path.join(
self.cache_root, self.role, 'hash')
self.cache_root, 'hash')
self.file_list_cachedir = os.path.join(
self.opts['cachedir'], 'file_lists', self.role)
@ -1609,7 +1609,7 @@ class GitBase(object):
pass
to_remove = []
for item in cachedir_ls:
if item in ('gitfs', 'refs'):
if item in ('hash', 'refs'):
continue
path = os.path.join(self.cache_root, item)
if os.path.isdir(path):

View file

@ -559,9 +559,9 @@ def dependency_information(include_salt_cloud=False):
('cffi', 'cffi', '__version__'),
('pycparser', 'pycparser', '__version__'),
('gitdb', 'gitdb', '__version__'),
('gitpython', 'gitpython', '__version__'),
('python-gnupg', 'python-gnupg', '__version__'),
('mysql-python', 'mysql-python', '__version__'),
('gitpython', 'git', '__version__'),
('python-gnupg', 'gnupg', '__version__'),
('mysql-python', 'MySQLdb', '__version__'),
('cherrypy', 'cherrypy', '__version__'),
]

View file

@ -35,10 +35,7 @@ import integration
log = logging.getLogger(__name__)
def _worktrees_supported():
'''
Check if the git version is 2.5.0 or later
'''
def _git_version():
git_version = subprocess.Popen(
['git', '--version'],
shell=False,
@ -49,9 +46,16 @@ def _worktrees_supported():
log.debug('Git not installed')
return False
log.debug('Detected git version ' + git_version)
return LooseVersion(git_version.split()[-1])
def _worktrees_supported():
'''
Check if the git version is 2.5.0 or later
'''
try:
return LooseVersion(git_version.split()[-1]) >= LooseVersion('2.5.0')
except Exception:
return _git_version() >= LooseVersion('2.5.0')
except AttributeError:
return False
@ -894,6 +898,13 @@ class GitModuleTest(integration.ModuleCase):
This tests git.worktree_add, git.is_worktree, git.list_worktrees,
git.worktree_rm, and git.worktree_prune
'''
# We don't need to enclose this comparison in a try/except, since the
# decorator would skip this test if git is not installed and we'd never
# get here in the first place.
if _git_version() >= LooseVersion('2.6.0'):
worktree_add_prefix = 'Preparing '
else:
worktree_add_prefix = 'Enter '
worktree_path = tempfile.mkdtemp(dir=integration.TMP)
worktree_basename = os.path.basename(worktree_path)
worktree_path2 = tempfile.mkdtemp(dir=integration.TMP)
@ -902,11 +913,11 @@ class GitModuleTest(integration.ModuleCase):
ret = self.run_function(
'git.worktree_add', [self.repo, worktree_path],
)
self.assertTrue('Enter ' + worktree_path in ret)
self.assertTrue(worktree_add_prefix + worktree_path in ret)
ret = self.run_function(
'git.worktree_add', [self.repo, worktree_path2]
)
self.assertTrue('Enter ' + worktree_path2 in ret)
self.assertTrue(worktree_add_prefix + worktree_path2 in ret)
# Check if this new path is a worktree
self.assertTrue(self.run_function('git.is_worktree', [worktree_path]))
# Check if the main repo is a worktree

View file

@ -26,6 +26,10 @@ dockerng_mod.__context__ = {'docker.docker_version': ''}
dockerng_mod.__salt__ = {}
def _docker_py_version():
return dockerng_mod.docker.version_info if dockerng_mod.HAS_DOCKER_PY else None
@skipIf(NO_MOCK, NO_MOCK_REASON)
class DockerngTestCase(TestCase):
'''
@ -78,6 +82,63 @@ class DockerngTestCase(TestCase):
mine_send.assert_called_with('dockerng.ps', verbose=True, all=True,
host=True)
@skipIf(_docker_py_version() < (1, 4, 0),
'docker module must be installed to run this test or is too old. >=1.4.0')
@patch.object(dockerng_mod, 'images', MagicMock())
@patch.object(dockerng_mod, 'inspect_image')
@patch.object(dockerng_mod, 'version', Mock(return_value={'ApiVersion': '1.19'}))
def test_create_with_arg_cmd(self, *args):
'''
When cmd argument is passed check it is renamed to command.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
host_config = {}
client = Mock()
client.api_version = '1.19'
client.create_host_config.return_value = host_config
client.create_container.return_value = {}
with patch.dict(dockerng_mod.__dict__,
{'__salt__': __salt__}):
with patch.dict(dockerng_mod.__context__,
{'docker.client': client}):
dockerng_mod.create('image', cmd='ls', name='ctn')
client.create_container.assert_called_once_with(
command='ls',
host_config=host_config,
image='image',
name='ctn')
@skipIf(_docker_py_version() < (1, 4, 0),
'docker module must be installed to run this test or is too old. >=1.4.0')
@patch.object(dockerng_mod, 'images', MagicMock())
@patch.object(dockerng_mod, 'inspect_image')
@patch.object(dockerng_mod, 'version', Mock(return_value={'ApiVersion': '1.19'}))
def test_create_send_host_config(self, *args):
'''
Check host_config object is passed to create_container.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
host_config = {'PublishAllPorts': True}
client = Mock()
client.api_version = '1.19'
client.create_host_config.return_value = host_config
client.create_container.return_value = {}
with patch.dict(dockerng_mod.__dict__,
{'__salt__': __salt__}):
with patch.dict(dockerng_mod.__context__,
{'docker.client': client}):
dockerng_mod.create('image', name='ctn', publish_all_ports=True)
client.create_container.assert_called_once_with(
host_config=host_config,
image='image',
name='ctn')
if __name__ == '__main__':
from integration import run_tests

View file

@ -23,6 +23,7 @@ from salt.states import boto_elb
boto_elb.__salt__ = {}
boto_elb.__opts__ = {}
boto_elb.__states__ = {}
@skipIf(NO_MOCK, NO_MOCK_REASON)
@ -59,9 +60,7 @@ class BotoElbTestCase(TestCase):
mock_true_bool = MagicMock(return_value=True)
mock_attributes = MagicMock(return_value=attrs)
mock_health_check = MagicMock(return_value=health_check)
mock_ret = MagicMock(return_value={'myelb': ret1})
mock = MagicMock(return_value={})
with patch.dict(boto_elb.__salt__,
{'config.option': mock,
'boto_elb.exists': mock_false_bool,
@ -78,61 +77,56 @@ class BotoElbTestCase(TestCase):
self.assertFalse(ret['result'])
mock = MagicMock(return_value={})
mock_ret = MagicMock(return_value={'myelb': ret1})
with patch.dict(boto_elb.__salt__,
{'config.option': mock,
'boto_elb.exists': mock_false_bool,
'boto_elb.create': mock_true_bool,
'boto_elb.get_attributes': mock_attributes,
'boto_elb.get_health_check': mock_health_check,
'boto_elb.get_elb_config': mock,
'state.single': mock_ret}):
'boto_elb.get_elb_config': mock}):
with patch.dict(boto_elb.__opts__, {'test': False}):
ret = boto_elb.present(
name,
listeners,
availability_zones=avail_zones,
health_check=health_check,
alarms=alarms
)
self.assertTrue(boto_elb.__salt__['boto_elb.exists'].called)
self.assertTrue(boto_elb.__salt__['boto_elb.create'].called)
self.assertTrue(boto_elb.__salt__['state.single'].called)
self.assertFalse(
boto_elb.__salt__['boto_elb.get_attributes'].called
)
self.assertTrue(
boto_elb.__salt__['boto_elb.get_health_check'].called
)
self.assertIn('ELB myelb created.', ret['comment'])
self.assertTrue(ret['result'])
with patch.dict(boto_elb.__states__, {'boto_cloudwatch_alarm.present': MagicMock(return_value=ret1)}):
ret = boto_elb.present(
name,
listeners,
availability_zones=avail_zones,
health_check=health_check,
alarms=alarms
)
self.assertTrue(boto_elb.__salt__['boto_elb.exists'].called)
self.assertTrue(boto_elb.__salt__['boto_elb.create'].called)
self.assertTrue(boto_elb.__states__['boto_cloudwatch_alarm.present'].called)
self.assertFalse(
boto_elb.__salt__['boto_elb.get_attributes'].called
)
self.assertTrue(
boto_elb.__salt__['boto_elb.get_health_check'].called
)
self.assertIn('ELB myelb created.', ret['comment'])
self.assertTrue(ret['result'])
mock = MagicMock(return_value={})
mock_elb = MagicMock(return_value={'dns_name': 'myelb.amazon.com'})
mock_ret = MagicMock(return_value={'myelb': ret1})
with patch.dict(boto_elb.__salt__,
{'config.option': mock,
'boto_elb.exists': mock_false_bool,
'boto_elb.create': mock_true_bool,
'boto_elb.get_attributes': mock_attributes,
'boto_elb.get_health_check': mock_health_check,
'boto_elb.get_elb_config': mock_elb,
'state.single': mock_ret}):
'boto_elb.get_elb_config': mock_elb}):
with patch.dict(boto_elb.__opts__, {'test': False}):
ret = boto_elb.present(
name,
listeners,
availability_zones=avail_zones,
health_check=health_check,
cnames=cnames
)
self.assertTrue(boto_elb.__salt__['state.single'].called)
cname_call = boto_elb.__salt__['state.single'].mock_calls[0]
self.assertEqual(
cname_call[1][0],
'boto_route53.present'
)
self.assertTrue(ret['result'])
with patch.dict(boto_elb.__states__, {'boto_route53.present': MagicMock(return_value=ret1)}):
ret = boto_elb.present(
name,
listeners,
availability_zones=avail_zones,
health_check=health_check,
cnames=cnames
)
mock_changes = {'new': {'elb': 'myelb'}, 'old': {'elb': None}}
self.assertTrue(boto_elb.__states__['boto_route53.present'].called)
self.assertEqual(mock_changes, ret['changes'])
self.assertTrue(ret['result'])
# 'register_instances' function tests: 1

View file

@ -19,7 +19,7 @@ import salt.transport.client
import salt.exceptions
# Import Salt Testing libs
from salttesting import TestCase
from salttesting import TestCase, skipIf
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../')
import integration
@ -178,6 +178,7 @@ class BaseTCPPubCase(AsyncTestCase):
raise Exception('FDs still attached to the IOLoop: {0}'.format(failures))
@skipIf(True, 'Skip until we can devote time to fix this test')
class AsyncPubChannelTest(BaseTCPPubCase, PubChannelMixin):
'''
Tests around the publish system

View file

@ -24,7 +24,7 @@ import salt.transport.client
import salt.exceptions
# Import Salt Testing libs
from salttesting import TestCase
from salttesting import TestCase, skipIf
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../')
@ -183,6 +183,7 @@ class BaseZMQPubCase(AsyncTestCase):
raise Exception('FDs still attached to the IOLoop: {0}'.format(failures))
@skipIf(True, 'Skip until we can devote time to fix this test')
class AsyncPubChannelTest(BaseZMQPubCase, PubChannelMixin):
'''
Tests around the publish system