salt/salt/modules/rbenv.py
2016-12-28 14:49:46 -07:00

431 lines
10 KiB
Python

# -*- coding: utf-8 -*-
'''
Manage ruby installations with rbenv. rbenv is supported on Linux and macOS.
rbenv doesn't work on Windows (and isn't really necessary on Windows as there is
no system Ruby on Windows). On Windows, the RubyInstaller and/or Pik are both
good alternatives to work with multiple versions of Ruby on the same box.
http://misheska.com/blog/2013/06/15/using-rbenv-to-manage-multiple-versions-of-ruby/
.. versionadded:: 0.16.0
'''
# Import python libs
from __future__ import absolute_import
import os
import re
import logging
# Import Salt libs
import salt.utils
from salt.exceptions import SaltInvocationError
# Import 3rd-party libs
import salt.ext.six as six
# Set up logger
log = logging.getLogger(__name__)
__func_alias__ = {
'list_': 'list'
}
__opts__ = {
'rbenv.root': None,
'rbenv.build_env': None,
}
def __virtual__():
'''
Only work on POSIX-like systems
'''
if salt.utils.is_windows():
return (False, 'The rbenv execution module failed to load: only available on non-Windows systems.')
return True
def _shlex_split(s):
# from python:salt.utils.shlex_split: passing None for s will read
# the string to split from standard input.
if s is None:
ret = salt.utils.shlex_split('')
else:
ret = salt.utils.shlex_split(s)
return ret
def _parse_env(env):
if not env:
env = {}
if isinstance(env, list):
env = salt.utils.repack_dictlist(env)
if not isinstance(env, dict):
env = {}
for bad_env_key in (x for x, y in six.iteritems(env) if y is None):
log.error('Environment variable \'{0}\' passed without a value. '
'Setting value to an empty string'.format(bad_env_key))
env[bad_env_key] = ''
return env
def _rbenv_bin(runas=None):
path = _rbenv_path(runas)
return '{0}/bin/rbenv'.format(path)
def _rbenv_path(runas=None):
path = None
if runas in (None, 'root'):
path = __salt__['config.option']('rbenv.root') or '/usr/local/rbenv'
else:
path = __salt__['config.option']('rbenv.root') \
or '~{0}/.rbenv'.format(runas)
return os.path.expanduser(path)
def _rbenv_exec(command, env=None, runas=None, ret=None):
if not is_installed(runas):
return False
binary = _rbenv_bin(runas)
path = _rbenv_path(runas)
environ = _parse_env(env)
environ['RBENV_ROOT'] = path
result = __salt__['cmd.run_all'](
[binary] + command,
runas=runas,
env=environ
)
if isinstance(ret, dict):
ret.update(result)
return ret
if result['retcode'] == 0:
return result['stdout']
else:
return False
def _install_rbenv(path, runas=None):
if os.path.isdir(path):
return True
cmd = ['git', 'clone', 'https://github.com/sstephenson/rbenv.git', path]
return __salt__['cmd.retcode'](cmd, runas=runas, python_shell=False) == 0
def _install_ruby_build(path, runas=None):
path = '{0}/plugins/ruby-build'.format(path)
if os.path.isdir(path):
return True
cmd = ['git', 'clone',
'https://github.com/sstephenson/ruby-build.git', path]
return __salt__['cmd.retcode'](cmd, runas=runas, python_shell=False) == 0
def _update_rbenv(path, runas=None):
if not os.path.isdir(path):
return False
return __salt__['cmd.retcode'](['git', 'pull'],
runas=runas,
cwd=path,
python_shell=False) == 0
def _update_ruby_build(path, runas=None):
path = '{0}/plugins/ruby-build'.format(path)
if not os.path.isdir(path):
return False
return __salt__['cmd.retcode'](['git', 'pull'],
runas=runas,
cwd=path,
python_shell=False) == 0
def install(runas=None, path=None):
'''
Install rbenv systemwide
CLI Example:
.. code-block:: bash
salt '*' rbenv.install
'''
path = path or _rbenv_path(runas)
path = os.path.expanduser(path)
return _install_rbenv(path, runas) and _install_ruby_build(path, runas)
def update(runas=None, path=None):
'''
Updates the current versions of rbenv and ruby-build
runas
The user under which to run rbenv. If not specified, then rbenv will be
run as the user under which Salt is running.
CLI Example:
.. code-block:: bash
salt '*' rbenv.update
'''
path = path or _rbenv_path(runas)
path = os.path.expanduser(path)
return _update_rbenv(path, runas) and _update_ruby_build(path, runas)
def is_installed(runas=None):
'''
Check if rbenv is installed
CLI Example:
.. code-block:: bash
salt '*' rbenv.is_installed
'''
return __salt__['cmd.has_exec'](_rbenv_bin(runas))
def install_ruby(ruby, runas=None):
'''
Install a ruby implementation.
ruby
The version of Ruby to install, should match one of the
versions listed by :py:func:`rbenv.list <salt.modules.rbenv.list>`
runas
The user under which to run rbenv. If not specified, then rbenv will be
run as the user under which Salt is running.
Additional environment variables can be configured in pillar /
grains / master:
.. code-block:: yaml
rbenv:
build_env: 'CONFIGURE_OPTS="--no-tcmalloc" CFLAGS="-fno-tree-dce"'
CLI Example:
.. code-block:: bash
salt '*' rbenv.install_ruby 2.0.0-p0
'''
ruby = re.sub(r'^ruby-', '', ruby)
env = None
env_list = []
if __grains__['os'] in ('FreeBSD', 'NetBSD', 'OpenBSD'):
env_list.append('MAKE=gmake')
if __salt__['config.get']('rbenv:build_env'):
env_list.append(__salt__['config.get']('rbenv:build_env'))
elif __salt__['config.option']('rbenv.build_env'):
env_list.append(__salt__['config.option']('rbenv.build_env'))
if env_list:
env = ' '.join(env_list)
ret = {}
ret = _rbenv_exec(['install', ruby], env=env, runas=runas, ret=ret)
if ret['retcode'] == 0:
rehash(runas=runas)
return ret['stderr']
else:
# Cleanup the failed installation so it doesn't list as installed
uninstall_ruby(ruby, runas=runas)
return False
def uninstall_ruby(ruby, runas=None):
'''
Uninstall a ruby implementation.
ruby
The version of ruby to uninstall. Should match one of the versions
listed by :py:func:`rbenv.versions <salt.modules.rbenv.versions>`.
runas
The user under which to run rbenv. If not specified, then rbenv will be
run as the user under which Salt is running.
CLI Example:
.. code-block:: bash
salt '*' rbenv.uninstall_ruby 2.0.0-p0
'''
ruby = re.sub(r'^ruby-', '', ruby)
_rbenv_exec(['uninstall', '--force', ruby], runas=runas)
return True
def versions(runas=None):
'''
List the installed versions of ruby
CLI Example:
.. code-block:: bash
salt '*' rbenv.versions
'''
ret = _rbenv_exec(['versions', '--bare'], runas=runas)
return [] if ret is False else ret.splitlines()
def default(ruby=None, runas=None):
'''
Returns or sets the currently defined default ruby
ruby
The version to set as the default. Should match one of the versions
listed by :py:func:`rbenv.versions <salt.modules.rbenv.versions>`.
Leave blank to return the current default.
CLI Example:
.. code-block:: bash
salt '*' rbenv.default
salt '*' rbenv.default 2.0.0-p0
'''
if ruby:
_rbenv_exec(['global', ruby], runas=runas)
return True
else:
ret = _rbenv_exec(['global'], runas=runas)
return '' if ret is False else ret.strip()
def list_(runas=None):
'''
List the installable versions of ruby
runas
The user under which to run rbenv. If not specified, then rbenv will be
run as the user under which Salt is running.
CLI Example:
.. code-block:: bash
salt '*' rbenv.list
'''
ret = []
output = _rbenv_exec(['install', '--list'], runas=runas)
if output:
for line in output.splitlines():
if line == 'Available versions:':
continue
ret.append(line.strip())
return ret
def rehash(runas=None):
'''
Run ``rbenv rehash`` to update the installed shims
runas
The user under which to run rbenv. If not specified, then rbenv will be
run as the user under which Salt is running.
CLI Example:
.. code-block:: bash
salt '*' rbenv.rehash
'''
_rbenv_exec(['rehash'], runas=runas)
return True
def do(cmdline, runas=None, env=None):
'''
Execute a ruby command with rbenv's shims from the user or the system
CLI Example:
.. code-block:: bash
salt '*' rbenv.do 'gem list bundler'
salt '*' rbenv.do 'gem list bundler' deploy
'''
if not cmdline:
# This is a positional argument so this should never happen, but this
# will handle cases where someone explicitly passes a false value for
# cmdline.
raise SaltInvocationError('Command must be specified')
path = _rbenv_path(runas)
if not env:
env = {}
env['PATH'] = '{0}/shims:{1}'.format(path, os.environ['PATH'])
try:
cmdline = salt.utils.shlex_split(cmdline)
except AttributeError:
cmdline = salt.utils.shlex_split(str(cmdline))
result = __salt__['cmd.run_all'](
cmdline,
runas=runas,
env=env,
python_shell=False
)
if result['retcode'] == 0:
rehash(runas=runas)
return result['stdout']
else:
return False
def do_with_ruby(ruby, cmdline, runas=None):
'''
Execute a ruby command with rbenv's shims using a specific ruby version
CLI Example:
.. code-block:: bash
salt '*' rbenv.do_with_ruby 2.0.0-p0 'gem list bundler'
salt '*' rbenv.do_with_ruby 2.0.0-p0 'gem list bundler' runas=deploy
'''
if not cmdline:
# This is a positional argument so this should never happen, but this
# will handle cases where someone explicitly passes a false value for
# cmdline.
raise SaltInvocationError('Command must be specified')
try:
cmdline = salt.utils.shlex_split(cmdline)
except AttributeError:
cmdline = salt.utils.shlex_split(str(cmdline))
env = {}
if ruby:
env['RBENV_VERSION'] = ruby
cmd = cmdline
else:
cmd = cmdline
return do(cmd, runas=runas, env=env)