diff --git a/conf/minion b/conf/minion index 7ce6610b3b..82c5001d81 100644 --- a/conf/minion +++ b/conf/minion @@ -402,7 +402,8 @@ # Set the file client. The client defaults to looking on the master server for # files, but can be directed to look at the local file directory setting -# defined below by setting it to local. +# defined below by setting it to "local". Setting a local file_client runs the +# minion in masterless mode. #file_client: remote # The file directory works on environments passed to the minion, each environment diff --git a/doc/conf.py b/doc/conf.py index 37d325ef10..c0c06c493e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -164,7 +164,7 @@ project = 'Salt' copyright = '2015 SaltStack, Inc.' version = salt.version.__version__ -latest_release = '2015.8.1' # latest release +latest_release = '2015.8.1' # latest release previous_release = '2015.5.6' # latest release from previous branch previous_release_dir = '2015.5' # path on web server for previous branch build_type = 'latest' # latest, previous, develop, inactive diff --git a/doc/faq.rst b/doc/faq.rst index 32f75331f9..15370f9f5a 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -307,7 +307,7 @@ https://github.com/saltstack-formulas/salt-formula .. _faq-grain-security: Is Targeting using Grain Data Secure? -===================================== +------------------------------------- Because grains can be set by users that have access to the minion configuration files on the local system, grains are considered less secure than other diff --git a/doc/topics/pillar/index.rst b/doc/topics/pillar/index.rst index 54cab0b1ae..9d535d9477 100644 --- a/doc/topics/pillar/index.rst +++ b/doc/topics/pillar/index.rst @@ -85,6 +85,10 @@ by their ``os`` grain: company: Foo Industries +.. important:: + See :ref:`Is Targeting using Grain Data Secure? ` for + important security information. + The above pillar sets two key/value pairs. If a minion is running RedHat, then the ``apache`` key is set to ``httpd`` and the ``git`` key is set to the value of ``git``. If the minion is running Debian, those values are changed to diff --git a/doc/topics/targeting/grains.rst b/doc/topics/targeting/grains.rst index f3e1d58497..0be990e3c0 100644 --- a/doc/topics/targeting/grains.rst +++ b/doc/topics/targeting/grains.rst @@ -22,6 +22,10 @@ to a custom grain, grain data is refreshed. Grains resolve to lowercase letters. For example, ``FOO``, and ``foo`` target the same grain. +.. important:: + See :ref:`Is Targeting using Grain Data Secure? ` for + important security information. + Match all CentOS minions: .. code-block:: bash diff --git a/salt/cloud/cli.py b/salt/cloud/cli.py index 9cc7af094e..86ea32fb7c 100644 --- a/salt/cloud/cli.py +++ b/salt/cloud/cli.py @@ -90,6 +90,8 @@ class SaltCloud(parsers.SaltCloudParser): log.info('salt-cloud starting') try: mapper = salt.cloud.Map(self.config) + except SaltCloudSystemExit as exc: + self.handle_exception(exc.args, exc) except SaltCloudException as exc: msg = 'There was an error generating the mapper.' self.handle_exception(msg, exc) @@ -374,7 +376,7 @@ class SaltCloud(parsers.SaltCloudParser): def handle_exception(self, msg, exc): if isinstance(exc, SaltCloudException): - # It's a know exception an we know own to handle it + # It's a known exception and we know how to handle it if isinstance(exc, SaltCloudSystemExit): # This is a salt cloud system exit if exc.exit_code > 0: diff --git a/salt/cloud/clouds/ec2.py b/salt/cloud/clouds/ec2.py index 88892b403c..8f0b261947 100644 --- a/salt/cloud/clouds/ec2.py +++ b/salt/cloud/clouds/ec2.py @@ -134,19 +134,17 @@ except ImportError: log = logging.getLogger(__name__) SIZE_MAP = { - 'Micro Instance': 't1.micro', - 'Small Instance': 'm1.small', - 'Medium Instance': 'm1.medium', - 'Large Instance': 'm1.large', - 'Extra Large Instance': 'm1.xlarge', - 'High-CPU Medium Instance': 'c1.medium', - 'High-CPU Extra Large Instance': 'c1.xlarge', - 'High-Memory Extra Large Instance': 'm2.xlarge', - 'High-Memory Double Extra Large Instance': 'm2.2xlarge', - 'High-Memory Quadruple Extra Large Instance': 'm2.4xlarge', - 'Cluster GPU Quadruple Extra Large Instance': 'cg1.4xlarge', - 'Cluster Compute Quadruple Extra Large Instance': 'cc1.4xlarge', - 'Cluster Compute Eight Extra Large Instance': 'cc2.8xlarge', + 'Micro Instance': 't2.micro', + 'Small Instance': 't2.small', + 'Medium Instance': 'm3.medium', + 'Large Instance': 'm4.large', + 'Extra Large Instance': 'm4.xlarge', + 'High-CPU Medium Instance': 'c4.large', + 'High-CPU Extra Large Instance': 'c4.xlarge', + 'High-Memory Extra Large Instance': 'r3.xlarge', + 'High-Memory Double Extra Large Instance': 'r3.2xlarge', + 'High-Memory Quadruple Extra Large Instance': 'r3.4xlarge', + 'Cluster GPU Quadruple Extra Large Instance': 'g2.8xlarge' } @@ -622,57 +620,93 @@ def avail_sizes(call=None): 'ram': '22.5 GiB' }, }, - 'High CPU': { - 'c1.xlarge': { - 'id': 'c1.xlarge', - 'cores': '8 (with 2.5 ECUs each)', - 'disk': '1680 GiB (4 x 420 GiB)', - 'ram': '8 GiB' + 'Compute Optimized': { + 'c4.large': { + 'id': 'c4.large', + 'cores': '2', + 'disk': 'EBS - 500 Mbps', + 'ram': '3.75 GiB' }, - 'c1.medium': { - 'id': 'c1.medium', - 'cores': '2 (with 2.5 ECUs each)', - 'disk': '340 GiB (1 x 340 GiB)', - 'ram': '1.7 GiB' + 'c4.xlarge': { + 'id': 'c4.xlarge', + 'cores': '4', + 'disk': 'EBS - 750 Mbps', + 'ram': '7.5 GiB' + }, + 'c4.2xlarge': { + 'id': 'c4.2xlarge', + 'cores': '8', + 'disk': 'EBS - 1000 Mbps', + 'ram': '15 GiB' + }, + 'c4.4xlarge': { + 'id': 'c4.4xlarge', + 'cores': '16', + 'disk': 'EBS - 2000 Mbps', + 'ram': '30 GiB' + }, + 'c4.8xlarge': { + 'id': 'c4.8xlarge', + 'cores': '36', + 'disk': 'EBS - 4000 Mbps', + 'ram': '60 GiB' }, 'c3.large': { 'id': 'c3.large', - 'cores': '2 (with 3.5 ECUs each)', + 'cores': '2', 'disk': '32 GiB (2 x 16 GiB SSD)', 'ram': '3.75 GiB' }, 'c3.xlarge': { 'id': 'c3.xlarge', - 'cores': '4 (with 3.5 ECUs each)', + 'cores': '4', 'disk': '80 GiB (2 x 40 GiB SSD)', 'ram': '7.5 GiB' }, 'c3.2xlarge': { 'id': 'c3.2xlarge', - 'cores': '8 (with 3.5 ECUs each)', + 'cores': '8', 'disk': '160 GiB (2 x 80 GiB SSD)', 'ram': '15 GiB' }, 'c3.4xlarge': { 'id': 'c3.4xlarge', - 'cores': '16 (with 3.5 ECUs each)', - 'disk': '320 GiB (2 x 80 GiB SSD)', + 'cores': '16', + 'disk': '320 GiB (2 x 160 GiB SSD)', 'ram': '30 GiB' }, 'c3.8xlarge': { 'id': 'c3.8xlarge', - 'cores': '32 (with 3.5 ECUs each)', - 'disk': '320 GiB (2 x 160 GiB SSD)', + 'cores': '32', + 'disk': '640 GiB (2 x 320 GiB SSD)', 'ram': '60 GiB' } }, 'High I/O': { - 'hi1.4xlarge': { - 'id': 'hi1.4xlarge', - 'cores': '8 (with 4.37 ECUs each)', - 'disk': '2 TiB', - 'ram': '60.5 GiB' + 'i2.xlarge': { + 'id': 'i2.xlarge', + 'cores': '4', + 'disk': 'SSD (1 x 800 GiB)', + 'ram': '30.5 GiB' }, + 'i2.2xlarge': { + 'id': 'i2.2xlarge', + 'cores': '8', + 'disk': 'SSD (2 x 800 GiB)', + 'ram': '61 GiB' + }, + 'i2.4xlarge': { + 'id': 'i2.4xlarge', + 'cores': '16', + 'disk': 'SSD (4 x 800 GiB)', + 'ram': '122 GiB' + }, + 'i2.8xlarge': { + 'id': 'i2.8xlarge', + 'cores': '32', + 'disk': 'SSD (8 x 800 GiB)', + 'ram': '244 GiB' + } }, 'High Memory': { 'm2.2xlarge': { @@ -740,51 +774,85 @@ def avail_sizes(call=None): 'ram': '117 GiB' }, }, - 'Micro': { - 't1.micro': { - 'id': 't1.micro', + 'General Purpose': { + 't2.micro': { + 'id': 't2.micro', 'cores': '1', 'disk': 'EBS', - 'ram': '615 MiB' + 'ram': '1 GiB' }, - }, - 'Standard': { - 'm1.xlarge': { - 'id': 'm1.xlarge', - 'cores': '4 (with 2 ECUs each)', - 'disk': '1680 GB (4 x 420 GiB)', - 'ram': '15 GiB' - }, - 'm1.large': { - 'id': 'm1.large', - 'cores': '2 (with 2 ECUs each)', - 'disk': '840 GiB (2 x 420 GiB)', - 'ram': '7.5 GiB' - }, - 'm1.medium': { - 'id': 'm1.medium', + 't2.small': { + 'id': 't2.small', 'cores': '1', - 'disk': '400 GiB', + 'disk': 'EBS', + 'ram': '2 GiB' + }, + 't2.medium': { + 'id': 't2.medium', + 'cores': '2', + 'disk': 'EBS', + 'ram': '4 GiB' + }, + 't2.large': { + 'id': 't2.large', + 'cores': '2', + 'disk': 'EBS', + 'ram': '8 GiB' + }, + 'm4.large': { + 'id': 'm4.large', + 'cores': '2', + 'disk': 'EBS - 450 Mbps', + 'ram': '8 GiB' + }, + 'm4.xlarge': { + 'id': 'm4.xlarge', + 'cores': '4', + 'disk': 'EBS - 750 Mbps', + 'ram': '16 GiB' + }, + 'm4.2xlarge': { + 'id': 'm4.2xlarge', + 'cores': '8', + 'disk': 'EBS - 1000 Mbps', + 'ram': '32 GiB' + }, + 'm4.4xlarge': { + 'id': 'm4.4xlarge', + 'cores': '16', + 'disk': 'EBS - 2000 Mbps', + 'ram': '64 GiB' + }, + 'm4.10xlarge': { + 'id': 'm4.10xlarge', + 'cores': '40', + 'disk': 'EBS - 4000 Mbps', + 'ram': '160 GiB' + }, + 'm3.medium': { + 'id': 'm3.medium', + 'cores': '1', + 'disk': 'SSD (1 x 4)', 'ram': '3.75 GiB' }, - 'm1.small': { - 'id': 'm1.small', - 'cores': '1', - 'disk': '150 GiB', - 'ram': '1.7 GiB' - }, - 'm3.2xlarge': { - 'id': 'm3.2xlarge', - 'cores': '8 (with 3.25 ECUs each)', - 'disk': 'EBS', - 'ram': '30 GiB' + 'm3.large': { + 'id': 'm3.large', + 'cores': '2', + 'disk': 'SSD (1 x 32)', + 'ram': '7.5 GiB' }, 'm3.xlarge': { 'id': 'm3.xlarge', - 'cores': '4 (with 3.25 ECUs each)', - 'disk': 'EBS', + 'cores': '4', + 'disk': 'SSD (2 x 40)', 'ram': '15 GiB' }, + 'm3.2xlarge': { + 'id': 'm3.2xlarge', + 'cores': '8', + 'disk': 'SSD (2 x 80)', + 'ram': '30 GiB' + }, } } return sizes diff --git a/salt/cloud/clouds/lxc.py b/salt/cloud/clouds/lxc.py index 3d26ee9935..725850ca3f 100644 --- a/salt/cloud/clouds/lxc.py +++ b/salt/cloud/clouds/lxc.py @@ -467,6 +467,18 @@ def create(vm_, call=None): __opts__['internal_lxc_profile'] = __opts__['profile'] del __opts__['profile'] + salt.utils.cloud.fire_event( + 'event', + 'created instance', + 'salt/cloud/{0}/created'.format(vm_['name']), + { + 'name': vm_['name'], + 'profile': vm_['profile'], + 'provider': vm_['driver'], + }, + transport=__opts__['transport'] + ) + return ret @@ -532,13 +544,10 @@ def get_configured_provider(vm_=None): {}).get(dalias, {}).get(driver, {}) # in all cases, verify that the linked saltmaster is alive. if data: - try: - ret = _salt('test.ping', salt_target=data['target']) - if not ret: - raise Exception('error') - return data - except Exception: + ret = _salt('test.ping', salt_target=data['target']) + if not ret: raise SaltCloudSystemExit( 'Configured provider {0} minion: {1} is unreachable'.format( __active_provider_name__, data['target'])) + return data return False diff --git a/salt/cloud/clouds/proxmox.py b/salt/cloud/clouds/proxmox.py index 6e65003d28..03d51c4e8b 100644 --- a/salt/cloud/clouds/proxmox.py +++ b/salt/cloud/clouds/proxmox.py @@ -123,10 +123,9 @@ def _authenticate(): 'password', get_configured_provider(), __opts__, search_global=False ) verify_ssl = config.get_cloud_config_value( - 'verify_ssl', get_configured_provider(), __opts__, search_global=False + 'verify_ssl', get_configured_provider(), __opts__, + default=True, search_global=False ) - if verify_ssl is None: - verify_ssl = True connect_data = {'username': username, 'password': passwd} full_url = 'https://{0}:8006/api2/json/access/ticket'.format(url) diff --git a/salt/fileclient.py b/salt/fileclient.py index 5b2406cb08..c3515b49db 100644 --- a/salt/fileclient.py +++ b/salt/fileclient.py @@ -10,6 +10,7 @@ import logging import hashlib import os import shutil +import ftplib # Import salt libs from salt.exceptions import ( @@ -572,6 +573,15 @@ class Client(object): return dest except Exception: raise MinionError('Could not fetch from {0}'.format(url)) + if url_data.scheme == 'ftp': + try: + ftp = ftplib.FTP(url_data.hostname) + ftp.login() + with salt.utils.fopen(dest, 'wb') as fp_: + ftp.retrbinary('RETR {0}'.format(url_data.path), fp_.write) + return dest + except Exception as exc: + raise MinionError('Could not retrieve {0} from FTP server. Exception: {1}'.format(url, exc)) if url_data.scheme == 'swift': try: diff --git a/salt/modules/hosts.py b/salt/modules/hosts.py index 049e868db8..dfb1318d66 100644 --- a/salt/modules/hosts.py +++ b/salt/modules/hosts.py @@ -159,15 +159,15 @@ def set_host(ip, alias): comps = tmpline.split() if comps[0] == ip: if not ovr: - lines[ind] = ip + '\t\t' + alias + '\n' + lines[ind] = ip + '\t\t' + alias + os.linesep ovr = True else: # remove other entries lines[ind] = '' if not ovr: # make sure there is a newline - if lines and not lines[-1].endswith(('\n', '\r')): - lines[-1] = '{0}\n'.format(lines[-1]) - line = ip + '\t\t' + alias + '\n' + if lines and not lines[-1].endswith(os.linesep): + lines[-1] += os.linesep + line = ip + '\t\t' + alias + os.linesep lines.append(line) with salt.utils.fopen(hfn, 'w+') as ofile: ofile.writelines(lines) @@ -206,7 +206,7 @@ def rm_host(ip, alias): lines[ind] = '' else: # Only an alias was removed - lines[ind] = '{0}\n'.format(newline) + lines[ind] = newline + os.linesep with salt.utils.fopen(hfn, 'w+') as ofile: ofile.writelines(lines) return True @@ -262,4 +262,4 @@ def _write_hosts(hosts): if line.strip(): # /etc/hosts needs to end with EOL so that some utils that read # it do not break - ofile.write('{0}\n'.format(line.strip())) + ofile.write(line.strip() + os.linesep) diff --git a/salt/modules/swift.py b/salt/modules/swift.py index 18e7556432..181a6f7d05 100644 --- a/salt/modules/swift.py +++ b/salt/modules/swift.py @@ -6,14 +6,16 @@ Author: Anthony Stanton Inspired by the S3 and Nova modules :depends: - swiftclient Python module -:configuration: This module is not usable until the user, password, tenant, and - auth URL are specified either in a pillar or in the minion's config file. +:configuration: This module is not usable until the user, tenant, auth URL, and password or auth_key + are specified either in a pillar or in the minion's config file. For example:: keystone.user: admin - keystone.password: verybadpass keystone.tenant: admin keystone.auth_url: 'http://127.0.0.1:5000/v2.0/' + keystone.password: verybadpass + # or + keystone.auth_key: 203802934809284k2j34lkj2l3kj43k If configuration for multiple OpenStack accounts is required, they can be set up as different configuration profiles: @@ -21,21 +23,27 @@ Inspired by the S3 and Nova modules openstack1: keystone.user: admin - keystone.password: verybadpass keystone.tenant: admin keystone.auth_url: 'http://127.0.0.1:5000/v2.0/' + keystone.password: verybadpass + # or + keystone.auth_key: 203802934809284k2j34lkj2l3kj43k openstack2: keystone.user: admin - keystone.password: verybadpass keystone.tenant: admin keystone.auth_url: 'http://127.0.0.2:5000/v2.0/' + keystone.password: verybadpass + # or + keystone.auth_key: 303802934809284k2j34lkj2l3kj43k With this configuration in place, any of the swift functions can make use of a configuration profile by declaring it explicitly. For example:: salt '*' swift.get mycontainer myfile /tmp/file profile=openstack1 + + NOTE: For Rackspace cloud files setting keystone.auth_version = 1 is recommended. ''' from __future__ import absolute_import @@ -67,26 +75,29 @@ def _auth(profile=None): if profile: credentials = __salt__['config.option'](profile) user = credentials['keystone.user'] - password = credentials['keystone.password'] + password = credentials.get('keystone.password', None) tenant = credentials['keystone.tenant'] auth_url = credentials['keystone.auth_url'] + auth_version = credentials.get('keystone.auth_version', 2) region_name = credentials.get('keystone.region_name', None) api_key = credentials.get('keystone.api_key', None) os_auth_system = credentials.get('keystone.os_auth_system', None) else: user = __salt__['config.option']('keystone.user') - password = __salt__['config.option']('keystone.password') + password = __salt__['config.option']('keystone.password', None) tenant = __salt__['config.option']('keystone.tenant') auth_url = __salt__['config.option']('keystone.auth_url') + auth_version = __salt__['config.option']('keystone.auth_version', 2) region_name = __salt__['config.option']('keystone.region_name') api_key = __salt__['config.option']('keystone.api_key') os_auth_system = __salt__['config.option']('keystone.os_auth_system') kwargs = { 'user': user, 'password': password, - 'api_key': api_key, + 'key': api_key, 'tenant_name': tenant, 'auth_url': auth_url, + 'auth_version': auth_version, 'region_name': region_name } diff --git a/salt/modules/win_path.py b/salt/modules/win_path.py index 921da62799..7e1601e225 100644 --- a/salt/modules/win_path.py +++ b/salt/modules/win_path.py @@ -75,11 +75,7 @@ def get_path(): ''' ret = __salt__['reg.read_value']('HKEY_LOCAL_MACHINE', 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', - 'PATH') - if isinstance(ret, dict): - ret = ret['vdata'].split(';') - if isinstance(ret, str): - ret = ret.split(';') + 'PATH')['vdata'].split(';') # Trim ending backslash return list(map(_normalize_dir, ret)) diff --git a/salt/states/pip_state.py b/salt/states/pip_state.py index becb7c75a3..47964067c3 100644 --- a/salt/states/pip_state.py +++ b/salt/states/pip_state.py @@ -704,7 +704,12 @@ def installed(name, trusted_host=trusted_host ) - if pip_install_call and (pip_install_call.get('retcode', 1) == 0): + # Check the retcode for success, but don't fail if using pip1 and the package is + # already present. Pip1 returns a retcode of 1 (instead of 0 for pip2) if you run + # "pip install" without any arguments. See issue #21845. + if pip_install_call and \ + (pip_install_call.get('retcode', 1) == 0 or pip_install_call.get('stdout', '').startswith( + 'You must give at least one requirement to install')): ret['result'] = True if requirements or editable: diff --git a/salt/states/saltmod.py b/salt/states/saltmod.py index bb73982b11..f243cdb445 100644 --- a/salt/states/saltmod.py +++ b/salt/states/saltmod.py @@ -243,8 +243,12 @@ def state( if mdata.get('failed', False): m_state = False else: - m_ret = mdata['ret'] - m_state = salt.utils.check_state_result(m_ret) + try: + m_ret = mdata['ret'] + except KeyError: + m_state = False + if not m_state: + m_state = salt.utils.check_state_result(m_ret) if not m_state: if minion not in fail_minions: