kpm 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +138 -0
  4. data/Gemfile +2 -0
  5. data/README.adoc +126 -109
  6. data/Rakefile +2 -1
  7. data/bin/kpm +4 -2
  8. data/kpm.gemspec +10 -8
  9. data/lib/kpm.rb +3 -0
  10. data/lib/kpm/account.rb +269 -337
  11. data/lib/kpm/base_artifact.rb +40 -36
  12. data/lib/kpm/base_installer.rb +71 -84
  13. data/lib/kpm/blob.rb +29 -0
  14. data/lib/kpm/cli.rb +3 -1
  15. data/lib/kpm/coordinates.rb +10 -12
  16. data/lib/kpm/database.rb +93 -103
  17. data/lib/kpm/diagnostic_file.rb +126 -146
  18. data/lib/kpm/formatter.rb +76 -48
  19. data/lib/kpm/inspector.rb +24 -34
  20. data/lib/kpm/installer.rb +53 -46
  21. data/lib/kpm/kaui_artifact.rb +4 -3
  22. data/lib/kpm/killbill_plugin_artifact.rb +10 -7
  23. data/lib/kpm/killbill_server_artifact.rb +24 -10
  24. data/lib/kpm/migrations.rb +26 -11
  25. data/lib/kpm/nexus_helper/actions.rb +45 -9
  26. data/lib/kpm/nexus_helper/github_api_calls.rb +70 -0
  27. data/lib/kpm/nexus_helper/nexus_api_calls_v2.rb +130 -108
  28. data/lib/kpm/nexus_helper/nexus_facade.rb +5 -3
  29. data/lib/kpm/plugins_directory.rb +14 -9
  30. data/lib/kpm/plugins_directory.yml +16 -175
  31. data/lib/kpm/plugins_manager.rb +29 -24
  32. data/lib/kpm/sha1_checker.rb +56 -15
  33. data/lib/kpm/system.rb +104 -135
  34. data/lib/kpm/system_helpers/cpu_information.rb +56 -55
  35. data/lib/kpm/system_helpers/disk_space_information.rb +60 -63
  36. data/lib/kpm/system_helpers/entropy_available.rb +37 -39
  37. data/lib/kpm/system_helpers/memory_information.rb +52 -51
  38. data/lib/kpm/system_helpers/os_information.rb +45 -47
  39. data/lib/kpm/system_helpers/system_proxy.rb +10 -10
  40. data/lib/kpm/tasks.rb +370 -443
  41. data/lib/kpm/tenant_config.rb +68 -83
  42. data/lib/kpm/tomcat_manager.rb +10 -8
  43. data/lib/kpm/trace_logger.rb +18 -16
  44. data/lib/kpm/uninstaller.rb +81 -14
  45. data/lib/kpm/utils.rb +13 -14
  46. data/lib/kpm/version.rb +3 -1
  47. data/packaging/Gemfile +2 -0
  48. data/pom.xml +1 -1
  49. data/spec/kpm/remote/base_artifact_spec.rb +33 -17
  50. data/spec/kpm/remote/base_installer_spec.rb +35 -34
  51. data/spec/kpm/remote/github_api_calls_spec.rb +40 -0
  52. data/spec/kpm/remote/installer_spec.rb +80 -78
  53. data/spec/kpm/remote/kaui_artifact_spec.rb +7 -6
  54. data/spec/kpm/remote/killbill_plugin_artifact_spec.rb +25 -30
  55. data/spec/kpm/remote/killbill_server_artifact_spec.rb +30 -13
  56. data/spec/kpm/remote/migrations_spec.rb +12 -11
  57. data/spec/kpm/remote/nexus_facade_spec.rb +32 -28
  58. data/spec/kpm/remote/tenant_config_spec.rb +30 -29
  59. data/spec/kpm/remote/tomcat_manager_spec.rb +4 -3
  60. data/spec/kpm/unit/actions_spec.rb +52 -0
  61. data/spec/kpm/unit/base_artifact_spec.rb +19 -18
  62. data/spec/kpm/unit/cpu_information_spec.rb +67 -0
  63. data/spec/kpm/unit/disk_space_information_spec.rb +47 -0
  64. data/spec/kpm/unit/entropy_information_spec.rb +36 -0
  65. data/spec/kpm/unit/formatter_spec.rb +163 -0
  66. data/spec/kpm/unit/inspector_spec.rb +34 -42
  67. data/spec/kpm/unit/installer_spec.rb +7 -6
  68. data/spec/kpm/unit/memory_information_spec.rb +102 -0
  69. data/spec/kpm/unit/os_information_spec.rb +38 -0
  70. data/spec/kpm/unit/plugins_directory_spec.rb +38 -22
  71. data/spec/kpm/unit/plugins_manager_spec.rb +62 -66
  72. data/spec/kpm/unit/sha1_checker_spec.rb +107 -60
  73. data/spec/kpm/unit/uninstaller_spec.rb +118 -72
  74. data/spec/kpm/unit_mysql/account_spec.rb +144 -143
  75. data/spec/spec_helper.rb +20 -18
  76. data/tasks/package.rake +18 -18
  77. metadata +26 -22
@@ -1,12 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rexml/document'
2
4
  require 'set'
3
5
 
4
6
  module KPM
5
7
  class KauiArtifact < BaseArtifact
6
8
  class << self
7
- def versions(overrides={}, ssl_verify=true)
8
-
9
- coordinate_map = {:group_id => KPM::BaseArtifact::KAUI_GROUP_ID, :artifact_id => KPM::BaseArtifact::KAUI_ARTIFACT_ID, :packaging => KPM::BaseArtifact::KAUI_PACKAGING, :classifier => KPM::BaseArtifact::KAUI_CLASSIFIER}
9
+ def versions(overrides = {}, ssl_verify = true)
10
+ coordinate_map = { group_id: KPM::BaseArtifact::KAUI_GROUP_ID, artifact_id: KPM::BaseArtifact::KAUI_ARTIFACT_ID, packaging: KPM::BaseArtifact::KAUI_PACKAGING, classifier: KPM::BaseArtifact::KAUI_CLASSIFIER }
10
11
 
11
12
  coordinates = KPM::Coordinates.build_coordinates(coordinate_map)
12
13
  response = REXML::Document.new nexus_remote(overrides, ssl_verify).search_for_artifacts(coordinates)
@@ -1,23 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rexml/document'
2
4
  require 'set'
3
5
 
4
6
  module KPM
5
7
  class KillbillPluginArtifact < BaseArtifact
6
8
  class << self
7
- def pull(logger, group_id, artifact_id, packaging='jar', classifier=nil, version='LATEST', plugin_name=nil, destination_path=nil, sha1_file=nil, force_download=false, verify_sha1=true, overrides={}, ssl_verify=true)
8
- coordinate_map = {:group_id => group_id, :artifact_id => artifact_id, :packaging => packaging, :classifier => classifier, :version => version}
9
- pull_and_put_in_place(logger, coordinate_map, plugin_name, destination_path, is_ruby_plugin_and_should_skip_top_dir(group_id, artifact_id), sha1_file, force_download, verify_sha1, overrides, ssl_verify)
9
+ def pull(logger, group_id, artifact_id, packaging = 'jar', classifier = nil, version = 'LATEST', plugin_name = nil, destination_path = nil, sha1_file = nil, force_download = false, verify_sha1 = true, overrides = {}, ssl_verify = true)
10
+ coordinate_map = { group_id: group_id, artifact_id: artifact_id, packaging: packaging, classifier: classifier, version: version }
11
+ pull_and_put_in_place(logger, coordinate_map, plugin_name, destination_path, ruby_plugin_and_should_skip_top_dir?(group_id, artifact_id), sha1_file, force_download, verify_sha1, overrides, ssl_verify)
10
12
  end
11
13
 
12
- def versions(overrides={}, ssl_verify=true)
13
- plugins = {:java => {}, :ruby => {}}
14
+ def versions(overrides = {}, ssl_verify = true)
15
+ plugins = { java: {}, ruby: {} }
14
16
 
15
17
  nexus = nexus_remote(overrides, ssl_verify)
16
18
 
17
19
  [[:java, KPM::BaseArtifact::KILLBILL_JAVA_PLUGIN_GROUP_ID], [:ruby, KPM::BaseArtifact::KILLBILL_RUBY_PLUGIN_GROUP_ID]].each do |type_and_group_id|
18
20
  response = REXML::Document.new nexus.search_for_artifacts(type_and_group_id[1])
19
21
  response.elements.each('searchNGResponse/data/artifact') do |element|
20
- artifact_id = element.elements['artifactId'].text
22
+ artifact_id = element.elements['artifactId'].text
21
23
  plugins[type_and_group_id[0]][artifact_id] ||= SortedSet.new
22
24
  plugins[type_and_group_id[0]][artifact_id] << element.elements['version'].text
23
25
  end
@@ -27,9 +29,10 @@ module KPM
27
29
  end
28
30
 
29
31
  protected
32
+
30
33
  # Magic methods...
31
34
 
32
- def is_ruby_plugin_and_should_skip_top_dir(group_id, artifact_id)
35
+ def ruby_plugin_and_should_skip_top_dir?(group_id, artifact_id)
33
36
  # The second check is for custom ruby plugins
34
37
  group_id == KPM::BaseArtifact::KILLBILL_RUBY_PLUGIN_GROUP_ID || artifact_id.include?('plugin')
35
38
  end
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rexml/document'
2
4
  require 'set'
3
5
 
4
6
  module KPM
5
7
  class KillbillServerArtifact < BaseArtifact
6
8
  class << self
7
- def versions(artifact_id, packaging=KPM::BaseArtifact::KILLBILL_PACKAGING, classifier=KPM::BaseArtifact::KILLBILL_CLASSIFIER, overrides={}, ssl_verify=true)
8
- coordinate_map = {:group_id => KPM::BaseArtifact::KILLBILL_GROUP_ID, :artifact_id => artifact_id, :packaging => packaging, :classifier => classifier}
9
+ def versions(artifact_id, packaging = KPM::BaseArtifact::KILLBILL_PACKAGING, classifier = KPM::BaseArtifact::KILLBILL_CLASSIFIER, overrides = {}, ssl_verify = true)
10
+ coordinate_map = { group_id: KPM::BaseArtifact::KILLBILL_GROUP_ID, artifact_id: artifact_id, packaging: packaging, classifier: classifier }
9
11
  coordinates = KPM::Coordinates.build_coordinates(coordinate_map)
10
12
  response = REXML::Document.new nexus_remote(overrides, ssl_verify).search_for_artifacts(coordinates)
11
13
  versions = SortedSet.new
@@ -13,10 +15,13 @@ module KPM
13
15
  versions
14
16
  end
15
17
 
16
- def info(version='LATEST', overrides={}, ssl_verify=true)
18
+ def info(version = 'LATEST', sha1_file = nil, force_download = false, verify_sha1 = true, overrides = {}, ssl_verify = true)
17
19
  logger = Logger.new(STDOUT)
18
20
  logger.level = Logger::ERROR
19
21
 
22
+ # Initialize as early as possible (used in rescue block below)
23
+ sha1_checker = sha1_file ? Sha1Checker.from_file(sha1_file) : nil
24
+
20
25
  version = KPM::Installer.get_kb_latest_stable_version if version == 'LATEST'
21
26
 
22
27
  versions = {}
@@ -29,9 +34,9 @@ module KPM
29
34
  nil,
30
35
  version,
31
36
  dir,
32
- nil,
33
- false,
34
- true,
37
+ sha1_file,
38
+ force_download,
39
+ verify_sha1,
35
40
  overrides,
36
41
  ssl_verify)
37
42
 
@@ -51,19 +56,28 @@ module KPM
51
56
  nil,
52
57
  oss_parent_version,
53
58
  dir,
54
- nil,
55
- false,
56
- true,
59
+ sha1_file,
60
+ force_download,
61
+ verify_sha1,
57
62
  overrides,
58
63
  ssl_verify)
59
64
 
60
65
  pom = REXML::Document.new(File.new(oss_pom_info[:file_path]))
61
66
  properties_element = pom.root.elements['properties']
62
- %w(killbill-api killbill-plugin-api killbill-commons killbill-platform).each do |property|
67
+ %w[killbill-api killbill-plugin-api killbill-commons killbill-platform].each do |property|
63
68
  versions[property] = properties_element.elements["#{property}.version"].text
64
69
  end
70
+
71
+ sha1_checker.cache_killbill_info(version, versions) if sha1_checker
65
72
  end
66
73
  versions
74
+ rescue StandardError => e
75
+ # Network down? Hopefully, we have something in the cache
76
+ cached_version = sha1_checker ? sha1_checker.killbill_info(version) : nil
77
+ raise e if force_download || !cached_version
78
+
79
+ # Use the cache
80
+ cached_version
67
81
  end
68
82
  end
69
83
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
  require 'json'
3
5
  require 'logger'
@@ -6,10 +8,9 @@ require 'pathname'
6
8
 
7
9
  module KPM
8
10
  class Migrations
9
-
10
- KILLBILL_MIGRATION_PATH = /src\/main\/resources\/org\/killbill\/billing\/[a-z]+\/migration\/(V[0-9a-zA-Z_]+.sql)/
11
- JAVA_PLUGIN_MIGRATION_PATH = /src\/main\/resources\/migration\/(V[0-9a-zA-Z_]+.sql)/
12
- RUBY_PLUGIN_MIGRATION_PATH = /db\/migrate\/([0-9a-zA-Z_]+.rb)/
11
+ KILLBILL_MIGRATION_PATH = %r{src/main/resources/org/killbill/billing/[a-z]+/migration/(V[0-9a-zA-Z_]+.sql)}.freeze
12
+ JAVA_PLUGIN_MIGRATION_PATH = %r{src/main/resources/migration/(V[0-9a-zA-Z_]+.sql)}.freeze
13
+ RUBY_PLUGIN_MIGRATION_PATH = %r{db/migrate/([0-9a-zA-Z_]+.rb)}.freeze
13
14
 
14
15
  # Go to https://github.com/settings/tokens to generate a token
15
16
  def initialize(from_version, to_version = nil, repository = 'killbill/killbill', oauth_token = nil, logger = Logger.new(STDOUT))
@@ -34,7 +35,7 @@ module KPM
34
35
  end
35
36
 
36
37
  def save(dir = nil)
37
- return nil if migrations.size == 0
38
+ return nil if migrations.empty?
38
39
 
39
40
  dir ||= Dir.mktmpdir
40
41
  @logger.debug("Storing migrations to #{dir}")
@@ -53,7 +54,7 @@ module KPM
53
54
 
54
55
  def for_version(version = @from_version, name_only = false, migrations_to_skip = Set.new)
55
56
  @logger.info("Looking for migrations repository=#{@repository}, version=#{version}")
56
- metadata = get_as_json("https://api.github.com/repos/#{@repository}/git/trees/#{version}?recursive=1&access_token=#{@oauth_token}")
57
+ metadata = get_as_json("https://api.github.com/repos/#{@repository}/git/trees/#{version}?recursive=1")
57
58
 
58
59
  migrations = []
59
60
  metadata['tree'].each do |entry|
@@ -66,13 +67,13 @@ module KPM
66
67
 
67
68
  sql = nil
68
69
  unless name_only
69
- blob_metadata = get_as_json("#{entry['url']}?access_token=#{@oauth_token}")
70
+ blob_metadata = get_as_json((entry['url']).to_s)
70
71
  sql = decode(blob_metadata['content'], blob_metadata['encoding'])
71
72
  end
72
73
 
73
74
  migrations << {
74
- :name => migration_name,
75
- :sql => sql
75
+ name: migration_name,
76
+ sql: sql
76
77
  }
77
78
  end
78
79
 
@@ -80,8 +81,22 @@ module KPM
80
81
  end
81
82
 
82
83
  def get_as_json(url)
83
- raw = URI.parse(url).read
84
- JSON.parse(raw)
84
+ uri = URI.parse(url)
85
+ http = Net::HTTP.new(uri.host, uri.port)
86
+ http.use_ssl = uri.scheme == 'https'
87
+
88
+ path = uri.path || '/'
89
+ path = "#{path}?#{uri.query}" unless uri.query.nil?
90
+ request = Net::HTTP::Get.new(path)
91
+ request['Authorization'] = "token #{@oauth_token}" unless @oauth_token.nil?
92
+
93
+ response = http.request(request)
94
+ case response.code
95
+ when '200'
96
+ JSON.parse(response.body)
97
+ else
98
+ raise "Unable to download #{url}: #{response.code}"
99
+ end
85
100
  end
86
101
 
87
102
  def decode(content, encoding)
@@ -1,9 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'nexus_api_calls_v2'
2
- #require_relative 'nexus_api_calls_v3'
4
+ require_relative 'github_api_calls'
3
5
 
4
6
  module KPM
5
7
  module NexusFacade
6
8
  class Actions
9
+ DEFAULT_RETRIES = 3
10
+ DEFAULT_CONNECTION_ERRORS = {
11
+ EOFError => 'The remote server dropped the connection',
12
+ Errno::ECONNREFUSED => 'The remote server refused the connection',
13
+ Errno::ECONNRESET => 'The remote server reset the connection',
14
+ Timeout::Error => 'The connection to the remote server timed out',
15
+ Errno::ETIMEDOUT => 'The connection to the remote server timed out',
16
+ SocketError => 'The connection to the remote server could not be established',
17
+ OpenSSL::X509::CertificateError => 'The remote server did not accept the provided SSL certificate',
18
+ OpenSSL::SSL::SSLError => 'The SSL connection to the remote server could not be established',
19
+ Zlib::BufError => 'The remote server replied with an invalid response',
20
+ KPM::NexusFacade::UnexpectedStatusCodeException => nil
21
+ }.freeze
22
+
7
23
  attr_reader :nexus_api_call
8
24
 
9
25
  def initialize(overrides, ssl_verify, logger)
@@ -11,24 +27,44 @@ module KPM
11
27
  overrides[:url] ||= 'https://oss.sonatype.org'
12
28
  overrides[:repository] ||= 'releases'
13
29
 
14
- #this is where the version is verified
15
- #example if
16
- #@nexus_api_call = overrides['version'] == '3' ? NexusApiCallsV3.new(overrides, ssl_verify) : NexusApiCallsV2.new(overrides, ssl_verify)
17
- @nexus_api_call = NexusApiCallsV2.new(overrides, ssl_verify, logger)
30
+ @logger = logger
31
+
32
+ @nexus_api_call = overrides[:url].start_with?('https://maven.pkg.github.com') ? GithubApiCalls.new(overrides, ssl_verify, logger) : NexusApiCallsV2.new(overrides, ssl_verify, logger)
18
33
  end
19
34
 
20
- def pull_artifact(coordinates, destination=nil)
21
- nexus_api_call.pull_artifact(coordinates, destination)
35
+ def pull_artifact(coordinates, destination = nil)
36
+ retry_exceptions("pull_artifact #{coordinates}") { nexus_api_call.pull_artifact(coordinates, destination) }
22
37
  end
23
38
 
24
39
  def get_artifact_info(coordinates)
25
- nexus_api_call.get_artifact_info(coordinates)
40
+ retry_exceptions("get_artifact_info #{coordinates}") { nexus_api_call.get_artifact_info(coordinates) }
26
41
  end
27
42
 
28
43
  def search_for_artifacts(coordinates)
29
- nexus_api_call.search_for_artifacts(coordinates)
44
+ retry_exceptions("search_for_artifacts #{coordinates}") { nexus_api_call.search_for_artifacts(coordinates) }
45
+ end
46
+
47
+ private
48
+
49
+ def retry_exceptions(tag)
50
+ retries = DEFAULT_RETRIES
51
+
52
+ begin
53
+ yield
54
+ rescue *DEFAULT_CONNECTION_ERRORS.keys => e
55
+ retries -= 1
56
+
57
+ @logger.warn(format('Transient error during %<tag>s, retrying (attempt=%<attempt>d): %<msg>s', tag: tag, attempt: DEFAULT_RETRIES - retries, msg: derived_error_message(DEFAULT_CONNECTION_ERRORS, e)))
58
+ retry unless retries.zero?
59
+
60
+ raise
61
+ end
30
62
  end
31
63
 
64
+ def derived_error_message(errors, exception)
65
+ key = (errors.keys & exception.class.ancestors).first
66
+ (key ? errors[key] : nil) || exception.message
67
+ end
32
68
  end
33
69
  end
34
70
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'rexml/document'
6
+ require 'openssl'
7
+
8
+ module KPM
9
+ module NexusFacade
10
+ class GithubApiCalls < NexusApiCallsV2
11
+ def pull_artifact_endpoint(coordinates)
12
+ base_path, versioned_artifact, = build_base_path_and_coords(coordinates)
13
+ "#{base_path}/#{versioned_artifact}"
14
+ end
15
+
16
+ def get_artifact_info(coordinates)
17
+ super
18
+
19
+ _, versioned_artifact, coords = build_base_path_and_coords(coordinates)
20
+ sha1 = get_sha1(coordinates)
21
+ "<artifact-resolution>
22
+ <data>
23
+ <presentLocally>true</presentLocally>
24
+ <groupId>#{coords[:group_id]}</groupId>
25
+ <artifactId>#{coords[:artifact_id]}</artifactId>
26
+ <version>#{coords[:version]}</version>
27
+ <extension>#{coords[:packaging]}</extension>
28
+ <snapshot>#{!(coords[:version] =~ /-SNAPSHOT$/).nil?}</snapshot>
29
+ <sha1>#{sha1}</sha1>
30
+ <repositoryPath>/#{coords[:group_id].gsub('.', '/')}/#{versioned_artifact}</repositoryPath>
31
+ </data>
32
+ </artifact-resolution>"
33
+ end
34
+
35
+ def get_artifact_info_endpoint(coordinates)
36
+ base_path, = build_base_path_and_coords(coordinates)
37
+ "#{base_path}/maven-metadata.xml"
38
+ end
39
+
40
+ def search_for_artifact_endpoint(_coordinates)
41
+ raise NoMethodError, 'GitHub Packages has no search support'
42
+ end
43
+
44
+ def build_query_params(_coordinates, _what_parameters = nil)
45
+ ''
46
+ end
47
+
48
+ private
49
+
50
+ def get_sha1(coordinates)
51
+ base_path, versioned_artifact, = build_base_path_and_coords(coordinates)
52
+ endpoint = "#{base_path}/#{versioned_artifact}.sha1"
53
+ get_response_with_retries(coordinates, endpoint, nil)
54
+ end
55
+
56
+ def build_base_path_and_coords(coordinates)
57
+ coords = parse_coordinates(coordinates)
58
+
59
+ # The url may contain the org and repo, e.g. 'https://maven.pkg.github.com/killbill/qualpay-java-client'
60
+ org_and_repo = URI.parse(configuration[:url]).path
61
+
62
+ [
63
+ "#{org_and_repo}/#{coords[:group_id].gsub('.', '/')}/#{coords[:artifact_id]}",
64
+ "#{coords[:version]}/#{coords[:artifact_id]}-#{coords[:version]}.#{coords[:extension]}",
65
+ coords
66
+ ]
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'net/http'
2
4
  require 'uri'
3
5
  require 'rexml/document'
@@ -5,13 +7,12 @@ require 'openssl'
5
7
 
6
8
  module KPM
7
9
  module NexusFacade
8
-
9
10
  class UnexpectedStatusCodeException < StandardError
10
11
  def initialize(code)
11
12
  @code = code
12
13
  end
13
14
 
14
- def message
15
+ def message
15
16
  "The server responded with a #{@code} status code which is unexpected."
16
17
  end
17
18
  end
@@ -26,19 +27,13 @@ module KPM
26
27
 
27
28
  # This is an extract and slim down of functions needed from nexus_cli to maintain the response expected by the base_artifact.
28
29
  class NexusApiCallsV2
29
- PULL_ARTIFACT_ENDPOINT = '/service/local/artifact/maven/redirect'
30
- GET_ARTIFACT_INFO_ENDPOINT = '/service/local/artifact/maven/resolve'
31
- SEARCH_FOR_ARTIFACT_ENDPOINT = '/service/local/lucene/search'
32
-
33
30
  READ_TIMEOUT_DEFAULT = 60
34
31
  OPEN_TIMEOUT_DEFAULT = 60
35
32
 
36
33
  ERROR_MESSAGE_404 = 'The artifact you requested information for could not be found. Please ensure it exists inside the Nexus.'
37
- ERROR_MESSAGE_503 = 'Could not connect to Nexus. Please ensure the url you are using is reachable.'
38
34
 
39
- attr_reader :version
40
- attr_reader :configuration
41
- attr_reader :ssl_verify
35
+ attr_reader :version, :configuration, :ssl_verify
36
+
42
37
  attr_accessor :logger
43
38
 
44
39
  def initialize(configuration, ssl_verify, logger)
@@ -49,141 +44,168 @@ module KPM
49
44
 
50
45
  def search_for_artifacts(coordinates)
51
46
  logger.debug "Entered - Search for artifact, coordinates: #{coordinates}"
52
- response = get_response(coordinates, SEARCH_FOR_ARTIFACT_ENDPOINT, [:g, :a])
47
+ response = get_response(coordinates, search_for_artifact_endpoint(coordinates), %i[g a])
53
48
 
54
49
  case response.code
55
- when '200'
56
- logger.debug "response body: #{response.body}"
57
- return response.body
58
- else
59
- raise UnexpectedStatusCodeException.new(response.code)
50
+ when '200'
51
+ logger.debug "response body: #{response.body}"
52
+ response.body
53
+ when '404'
54
+ raise StandardError, ERROR_MESSAGE_404
55
+ else
56
+ raise UnexpectedStatusCodeException, response.code
60
57
  end
61
58
  end
62
59
 
63
60
  def get_artifact_info(coordinates)
64
- logger.debug "Entered - Get artifact info, coordinates: #{coordinates}"
65
- response = get_response(coordinates, GET_ARTIFACT_INFO_ENDPOINT, nil)
66
-
67
- case response.code
68
- when '200'
69
- logger.debug "response body: #{response.body}"
70
- return response.body
71
- when '404'
72
- raise StandardError.new(ERROR_MESSAGE_404)
73
- when '503'
74
- raise StandardError.new(ERROR_MESSAGE_503)
75
- else
76
- raise UnexpectedStatusCodeException.new(response.code)
77
- end
61
+ get_response_with_retries(coordinates, get_artifact_info_endpoint(coordinates), nil)
78
62
  end
79
63
 
80
- def pull_artifact(coordinates ,destination)
81
- logger.debug "Entered - Pull artifact, coordinates: #{coordinates}"
64
+ def pull_artifact(coordinates, destination)
82
65
  file_name = get_file_name(coordinates)
83
- destination = File.join(File.expand_path(destination || "."), file_name)
84
- logger.debug "destination: #{destination}"
85
- response = get_response(coordinates, PULL_ARTIFACT_ENDPOINT, nil)
66
+ destination = File.join(File.expand_path(destination || '.'), file_name)
67
+ logger.debug { "Downloading to destination: #{destination}" }
86
68
 
87
- case response.code
88
- when '301', '307'
89
- location = response['Location'].gsub!(configuration[:url],'')
90
- logger.debug 'fetching artifact'
91
- file_response = get_response(nil,location, nil)
92
-
93
- File.open(destination, "wb") do |io|
94
- io.write(file_response.body)
95
- end
96
- when 404
97
- raise StandardError.new(ERROR_MESSAGE_404)
98
- else
99
- raise UnexpectedStatusCodeException.new(response.code)
69
+ File.open(destination, 'wb') do |io|
70
+ io.write(get_response_with_retries(coordinates, pull_artifact_endpoint(coordinates), nil))
100
71
  end
72
+
101
73
  {
102
- :file_name => file_name,
103
- :file_path => File.expand_path(destination),
104
- :version => version,
105
- :size => File.size(File.expand_path(destination))
74
+ file_name: file_name,
75
+ file_path: File.expand_path(destination),
76
+ version: version,
77
+ size: File.size(File.expand_path(destination))
106
78
  }
107
79
  end
108
80
 
109
- private
110
- def parse_coordinates(coordinates)
81
+ def pull_artifact_endpoint(_coordinates)
82
+ '/service/local/artifact/maven/redirect'
83
+ end
111
84
 
112
- if coordinates.nil?
113
- raise ArtifactMalformedException
114
- end
85
+ def get_artifact_info_endpoint(_coordinates)
86
+ '/service/local/artifact/maven/resolve'
87
+ end
115
88
 
116
- split_coordinates = coordinates.split(":")
117
- if (split_coordinates.size == 0 or split_coordinates.size > 5)
118
- raise ArtifactMalformedException
119
- end
89
+ def search_for_artifact_endpoint(_coordinates)
90
+ '/service/local/lucene/search'
91
+ end
120
92
 
121
- artifact = Hash.new
93
+ def build_query_params(coordinates, what_parameters = nil)
94
+ artifact = parse_coordinates(coordinates)
95
+ @version = artifact[:version].to_s.upcase
122
96
 
123
- artifact[:group_id] = split_coordinates[0]
124
- artifact[:artifact_id] = split_coordinates[1]
125
- artifact[:extension] = split_coordinates.size > 3 ? split_coordinates[2] : "jar"
126
- artifact[:classifier] = split_coordinates.size > 4 ? split_coordinates[3] : nil
127
- artifact[:version] = split_coordinates[-1]
97
+ query = { g: artifact[:group_id], a: artifact[:artifact_id], e: artifact[:extension], v: version, r: configuration[:repository] }
98
+ query.merge!(c: artifact[:classifier]) unless artifact[:classifier].nil?
128
99
 
129
- artifact[:version].upcase! if version == "latest"
100
+ params = what_parameters.nil? ? query : {}
101
+ what_parameters.each { |key| params[key] = query[key] unless query[key].nil? } unless what_parameters.nil?
130
102
 
131
- return artifact
132
- end
103
+ params.map { |key, value| "#{key}=#{value}" }.join('&')
104
+ end
133
105
 
134
- def get_file_name(coordinates)
135
- artifact = parse_coordinates(coordinates)
106
+ private
136
107
 
137
- if artifact[:version].casecmp("latest")
138
- artifact[:version] = REXML::Document.new(get_artifact_info(coordinates)).elements["//version"].text
139
- end
108
+ def parse_coordinates(coordinates)
109
+ raise ArtifactMalformedException if coordinates.nil?
140
110
 
141
- if artifact[:classifier].nil?
142
- "#{artifact[:artifact_id]}-#{artifact[:version]}.#{artifact[:extension]}"
143
- else
144
- "#{artifact[:artifact_id]}-#{artifact[:version]}-#{artifact[:classifier]}.#{artifact[:extension]}"
145
- end
146
- end
111
+ split_coordinates = coordinates.split(':')
112
+ raise ArtifactMalformedException if split_coordinates.empty? || (split_coordinates.size > 5)
147
113
 
148
- def get_query_params(coordinates, what_parameters = nil)
149
- artifact = parse_coordinates(coordinates)
150
- @version = artifact[:version].to_s.upcase
114
+ artifact = {}
151
115
 
152
- query = {:g => artifact[:group_id], :a => artifact[:artifact_id], :e => artifact[:extension], :v => version, :r => configuration[:repository]}
153
- query.merge!({:c => artifact[:classifier]}) unless artifact[:classifier].nil?
116
+ artifact[:group_id] = split_coordinates[0]
117
+ artifact[:artifact_id] = split_coordinates[1]
118
+ artifact[:extension] = split_coordinates.size > 3 ? split_coordinates[2] : 'jar'
119
+ artifact[:classifier] = split_coordinates.size > 4 ? split_coordinates[3] : nil
120
+ artifact[:version] = split_coordinates[-1]
154
121
 
155
- params = what_parameters.nil? ? query : Hash.new
156
- what_parameters.each {|key| params[key] = query[key] unless query[key].nil? } unless what_parameters.nil?
122
+ artifact[:version].upcase! if version == 'latest'
157
123
 
158
- params.map{|key,value| "#{key}=#{value}"}.join('&')
159
- end
124
+ artifact
125
+ end
160
126
 
161
- def get_response(coordinates, endpoint, what_parameters)
162
- http = get_http
163
- query_params = get_query_params(coordinates, what_parameters) unless coordinates.nil?
164
- endpoint = get_endpoint_with_params(endpoint, query_params) unless coordinates.nil?
165
- request = Net::HTTP::Get.new(endpoint)
127
+ def get_file_name(coordinates)
128
+ artifact = parse_coordinates(coordinates)
166
129
 
167
- logger.debug "request endpoint: #{endpoint}"
130
+ artifact[:version] = REXML::Document.new(get_artifact_info(coordinates)).elements['//version'].text if artifact[:version].casecmp('latest')
168
131
 
169
- response = http.request(request)
170
- response
132
+ if artifact[:classifier].nil?
133
+ "#{artifact[:artifact_id]}-#{artifact[:version]}.#{artifact[:extension]}"
134
+ else
135
+ "#{artifact[:artifact_id]}-#{artifact[:version]}-#{artifact[:classifier]}.#{artifact[:extension]}"
136
+ end
137
+ end
138
+
139
+ def get_response_with_retries(coordinates, endpoint, what_parameters)
140
+ logger.debug { "Fetching coordinates=#{coordinates}, endpoint=#{endpoint}, params=#{what_parameters}" }
141
+ response = get_response(coordinates, endpoint, what_parameters)
142
+ logger.debug { "Response body: #{response.body}" }
143
+ process_response_with_retries(response)
144
+ end
145
+
146
+ def process_response_with_retries(response)
147
+ case response.code
148
+ when '200'
149
+ response.body
150
+ when '301', '307'
151
+ location = response['Location']
152
+ logger.debug { "Following redirect to #{location}" }
153
+
154
+ new_path = location.gsub!(configuration[:url], '')
155
+ if new_path.nil?
156
+ # Redirect to another domain (e.g. CDN)
157
+ get_raw_response_with_retries(location)
158
+ else
159
+ get_response_with_retries(nil, location, nil)
160
+ end
161
+ when '404'
162
+ raise StandardError, ERROR_MESSAGE_404
163
+ else
164
+ raise UnexpectedStatusCodeException, response.code
171
165
  end
166
+ end
172
167
 
173
- def get_http
174
- uri = URI.parse(configuration[:url])
175
- http = Net::HTTP.new(uri.host,uri.port)
176
- http.open_timeout = configuration[:open_timeout] || OPEN_TIMEOUT_DEFAULT #seconds
177
- http.read_timeout = configuration[:read_timeout] || READ_TIMEOUT_DEFAULT #seconds
178
- http.use_ssl = (ssl_verify != false)
179
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless ssl_verify
180
- http
168
+ def get_response(coordinates, endpoint, what_parameters)
169
+ http = build_http
170
+ query_params = build_query_params(coordinates, what_parameters) unless coordinates.nil?
171
+ endpoint = endpoint_with_params(endpoint, query_params) unless coordinates.nil?
172
+ request = Net::HTTP::Get.new(endpoint)
173
+ if configuration.key?(:username) && configuration.key?(:password)
174
+ request.basic_auth(configuration[:username], configuration[:password])
175
+ elsif configuration.key?(:token)
176
+ request['Authorization'] = "token #{configuration[:token]}"
181
177
  end
182
178
 
183
- def get_endpoint_with_params(endpoint,query_params)
184
- "#{endpoint}?#{URI::DEFAULT_PARSER.escape(query_params)}"
179
+ logger.debug do
180
+ http.set_debug_output(logger)
181
+ "HTTP path: #{endpoint}"
185
182
  end
186
183
 
184
+ http.request(request)
185
+ end
186
+
187
+ def build_http
188
+ uri = URI.parse(configuration[:url])
189
+ http = Net::HTTP.new(uri.host, uri.port)
190
+ http.open_timeout = configuration[:open_timeout] || OPEN_TIMEOUT_DEFAULT # seconds
191
+ http.read_timeout = configuration[:read_timeout] || READ_TIMEOUT_DEFAULT # seconds
192
+ http.use_ssl = (ssl_verify != false)
193
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless ssl_verify
194
+
195
+ logger.debug { "HTTP connection: #{http.inspect}" }
196
+
197
+ http
198
+ end
199
+
200
+ def get_raw_response_with_retries(location)
201
+ response = Net::HTTP.get_response(URI(location))
202
+ logger.debug { "Response body: #{response.body}" }
203
+ process_response_with_retries(response)
204
+ end
205
+
206
+ def endpoint_with_params(endpoint, query_params)
207
+ "#{endpoint}?#{URI::DEFAULT_PARSER.escape(query_params)}"
208
+ end
187
209
  end
188
210
  end
189
211
  end