Skip to content

Instantly share code, notes, and snippets.

@tko
Created June 27, 2015 22:45
Show Gist options
  • Save tko/7768c3cc87fd29559ea0 to your computer and use it in GitHub Desktop.
Save tko/7768c3cc87fd29559ea0 to your computer and use it in GitHub Desktop.
puppet provider for R packages
# coding: utf-8
# puppet/modules/r/lib/puppet/provider/package/r.rb
#
# puppet provider for R packages that should handle local files, puppet files, and CRAN repositories
#
# Usage:
# package { "ggplot2": provider => r, source => "cran+http://cran-mirror.cs.uu.nl" }
# package { "ggplot2": provider => r, source => "puppet:///modules/whatever/ggplot2_1.0.0.tar.gz" }
# package { "ggplot2": provider => r, source => ["puppet:///modules/whatever/ggplot2_${my_nonexistent_version}.tar.gz",
# "cran+http://cran-mirror.cs.uu.nl",
# ]}
# package { "ggplot2": provider => r, ensure => absent }
#
require 'tempfile'
require "puppet/provider/package"
require "puppet/file_serving"
Puppet::Type.type(:package).provide :r, :parent => Puppet::Provider::Package do
include Puppet::Util
# has_feature :unversionable
has_feature :uninstallable
commands :rcmd => "R"
def self.instances
instances = []
output = rcmd("--vanilla", "--slave", "-e", "pkgs=installed.packages(); cat(pkgs[, 'Version'], labels=rownames(pkgs), fill=1)")
output.each_line { |line|
name, version = line.split()
instances << new({:provider => self.name, :name => name, :ensure => version})
}
return instances
end
def validate_source(sources)
sources = [sources] unless sources.is_a?(Array)
sources.each do |source|
next if Puppet::Util.absolute_path?(source)
begin
uri = URI.parse(URI.escape(source))
rescue => detail
self.fail "Could not understand source #{source}: #{detail}"
end
self.fail "Cannot use relative URLs '#{source}'" unless uri.absolute?
self.fail "Cannot use opaque URLs '#{source}'" unless uri.hierarchical?
unless %w{file puppet cran+http cran+https}.include?(uri.scheme)
self.fail "Cannot use URLs of type '#{uri.scheme}' as source for R modules"
end
end
end
def query
begin
return self.check_installed
rescue Puppet::ExecutionFailure => detail
end
return nil
end
def check_installed
version = rcmd("--vanilla", "--slave", "-e", "cat(installed.packages()[#{self.resource[:name].inspect}, 'Version'])")
return { :ensure => version, :name => self.resource[:name] }
end
def install
self.fail "source is required" unless self.resource[:source]
sources = self.resource[:source]
sources = [sources] unless sources.is_a?(Array)
we_tried = false
sources.each do |source|
next unless self.maybe_install_from(source)
we_tried = true
break
end
fail "Could not retrieve information from environment #{Puppet[:environment]} source(s) #{sources.join(", ")}" unless we_tried
begin
# For some reason install.packages() might silently decide to not install
# the package, only download it. Check to make sure.
self.check_installed
rescue Puppet::ExecutionFailure => detail
fail("Installation failed")
end
end
def maybe_install_from(source)
uri = URI.parse(URI.escape(source))
if %w{cran+http cran+https}.include?(uri.scheme)
repos = source.sub('cran+', '')
self.install_from_repository(repos)
return true
else
return self.maybe_install_from_file(source)
end
end
def maybe_install_from_file(source)
self.with_tarball_path(source) { |path|
rcmd("--vanilla", "--slave", "CMD", "INSTALL", path)
return true
}
return false
end
def install_from_repository(repos)
package = self.resource[:name]
rcmd("--vanilla", "--slave", "-e", "options(warn=2); install.packages(#{package.inspect}, repos=#{repos.inspect})")
end
def uninstall
rcmd("--vanilla", "--slave", "CMD", "REMOVE", self.resource[:name])
end
def with_tarball_path(source)
return nil unless metadata = self.metadata(source)
unless content = Puppet::FileServing::Content.indirection.find(metadata.source)
fail "Could not find any content at #{metadata.source}"
end
if Puppet::Util.absolute_path?(metadata.path)
yield metadata.path
else
Tempfile.open(self.resource[:name]) { |file|
file.write content.content
file.flush
# FIXME: checksums?
yield file.path
}
end
end
def metadata(source)
begin
if data = Puppet::FileServing::Metadata.indirection.find(source)
metadata = data
metadata.source = source
return metadata
end
rescue => detail
fail detail, "Could not retrieve file metadata for #{source}: #{detail}"
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment