kpm 0.7.2 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|