kpm 0.11.2 → 0.12.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52403662d712a2a4e5d5f4858f9f5fdec00cfae193fb0fa584ac9e4ad0915b88
4
- data.tar.gz: f44f9d18991344583ca2cf6edcea3973930396d8ed72781c6d178afa07c42e0f
3
+ metadata.gz: 6643e5b3189a2c301e1f8ed870467b15e8dcec6705f28b871af562dc18d5e908
4
+ data.tar.gz: 4afba99b8398ec1ab9da578b1966e6512b98bb5e3249400df28912d9aae0f0ba
5
5
  SHA512:
6
- metadata.gz: 3f1197e9c21b9ff52f6938a329af118434243e8fceb8145826139e00598f68c1ae9567347cc3d2e462ad2fe6d2e26bc116037b259698656207a61366eda24e28
7
- data.tar.gz: 1b48ddb9aa45a443c2d5a2af4ea8b38b2b3e8dfb66dbffe44ab5b8249dedad13a1cb68398f3f0ed2cef668b7731cc1bfcbf6029ea1bf2b700def47a6e7c2fa7b
6
+ metadata.gz: 1f96129abad9b2d687d2d2ab59689b80b1a799d442d61cd1d2a9716ebb1c6d7b006b36dbf8bd7c71df935a78fdf177fc72504f9ea8ffc189e97d7005a7c53f69
7
+ data.tar.gz: 274e48a2e1a95f93e5aa4b8603d38be14a36ec6daf0ca19d8504e7d6736ad8ef8f5ca108210412d246ebd3eaecbe03b27fc83cac973f4fb3d869e31adfd52635
@@ -49,7 +49,7 @@ module KPM
49
49
 
50
50
  def nexus_defaults
51
51
  {
52
- url: 'https://oss.sonatype.org',
52
+ url: 'https://repo1.maven.org/maven2',
53
53
  repository: 'releases'
54
54
  }
55
55
  end
@@ -3,6 +3,7 @@
3
3
  require_relative 'nexus_api_calls_v2'
4
4
  require_relative 'github_api_calls'
5
5
  require_relative 'cloudsmith_api_calls'
6
+ require_relative 'maven_central_api_calls'
6
7
 
7
8
  module KPM
8
9
  module NexusFacade
@@ -25,15 +26,18 @@ module KPM
25
26
 
26
27
  def initialize(overrides, ssl_verify, logger)
27
28
  overrides ||= {}
28
- overrides[:url] ||= 'https://oss.sonatype.org'
29
+ overrides[:url] ||= 'https://repo1.maven.org/maven2'
29
30
  overrides[:repository] ||= 'releases'
30
31
 
31
32
  @logger = logger
33
+ logger.level = Logger::INFO
32
34
 
33
35
  @nexus_api_call = if overrides[:url].start_with?('https://maven.pkg.github.com')
34
36
  GithubApiCalls.new(overrides, ssl_verify, logger)
35
37
  elsif overrides[:url].start_with?('https://dl.cloudsmith.io')
36
38
  CloudsmithApiCalls.new(overrides, ssl_verify, logger)
39
+ elsif overrides[:url].start_with?('https://repo1.maven.org')
40
+ MavenCentralApiCalls.new(overrides, ssl_verify, logger)
37
41
  else
38
42
  NexusApiCallsV2.new(overrides, ssl_verify, logger)
39
43
  end
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
+ require 'rexml/document'
7
+
8
+ module KPM
9
+ module NexusFacade
10
+ class MavenCentralApiCalls < NexusApiCallsV2
11
+ READ_TIMEOUT_DEFAULT = 60
12
+ OPEN_TIMEOUT_DEFAULT = 60
13
+
14
+ attr_reader :configuration
15
+ attr_accessor :logger
16
+
17
+ BASE_REPO_URL = 'https://repo1.maven.org/maven2'
18
+ SEARCH_API = 'https://search.maven.org/solrsearch/select'
19
+
20
+ def initialize(configuration, _ssl_verify, logger)
21
+ @configuration = configuration
22
+ @configuration[:url] ||= BASE_REPO_URL
23
+ @logger = logger
24
+ end
25
+
26
+ def search_for_artifacts(coordinates)
27
+ artifact = parse_coordinates(coordinates)
28
+ params = {
29
+ q: "g:\"#{artifact[:group_id]}\" AND a:\"#{artifact[:artifact_id]}\"",
30
+ rows: 200,
31
+ wt: 'json',
32
+ core: 'gav'
33
+ }
34
+ query = params.map { |k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')
35
+ url = "#{SEARCH_API}?#{query}"
36
+
37
+ response = Net::HTTP.get_response(URI(url))
38
+ raise "Search failed: #{response.code}" unless response.code.to_i == 200
39
+
40
+ json = JSON.parse(response.body)
41
+ docs = json['response']['docs']
42
+ search_versions = docs.map { |doc| doc['v'] }.uniq
43
+
44
+ # Apply when the artifact provided
45
+ if artifact[:artifact_id]
46
+ # Fetch metadata versions (incase the artifact is not indexed)
47
+ metadata_url = build_metadata_url(artifact)
48
+ metadata_versions = []
49
+ begin
50
+ metadata_response = Net::HTTP.get(URI(metadata_url))
51
+ if metadata_response.nil? || metadata_response.strip.empty?
52
+ logger.debug { "Empty metadata response for #{artifact[:artifact_id]}" }
53
+ else
54
+ begin
55
+ metadata_xml = REXML::Document.new(metadata_response)
56
+ metadata_xml.elements.each('//versioning/versions/version') do |version_node|
57
+ metadata_versions << version_node.text
58
+ end
59
+ rescue REXML::ParseException => e
60
+ logger.debug { "Malformed XML in metadata for #{artifact[:artifact_id]}: #{e.message}" }
61
+ end
62
+ end
63
+ rescue StandardError => e
64
+ logger.debug { "Failed to fetch metadata for #{artifact[:artifact_id]}: #{e.message}" }
65
+ end
66
+
67
+ # Combine versions
68
+ search_versions = (search_versions + metadata_versions).uniq.sort_by do |v|
69
+ begin
70
+ Gem::Version.new(v)
71
+ rescue ArgumentError
72
+ v
73
+ end
74
+ end.reverse
75
+
76
+ artifacts_xml = '<searchNGResponse><data>'
77
+ search_versions.each do |version|
78
+ artifacts_xml += '<artifact>'
79
+ artifacts_xml += "<groupId>#{artifact[:group_id]}</groupId>"
80
+ artifacts_xml += "<artifactId>#{artifact[:artifact_id]}</artifactId>"
81
+ artifacts_xml += "<version>#{version}</version>"
82
+ artifacts_xml += '<repositoryId>central</repositoryId>'
83
+ artifacts_xml += '</artifact>'
84
+ end
85
+ else # Incase no artifact_id is provided for plugin search
86
+ artifacts_xml = '<searchNGResponse><data>'
87
+ docs.each do |doc|
88
+ artifacts_xml += '<artifact>'
89
+ artifacts_xml += "<groupId>#{doc['g']}</groupId>"
90
+ artifacts_xml += "<artifactId>#{doc['a']}</artifactId>"
91
+ artifacts_xml += "<version>#{doc['v']}</version>"
92
+ artifacts_xml += '<repositoryId>central</repositoryId>'
93
+ artifacts_xml += '</artifact>'
94
+ end
95
+ end
96
+
97
+ artifacts_xml += '</data></searchNGResponse>'
98
+ artifacts_xml
99
+ end
100
+
101
+ def get_artifact_info(coordinates)
102
+ coords = parse_coordinates(coordinates)
103
+ version = coords[:version]
104
+ if version.casecmp('latest').zero?
105
+ version = fetch_latest_version(coords)
106
+ coords = coords.merge(version: version)
107
+ end
108
+ _, versioned_artifact, coords = build_base_path_and_coords([coords[:group_id], coords[:artifact_id], coords[:extension], coords[:classifier], version].compact.join(':'))
109
+ sha1 = get_sha1([coords[:group_id], coords[:artifact_id], coords[:extension], coords[:classifier], version].compact.join(':'))
110
+ artifact_xml = '<artifact-resolution><data>'
111
+ artifact_xml += '<presentLocally>true</presentLocally>'
112
+ artifact_xml += "<groupId>#{coords[:group_id]}</groupId>"
113
+ artifact_xml += "<artifactId>#{coords[:artifact_id]}</artifactId>"
114
+ artifact_xml += "<version>#{coords[:version]}</version>"
115
+ artifact_xml += "<extension>#{coords[:extension]}</extension>"
116
+ artifact_xml += "<snapshot>#{!(coords[:version] =~ /-SNAPSHOT$/).nil?}</snapshot>"
117
+ artifact_xml += "<sha1>#{sha1}</sha1>"
118
+ artifact_xml += "<repositoryPath>/#{coords[:group_id].gsub('.', '/')}/#{versioned_artifact}</repositoryPath>"
119
+ artifact_xml += '</data></artifact-resolution>'
120
+ artifact_xml
121
+ end
122
+
123
+ def pull_artifact(coordinates, destination)
124
+ artifact = parse_coordinates(coordinates)
125
+ version = artifact[:version]
126
+ version = fetch_latest_version(artifact) if version.casecmp('latest').zero?
127
+ file_name = build_file_name(artifact[:artifact_id], version, artifact[:classifier], artifact[:extension])
128
+ download_url = build_download_url(artifact, version, file_name)
129
+
130
+ dest_path = File.join(File.expand_path(destination || '.'), file_name)
131
+
132
+ File.open(dest_path, 'wb') do |io|
133
+ io.write(Net::HTTP.get(URI(download_url)))
134
+ end
135
+
136
+ {
137
+ file_name: file_name,
138
+ file_path: File.expand_path(dest_path),
139
+ version: version,
140
+ size: File.size(File.expand_path(dest_path))
141
+ }
142
+ end
143
+
144
+ private
145
+
146
+ def parse_coordinates(coordinates)
147
+ raise 'Invalid coordinates' if coordinates.nil?
148
+
149
+ parts = coordinates.split(':')
150
+
151
+ {
152
+ group_id: parts[0],
153
+ artifact_id: parts[1],
154
+ extension: parts.size > 3 ? parts[2] : 'jar',
155
+ classifier: parts.size > 4 ? parts[3] : nil,
156
+ version: parts[-1]
157
+ }
158
+ end
159
+
160
+ def build_base_path_and_coords(coordinates)
161
+ coords = parse_coordinates(coordinates)
162
+
163
+ token_org_and_repo = URI.parse(configuration[:url]).path
164
+
165
+ [
166
+ "#{token_org_and_repo}/#{coords[:group_id].gsub('.', '/')}/#{coords[:artifact_id]}",
167
+ "#{coords[:version]}/#{coords[:artifact_id]}-#{coords[:version]}.#{coords[:extension]}",
168
+ coords
169
+ ]
170
+ end
171
+
172
+ def get_sha1(coordinates)
173
+ base_path, versioned_artifact, = build_base_path_and_coords(coordinates)
174
+ endpoint = "#{base_path}/#{versioned_artifact}.sha1"
175
+ get_response_with_retries(nil, endpoint, nil)
176
+ end
177
+
178
+ def get_response_with_retries(coordinates, endpoint, what_parameters)
179
+ logger.debug { "Fetching coordinates=#{coordinates}, endpoint=#{endpoint}, params=#{what_parameters}" }
180
+ response = get_response(coordinates, endpoint, what_parameters)
181
+ logger.debug { "Response body: #{response.body}" }
182
+ process_response_with_retries(response)
183
+ end
184
+
185
+ def process_response_with_retries(response)
186
+ case response.code
187
+ when '200'
188
+ response.body
189
+ when '301', '302', '307'
190
+ location = response['Location']
191
+ logger.debug { "Following redirect to #{location}" }
192
+
193
+ new_path = location.gsub!(configuration[:url], '')
194
+ if new_path.nil?
195
+ # Redirect to another domain (e.g. CDN)
196
+ get_raw_response_with_retries(location)
197
+ else
198
+ get_response_with_retries(nil, location, nil)
199
+ end
200
+ when '404'
201
+ raise StandardError, ERROR_MESSAGE_404
202
+ else
203
+ raise UnexpectedStatusCodeException, response.code
204
+ end
205
+ end
206
+
207
+ def get_response(coordinates, endpoint, what_parameters)
208
+ http = build_http
209
+ query_params = build_query_params(coordinates, what_parameters) unless coordinates.nil?
210
+ endpoint = endpoint_with_params(endpoint, query_params) unless coordinates.nil?
211
+ request = Net::HTTP::Get.new(endpoint)
212
+ if configuration.key?(:username) && configuration.key?(:password)
213
+ request.basic_auth(configuration[:username], configuration[:password])
214
+ elsif configuration.key?(:token)
215
+ request['Authorization'] = "token #{configuration[:token]}"
216
+ end
217
+
218
+ logger.debug do
219
+ http.set_debug_output(logger)
220
+ "HTTP path: #{endpoint}"
221
+ end
222
+
223
+ http.request(request)
224
+ end
225
+
226
+ def build_metadata_url(artifact)
227
+ group_path = artifact[:group_id].tr('.', '/')
228
+ "#{BASE_REPO_URL}/#{group_path}/#{artifact[:artifact_id]}/maven-metadata.xml"
229
+ end
230
+
231
+ def fetch_latest_version(artifact)
232
+ xml = REXML::Document.new(Net::HTTP.get(URI(build_metadata_url(artifact))))
233
+ xml.elements['//versioning/latest']&.text || xml.elements['//version']&.text
234
+ end
235
+
236
+ def build_file_name(artifact_id, version, classifier, extension)
237
+ classifier ? "#{artifact_id}-#{version}-#{classifier}.#{extension}" : "#{artifact_id}-#{version}.#{extension}"
238
+ end
239
+
240
+ def build_download_url(artifact, version, file_name)
241
+ group_path = artifact[:group_id].tr('.', '/')
242
+ "#{BASE_REPO_URL}/#{group_path}/#{artifact[:artifact_id]}/#{version}/#{file_name}"
243
+ end
244
+ end
245
+ end
246
+ end
data/lib/kpm/tasks.rb CHANGED
@@ -245,7 +245,7 @@ module KPM
245
245
  def search_for_plugins
246
246
  all_plugins = KillbillPluginArtifact.versions(options[:overrides], options[:ssl_verify])
247
247
 
248
- result = ''
248
+ result = String.new
249
249
  all_plugins.each do |type, plugins|
250
250
  result << "Available #{type} plugins:\n"
251
251
  Hash[plugins.sort].each do |name, versions|
data/lib/kpm/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KPM
4
- VERSION = '0.11.2'
4
+ VERSION = '0.12.1'
5
5
  end
data/pom.xml CHANGED
@@ -22,7 +22,7 @@
22
22
  <modelVersion>4.0.0</modelVersion>
23
23
  <groupId>org.kill-bill.billing.installer</groupId>
24
24
  <artifactId>kpm</artifactId>
25
- <version>0.11.2</version>
25
+ <version>0.12.1</version>
26
26
  <packaging>pom</packaging>
27
27
  <name>KPM</name>
28
28
  <description>KPM: the Kill Bill Package Manager</description>
@@ -77,12 +77,12 @@
77
77
  </snapshotRepository>
78
78
  </distributionManagement>
79
79
  <properties>
80
- <repository.release.id>sonatype-nexus-staging</repository.release.id>
81
- <repository.release.name>Nexus Release Repository</repository.release.name>
82
- <repository.release.url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</repository.release.url>
83
- <repository.snapshot.id>sonatype-nexus-snapshots</repository.snapshot.id>
84
- <repository.snapshot.name>Sonatype Nexus Snapshots</repository.snapshot.name>
85
- <repository.snapshot.url>https://oss.sonatype.org/content/repositories/snapshots/</repository.snapshot.url>
80
+ <repository.release.id>central</repository.release.id>
81
+ <repository.release.name>Central Release Repository</repository.release.name>
82
+ <repository.release.url>https://central.sonatype.com/service/local/staging/deploy/maven2/</repository.release.url>
83
+ <repository.snapshot.id>central</repository.snapshot.id>
84
+ <repository.snapshot.name>Central Snapshots Repository</repository.snapshot.name>
85
+ <repository.snapshot.url>https://central.sonatype.com/repository/maven-snapshots/</repository.snapshot.url>
86
86
  </properties>
87
87
  <build>
88
88
  <pluginManagement>
@@ -201,17 +201,14 @@
201
201
  </executions>
202
202
  </plugin>
203
203
  <plugin>
204
- <groupId>org.sonatype.plugins</groupId>
205
- <artifactId>nexus-staging-maven-plugin</artifactId>
206
- <version>1.6.8</version>
204
+ <groupId>org.sonatype.central</groupId>
205
+ <artifactId>central-publishing-maven-plugin</artifactId>
206
+ <version>0.7.0</version>
207
207
  <extensions>true</extensions>
208
208
  <configuration>
209
- <serverId>ossrh-releases</serverId>
210
- <nexusUrl>https://oss.sonatype.org/</nexusUrl>
211
- <keepStagingRepositoryOnFailure>true</keepStagingRepositoryOnFailure>
212
- <keepStagingRepositoryOnCloseRuleFailure>true</keepStagingRepositoryOnCloseRuleFailure>
213
- <autoReleaseAfterClose>true</autoReleaseAfterClose>
214
- <stagingProgressTimeoutMinutes>10</stagingProgressTimeoutMinutes>
209
+ <autoPublish>true</autoPublish>
210
+ <publishingServerId>central</publishingServerId>
211
+ <waitUntil>published</waitUntil>
215
212
  </configuration>
216
213
  </plugin>
217
214
  </plugins>
@@ -34,9 +34,6 @@ describe KPM::KillbillPluginArtifact do
34
34
  expect(versions[:java]['analytics-plugin']).not_to be_nil
35
35
  logging_plugin_versions = versions[:java]['analytics-plugin'].to_a
36
36
  expect(logging_plugin_versions.size).to be >= 3
37
- expect(logging_plugin_versions[0]).to eq '0.6.0'
38
- expect(logging_plugin_versions[1]).to eq '0.7.0'
39
- expect(logging_plugin_versions[2]).to eq '0.7.1'
40
37
  end
41
38
 
42
39
  private
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'rexml/document'
5
+
6
+ describe KPM::NexusFacade do
7
+ let(:logger) do
8
+ logger = ::Logger.new(STDOUT)
9
+ logger.level = Logger::DEBUG
10
+ logger
11
+ end
12
+ let(:test_version) { '0.24.15' }
13
+ let(:coordinates_map) do
14
+ { version: test_version,
15
+ group_id: 'org.kill-bill.billing',
16
+ artifact_id: 'killbill',
17
+ packaging: 'pom',
18
+ classifier: nil }
19
+ end
20
+ let(:coordinates) { KPM::Coordinates.build_coordinates(coordinates_map) }
21
+ let(:nexus_remote) { described_class::MavenCentralApiCalls.new({}, nil, logger) }
22
+
23
+ context 'when pulling release artifact' do
24
+ it {
25
+ response = nil
26
+ expect { response = nexus_remote.get_artifact_info(coordinates) }.not_to raise_exception
27
+ parsed_doc = REXML::Document.new(response)
28
+ expect(parsed_doc.elements['//version'].text).to eq(test_version)
29
+ expect(parsed_doc.elements['//repositoryPath'].text).to eq("/org/kill-bill/billing/#{test_version}/killbill-#{test_version}.pom")
30
+ expect(parsed_doc.elements['//snapshot'].text).to eq('false')
31
+ }
32
+
33
+ it {
34
+ response = nil
35
+ destination = Dir.mktmpdir('artifact')
36
+ expect { response = nexus_remote.pull_artifact(coordinates, destination) }.not_to raise_exception
37
+ destination = File.join(File.expand_path(destination), response[:file_name])
38
+ parsed_pom = REXML::Document.new(File.read(destination))
39
+ expect(parsed_pom.elements['//groupId'].text).to eq('org.kill-bill.billing')
40
+ expect(parsed_pom.elements['//artifactId'].text).to eq('killbill-oss-parent')
41
+ expect(parsed_pom.elements['//version'].text).to eq('0.146.63')
42
+ }
43
+ end
44
+
45
+ context 'when getting artifact info' do
46
+ it 'returns artifact info in XML format' do
47
+ response = nil
48
+ expect { response = nexus_remote.get_artifact_info(coordinates) }.not_to raise_exception
49
+ parsed_doc = REXML::Document.new(response)
50
+ expect(parsed_doc.elements['//groupId'].text).to eq('org.kill-bill.billing')
51
+ expect(parsed_doc.elements['//artifactId'].text).to eq('killbill')
52
+ expect(parsed_doc.elements['//version'].text).to eq(test_version)
53
+ end
54
+ end
55
+
56
+ context 'when searching for release artifact' do
57
+ it 'searches for artifacts and returns XML format' do
58
+ response = nil
59
+ expect do
60
+ response = nexus_remote.search_for_artifacts(coordinates)
61
+ end.not_to raise_exception
62
+
63
+ parsed_doc = REXML::Document.new(response)
64
+ expect(parsed_doc.elements['//groupId'].text).to eq('org.kill-bill.billing')
65
+ expect(parsed_doc.elements['//artifactId'].text).to eq('killbill')
66
+ expect(parsed_doc.elements['//version'].text).to eq(test_version)
67
+ end
68
+ end
69
+ end
data/tasks/package.rake CHANGED
@@ -156,7 +156,7 @@ def gem_exists?
156
156
  response = `gem specification 'kpm' -r -v #{VERSION} 2>&1`
157
157
  return false if response.nil?
158
158
 
159
- specification = YAML.load(response)
159
+ specification = YAML.safe_load(response, permitted_classes: [Gem::Dependency, Gem::Requirement, Gem::Specification, Gem::Version, Symbol, Time])
160
160
  specification.instance_of?(Gem::Specification)
161
161
  end
162
162
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kpm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.2
4
+ version: 0.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kill Bill core team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-26 00:00:00.000000000 Z
11
+ date: 2025-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -176,6 +176,7 @@ files:
176
176
  - lib/kpm/nexus_helper/actions.rb
177
177
  - lib/kpm/nexus_helper/cloudsmith_api_calls.rb
178
178
  - lib/kpm/nexus_helper/github_api_calls.rb
179
+ - lib/kpm/nexus_helper/maven_central_api_calls.rb
179
180
  - lib/kpm/nexus_helper/nexus_api_calls_v2.rb
180
181
  - lib/kpm/nexus_helper/nexus_facade.rb
181
182
  - lib/kpm/plugins_directory.rb
@@ -207,6 +208,7 @@ files:
207
208
  - spec/kpm/remote/kaui_artifact_spec.rb
208
209
  - spec/kpm/remote/killbill_plugin_artifact_spec.rb
209
210
  - spec/kpm/remote/killbill_server_artifact_spec.rb
211
+ - spec/kpm/remote/maven_central_api_calls_spec.rb
210
212
  - spec/kpm/remote/migrations_spec.rb
211
213
  - spec/kpm/remote/nexus_facade_spec.rb
212
214
  - spec/kpm/remote/tenant_config_spec.rb