r10k 1.5.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +0 -1
  3. data/CHANGELOG.mkd +63 -0
  4. data/Gemfile +2 -2
  5. data/README.mkd +1 -10
  6. data/doc/dynamic-environments/configuration.mkd +24 -6
  7. data/doc/dynamic-environments/quickstart.mkd +1 -1
  8. data/integration/Gemfile +1 -1
  9. data/integration/configs/pe/centos-5-64mda +25 -0
  10. data/integration/configs/pe/centos-6-64mda +1 -1
  11. data/integration/configs/pe/centos-7-64mda +1 -1
  12. data/integration/configs/pe/debian-6-64mda +1 -1
  13. data/integration/configs/pe/debian-7-64mda +1 -1
  14. data/integration/configs/pe/redhat-6-64mda +1 -1
  15. data/integration/configs/pe/redhat-7-64mda +1 -1
  16. data/integration/configs/pe/sles-11-64mda +1 -1
  17. data/integration/configs/pe/sles-12-64mda +25 -0
  18. data/integration/configs/pe/ubuntu-1004-64mda +1 -1
  19. data/integration/configs/pe/ubuntu-1204-64mda +1 -1
  20. data/integration/configs/pe/ubuntu-1404-64mda +1 -1
  21. data/integration/files/pre-suite/git_config.pp.erb +19 -0
  22. data/integration/pre-suite/01_git_config.rb +5 -17
  23. data/integration/pre-suite/02_pe_r10k.rb +0 -2
  24. data/integration/scripts/README.mkd +86 -0
  25. data/integration/scripts/setup_r10k_env_centos5.sh +23 -0
  26. data/integration/scripts/setup_r10k_env_centos6.sh +23 -0
  27. data/integration/scripts/setup_r10k_env_rhel7.sh +23 -0
  28. data/integration/scripts/setup_r10k_env_sles11.sh +23 -0
  29. data/integration/scripts/setup_r10k_env_sles12.sh +23 -0
  30. data/integration/scripts/setup_r10k_env_ubuntu1004.sh +23 -0
  31. data/integration/scripts/setup_r10k_env_ubuntu1204.sh +23 -0
  32. data/integration/scripts/setup_r10k_env_ubuntu1404.sh +23 -0
  33. data/integration/tests/basic_functionality/negative/neg_invalid_git_provider.rb +44 -0
  34. data/integration/tests/basic_functionality/rugged_git_provider_with_ssh.rb +106 -0
  35. data/integration/tests/basic_functionality/rugged_git_provider_without_ssh.rb +107 -0
  36. data/integration/tests/git_source/git_source_git.rb +1 -1
  37. data/integration/tests/git_source/git_source_ssh.rb +1 -1
  38. data/integration/tests/git_source/negative/neg_git_unauthorized_https.rb +1 -1
  39. data/integration/tests/git_source/negative/neg_git_unauthorized_ssh.rb +1 -1
  40. data/integration/tests/user_scenario/basic_workflow/negative/neg_bad_basedir.rb +1 -1
  41. data/integration/tests/user_scenario/basic_workflow/negative/neg_bad_git_module.rb +8 -2
  42. data/integration/tests/user_scenario/basic_workflow/negative/neg_bad_git_module_ref.rb +1 -1
  43. data/integration/tests/user_scenario/basic_workflow/negative/neg_bad_git_remote.rb +1 -1
  44. data/integration/tests/user_scenario/basic_workflow/negative/neg_branch_name_collision.rb +1 -1
  45. data/integration/tests/user_scenario/basic_workflow/negative/neg_disk_full.rb +5 -1
  46. data/integration/tests/user_scenario/basic_workflow/negative/neg_duplicate_module_names.rb +3 -3
  47. data/integration/tests/user_scenario/basic_workflow/negative/neg_invalid_puppet_file.rb +1 -1
  48. data/integration/tests/user_scenario/basic_workflow/negative/neg_read_only.rb +1 -1
  49. data/integration/tests/user_scenario/basic_workflow/single_env_10000_files.rb +4 -0
  50. data/integration/tests/user_scenario/basic_workflow/single_env_large_files.rb +4 -0
  51. data/integration/tests/user_scenario/complex_workflow/multi_env_unamanaged.rb +77 -0
  52. data/integration/tests/user_scenario/complex_workflow/single_env_git_module_update.rb +99 -0
  53. data/lib/r10k/action/deploy/display.rb +0 -1
  54. data/lib/r10k/action/runner.rb +20 -0
  55. data/lib/r10k/cli.rb +0 -3
  56. data/lib/r10k/cli/deploy.rb +0 -3
  57. data/lib/r10k/deployment.rb +1 -7
  58. data/lib/r10k/deployment/config.rb +15 -1
  59. data/lib/r10k/deployment/config/loader.rb +0 -11
  60. data/lib/r10k/environment/git.rb +0 -9
  61. data/lib/r10k/environment/svn.rb +0 -9
  62. data/lib/r10k/errors.rb +0 -3
  63. data/lib/r10k/feature.rb +24 -3
  64. data/lib/r10k/forge/module_release.rb +142 -0
  65. data/lib/r10k/git.rb +32 -11
  66. data/lib/r10k/module/forge.rb +25 -54
  67. data/lib/r10k/module_repository/forge.rb +2 -5
  68. data/lib/r10k/puppetfile.rb +1 -1
  69. data/lib/r10k/util/license.rb +20 -0
  70. data/lib/r10k/version.rb +1 -1
  71. data/lib/shared/puppet_forge/connection.rb +62 -0
  72. data/lib/shared/puppet_forge/error.rb +28 -0
  73. data/lib/shared/puppet_forge/tar.rb +10 -0
  74. data/lib/shared/puppet_forge/tar/mini.rb +81 -0
  75. data/lib/shared/puppet_forge/unpacker.rb +68 -0
  76. data/lib/shared/puppet_forge/v3.rb +13 -0
  77. data/lib/shared/puppet_forge/v3/module.rb +66 -0
  78. data/lib/shared/puppet_forge/v3/module_release.rb +73 -0
  79. data/lib/shared/puppet_forge/version.rb +3 -0
  80. data/r10k.gemspec +2 -2
  81. data/r10k.yaml.example +80 -5
  82. data/spec/spec_helper.rb +0 -12
  83. data/spec/unit/action/runner_spec.rb +53 -0
  84. data/spec/unit/deployment/config/loader_spec.rb +5 -19
  85. data/spec/unit/deployment/config_spec.rb +8 -0
  86. data/spec/unit/deployment_spec.rb +1 -1
  87. data/spec/unit/forge/module_release_spec.rb +130 -0
  88. data/spec/unit/git_spec.rb +5 -5
  89. data/spec/unit/module/forge_spec.rb +66 -116
  90. data/spec/unit/module_repository/forge_spec.rb +35 -5
  91. data/spec/unit/module_spec.rb +10 -10
  92. data/spec/unit/puppet_forge/connection_spec.rb +41 -0
  93. data/spec/unit/puppet_forge/tar/mini_spec.rb +87 -0
  94. data/spec/unit/puppet_forge/tar_spec.rb +9 -0
  95. data/spec/unit/puppet_forge/unpacker_spec.rb +59 -0
  96. data/spec/unit/puppet_forge/v3/module_release_spec.rb +68 -0
  97. data/spec/unit/puppet_forge/v3/module_spec.rb +67 -0
  98. metadata +48 -112
  99. data/lib/r10k/cli/environment.rb +0 -28
  100. data/lib/r10k/cli/environment/deploy.rb +0 -26
  101. data/lib/r10k/cli/environment/list.rb +0 -23
  102. data/lib/r10k/cli/environment/stale.rb +0 -24
  103. data/lib/r10k/cli/module.rb +0 -29
  104. data/lib/r10k/cli/module/deploy.rb +0 -27
  105. data/lib/r10k/cli/module/list.rb +0 -24
  106. data/lib/r10k/cli/synchronize.rb +0 -27
  107. data/lib/r10k/deployment/basedir.rb +0 -4
  108. data/lib/r10k/deployment/environment.rb +0 -20
  109. data/lib/r10k/deployment/source.rb +0 -37
  110. data/lib/r10k/git/commit.rb +0 -22
  111. data/lib/r10k/git/head.rb +0 -36
  112. data/lib/r10k/git/ref.rb +0 -66
  113. data/lib/r10k/git/remote_head.rb +0 -19
  114. data/lib/r10k/git/repository.rb +0 -158
  115. data/lib/r10k/git/tag.rb +0 -29
  116. data/lib/r10k/git/working_dir.rb +0 -186
  117. data/lib/r10k/registry.rb +0 -4
  118. data/lib/r10k/semver.rb +0 -128
  119. data/lib/r10k/task.rb +0 -12
  120. data/lib/r10k/task/deployment.rb +0 -164
  121. data/lib/r10k/task/environment.rb +0 -31
  122. data/lib/r10k/task/module.rb +0 -19
  123. data/lib/r10k/task/puppetfile.rb +0 -102
  124. data/lib/r10k/task_runner.rb +0 -72
  125. data/lib/r10k/util/monkey_patches.rb +0 -11
  126. data/spec/fixtures/vcr/cassettes/R10K_ModuleRepository_Forge/and_the_expected_version_is_latest/can_fetch_all_versions_of_a_given_module.yml +0 -187
  127. data/spec/fixtures/vcr/cassettes/R10K_ModuleRepository_Forge/and_the_expected_version_is_latest/can_fetch_the_latest_version_of_a_given_module.yml +0 -187
  128. data/spec/fixtures/vcr/cassettes/R10K_ModuleRepository_Forge/and_the_expected_version_is_latest/ignores_deleted_releases.yml +0 -190
  129. data/spec/fixtures/vcr/cassettes/R10K_ModuleRepository_Forge/it_handles_errors_from_forgeapi_puppetlabs_com/raises_an_error_for_a_non-existant_module.yml +0 -34
  130. data/spec/fixtures/vcr/cassettes/R10K_Module_Forge/and_the_expected_version_is_latest/sets_the_expected_version_based_on_the_latest_forge_version.yml +0 -103
  131. data/spec/shared-examples/git-ref.rb +0 -49
  132. data/spec/unit/deployment/environment_spec.rb +0 -25
  133. data/spec/unit/git/commit_spec.rb +0 -34
  134. data/spec/unit/git/head_spec.rb +0 -22
  135. data/spec/unit/git/ref_spec.rb +0 -45
  136. data/spec/unit/git/repository_spec.rb +0 -34
  137. data/spec/unit/git/tag_spec.rb +0 -32
  138. data/spec/unit/git/working_dir_spec.rb +0 -122
  139. data/spec/unit/util/monkey_patches_spec.rb +0 -20
@@ -69,12 +69,9 @@ class R10K::ModuleRepository::Forge
69
69
  private
70
70
 
71
71
  def make_conn
72
- # Force use of json_pure with multi_json on Ruby 1.8.7
73
- multi_json_opts = (RUBY_VERSION == "1.8.7" ? {:adapter => :json_pure} : {})
74
-
75
72
  Faraday.new(:url => "https://#{@forge}") do |builder|
76
- builder.request(:multi_json, multi_json_opts)
77
- builder.response(:multi_json, multi_json_opts)
73
+ builder.request(:multi_json)
74
+ builder.response(:multi_json)
78
75
 
79
76
  # This needs to be _after_ request/response configuration for testing
80
77
  # purposes. Without this ordering the tests get badly mangled.
@@ -111,7 +111,7 @@ class Puppetfile
111
111
  @librarian = librarian
112
112
  end
113
113
 
114
- def mod(name, args = [])
114
+ def mod(name, args = nil)
115
115
  @librarian.add_module(name, args)
116
116
  end
117
117
 
@@ -0,0 +1,20 @@
1
+ require 'r10k/errors'
2
+ require 'rubygems'
3
+
4
+ module R10K
5
+ module Util
6
+ module License
7
+ def self.load
8
+ if Gem::Specification::find_all_by_name('pe-license').any?
9
+ begin
10
+ return PELicense.load_license_key
11
+ rescue PELicense::InvalidLicenseError => e
12
+ raise R10K::Error.wrap(e, "Invalid PE license detected: #{e.message}")
13
+ end
14
+ end
15
+
16
+ nil
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,3 +1,3 @@
1
1
  module R10K
2
- VERSION = '1.5.1'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -0,0 +1,62 @@
1
+ require 'shared/puppet_forge/version'
2
+
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+
6
+ module PuppetForge
7
+ # Provide a common mixin for adding a HTTP connection to classes.
8
+ #
9
+ # This module provides a common method for creating HTTP connections as well
10
+ # as reusing a single connection object between multiple classes. Including
11
+ # classes can invoke #conn to get a reasonably configured HTTP connection.
12
+ # Connection objects can be passed with the #conn= method.
13
+ #
14
+ # @example
15
+ # class HTTPThing
16
+ # include PuppetForge::Connection
17
+ # end
18
+ # thing = HTTPThing.new
19
+ # thing.conn = thing.make_connection('https://non-standard-forge.site')
20
+ #
21
+ # @api private
22
+ module Connection
23
+
24
+ attr_writer :conn
25
+
26
+ USER_AGENT = "PuppetForge/#{PuppetForge::VERSION} Faraday/#{Faraday::VERSION} Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})"
27
+
28
+ def self.authorization=(token)
29
+ @authorization = token
30
+ end
31
+
32
+ def self.authorization
33
+ @authorization
34
+ end
35
+
36
+ # @return [Faraday::Connection] An existing Faraday connection if one was
37
+ # already set, otherwise a new Faraday connection.
38
+ def conn
39
+ @conn ||= make_connection('https://forgeapi.puppetlabs.com')
40
+ end
41
+
42
+ # Generate a new Faraday connection for the given URL.
43
+ #
44
+ # @param url [String] the base URL for this connection
45
+ # @return [Faraday::Connection]
46
+ def make_connection(url, adapter_args = nil)
47
+ adapter_args ||= [Faraday.default_adapter]
48
+ options = { :headers => { :user_agent => USER_AGENT }}
49
+
50
+ if token = PuppetForge::Connection.authorization
51
+ options[:headers][:authorization] = token
52
+ end
53
+
54
+ Faraday.new(url, options) do |builder|
55
+ builder.response(:json, :content_type => /\bjson$/)
56
+ builder.response(:raise_error)
57
+
58
+ builder.adapter(*adapter_args)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,28 @@
1
+ module PuppetForge
2
+ class Error < RuntimeError
3
+ attr_accessor :original
4
+ def initialize(message, original=nil)
5
+ super(message)
6
+ @original = original
7
+ end
8
+ end
9
+
10
+ class ExecutionFailure < PuppetForge::Error
11
+ end
12
+
13
+ class InvalidPathInPackageError < PuppetForge::Error
14
+ def initialize(options)
15
+ @entry_path = options[:entry_path]
16
+ @directory = options[:directory]
17
+ super "Attempt to install file into #{@entry_path.inspect} under #{@directory.inspect}"
18
+ end
19
+
20
+ def multiline
21
+ <<-MSG.strip
22
+ Could not install package
23
+ Package attempted to install file into
24
+ #{@entry_path.inspect} under #{@directory.inspect}.
25
+ MSG
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+
2
+ module PuppetForge
3
+ class Tar
4
+ require 'shared/puppet_forge/tar/mini'
5
+
6
+ def self.instance
7
+ Mini.new
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,81 @@
1
+ require 'zlib'
2
+ require 'archive/tar/minitar'
3
+
4
+ module PuppetForge
5
+ class Tar
6
+ class Mini
7
+
8
+ SYMLINK_FLAGS = [2]
9
+ VALID_TAR_FLAGS = (0..7)
10
+
11
+ # @return [Hash{:symbol => Array<String>}] a hash with file-category keys pointing to lists of filenames.
12
+ def unpack(sourcefile, destdir)
13
+ # directories need to be changed outside of the Minitar::unpack because directories don't have a :file_done action
14
+ dirlist = []
15
+ file_lists = {}
16
+ Zlib::GzipReader.open(sourcefile) do |reader|
17
+ file_lists = validate_files(reader)
18
+ Archive::Tar::Minitar.unpack(reader, destdir, file_lists[:valid]) do |action, name, stats|
19
+ case action
20
+ when :file_done
21
+ FileUtils.chmod('u+rw,g+r,a-st', "#{destdir}/#{name}")
22
+ when :file_start
23
+ validate_entry(destdir, name)
24
+ when :dir
25
+ validate_entry(destdir, name)
26
+ dirlist << "#{destdir}/#{name}"
27
+ end
28
+ end
29
+ end
30
+ dirlist.each {|d| File.chmod(0755, d)}
31
+ file_lists
32
+ end
33
+
34
+ def pack(sourcedir, destfile)
35
+ Zlib::GzipWriter.open(destfile) do |writer|
36
+ Archive::Tar::Minitar.pack(sourcedir, writer)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ # Categorize all the files in tarfile as :valid, :invalid, or :symlink.
43
+ #
44
+ # :invalid files include 'x' and 'g' flags from the PAX standard but and any other non-standard tar flags.
45
+ # tar format info: http://pic.dhe.ibm.com/infocenter/zos/v1r13/index.jsp?topic=%2Fcom.ibm.zos.r13.bpxa500%2Ftaf.htm
46
+ # pax format info: http://pic.dhe.ibm.com/infocenter/zos/v1r13/index.jsp?topic=%2Fcom.ibm.zos.r13.bpxa500%2Fpxarchfm.htm
47
+ # :symlinks are not supported in Puppet modules
48
+ # :valid files are any of those that can be used in modules
49
+ # @param tarfile name of the tarfile
50
+ # @return [Hash{:symbol => Array<String>}] a hash with file-category keys pointing to lists of filenames.
51
+ def validate_files(tarfile)
52
+ file_lists = {:valid => [], :invalid => [], :symlinks => []}
53
+ Archive::Tar::Minitar.open(tarfile).each do |entry|
54
+ flag = entry.typeflag
55
+ if flag.nil? || flag =~ /[[:digit:]]/ && SYMLINK_FLAGS.include?(flag.to_i)
56
+ file_lists[:symlinks] << entry.name
57
+ elsif flag.nil? || flag =~ /[[:digit:]]/ && VALID_TAR_FLAGS.include?(flag.to_i)
58
+ file_lists[:valid] << entry.name
59
+ else
60
+ file_lists[:invalid] << entry.name
61
+ end
62
+ end
63
+ file_lists
64
+ end
65
+
66
+ def validate_entry(destdir, path)
67
+ if Pathname.new(path).absolute?
68
+ raise PuppetForge::InvalidPathInPackageError, :entry_path => path, :directory => destdir
69
+ end
70
+
71
+ path = File.expand_path File.join(destdir, path)
72
+
73
+ if path !~ /\A#{Regexp.escape destdir}/
74
+ raise PuppetForge::InvalidPathInPackageError, :entry_path => path, :directory => destdir
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ end
81
+
@@ -0,0 +1,68 @@
1
+ require 'pathname'
2
+ require 'shared/puppet_forge/error'
3
+ require 'shared/puppet_forge/tar'
4
+
5
+ module PuppetForge
6
+ class Unpacker
7
+ # Unpack a tar file into a specified directory
8
+ #
9
+ # @param filename [String] the file to unpack
10
+ # @param target [String] the target directory to unpack into
11
+ # @return [Hash{:symbol => Array<String>}] a hash with file-category keys pointing to lists of filenames.
12
+ # The categories are :valid, :invalid and :symlink
13
+ def self.unpack(filename, target, tmpdir)
14
+ inst = self.new(filename, target, tmpdir)
15
+ file_lists = inst.unpack
16
+ inst.move_into(Pathname.new(target))
17
+ file_lists
18
+ end
19
+
20
+ # Set the owner/group of the target directory to those of the source
21
+ # Note: don't call this function on Microsoft Windows
22
+ #
23
+ # @param source [Pathname] source of the permissions
24
+ # @param target [Pathname] target of the permissions change
25
+ def self.harmonize_ownership(source, target)
26
+ FileUtils.chown_R(source.stat.uid, source.stat.gid, target)
27
+ end
28
+
29
+ # @param filename [String] the file to unpack
30
+ # @param target [String] the target directory to unpack into
31
+ def initialize(filename, target, tmpdir)
32
+ @filename = filename
33
+ @target = target
34
+ @tmpdir = tmpdir
35
+ end
36
+
37
+ # @api private
38
+ def unpack
39
+ begin
40
+ PuppetForge::Tar.instance.unpack(@filename, @tmpdir)
41
+ rescue PuppetForge::ExecutionFailure => e
42
+ raise RuntimeError, "Could not extract contents of module archive: #{e.message}"
43
+ end
44
+ end
45
+
46
+ # @api private
47
+ def move_into(dir)
48
+ dir.rmtree if dir.exist?
49
+ FileUtils.mv(root_dir, dir)
50
+ ensure
51
+ FileUtils.rmtree(@tmpdir)
52
+ end
53
+
54
+ # @api private
55
+ def root_dir
56
+ return @root_dir if @root_dir
57
+
58
+ # Grab the first directory containing a metadata.json file
59
+ metadata_file = Dir["#{@tmpdir}/**/metadata.json"].sort_by(&:length)[0]
60
+
61
+ if metadata_file
62
+ @root_dir = Pathname.new(metadata_file).dirname
63
+ else
64
+ raise "No valid metadata.json found!"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,13 @@
1
+ module PuppetForge
2
+ module V3
3
+ # Normalize a module name to use a hyphen as the separator between the
4
+ # author and module.
5
+ #
6
+ # @example
7
+ # PuppetForge::V3.normalize_name('my/module') #=> 'my-module'
8
+ # PuppetForge::V3.normalize_name('my-module') #=> 'my-module'
9
+ def self.normalize_name(name)
10
+ name.tr('/', '-')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,66 @@
1
+ require 'shared/puppet_forge/v3'
2
+ require 'shared/puppet_forge/v3/module_release'
3
+ require 'shared/puppet_forge/connection'
4
+
5
+ module PuppetForge
6
+ module V3
7
+ # Represents metadata for a single Forge module and provides access to
8
+ # the releases of a module.
9
+ class Module
10
+
11
+ include PuppetForge::Connection
12
+
13
+ # @!attribute [r] full_name
14
+ # @return [String] The hyphen separated full name of this module
15
+ attr_reader :full_name
16
+
17
+ # @param full_name [String] The name of this module, will be normalized to
18
+ # a hyphen separated name.
19
+ def initialize(full_name)
20
+ @full_name = PuppetForge::V3.normalize_name(full_name)
21
+ end
22
+
23
+ # Get all released versions of this module
24
+ #
25
+ # @example
26
+ # mod = PuppetForge::V3::Module.new('timmy-boolean')
27
+ # mod.versions
28
+ # #=> ["0.9.0-rc1", "0.9.0", "1.0.0", "1.0.1"]
29
+ #
30
+ # @return [Array<String>] All published versions of the given module
31
+ def versions
32
+ path = "/v3/modules/#{@full_name}"
33
+ response = conn.get(path)
34
+
35
+ releases = []
36
+
37
+ response.body['releases'].each do |release|
38
+ if !release['deleted_at']
39
+ releases << release['version']
40
+ end
41
+ end
42
+
43
+ releases.reverse
44
+ end
45
+
46
+ # Get all released versions of this module
47
+ #
48
+ # @example
49
+ # mod = PuppetForge::V3::Module.new('timmy-boolean')
50
+ # mod.latest_version
51
+ # #=> "1.0.1"
52
+ #
53
+ # @return [String] The latest published version of the given module
54
+ def latest_version
55
+ versions.last
56
+ end
57
+
58
+ # Get a specific release of this module off of the forge.
59
+ #
60
+ # @return [ModuleRelease] a release object of the given version for this module.
61
+ def release(version)
62
+ PuppetForge::V3::ModuleRelease.new(@full_name, version).tap { |mr| mr.conn = conn }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,73 @@
1
+ require 'shared/puppet_forge/v3'
2
+ require 'shared/puppet_forge/connection'
3
+
4
+ module PuppetForge
5
+ module V3
6
+ # Access metadata and downloads for a specific module release.
7
+ class ModuleRelease
8
+
9
+ include PuppetForge::Connection
10
+
11
+ # @!attribute [r] full_name
12
+ # @return [String] The hyphen delimited name of this module
13
+ attr_reader :full_name
14
+
15
+ # @!attribute [r] version
16
+ # @return [String] The version of this module
17
+ attr_reader :version
18
+
19
+ # @param full_name [String] The name of the module, will be normalized
20
+ # to a hyphen delimited name.
21
+ # @param version [String]
22
+ def initialize(full_name, version)
23
+ @full_name = PuppetForge::V3.normalize_name(full_name)
24
+ @version = version
25
+ end
26
+
27
+ # @return [Hash] The complete Forge resposne for this release.
28
+ def data
29
+ @data ||= conn.get(resource_url).body
30
+ end
31
+
32
+ # @return [String] The unique identifier for this module release.
33
+ def slug
34
+ "#{full_name}-#{version}"
35
+ end
36
+
37
+ # Download this module release to the specified path.
38
+ #
39
+ # @param path [Pathname]
40
+ # @return [void]
41
+ def download(path)
42
+ resp = conn.get(file_url)
43
+ path.open('wb') { |fh| fh.write(resp.body) }
44
+ end
45
+
46
+ # Verify that a downloaded module matches the checksum in the metadata for this release.
47
+ #
48
+ # @param path [Pathname]
49
+ # @return [void]
50
+ def verify(path)
51
+ expected_md5 = data['file_md5']
52
+ file_md5 = Digest::MD5.file(path).hexdigest
53
+ if expected_md5 != file_md5
54
+ raise ChecksumMismatch.new("Expected #{path} checksum to be #{expected_md5}, got #{file_md5}")
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def file_url
61
+ "/v3/files/#{slug}.tar.gz"
62
+ end
63
+
64
+ def resource_url
65
+ "/v3/releases/#{slug}"
66
+ end
67
+
68
+ class ChecksumMismatch < StandardError
69
+
70
+ end
71
+ end
72
+ end
73
+ end