lib/puppet_library/puppet_library.rb
# -*- encoding: utf-8 -*-
# Puppet Library
# Copyright (C) 2014 drrb
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
require 'optparse'
require 'rack'
require 'yaml'
require 'puppet_library/forge/directory'
require 'puppet_library/forge/multi'
require 'puppet_library/forge/proxy'
require 'puppet_library/forge/source'
require 'puppet_library/version'
module PuppetLibrary
class PuppetLibrary
def initialize(log = STDERR)
@log = log
end
def go(args)
options = parse_options(args)
server = build_server(options)
announce_server_start(options)
start_server(server, options)
rescue ExpectedError => error
@log.puts "Error: #{error}"
end
private
def parse_options(args)
options = {}
option_parser = OptionParser.new do |opts|
opts.banner = "Usage: #{File.basename $0} [options]"
opts.version = VERSION
opts.on("-c", "--config-file FILE", "Config file to read config values from") do |config_file|
options[:config_file] = config_file
end
opts.on("-p", "--port PORT", "Port to listen on (defaults to whatever Rack wants to use)") do |port|
options[:port] = port
end
opts.on("-s", "--server SERVER", "Server to use (defaults to whatever Rack wants to use)") do |server|
options[:server] = server
end
opts.on("-b", "--bind-host HOSTNAME", "Host name to bind to (defaults to whatever Rack wants to use)") do |hostname|
options[:hostname] = hostname
end
opts.on("--daemonize", "Run the server in the background") do
options[:daemonize] = true
end
opts.on("--pidfile FILE", "Write a pidfile to this location after starting (implies --daemonize)") do |pidfile|
options[:daemonize] = true
options[:pidfile] = File.expand_path pidfile
end
options[:forges] = []
opts.on("-m", "--module-dir DIR", "Directory containing packaged modules (can be specified multiple times)") do |module_dir|
options[:forges] << [Forge::Directory, module_dir]
end
opts.on("-x", "--proxy URL", "Remote forge to proxy (can be specified multiple times)") do |url|
options[:forges] << [Forge::Proxy, sanitize_url(url)]
end
opts.on("--source-dir DIR", "Directory containing a module's source (can be specified multiple times)") do |module_dir|
options[:forges] << [Forge::Source, module_dir]
end
opts.on("--cache-basedir DIR", "Cache all proxies' downloaded modules under this directory") do |cache_basedir|
options[:cache_basedir] = cache_basedir
end
end
begin
option_parser.parse(args)
rescue OptionParser::InvalidOption => parse_error
raise ExpectedError, parse_error.message + "\n" + option_parser.help
end
return options
end
def build_server(options)
if options[:config_file]
load_config!(options)
end
load_defaults!(options)
process_options!(options)
Server.configure do
options[:forges].each do |(forge_type, config)|
forge forge_type.new(*config)
end
end
end
def announce_server_start(options)
options = options.clone
options.default = "default"
action = options[:daemonize] ? "Daemonizing" : "Starting"
@log.puts "#{action} Puppet Library server:"
@log.puts " |- Port: #{options[:port]}"
@log.puts " |- Host: #{options[:hostname]}"
@log.puts " |- Pidfile: #{options[:pidfile]}" if options[:pidfile]
@log.puts " |- Server: #{options[:server]}"
@log.puts " `- Forges:"
options[:forges].each do |(forge_type, config)|
@log.puts " - #{forge_type}: #{config}"
end
end
def start_server(server, options)
Rack::Server.start(
:app => server,
:Host => options[:hostname],
:Port => options[:port],
:server => options[:server],
:daemonize => options[:daemonize],
:pid => options[:pidfile]
)
end
def load_config!(options)
config = read_yaml_file(options[:config_file])
options[:daemonize] = config["daemonize"]
options[:port] = config["port"]
options[:pidfile] = config["pidfile"]
options[:server] = config["server"]
options[:hostname] = config["hostname"]
forges_config = config["forges"] || []
configured_forges = forges_config.map do |forge|
[ Forge.const_get(forge.keys.first), forge.values.first ]
end
options[:forges] = configured_forges + options[:forges]
end
def load_defaults!(options)
options[:daemonize] ||= false
options[:pidfile] ||= nil
if options[:forges].empty?
options[:forges] << [ Forge::Proxy, "http://forge.puppetlabs.com" ]
end
end
def process_options!(options)
options[:forges].map! do |(forge_type, config)|
if [ Forge::Directory, Forge::Source ].include? forge_type
[ forge_type, [ Dir.new(sanitize_path(config)) ]]
elsif forge_type == Forge::Proxy && options[:cache_basedir]
cache_dir = File.join(options[:cache_basedir], url_hostname(config))
path = sanitize_path(cache_dir)
FileUtils.mkdir_p path
dir = Dir.new(path)
[ Forge::Cache, [ config, dir ] ]
else
[ forge_type, config ]
end
end
end
def read_yaml_file(path)
YAML.load_file(File.expand_path(path)) || {}
end
def sanitize_url(url)
Http::Url.normalize(url)
end
def sanitize_path(path)
File.expand_path path
end
def url_hostname(url)
URI.parse(Http::Url.normalize(url)).host
end
end
class ExpectedError < StandardError
end
end