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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.yml +11 -0
  3. data/README.md +16 -7
  4. data/Rakefile +21 -21
  5. data/TODO.yml +10 -6
  6. data/bin/puppet-library +0 -1
  7. data/config.ru +14 -16
  8. data/features/step_definitions/sinatra_steps.rb +3 -12
  9. data/lib/puppet_library/forge.rb +1 -0
  10. data/lib/puppet_library/forge/abstract.rb +2 -1
  11. data/lib/puppet_library/forge/cache.rb +11 -7
  12. data/lib/puppet_library/forge/directory.rb +15 -13
  13. data/lib/puppet_library/forge/forge.rb +25 -0
  14. data/lib/puppet_library/forge/git_repository.rb +42 -17
  15. data/lib/puppet_library/forge/multi.rb +11 -1
  16. data/lib/puppet_library/forge/proxy.rb +14 -9
  17. data/lib/puppet_library/forge/source.rb +12 -13
  18. data/lib/puppet_library/http/cache/disk.rb +26 -8
  19. data/lib/puppet_library/http/cache/in_memory.rb +13 -10
  20. data/lib/puppet_library/http/cache/noop.rb +4 -1
  21. data/lib/puppet_library/puppet_library.rb +10 -7
  22. data/lib/puppet_library/puppet_module/modulefile.rb +5 -1
  23. data/lib/puppet_library/server.rb +13 -9
  24. data/lib/puppet_library/util.rb +1 -0
  25. data/lib/puppet_library/util/config_api.rb +115 -0
  26. data/lib/puppet_library/util/git.rb +36 -8
  27. data/lib/puppet_library/util/logging.rb +49 -0
  28. data/lib/puppet_library/util/patches.rb +21 -0
  29. data/lib/puppet_library/util/temp_dir.rb +20 -5
  30. data/lib/puppet_library/version.rb +1 -1
  31. data/puppet-library.gemspec +2 -0
  32. data/spec/archive/archive_reader_spec.rb +2 -6
  33. data/spec/archive/archiver_spec.rb +5 -9
  34. data/spec/forge/cache_spec.rb +13 -7
  35. data/spec/forge/directory_spec.rb +31 -18
  36. data/spec/forge/git_repository_spec.rb +60 -13
  37. data/spec/forge/multi_spec.rb +16 -0
  38. data/spec/forge/proxy_spec.rb +18 -0
  39. data/spec/forge/source_spec.rb +14 -9
  40. data/spec/http/cache/disk_spec.rb +11 -6
  41. data/spec/http/cache/in_memory_spec.rb +12 -0
  42. data/spec/http/cache/noop_spec.rb +6 -0
  43. data/spec/integration_test_helper.rb +3 -3
  44. data/spec/module_spec_helper.rb +2 -2
  45. data/spec/puppet_library_spec.rb +34 -16
  46. data/spec/puppet_module/modulefile_spec.rb +48 -9
  47. data/spec/server_spec.rb +28 -1
  48. data/spec/spec_helper.rb +2 -14
  49. data/spec/util/config_api_spec.rb +77 -0
  50. data/spec/util/git_spec.rb +20 -20
  51. data/spec/util/patches_spec.rb +18 -0
  52. data/test/directory_forge_integration_test.rb +6 -8
  53. data/test/offline_git_repo_forge_integration_test.rb +9 -14
  54. data/test/offline_proxy_forge_integration_test.rb +11 -14
  55. data/test/online_git_repo_forge_integration_test.rb +7 -8
  56. data/test/online_proxy_forge_integration_test.rb +10 -13
  57. data/test/source_forge_integration_test.rb +7 -8
  58. 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
- class Config
35
- attr_accessor :url
36
- end
37
-
38
- def self.configure
39
- config = Config.new
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
- class Config
31
- attr_accessor :path
32
- end
33
-
34
- def self.configure
35
- config = Config.new
36
- yield(config)
37
- Source.new(config.path)
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 = File.expand_path(module_dir)
46
- raise "Module directory '#{module_dir}' doesn't exist" unless File.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
- File.exist? entry_path(path)
45
+ @mutex.synchronize do
46
+ File.exist? entry_path(path)
47
+ end
36
48
  end
37
49
 
38
50
  def save(path, buffer)
39
- file_path = entry_path(path)
40
- FileUtils.mkdir_p File.dirname(file_path)
41
- File.open(file_path, "w") do |file|
42
- file.write buffer.read
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
- File.open(entry_path(path))
61
+ @mutex.synchronize do
62
+ File.open(entry_path(path))
63
+ end
48
64
  end
49
65
 
50
66
  def entry_path(path)
51
- File.join(@directory, path)
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
- ARBITRARY_CACHE_TTL_MILLIS = 10 * 1000
22
- def initialize(millis_to_live = ARBITRARY_CACHE_TTL_MILLIS)
23
- @reaper = Reaper.new(millis_to_live)
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 age_millis
59
- age_seconds = Time.now - @birth
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(millis_to_live)
66
- @millis_to_live = millis_to_live
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.age_millis > @millis_to_live
73
+ entry.age > @time_to_let_live
71
74
  end
72
75
  end
73
76
  end
@@ -20,9 +20,12 @@ require 'puppet_library/http/cache/in_memory'
20
20
  module PuppetLibrary::Http
21
21
  module Cache
22
22
  class NoOp
23
- def get(path)
23
+ def get(path = "entry")
24
24
  yield
25
25
  end
26
+
27
+ def clear
28
+ end
26
29
  end
27
30
  end
28
31
  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 |server|
107
+ Server.configure do
107
108
  options[:forges].each do |(forge_type, config)|
108
- subforge = forge_type.new(*config)
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 forge_type == Forge::Proxy
165
- end
166
- if forge_type == Forge::Proxy && options[:cache_basedir]
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
- [ Forge::Cache, [ config, sanitize_path(cache_dir) ] ]
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? Class
45
- @forge.add_forge forge.configure(&block)
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.set_up(&config_block)
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
- yield(Config.new(forge))
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)
@@ -19,5 +19,6 @@ module PuppetLibrary::Util
19
19
  end
20
20
 
21
21
  require 'puppet_library/util/git'
22
+ require 'puppet_library/util/logging'
22
23
  require 'puppet_library/util/patches'
23
24
  require 'puppet_library/util/temp_dir'
@@ -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