Merge pull request #37956 from rallytime/merge-develop

[develop] Merge forward from 2016.11 to develop
This commit is contained in:
Nicole Thomas 2016-11-29 13:52:38 -07:00 committed by GitHub
commit 2723188c6f
30 changed files with 224 additions and 84 deletions

View File

@ -494,7 +494,7 @@ Aliasing Functions
Sometimes one wishes to use a function name that would shadow a python built-in.
A common example would be ``set()``. To support this, append an underscore to
the function defintion, ``def set_():``, and use the ``__func_alias__`` feature
the function definition, ``def set_():``, and use the ``__func_alias__`` feature
to provide an alias to the function.
``__func_alias__`` is a dictionary where each key is the name of a function in

View File

@ -38,7 +38,7 @@ Changes:
- **PR** `#27516`_: (*basepi*) [2015.5] Merge forward from 2014.7 to 2015.5
- **PR** `#27472`_: (*cachedout*) Change recommeded schema for data field in mysql event table
- **PR** `#27472`_: (*cachedout*) Change recommended schema for data field in mysql event table
- **PR** `#27468`_: (*cachedout*) Fix 27351

View File

@ -1,15 +1,7 @@
:orphan:
==============================================
Salt 2016.11.0 Release Notes - Codename Carbon
==============================================
Release Candidate
=================
See :ref:`Installing/Testing a Salt Release Candidate <release-candidate>` for instructions to install the
latest release candidate.
New Features
============
@ -24,7 +16,7 @@ commands to be run inside docker containers as well. This makes
container introspection simple and powerful. See the tutorial on using
this new feature here:
#TODO: Add link to docker sls tutorial
See :ref:`Salt in Docker Containers <docker-sls>`.
Advanced Ceph Control
---------------------

View File

@ -290,7 +290,7 @@ Thorium - Provisional New Reactor
---------------------------------
The 2016.3 release introduces the new Thorium Reactor. This reactor is an
experimental new feature that implements a flow programing interface using
experimental new feature that implements a flow programming interface using
the salt state system as the engine. This means that the Thorium reactor uses
a classic state tree approach to create a reactor that can aggregate event
data from multiple sources and make aggregate decisions about executing

View File

@ -18,7 +18,7 @@ standard ``salt`` commands.
- Python is required on the remote system (unless using the ``-r`` option to send raw ssh commands)
- On many systems, the ``salt-ssh`` executable will be in its own package, usually named
``salt-ssh``
- The Salt SSH system does not supercede the standard Salt communication
- The Salt SSH system does not supersede the standard Salt communication
systems, it simply offers an SSH-based alternative that does not require
ZeroMQ and a remote agent. Be aware that since all communication with Salt SSH is
executed via SSH it is substantially slower than standard Salt with ZeroMQ.
@ -184,7 +184,7 @@ Salt SSH with a regular user you have to modify some paths or you will get
"Permission denied" messages. You have to modify two parameters: ``pki_dir``
and ``cachedir``. Those should point to a full path writable for the user.
It's recommed not to modify /etc/salt for this purpose. Create a private copy
It's recommended not to modify /etc/salt for this purpose. Create a private copy
of /etc/salt for the user and run the command with ``-c /new/config/path``.
Define CLI Options with Saltfile

View File

@ -1,3 +1,6 @@
.. _docker-sls:
=====================================================
Running Salt States and Commands in Docker Containers
=====================================================

View File

@ -256,7 +256,7 @@ def create(name, allocated_storage, db_instance_class, engine,
raise SaltInvocationError('master_username is required')
if not master_user_password:
raise SaltInvocationError('master_user_password is required')
if availability_zone and MultiAZ:
if availability_zone and multi_az:
raise SaltInvocationError('availability_zone and multi_az are mutually'
' exclusive arguments.')
if wait_status:
@ -529,6 +529,10 @@ def describe(name, tags=None, region=None, key=None, keyid=None,
return {'results': bool(conn)}
rds = conn.describe_db_instances(DBInstanceIdentifier=name)
rds = [
i for i in rds.get('DBInstances', [])
if i.get('DBInstanceIdentifier') == name
].pop(0)
if rds:
keys = ('DBInstanceIdentifier', 'DBInstanceClass', 'Engine',
@ -549,6 +553,8 @@ def describe(name, tags=None, region=None, key=None, keyid=None,
return {'rds': None}
except ClientError as e:
return {'error': salt.utils.boto3.get_error(e)}
except IndexError:
return {'rds': None}
def get_endpoint(name, tags=None, region=None, key=None, keyid=None,

View File

@ -165,7 +165,12 @@ def update(zone, name, ttl, rdtype, data, nameserver='127.0.0.1', timeout=5,
salt ns1 ddns.update example.com host1 60 A 10.0.0.1
'''
name = str(name)
fqdn = '{0}.{1}'.format(name, zone)
if name[-1:] == '.':
fqdn = name
else:
fqdn = '{0}.{1}'.format(name, zone)
request = dns.message.make_query(fqdn, rdtype)
answer = dns.query.udp(request, nameserver, timeout, port)
@ -211,9 +216,13 @@ def delete(zone, name, rdtype=None, data=None, nameserver='127.0.0.1',
salt ns1 ddns.delete example.com host1 A
'''
name = str(name)
fqdn = '{0}.{1}'.format(name, zone)
request = dns.message.make_query(fqdn, (rdtype or 'ANY'))
if name[-1:] == '.':
fqdn = name
else:
fqdn = '{0}.{1}'.format(name, zone)
request = dns.message.make_query(fqdn, (rdtype or 'ANY'))
answer = dns.query.udp(request, nameserver, timeout, port)
if not answer.answer:
return None

View File

@ -73,7 +73,11 @@ For example:
username: foo
Reauth is an optional parameter that forces the docker login to reauthorize using
the credentials passed in the pillar data. Defaults to false. For example:
the credentials passed in the pillar data. Defaults to false.
.. versionadded:: 2016.3.5,2016.11.1
For example:
.. code-block:: yaml

View File

@ -257,7 +257,7 @@ def create_user(username, password, permissions, users=None):
.. code-block:: bash
salt dell drac.create_user [USERNAME] [PASSWORD] [PRIVELEGES]
salt dell drac.create_user [USERNAME] [PASSWORD] [PRIVILEGES]
salt dell drac.create_user diana secret login,test_alerts,clear_logs
DRAC Privileges
@ -320,7 +320,7 @@ def set_permissions(username, permissions, uid=None):
.. code-block:: bash
salt dell drac.set_permissions [USERNAME] [PRIVELEGES] [USER INDEX - optional]
salt dell drac.set_permissions [USERNAME] [PRIVILEGES] [USER INDEX - optional]
salt dell drac.set_permissions diana login,test_alerts,clear_logs 4
DRAC Privileges

View File

@ -548,7 +548,7 @@ def create_user(username, password, permissions,
.. code-block:: bash
salt dell dracr.create_user [USERNAME] [PASSWORD] [PRIVELEGES]
salt dell dracr.create_user [USERNAME] [PASSWORD] [PRIVILEGES]
salt dell dracr.create_user diana secret login,test_alerts,clear_logs
DRAC Privileges
@ -616,7 +616,7 @@ def set_permissions(username, permissions,
.. code-block:: bash
salt dell dracr.set_permissions [USERNAME] [PRIVELEGES]
salt dell dracr.set_permissions [USERNAME] [PRIVILEGES]
[USER INDEX - optional]
salt dell dracr.set_permissions diana login,test_alerts,clear_logs 4

View File

@ -245,6 +245,8 @@ def dump(device, args=None):
elif line.startswith('Group') and not line.startswith('Group descriptor size'):
mode = 'blocks'
else:
if len(comps) < 2:
continue
ret['attributes'][comps[0]] = comps[1].strip()
if mode == 'blocks':

View File

@ -704,7 +704,7 @@ def set_bootdev(bootdev='default', persist=False, uefiboot=False, **kwargs):
this
:param uefiboot: If true, request UEFI boot explicitly. Strictly
speaking, the spec sugests that if not set, the system
speaking, the spec suggests that if not set, the system
should BIOS boot and offers no "don't care" option.
In practice, this flag not being set does not preclude
UEFI boot on any system I've encountered.

View File

@ -78,6 +78,30 @@ Or do something interesting with grains like:
'{{ opts['id'] }}':
- {{ role }}
{%- endif %}
Multi-line text items like certificates require a bit of extra work. You have to strip the new lines
and replace them with '/n' characters. Certificates specifically require some leading white space when
calling nacl.enc so that the '--' in the first line (commonly -----BEGIN CERTIFICATE-----) doesn't get
interpreted as an argument to nacl.enc. For instance if you have a certificate file that lives in cert.crt:
.. code-block:: bash
cert=$(cat cert.crt |awk '{printf "%s\\n",$0} END {print ""}'); salt-run nacl.enc " $cert"
Pillar data should look the same, even though the secret will be quite long. However, when calling
multiline encrypted secrets from pillar in a state, use the following format to avoid issues with /n
creating extra whitespace at the beginning of each line in the cert file:
.. code-block:: yaml
secret.txt:
file.managed:
- template: jinja
- user: user
- group: group
- mode: 700
- contents: "{{- salt['pillar.get']('secret') }}"
The '{{-' will tell jinja to strip the whitespace from the beginning of each of the new lines.
'''
from __future__ import absolute_import

View File

@ -601,6 +601,9 @@ def info(*packages, **attr):
ret = dict()
for pkg_data in reversed(sorted(_ret, cmp=lambda a_vrs, b_vrs: version_cmp(a_vrs['edition'], b_vrs['edition']))):
pkg_name = pkg_data.pop('name')
# Filter out GPG public keys packages
if pkg_name.startswith('gpg-pubkey'):
continue
if pkg_name not in ret:
ret[pkg_name] = pkg_data.copy()
del ret[pkg_name]['edition']

View File

@ -266,16 +266,36 @@ def list_semod():
.. versionadded:: 2016.3.0
'''
mdata = __salt__['cmd.run']('semodule -l').splitlines()
ret = {}
for line in mdata[1:]:
if not line.strip():
continue
comps = line.split()
if len(comps) == 3:
ret[comps[0]] = {'Enabled': False,
'Version': comps[1]}
else:
ret[comps[0]] = {'Enabled': True,
'Version': comps[1]}
helptext = __salt__['cmd.run']('semodule -h').splitlines()
semodule_version = ''
for line in helptext:
if line.strip().startswith('full'):
semodule_version = 'new'
if semodule_version == 'new':
mdata = __salt__['cmd.run']('semodule -lfull').splitlines()
ret = {}
for line in mdata:
if not line.strip():
continue
comps = line.split()
if len(comps) == 4:
ret[comps[1]] = {'Enabled': False,
'Version': None}
else:
ret[comps[1]] = {'Enabled': True,
'Version': None}
else:
mdata = __salt__['cmd.run']('semodule -l').splitlines()
ret = {}
for line in mdata:
if not line.strip():
continue
comps = line.split()
if len(comps) == 3:
ret[comps[0]] = {'Enabled': False,
'Version': comps[1]}
else:
ret[comps[0]] = {'Enabled': True,
'Version': comps[1]}
return ret

View File

@ -551,7 +551,7 @@ def list_updates(verbose=False, fields=None, skips=None, retries=5, categories=N
fields
Return a list of specific fields for each update. The optional
values here are those at the root level of the verbose list. This
is superceded by the verbose option.
is superseded by the verbose option.
retries
Number of retries to make before giving up. This is total, not per

View File

@ -1089,7 +1089,7 @@ def create_certificate(
- x509.sign_remote_certificate
subject properties:
Any of the values below can be incldued to set subject properties
Any of the values below can be included to set subject properties
Any other subject properties supported by OpenSSL should also work.
C:

View File

@ -264,7 +264,7 @@ documentation, but in short:
<salt.wheel.key>` exposes similar functionality as the ``salt-key`` CLI
command.
Most clients have variants like synchronous or asyncronous execution as well as
Most clients have variants like synchronous or asynchronous execution as well as
others like batch execution. See the :ref:`full list of client interfaces
<netapi-clients>`.

View File

@ -265,7 +265,7 @@ def change(name, context=None, changes=None, lens=None,
filename = re.sub('^/files|/$', '', context)
if __opts__['test']:
ret['result'] = None
ret['result'] = True
ret['comment'] = 'Executing commands'
if context:
ret['comment'] += ' in file "{0}":\n'.format(context)

View File

@ -309,7 +309,7 @@ def present(name,
'changes': {}
}
r = __salt__['boto_rds.exists'](name, region, key, keyid, profile)
r = __salt__['boto_rds.exists'](name, tags, region, key, keyid, profile)
if not r.get('exists'):
if __opts__['test']:
@ -350,7 +350,7 @@ def present(name,
ret['comment'] = 'Failed to create RDS instance {0}.'.format(r['error']['message'])
return ret
_describe = __salt__['boto_rds.describe'](name, region, key, keyid, profile)
_describe = __salt__['boto_rds.describe'](name, tags, region, key, keyid, profile)
ret['changes']['old'] = {'instance': None}
ret['changes']['new'] = _describe
ret['comment'] = 'RDS instance {0} created.'.format(name)
@ -488,36 +488,33 @@ def subnet_group_present(name, description, subnet_ids=None, subnet_names=None,
return ret
subnet_ids.append(r['id'])
for i in subnet_ids:
r = __salt__['boto_rds.create_subnet_group'](name=name,
exists = __salt__['boto_rds.subnet_group_exists'](name=name, tags=tags, region=region, key=key,
keyid=keyid, profile=profile)
if not exists:
if __opts__['test']:
ret['comment'] = 'Subnet group {0} is set to be created.'.format(name)
ret['result'] = None
return ret
if not r.get('created'):
ret['result'] = False
ret['comment'] = 'Failed to create {0} subnet group.'.format(r['error']['message'])
return ret
created = __salt__['boto_rds.create_subnet_group'](name=name,
description=description,
subnet_ids=subnet_ids,
tags=tags, region=region,
key=key, keyid=keyid,
profile=profile)
if not r.get('exists'):
if __opts__['test']:
ret['comment'] = 'Subnet group {0} is set to be created.'.format(name)
ret['result'] = None
return ret
if not r.get('created'):
ret['result'] = False
ret['comment'] = 'Failed to create {0} subnet group.'.format(r['error']['message'])
return ret
_describe = __salt__['boto_rds.describe']('subnet',
name=i,
region=region,
key=key,
keyid=keyid,
profile=profile)
ret['changes']['old'] = None
ret['changes']['new'] = _describe
ret['comment'] = 'Subnet {0} created.'.format(name)
else:
ret['comment'] = 'Subnet {0} present.'.format(name)
if not created:
ret['result'] = False
ret['comment'] = 'Failed to create {0} subnet group.'.format(name)
return ret
ret['changes']['old'] = None
ret['changes']['new'] = name
ret['comment'] = 'Subnet {0} created.'.format(name)
else:
ret['comment'] = 'Subnet {0} present.'.format(name)
return ret

View File

@ -31,8 +31,9 @@ def install(*args, **kwargs):
installed(*args, **kwargs)
def installed(name, version=None, source=None, force=False, install_args=None,
override_args=False, force_x86=False, package_args=None):
def installed(name, version=None, source=None, force=False, pre_versions=False,
install_args=None, override_args=False, force_x86=False,
package_args=None):
'''
Installs a package if not already installed
@ -50,6 +51,9 @@ def installed(name, version=None, source=None, force=False, install_args=None,
force
Reinstall the current version of an existing package. Default is false.
pre_versions
Include pre-release packages. Default is False.
install_args
A list of install arguments you want to pass to the installation
process i.e product key or feature list
@ -108,13 +112,11 @@ def installed(name, version=None, source=None, force=False, install_args=None,
return ret
# Install the package
ret['changes'] = {name: __salt__['chocolatey.install'](name, version,
source,
force,
install_args,
override_args,
force_x86,
package_args)}
ret['changes'] = {name: __salt__['chocolatey.install'](
name=name, version=version, source=source, force=force,
pre_versions=pre_versions, install_args=install_args,
override_args=override_args, force_x86=force_x86,
package_args=package_args)}
if 'Running chocolatey failed' not in ret['changes']:
ret['result'] = True

View File

@ -37,8 +37,9 @@ def present(name, zone, ttl, data, rdtype='A', **kwargs):
name
The host portion of the DNS record, e.g., 'webserver'. Name and zone
are concatenated when the entry is created, so make sure that
information is not duplicated in these two arguments.
are concatenated when the entry is created unless name includes a
trailing dot, so make sure that information is not duplicated in these
two arguments.
zone
The zone to check/update
@ -95,8 +96,9 @@ def absent(name, zone, data=None, rdtype=None, **kwargs):
name
The host portion of the DNS record, e.g., 'webserver'. Name and zone
are concatenated when the entry is created, so make sure that
information is not duplicated in these two arguments.
are concatenated when the entry is created unless name includes a
trailing dot, so make sure that information is not duplicated in these
two arguments.
zone
The zone to check

View File

@ -111,8 +111,8 @@ def _compute_diff(configured, expected):
remove_usernames = configured_users - expected_users
common_usernames = expected_users & configured_users
add = {username: expected.get(username) for username in add_usernames} # pylint: disable=minimum-python-version
remove = {username: configured.get(username) for username in remove_usernames} # pylint: disable=minimum-python-version
add = dict((username, expected.get(username)) for (username, _) in add_usernames)
remove = dict((username, configured.get(username)) for (username, _) in remove_usernames)
update = {}
for username in common_usernames:

View File

@ -202,7 +202,8 @@ def module(name, module_state='Enabled', version='any'):
installed_version = modules[name]['Version']
if not installed_version == version:
ret['comment'] = 'Module version is {0} and does not match ' \
'the desired version of {1}'.format(installed_version, version)
'the desired version of {1} or you are ' \
'using semodule >= 2.4'.format(installed_version, version)
ret['result'] = False
return ret
current_module_state = _refine_module_state(modules[name]['Enabled'])
@ -211,7 +212,7 @@ def module(name, module_state='Enabled', version='any'):
return ret
if __opts__['test']:
ret['result'] = None
ret['comment'] = 'Module {0} is set to be togggled to {1}'.format(
ret['comment'] = 'Module {0} is set to be toggled to {1}'.format(
name, module_state)
return ret

View File

@ -102,6 +102,10 @@ def latest(name,
('{0} doesn\'t exist and is set to be checked out.').format(target))
svn_cmd = 'svn.diff'
opts += ('-r', 'HEAD')
if trust:
opts += ('--trust-server-cert',)
out = __salt__[svn_cmd](cwd, target, user, username, password, *opts)
return _neutral_test(
ret,

View File

@ -4,5 +4,7 @@
{%endif%}{% if gateway %}GATEWAY={{gateway}}
{%endif%}{% if gatewaydev %}GATEWAYDEV={{gatewaydev}}
{%endif%}{% if nisdomain %}NISDOMAIN={{nisdomain}}
{%endif%}{% if networkdelay %}NETWORKDELAY={{networkdelay}}
{%endif%}{% if devtimeout %}DEVTIMEOUT={{devtimeout}}
{%endif%}{% if nozeroconf %}NOZEROCONF={{nozeroconf}}
{%endif%}

64
salt/utils/sanitizers.py Normal file
View File

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 SUSE LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Import Python libs
from __future__ import absolute_import
import re
import os.path
# Import Salt libs
from salt.ext.six import text_type as text
from salt.exceptions import CommandExecutionError
class InputSanitizer(object):
@staticmethod
def trim(value):
'''
Raise an exception if value is empty. Otherwise strip it down.
:param value:
:return:
'''
value = (value or '').strip()
if not value:
raise CommandExecutionError("Empty value during sanitation")
return text(value)
@staticmethod
def filename(value):
'''
Remove everything that would affect paths in the filename
:param value:
:return:
'''
return re.sub('[^a-zA-Z0-9.-_ ]', '', os.path.basename(InputSanitizer.trim(value)))
@staticmethod
def hostname(value):
'''
Clean value for RFC1123.
:param value:
:return:
'''
return re.sub(r'[^a-zA-Z0-9.-]', '', InputSanitizer.trim(value))
id = hostname
clean = InputSanitizer()

View File

@ -37,6 +37,8 @@ import logging
from salt.key import get_key
import salt.crypt
import salt.utils
from salt.utils.sanitizers import clean
__func_alias__ = {
'list_': 'list',
@ -318,6 +320,8 @@ def gen(id_=None, keysize=2048):
'''
if id_ is None:
id_ = hashlib.sha512(os.urandom(32)).hexdigest()
else:
id_ = clean.filename(id_)
ret = {'priv': '',
'pub': ''}
priv = salt.crypt.gen_keys(__opts__['pki_dir'], id_, keysize)
@ -371,6 +375,7 @@ def gen_accept(id_, keysize=2048, force=False):
>>> wheel.cmd('key.list', ['accepted'])
{'minions': ['foo', 'minion1', 'minion2', 'minion3']}
'''
id_ = clean.id(id_)
ret = gen(id_, keysize)
acc_path = os.path.join(__opts__['pki_dir'], 'minions', id_)
if os.path.isfile(acc_path) and not force:

View File

@ -84,7 +84,7 @@ class AugeasTestCase(TestCase):
comt = ('Executing commands in file "/files/etc/services":\n'
'ins service-name after service-name[last()]'
'\nset service-name[last()] zabbix-agent')
self.ret.update({'comment': comt, 'result': None})
self.ret.update({'comment': comt, 'result': True})
with patch.dict(augeas.__opts__, {'test': True}):
self.assertDictEqual(