From 1cd0b22c57cc3acc371483887dd8b28efa878701 Mon Sep 17 00:00:00 2001 From: Anselm Helbig Date: Thu, 23 Feb 2012 15:09:14 +0100 Subject: [PATCH] added modules and state modules for rubygem and rvm support; see #667 --- salt/modules/gem.py | 131 ++++++++++++++++++++++++ salt/modules/rvm.py | 235 ++++++++++++++++++++++++++++++++++++++++++++ salt/states/gem.py | 70 +++++++++++++ salt/states/rvm.py | 221 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 657 insertions(+) create mode 100644 salt/modules/gem.py create mode 100644 salt/modules/rvm.py create mode 100644 salt/states/gem.py create mode 100644 salt/states/rvm.py diff --git a/salt/modules/gem.py b/salt/modules/gem.py new file mode 100644 index 0000000000..62a869cb58 --- /dev/null +++ b/salt/modules/gem.py @@ -0,0 +1,131 @@ +""" +Manage ruby gems. +""" + +__opts__ = { + "rvm.runas": None, +} + +import re + +def _gem(command, ruby = None, runas = None): + cmdline = "gem {command}".format(command = command) + if __salt__["rvm.is_installed"]: + return __salt__["rvm.do"](ruby, cmdline, runas = runas) + + ret = __salt__["cmd.run_all"](cmdline, runas = runas or __opts__["rvm.runas"]) + + if ret["retcode"] == 0: + return ret["stdout"] + else: + return False + + +def install(gems, ruby = None, runas = None): + """ + Installs one or several gems. + + gems + The gems to install. + ruby : None + If RVM is installed, the ruby version and gemset to use. + runas : None + The user to run gem as. + """ + return _gem("install {gems}".format(gems = gems), ruby, runas = runas) + +def uninstall(gems, ruby = None, runas = None): + """ + Uninstall one or several gems. + + gems + The gems to uninstall. + ruby : None + If RVM is installed, the ruby version and gemset to use. + runas : None + The user to run gem as. + """ + return _gem("uninstall {gems}".format(gems = gems), ruby, runas = runas) + +def update(gems, ruby = None, runas = None): + """ + Update one or several gems. + + gems + The gems to update. + ruby : None + If RVM is installed, the ruby version and gemset to use. + runas : None + The user to run gem as. + """ + return _gem("update {gems}".format(gems = gems), ruby, runas = runas) + +def update_system(version = "", ruby = None, runas = None): + """ + Update rubygems. + + version : (newest) + The version of rubygems to install. + ruby : None + If RVM is installed, the ruby version and gemset to use. + runas : None + The user to run gem as. + """ + return _gem("update --system {version}".format(version = version), ruby, runas = runas) + +def list(prefix = "", ruby = None, runas = None): + """ + List locally installed gems. + + prefix : + Only list gems when the name matches this prefix. + ruby : None + If RVM is installed, the ruby version and gemset to use. + runas : None + The user to run gem as. + """ + gems = {} + for line in _gem("list {prefix}".format(prefix = prefix), ruby, runas = runas).splitlines(): + m = re.match("^([^ ]+) \((.+)\)", line) + if m: + gem = m.group(1) + versions = m.group(2).split(", ") + gems[gem] = versions + return gems + +def sources_add(source_uri, ruby = None, runas = None): + """ + Add a gem source. + + source_uri + The source URI to add. + ruby : None + If RVM is installed, the ruby version and gemset to use. + runas : None + The user to run gem as. + """ + return _gem("sources --add {source_uri}".format(source_uri = source_uri), ruby, runas = runas) + +def sources_remove(source_uri, ruby = None, runas = None): + """ + Remove a gem source. + + source_uri + The source URI to remove. + ruby : None + If RVM is installed, the ruby version and gemset to use. + runas : None + The user to run gem as. + """ + return _gem("sources --remove {source_uri}".format(source_uri = source_uri), ruby, runas = runas) + +def sources_list(ruby = None, runas = None): + """ + List the configured gem sources. + + ruby : None + If RVM is installed, the ruby version and gemset to use. + runas : None + The user to run gem as. + """ + return _gem("sources", ruby, runas = runas).splitlines()[2:] diff --git a/salt/modules/rvm.py b/salt/modules/rvm.py new file mode 100644 index 0000000000..737961fa19 --- /dev/null +++ b/salt/modules/rvm.py @@ -0,0 +1,235 @@ +""" +Manage ruby installations and gemsets with RVM, the Ruby Version Manager. +""" + +__opts__ = { + "rvm.runas": None, +} + +import re + +def _rvm(command, arguments = "", runas = None): + if not is_installed(): + return False + + ret = __salt__["cmd.run_all"]("/usr/local/rvm/bin/rvm {command} {arguments}".format(command = command, arguments = arguments), runas = runas or __opts__["rvm.runas"]) + if ret["retcode"] == 0: + return ret["stdout"] + else: + return False + +def _rvm_do(ruby, command, runas = None): + return _rvm("{ruby} do {command}".format(ruby = ruby or "default", command = command), runas = runas) + +def is_installed(): + """ + Check if RVM is installed. + """ + return __salt__["cmd.has_exec"]("/usr/local/rvm/bin/rvm") + +# RVM dependencies on Ubuntu 10.04: +# bash coreutils gzip bzip2 gawk sed curl git-core subversion sudo +def install(): + """ + Install RVM system wide. + """ + return 0 == __salt__["cmd.retcode"]( + # the RVM installer only does a multi-user install when it is invoked under sudo + "curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer | sudo bash -s stable") + +# MRI/RBX/REE dependencies for Ubuntu 10.04: +# build-essential openssl libreadline6 libreadline6-dev curl +# git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 +# libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev autoconf libc6-dev +# libncurses5-dev automake libtool bison subversion ruby +def install_ruby(ruby, runas = None): + """ + Install a ruby implementation. + + ruby + The version of ruby to install. + runas : None + The user to run rvm as. + """ + return _rvm("install", ruby, runas = runas) + +def reinstall_ruby(ruby, runas = None): + """ + Reinstall a ruby implementation. + + ruby + The version of ruby to reinstall. + runas : None + The user to run rvm as. + """ + return _rvm("reinstall", ruby, runas = runas) + +def list(runas = None): + """ + List all rvm installed rubies. + + runas : None + The user to run rvm as. + """ + rubies = [] + + for line in _rvm("list", "", runas = runas).splitlines(): + m = re.match("^[= ]([*> ]) ([^- ]+)-([^ ]+) \[ (.*) \]", line) + if m: + rubies.append([m.group(2), m.group(3), m.group(1) == "*"]) + return rubies + +def set_default(ruby, runas = None): + """ + Set the default ruby. + + ruby + The version of ruby to make the default. + runas : None + The user to run rvm as. + """ + return _rvm("alias", "create default {ruby}".format(ruby = ruby), runas = runas) + +def get(version = "stable", runas = None): + """ + Update RVM. + + version : stable + Which version of RVM to install, e.g. stable or head. + ruby + The version of ruby to reinstall. + """ + return _rvm("get", version, runas = runas) + +def wrapper(ruby_string, wrapper_prefix, runas = None, *binaries): + """ + Install RVM wrapper scripts. + + ruby_string + Ruby/gemset to install wrappers for. + wrapper_prefix + What to prepend to the name of the generated wrapper binaries. + runas : None + The user to run rvm as. + binaries : None + The names of the binaries to create wrappers for. When nothing is given, wrappers for ruby, gem, rake, irb, rdoc, ri and testrb are generated. + """ + return _rvm("wrapper", "{ruby_string} {wrapper_prefix} {binaries}". + format(ruby_string = ruby_string, + wrapper_prefix = wrapper_prefix, + binaries = " ".join(binaries)), runas = runas) + +def rubygems(ruby, version, runas = None): + """ + Installs a specific rubygems version in the given ruby. + + ruby + The ruby to install rubygems for. + version + The version of rubygems to install or 'remove' to use the version that ships with 1.9 + runas : None + The user to run rvm as. + """ + return _rvm_do(ruby, "rubygems", version, runas = runas) + +def gemset_create(ruby, gemset, runas = None): + """ + Creates a gemset. + + ruby + The ruby version to create the gemset for. + gemset + The name of the gemset to create. + runas : None + The user to run rvm as. + """ + return _rvm_do(ruby, "rvm gemset create {gemset}".format(gemset = gemset), runas = runas) + +def gemset_list(ruby = "default", runas = None): + """ + List all gemsets for the given ruby. + + ruby : default + The ruby version to list the gemsets for + runas : None + The user to run rvm as. + """ + gemsets = [] + for line in _rvm_do(ruby, "rvm gemset list", runas = runas).splitlines(): + m = re.match("^ ([^ ]+)", line) + if m: + gemsets.append(m.group(1)) + return gemsets + +def gemset_delete(ruby, gemset, runas = None): + """ + Deletes a gemset. + + ruby + The ruby version the gemset belongs to. + gemset + The gemset to delete. + runas : None + The user to run rvm as. + """ + return _rvm_do(ruby, "rvm --force gemset delete {gemset}".format(gemset = gemset), runas = runas) + +def gemset_empty(ruby, gemset, runas = None): + """ + Remove all gems from a gemset. + + ruby + The ruby version the gemset belongs to. + gemset + The gemset to empty. + runas : None + The user to run rvm as. + """ + return _rvm_do(ruby, "rvm --force gemset empty", gemset, runas = runas) + +def gemset_copy(source, destination, runas = None): + """ + Copy all gems from one gemset to another. + + source + The name of the gemset to copy, complete with ruby version. + destination + The destination gemset. + runas : None + The user to run rvm as. + """ + return _rvm("gemset copy", source, destination, runas = runas) + +def gemset_list_all(runas = None): + """ + List all gemsets for all installed rubies. + + Note that you must have set a default ruby before this can work. + runas : None + The user to run rvm as. + """ + gemsets = {} + current_ruby = None + for line in _rvm_do("default", "rvm gemset list_all", runas = runas).splitlines(): + m = re.match("^gemsets for ([^ ]+)", line) + if m: + current_ruby = m.group(1) + gemsets[current_ruby] = [] + m = re.match("^ ([^ ]+)", line) + if m: + gemsets[current_ruby].append(m.group(1)) + return gemsets + +def do(ruby, command, runas = None): + """ + Execute a command in an RVM controlled environment. + + ruby: + The ruby to use. + command: + The command to execute. + runas : None + The user to run rvm as. + """ + return _rvm_do(ruby, command, runas = runas) + diff --git a/salt/states/gem.py b/salt/states/gem.py new file mode 100644 index 0000000000..422ef8a52f --- /dev/null +++ b/salt/states/gem.py @@ -0,0 +1,70 @@ +""" +Management of rubygems +======================= +A state module to manage rubygems. Gems can be set up to be installed +or removed. This module will use RVM if it is installed. In that case +you can specify what ruby version and gemset to target. + +.. code-block:: yaml + + addressable: + gem: + - installed + - runas: rvm + - ruby: jruby@jgemset +""" + +def installed(name, ruby = None, runas = None): + """ + Make sure that a gem is installed. + + name + The name of the gem to install + ruby : None + For RVM installations: the ruby version and gemset to target. + runas : None + The user to run gem as. + """ + ret = {'name': name, 'result': None, 'comment': '', 'changes': {}} + if name in __salt__["gem.list"](name, ruby, runas = runas): + ret["result"] = True + ret["comment"] = "Gem is already installed." + return ret + + if __salt__["gem.install"](name, ruby, runas = runas): + ret["result"] = True + ret["changes"][name] = "Installed" + ret["comment"] = "Gem was successfully installed" + else: + ret["result"] = False + ret["comment"] = "Could not install gem." + + return ret + +def removed(name, ruby = None, runas = None): + """ + Make sure that a gem is not installed. + + name + The name of the gem to uninstall + ruby : None + For RVM installations: the ruby version and gemset to target. + runas : None + The user to run gem as. + """ + ret = {'name': name, 'result': None, 'comment': '', 'changes': {}} + if name not in __salt__["gem.list"](name, ruby, runas = runas): + ret["result"] = True + ret["comment"] = "Gem is not installed." + return ret + + if __salt__["gem.uninstall"](name, ruby, runas = runas): + ret["result"] = True + ret["changes"][name] = "Removed" + ret["comment"] = "Gem was successfully removed." + else: + ret["result"] = False + ret["comment"] = "Could not remove gem." + return ret + + diff --git a/salt/states/rvm.py b/salt/states/rvm.py new file mode 100644 index 0000000000..c1603c51ce --- /dev/null +++ b/salt/states/rvm.py @@ -0,0 +1,221 @@ +""" +Management of ruby installations and gemsets with RVM +===================================================== +This module is used to install and manage ruby installations and +gemsets with RVM, the Ruby Version Manager. Different versions of ruby +can be installed and gemsets created. RVM itself will be installed +automatically if it's not present. This module will not automatically +install packages that RVM depends on or ones that are needed to build +ruby. If you want to run RVM as an unprivileged user (recommended) you +will have to create this user yourself. This is how a state +configuration could look like: + +.. code-block:: yaml + +rvm: + group: + - present + user: + - present + - gid: rvm + - home: /home/rvm + - require: + - group: rvm + +rvm-deps: + pkg: + - installed + - names: + - bash + - coreutils + - gzip + - bzip2 + - gawk + - sed + - curl + - git-core + - subversion + - sudo + +mri-deps: + pkg: + - installed + - names: + - build-essential + - openssl + - libreadline6 + - libreadline6-dev + - curl + - git-core + - zlib1g + - zlib1g-dev + - libssl-dev + - libyaml-dev + - libsqlite3-0 + - libsqlite3-dev + - sqlite3 + - libxml2-dev + - libxslt1-dev + - autoconf + - libc6-dev + - libncurses5-dev + - automake + - libtool + - bison + - subversion + - ruby + +jruby-deps: + pkg: + - installed + - names: + - curl + - g++ + - openjdk-6-jre-headless + +ruby-1.9.2: + rvm: + - installed + - default: True + - runas: rvm + - require: + - pkg: rvm-deps + - pkg: mri-deps + - user: rvm + +jruby: + rvm: + - installed + - runas: rvm + - require: + - pkg: rvm-deps + - pkg: jruby-deps + - user: rvm + +jgemset: + rvm: + - gemset_present + - ruby: jruby + - runas: rvm + - require: + - rvm: jruby + +mygemset: + rvm: + - gemset_present + - ruby: ruby-1.9.2 + - runas: rvm + - require: + - rvm: ruby-1.9.2 +""" + +import re + +def _check_rvm(ret): + if not __salt__["rvm.is_installed"](): + if __salt__["rvm.install"](): + ret["changes"]["rvm"] = "Installed" + else: + ret["result"] = False + ret["comment"] = "Could not install RVM." + return ret + +def _check_and_install_ruby(ret, ruby, default = False, runas = None): + ret = _check_ruby(ret, ruby, runas = runas) + if not ret["result"]: + if __salt__["rvm.install_ruby"](ruby, runas = runas): + ret["result"] = True + ret["changes"][ruby] = "Installed" + ret["comment"] = "Successfully installed ruby." + ret["default"] = False + else: + ret["result"] = False + ret["comment"] = "Could not install ruby." + return ret + + if not ret["default"] and default: + __salt__["rvm.set_default"](ruby, runas = runas) + + return ret + +def _check_ruby(ret, ruby, runas = None): + match_version = True + match_micro_version = False + micro_version_regex = re.compile("-([0-9]{4}\.[0-9]{2}|p[0-9]+)$") + if micro_version_regex.match(ruby): + match_micro_version = True + if re.match("^[a-z]+$", ruby): + match_version = False + ruby = re.sub("^ruby-", "", ruby) + + for impl, version, default in __salt__["rvm.list"](runas = runas): + if impl != "ruby": + version = "{impl}-{version}".format(impl = impl, version = version) + if not match_micro_version: + version = micro_version_regex.sub("", version) + if not match_version: + version = re.sub("-.*", "") + if version == ruby: + ret["result"] = True + ret["comment"] = "Requested ruby exists." + ret["default"] = default + break + return ret + +def installed(name, default = False, runas = None): + """ + Verify that the specified ruby is installed with RVM. RVM is installed when necessary. + + name + The version of ruby to install + default : False + Whether to make this ruby the default. + runas : None + The user to run rvm as. + """ + ret = {'name': name, 'result': None, 'comment': '', 'changes': {}} + + ret = _check_rvm(ret) + if ret["result"] == False: + return ret + + return _check_and_install_ruby(ret, name, default, runas = runas) + +def gemset_present(name, ruby = "default", runas = None): + """ + Verify that the gemset is present. + + name + The name of the gemset. + ruby : default + The ruby version this gemset belongs to. + runas : None + The use user to run rvm as. + """ + ret = {'name': name, 'result': None, 'comment': '', 'changes': {}} + + ret = _check_rvm(ret) + if ret["result"] == False: + return ret + + if name.find("@") != -1: + ruby, name = name.split("@") + ret = _check_ruby(ret, ruby) + if not ret["result"]: + ret["result"] = False + ret["comment"] = "Requested ruby implementation was not found." + return ret + + if name in __salt__["rvm.gemset_list"](ruby, runas = runas): + ret["result"] = True + ret["comment"] = "Gemset already exists." + else: + if __salt__["rvm.gemset_create"](ruby, name, runas = runas): + ret["result"] = True + ret["comment"] = "Gemset successfully created." + ret["changes"][name] = "created" + else: + ret["result"] = False + ret["comment"] = "Gemset could not be created." + + return ret