def test_version_comparison(self): examples = ( ('debian/0.11.1+ds-1-3-ga0afcbd', '0.11.1+ds-2'), ('v0.12.0-85-g2880105', 'v0.12.0-19-g767d4f9'), ('v0.17.0rc1-1-g52ebdfd', '0.17.0rc1'), ('v0.17.0', 'v0.17.0rc1') ) for v1, v2 in examples: self.assertTrue(SaltStackVersion.parse(v1) > v2) self.assertTrue(SaltStackVersion.parse(v2) < v1)
def test_version_comparison(self): examples = ( ('debian/0.11.1+ds-1-3-ga0afcbd', '0.11.1+ds-2'), ('v0.12.0-85-g2880105', 'v0.12.0-19-g767d4f9'), ('v0.17.0rc1-1-g52ebdfd', '0.17.0rc1'), ('v0.17.0', 'v0.17.0rc1'), ('Hydrogen', '0.17.0'), ('Helium', 'Hydrogen'), ('v2014.1.4.1-n/a-abcdefgh', 'v2014.1.4.1rc3-n/a-abcdefgh'), ('v2014.1.4.1-1-abcdefgh', 'v2014.1.4.1-n/a-abcdefgh') ) for higher_version, lower_version in examples: self.assertTrue(SaltStackVersion.parse(higher_version) > lower_version) self.assertTrue(SaltStackVersion.parse(lower_version) < higher_version)
def _mk_version(self, name): ''' Make a version :return: ''' return name, SaltStackVersion.from_name(name)
def test_version_parsing(self): strip_initial_non_numbers_regex = re.compile(r'(?:[^\d]+)?(?P<vs>.*)') expect = ( ('v0.12.0-19-g767d4f9', (0, 12, 0, 0, '', 0, 19, 'g767d4f9'), None), ('v0.12.0-85-g2880105', (0, 12, 0, 0, '', 0, 85, 'g2880105'), None), ('debian/0.11.1+ds-1-3-ga0afcbd', (0, 11, 1, 0, '', 0, 3, 'ga0afcbd'), '0.11.1-3-ga0afcbd'), ('0.12.1', (0, 12, 1, 0, '', 0, 0, None), None), ('0.12.1', (0, 12, 1, 0, '', 0, 0, None), None), ('0.17.0rc1', (0, 17, 0, 0, 'rc', 1, 0, None), None), ('v0.17.0rc1-1-g52ebdfd', (0, 17, 0, 0, 'rc', 1, 1, 'g52ebdfd'), None), ('v2014.1.4.1', (2014, 1, 4, 1, '', 0, 0, None), None), ('v2014.1.4.1rc3-n/a-abcdefgh', (2014, 1, 4, 1, 'rc', 3, -1, 'abcdefgh'), None), ('v3.4.1.1', (3, 4, 1, 1, '', 0, 0, None), None) ) for vstr, full_info, version in expect: saltstack_version = SaltStackVersion.parse(vstr) self.assertEqual( saltstack_version.full_info, full_info ) if version is None: version = strip_initial_non_numbers_regex.search(vstr).group('vs') self.assertEqual(saltstack_version.string, version)
def test_check_dns_deprecation_warning(self): helium_version = SaltStackVersion.from_name('Helium') if salt_version.__version_info__ >= helium_version: raise AssertionError( 'Failing this test on purpose! Please delete this test case, ' 'the \'check_dns\' keyword argument and the deprecation ' 'warnings in `salt.config.minion_config` and ' 'salt.config.apply_minion_config`' ) # Let's force the warning to always be thrown warnings.resetwarnings() warnings.filterwarnings( 'always', '(.*)check_dns(.*)', DeprecationWarning, 'salt.config' ) with warnings.catch_warnings(record=True) as w: sconfig.minion_config(None, None, check_dns=True) self.assertEqual( 'The functionality behind the \'check_dns\' keyword argument ' 'is no longer required, as such, it became unnecessary and is ' 'now deprecated. \'check_dns\' will be removed in Salt ' '{0}.'.format(helium_version.formatted_version), str(w[-1].message) ) with warnings.catch_warnings(record=True) as w: sconfig.apply_minion_config( overrides=None, defaults=None, check_dns=True ) self.assertEqual( 'The functionality behind the \'check_dns\' keyword argument ' 'is no longer required, as such, it became unnecessary and is ' 'now deprecated. \'check_dns\' will be removed in Salt ' '{0}.'.format(helium_version.formatted_version), str(w[-1].message) ) with warnings.catch_warnings(record=True) as w: sconfig.minion_config(None, None, check_dns=False) self.assertEqual( 'The functionality behind the \'check_dns\' keyword argument ' 'is no longer required, as such, it became unnecessary and is ' 'now deprecated. \'check_dns\' will be removed in Salt ' '{0}.'.format(helium_version.formatted_version), str(w[-1].message) ) with warnings.catch_warnings(record=True) as w: sconfig.apply_minion_config( overrides=None, defaults=None, check_dns=False ) self.assertEqual( 'The functionality behind the \'check_dns\' keyword argument ' 'is no longer required, as such, it became unnecessary and is ' 'now deprecated. \'check_dns\' will be removed in Salt ' '{0}.'.format(helium_version.formatted_version), str(w[-1].message) )
def __init__(self, globals, version): """ Constructor. :param globals: Module globals. Important for finding out replacement functions :param version: Expiration version :return: """ from salt.version import SaltStackVersion, __saltstack_version__ self._globals = globals self._exp_version_name = version self._exp_version = SaltStackVersion.from_name(self._exp_version_name) self._curr_version = __saltstack_version__.info self._raise_later = None self._function = None self._orig_f_name = None
def test_salt_with_git_version(self): if getattr(self, '_call_binary_', None) is None: self.skipTest('\'_call_binary_\' not defined.') from salt.utils import which from salt.version import __version_info__, SaltStackVersion git = which('git') if not git: self.skipTest('The git binary is not available') # Let's get the output of git describe process = subprocess.Popen( [git, 'describe', '--tags', '--match', 'v[0-9]*'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, cwd=CODE_DIR ) out, err = process.communicate() if not out: self.skipTest( 'Failed to get the output of \'git describe\'. ' 'Error: {0!r}'.format( err ) ) parsed_version = SaltStackVersion.parse(out) if parsed_version.info < __version_info__: self.skipTest( 'We\'re likely about to release a new version. This test ' 'would fail. Parsed({0!r}) < Expected({1!r})'.format( parsed_version.info, __version_info__ ) ) elif parsed_version.info != __version_info__: self.skipTest( 'In order to get the proper salt version with the ' 'git hash you need to update salt\'s local git ' 'tags. Something like: \'git fetch --tags\' or ' '\'git fetch --tags upstream\' if you followed ' 'salt\'s contribute documentation. The version ' 'string WILL NOT include the git hash.' ) out = '\n'.join(self.run_script(self._call_binary_, '--version')) self.assertIn(parsed_version.string, out)
def test_salt_with_git_version(self): if getattr(self, "_call_binary_", None) is None: self.skipTest("'_call_binary_' not defined.") from salt.version import __version_info__, SaltStackVersion git = salt.utils.path.which("git") if not git: self.skipTest("The git binary is not available") opts = { "stdout": subprocess.PIPE, "stderr": subprocess.PIPE, "cwd": CODE_DIR, } if not salt.utils.platform.is_windows(): opts["close_fds"] = True # Let's get the output of git describe process = subprocess.Popen([ git, "describe", "--tags", "--first-parent", "--match", "v[0-9]*" ], **opts) out, err = process.communicate() if process.returncode != 0: process = subprocess.Popen( [git, "describe", "--tags", "--match", "v[0-9]*"], **opts) out, err = process.communicate() if not out: self.skipTest("Failed to get the output of 'git describe'. " "Error: '{}'".format( salt.utils.stringutils.to_str(err))) parsed_version = SaltStackVersion.parse(out) if parsed_version.info < __version_info__: self.skipTest( "We're likely about to release a new version. This test " "would fail. Parsed('{}') < Expected('{}')".format( parsed_version.info, __version_info__)) elif parsed_version.info != __version_info__: self.skipTest("In order to get the proper salt version with the " "git hash you need to update salt's local git " "tags. Something like: 'git fetch --tags' or " "'git fetch --tags upstream' if you followed " "salt's contribute documentation. The version " "string WILL NOT include the git hash.") out = "\n".join(self.run_script(self._call_binary_, "--version")) self.assertIn(parsed_version.string, out)
def test_version_repr(self): ''' Test SaltStackVersion repr for both date and new versioning scheme ''' expect = ( ((3000, 1, None, None, '', 0, 0, None), "<SaltStackVersion name='Neon' major=3000 minor=1>"), ((3000, 0, None, None, '', 0, 0, None), "<SaltStackVersion name='Neon' major=3000>"), ((2019, 2, 3, None, '', 0, 0, None), "<SaltStackVersion name='Fluorine' major=2019 minor=2 bugfix=3>"), ((2019, 2, 3, None, 'rc', 1, 0, None), "<SaltStackVersion name='Fluorine' major=2019 minor=2 bugfix=3 rc=1>" )) for ver, repr_ret in expect: assert repr(SaltStackVersion(*ver)) == repr_ret
def test_noc_info(self): """ Test noc_info property method """ expect = ( ("v2014.1.4.1rc3-n/a-abcdefff", (2014, 1, 4, 1, "rc", 3, -1)), ("v3.4.1.1", (3, 4, 1, 1, "", 0, 0)), ("v3000", (3000, "", 0, 0)), ("v3000.0", (3000, "", 0, 0)), ("v4518.1", (4518, 1, "", 0, 0)), ("v3000rc1", (3000, "rc", 1, 0)), ("v3000rc1-n/a-abcdefff", (3000, "rc", 1, -1)), ) for vstr, noc_info in expect: saltstack_version = SaltStackVersion.parse(vstr) assert saltstack_version.noc_info, noc_info assert len(saltstack_version.noc_info) == len(noc_info)
def test_noc_info(self): ''' Test noc_info property method ''' expect = ( ('v2014.1.4.1rc3-n/a-abcdefff', (2014, 1, 4, 1, 'rc', 3, -1)), ('v3.4.1.1', (3, 4, 1, 1, '', 0, 0)), ('v3000', (3000, '', 0, 0)), ('v3000.0', (3000, '', 0, 0)), ('v4518.1', (4518, 1, '', 0, 0)), ('v3000rc1', (3000, 'rc', 1, 0)), ('v3000rc1-n/a-abcdefff', (3000, 'rc', 1, -1)), ) for vstr, noc_info in expect: saltstack_version = SaltStackVersion.parse(vstr) assert saltstack_version.noc_info, noc_info assert len(saltstack_version.noc_info) == len(noc_info)
def test_full_info_all_versions(self): """ Test full_info_all_versions property method """ expect = ( ("v2014.1.4.1rc3-n/a-abcdefff", (2014, 1, 4, 1, "rc", 3, -1, "abcdefff")), ("v3.4.1.1", (3, 4, 1, 1, "", 0, 0, None)), ("v3000", (3000, None, None, 0, "", 0, 0, None)), ("v3000.0", (3000, 0, None, 0, "", 0, 0, None)), ("v4518.1", (4518, 1, None, 0, "", 0, 0, None)), ("v3000rc1", (3000, None, None, 0, "rc", 2, 0, None)), ("v3000rc1-n/a-abcdefff", (3000, None, None, 0, "rc", 1, -1, "abcdefff")), ) for vstr, full_info in expect: saltstack_version = SaltStackVersion.parse(vstr) assert saltstack_version.full_info_all_versions, full_info assert len(saltstack_version.full_info_all_versions) == len(full_info)
def test_salt_with_git_version(self): if getattr(self, '_call_binary_', None) is None: self.skipTest('\'_call_binary_\' not defined.') from salt.version import __version_info__, SaltStackVersion git = salt.utils.path.which('git') if not git: self.skipTest('The git binary is not available') opts = { 'stdout': subprocess.PIPE, 'stderr': subprocess.PIPE, 'cwd': CODE_DIR, } if not salt.utils.platform.is_windows(): opts['close_fds'] = True # Let's get the output of git describe process = subprocess.Popen([ git, 'describe', '--tags', '--first-parent', '--match', 'v[0-9]*' ], **opts) out, err = process.communicate() if process.returncode != 0: process = subprocess.Popen( [git, 'describe', '--tags', '--match', 'v[0-9]*'], **opts) out, err = process.communicate() if not out: self.skipTest('Failed to get the output of \'git describe\'. ' 'Error: \'{0}\''.format( salt.utils.stringutils.to_str(err))) parsed_version = SaltStackVersion.parse(out) if parsed_version.info < __version_info__: self.skipTest( 'We\'re likely about to release a new version. This test ' 'would fail. Parsed(\'{0}\') < Expected(\'{1}\')'.format( parsed_version.info, __version_info__)) elif parsed_version.info != __version_info__: self.skipTest('In order to get the proper salt version with the ' 'git hash you need to update salt\'s local git ' 'tags. Something like: \'git fetch --tags\' or ' '\'git fetch --tags upstream\' if you followed ' 'salt\'s contribute documentation. The version ' 'string WILL NOT include the git hash.') out = '\n'.join(self.run_script(self._call_binary_, '--version')) self.assertIn(parsed_version.string, out)
def run(self): if not os.path.exists( SALT_VERSION_HARDCODED) or self.distribution.with_salt_version: # Write the version file if getattr(self.distribution, 'salt_version_hardcoded_path', None) is None: print('This command is not meant to be called on it\'s own') exit(1) if not self.distribution.with_salt_version: salt_version = __saltstack_version__ # pylint: disable=undefined-variable else: from salt.version import SaltStackVersion salt_version = SaltStackVersion.parse( self.distribution.with_salt_version) # pylint: disable=E0602 open(self.distribution.salt_version_hardcoded_path, 'w').write( INSTALL_VERSION_TEMPLATE.format( date=DATE, full_version_info=salt_version.full_info))
def test_full_info_all_versions(self): ''' Test full_info_all_versions property method ''' expect = ( ('v2014.1.4.1rc3-n/a-abcdefff', (2014, 1, 4, 1, 'rc', 3, -1, 'abcdefff')), ('v3.4.1.1', (3, 4, 1, 1, '', 0, 0, None)), ('v3000', (3000, None, None, 0, '', 0, 0, None)), ('v3000.0', (3000, 0, None, 0, '', 0, 0, None)), ('v4518.1', (4518, 1, None, 0, '', 0, 0, None)), ('v3000rc1', (3000, None, None, 0, 'rc', 2, 0, None)), ('v3000rc1-n/a-abcdefff', (3000, None, None, 0, 'rc', 1, -1, 'abcdefff')), ) for vstr, full_info in expect: saltstack_version = SaltStackVersion.parse(vstr) assert saltstack_version.full_info_all_versions, full_info assert len( saltstack_version.full_info_all_versions) == len(full_info)
def test_version_parsing(self): strip_initial_non_numbers_regex = re.compile(r'(?:[^\d]+)?(?P<vs>.*)') expect = ( ('v0.12.0-19-g767d4f9', (0, 12, 0, 0, 19, 'g767d4f9'), None), ('v0.12.0-85-g2880105', (0, 12, 0, 0, 85, 'g2880105'), None), ('debian/0.11.1+ds-1-3-ga0afcbd', (0, 11, 1, 0, 3, 'ga0afcbd'), '0.11.1-3-ga0afcbd'), ('0.12.1', (0, 12, 1, 0, 0, None), None), ('0.12.1', (0, 12, 1, 0, 0, None), None), ('0.17.0rc1', (0, 17, 0, 1, 0, None), None), ('v0.17.0rc1-1-g52ebdfd', (0, 17, 0, 1, 1, 'g52ebdfd'), None), ) for vs, full_info, version in expect: saltstack_version = SaltStackVersion.parse(vs) self.assertEqual(saltstack_version.full_info, full_info) if version is None: version = \ strip_initial_non_numbers_regex.search(vs).group('vs') self.assertEqual(saltstack_version.string, version)
def test_version_parsing(self): strip_initial_non_numbers_regex = re.compile(r"(?:[^\d]+)?(?P<vs>.*)") expect = ( ("v0.12.0-19-g767d4f9", (0, 12, 0, 0, "", 0, 19, "g767d4f9"), None), ("v0.12.0-85-g2880105", (0, 12, 0, 0, "", 0, 85, "g2880105"), None), ( "debian/0.11.1+ds-1-3-ga0afcbd", (0, 11, 1, 0, "", 0, 3, "ga0afcbd"), "0.11.1-3-ga0afcbd", ), ("0.12.1", (0, 12, 1, 0, "", 0, 0, None), None), ("0.12.1", (0, 12, 1, 0, "", 0, 0, None), None), ("0.17.0rc1", (0, 17, 0, 0, "rc", 1, 0, None), None), ("v0.17.0rc1-1-g52ebdfd", (0, 17, 0, 0, "rc", 1, 1, "g52ebdfd"), None), ("v2014.1.4.1", (2014, 1, 4, 1, "", 0, 0, None), None), ( "v2014.1.4.1rc3-n/a-abcdefff", (2014, 1, 4, 1, "rc", 3, -1, "abcdefff"), None, ), ("v3.4.1.1", (3, 4, 1, 1, "", 0, 0, None), None), ("v3000", (3000, "", 0, 0, None), "3000"), ("v3000.0", (3000, "", 0, 0, None), "3000"), ("v4518.1", (4518, 1, "", 0, 0, None), "4518.1"), ("v3000rc1", (3000, "rc", 1, 0, None), "3000rc1"), ("v3000rc1-n/a-abcdefff", (3000, "rc", 1, -1, "abcdefff"), None), ("3000-n/a-1e7bc8f", (3000, "", 0, -1, "1e7bc8f"), None), ("3000.1-n/a-1e7bc8f", (3000, 1, "", 0, -1, "1e7bc8f"), None), ) for vstr, full_info, version in expect: saltstack_version = SaltStackVersion.parse(vstr) self.assertEqual(saltstack_version.full_info, full_info) if version is None: version = strip_initial_non_numbers_regex.search(vstr).group( "vs") self.assertEqual(saltstack_version.string, version)
def test_salt_with_git_version(self): if getattr(self, '_call_binary_', None) is None: self.skipTest('\'_call_binary_\' not defined.') from salt.utils import which from salt.version import __version_info__, SaltStackVersion git = which('git') if not git: self.skipTest('The git binary is not available') # Let's get the output of git describe process = subprocess.Popen( [git, 'describe', '--tags', '--match', 'v[0-9]*'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, cwd=CODE_DIR) out, err = process.communicate() if not out: self.skipTest('Failed to get the output of \'git describe\'. ' 'Error: {0!r}'.format(err)) parsed_version = SaltStackVersion.parse(out) if parsed_version.info < __version_info__: self.skipTest( 'We\'re likely about to release a new version. This test ' 'would fail. Parsed({0!r}) < Expected({1!r})'.format( parsed_version.info, __version_info__)) elif parsed_version.info != __version_info__: self.skipTest('In order to get the proper salt version with the ' 'git hash you need to update salt\'s local git ' 'tags. Something like: \'git fetch --tags\' or ' '\'git fetch --tags upstream\' if you followed ' 'salt\'s contribute documentation. The version ' 'string WILL NOT include the git hash.') out = '\n'.join(self.run_script(self._call_binary_, '--version')) self.assertIn(parsed_version.string, out)
def check_bootstrapped_minion_version(options): ''' Confirm that the bootstrapped minion version matches the desired one ''' if 'salt_minion_bootstrapped' not in options: print_bulleted(options, 'Minion not boostrapped. Not grabbing minion version information.', 'RED') sys.exit(1) print_bulleted(options, 'Grabbing bootstrapped minion version information ... ') cmd = [ 'salt', '-t', '100', '--out=json', '-l', options.log_level ] if options.no_color: cmd.append('--no-color') cmd.extend([ options.vm_name, 'test.version' ]) stdout, stderr, exitcode = run_command(cmd, options, return_output=True, stream_stdout=False, stream_stderr=False) if exitcode: print_bulleted( options, 'Failed to get the bootstrapped minion version. Exit code: {0}'.format(exitcode), 'RED' ) sys.exit(exitcode) if not stdout.strip(): print_bulleted(options, 'Failed to get the bootstrapped minion version(no output).', 'RED') sys.stdout.flush() sys.exit(1) try: version_info = json.loads(stdout.strip()) bootstrap_minion_version = os.environ.get( 'SALT_MINION_BOOTSTRAP_RELEASE', options.bootstrap_salt_commit[:7] ) if bootstrap_minion_version.startswith('v'): bootstrap_minion_version = bootstrap_minion_version[1:] if bootstrap_minion_version not in version_info[options.vm_name]: print_bulleted(options, '\n\nATTENTION!!!!\n', 'YELLOW') print_bulleted( options, 'The boostrapped minion version commit does not contain the desired commit:', 'YELLOW' ) print_bulleted( options, '{0!r} does not contain {1!r}'.format(version_info[options.vm_name], bootstrap_minion_version), 'YELLOW' ) print('\n\n') sys.stdout.flush() else: print_bulleted(options, 'Matches!', 'LIGHT_GREEN') setattr(options, 'bootstrapped_salt_minion_version', SaltStackVersion.parse(version_info[options.vm_name])) except (ValueError, TypeError): print_bulleted(options, 'Failed to load any JSON from {0!r}'.format(stdout.strip()), 'RED')
def test_version_repr(version_tuple, expected): """ Test SaltStackVersion repr for both date and new versioning scheme """ assert repr(SaltStackVersion(*version_tuple)) == expected
def test_unparsable_version(): with pytest.raises(ValueError): SaltStackVersion.parse("Drunk")
# Import salt libs from salt.exceptions import ( #CommandExecutionError, SaltInvocationError ) from salt.utils import warn_until from salt.version import ( __version__, SaltStackVersion ) # is there not SaltStackVersion.current() to get # the version of the salt running this code?? CUR_VER = SaltStackVersion(__version__[0], __version__[1]) BORON = SaltStackVersion.from_name('Boron') # pylint: disable=import-error HAS_GLANCE = False try: from glanceclient import client from glanceclient import exc HAS_GLANCE = True except ImportError: pass # Workaround, as the Glance API v2 requires you to # already have a keystone session token HAS_KEYSTONE = False try: from keystoneclient.v2_0 import client as kstone
def installed(name, pip_bin=None, requirements=None, env=None, bin_env=None, use_wheel=False, log=None, proxy=None, timeout=None, repo=None, editable=None, find_links=None, index_url=None, extra_index_url=None, no_index=False, mirrors=None, build=None, target=None, download=None, download_cache=None, source=None, upgrade=False, force_reinstall=False, ignore_installed=False, exists_action=None, no_deps=False, no_install=False, no_download=False, install_options=None, global_options=None, user=None, runas=None, no_chown=False, cwd=None, activate=False, pre_releases=False): ''' Make sure the package is installed name The name of the python package to install. You can also specify version numbers here using the standard operators ``==, >=, <=``. If ``requirements`` is given, this parameter will be ignored. Example:: django: pip.installed: - name: django >= 1.6, <= 1.7 - require: - pkg: python-pip This will install the latest Django version greater than 1.6 but less than 1.7. user The user under which to run pip use_wheel : False Prefer wheel archives (requires pip>=1.4) bin_env : None Absolute path to a virtual environment directory or absolute path to a pip executable. The example below assumes a virtual environment has been created at ``/foo/.virtualenvs/bar``. Example:: django: pip.installed: - name: django >= 1.6, <= 1.7 - bin_env: /foo/.virtualenvs/bar - require: - pkg: python-pip Or Example:: django: pip.installed: - name: django >= 1.6, <= 1.7 - bin_env: /foo/.virtualenvs/bar/bin/pip - require: - pkg: python-pip .. admonition:: Attention The following arguments are deprecated, do not use. pip_bin : None Deprecated, use ``bin_env`` env : None Deprecated, use ``bin_env`` .. versionchanged:: 0.17.0 ``use_wheel`` option added. install_options Extra arguments to be supplied to the setup.py install command. If you are using an option with a directory path, be sure to use absolute path. Example: .. code-block:: yaml django: pip.installed: - name: django - install_options: - --prefix=/blah - require: - pkg: python-pip global_options Extra global options to be supplied to the setup.py call before the install command. .. versionadded:: 2014.1.3 .. admonition:: Attention As of Salt 0.17.0 the pip state **needs** an importable pip module. This usually means having the system's pip package installed or running Salt from an active `virtualenv`_. The reason for this requirement is because ``pip`` already does a pretty good job parsing it's own requirements. It makes no sense for Salt to do ``pip`` requirements parsing and validation before passing them to the ``pip`` library. It's functionality duplication and it's more error prone. .. _`virtualenv`: http://www.virtualenv.org/en/latest/ ''' if pip_bin and not bin_env: bin_env = pip_bin elif env and not bin_env: bin_env = env ret = {'name': name, 'result': None, 'comment': '', 'changes': {}} if use_wheel: min_version = '1.4' cur_version = __salt__['pip.version'](bin_env) if not salt.utils.compare_versions( ver1=cur_version, oper='>=', ver2=min_version): ret['result'] = False ret['comment'] = ('The \'use_wheel\' option is only supported in ' 'pip {0} and newer. The version of pip detected ' 'was {1}.').format(min_version, cur_version) return ret if repo is not None: msg = ('The \'repo\' argument to pip.installed is deprecated and will ' 'be removed in Salt {version}. Please use \'name\' instead. ' 'The current value for name, {0!r} will be replaced by the ' 'value of repo, {1!r}'.format( name, repo, version=_SaltStackVersion.from_name( 'Lithium').formatted_version)) salt.utils.warn_until('Lithium', msg) ret.setdefault('warnings', []).append(msg) name = repo from_vcs = False if name and not requirements: try: try: # With pip < 1.2, the __version__ attribute does not exist and # vcs+URL urls are not properly parsed. # The next line is meant to trigger an AttributeError and # handle lower pip versions logger.debug('Installed pip version: {0}'.format( pip.__version__)) install_req = pip.req.InstallRequirement.from_line(name) except AttributeError: logger.debug('Installed pip version is lower than 1.2') supported_vcs = ('git', 'svn', 'hg', 'bzr') if name.startswith(supported_vcs): for vcs in supported_vcs: if name.startswith(vcs): from_vcs = True install_req = pip.req.InstallRequirement.from_line( name.split('{0}+'.format(vcs))[-1]) break else: install_req = pip.req.InstallRequirement.from_line(name) except ValueError as exc: ret['result'] = False if not from_vcs and '=' in name and '==' not in name: ret['comment'] = ( 'Invalid version specification in package {0}. \'=\' is ' 'not supported, use \'==\' instead.'.format(name)) return ret ret['comment'] = ( 'pip raised an exception while parsing {0!r}: {1}'.format( name, exc)) return ret if install_req.req is None: # This is most likely an url and there's no way to know what will # be installed before actually installing it. prefix = '' version_spec = [] else: prefix = install_req.req.project_name version_spec = install_req.req.specs else: prefix = '' version_spec = [] if runas is not None: # The user is using a deprecated argument, warn! msg = ('The \'runas\' argument to pip.installed is deprecated, and ' 'will be removed in Salt {version}. Please use \'user\' ' 'instead.'.format(version=_SaltStackVersion.from_name( 'Lithium').formatted_version)) salt.utils.warn_until('Lithium', msg) ret.setdefault('warnings', []).append(msg) # "There can only be one" if user: raise CommandExecutionError( 'The \'runas\' and \'user\' arguments are mutually exclusive. ' 'Please use \'user\' as \'runas\' is being deprecated.') # Support deprecated 'runas' arg else: user = runas # Replace commas (used for version ranges) with semicolons (which are not # supported) in name so it does not treat them as multiple packages. Comma # will be re-added in pip.install call. name = name.replace(',', ';') # If a requirements file is specified, only install the contents of the # requirements file. Similarly, using the --editable flag with pip should # also ignore the "name" parameter. if requirements or editable: name = '' comments = [] if __opts__['test']: ret['result'] = None if requirements: # TODO: Check requirements file against currently-installed # packages to provide more accurate state output. comments.append('Requirements file {0!r} will be ' 'processed.'.format(requirements)) if editable: comments.append( 'Package will be installed in editable mode (i.e. ' 'setuptools "develop mode") from {0}.'.format(editable)) ret['comment'] = ' '.join(comments) return ret else: try: pip_list = __salt__['pip.list'](prefix, bin_env=bin_env, user=user, cwd=cwd) prefix_realname = _find_key(prefix, pip_list) except (CommandNotFoundError, CommandExecutionError) as err: ret['result'] = False ret['comment'] = 'Error installing {0!r}: {1}'.format(name, err) return ret if ignore_installed is False and prefix_realname is not None: if force_reinstall is False and not upgrade: # Check desired version (if any) against currently-installed if (any(version_spec) and _fulfills_version_spec( pip_list[prefix_realname], version_spec)) or (not any(version_spec)): ret['result'] = True ret['comment'] = ('Python package {0} already ' 'installed'.format(name)) return ret if __opts__['test']: ret['result'] = None ret['comment'] = \ 'Python package {0} is set to be installed'.format(name) return ret pip_install_call = __salt__['pip.install']( pkgs='{0}'.format(name) if name else '', requirements=requirements, bin_env=bin_env, use_wheel=use_wheel, log=log, proxy=proxy, timeout=timeout, editable=editable, find_links=find_links, index_url=index_url, extra_index_url=extra_index_url, no_index=no_index, mirrors=mirrors, build=build, target=target, download=download, download_cache=download_cache, source=source, upgrade=upgrade, force_reinstall=force_reinstall, ignore_installed=ignore_installed, exists_action=exists_action, no_deps=no_deps, no_install=no_install, no_download=no_download, install_options=install_options, global_options=global_options, user=user, no_chown=no_chown, cwd=cwd, activate=activate, pre_releases=pre_releases, saltenv=__env__) if pip_install_call and (pip_install_call.get('retcode', 1) == 0): ret['result'] = True if requirements or editable: comments = [] if requirements: comments.append('Successfully processed requirements file ' '{0}.'.format(requirements)) ret['changes']['requirements'] = True if editable: comments.append('Package successfully installed from VCS ' 'checkout {0}.'.format(editable)) ret['changes']['editable'] = True ret['comment'] = ' '.join(comments) else: if not prefix: pkg_list = {} else: pkg_list = __salt__['pip.list'](prefix, bin_env, user=user, cwd=cwd) if not pkg_list: ret['comment'] = ( 'There was no error installing package \'{0}\' although ' 'it does not show when calling ' '\'pip.freeze\'.'.format(name)) ret['changes']['{0}==???'.format(name)] = 'Installed' return ret version = list(pkg_list.values())[0] pkg_name = next(iter(pkg_list)) ret['changes']['{0}=={1}'.format(pkg_name, version)] = 'Installed' ret['comment'] = 'Package was successfully installed' elif pip_install_call: ret['result'] = False if 'stdout' in pip_install_call: error = 'Error: {0} {1}'.format(pip_install_call['stdout'], pip_install_call['stderr']) else: error = 'Error: {0}'.format(pip_install_call['comment']) if requirements or editable: comments = [] if requirements: comments.append('Unable to process requirements file ' '{0}.'.format(requirements)) if editable: comments.append('Unable to install from VCS checkout' '{0}.'.format(editable)) comments.append(error) ret['comment'] = ' '.join(comments) else: ret['comment'] = ('Failed to install package {0}. ' '{1}'.format(name, error)) else: ret['result'] = False ret['comment'] = 'Could not install package' return ret
def installed(name, pkgs=None, pip_bin=None, requirements=None, bin_env=None, use_wheel=False, no_use_wheel=False, log=None, proxy=None, timeout=None, repo=None, editable=None, find_links=None, index_url=None, extra_index_url=None, no_index=False, mirrors=None, build=None, target=None, download=None, download_cache=None, source=None, upgrade=False, force_reinstall=False, ignore_installed=False, exists_action=None, no_deps=False, no_install=False, no_download=False, install_options=None, global_options=None, user=None, cwd=None, pre_releases=False, cert=None, allow_all_external=False, allow_external=None, allow_unverified=None, process_dependency_links=False, env_vars=None, use_vt=False, trusted_host=None, no_cache_dir=False, cache_dir=None, no_binary=None, **kwargs): ''' Make sure the package is installed name The name of the python package to install. You can also specify version numbers here using the standard operators ``==, >=, <=``. If ``requirements`` is given, this parameter will be ignored. Example: .. code-block:: yaml django: pip.installed: - name: django >= 1.6, <= 1.7 - require: - pkg: python-pip This will install the latest Django version greater than 1.6 but less than 1.7. requirements Path to a pip requirements file. If the path begins with salt:// the file will be transferred from the master file server. user The user under which to run pip use_wheel : False Prefer wheel archives (requires pip>=1.4) no_use_wheel : False Force to not use wheel archives (requires pip>=1.4) no_binary Force to not use binary packages (requires pip >= 7.0.0) Accepts either :all: to disable all binary packages, :none: to empty the set, or a list of one or more packages Example: .. code-block:: yaml django: pip.installed: - no_binary: ':all:' flask: pip.installed: - no_binary: - itsdangerous - click log Log file where a complete (maximum verbosity) record will be kept proxy Specify a proxy in the form user:[email protected]:port. Note that the user:password@ is optional and required only if you are behind an authenticated proxy. If you provide [email protected]:port then you will be prompted for a password. timeout Set the socket timeout (default 15 seconds) editable install something editable (i.e. git+https://github.com/worldcompany/djangoembed.git#egg=djangoembed) find_links URL to look for packages at index_url Base URL of Python Package Index extra_index_url Extra URLs of package indexes to use in addition to ``index_url`` no_index Ignore package index mirrors Specific mirror URL(s) to query (automatically adds --use-mirrors) build Unpack packages into ``build`` dir target Install packages into ``target`` dir download Download packages into ``download`` instead of installing them download_cache Cache downloaded packages in ``download_cache`` dir source Check out ``editable`` packages into ``source`` dir upgrade Upgrade all packages to the newest available version force_reinstall When upgrading, reinstall all packages even if they are already up-to-date. ignore_installed Ignore the installed packages (reinstalling instead) exists_action Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup no_deps Ignore package dependencies no_install Download and unpack all packages, but don't actually install them no_cache_dir: Disable the cache. cwd Current working directory to run pip from pre_releases Include pre-releases in the available versions cert Provide a path to an alternate CA bundle allow_all_external Allow the installation of all externally hosted files allow_external Allow the installation of externally hosted files (comma separated list) allow_unverified Allow the installation of insecure and unverifiable files (comma separated list) process_dependency_links Enable the processing of dependency links bin_env : None Absolute path to a virtual environment directory or absolute path to a pip executable. The example below assumes a virtual environment has been created at ``/foo/.virtualenvs/bar``. env_vars Add or modify environment variables. Useful for tweaking build steps, such as specifying INCLUDE or LIBRARY paths in Makefiles, build scripts or compiler calls. This must be in the form of a dictionary or a mapping. Example: .. code-block:: yaml django: pip.installed: - name: django_app - env_vars: CUSTOM_PATH: /opt/django_app VERBOSE: True use_vt Use VT terminal emulation (see output while installing) trusted_host Mark this host as trusted, even though it does not have valid or any HTTPS. Example: .. code-block:: yaml django: pip.installed: - name: django >= 1.6, <= 1.7 - bin_env: /foo/.virtualenvs/bar - require: - pkg: python-pip Or Example: .. code-block:: yaml django: pip.installed: - name: django >= 1.6, <= 1.7 - bin_env: /foo/.virtualenvs/bar/bin/pip - require: - pkg: python-pip .. admonition:: Attention The following arguments are deprecated, do not use. pip_bin : None Deprecated, use ``bin_env`` .. versionchanged:: 0.17.0 ``use_wheel`` option added. install_options Extra arguments to be supplied to the setup.py install command. If you are using an option with a directory path, be sure to use absolute path. Example: .. code-block:: yaml django: pip.installed: - name: django - install_options: - --prefix=/blah - require: - pkg: python-pip global_options Extra global options to be supplied to the setup.py call before the install command. .. versionadded:: 2014.1.3 .. admonition:: Attention As of Salt 0.17.0 the pip state **needs** an importable pip module. This usually means having the system's pip package installed or running Salt from an active `virtualenv`_. The reason for this requirement is because ``pip`` already does a pretty good job parsing its own requirements. It makes no sense for Salt to do ``pip`` requirements parsing and validation before passing them to the ``pip`` library. It's functionality duplication and it's more error prone. .. admonition:: Attention Please set ``reload_modules: True`` to have the salt minion import this module after installation. Example: .. code-block:: yaml pyopenssl: pip.installed: - name: pyOpenSSL - reload_modules: True - exists_action: i .. _`virtualenv`: http://www.virtualenv.org/en/latest/ ''' if 'no_chown' in kwargs: salt.utils.warn_until( 'Flourine', 'The no_chown argument has been deprecated and is no longer used. ' 'Its functionality was removed in Boron.') kwargs.pop('no_chown') if pip_bin and not bin_env: bin_env = pip_bin # If pkgs is present, ignore name if pkgs: if not isinstance(pkgs, list): return { 'name': name, 'result': False, 'changes': {}, 'comment': 'pkgs argument must be formatted as a list' } else: pkgs = [name] # Assumption: If `pkg` is not an `string`, it's a `collections.OrderedDict` # prepro = lambda pkg: pkg if type(pkg) == str else \ # ' '.join((pkg.items()[0][0], pkg.items()[0][1].replace(',', ';'))) # pkgs = ','.join([prepro(pkg) for pkg in pkgs]) prepro = lambda pkg: pkg if isinstance(pkg, str) else \ ' '.join((six.iteritems(pkg)[0][0], six.iteritems(pkg)[0][1])) pkgs = [prepro(pkg) for pkg in pkgs] ret = { 'name': ';'.join(pkgs), 'result': None, 'comment': '', 'changes': {} } try: cur_version = __salt__['pip.version'](bin_env) except (CommandNotFoundError, CommandExecutionError) as err: ret['result'] = None ret['comment'] = 'Error installing \'{0}\': {1}'.format(name, err) return ret # Check that the pip binary supports the 'use_wheel' option if use_wheel: min_version = '1.4' max_version = '9.0.3' too_low = salt.utils.compare_versions(ver1=cur_version, oper='<', ver2=min_version) too_high = salt.utils.compare_versions(ver1=cur_version, oper='>', ver2=max_version) if too_low or too_high: ret['result'] = False ret['comment'] = ( 'The \'use_wheel\' option is only supported in ' 'pip between {0} and {1}. The version of pip detected ' 'was {2}.').format(min_version, max_version, cur_version) return ret # Check that the pip binary supports the 'no_use_wheel' option if no_use_wheel: min_version = '1.4' max_version = '9.0.3' too_low = salt.utils.compare_versions(ver1=cur_version, oper='<', ver2=min_version) too_high = salt.utils.compare_versions(ver1=cur_version, oper='>', ver2=max_version) if too_low or too_high: ret['result'] = False ret['comment'] = ( 'The \'no_use_wheel\' option is only supported in ' 'pip between {0} and {1}. The version of pip detected ' 'was {2}.').format(min_version, max_version, cur_version) return ret # Check that the pip binary supports the 'no_binary' option if no_binary: min_version = '7.0.0' too_low = salt.utils.compare_versions(ver1=cur_version, oper='<', ver2=min_version) if too_low: ret['result'] = False ret['comment'] = ('The \'no_binary\' option is only supported in ' 'pip {0} and newer. The version of pip detected ' 'was {1}.').format(min_version, cur_version) return ret # Deprecation warning for the repo option if repo is not None: msg = ('The \'repo\' argument to pip.installed is deprecated and will ' 'be removed in Salt {version}. Please use \'name\' instead. ' 'The current value for name, \'{0}\' will be replaced by the ' 'value of repo, \'{1}\''.format( name, repo, version=_SaltStackVersion.from_name( 'Lithium').formatted_version)) salt.utils.warn_until('Lithium', msg) ret.setdefault('warnings', []).append(msg) name = repo # Get the packages parsed name and version from the pip library. # This only is done when there is no requirements or editable parameter. pkgs_details = [] if pkgs and not (requirements or editable): comments = [] for pkg in iter(pkgs): out = _check_pkg_version_format(pkg) if out['result'] is False: ret['result'] = False comments.append(out['comment']) elif out['result'] is True: pkgs_details.append((out['prefix'], pkg, out['version_spec'])) if ret['result'] is False: ret['comment'] = '\n'.join(comments) return ret # If a requirements file is specified, only install the contents of the # requirements file. Similarly, using the --editable flag with pip should # also ignore the "name" and "pkgs" parameters. target_pkgs = [] already_installed_comments = [] if requirements or editable: comments = [] # Append comments if this is a dry run. if __opts__['test']: ret['result'] = None if requirements: # TODO: Check requirements file against currently-installed # packages to provide more accurate state output. comments.append('Requirements file \'{0}\' will be ' 'processed.'.format(requirements)) if editable: comments.append( 'Package will be installed in editable mode (i.e. ' 'setuptools "develop mode") from {0}.'.format(editable)) ret['comment'] = ' '.join(comments) return ret # No requirements case. # Check pre-existence of the requested packages. else: # Attempt to pre-cache a the current pip list try: pip_list = __salt__['pip.list'](bin_env=bin_env, user=user, cwd=cwd) # If we fail, then just send False, and we'll try again in the next function call except Exception as exc: logger.exception(exc) pip_list = False for prefix, state_pkg_name, version_spec in pkgs_details: if prefix: state_pkg_name = state_pkg_name version_spec = version_spec out = _check_if_installed(prefix, state_pkg_name, version_spec, ignore_installed, force_reinstall, upgrade, user, cwd, bin_env, env_vars, pip_list, **kwargs) # If _check_if_installed result is None, something went wrong with # the command running. This way we keep stateful output. if out['result'] is None: ret['result'] = False ret['comment'] = out['comment'] return ret else: out = {'result': False, 'comment': None} result = out['result'] # The package is not present. Add it to the pkgs to install. if result is False: # Replace commas (used for version ranges) with semicolons # (which are not supported) in name so it does not treat # them as multiple packages. target_pkgs.append((prefix, state_pkg_name.replace(',', ';'))) # Append comments if this is a dry run. if __opts__['test']: msg = 'Python package {0} is set to be installed' ret['result'] = None ret['comment'] = msg.format(state_pkg_name) return ret # The package is already present and will not be reinstalled. elif result is True: # Append comment stating its presence already_installed_comments.append(out['comment']) # The command pip.list failed. Abort. elif result is None: ret['result'] = None ret['comment'] = out['comment'] return ret # No packages to install. if not target_pkgs: ret['result'] = True aicomms = '\n'.join(already_installed_comments) last_line = 'All specified packages are already installed' + ( ' and up-to-date' if upgrade else '') ret['comment'] = aicomms + ('\n' if aicomms else '') + last_line return ret # Construct the string that will get passed to the install call pkgs_str = ','.join([state_name for _, state_name in target_pkgs]) # Call to install the package. Actual installation takes place here pip_install_call = __salt__['pip.install']( pkgs='{0}'.format(pkgs_str) if pkgs_str else '', requirements=requirements, bin_env=bin_env, use_wheel=use_wheel, no_use_wheel=no_use_wheel, no_binary=no_binary, log=log, proxy=proxy, timeout=timeout, editable=editable, find_links=find_links, index_url=index_url, extra_index_url=extra_index_url, no_index=no_index, mirrors=mirrors, build=build, target=target, download=download, download_cache=download_cache, source=source, upgrade=upgrade, force_reinstall=force_reinstall, ignore_installed=ignore_installed, exists_action=exists_action, no_deps=no_deps, no_install=no_install, no_download=no_download, install_options=install_options, global_options=global_options, user=user, cwd=cwd, pre_releases=pre_releases, cert=cert, allow_all_external=allow_all_external, allow_external=allow_external, allow_unverified=allow_unverified, process_dependency_links=process_dependency_links, saltenv=__env__, env_vars=env_vars, use_vt=use_vt, trusted_host=trusted_host, no_cache_dir=no_cache_dir, **kwargs) if pip_install_call and pip_install_call.get('retcode', 1) == 0: ret['result'] = True if requirements or editable: comments = [] if requirements: PIP_REQUIREMENTS_NOCHANGE = [ 'Requirement already satisfied', 'Requirement already up-to-date', 'Requirement not upgraded', 'Collecting', 'Cloning', 'Cleaning up...', ] for line in pip_install_call.get('stdout', '').split('\n'): if not any([ line.strip().startswith(x) for x in PIP_REQUIREMENTS_NOCHANGE ]): ret['changes']['requirements'] = True if ret['changes'].get('requirements'): comments.append('Successfully processed requirements file ' '{0}.'.format(requirements)) else: comments.append('Requirements were already installed.') if editable: comments.append('Package successfully installed from VCS ' 'checkout {0}.'.format(editable)) ret['changes']['editable'] = True ret['comment'] = ' '.join(comments) else: # Check that the packages set to be installed were installed. # Create comments reporting success and failures pkg_404_comms = [] already_installed_packages = set() for line in pip_install_call.get('stdout', '').split('\n'): # Output for already installed packages: # 'Requirement already up-to-date: jinja2 in /usr/local/lib/python2.7/dist-packages\nCleaning up...' if line.startswith('Requirement already up-to-date: '): package = line.split(':', 1)[1].split()[0] already_installed_packages.add(package.lower()) for prefix, state_name in target_pkgs: # Case for packages that are not an URL if prefix: pipsearch = __salt__['pip.list'](prefix, bin_env, user=user, cwd=cwd, env_vars=env_vars, **kwargs) # If we didn't find the package in the system after # installing it report it if not pipsearch: pkg_404_comms.append( 'There was no error installing package \'{0}\' ' 'although it does not show when calling ' '\'pip.freeze\'.'.format(pkg)) else: pkg_name = _find_key(prefix, pipsearch) if pkg_name.lower() in already_installed_packages: continue ver = pipsearch[pkg_name] ret['changes']['{0}=={1}'.format(pkg_name, ver)] = 'Installed' # Case for packages that are an URL else: ret['changes']['{0}==???'.format(state_name)] = 'Installed' # Set comments aicomms = '\n'.join(already_installed_comments) succ_comm = 'All packages were successfully installed'\ if not pkg_404_comms else '\n'.join(pkg_404_comms) ret['comment'] = aicomms + ('\n' if aicomms else '') + succ_comm return ret elif pip_install_call: ret['result'] = False if 'stdout' in pip_install_call: error = 'Error: {0} {1}'.format(pip_install_call['stdout'], pip_install_call['stderr']) else: error = 'Error: {0}'.format(pip_install_call['comment']) if requirements or editable: comments = [] if requirements: comments.append('Unable to process requirements file ' '"{0}".'.format(requirements)) if editable: comments.append('Unable to install from VCS checkout' '{0}.'.format(editable)) comments.append(error) ret['comment'] = ' '.join(comments) else: pkgs_str = ', '.join([state_name for _, state_name in target_pkgs]) aicomms = '\n'.join(already_installed_comments) error_comm = ('Failed to install packages: {0}. ' '{1}'.format(pkgs_str, error)) ret['comment'] = aicomms + ('\n' if aicomms else '') + error_comm else: ret['result'] = False ret['comment'] = 'Could not install package' return ret
def test_unparsable_version(self): with self.assertRaises(ValueError): SaltStackVersion.from_name('Drunk') with self.assertRaises(ValueError): SaltStackVersion.parse('Drunk')
def installed( name, pip_bin=None, requirements=None, env=None, bin_env=None, use_wheel=False, log=None, proxy=None, timeout=None, repo=None, editable=None, find_links=None, index_url=None, extra_index_url=None, no_index=False, mirrors=None, build=None, target=None, download=None, download_cache=None, source=None, upgrade=False, force_reinstall=False, ignore_installed=False, exists_action=None, no_deps=False, no_install=False, no_download=False, install_options=None, user=None, runas=None, no_chown=False, cwd=None, activate=False, pre_releases=False, ): """ Make sure the package is installed name The name of the python package to install. You can also specify version numbers here using the standard operators ``==, >=, <=``. If ``requirements`` is given, this parameter will be ignored. Example:: django: pip.installed: - name: django >= 1.6, <= 1.7 - require: - pkg: python-pip This will install the latest Django version greater than 1.6 but less than 1.7. user The user under which to run pip use_wheel : False Prefer wheel archives (requires pip>=1.4) bin_env : None Absolute path to a virtual environment directory or absolute path to a pip executable. The example below assumes a virtual environment has been created at ``/foo/.virtualenvs/bar``. Example:: django: pip.installed: - name: django >= 1.6, <= 1.7 - bin_env: /foo/.virtualenvs/bar - require: - pkg: python-pip Or Example:: django: pip.installed: - name: django >= 1.6, <= 1.7 - bin_env: /foo/.virtualenvs/bar/bin/pip - require: - pkg: python-pip .. admonition:: Attention The following arguments are deprecated, do not use. pip_bin : None Deprecated, use ``bin_env`` env : None Deprecated, use ``bin_env`` .. versionchanged:: 0.17.0 ``use_wheel`` option added. .. admonition:: Attention As of Salt 0.17.0 the pip state **needs** an importable pip module. This usually means having the system's pip package installed or running Salt from an active `virtualenv`_. The reason for this requirement is because ``pip`` already does a pretty good job parsing it's own requirements. It makes no sense for Salt to do ``pip`` requirements parsing and validation before passing them to the ``pip`` library. It's functionality duplication and it's more error prone. .. _`virtualenv`: http://www.virtualenv.org """ if pip_bin and not bin_env: bin_env = pip_bin elif env and not bin_env: bin_env = env ret = {"name": name, "result": None, "comment": "", "changes": {}, "state_stdout": ""} if use_wheel: min_version = "1.4" cur_version = __salt__["pip.version"](bin_env) if not salt.utils.compare_versions(ver1=cur_version, oper=">=", ver2=min_version): ret["result"] = False ret["comment"] = ( "The 'use_wheel' option is only supported in " "pip {0} and newer. The version of pip detected " "was {1}." ).format(min_version, cur_version) return ret if repo is not None: msg = ( "The 'repo' argument to pip.installed is deprecated and will " "be removed in Salt {version}. Please use 'name' instead. " "The current value for name, {0!r} will be replaced by the " "value of repo, {1!r}".format(name, repo, version=_SaltStackVersion.from_name("Hydrogen").formatted_version) ) salt.utils.warn_until("Hydrogen", msg) ret.setdefault("warnings", []).append(msg) name = repo from_vcs = False if name and not requirements: try: try: # With pip < 1.2, the __version__ attribute does not exist and # vcs+URL urls are not properly parsed. # The next line is meant to trigger an AttributeError and # handle lower pip versions logger.debug("Installed pip version: {0}".format(pip.__version__)) install_req = pip.req.InstallRequirement.from_line(name) except AttributeError: logger.debug("Installed pip version is lower than 1.2") supported_vcs = ("git", "svn", "hg", "bzr") if name.startswith(supported_vcs): for vcs in supported_vcs: if name.startswith(vcs): from_vcs = True install_req = pip.req.InstallRequirement.from_line(name.split("{0}+".format(vcs))[-1]) break else: install_req = pip.req.InstallRequirement.from_line(name) except ValueError as exc: ret["result"] = False if not from_vcs and "=" in name and "==" not in name: ret["comment"] = ( "Invalid version specification in package {0}. '=' is " "not supported, use '==' instead.".format(name) ) return ret ret["comment"] = "pip raised an exception while parsing {0!r}: {1}".format(name, exc) return ret if install_req.req is None: # This is most likely an url and there's no way to know what will # be installed before actually installing it. prefix = "" version_spec = [] else: prefix = install_req.req.project_name version_spec = install_req.req.specs else: prefix = "" version_spec = [] if runas is not None: # The user is using a deprecated argument, warn! msg = ( "The 'runas' argument to pip.installed is deprecated, and " "will be removed in Salt {version}. Please use 'user' " "instead.".format(version=_SaltStackVersion.from_name("Hydrogen").formatted_version) ) salt.utils.warn_until("Hydrogen", msg) ret.setdefault("warnings", []).append(msg) # "There can only be one" if user: raise CommandExecutionError( "The 'runas' and 'user' arguments are mutually exclusive. " "Please use 'user' as 'runas' is being deprecated." ) # Support deprecated 'runas' arg else: user = runas # Replace commas (used for version ranges) with semicolons (which are not # supported) in name so it does not treat them as multiple packages. Comma # will be re-added in pip.install call. name = name.replace(",", ";") # If a requirements file is specified, only install the contents of the # requirements file. Similarly, using the --editable flag with pip should # also ignore the "name" parameter. if requirements or editable: name = "" comments = [] if __opts__["test"]: ret["result"] = None if requirements: # TODO: Check requirements file against currently-installed # packages to provide more accurate state output. comments.append("Requirements file {0!r} will be " "processed.".format(requirements)) if editable: comments.append( "Package will be installed in editable mode (i.e. " 'setuptools "develop mode") from {0}.'.format(editable) ) ret["comment"] = " ".join(comments) return ret else: try: pip_list = __salt__["pip.list"](prefix, bin_env=bin_env, user=user, cwd=cwd) prefix_realname = _find_key(prefix, pip_list) except (CommandNotFoundError, CommandExecutionError) as err: ret["result"] = False ret["comment"] = "Error installing {0!r}: {1}".format(name, err) return ret if ignore_installed is False and prefix_realname is not None: if force_reinstall is False and not upgrade: # Check desired version (if any) against currently-installed if (any(version_spec) and _fulfills_version_spec(pip_list[prefix_realname], version_spec)) or ( not any(version_spec) ): ret["result"] = True ret["comment"] = "Python package {0} already " "installed".format(name) return ret if __opts__["test"]: ret["result"] = None ret["comment"] = "Python package {0} is set to be installed".format(name) return ret pip_install_call = __salt__["pip.install"]( pkgs="{0}".format(name) if name else "", requirements=requirements, bin_env=bin_env, use_wheel=use_wheel, log=log, proxy=proxy, timeout=timeout, editable=editable, find_links=find_links, index_url=index_url, extra_index_url=extra_index_url, no_index=no_index, mirrors=mirrors, build=build, target=target, download=download, download_cache=download_cache, source=source, upgrade=upgrade, force_reinstall=force_reinstall, ignore_installed=ignore_installed, exists_action=exists_action, no_deps=no_deps, no_install=no_install, no_download=no_download, install_options=install_options, user=user, no_chown=no_chown, cwd=cwd, activate=activate, pre_releases=pre_releases, saltenv=__env__, state_ret=ret, ) if pip_install_call and (pip_install_call.get("retcode", 1) == 0): ret["result"] = True if requirements or editable: comments = [] if requirements: comments.append("Successfully processed requirements file " "{0}.".format(requirements)) ret["changes"]["requirements"] = True if editable: comments.append("Package successfully installed from VCS " "checkout {0}.".format(editable)) ret["changes"]["editable"] = True ret["comment"] = " ".join(comments) else: if not prefix: pkg_list = {} else: pkg_list = __salt__["pip.list"](prefix, bin_env, user=user, cwd=cwd) if not pkg_list: ret["comment"] = ( "There was no error installing package '{0}' although " "it does not show when calling " "'pip.freeze'.".format(name) ) ret["changes"]["{0}==???".format(name)] = "Installed" return ret version = list(pkg_list.values())[0] pkg_name = next(iter(pkg_list)) ret["changes"]["{0}=={1}".format(pkg_name, version)] = "Installed" ret["comment"] = "Package was successfully installed" elif pip_install_call: ret["result"] = False if "stdout" in pip_install_call: error = "Error: {0} {1}".format(pip_install_call["stdout"], pip_install_call["stderr"]) else: error = "Error: {0}".format(pip_install_call["comment"]) if requirements or editable: comments = [] if requirements: comments.append("Unable to process requirements file " "{0}.".format(requirements)) if editable: comments.append("Unable to install from VCS checkout" "{0}.".format(editable)) comments.append(error) ret["comment"] = " ".join(comments) else: ret["comment"] = "Failed to install package {0}. " "{1}".format(name, error) else: ret["result"] = False ret["comment"] = "Could not install package" return ret
# This file was auto-generated by salt's setup from salt.version import SaltStackVersion __saltstack_version__ = SaltStackVersion(2019, 2, 0, 0, u'', 0, 0, None)
''' Override module to fix a bug in 'ssh.host_keys' ''' from __future__ import absolute_import import sys import logging import salt.modules.ssh as orig_mod from salt.version import __saltstack_version__, SaltStackVersion log = logging.getLogger(__name__) log.trace("Overriding the default ssh module") if __saltstack_version__ < SaltStackVersion.from_name('oxygen'): log.trace("Overriding 'ssh.host_keys'") def host_keys(keydir=None, private=True): ''' Return the minion's host keys CLI Example: .. code-block:: bash salt '*' ssh.host_keys salt '*' ssh.host_keys keydir=/etc/ssh salt '*' ssh.host_keys keydir=/etc/ssh private=False ''' # TODO: support parsing sshd_config for the key directory if not keydir:
Set up the version of Salt ''' # Import python libs import sys import pkg_resources # Import salt libs from salt.version import SaltStackVersion, __version__ as __saltversion__ # ----- Hardcoded Salt Fuse Version Information ------------------------------> # # Please bump version information for __saltstack_version__ on new releases # ---------------------------------------------------------------------------- __saltstack_version__ = SaltStackVersion(0, 4, 0) __version_info__ = __saltstack_version__.info __version__ = __saltstack_version__.string # <---- Hardcoded Salt Fuse Version Information ------------------------------- # ----- Dynamic/Runtime Salt Fuse Version Information ------------------------> def __get_version(version, version_info): ''' If we can get a version provided at installation time or from Git, use that instead, otherwise we carry on. ''' try: # Try to import the version information provided at install time from saltfuse._version import __version__, __version_info__ # pylint: disable=E0611 return __version__, __version_info__
def removed(name, requirements=None, bin_env=None, log=None, proxy=None, timeout=None, user=None, runas=None, cwd=None): ''' Make sure that a package is not installed. name The name of the package to uninstall user The user under which to run pip bin_env : None the pip executable or virtualenenv to use ''' ret = {'name': name, 'result': None, 'comment': '', 'changes': {}} if runas is not None: # The user is using a deprecated argument, warn! msg = ('The \'runas\' argument to pip.installed is deprecated, and ' 'will be removed in Salt {version}. Please use \'user\' ' 'instead.'.format(version=_SaltStackVersion.from_name( 'Lithium').formatted_version)) salt.utils.warn_until('Lithium', msg) ret.setdefault('warnings', []).append(msg) # "There can only be one" if runas is not None and user: raise CommandExecutionError( 'The \'runas\' and \'user\' arguments are mutually exclusive. ' 'Please use \'user\' as \'runas\' is being deprecated.') # Support deprecated 'runas' arg elif runas is not None and not user: user = runas try: pip_list = __salt__['pip.list'](bin_env=bin_env, user=user, cwd=cwd) except (CommandExecutionError, CommandNotFoundError) as err: ret['result'] = False ret['comment'] = 'Error uninstalling \'{0}\': {1}'.format(name, err) return ret if name not in pip_list: ret['result'] = True ret['comment'] = 'Package is not installed.' return ret if __opts__['test']: ret['result'] = None ret['comment'] = 'Package {0} is set to be removed'.format(name) return ret if __salt__['pip.uninstall'](pkgs=name, requirements=requirements, bin_env=bin_env, log=log, proxy=proxy, timeout=timeout, user=user, cwd=cwd): ret['result'] = True ret['changes'][name] = 'Removed' ret['comment'] = 'Package was successfully removed.' else: ret['result'] = False ret['comment'] = 'Could not remove package.' return ret
def removed(name, requirements=None, bin_env=None, log=None, proxy=None, timeout=None, user=None, runas=None, cwd=None): """ Make sure that a package is not installed. name The name of the package to uninstall user The user under which to run pip bin_env : None the pip executable or virtualenenv to use """ ret = {"name": name, "result": None, "comment": "", "changes": {}, "state_stdout": ""} if runas is not None: # The user is using a deprecated argument, warn! msg = ( "The 'runas' argument to pip.installed is deprecated, and " "will be removed in Salt {version}. Please use 'user' " "instead.".format(version=_SaltStackVersion.from_name("Hydrogen").formatted_version) ) salt.utils.warn_until("Hydrogen", msg) ret.setdefault("warnings", []).append(msg) # "There can only be one" if runas is not None and user: raise CommandExecutionError( "The 'runas' and 'user' arguments are mutually exclusive. " "Please use 'user' as 'runas' is being deprecated." ) # Support deprecated 'runas' arg elif runas is not None and not user: user = runas try: pip_list = __salt__["pip.list"](bin_env=bin_env, user=user, cwd=cwd) except (CommandExecutionError, CommandNotFoundError) as err: ret["result"] = False ret["comment"] = "Error uninstalling '{0}': {1}".format(name, err) return ret if name not in pip_list: ret["result"] = True ret["comment"] = "Package is not installed." return ret if __opts__["test"]: ret["result"] = None ret["comment"] = "Package {0} is set to be removed".format(name) return ret if __salt__["pip.uninstall"]( pkgs=name, requirements=requirements, bin_env=bin_env, log=log, proxy=proxy, timeout=timeout, user=user, cwd=cwd, state_ret=ret, ): ret["result"] = True ret["changes"][name] = "Removed" ret["comment"] = "Package was successfully removed." else: ret["result"] = False ret["comment"] = "Could not remove package." return ret
def test_warn_until_warning_raised(self): # We *always* want *all* warnings thrown on this module warnings.filterwarnings('always', '', DeprecationWarning, __name__) def raise_warning(_version_info_=(0, 16, 0)): warn_until( (0, 17), 'Deprecation Message!', _version_info_=_version_info_ ) def raise_named_version_warning(_version_info_=(0, 16, 0)): warn_until( 'Hydrogen', 'Deprecation Message!', _version_info_=_version_info_ ) # raise_warning should show warning until version info is >= (0, 17) with warnings.catch_warnings(record=True) as recorded_warnings: raise_warning() self.assertEqual( 'Deprecation Message!', str(recorded_warnings[0].message) ) # raise_warning should show warning until version info is >= (0, 17) with warnings.catch_warnings(record=True) as recorded_warnings: raise_named_version_warning() self.assertEqual( 'Deprecation Message!', str(recorded_warnings[0].message) ) # the deprecation warning is not issued because we passed # _dont_call_warning with warnings.catch_warnings(record=True) as recorded_warnings: warn_until( (0, 17), 'Foo', _dont_call_warnings=True, _version_info_=(0, 16) ) self.assertEqual(0, len(recorded_warnings)) # Let's set version info to (0, 17), a RuntimeError should be raised with self.assertRaisesRegexp( RuntimeError, r'The warning triggered on filename \'(.*)warnings_test.py\', ' r'line number ([\d]+), is supposed to be shown until version ' r'\'0.17.0\' is released. Current version is now \'0.17.0\'. ' r'Please remove the warning.'): raise_warning(_version_info_=(0, 17, 0)) # Let's set version info to (0, 17), a RuntimeError should be raised with self.assertRaisesRegexp( RuntimeError, r'The warning triggered on filename \'(.*)warnings_test.py\', ' r'line number ([\d]+), is supposed to be shown until version ' r'\'Hydrogen((.*))\' is released. Current version is now ' r'\'([\d.]+)\'. Please remove the warning.'): raise_named_version_warning(_version_info_=(sys.maxint, 16, 0)) # Even though we're calling warn_until, we pass _dont_call_warnings # because we're only after the RuntimeError with self.assertRaisesRegexp( RuntimeError, r'The warning triggered on filename \'(.*)warnings_test.py\', ' r'line number ([\d]+), is supposed to be shown until version ' r'\'0.17.0\' is released. Current version is now ' r'\'([\d.]+)\'. Please remove the warning.'): warn_until( (0, 17), 'Foo', _dont_call_warnings=True ) with self.assertRaisesRegexp( RuntimeError, r'The warning triggered on filename \'(.*)warnings_test.py\', ' r'line number ([\d]+), is supposed to be shown until version ' r'\'Hydrogen((.*))\' is released. Current version is now ' r'\'([\d.]+)\'. Please remove the warning.'): warn_until( 'Hydrogen', 'Foo', _dont_call_warnings=True, _version_info_=(sys.maxint, 16, 0) ) # version on the deprecation message gets properly formatted with warnings.catch_warnings(record=True) as recorded_warnings: vrs = SaltStackVersion.from_name('Helium') warn_until( 'Helium', 'Deprecation Message until {version}!', _version_info_=(vrs.major - 1, 0) ) self.assertEqual( 'Deprecation Message until {0}!'.format(vrs.formatted_version), str(recorded_warnings[0].message) )
def installed(name, pip_bin=None, requirements=None, env=None, bin_env=None, use_wheel=False, no_use_wheel=False, log=None, proxy=None, timeout=None, repo=None, editable=None, find_links=None, index_url=None, extra_index_url=None, no_index=False, mirrors=None, build=None, target=None, download=None, download_cache=None, source=None, upgrade=False, force_reinstall=False, ignore_installed=False, exists_action=None, no_deps=False, no_install=False, no_download=False, install_options=None, global_options=None, user=None, runas=None, no_chown=False, cwd=None, activate=False, pre_releases=False, cert=None, allow_all_external=False, allow_external=None, allow_unverified=None, process_dependency_links=False, env_vars=None, use_vt=False): ''' Make sure the package is installed name The name of the python package to install. You can also specify version numbers here using the standard operators ``==, >=, <=``. If ``requirements`` is given, this parameter will be ignored. Example: .. code-block:: yaml django: pip.installed: - name: django >= 1.6, <= 1.7 - require: - pkg: python-pip This will install the latest Django version greater than 1.6 but less than 1.7. requirements Path to a pip requirements file. If the path begins with salt:// the file will be transferred from the master file server. user The user under which to run pip use_wheel : False Prefer wheel archives (requires pip>=1.4) no_use_wheel : False Force to not use wheel archives (requires pip>=1.4) log Log file where a complete (maximum verbosity) record will be kept proxy Specify a proxy in the form user:[email protected]:port. Note that the user:password@ is optional and required only if you are behind an authenticated proxy. If you provide [email protected]:port then you will be prompted for a password. timeout Set the socket timeout (default 15 seconds) editable install something editable (i.e. git+https://github.com/worldcompany/djangoembed.git#egg=djangoembed) find_links URL to look for packages at index_url Base URL of Python Package Index extra_index_url Extra URLs of package indexes to use in addition to ``index_url`` no_index Ignore package index mirrors Specific mirror URL(s) to query (automatically adds --use-mirrors) build Unpack packages into ``build`` dir target Install packages into ``target`` dir download Download packages into ``download`` instead of installing them download_cache Cache downloaded packages in ``download_cache`` dir source Check out ``editable`` packages into ``source`` dir upgrade Upgrade all packages to the newest available version force_reinstall When upgrading, reinstall all packages even if they are already up-to-date. ignore_installed Ignore the installed packages (reinstalling instead) exists_action Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup no_deps Ignore package dependencies no_install Download and unpack all packages, but don't actually install them no_chown When user is given, do not attempt to copy and chown a requirements file cwd Current working directory to run pip from activate Activates the virtual environment, if given via bin_env, before running install. pre_releases Include pre-releases in the available versions cert Provide a path to an alternate CA bundle allow_all_external Allow the installation of all externally hosted files allow_external Allow the installation of externally hosted files (comma separated list) allow_unverified Allow the installation of insecure and unverifiable files (comma separated list) process_dependency_links Enable the processing of dependency links bin_env : None Absolute path to a virtual environment directory or absolute path to a pip executable. The example below assumes a virtual environment has been created at ``/foo/.virtualenvs/bar``. env_vars Add or modify environment variables. Useful for tweaking build steps, such as specifying INCLUDE or LIBRARY paths in Makefiles, build scripts or compiler calls. use_vt Use VT terminal emulation (see ouptut while installing) Example: .. code-block:: yaml django: pip.installed: - name: django >= 1.6, <= 1.7 - bin_env: /foo/.virtualenvs/bar - require: - pkg: python-pip Or Example: .. code-block:: yaml django: pip.installed: - name: django >= 1.6, <= 1.7 - bin_env: /foo/.virtualenvs/bar/bin/pip - require: - pkg: python-pip .. admonition:: Attention The following arguments are deprecated, do not use. pip_bin : None Deprecated, use ``bin_env`` env : None Deprecated, use ``bin_env`` .. versionchanged:: 0.17.0 ``use_wheel`` option added. install_options Extra arguments to be supplied to the setup.py install command. If you are using an option with a directory path, be sure to use absolute path. Example: .. code-block:: yaml django: pip.installed: - name: django - install_options: - --prefix=/blah - require: - pkg: python-pip global_options Extra global options to be supplied to the setup.py call before the install command. .. versionadded:: 2014.1.3 .. admonition:: Attention As of Salt 0.17.0 the pip state **needs** an importable pip module. This usually means having the system's pip package installed or running Salt from an active `virtualenv`_. The reason for this requirement is because ``pip`` already does a pretty good job parsing its own requirements. It makes no sense for Salt to do ``pip`` requirements parsing and validation before passing them to the ``pip`` library. It's functionality duplication and it's more error prone. .. _`virtualenv`: http://www.virtualenv.org/en/latest/ ''' if pip_bin and not bin_env: bin_env = pip_bin elif env and not bin_env: bin_env = env ret = {'name': name, 'result': None, 'comment': '', 'changes': {}} if use_wheel: min_version = '1.4' cur_version = __salt__['pip.version'](bin_env) if not salt.utils.compare_versions( ver1=cur_version, oper='>=', ver2=min_version): ret['result'] = False ret['comment'] = ('The \'use_wheel\' option is only supported in ' 'pip {0} and newer. The version of pip detected ' 'was {1}.').format(min_version, cur_version) return ret if no_use_wheel: min_version = '1.4' cur_version = __salt__['pip.version'](bin_env) if not salt.utils.compare_versions( ver1=cur_version, oper='>=', ver2=min_version): ret['result'] = False ret['comment'] = ( 'The \'no_use_wheel\' option is only supported in ' 'pip {0} and newer. The version of pip detected ' 'was {1}.').format(min_version, cur_version) return ret if repo is not None: msg = ('The \'repo\' argument to pip.installed is deprecated and will ' 'be removed in Salt {version}. Please use \'name\' instead. ' 'The current value for name, {0!r} will be replaced by the ' 'value of repo, {1!r}'.format( name, repo, version=_SaltStackVersion.from_name( 'Lithium').formatted_version)) salt.utils.warn_until('Lithium', msg) ret.setdefault('warnings', []).append(msg) name = repo from_vcs = False if name and not requirements: try: try: # With pip < 1.2, the __version__ attribute does not exist and # vcs+URL urls are not properly parsed. # The next line is meant to trigger an AttributeError and # handle lower pip versions logger.debug('Installed pip version: {0}'.format( pip.__version__)) install_req = pip.req.InstallRequirement.from_line(name) except AttributeError: logger.debug('Installed pip version is lower than 1.2') supported_vcs = ('git', 'svn', 'hg', 'bzr') if name.startswith(supported_vcs): for vcs in supported_vcs: if name.startswith(vcs): from_vcs = True install_req = pip.req.InstallRequirement.from_line( name.split('{0}+'.format(vcs))[-1]) break else: install_req = pip.req.InstallRequirement.from_line(name) except ValueError as exc: ret['result'] = False if not from_vcs and '=' in name and '==' not in name: ret['comment'] = ( 'Invalid version specification in package {0}. \'=\' is ' 'not supported, use \'==\' instead.'.format(name)) return ret ret['comment'] = ( 'pip raised an exception while parsing {0!r}: {1}'.format( name, exc)) return ret if install_req.req is None: # This is most likely an url and there's no way to know what will # be installed before actually installing it. prefix = '' version_spec = [] else: prefix = install_req.req.project_name version_spec = install_req.req.specs else: prefix = '' version_spec = [] if runas is not None: # The user is using a deprecated argument, warn! msg = ('The \'runas\' argument to pip.installed is deprecated, and ' 'will be removed in Salt {version}. Please use \'user\' ' 'instead.'.format(version=_SaltStackVersion.from_name( 'Lithium').formatted_version)) salt.utils.warn_until('Lithium', msg) ret.setdefault('warnings', []).append(msg) # "There can only be one" if user: raise CommandExecutionError( 'The \'runas\' and \'user\' arguments are mutually exclusive. ' 'Please use \'user\' as \'runas\' is being deprecated.') # Support deprecated 'runas' arg else: user = runas # Replace commas (used for version ranges) with semicolons (which are not # supported) in name so it does not treat them as multiple packages. Comma # will be re-added in pip.install call. name = name.replace(',', ';') # If a requirements file is specified, only install the contents of the # requirements file. Similarly, using the --editable flag with pip should # also ignore the "name" parameter. if requirements or editable: name = '' comments = [] if __opts__['test']: ret['result'] = None if requirements: # TODO: Check requirements file against currently-installed # packages to provide more accurate state output. comments.append('Requirements file {0!r} will be ' 'processed.'.format(requirements)) if editable: comments.append( 'Package will be installed in editable mode (i.e. ' 'setuptools "develop mode") from {0}.'.format(editable)) ret['comment'] = ' '.join(comments) return ret else: try: pip_list = __salt__['pip.list'](prefix, bin_env=bin_env, user=user, cwd=cwd) prefix_realname = _find_key(prefix, pip_list) except (CommandNotFoundError, CommandExecutionError) as err: ret['result'] = False ret['comment'] = 'Error installing {0!r}: {1}'.format(name, err) return ret if ignore_installed is False and prefix_realname is not None: if force_reinstall is False and not upgrade: # Check desired version (if any) against currently-installed if (any(version_spec) and _fulfills_version_spec( pip_list[prefix_realname], version_spec)) or (not any(version_spec)): ret['result'] = True ret['comment'] = ('Python package {0} already ' 'installed'.format(name)) return ret if __opts__['test']: ret['result'] = None ret['comment'] = \ 'Python package {0} is set to be installed'.format(name) return ret pip_install_call = __salt__['pip.install']( pkgs='{0}'.format(name) if name else '', requirements=requirements, bin_env=bin_env, use_wheel=use_wheel, no_use_wheel=no_use_wheel, log=log, proxy=proxy, timeout=timeout, editable=editable, find_links=find_links, index_url=index_url, extra_index_url=extra_index_url, no_index=no_index, mirrors=mirrors, build=build, target=target, download=download, download_cache=download_cache, source=source, upgrade=upgrade, force_reinstall=force_reinstall, ignore_installed=ignore_installed, exists_action=exists_action, no_deps=no_deps, no_install=no_install, no_download=no_download, install_options=install_options, global_options=global_options, user=user, no_chown=no_chown, cwd=cwd, activate=activate, pre_releases=pre_releases, cert=cert, allow_all_external=allow_all_external, allow_external=allow_external, allow_unverified=allow_unverified, process_dependency_links=process_dependency_links, saltenv=__env__, env_vars=env_vars, use_vt=use_vt) if pip_install_call and (pip_install_call.get('retcode', 1) == 0): ret['result'] = True if requirements or editable: comments = [] if requirements: for eachline in pip_install_call.get('stdout', '').split('\n'): if not eachline.startswith( 'Requirement already satisfied' ) and eachline != 'Cleaning up...': ret['changes']['requirements'] = True if ret['changes'].get('requirements'): comments.append('Successfully processed requirements file ' '{0}.'.format(requirements)) else: comments.append('Requirements was successfully installed') comments.append('Successfully processed requirements file ' '{0}.'.format(requirements)) if editable: comments.append('Package successfully installed from VCS ' 'checkout {0}.'.format(editable)) ret['changes']['editable'] = True ret['comment'] = ' '.join(comments) else: if not prefix: pkg_list = {} else: pkg_list = __salt__['pip.list'](prefix, bin_env, user=user, cwd=cwd) if not pkg_list: ret['comment'] = ( 'There was no error installing package \'{0}\' although ' 'it does not show when calling ' '\'pip.freeze\'.'.format(name)) ret['changes']['{0}==???'.format(name)] = 'Installed' return ret version = next(pkg_list.itervalues()) pkg_name = next(iter(pkg_list)) ret['changes']['{0}=={1}'.format(pkg_name, version)] = 'Installed' ret['comment'] = 'Package was successfully installed' elif pip_install_call: ret['result'] = False if 'stdout' in pip_install_call: error = 'Error: {0} {1}'.format(pip_install_call['stdout'], pip_install_call['stderr']) else: error = 'Error: {0}'.format(pip_install_call['comment']) if requirements or editable: comments = [] if requirements: comments.append('Unable to process requirements file ' '{0}.'.format(requirements)) if editable: comments.append('Unable to install from VCS checkout' '{0}.'.format(editable)) comments.append(error) ret['comment'] = ' '.join(comments) else: ret['comment'] = ('Failed to install package {0}. ' '{1}'.format(name, error)) else: ret['result'] = False ret['comment'] = 'Could not install package' return ret
import re # Import third party libs #import salt.ext.six as six # Import salt libs from salt.exceptions import ( #CommandExecutionError, SaltInvocationError) from salt.utils import warn_until from salt.version import (__version__, SaltStackVersion) # is there not SaltStackVersion.current() to get # the version of the salt running this code?? _version_ary = __version__.split('.') CUR_VER = SaltStackVersion(_version_ary[0], _version_ary[1]) BORON = SaltStackVersion.from_name('Boron') # pylint: disable=import-error HAS_GLANCE = False try: from glanceclient import client from glanceclient import exc HAS_GLANCE = True except ImportError: pass # Workaround, as the Glance API v2 requires you to # already have a keystone session token HAS_KEYSTONE = False try:
def installed(name, pkgs=None, pip_bin=None, requirements=None, env=None, bin_env=None, use_wheel=False, no_use_wheel=False, log=None, proxy=None, timeout=None, repo=None, editable=None, find_links=None, index_url=None, extra_index_url=None, no_index=False, mirrors=None, build=None, target=None, download=None, download_cache=None, source=None, upgrade=False, force_reinstall=False, ignore_installed=False, exists_action=None, no_deps=False, no_install=False, no_download=False, install_options=None, global_options=None, user=None, no_chown=False, cwd=None, activate=False, pre_releases=False, cert=None, allow_all_external=False, allow_external=None, allow_unverified=None, process_dependency_links=False, env_vars=None, use_vt=False, trusted_host=None): ''' Make sure the package is installed name The name of the python package to install. You can also specify version numbers here using the standard operators ``==, >=, <=``. If ``requirements`` is given, this parameter will be ignored. Example: .. code-block:: yaml django: pip.installed: - name: django >= 1.6, <= 1.7 - require: - pkg: python-pip This will install the latest Django version greater than 1.6 but less than 1.7. requirements Path to a pip requirements file. If the path begins with salt:// the file will be transferred from the master file server. user The user under which to run pip use_wheel : False Prefer wheel archives (requires pip>=1.4) no_use_wheel : False Force to not use wheel archives (requires pip>=1.4) log Log file where a complete (maximum verbosity) record will be kept proxy Specify a proxy in the form user:[email protected]:port. Note that the user:password@ is optional and required only if you are behind an authenticated proxy. If you provide [email protected]:port then you will be prompted for a password. timeout Set the socket timeout (default 15 seconds) editable install something editable (i.e. git+https://github.com/worldcompany/djangoembed.git#egg=djangoembed) find_links URL to look for packages at index_url Base URL of Python Package Index extra_index_url Extra URLs of package indexes to use in addition to ``index_url`` no_index Ignore package index mirrors Specific mirror URL(s) to query (automatically adds --use-mirrors) build Unpack packages into ``build`` dir target Install packages into ``target`` dir download Download packages into ``download`` instead of installing them download_cache Cache downloaded packages in ``download_cache`` dir source Check out ``editable`` packages into ``source`` dir upgrade Upgrade all packages to the newest available version force_reinstall When upgrading, reinstall all packages even if they are already up-to-date. ignore_installed Ignore the installed packages (reinstalling instead) exists_action Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup no_deps Ignore package dependencies no_install Download and unpack all packages, but don't actually install them no_chown When user is given, do not attempt to copy and chown a requirements file cwd Current working directory to run pip from activate Activates the virtual environment, if given via bin_env, before running install. .. deprecated:: 2014.7.2 If `bin_env` is given, pip will already be sourced from that virtualenv, making `activate` effectively a noop. pre_releases Include pre-releases in the available versions cert Provide a path to an alternate CA bundle allow_all_external Allow the installation of all externally hosted files allow_external Allow the installation of externally hosted files (comma separated list) allow_unverified Allow the installation of insecure and unverifiable files (comma separated list) process_dependency_links Enable the processing of dependency links bin_env : None Absolute path to a virtual environment directory or absolute path to a pip executable. The example below assumes a virtual environment has been created at ``/foo/.virtualenvs/bar``. env_vars Add or modify environment variables. Useful for tweaking build steps, such as specifying INCLUDE or LIBRARY paths in Makefiles, build scripts or compiler calls. This must be in the form of a dictionary or a mapping. Example: .. code-block:: yaml django: pip.installed: - name: django_app - env_vars: CUSTOM_PATH: /opt/django_app VERBOSE: True use_vt Use VT terminal emulation (see output while installing) trusted_host Mark this host as trusted, even though it does not have valid or any HTTPS. Example: .. code-block:: yaml django: pip.installed: - name: django >= 1.6, <= 1.7 - bin_env: /foo/.virtualenvs/bar - require: - pkg: python-pip Or Example: .. code-block:: yaml django: pip.installed: - name: django >= 1.6, <= 1.7 - bin_env: /foo/.virtualenvs/bar/bin/pip - require: - pkg: python-pip .. admonition:: Attention The following arguments are deprecated, do not use. pip_bin : None Deprecated, use ``bin_env`` env : None Deprecated, use ``bin_env`` .. versionchanged:: 0.17.0 ``use_wheel`` option added. install_options Extra arguments to be supplied to the setup.py install command. If you are using an option with a directory path, be sure to use absolute path. Example: .. code-block:: yaml django: pip.installed: - name: django - install_options: - --prefix=/blah - require: - pkg: python-pip global_options Extra global options to be supplied to the setup.py call before the install command. .. versionadded:: 2014.1.3 .. admonition:: Attention As of Salt 0.17.0 the pip state **needs** an importable pip module. This usually means having the system's pip package installed or running Salt from an active `virtualenv`_. The reason for this requirement is because ``pip`` already does a pretty good job parsing its own requirements. It makes no sense for Salt to do ``pip`` requirements parsing and validation before passing them to the ``pip`` library. It's functionality duplication and it's more error prone. .. admonition:: Attention Please set ``reload_modules: True`` to have the salt minion import this module after installation. Example: .. code-block:: yaml pyopenssl: pip.installed: - name: pyOpenSSL - reload_modules: True - exists_action: i .. _`virtualenv`: http://www.virtualenv.org/en/latest/ ''' if pip_bin and not bin_env: bin_env = pip_bin elif env and not bin_env: bin_env = env # If pkgs is present, ignore name if pkgs: if not isinstance(pkgs, list): return {'name': name, 'result': False, 'changes': {}, 'comment': 'pkgs argument must be formatted as a list'} else: pkgs = [name] # Assumption: If `pkg` is not an `string`, it's a `collections.OrderedDict` # prepro = lambda pkg: pkg if type(pkg) == str else \ # ' '.join((pkg.items()[0][0], pkg.items()[0][1].replace(',', ';'))) # pkgs = ','.join([prepro(pkg) for pkg in pkgs]) prepro = lambda pkg: pkg if isinstance(pkg, str) else \ ' '.join((six.iteritems(pkg)[0][0], six.iteritems(pkg)[0][1])) pkgs = [prepro(pkg) for pkg in pkgs] ret = {'name': ';'.join(pkgs), 'result': None, 'comment': '', 'changes': {}} # Check that the pip binary supports the 'use_wheel' option if use_wheel: min_version = '1.4' cur_version = __salt__['pip.version'](bin_env) if not salt.utils.compare_versions(ver1=cur_version, oper='>=', ver2=min_version): ret['result'] = False ret['comment'] = ('The \'use_wheel\' option is only supported in ' 'pip {0} and newer. The version of pip detected ' 'was {1}.').format(min_version, cur_version) return ret # Check that the pip binary supports the 'no_use_wheel' option if no_use_wheel: min_version = '1.4' cur_version = __salt__['pip.version'](bin_env) if not salt.utils.compare_versions(ver1=cur_version, oper='>=', ver2=min_version): ret['result'] = False ret['comment'] = ('The \'no_use_wheel\' option is only supported in ' 'pip {0} and newer. The version of pip detected ' 'was {1}.').format(min_version, cur_version) return ret # Deprecation warning for the repo option if repo is not None: msg = ('The \'repo\' argument to pip.installed is deprecated and will ' 'be removed in Salt {version}. Please use \'name\' instead. ' 'The current value for name, {0!r} will be replaced by the ' 'value of repo, {1!r}'.format( name, repo, version=_SaltStackVersion.from_name('Lithium').formatted_version )) salt.utils.warn_until('Lithium', msg) ret.setdefault('warnings', []).append(msg) name = repo # Get the packages parsed name and version from the pip library. # This only is done when there is no requirements or editable parameter. pkgs_details = [] if pkgs and not (requirements or editable): comments = [] for pkg in iter(pkgs): out = _check_pkg_version_format(pkg) if out['result'] is False: ret['result'] = False comments.append(out['comment']) elif out['result'] is True: pkgs_details.append((out['prefix'], pkg, out['version_spec'])) if ret['result'] is False: ret['comment'] = '\n'.join(comments) return ret # If a requirements file is specified, only install the contents of the # requirements file. Similarly, using the --editable flag with pip should # also ignore the "name" and "pkgs" parameters. target_pkgs = [] already_installed_comments = [] if requirements or editable: comments = [] # Append comments if this is a dry run. if __opts__['test']: ret['result'] = None if requirements: # TODO: Check requirements file against currently-installed # packages to provide more accurate state output. comments.append('Requirements file {0!r} will be ' 'processed.'.format(requirements)) if editable: comments.append( 'Package will be installed in editable mode (i.e. ' 'setuptools "develop mode") from {0}.'.format(editable) ) ret['comment'] = ' '.join(comments) return ret # No requirements case. # Check pre-existence of the requested packages. else: for prefix, state_pkg_name, version_spec in pkgs_details: if prefix: state_pkg_name = state_pkg_name version_spec = version_spec out = _check_if_installed(prefix, state_pkg_name, version_spec, ignore_installed, force_reinstall, upgrade, user, cwd, bin_env) # If _check_if_installed result is None, something went wrong with # the command running. This way we keep stateful output. if out['result'] is None: ret['result'] = False ret['comment'] = out['comment'] return ret else: out = {'result': False, 'comment': None} result = out['result'] # The package is not present. Add it to the pkgs to install. if result is False: # Replace commas (used for version ranges) with semicolons # (which are not supported) in name so it does not treat # them as multiple packages. target_pkgs.append((prefix, state_pkg_name.replace(',', ';'))) # Append comments if this is a dry run. if __opts__['test']: msg = 'Python package {0} is set to be installed' ret['result'] = None ret['comment'] = msg.format(state_pkg_name) return ret # The package is already present and will not be reinstalled. elif result is True: # Append comment stating its presence already_installed_comments.append(out['comment']) # The command pip.list failed. Abort. elif result is None: ret['result'] = None ret['comment'] = out['comment'] return ret # Construct the string that will get passed to the install call pkgs_str = ','.join([state_name for _, state_name in target_pkgs]) # Call to install the package. Actual installation takes place here pip_install_call = __salt__['pip.install']( pkgs='{0}'.format(pkgs_str) if pkgs_str else '', requirements=requirements, bin_env=bin_env, use_wheel=use_wheel, no_use_wheel=no_use_wheel, log=log, proxy=proxy, timeout=timeout, editable=editable, find_links=find_links, index_url=index_url, extra_index_url=extra_index_url, no_index=no_index, mirrors=mirrors, build=build, target=target, download=download, download_cache=download_cache, source=source, upgrade=upgrade, force_reinstall=force_reinstall, ignore_installed=ignore_installed, exists_action=exists_action, no_deps=no_deps, no_install=no_install, no_download=no_download, install_options=install_options, global_options=global_options, user=user, no_chown=no_chown, cwd=cwd, activate=activate, pre_releases=pre_releases, cert=cert, allow_all_external=allow_all_external, allow_external=allow_external, allow_unverified=allow_unverified, process_dependency_links=process_dependency_links, saltenv=__env__, env_vars=env_vars, use_vt=use_vt, trusted_host=trusted_host ) # 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: comments = [] if requirements: for line in pip_install_call.get('stdout', '').split('\n'): if not line.startswith('Requirement already satisfied') \ and line != 'Cleaning up...': ret['changes']['requirements'] = True if ret['changes'].get('requirements'): comments.append('Successfully processed requirements file ' '{0}.'.format(requirements)) else: comments.append('Requirements were already installed.') if editable: comments.append('Package successfully installed from VCS ' 'checkout {0}.'.format(editable)) ret['changes']['editable'] = True ret['comment'] = ' '.join(comments) else: # Check that the packages set to be installed were installed. # Create comments reporting success and failures pkg_404_comms = [] for prefix, state_name in target_pkgs: # Case for packages that are not an URL if prefix: pipsearch = __salt__['pip.list'](prefix, bin_env, user=user, cwd=cwd) # If we didnt find the package in the system after # installing it report it if not pipsearch: pkg_404_comms.append( 'There was no error installing package \'{0}\' ' 'although it does not show when calling ' '\'pip.freeze\'.'.format(pkg) ) else: pkg_name = _find_key(prefix, pipsearch) ver = pipsearch[pkg_name] ret['changes']['{0}=={1}'.format(pkg_name, ver)] = 'Installed' # Case for packages that are an URL else: ret['changes']['{0}==???'.format(state_name)] = 'Installed' # Set comments aicomms = '\n'.join(already_installed_comments) succ_comm = 'All packages were successfully installed'\ if not pkg_404_comms else '\n'.join(pkg_404_comms) ret['comment'] = aicomms + ('\n' if aicomms else '') + succ_comm return ret elif pip_install_call: ret['result'] = False if 'stdout' in pip_install_call: error = 'Error: {0} {1}'.format(pip_install_call['stdout'], pip_install_call['stderr']) else: error = 'Error: {0}'.format(pip_install_call['comment']) if requirements or editable: comments = [] if requirements: comments.append('Unable to process requirements file ' '"{0}".'.format(requirements)) if editable: comments.append('Unable to install from VCS checkout' '{0}.'.format(editable)) comments.append(error) ret['comment'] = ' '.join(comments) else: pkgs_str = ', '.join([state_name for _, state_name in target_pkgs]) aicomms = '\n'.join(already_installed_comments) error_comm = ('Failed to install packages: {0}. ' '{1}'.format(pkgs_str, error)) ret['comment'] = aicomms + ('\n' if aicomms else '') + error_comm else: ret['result'] = False ret['comment'] = 'Could not install package' return ret
def test_version_comparison(higher_version, lower_version): assert SaltStackVersion.parse(higher_version) > lower_version assert SaltStackVersion.parse(lower_version) < higher_version assert SaltStackVersion.parse(lower_version) != higher_version
def removed(name, requirements=None, bin_env=None, log=None, proxy=None, timeout=None, user=None, runas=None, cwd=None, use_vt=False): ''' Make sure that a package is not installed. name The name of the package to uninstall user The user under which to run pip bin_env : None the pip executable or virtualenenv to use use_vt Use VT terminal emulation (see ouptut while installing) ''' ret = {'name': name, 'result': None, 'comment': '', 'changes': {}} if runas is not None: # The user is using a deprecated argument, warn! msg = ('The \'runas\' argument to pip.installed is deprecated, and ' 'will be removed in Salt {version}. Please use \'user\' ' 'instead.'.format( version=_SaltStackVersion.from_name('Lithium').formatted_version )) salt.utils.warn_until('Lithium', msg) ret.setdefault('warnings', []).append(msg) # "There can only be one" if runas is not None and user: raise CommandExecutionError( 'The \'runas\' and \'user\' arguments are mutually exclusive. ' 'Please use \'user\' as \'runas\' is being deprecated.' ) # Support deprecated 'runas' arg elif runas is not None and not user: user = runas try: pip_list = __salt__['pip.list'](bin_env=bin_env, user=user, cwd=cwd) except (CommandExecutionError, CommandNotFoundError) as err: ret['result'] = False ret['comment'] = 'Error uninstalling \'{0}\': {1}'.format(name, err) return ret if name not in pip_list: ret['result'] = True ret['comment'] = 'Package is not installed.' return ret if __opts__['test']: ret['result'] = None ret['comment'] = 'Package {0} is set to be removed'.format(name) return ret if __salt__['pip.uninstall'](pkgs=name, requirements=requirements, bin_env=bin_env, log=log, proxy=proxy, timeout=timeout, user=user, cwd=cwd, use_vt=use_vt): ret['result'] = True ret['changes'][name] = 'Removed' ret['comment'] = 'Package was successfully removed.' else: ret['result'] = False ret['comment'] = 'Could not remove package.' return ret
def test_unparsable_version_from_name(): with pytest.raises(ValueError): SaltStackVersion.from_name("Drunk")
def installed(name, pip_bin=None, requirements=None, env=None, bin_env=None, use_wheel=False, no_use_wheel=False, log=None, proxy=None, timeout=None, repo=None, editable=None, find_links=None, index_url=None, extra_index_url=None, no_index=False, mirrors=None, build=None, target=None, download=None, download_cache=None, source=None, upgrade=False, force_reinstall=False, ignore_installed=False, exists_action=None, no_deps=False, no_install=False, no_download=False, install_options=None, global_options=None, user=None, runas=None, no_chown=False, cwd=None, activate=False, pre_releases=False, cert=None, allow_all_external=False, allow_external=None, allow_unverified=None, process_dependency_links=False, env_vars=None, use_vt=False): ''' Make sure the package is installed name The name of the python package to install. You can also specify version numbers here using the standard operators ``==, >=, <=``. If ``requirements`` is given, this parameter will be ignored. Example: .. code-block:: yaml django: pip.installed: - name: django >= 1.6, <= 1.7 - require: - pkg: python-pip This will install the latest Django version greater than 1.6 but less than 1.7. requirements Path to a pip requirements file. If the path begins with salt:// the file will be transferred from the master file server. user The user under which to run pip use_wheel : False Prefer wheel archives (requires pip>=1.4) no_use_wheel : False Force to not use wheel archives (requires pip>=1.4) log Log file where a complete (maximum verbosity) record will be kept proxy Specify a proxy in the form user:[email protected]:port. Note that the user:password@ is optional and required only if you are behind an authenticated proxy. If you provide [email protected]:port then you will be prompted for a password. timeout Set the socket timeout (default 15 seconds) editable install something editable (i.e. git+https://github.com/worldcompany/djangoembed.git#egg=djangoembed) find_links URL to look for packages at index_url Base URL of Python Package Index extra_index_url Extra URLs of package indexes to use in addition to ``index_url`` no_index Ignore package index mirrors Specific mirror URL(s) to query (automatically adds --use-mirrors) build Unpack packages into ``build`` dir target Install packages into ``target`` dir download Download packages into ``download`` instead of installing them download_cache Cache downloaded packages in ``download_cache`` dir source Check out ``editable`` packages into ``source`` dir upgrade Upgrade all packages to the newest available version force_reinstall When upgrading, reinstall all packages even if they are already up-to-date. ignore_installed Ignore the installed packages (reinstalling instead) exists_action Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup no_deps Ignore package dependencies no_install Download and unpack all packages, but don't actually install them no_chown When user is given, do not attempt to copy and chown a requirements file cwd Current working directory to run pip from activate Activates the virtual environment, if given via bin_env, before running install. pre_releases Include pre-releases in the available versions cert Provide a path to an alternate CA bundle allow_all_external Allow the installation of all externally hosted files allow_external Allow the installation of externally hosted files (comma separated list) allow_unverified Allow the installation of insecure and unverifiable files (comma separated list) process_dependency_links Enable the processing of dependency links bin_env : None Absolute path to a virtual environment directory or absolute path to a pip executable. The example below assumes a virtual environment has been created at ``/foo/.virtualenvs/bar``. env_vars Add or modify environment variables. Useful for tweaking build steps, such as specifying INCLUDE or LIBRARY paths in Makefiles, build scripts or compiler calls. use_vt Use VT terminal emulation (see ouptut while installing) Example: .. code-block:: yaml django: pip.installed: - name: django >= 1.6, <= 1.7 - bin_env: /foo/.virtualenvs/bar - require: - pkg: python-pip Or Example: .. code-block:: yaml django: pip.installed: - name: django >= 1.6, <= 1.7 - bin_env: /foo/.virtualenvs/bar/bin/pip - require: - pkg: python-pip .. admonition:: Attention The following arguments are deprecated, do not use. pip_bin : None Deprecated, use ``bin_env`` env : None Deprecated, use ``bin_env`` .. versionchanged:: 0.17.0 ``use_wheel`` option added. install_options Extra arguments to be supplied to the setup.py install command. If you are using an option with a directory path, be sure to use absolute path. Example: .. code-block:: yaml django: pip.installed: - name: django - install_options: - --prefix=/blah - require: - pkg: python-pip global_options Extra global options to be supplied to the setup.py call before the install command. .. versionadded:: 2014.1.3 .. admonition:: Attention As of Salt 0.17.0 the pip state **needs** an importable pip module. This usually means having the system's pip package installed or running Salt from an active `virtualenv`_. The reason for this requirement is because ``pip`` already does a pretty good job parsing its own requirements. It makes no sense for Salt to do ``pip`` requirements parsing and validation before passing them to the ``pip`` library. It's functionality duplication and it's more error prone. .. _`virtualenv`: http://www.virtualenv.org/en/latest/ ''' if pip_bin and not bin_env: bin_env = pip_bin elif env and not bin_env: bin_env = env ret = {'name': name, 'result': None, 'comment': '', 'changes': {}} if use_wheel: min_version = '1.4' cur_version = __salt__['pip.version'](bin_env) if not salt.utils.compare_versions(ver1=cur_version, oper='>=', ver2=min_version): ret['result'] = False ret['comment'] = ('The \'use_wheel\' option is only supported in ' 'pip {0} and newer. The version of pip detected ' 'was {1}.').format(min_version, cur_version) return ret if no_use_wheel: min_version = '1.4' cur_version = __salt__['pip.version'](bin_env) if not salt.utils.compare_versions(ver1=cur_version, oper='>=', ver2=min_version): ret['result'] = False ret['comment'] = ('The \'no_use_wheel\' option is only supported in ' 'pip {0} and newer. The version of pip detected ' 'was {1}.').format(min_version, cur_version) return ret if repo is not None: msg = ('The \'repo\' argument to pip.installed is deprecated and will ' 'be removed in Salt {version}. Please use \'name\' instead. ' 'The current value for name, {0!r} will be replaced by the ' 'value of repo, {1!r}'.format( name, repo, version=_SaltStackVersion.from_name('Lithium').formatted_version )) salt.utils.warn_until('Lithium', msg) ret.setdefault('warnings', []).append(msg) name = repo from_vcs = False if name and not requirements: try: try: # With pip < 1.2, the __version__ attribute does not exist and # vcs+URL urls are not properly parsed. # The next line is meant to trigger an AttributeError and # handle lower pip versions logger.debug( 'Installed pip version: {0}'.format(pip.__version__) ) install_req = pip.req.InstallRequirement.from_line(name) except AttributeError: logger.debug('Installed pip version is lower than 1.2') supported_vcs = ('git', 'svn', 'hg', 'bzr') if name.startswith(supported_vcs): for vcs in supported_vcs: if name.startswith(vcs): from_vcs = True install_req = pip.req.InstallRequirement.from_line( name.split('{0}+'.format(vcs))[-1] ) break else: install_req = pip.req.InstallRequirement.from_line(name) except ValueError as exc: ret['result'] = False if not from_vcs and '=' in name and '==' not in name: ret['comment'] = ( 'Invalid version specification in package {0}. \'=\' is ' 'not supported, use \'==\' instead.'.format(name) ) return ret ret['comment'] = ( 'pip raised an exception while parsing {0!r}: {1}'.format( name, exc ) ) return ret if install_req.req is None: # This is most likely an url and there's no way to know what will # be installed before actually installing it. prefix = '' version_spec = [] else: prefix = install_req.req.project_name version_spec = install_req.req.specs else: prefix = '' version_spec = [] if runas is not None: # The user is using a deprecated argument, warn! msg = ('The \'runas\' argument to pip.installed is deprecated, and ' 'will be removed in Salt {version}. Please use \'user\' ' 'instead.'.format( version=_SaltStackVersion.from_name('Lithium').formatted_version )) salt.utils.warn_until('Lithium', msg) ret.setdefault('warnings', []).append(msg) # "There can only be one" if user: raise CommandExecutionError( 'The \'runas\' and \'user\' arguments are mutually exclusive. ' 'Please use \'user\' as \'runas\' is being deprecated.' ) # Support deprecated 'runas' arg else: user = runas # Replace commas (used for version ranges) with semicolons (which are not # supported) in name so it does not treat them as multiple packages. Comma # will be re-added in pip.install call. name = name.replace(',', ';') # If a requirements file is specified, only install the contents of the # requirements file. Similarly, using the --editable flag with pip should # also ignore the "name" parameter. if requirements or editable: name = '' comments = [] if __opts__['test']: ret['result'] = None if requirements: # TODO: Check requirements file against currently-installed # packages to provide more accurate state output. comments.append('Requirements file {0!r} will be ' 'processed.'.format(requirements)) if editable: comments.append( 'Package will be installed in editable mode (i.e. ' 'setuptools "develop mode") from {0}.'.format(editable) ) ret['comment'] = ' '.join(comments) return ret else: try: pip_list = __salt__['pip.list'](prefix, bin_env=bin_env, user=user, cwd=cwd) prefix_realname = _find_key(prefix, pip_list) except (CommandNotFoundError, CommandExecutionError) as err: ret['result'] = False ret['comment'] = 'Error installing {0!r}: {1}'.format(name, err) return ret if ignore_installed is False and prefix_realname is not None: if force_reinstall is False and not upgrade: # Check desired version (if any) against currently-installed if ( any(version_spec) and _fulfills_version_spec(pip_list[prefix_realname], version_spec) ) or (not any(version_spec)): ret['result'] = True ret['comment'] = ('Python package {0} already ' 'installed'.format(name)) return ret if __opts__['test']: ret['result'] = None ret['comment'] = \ 'Python package {0} is set to be installed'.format(name) return ret pip_install_call = __salt__['pip.install']( pkgs='{0}'.format(name) if name else '', requirements=requirements, bin_env=bin_env, use_wheel=use_wheel, no_use_wheel=no_use_wheel, log=log, proxy=proxy, timeout=timeout, editable=editable, find_links=find_links, index_url=index_url, extra_index_url=extra_index_url, no_index=no_index, mirrors=mirrors, build=build, target=target, download=download, download_cache=download_cache, source=source, upgrade=upgrade, force_reinstall=force_reinstall, ignore_installed=ignore_installed, exists_action=exists_action, no_deps=no_deps, no_install=no_install, no_download=no_download, install_options=install_options, global_options=global_options, user=user, no_chown=no_chown, cwd=cwd, activate=activate, pre_releases=pre_releases, cert=cert, allow_all_external=allow_all_external, allow_external=allow_external, allow_unverified=allow_unverified, process_dependency_links=process_dependency_links, saltenv=__env__, env_vars=env_vars, use_vt=use_vt ) if pip_install_call and (pip_install_call.get('retcode', 1) == 0): ret['result'] = True if requirements or editable: comments = [] if requirements: for eachline in pip_install_call.get('stdout', '').split('\n'): if not eachline.startswith('Requirement already satisfied') and eachline != 'Cleaning up...': ret['changes']['requirements'] = True if ret['changes'].get('requirements'): comments.append('Successfully processed requirements file ' '{0}.'.format(requirements)) else: comments.append('Requirements was successfully installed') comments.append('Successfully processed requirements file ' '{0}.'.format(requirements)) if editable: comments.append('Package successfully installed from VCS ' 'checkout {0}.'.format(editable)) ret['changes']['editable'] = True ret['comment'] = ' '.join(comments) else: if not prefix: pkg_list = {} else: pkg_list = __salt__['pip.list']( prefix, bin_env, user=user, cwd=cwd ) if not pkg_list: ret['comment'] = ( 'There was no error installing package \'{0}\' although ' 'it does not show when calling ' '\'pip.freeze\'.'.format(name) ) ret['changes']['{0}==???'.format(name)] = 'Installed' return ret version = next(pkg_list.itervalues()) pkg_name = next(iter(pkg_list)) ret['changes']['{0}=={1}'.format(pkg_name, version)] = 'Installed' ret['comment'] = 'Package was successfully installed' elif pip_install_call: ret['result'] = False if 'stdout' in pip_install_call: error = 'Error: {0} {1}'.format(pip_install_call['stdout'], pip_install_call['stderr']) else: error = 'Error: {0}'.format(pip_install_call['comment']) if requirements or editable: comments = [] if requirements: comments.append('Unable to process requirements file ' '{0}.'.format(requirements)) if editable: comments.append('Unable to install from VCS checkout' '{0}.'.format(editable)) comments.append(error) ret['comment'] = ' '.join(comments) else: ret['comment'] = ('Failed to install package {0}. ' '{1}'.format(name, error)) else: ret['result'] = False ret['comment'] = 'Could not install package' return ret
def test_warn_until_warning_raised(self): # We *always* want *all* warnings thrown on this module warnings.filterwarnings('always', '', DeprecationWarning, __name__) def raise_warning(_version_info_=(0, 16, 0)): warn_until((0, 17), 'Deprecation Message!', _version_info_=_version_info_) def raise_named_version_warning(_version_info_=(0, 16, 0)): warn_until('Hydrogen', 'Deprecation Message!', _version_info_=_version_info_) # raise_warning should show warning until version info is >= (0, 17) with warnings.catch_warnings(record=True) as recorded_warnings: raise_warning() self.assertEqual('Deprecation Message!', str(recorded_warnings[0].message)) # raise_warning should show warning until version info is >= (0, 17) with warnings.catch_warnings(record=True) as recorded_warnings: raise_named_version_warning() self.assertEqual('Deprecation Message!', str(recorded_warnings[0].message)) # the deprecation warning is not issued because we passed # _dont_call_warning with warnings.catch_warnings(record=True) as recorded_warnings: warn_until((0, 17), 'Foo', _dont_call_warnings=True, _version_info_=(0, 16)) self.assertEqual(0, len(recorded_warnings)) # Let's set version info to (0, 17), a RuntimeError should be raised with self.assertRaisesRegexp( RuntimeError, r'The warning triggered on filename \'(.*)warnings_test.py\', ' r'line number ([\d]+), is supposed to be shown until version ' r'\'0.17.0\' is released. Current version is now \'0.17.0\'. ' r'Please remove the warning.'): raise_warning(_version_info_=(0, 17, 0)) # Let's set version info to (0, 17), a RuntimeError should be raised with self.assertRaisesRegexp( RuntimeError, r'The warning triggered on filename \'(.*)warnings_test.py\', ' r'line number ([\d]+), is supposed to be shown until version ' r'\'Hydrogen((.*))\' is released. Current version is now ' r'\'([\d.]+)\'. Please remove the warning.'): raise_named_version_warning(_version_info_=(sys.maxint, 16, 0)) # Even though we're calling warn_until, we pass _dont_call_warnings # because we're only after the RuntimeError with self.assertRaisesRegexp( RuntimeError, r'The warning triggered on filename \'(.*)warnings_test.py\', ' r'line number ([\d]+), is supposed to be shown until version ' r'\'0.17.0\' is released. Current version is now \'0.17.0\'. ' r'Please remove the warning.'): warn_until((0, 17), 'Foo', _dont_call_warnings=True) with self.assertRaisesRegexp( RuntimeError, r'The warning triggered on filename \'(.*)warnings_test.py\', ' r'line number ([\d]+), is supposed to be shown until version ' r'\'Hydrogen((.*))\' is released. Current version is now ' r'\'([\d.]+)\'. Please remove the warning.'): warn_until('Hydrogen', 'Foo', _dont_call_warnings=True, _version_info_=(sys.maxint, 16, 0)) # version on the deprecation message gets properly formatted with warnings.catch_warnings(record=True) as recorded_warnings: vrs = SaltStackVersion.from_name('Helium') warn_until('Helium', 'Deprecation Message until {version}!', _version_info_=(vrs.major - 1, 0)) self.assertEqual( 'Deprecation Message until {0}!'.format(vrs.formatted_version), str(recorded_warnings[0].message))
def __get_version(version, version_info): ''' If we can get a version provided at installation time or from Git, use that instead, otherwise we carry on. ''' try: # Try to import the version information provided at install time from saltfuse._version import __version__, __version_info__ # pylint: disable=E0611 return __version__, __version_info__ except ImportError: pass # This might be a 'python setup.py develop' installation type. Let's # discover the version information at runtime. import os import warnings import subprocess if 'SETUP_DIRNAME' in globals(): # This is from the exec() call in Salt Fuse's setup.py cwd = SETUP_DIRNAME # pylint: disable=E0602 if not os.path.exists(os.path.join(cwd, '.git')): # This is not a Salt Fuse git checkout!!! Don't even try to parse... return version, version_info else: cwd = os.path.abspath(os.path.dirname(__file__)) if not os.path.exists(os.path.join(os.path.dirname(cwd), '.git')): # This is not a Salt git checkout!!! Don't even try to parse... return version, version_info try: kwargs = dict( stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd ) if not sys.platform.startswith('win'): # Let's not import `salt.utils` for the above check kwargs['close_fds'] = True process = subprocess.Popen( ['git', 'describe', '--tags', '--match', 'v[0-9]*'], **kwargs) out, err = process.communicate() out = out.strip() err = err.strip() if not out or err: return version, version_info parsed_version = SaltStackVersion.parse(out) if parsed_version.info > version_info: warnings.warn( 'The parsed version info, `{0}`, is bigger than the one ' 'defined in the file, `{1}`. Missing version bump?'.format( parsed_version.info, version_info ), UserWarning, stacklevel=2 ) return version, version_info elif parsed_version.info < version_info: warnings.warn( 'The parsed version info, `{0}`, is lower than the one ' 'defined in the file, `{1}`.' 'In order to get the proper salt version with the git hash ' 'you need to update salt\'s local git tags. Something like: ' '\'git fetch --tags\' or \'git fetch --tags upstream\' if ' 'you followed salt\'s contribute documentation. The version ' 'string WILL NOT include the git hash.'.format( parsed_version.info, version_info ), UserWarning, stacklevel=2 ) return version, version_info return parsed_version.string, parsed_version.info except OSError as os_err: if os_err.errno != 2: # If the errno is not 2(The system cannot find the file # specified), raise the exception so it can be catch by the # developers raise return version, version_info
from __future__ import absolute_import import re # Import third party libs #import salt.ext.six as six # Import salt libs from salt.exceptions import ( #CommandExecutionError, SaltInvocationError) from salt.utils import warn_until from salt.version import (__version__, SaltStackVersion) # is there not SaltStackVersion.current() to get # the version of the salt running this code?? CUR_VER = SaltStackVersion(__version__[0], __version__[1]) BORON = SaltStackVersion.from_name('Boron') # pylint: disable=import-error HAS_GLANCE = False try: from glanceclient import client from glanceclient import exc HAS_GLANCE = True except ImportError: pass # Workaround, as the Glance API v2 requires you to # already have a keystone session token HAS_KEYSTONE = False try:
# This file was auto-generated by salt's setup on Tuesday, 22 March 2016 @ 22:03:14 UTC. from salt.version import SaltStackVersion __saltstack_version__ = SaltStackVersion(2015, 8, 8, 0, 0, 0, None)
def installed(name, pip_bin=None, requirements=None, env=None, bin_env=None, use_wheel=False, log=None, proxy=None, timeout=None, repo=None, editable=None, find_links=None, index_url=None, extra_index_url=None, no_index=False, mirrors=None, build=None, target=None, download=None, download_cache=None, source=None, upgrade=False, force_reinstall=False, ignore_installed=False, exists_action=None, no_deps=False, no_install=False, no_download=False, install_options=None, user=None, runas=None, no_chown=False, cwd=None, activate=False, pre_releases=False): ''' Make sure the package is installed name The name of the python package to install user The user under which to run pip pip_bin : None Deprecated, use bin_env use_wheel : False Prefer wheel archives (requires pip>=1.4) env : None Deprecated, use bin_env bin_env : None the pip executable or virtualenv to use .. versionchanged:: 0.17.0 ``use_wheel`` option added. .. admonition:: Attention As of Salt 0.17.0 the pip state **needs** an importable pip module. This usually means having the system's pip package installed or running Salt from an active `virtualenv`_. The reason for this requirement is because ``pip`` already does a pretty good job parsing it's own requirements. It makes no sense for Salt to do ``pip`` requirements parsing and validation before passing them to the ``pip`` library. It's functionality duplication and it's more error prone. .. _`virtualenv`: http://www.virtualenv.org ''' if pip_bin and not bin_env: bin_env = pip_bin elif env and not bin_env: bin_env = env ret = {'name': name, 'result': None, 'comment': '', 'changes': {}} if use_wheel: min_version = '1.4' cur_version = __salt__['pip.version'](bin_env) if not salt.utils.compare_versions(ver1=cur_version, oper='>=', ver2=min_version): ret['result'] = False ret['comment'] = ('The \'use_wheel\' option is only supported in ' 'pip {0} and newer. The version of pip detected ' 'was {1}.').format(min_version, cur_version) return ret if repo is not None: msg = ('The \'repo\' argument to pip.installed is deprecated and will ' 'be removed in Salt {version}. Please use \'name\' instead. ' 'The current value for name, {0!r} will be replaced by the ' 'value of repo, {1!r}'.format( name, repo, version=_SaltStackVersion.from_name( 'Hydrogen').formatted_version )) salt.utils.warn_until('Hydrogen', msg) ret.setdefault('warnings', []).append(msg) name = repo from_vcs = False if name: try: try: # With pip < 1.2, the __version__ attribute does not exist and # vcs+URL urls are not properly parsed. # The next line is meant to trigger an AttributeError and # handle lower pip versions logger.debug( 'Installed pip version: {0}'.format(pip.__version__) ) install_req = pip.req.InstallRequirement.from_line(name) except AttributeError: logger.debug('Installed pip version is lower than 1.2') supported_vcs = ('git', 'svn', 'hg', 'bzr') if name.startswith(supported_vcs): for vcs in supported_vcs: if name.startswith(vcs): from_vcs = True install_req = pip.req.InstallRequirement.from_line( name.split('{0}+'.format(vcs))[-1] ) break else: install_req = pip.req.InstallRequirement.from_line(name) except ValueError as exc: ret['result'] = False if not from_vcs and '=' in name and '==' not in name: ret['comment'] = ( 'Invalid version specification in package {0}. \'=\' is ' 'not supported, use \'==\' instead.'.format(name) ) return ret ret['comment'] = ( 'pip raised an exception while parsing {0!r}: {1}'.format( name, exc ) ) return ret if install_req.req is None: # This is most likely an url and there's no way to know what will # be installed before actually installing it. prefix = '' version_spec = [] else: prefix = install_req.req.project_name version_spec = install_req.req.specs else: prefix = '' version_spec = [] if runas is not None: # The user is using a deprecated argument, warn! msg = ('The \'runas\' argument to pip.installed is deprecated, and ' 'will be removed in Salt {version}. Please use \'user\' ' 'instead.'.format( version=_SaltStackVersion.from_name( 'Hydrogen').formatted_version )) salt.utils.warn_until('Hydrogen', msg) ret.setdefault('warnings', []).append(msg) # "There can only be one" if user: raise CommandExecutionError( 'The \'runas\' and \'user\' arguments are mutually exclusive. ' 'Please use \'user\' as \'runas\' is being deprecated.' ) # Support deprecated 'runas' arg else: user = runas # Replace commas (used for version ranges) with semicolons (which are not # supported) in name so it does not treat them as multiple packages. Comma # will be re-added in pip.install call. name = name.replace(',', ';') # If a requirements file is specified, only install the contents of the # requirements file. Similarly, using the --editable flag with pip should # also ignore the "name" parameter. if requirements or editable: name = '' comments = [] if __opts__['test']: ret['result'] = None if requirements: # TODO: Check requirements file against currently-installed # packages to provide more accurate state output. comments.append('Requirements file {0!r} will be ' 'processed.'.format(requirements)) if editable: comments.append( 'Package will be installed in editable mode (i.e. ' 'setuptools "develop mode") from {0}.'.format(editable) ) ret['comment'] = ' '.join(comments) return ret else: try: pip_list = __salt__['pip.list'](prefix, bin_env=bin_env, user=user, cwd=cwd) prefix_realname = _find_key(prefix, pip_list) except (CommandNotFoundError, CommandExecutionError) as err: ret['result'] = False ret['comment'] = 'Error installing {0!r}: {1}'.format(name, err) return ret if ignore_installed is False and prefix_realname is not None: if force_reinstall is False and not upgrade: # Check desired version (if any) against currently-installed if ( any(version_spec) and _fulfills_version_spec(pip_list[prefix_realname], version_spec) ) or (not any(version_spec)): ret['result'] = True ret['comment'] = ('Python package {0} already ' 'installed'.format(name)) return ret if __opts__['test']: ret['result'] = None ret['comment'] = \ 'Python package {0} is set to be installed'.format(name) return ret pip_install_call = __salt__['pip.install']( pkgs='{0}'.format(name) if name else '', requirements=requirements, bin_env=bin_env, use_wheel=use_wheel, log=log, proxy=proxy, timeout=timeout, editable=editable, find_links=find_links, index_url=index_url, extra_index_url=extra_index_url, no_index=no_index, mirrors=mirrors, build=build, target=target, download=download, download_cache=download_cache, source=source, upgrade=upgrade, force_reinstall=force_reinstall, ignore_installed=ignore_installed, exists_action=exists_action, no_deps=no_deps, no_install=no_install, no_download=no_download, install_options=install_options, user=user, no_chown=no_chown, cwd=cwd, activate=activate, pre_releases=pre_releases, ) if pip_install_call and (pip_install_call.get('retcode', 1) == 0): ret['result'] = True if requirements or editable: comments = [] if requirements: comments.append('Successfully processed requirements file ' '{0}.'.format(requirements)) ret['changes']['requirements'] = True if editable: comments.append('Package successfully installed from VCS ' 'checkout {0}.'.format(editable)) ret['changes']['editable'] = True ret['comment'] = ' '.join(comments) else: if not prefix: pkg_list = {} else: pkg_list = __salt__['pip.list']( prefix, bin_env, user=user, cwd=cwd ) if not pkg_list: ret['comment'] = ( 'There was no error installing package \'{0}\' although ' 'it does not show when calling ' '\'pip.freeze\'.'.format(name) ) ret['changes']['{0}==???'.format(name)] = 'Installed' return ret version = list(pkg_list.values())[0] pkg_name = next(iter(pkg_list)) ret['changes']['{0}=={1}'.format(pkg_name, version)] = 'Installed' ret['comment'] = 'Package was successfully installed' elif pip_install_call: ret['result'] = False if 'stdout' in pip_install_call: error = 'Error: {0} {1}'.format(pip_install_call['stdout'], pip_install_call['stderr']) else: error = 'Error: {0}'.format(pip_install_call['comment']) if requirements or editable: comments = [] if requirements: comments.append('Unable to process requirements file ' '{0}.'.format(requirements)) if editable: comments.append('Unable to install from VCS checkout' '{0}.'.format(editable)) comments.append(error) ret['comment'] = ' '.join(comments) else: ret['comment'] = ('Failed to install package {0}. ' '{1}'.format(name, error)) else: ret['result'] = False ret['comment'] = 'Could not install package' return ret