puppet-library 0.10.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.yml +11 -0
- data/README.md +16 -7
- data/Rakefile +21 -21
- data/TODO.yml +10 -6
- data/bin/puppet-library +0 -1
- data/config.ru +14 -16
- data/features/step_definitions/sinatra_steps.rb +3 -12
- data/lib/puppet_library/forge.rb +1 -0
- data/lib/puppet_library/forge/abstract.rb +2 -1
- data/lib/puppet_library/forge/cache.rb +11 -7
- data/lib/puppet_library/forge/directory.rb +15 -13
- data/lib/puppet_library/forge/forge.rb +25 -0
- data/lib/puppet_library/forge/git_repository.rb +42 -17
- data/lib/puppet_library/forge/multi.rb +11 -1
- data/lib/puppet_library/forge/proxy.rb +14 -9
- data/lib/puppet_library/forge/source.rb +12 -13
- data/lib/puppet_library/http/cache/disk.rb +26 -8
- data/lib/puppet_library/http/cache/in_memory.rb +13 -10
- data/lib/puppet_library/http/cache/noop.rb +4 -1
- data/lib/puppet_library/puppet_library.rb +10 -7
- data/lib/puppet_library/puppet_module/modulefile.rb +5 -1
- data/lib/puppet_library/server.rb +13 -9
- data/lib/puppet_library/util.rb +1 -0
- data/lib/puppet_library/util/config_api.rb +115 -0
- data/lib/puppet_library/util/git.rb +36 -8
- data/lib/puppet_library/util/logging.rb +49 -0
- data/lib/puppet_library/util/patches.rb +21 -0
- data/lib/puppet_library/util/temp_dir.rb +20 -5
- data/lib/puppet_library/version.rb +1 -1
- data/puppet-library.gemspec +2 -0
- data/spec/archive/archive_reader_spec.rb +2 -6
- data/spec/archive/archiver_spec.rb +5 -9
- data/spec/forge/cache_spec.rb +13 -7
- data/spec/forge/directory_spec.rb +31 -18
- data/spec/forge/git_repository_spec.rb +60 -13
- data/spec/forge/multi_spec.rb +16 -0
- data/spec/forge/proxy_spec.rb +18 -0
- data/spec/forge/source_spec.rb +14 -9
- data/spec/http/cache/disk_spec.rb +11 -6
- data/spec/http/cache/in_memory_spec.rb +12 -0
- data/spec/http/cache/noop_spec.rb +6 -0
- data/spec/integration_test_helper.rb +3 -3
- data/spec/module_spec_helper.rb +2 -2
- data/spec/puppet_library_spec.rb +34 -16
- data/spec/puppet_module/modulefile_spec.rb +48 -9
- data/spec/server_spec.rb +28 -1
- data/spec/spec_helper.rb +2 -14
- data/spec/util/config_api_spec.rb +77 -0
- data/spec/util/git_spec.rb +20 -20
- data/spec/util/patches_spec.rb +18 -0
- data/test/directory_forge_integration_test.rb +6 -8
- data/test/offline_git_repo_forge_integration_test.rb +9 -14
- data/test/offline_proxy_forge_integration_test.rb +11 -14
- data/test/online_git_repo_forge_integration_test.rb +7 -8
- data/test/online_proxy_forge_integration_test.rb +10 -13
- data/test/source_forge_integration_test.rb +7 -8
- metadata +35 -2
@@ -15,7 +15,9 @@
|
|
15
15
|
# You should have received a copy of the GNU General Public License
|
16
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
|
18
|
+
require 'puppet_library/forge/forge'
|
18
19
|
require 'puppet_library/forge/search_result'
|
20
|
+
require 'puppet_library/util/patches'
|
19
21
|
|
20
22
|
module PuppetLibrary::Forge
|
21
23
|
|
@@ -35,11 +37,19 @@ module PuppetLibrary::Forge
|
|
35
37
|
# multi_forge = Multi.new
|
36
38
|
# multi_forge.add_forge(Directory.new("/var/modules"))
|
37
39
|
# multi_forge.add_forge(Proxy.new("http://forge.puppetlabs.com"))
|
38
|
-
class Multi
|
40
|
+
class Multi < Forge
|
39
41
|
def initialize
|
40
42
|
@forges = []
|
41
43
|
end
|
42
44
|
|
45
|
+
def prime
|
46
|
+
@forges.each_in_parallel &:prime
|
47
|
+
end
|
48
|
+
|
49
|
+
def clear_cache
|
50
|
+
@forges.each_in_parallel &:clear_cache
|
51
|
+
end
|
52
|
+
|
43
53
|
# Add another forge to delegate to.
|
44
54
|
def add_forge(forge)
|
45
55
|
@forges << forge
|
@@ -15,9 +15,11 @@
|
|
15
15
|
# You should have received a copy of the GNU General Public License
|
16
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
|
18
|
+
require 'puppet_library/forge/forge'
|
18
19
|
require 'puppet_library/http/http_client'
|
19
20
|
require 'puppet_library/http/cache/in_memory'
|
20
21
|
require 'puppet_library/http/cache/noop'
|
22
|
+
require 'puppet_library/util/config_api'
|
21
23
|
|
22
24
|
module PuppetLibrary::Forge
|
23
25
|
|
@@ -29,16 +31,14 @@ module PuppetLibrary::Forge
|
|
29
31
|
# # The URL of the remote forge
|
30
32
|
# repo.url = "http://forge.example.com
|
31
33
|
# end
|
32
|
-
class Proxy
|
34
|
+
class Proxy < Forge
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
config
|
40
|
-
yield(config)
|
41
|
-
Proxy.new(config.url)
|
36
|
+
def self.configure(&block)
|
37
|
+
config_api = PuppetLibrary::Util::ConfigApi.for(Proxy) do
|
38
|
+
required :url, "URL"
|
39
|
+
end
|
40
|
+
config = config_api.configure(&block)
|
41
|
+
Proxy.new(config.get_url)
|
42
42
|
end
|
43
43
|
|
44
44
|
# * <tt>:url</tt> - The URL of the remote forge.
|
@@ -49,6 +49,11 @@ module PuppetLibrary::Forge
|
|
49
49
|
@download_cache = download_cache
|
50
50
|
end
|
51
51
|
|
52
|
+
def clear_cache
|
53
|
+
@query_cache.clear
|
54
|
+
@download_cache.clear
|
55
|
+
end
|
56
|
+
|
52
57
|
def search_modules(query)
|
53
58
|
query_parameter = query.nil? ? "" : "?q=#{query}"
|
54
59
|
results = get("/modules.json#{query_parameter}")
|
@@ -27,14 +27,14 @@ module PuppetLibrary::Forge
|
|
27
27
|
# <b>Note:</b>
|
28
28
|
# The module directory must have a +Modulefile+.
|
29
29
|
class Source < PuppetLibrary::Forge::Abstract
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
Source.new(config.
|
30
|
+
def self.configure(&block)
|
31
|
+
config_api = PuppetLibrary::Util::ConfigApi.for(Source) do
|
32
|
+
required :path, "path to the module's source" do |path|
|
33
|
+
Dir.new(File.expand_path(path))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
config = config_api.configure(&block)
|
37
|
+
Source.new(config.get_path)
|
38
38
|
end
|
39
39
|
|
40
40
|
CACHE_TTL_MILLIS = 500
|
@@ -42,16 +42,15 @@ module PuppetLibrary::Forge
|
|
42
42
|
# * <tt>:module_dir</tt> - The directory containing the module's source.
|
43
43
|
def initialize(module_dir)
|
44
44
|
super(self)
|
45
|
-
module_dir
|
46
|
-
raise "Module directory '#{module_dir}'
|
47
|
-
raise "Module directory '#{module_dir}' isn't readable" unless File.executable? module_dir
|
45
|
+
raise "Module directory '#{module_dir.path}' doesn't exist" unless File.directory? module_dir.path
|
46
|
+
raise "Module directory '#{module_dir.path}' isn't readable" unless File.executable? module_dir.path
|
48
47
|
@module_dir = module_dir
|
49
48
|
@cache = PuppetLibrary::Http::Cache::InMemory.new(CACHE_TTL_MILLIS)
|
50
49
|
end
|
51
50
|
|
52
51
|
def get_module(author, name, version)
|
53
52
|
return nil unless this_module?(author, name, version)
|
54
|
-
PuppetLibrary::Archive::Archiver.archive_dir(@module_dir, "#{author}-#{name}-#{version}") do |archive|
|
53
|
+
PuppetLibrary::Archive::Archiver.archive_dir(@module_dir.path, "#{author}-#{name}-#{version}") do |archive|
|
55
54
|
archive.add_file("metadata.json", 0644) do |entry|
|
56
55
|
entry.write modulefile.to_metadata.to_json
|
57
56
|
end
|
@@ -78,7 +77,7 @@ module PuppetLibrary::Forge
|
|
78
77
|
end
|
79
78
|
|
80
79
|
def modulefile
|
81
|
-
modulefile_path = File.join(@module_dir, "Modulefile")
|
80
|
+
modulefile_path = File.join(@module_dir.path, "Modulefile")
|
82
81
|
@cache.get modulefile_path do
|
83
82
|
PuppetLibrary::PuppetModule::Modulefile.read(modulefile_path)
|
84
83
|
end
|
@@ -15,14 +15,18 @@
|
|
15
15
|
# You should have received a copy of the GNU General Public License
|
16
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
|
18
|
+
require 'fileutils'
|
19
|
+
require 'monitor'
|
20
|
+
|
18
21
|
module PuppetLibrary::Http
|
19
22
|
module Cache
|
20
23
|
class Disk
|
21
24
|
def initialize(directory)
|
22
25
|
@directory = directory
|
26
|
+
@mutex = Monitor.new
|
23
27
|
end
|
24
28
|
|
25
|
-
def get(path)
|
29
|
+
def get(path = "entry")
|
26
30
|
unless include? path
|
27
31
|
buffer = yield
|
28
32
|
save(path, buffer)
|
@@ -30,25 +34,39 @@ module PuppetLibrary::Http
|
|
30
34
|
retrieve(path)
|
31
35
|
end
|
32
36
|
|
37
|
+
def clear
|
38
|
+
@mutex.synchronize do
|
39
|
+
FileUtils.rm_rf @directory.path
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
33
43
|
private
|
34
44
|
def include?(path)
|
35
|
-
|
45
|
+
@mutex.synchronize do
|
46
|
+
File.exist? entry_path(path)
|
47
|
+
end
|
36
48
|
end
|
37
49
|
|
38
50
|
def save(path, buffer)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
51
|
+
@mutex.synchronize do
|
52
|
+
file_path = entry_path(path)
|
53
|
+
FileUtils.mkdir_p File.dirname(file_path)
|
54
|
+
File.open(file_path, "w") do |file|
|
55
|
+
file.write buffer.read
|
56
|
+
end
|
43
57
|
end
|
44
58
|
end
|
45
59
|
|
46
60
|
def retrieve(path)
|
47
|
-
|
61
|
+
@mutex.synchronize do
|
62
|
+
File.open(entry_path(path))
|
63
|
+
end
|
48
64
|
end
|
49
65
|
|
50
66
|
def entry_path(path)
|
51
|
-
|
67
|
+
@mutex.synchronize do
|
68
|
+
File.join(@directory.path, path)
|
69
|
+
end
|
52
70
|
end
|
53
71
|
end
|
54
72
|
end
|
@@ -18,12 +18,12 @@
|
|
18
18
|
module PuppetLibrary::Http
|
19
19
|
module Cache
|
20
20
|
class InMemory
|
21
|
-
|
22
|
-
def initialize(
|
23
|
-
@reaper = Reaper.new(
|
21
|
+
ARBITRARY_CACHE_TTL_SECONDS = 10
|
22
|
+
def initialize(seconds_to_live = ARBITRARY_CACHE_TTL_SECONDS)
|
23
|
+
@reaper = Reaper.new(seconds_to_live)
|
24
24
|
end
|
25
25
|
|
26
|
-
def get(key)
|
26
|
+
def get(key = "entry")
|
27
27
|
entry = retrieve(key)
|
28
28
|
if entry
|
29
29
|
return entry.value unless @reaper.wants_to_kill? entry
|
@@ -42,6 +42,10 @@ module PuppetLibrary::Http
|
|
42
42
|
cache[key] = entry
|
43
43
|
end
|
44
44
|
|
45
|
+
def clear
|
46
|
+
@cache.clear
|
47
|
+
end
|
48
|
+
|
45
49
|
private
|
46
50
|
def cache
|
47
51
|
@cache ||= {}
|
@@ -55,19 +59,18 @@ module PuppetLibrary::Http
|
|
55
59
|
@value = value
|
56
60
|
end
|
57
61
|
|
58
|
-
def
|
59
|
-
|
60
|
-
age_seconds * 1000
|
62
|
+
def age
|
63
|
+
Time.now - @birth
|
61
64
|
end
|
62
65
|
end
|
63
66
|
|
64
67
|
class Reaper
|
65
|
-
def initialize(
|
66
|
-
@
|
68
|
+
def initialize(time_to_let_live)
|
69
|
+
@time_to_let_live = time_to_let_live
|
67
70
|
end
|
68
71
|
|
69
72
|
def wants_to_kill?(entry)
|
70
|
-
entry.
|
73
|
+
entry.age > @time_to_let_live
|
71
74
|
end
|
72
75
|
end
|
73
76
|
end
|
@@ -15,6 +15,7 @@
|
|
15
15
|
# You should have received a copy of the GNU General Public License
|
16
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
|
18
|
+
require 'optparse'
|
18
19
|
require 'rack'
|
19
20
|
require 'yaml'
|
20
21
|
require 'puppet_library/forge/directory'
|
@@ -103,10 +104,9 @@ module PuppetLibrary
|
|
103
104
|
load_defaults!(options)
|
104
105
|
process_options!(options)
|
105
106
|
|
106
|
-
Server.configure do
|
107
|
+
Server.configure do
|
107
108
|
options[:forges].each do |(forge_type, config)|
|
108
|
-
|
109
|
-
server.forge subforge
|
109
|
+
forge forge_type.new(*config)
|
110
110
|
end
|
111
111
|
end
|
112
112
|
end
|
@@ -161,11 +161,14 @@ module PuppetLibrary
|
|
161
161
|
|
162
162
|
def process_options!(options)
|
163
163
|
options[:forges].map! do |(forge_type, config)|
|
164
|
-
if
|
165
|
-
|
166
|
-
|
164
|
+
if [ Forge::Directory, Forge::Source ].include? forge_type
|
165
|
+
[ forge_type, [ Dir.new(sanitize_path(config)) ]]
|
166
|
+
elsif forge_type == Forge::Proxy && options[:cache_basedir]
|
167
167
|
cache_dir = File.join(options[:cache_basedir], url_hostname(config))
|
168
|
-
|
168
|
+
path = sanitize_path(cache_dir)
|
169
|
+
FileUtils.mkdir_p path
|
170
|
+
dir = Dir.new(path)
|
171
|
+
[ Forge::Cache, [ config, dir ] ]
|
169
172
|
else
|
170
173
|
[ forge_type, config ]
|
171
174
|
end
|
@@ -33,7 +33,7 @@ module PuppetLibrary::PuppetModule
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def get_#{property}
|
36
|
-
@#{property}
|
36
|
+
@#{property} || ""
|
37
37
|
end
|
38
38
|
EOF
|
39
39
|
end
|
@@ -58,8 +58,12 @@ module PuppetLibrary::PuppetModule
|
|
58
58
|
{
|
59
59
|
"name" => get_name,
|
60
60
|
"version" => get_version,
|
61
|
+
"source" => get_source,
|
61
62
|
"author" => get_author,
|
63
|
+
"license" => get_license,
|
64
|
+
"summary" => get_summary,
|
62
65
|
"description" => get_description,
|
66
|
+
"project_page" => get_project_page,
|
63
67
|
"dependencies" => get_dependencies
|
64
68
|
}
|
65
69
|
end
|
@@ -17,8 +17,10 @@
|
|
17
17
|
|
18
18
|
require 'sinatra/base'
|
19
19
|
require 'haml'
|
20
|
+
require 'docile'
|
20
21
|
|
21
22
|
require 'puppet_library/forge/multi'
|
23
|
+
require 'puppet_library/util/patches'
|
22
24
|
|
23
25
|
module PuppetLibrary
|
24
26
|
# The Puppet Library server
|
@@ -41,28 +43,26 @@ module PuppetLibrary
|
|
41
43
|
end
|
42
44
|
|
43
45
|
def forge(forge, &block)
|
44
|
-
if forge.is_a?
|
45
|
-
|
46
|
+
if forge.is_a? Symbol
|
47
|
+
class_name = forge.to_s.snake_case_to_camel_case
|
48
|
+
forge_type = Forge.module_eval(class_name)
|
49
|
+
@forge.add_forge forge_type.configure(&block)
|
46
50
|
else
|
47
51
|
@forge.add_forge forge
|
48
52
|
end
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
52
|
-
def self.
|
53
|
-
puts "PuppetLibrary::Server::set_up deprecated: use #configure instead"
|
54
|
-
self.configure(&config_block)
|
55
|
-
end
|
56
|
-
|
57
|
-
def self.configure
|
56
|
+
def self.configure(&block)
|
58
57
|
forge = Forge::Multi.new
|
59
|
-
|
58
|
+
Docile.dsl_eval(Config.new(forge), &block)
|
60
59
|
Server.new(forge)
|
61
60
|
end
|
62
61
|
|
63
62
|
def initialize(forge)
|
64
63
|
super(nil)
|
65
64
|
@forge = forge
|
65
|
+
@forge.prime
|
66
66
|
end
|
67
67
|
|
68
68
|
configure do
|
@@ -137,6 +137,10 @@ module PuppetLibrary
|
|
137
137
|
end
|
138
138
|
end
|
139
139
|
|
140
|
+
post "/api/forge/clear-cache" do
|
141
|
+
@forge.clear_cache
|
142
|
+
end
|
143
|
+
|
140
144
|
private
|
141
145
|
def download(buffer)
|
142
146
|
if buffer.respond_to?(:size)
|
data/lib/puppet_library/util.rb
CHANGED
@@ -0,0 +1,115 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# Puppet Library
|
3
|
+
# Copyright (C) 2014 drrb
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require 'docile'
|
19
|
+
|
20
|
+
module PuppetLibrary::Util
|
21
|
+
class ConfigApi
|
22
|
+
def self.for(owner, &block)
|
23
|
+
Docile.dsl_eval(ConfigApi.new(owner), &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(owner)
|
27
|
+
@owner = owner
|
28
|
+
@params = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def configure(&block)
|
32
|
+
config_api = config_class.new
|
33
|
+
Docile.dsl_eval(config_api, &block)
|
34
|
+
config_api.validate!
|
35
|
+
config_api
|
36
|
+
end
|
37
|
+
|
38
|
+
def required(name, description, &process)
|
39
|
+
param(name, description, true, process)
|
40
|
+
end
|
41
|
+
|
42
|
+
def param(name, description, required, process)
|
43
|
+
@params << Param.new(name, description, required, process)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def config_class
|
48
|
+
class_name = "#{@owner.to_s.split('::').last}Config"
|
49
|
+
if PuppetLibrary.const_defined?(class_name.intern)
|
50
|
+
PuppetLibrary.const_get(class_name)
|
51
|
+
else
|
52
|
+
define_config_class(class_name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def define_config_class(class_name)
|
57
|
+
params = @params
|
58
|
+
config_class = Class.new(Config) do
|
59
|
+
define_method(:params) { params }
|
60
|
+
params.each do |param|
|
61
|
+
define_method(param.name.to_sym) do |new_value|
|
62
|
+
set(param, new_value)
|
63
|
+
end
|
64
|
+
|
65
|
+
define_method("get_#{param.name}".to_sym) do
|
66
|
+
get(param)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
PuppetLibrary.const_set(class_name, config_class)
|
71
|
+
end
|
72
|
+
|
73
|
+
class Config
|
74
|
+
def initialize
|
75
|
+
@values = {}
|
76
|
+
end
|
77
|
+
|
78
|
+
def validate!
|
79
|
+
missing_params = params.select { |param| param.required? && @values[param].nil? }
|
80
|
+
unless missing_params.empty?
|
81
|
+
param = missing_params.first
|
82
|
+
raise "Config parameter '#{param.name}' is required (expected #{param.description}), but wasn't specified"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def get(param)
|
87
|
+
@values[param]
|
88
|
+
end
|
89
|
+
|
90
|
+
def set(param, new_value)
|
91
|
+
@values[param] = param.process(new_value)
|
92
|
+
rescue => validation_error
|
93
|
+
raise "Invalid value for config parameter '#{param.name}': #{validation_error.message} (was expecting #{param.description})"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Param
|
98
|
+
attr_reader :name, :description
|
99
|
+
|
100
|
+
def initialize(name, description, required, process)
|
101
|
+
@name, @description, @required = name, description, required
|
102
|
+
do_nothing = lambda { |x| x }
|
103
|
+
@process = process || do_nothing
|
104
|
+
end
|
105
|
+
|
106
|
+
def required?
|
107
|
+
@required
|
108
|
+
end
|
109
|
+
|
110
|
+
def process(value)
|
111
|
+
@process.nil? || @process.call(value)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|