kpm 0.7.2 → 0.10.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.
- checksums.yaml +5 -5
- data/.gitignore +2 -0
- data/.rubocop.yml +138 -0
- data/Gemfile +2 -0
- data/README.adoc +144 -107
- data/Rakefile +2 -1
- data/bin/kpm +4 -2
- data/kpm.gemspec +11 -8
- data/lib/kpm.rb +3 -0
- data/lib/kpm/account.rb +268 -338
- data/lib/kpm/base_artifact.rb +33 -39
- data/lib/kpm/base_installer.rb +69 -83
- data/lib/kpm/blob.rb +29 -0
- data/lib/kpm/cli.rb +3 -1
- data/lib/kpm/coordinates.rb +10 -12
- data/lib/kpm/database.rb +94 -113
- data/lib/kpm/diagnostic_file.rb +126 -147
- data/lib/kpm/formatter.rb +76 -48
- data/lib/kpm/inspector.rb +24 -34
- data/lib/kpm/installer.rb +53 -46
- data/lib/kpm/kaui_artifact.rb +4 -3
- data/lib/kpm/killbill_plugin_artifact.rb +10 -7
- data/lib/kpm/killbill_server_artifact.rb +13 -12
- data/lib/kpm/migrations.rb +26 -11
- data/lib/kpm/nexus_helper/actions.rb +52 -9
- data/lib/kpm/nexus_helper/cloudsmith_api_calls.rb +83 -0
- data/lib/kpm/nexus_helper/github_api_calls.rb +70 -0
- data/lib/kpm/nexus_helper/nexus_api_calls_v2.rb +130 -108
- data/lib/kpm/nexus_helper/nexus_facade.rb +5 -3
- data/lib/kpm/plugins_directory.rb +9 -8
- data/lib/kpm/plugins_directory.yml +14 -173
- data/lib/kpm/plugins_manager.rb +29 -24
- data/lib/kpm/sha1_checker.rb +31 -18
- data/lib/kpm/system.rb +104 -135
- data/lib/kpm/system_helpers/cpu_information.rb +56 -55
- data/lib/kpm/system_helpers/disk_space_information.rb +60 -63
- data/lib/kpm/system_helpers/entropy_available.rb +37 -39
- data/lib/kpm/system_helpers/memory_information.rb +52 -51
- data/lib/kpm/system_helpers/os_information.rb +45 -47
- data/lib/kpm/system_helpers/system_proxy.rb +10 -10
- data/lib/kpm/tasks.rb +381 -438
- data/lib/kpm/tenant_config.rb +68 -83
- data/lib/kpm/tomcat_manager.rb +10 -8
- data/lib/kpm/trace_logger.rb +18 -16
- data/lib/kpm/uninstaller.rb +81 -14
- data/lib/kpm/utils.rb +13 -14
- data/lib/kpm/version.rb +3 -1
- data/packaging/Gemfile +2 -0
- data/pom.xml +211 -40
- data/spec/kpm/remote/base_artifact_spec.rb +20 -20
- data/spec/kpm/remote/base_installer_spec.rb +35 -34
- data/spec/kpm/remote/cloudsmith_api_calls_spec.rb +40 -0
- data/spec/kpm/remote/github_api_calls_spec.rb +40 -0
- data/spec/kpm/remote/installer_spec.rb +80 -79
- data/spec/kpm/remote/kaui_artifact_spec.rb +7 -6
- data/spec/kpm/remote/killbill_plugin_artifact_spec.rb +25 -30
- data/spec/kpm/remote/killbill_server_artifact_spec.rb +17 -16
- data/spec/kpm/remote/migrations_spec.rb +12 -11
- data/spec/kpm/remote/nexus_facade_spec.rb +32 -28
- data/spec/kpm/remote/tenant_config_spec.rb +30 -29
- data/spec/kpm/remote/tomcat_manager_spec.rb +4 -3
- data/spec/kpm/unit/actions_spec.rb +52 -0
- data/spec/kpm/unit/base_artifact_spec.rb +19 -18
- data/spec/kpm/unit/cpu_information_spec.rb +67 -0
- data/spec/kpm/unit/disk_space_information_spec.rb +47 -0
- data/spec/kpm/unit/entropy_information_spec.rb +36 -0
- data/spec/kpm/unit/formatter_spec.rb +163 -0
- data/spec/kpm/unit/inspector_spec.rb +34 -42
- data/spec/kpm/unit/installer_spec.rb +7 -6
- data/spec/kpm/unit/memory_information_spec.rb +102 -0
- data/spec/kpm/unit/os_information_spec.rb +38 -0
- data/spec/kpm/unit/plugins_directory_spec.rb +38 -22
- data/spec/kpm/unit/plugins_manager_spec.rb +62 -66
- data/spec/kpm/unit/sha1_checker_spec.rb +107 -60
- data/spec/kpm/unit/uninstaller_spec.rb +118 -72
- data/spec/kpm/unit_mysql/account_spec.rb +127 -142
- data/spec/spec_helper.rb +20 -18
- data/tasks/package.rake +18 -18
- metadata +42 -22
data/lib/kpm/kaui_artifact.rb
CHANGED
@@ -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 = {:
|
9
|
-
pull_and_put_in_place(logger, coordinate_map, plugin_name, destination_path,
|
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 = {:
|
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
|
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
|
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 = {:
|
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,14 +15,15 @@ module KPM
|
|
13
15
|
versions
|
14
16
|
end
|
15
17
|
|
16
|
-
def info(version='LATEST', sha1_file=nil, force_download=false, verify_sha1=true, 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
|
|
20
|
-
|
21
|
-
|
22
|
+
# Initialize as early as possible (used in rescue block below)
|
22
23
|
sha1_checker = sha1_file ? Sha1Checker.from_file(sha1_file) : nil
|
23
24
|
|
25
|
+
version = KPM::Installer.get_kb_latest_stable_version if version == 'LATEST'
|
26
|
+
|
24
27
|
versions = {}
|
25
28
|
Dir.mktmpdir do |dir|
|
26
29
|
# Retrieve the main Kill Bill pom
|
@@ -61,7 +64,7 @@ module KPM
|
|
61
64
|
|
62
65
|
pom = REXML::Document.new(File.new(oss_pom_info[:file_path]))
|
63
66
|
properties_element = pom.root.elements['properties']
|
64
|
-
%w
|
67
|
+
%w[killbill-api killbill-plugin-api killbill-commons killbill-platform].each do |property|
|
65
68
|
versions[property] = properties_element.elements["#{property}.version"].text
|
66
69
|
end
|
67
70
|
|
@@ -71,12 +74,10 @@ module KPM
|
|
71
74
|
rescue StandardError => e
|
72
75
|
# Network down? Hopefully, we have something in the cache
|
73
76
|
cached_version = sha1_checker ? sha1_checker.killbill_info(version) : nil
|
74
|
-
if force_download || !cached_version
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
return cached_version
|
79
|
-
end
|
77
|
+
raise e if force_download || !cached_version
|
78
|
+
|
79
|
+
# Use the cache
|
80
|
+
cached_version
|
80
81
|
end
|
81
82
|
end
|
82
83
|
end
|
data/lib/kpm/migrations.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
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.
|
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
|
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(
|
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
|
-
|
75
|
-
|
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
|
-
|
84
|
-
|
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,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'nexus_api_calls_v2'
|
2
|
-
|
4
|
+
require_relative 'github_api_calls'
|
5
|
+
require_relative 'cloudsmith_api_calls'
|
3
6
|
|
4
7
|
module KPM
|
5
8
|
module NexusFacade
|
6
9
|
class Actions
|
10
|
+
DEFAULT_RETRIES = 3
|
11
|
+
DEFAULT_CONNECTION_ERRORS = {
|
12
|
+
EOFError => 'The remote server dropped the connection',
|
13
|
+
Errno::ECONNREFUSED => 'The remote server refused the connection',
|
14
|
+
Errno::ECONNRESET => 'The remote server reset the connection',
|
15
|
+
Timeout::Error => 'The connection to the remote server timed out',
|
16
|
+
Errno::ETIMEDOUT => 'The connection to the remote server timed out',
|
17
|
+
SocketError => 'The connection to the remote server could not be established',
|
18
|
+
OpenSSL::X509::CertificateError => 'The remote server did not accept the provided SSL certificate',
|
19
|
+
OpenSSL::SSL::SSLError => 'The SSL connection to the remote server could not be established',
|
20
|
+
Zlib::BufError => 'The remote server replied with an invalid response',
|
21
|
+
KPM::NexusFacade::UnexpectedStatusCodeException => nil
|
22
|
+
}.freeze
|
23
|
+
|
7
24
|
attr_reader :nexus_api_call
|
8
25
|
|
9
26
|
def initialize(overrides, ssl_verify, logger)
|
@@ -11,24 +28,50 @@ module KPM
|
|
11
28
|
overrides[:url] ||= 'https://oss.sonatype.org'
|
12
29
|
overrides[:repository] ||= 'releases'
|
13
30
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
31
|
+
@logger = logger
|
32
|
+
|
33
|
+
@nexus_api_call = if overrides[:url].start_with?('https://maven.pkg.github.com')
|
34
|
+
GithubApiCalls.new(overrides, ssl_verify, logger)
|
35
|
+
elsif overrides[:url].start_with?('https://dl.cloudsmith.io')
|
36
|
+
CloudsmithApiCalls.new(overrides, ssl_verify, logger)
|
37
|
+
else
|
38
|
+
NexusApiCallsV2.new(overrides, ssl_verify, logger)
|
39
|
+
end
|
18
40
|
end
|
19
41
|
|
20
|
-
def pull_artifact(coordinates, destination=nil)
|
21
|
-
nexus_api_call.pull_artifact(coordinates, destination)
|
42
|
+
def pull_artifact(coordinates, destination = nil)
|
43
|
+
retry_exceptions("pull_artifact #{coordinates}") { nexus_api_call.pull_artifact(coordinates, destination) }
|
22
44
|
end
|
23
45
|
|
24
46
|
def get_artifact_info(coordinates)
|
25
|
-
nexus_api_call.get_artifact_info(coordinates)
|
47
|
+
retry_exceptions("get_artifact_info #{coordinates}") { nexus_api_call.get_artifact_info(coordinates) }
|
26
48
|
end
|
27
49
|
|
28
50
|
def search_for_artifacts(coordinates)
|
29
|
-
nexus_api_call.search_for_artifacts(coordinates)
|
51
|
+
retry_exceptions("search_for_artifacts #{coordinates}") { nexus_api_call.search_for_artifacts(coordinates) }
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def retry_exceptions(tag)
|
57
|
+
retries = DEFAULT_RETRIES
|
58
|
+
|
59
|
+
begin
|
60
|
+
yield
|
61
|
+
rescue *DEFAULT_CONNECTION_ERRORS.keys => e
|
62
|
+
retries -= 1
|
63
|
+
|
64
|
+
@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)))
|
65
|
+
retry unless retries.zero?
|
66
|
+
|
67
|
+
raise
|
68
|
+
end
|
30
69
|
end
|
31
70
|
|
71
|
+
def derived_error_message(errors, exception)
|
72
|
+
key = (errors.keys & exception.class.ancestors).first
|
73
|
+
(key ? errors[key] : nil) || exception.message
|
74
|
+
end
|
32
75
|
end
|
33
76
|
end
|
34
77
|
end
|
@@ -0,0 +1,83 @@
|
|
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 CloudsmithApiCalls < NexusApiCallsV2
|
11
|
+
def pull_artifact_endpoint(coordinates)
|
12
|
+
version_artifact_details = parent_get_artifact_info(coordinates)
|
13
|
+
|
14
|
+
# For SNAPSHOTs, we need to figure out the version used as part of the filename
|
15
|
+
filename_version = begin
|
16
|
+
REXML::Document.new(version_artifact_details).elements['//versioning/snapshotVersions/snapshotVersion[1]/value'].text
|
17
|
+
rescue StandardError
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
coords = parse_coordinates(coordinates)
|
21
|
+
coords[:version] = filename_version unless filename_version.nil?
|
22
|
+
new_coordinates = coords.values.compact.join(':')
|
23
|
+
|
24
|
+
base_path, versioned_artifact, = build_base_path_and_coords(new_coordinates)
|
25
|
+
"#{base_path}/#{versioned_artifact}"
|
26
|
+
end
|
27
|
+
|
28
|
+
alias parent_get_artifact_info get_artifact_info
|
29
|
+
def get_artifact_info(coordinates)
|
30
|
+
super
|
31
|
+
|
32
|
+
_, versioned_artifact, coords = build_base_path_and_coords(coordinates)
|
33
|
+
sha1 = get_sha1(coordinates)
|
34
|
+
"<artifact-resolution>
|
35
|
+
<data>
|
36
|
+
<presentLocally>true</presentLocally>
|
37
|
+
<groupId>#{coords[:group_id]}</groupId>
|
38
|
+
<artifactId>#{coords[:artifact_id]}</artifactId>
|
39
|
+
<version>#{coords[:version]}</version>
|
40
|
+
<extension>#{coords[:packaging]}</extension>
|
41
|
+
<snapshot>#{!(coords[:version] =~ /-SNAPSHOT$/).nil?}</snapshot>
|
42
|
+
<sha1>#{sha1}</sha1>
|
43
|
+
<repositoryPath>/#{coords[:group_id].gsub('.', '/')}/#{versioned_artifact}</repositoryPath>
|
44
|
+
</data>
|
45
|
+
</artifact-resolution>"
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_artifact_info_endpoint(coordinates)
|
49
|
+
base_path, _, coords = build_base_path_and_coords(coordinates)
|
50
|
+
# Note: we must retrieve the XML for the version, to support SNAPSHOTs
|
51
|
+
"#{base_path}/#{coords[:version]}/maven-metadata.xml"
|
52
|
+
end
|
53
|
+
|
54
|
+
def search_for_artifact_endpoint(_coordinates)
|
55
|
+
raise NoMethodError, 'Cloudsmith has no search support'
|
56
|
+
end
|
57
|
+
|
58
|
+
def build_query_params(_coordinates, _what_parameters = nil)
|
59
|
+
''
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def get_sha1(coordinates)
|
65
|
+
base_path, versioned_artifact, = build_base_path_and_coords(coordinates)
|
66
|
+
endpoint = "#{base_path}/#{versioned_artifact}.sha1"
|
67
|
+
get_response_with_retries(coordinates, endpoint, nil)
|
68
|
+
end
|
69
|
+
|
70
|
+
def build_base_path_and_coords(coordinates)
|
71
|
+
coords = parse_coordinates(coordinates)
|
72
|
+
|
73
|
+
token_org_and_repo = URI.parse(configuration[:url]).path
|
74
|
+
|
75
|
+
[
|
76
|
+
"#{token_org_and_repo}/#{coords[:group_id].gsub('.', '/')}/#{coords[:artifact_id]}",
|
77
|
+
"#{coords[:version]}/#{coords[:artifact_id]}-#{coords[:version]}.#{coords[:extension]}",
|
78
|
+
coords
|
79
|
+
]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
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
|
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
|
-
|
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,
|
47
|
+
response = get_response(coordinates, search_for_artifact_endpoint(coordinates), %i[g a])
|
53
48
|
|
54
49
|
case response.code
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
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
|
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 ||
|
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
|
-
|
88
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
110
|
-
|
81
|
+
def pull_artifact_endpoint(_coordinates)
|
82
|
+
'/service/local/artifact/maven/redirect'
|
83
|
+
end
|
111
84
|
|
112
|
-
|
113
|
-
|
114
|
-
|
85
|
+
def get_artifact_info_endpoint(_coordinates)
|
86
|
+
'/service/local/artifact/maven/resolve'
|
87
|
+
end
|
115
88
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
89
|
+
def search_for_artifact_endpoint(_coordinates)
|
90
|
+
'/service/local/lucene/search'
|
91
|
+
end
|
120
92
|
|
121
|
-
|
93
|
+
def build_query_params(coordinates, what_parameters = nil)
|
94
|
+
artifact = parse_coordinates(coordinates)
|
95
|
+
@version = artifact[:version].to_s.upcase
|
122
96
|
|
123
|
-
|
124
|
-
|
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
|
-
|
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
|
-
|
132
|
-
|
103
|
+
params.map { |key, value| "#{key}=#{value}" }.join('&')
|
104
|
+
end
|
133
105
|
|
134
|
-
|
135
|
-
artifact = parse_coordinates(coordinates)
|
106
|
+
private
|
136
107
|
|
137
|
-
|
138
|
-
|
139
|
-
end
|
108
|
+
def parse_coordinates(coordinates)
|
109
|
+
raise ArtifactMalformedException if coordinates.nil?
|
140
110
|
|
141
|
-
|
142
|
-
|
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
|
-
|
149
|
-
artifact = parse_coordinates(coordinates)
|
150
|
-
@version = artifact[:version].to_s.upcase
|
114
|
+
artifact = {}
|
151
115
|
|
152
|
-
|
153
|
-
|
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
|
-
|
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
|
-
|
159
|
-
|
124
|
+
artifact
|
125
|
+
end
|
160
126
|
|
161
|
-
|
162
|
-
|
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
|
-
|
130
|
+
artifact[:version] = REXML::Document.new(get_artifact_info(coordinates)).elements['//version'].text if artifact[:version].casecmp('latest')
|
168
131
|
|
169
|
-
|
170
|
-
|
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
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
184
|
-
|
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
|