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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e4ad4f98c15d6ba4b89830bb6f30c72dc2ce3efd
4
- data.tar.gz: 487e711b4ecdea363118803048f8320a902ed6e2
3
+ metadata.gz: 5501e3e79913a9181ca6b829be328e8c6b3e3c64
4
+ data.tar.gz: 6625c303010b85e82a6f6d121379becb92634eff
5
5
  SHA512:
6
- metadata.gz: 11bb00e50d4037bb7e14c60e69fe8c5d99e5f032dd4cb76adaa670462df40c62b7fe230488b2747fa730c685b88315560bb9543dff3698ef81894e4262ace257
7
- data.tar.gz: 986c55e4faa32246bc1c9387180fec9488b2c153caed62038328f10a5af4b78180880f007e548e6fe0f48bd8e597b04c89a5d48eb3753c754c08855ae04eddc4
6
+ metadata.gz: 2cfa2f30bc16b177d5c272fa495b3d3b5f522959507faf17612b3234c2eb13fc19835b64d8703103692102e25a186188de8b977fd7764eb3c2083ab5e4ec0908
7
+ data.tar.gz: 4df43a457d93c2722154f295aeec9c43e250f4dba2ef7be9e881e2d56348c7adeafe88bd8c8186ee1cf171f844cb07865be2effb4a15258b1ecd1d55dc0e01a0
@@ -64,3 +64,14 @@
64
64
  changes:
65
65
  - Added new Git repo forge that serves modules from Git repositories, using tags for version numbers!
66
66
  - Fixed imports to enable easier embedding of Puppet Library
67
+ - tag: v0.11.0
68
+ changes:
69
+ - Added support for nontrivial tag names as version numbers when proxying Git repositories (e.g. tags named "v1.0.1")
70
+ - Added REST API to clear forge caches
71
+ - Git repositories are cached at startup for better performance and reliability
72
+ - Ensured temp directories are cleaned up more reliably
73
+ - Improved logging
74
+ - Improved Ruby config API for Rack/Passenger/embedded use
75
+ - Fixed issue with "Too many open files" caused by Git repo forges on Ruby 1.8
76
+ - Fixed issue Git repo forges with tags without Modulefiles
77
+ - Fixed issue Git repo forges with no description in Modulefile
data/README.md CHANGED
@@ -16,6 +16,7 @@ Plugins can be created to serve modules from arbitrary sources. Puppet Library c
16
16
  - proxying a remote forge (or forges)
17
17
  - caching proxied forges to disk
18
18
  - serving modules from source on disk
19
+ - serving modules from Git repositories using tags as version numbers!
19
20
 
20
21
  ## Installation
21
22
 
@@ -90,6 +91,7 @@ Puppet Library contains built-in support for:
90
91
  - serving packaged (`.tar.gz`) modules from a directory (or directories) of your choosing
91
92
  - proxying a remote forge (or forges)
92
93
  - serving modules from source on disk
94
+ - serving modules from Git repositories using tags as version numbers!
93
95
  - serving modules from a combination of the above
94
96
 
95
97
  ## Compatibility with other tools
@@ -145,16 +147,23 @@ cat > /webapps/puppet-library/config.ru <<EOF
145
147
  require "rubygems"
146
148
  require "puppet_library"
147
149
 
148
- # NB: this config API is not yet stable, and may change without notice
149
- server = PuppetLibrary::Server.configure do |library|
150
- # Serve our private modules
151
- library.forge PuppetLibrary::Forge::Directory do |forge|
152
- forge.path = "/var/lib/modules"
150
+ # NB: this config API is not yet stable, and may change without notice.
151
+ # The format shown is valid from Puppet Library v0.11.0
152
+ server = PuppetLibrary::Server.configure do
153
+ # Serve our private modules out of a directory on disk
154
+ forge :directory do
155
+ path = "/var/lib/modules"
156
+ end
157
+
158
+ # Serve the latest versions from GitHub
159
+ forge :git_repository do
160
+ source "http://github.com/example/puppetlabs-apache-fork.git"
161
+ include_tags /[0-9.]+/
153
162
  end
154
163
 
155
164
  # Download all other modules from the Puppet Forge
156
- library.forge PuppetLibrary::Forge::Proxy do |forge|
157
- forge.url = "http://forge.puppetlabs.com"
165
+ forge :proxy do
166
+ url "http://forge.puppetlabs.com"
158
167
  end
159
168
  end
160
169
 
data/Rakefile CHANGED
@@ -223,32 +223,32 @@ end
223
223
  desc "Import files individually to make sure they can be imported externally"
224
224
  task "test-imports" do
225
225
  puts "Testing imports:"
226
- Dir.chdir "lib"
227
-
228
- paths = Dir["**/*.rb"].map {|f| f.sub /\.rb$/, "" }
229
- paths = paths.sort_by {|p| p.count "/"}
230
- errors = []
231
- paths.each do |path|
232
- print "importing #{path}..."
233
- output = `bundle exec ruby -e '$LOAD_PATH.unshift(File.expand_path(".")); require "#{path}"' 2>&1`
234
- print " ["
235
- if $?.success?
236
- print "OK".green
237
- else
238
- print "FAIL".red
239
- errors << "#{path}: #{output.red}"
226
+ Dir.chdir "lib" do
227
+ paths = Dir["**/*.rb"].map {|f| f.sub /\.rb$/, "" }
228
+ paths = paths.sort_by {|p| p.count "/"}
229
+ errors = []
230
+ paths.each do |path|
231
+ print "importing #{path}..."
232
+ output = `bundle exec ruby -e '$LOAD_PATH.unshift(File.expand_path(".")); require "#{path}"' 2>&1`
233
+ print " ["
234
+ if $?.success?
235
+ print "OK".green
236
+ else
237
+ print "FAIL".red
238
+ errors << "#{path}: #{output.red}"
239
+ end
240
+ puts "]"
240
241
  end
241
- puts "]"
242
- end
243
242
 
244
- unless errors.empty?
245
- puts
246
- puts
247
- fail <<-EOFAILURE
243
+ unless errors.empty?
244
+ puts
245
+ puts
246
+ fail <<-EOFAILURE
248
247
  Failed to import some files:
249
248
 
250
249
  #{errors.join "\n\n"}
251
- EOFAILURE
250
+ EOFAILURE
251
+ end
252
252
  end
253
253
  end
254
254
 
data/TODO.yml CHANGED
@@ -1,10 +1,15 @@
1
1
  features:
2
2
  - Web UI:
3
3
  - asynchronous (index page loads too slowly)
4
- - Proxy a git repository:
5
- - make it work with remote forges (clone and fetch on query/download)
6
- - make it fast (pre-clone repos on startup (maybe just warn if they can't be found). Maybe cache artifacts?)
7
- - logging
4
+ - Git repo forge:
5
+ - make it more flexible:
6
+ - fail gracefully if we can't contact the remote repo
7
+ - Performance:
8
+ - cache stuff so it's faster
9
+ - logging:
10
+ - use Ruby logging
11
+ - include some verbose logging
12
+ - improve error messages (e.g. for when a module can't be untarred)
8
13
  - Named subforges:
9
14
  - per-subforge queries
10
15
  - upload modules (web form and API)
@@ -13,8 +18,7 @@ features:
13
18
  - delete modules from disk (web form)
14
19
  - Authentication
15
20
  - Config APIs:
16
- - parameterless blocks (especially for Server. Include PuppetLibrary::Forge aliased to Forge in scope)
17
- - validation of required params
21
+ - documentation
18
22
 
19
23
  dubious_features:
20
24
  - Proxy modules' source in a directory (supported individually for now: should we just leave it that way?)
@@ -19,7 +19,6 @@ lib_path = File.join(File.dirname(__FILE__), '..', 'lib')
19
19
  $LOAD_PATH.unshift lib_path unless $LOAD_PATH.include? lib_path
20
20
 
21
21
  require "puppet_library"
22
- require 'optparse'
23
22
 
24
23
  module PuppetLibrary
25
24
  PuppetLibrary.new.go(ARGV)
data/config.ru CHANGED
@@ -18,33 +18,31 @@
18
18
  require 'rubygems'
19
19
  require 'puppet_library'
20
20
 
21
- server = PuppetLibrary::Server.configure do |library|
22
- Forge = PuppetLibrary::Forge
23
-
21
+ server = PuppetLibrary::Server.configure do
24
22
  # My custom modules
25
- library.forge Forge::Directory do |forge|
26
- forge.path = "/var/lib/modules"
23
+ forge :directory do
24
+ path "/var/puppet/modules"
27
25
  end
28
26
 
29
27
  # Unreleased versions from Github
30
- library.forge Forge::GitRepository do |forge|
31
- forge.repository = "https://github.com/puppetlabs/puppetlabs-apache.git"
32
- forge.tag_regex = /[0-9.]+/
28
+ forge :git_repository do
29
+ source "https://github.com/puppetlabs/puppetlabs-apache.git"
30
+ include_tags /[0-9.]+/
33
31
  end
34
32
 
35
- library.forge Forge::GitRepository do |forge|
36
- forge.repository = "https://github.com/puppetlabs/puppetlabs-concat.git"
37
- forge.tag_regex = /[0-9.]+/
33
+ forge :git_repository do
34
+ source "https://github.com/puppetlabs/puppetlabs-concat.git"
35
+ include_tags /[0-9.]+/
38
36
  end
39
37
 
40
- library.forge Forge::GitRepository do |forge|
41
- forge.repository = "https://github.com/puppetlabs/puppetlabs-stdlib.git"
42
- forge.tag_regex = /[0-9.]+/
38
+ forge :git_repository do
39
+ source "https://github.com/puppetlabs/puppetlabs-stdlib.git"
40
+ include_tags /[0-9.]+/
43
41
  end
44
42
 
45
43
  # Everything from The Forge
46
- library.forge Forge::Proxy do |forge|
47
- forge.url = "http://forge.puppetlabs.com"
44
+ forge :proxy do
45
+ url "http://forge.puppetlabs.com"
48
46
  end
49
47
  end
50
48
 
@@ -19,32 +19,23 @@ spec_path = File.expand_path("../../spec", File.dirname(__FILE__))
19
19
  $LOAD_PATH.unshift spec_path unless $LOAD_PATH.include? spec_path
20
20
 
21
21
  require 'module_spec_helper'
22
+ require 'spec_helper'
22
23
  require 'fileutils'
23
24
 
24
25
  include FileUtils
25
26
 
26
27
  def module_dir
27
- unless @module_dir
28
- file = Tempfile.new("module_dir")
29
- @module_dir = file.path
30
- file.delete
31
- end
32
- @module_dir
28
+ @module_dir ||= Tempdir.new("module_dir")
33
29
  end
34
30
 
35
31
  def module_writer
36
- @module_writer ||= ModuleSpecHelper::ModuleWriter.new(module_dir)
32
+ @module_writer ||= ModuleSpecHelper::ModuleWriter.new(module_dir.path)
37
33
  end
38
34
 
39
35
  Before do
40
- mkdir_p module_dir
41
36
  forge.add_forge PuppetLibrary::Forge::Directory.new(module_dir)
42
37
  end
43
38
 
44
- After do
45
- rm_rf module_dir
46
- end
47
-
48
39
  Given /^the "(.*?)" module is available at version "(.*?)"$/ do |full_name, version|
49
40
  author, name = full_name.split "/"
50
41
  module_writer.write_module(author, name, version)
@@ -22,6 +22,7 @@ module PuppetLibrary::Forge
22
22
  require 'puppet_library/forge/abstract'
23
23
  require 'puppet_library/forge/cache'
24
24
  require 'puppet_library/forge/directory'
25
+ require 'puppet_library/forge/forge'
25
26
  require 'puppet_library/forge/git_repository'
26
27
  require 'puppet_library/forge/multi'
27
28
  require 'puppet_library/forge/proxy'
@@ -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 'puppet_library/forge/forge'
18
19
  require 'puppet_library/forge/search_result'
19
20
  require 'puppet_library/util'
20
21
 
@@ -24,7 +25,7 @@ module PuppetLibrary::Forge
24
25
  # types.
25
26
  #
26
27
  # See PuppetLibrary::Forge::Directory for an example
27
- class Abstract
28
+ class Abstract < Forge
28
29
  def initialize(module_repo)
29
30
  @repo = module_repo
30
31
  end
@@ -17,6 +17,8 @@
17
17
 
18
18
  require 'puppet_library/forge/proxy'
19
19
  require 'puppet_library/http/http_client'
20
+ require 'puppet_library/util/config_api'
21
+
20
22
  module PuppetLibrary::Forge
21
23
 
22
24
  # A forge that proxies a remote forge. All module archives downloaded from the
@@ -32,14 +34,16 @@ module PuppetLibrary::Forge
32
34
  # repo.path = "/var/modules/cache
33
35
  # end
34
36
  class Cache < Proxy
35
- class Config
36
- attr_accessor :url, :path
37
- end
38
37
 
39
- def self.configure
40
- config = Config.new
41
- yield(config)
42
- Cache.new(config.url, config.path)
38
+ def self.configure(&block)
39
+ config_api = PuppetLibrary::Util::ConfigApi.for(Cache) do
40
+ required :url, "URL"
41
+ required :path, "path" do |path|
42
+ Dir.new(path)
43
+ end
44
+ end
45
+ config = config_api.configure(&block)
46
+ Cache.new(config.get_url, config.get_path)
43
47
  end
44
48
 
45
49
  # * <tt>:url</tt> - The URL of the remote forge.
@@ -17,6 +17,7 @@
17
17
  require 'json'
18
18
  require 'puppet_library/forge/abstract'
19
19
  require 'puppet_library/archive/archive_reader'
20
+ require 'puppet_library/util/config_api'
20
21
 
21
22
  module PuppetLibrary::Forge
22
23
 
@@ -35,27 +36,28 @@ module PuppetLibrary::Forge
35
36
  # repo.path = "/var/modules/cache
36
37
  # end
37
38
  class Directory < PuppetLibrary::Forge::Abstract
38
- class Config
39
- attr_accessor :path
40
- end
41
39
 
42
- def self.configure
43
- config = Config.new
44
- yield(config)
45
- Directory.new(config.path)
40
+ def self.configure(&block)
41
+ config_api = PuppetLibrary::Util::ConfigApi.for(Directory) do
42
+ required :path, "path to a directory to serve modules from" do |dir|
43
+ Dir.new(File.expand_path(dir)).tap do |dir|
44
+ raise "Module directory '#{dir}' isn't readable" unless File.executable? dir
45
+ end
46
+ end
47
+ end
48
+ config = config_api.configure(&block)
49
+ Directory.new(config.get_path)
46
50
  end
47
51
 
48
52
  # * <tt>:module_dir</tt> - The directory containing the packaged modules.
49
53
  def initialize(module_dir)
50
54
  super(self)
51
- raise "Module directory '#{module_dir}' doesn't exist" unless File.directory? module_dir
52
- raise "Module directory '#{module_dir}' isn't readable" unless File.executable? module_dir
53
55
  @module_dir = module_dir
54
56
  end
55
57
 
56
58
  def get_module(author, name, version)
57
59
  file_name = "#{author}-#{name}-#{version}.tar.gz"
58
- path = File.join(File.expand_path(@module_dir), file_name)
60
+ path = File.join(File.expand_path(@module_dir.path), file_name)
59
61
  if File.exist? path
60
62
  File.open(path, 'r')
61
63
  else
@@ -63,8 +65,8 @@ module PuppetLibrary::Forge
63
65
  end
64
66
  end
65
67
 
66
- def get_metadata(author = "*", module_name = "")
67
- Dir["#{@module_dir}/#{author}-#{module_name}*"].map do |module_path|
68
+ def get_metadata(author, module_name)
69
+ Dir["#{@module_dir.path}/#{author}-#{module_name}*"].map do |module_path|
68
70
  archive = PuppetLibrary::Archive::ArchiveReader.new(module_path)
69
71
  metadata_file = archive.read_entry %r[[^/]+/metadata\.json$]
70
72
  JSON.parse(metadata_file)
@@ -72,7 +74,7 @@ module PuppetLibrary::Forge
72
74
  end
73
75
 
74
76
  def get_all_metadata
75
- get_metadata
77
+ get_metadata("*", "")
76
78
  end
77
79
  end
78
80
  end
@@ -0,0 +1,25 @@
1
+ # Puppet Library
2
+ # Copyright (C) 2014 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ module PuppetLibrary::Forge
18
+ class Forge
19
+ def prime
20
+ end
21
+
22
+ def clear_cache
23
+ end
24
+ end
25
+ end
@@ -21,6 +21,8 @@ require 'rubygems/package'
21
21
  require 'puppet_library/forge/abstract'
22
22
  require 'puppet_library/util/git'
23
23
  require 'puppet_library/util/temp_dir'
24
+ require 'puppet_library/util/config_api'
25
+ require 'puppet_library/http/cache/in_memory'
24
26
 
25
27
  module PuppetLibrary::Forge
26
28
  # A forge for serving modules from a Git repository
@@ -35,27 +37,38 @@ module PuppetLibrary::Forge
35
37
  # repo.include_tags = /[0-9.]+/
36
38
  # end
37
39
  class GitRepository < PuppetLibrary::Forge::Abstract
38
- class Config
39
- attr_accessor :source
40
- attr_accessor :include_tags
41
- end
42
40
 
43
- def self.configure
44
- config = Config.new
45
- yield(config)
46
- GitRepository.new(config.source, config.include_tags)
41
+ def self.configure(&block)
42
+ config_api = PuppetLibrary::Util::ConfigApi.for(GitRepository) do
43
+ required :source, "path or URL"
44
+ required :include_tags, "regex" do |value|
45
+ value.tap do |value|
46
+ raise "not a regex" unless value.is_a? Regexp
47
+ end
48
+ end
49
+ end
50
+
51
+ config = config_api.configure(&block)
52
+ cache_dir = PuppetLibrary::Util::TempDir.new("git-repo-cache")
53
+ git = PuppetLibrary::Util::Git.new(config.get_source, cache_dir)
54
+ GitRepository.new(git, config.get_include_tags)
47
55
  end
48
56
 
49
57
  # * <tt>:source</tt> - The URL or path of the git repository
50
58
  # * <tt>:version_tag_regex</tt> - A regex that describes which tags to serve
51
- def initialize(source, version_tag_regex)
59
+ def initialize(git, version_tag_regex)
52
60
  super(self)
53
- cache_path = PuppetLibrary::Util::TempDir.create("git-repo-cache")
54
61
  @version_tag_regex = version_tag_regex
55
- @git = PuppetLibrary::Util::Git.new(source, cache_path)
62
+ @git = git
63
+ @modulefile_cache = PuppetLibrary::Http::Cache::InMemory.new
64
+ @tags_cache = PuppetLibrary::Http::Cache::InMemory.new
65
+ end
66
+
67
+ def prime
68
+ @git.update_cache!
56
69
  end
57
70
 
58
- def destroy!
71
+ def clear_cache
59
72
  @git.clear_cache!
60
73
  end
61
74
 
@@ -89,14 +102,19 @@ module PuppetLibrary::Forge
89
102
  end
90
103
 
91
104
  private
92
-
93
105
  def tags
94
- @git.tags.select {|tag| tag =~ @version_tag_regex }
106
+ @tags_cache.get do
107
+ tags = @git.tags
108
+ tags = tags.select {|tag| tag =~ @version_tag_regex }
109
+ tags = tags.select {|tag| @git.file_exists?("Modulefile", tag) }
110
+ end
95
111
  end
96
112
 
97
113
  def modulefile_for_tag(tag)
98
- modulefile_source = @git.read_file("Modulefile", tag)
99
- PuppetLibrary::PuppetModule::Modulefile.parse(modulefile_source)
114
+ @modulefile_cache.get(tag) do
115
+ modulefile_source = @git.read_file("Modulefile", tag)
116
+ PuppetLibrary::PuppetModule::Modulefile.parse(modulefile_source)
117
+ end
100
118
  end
101
119
 
102
120
  def modulefile_for(version)
@@ -108,7 +126,14 @@ module PuppetLibrary::Forge
108
126
  end
109
127
 
110
128
  def tag_for(version)
111
- version
129
+ tag_versions[version]
130
+ end
131
+
132
+ def tag_versions
133
+ tags_to_versions = tags.map do |tag|
134
+ [ modulefile_for_tag(tag).get_version, tag ]
135
+ end
136
+ Hash[tags_to_versions]
112
137
  end
113
138
  end
114
139
  end