nexus_api 1.0.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.
data/lib/nexus_api.rb ADDED
@@ -0,0 +1,372 @@
1
+ require 'nexus_api/version'
2
+ require 'nexus_api/docker_shell'
3
+ require 'nexus_api/docker_manager'
4
+ require 'nexus_api/nexus_connection'
5
+ require 'nexus_api/config_manager'
6
+ require 'json'
7
+ require 'pry'
8
+
9
+ # Nexus API docs:
10
+ # https://help.sonatype.com/repomanager3/rest-and-integration-api#app
11
+ # https://[NEXUS_URL]/#admin/system/api
12
+ module NexusAPI
13
+ class API
14
+ attr_accessor :connection
15
+ attr_accessor :docker
16
+ attr_accessor :team_config
17
+
18
+ TEAM_CONFIG = File.join(File.dirname(__dir__), 'team_configs/default.yaml').freeze
19
+
20
+ def initialize(username:, password:, hostname:, docker_pull_hostname: nil, docker_push_hostname: nil, team_config: nil)
21
+ @connection = NexusAPI::NexusConnection.new(
22
+ username: username,
23
+ password: password,
24
+ hostname: hostname,
25
+ )
26
+ if docker_pull_hostname.nil? || docker_push_hostname.nil?
27
+ @docker = nil
28
+ else
29
+ @docker = NexusAPI::DockerManager.new(
30
+ docker: NexusAPI::DockerShell.new,
31
+ options: {
32
+ 'username' => username,
33
+ 'password' => password,
34
+ 'pull_host' => docker_pull_hostname,
35
+ 'push_host' => docker_push_hostname,
36
+ }
37
+ )
38
+ end
39
+ team_config ||= TEAM_CONFIG
40
+ @team_config = NexusAPI::ConfigManager.new(config_path: team_config)
41
+ end
42
+
43
+
44
+ # ---ASSETS---
45
+ # GET /service/rest/v1/assets
46
+ def list_assets(repository: nil, paginate: false)
47
+ repository ||= @team_config.assets_repository
48
+ @connection.get_response(endpoint: "assets?repository=#{repository}", paginate: paginate)
49
+ end
50
+
51
+ # GET /service/rest/v1/assets/{id}
52
+ def list_asset(id:)
53
+ @connection.get_response(endpoint: "assets/#{id}")
54
+ end
55
+
56
+ # DELETE /service/rest/v1/assets/{id}
57
+
58
+
59
+ # ---BLOB STORE---
60
+ # GET /service/rest/v1/blobstores/{id}/quota-status
61
+
62
+
63
+ # ---COMPONENTS---
64
+ # GET /service/rest/v1/components
65
+ def list_components(repository: nil, paginate: false)
66
+ repository ||= @team_config.components_repository
67
+ @connection.get_response(endpoint: "components?repository=#{repository}", paginate: paginate)
68
+ end
69
+
70
+ # POST /service/rest/v1/components
71
+ def upload_maven_component(filename:, group_id:, artifact_id:, version:, repository: nil, tag: nil)
72
+ repository ||= @team_config.maven_repository
73
+ parameters = {
74
+ 'maven2.groupId' => group_id,
75
+ 'maven2.artifactId' => artifact_id,
76
+ 'maven2.version' => version,
77
+ 'maven2.asset1' => File.open(filename, 'r'),
78
+ 'maven2.asset1.extension' => filename.split('.').last,
79
+ }
80
+ parameters['maven2.tag'] = tag if tag
81
+ @connection.post(endpoint: "components?repository=#{repository}", parameters: parameters, headers: {})
82
+ end
83
+
84
+ def upload_npm_component(filename:, repository: nil, tag: nil)
85
+ repository ||= @team_config.npm_repository
86
+ parameters = {
87
+ 'npm.asset' => File.open(filename, 'r'),
88
+ }
89
+ parameters['npm.tag'] = tag if tag
90
+ @connection.post(endpoint: "components?repository=#{repository}", parameters: parameters, headers: {})
91
+ end
92
+
93
+ def upload_pypi_component(filename:, repository: nil, tag: nil)
94
+ repository ||= @team_config.pypi_repository
95
+ parameters = {
96
+ 'pypi.asset' => File.open(filename, 'r'),
97
+ }
98
+ parameters['pypi.tag'] = tag if tag
99
+ @connection.post(endpoint: "components?repository=#{repository}", parameters: parameters, headers: {})
100
+ end
101
+
102
+ def upload_raw_component(filename:, directory:, repository: nil, tag: nil)
103
+ repository ||= @team_config.raw_repository
104
+ parameters = {
105
+ 'raw.directory' => directory,
106
+ 'raw.asset1' => File.open(filename, 'r'),
107
+ 'raw.asset1.filename' => filename.split('/').last,
108
+ }
109
+ parameters['raw.tag'] = tag if tag
110
+ @connection.post(endpoint: "components?repository=#{repository}", parameters: parameters, headers: {})
111
+ end
112
+
113
+ def upload_rubygems_component(filename:, repository: nil, tag: nil)
114
+ repository ||= @team_config.rubygems_repository
115
+ parameters = {
116
+ 'rubygems.asset' => File.open(filename, 'r'),
117
+ }
118
+ parameters['rubygems.tag'] = tag if tag
119
+ @connection.post(endpoint: "components?repository=#{repository}", parameters: parameters, headers: {})
120
+ end
121
+
122
+ def upload_yum_component(filename:, directory:, repository: nil, tag: nil)
123
+ repository ||= @team_config.yum_repository
124
+ parameters = {
125
+ 'yum.directory' => directory,
126
+ 'yum.asset' => File.open(filename, 'r'),
127
+ 'yum.asset.filename' => filename.split('/').last,
128
+ }
129
+ parameters['yum.tag'] = tag if tag
130
+ @connection.post(endpoint: "components?repository=#{repository}", parameters: parameters, headers: {})
131
+ end
132
+
133
+ # GET /service/rest/v1/components/{id}
134
+ def list_component(id:)
135
+ @connection.get_response(endpoint: "components/#{id}")
136
+ end
137
+
138
+ # DELETE /service/rest/v1/components/{id}
139
+
140
+
141
+ # ---FORMATS---
142
+ # GET /service/rest/v1/formats/{format}/upload-specs
143
+ # GET /service/rest/v1/formats/upload-specs
144
+
145
+
146
+ # ---LIFECYCLE---
147
+ # PUT /service/rest/v1/lifecycle/bounce
148
+ # GET /service/rest/v1/lifecycle/phase
149
+ # PUT /service/rest/v1/lifecycle/phase
150
+
151
+
152
+ # ---MAINTENANCE---
153
+ # PUT /service/rest/v1/maintenance/{databaseName}/check
154
+ # PUT /service/rest/v1/maintenance/{databaseName}/reinstall
155
+ # PUT /service/rest/v1/maintenance/{databaseName}/repair
156
+ # GET /service/rest/v1/maintenance/{databaseName}/role
157
+ # PUT /service/rest/v1/maintenance/{databaseName}/role
158
+ # GET /service/rest/v1/maintenance/{databaseName}/status
159
+ # PUT /service/rest/v1/maintenance/{databaseName}/status
160
+
161
+
162
+ # ---NODES---
163
+ # GET /service/rest/v1/nodes
164
+ # PUT /service/rest/v1/nodes
165
+ # GET /service/rest/v1/nodes/supportzipdownload
166
+ # POST /service/rest/v1/nodes/supportzips
167
+
168
+
169
+ # ---READ-ONLY---
170
+ # GET /service/rest/v1/read-only
171
+ # POST /service/rest/v1/read-only/force-release
172
+ # POST /service/rest/v1/read-only/freeze
173
+ # POST /service/rest/v1/read-only/release
174
+
175
+
176
+ # ---REPOSITORIES--- (This endpoint does not paginate)
177
+ # GET /service/rest/v1/repositories
178
+ def list_repositories
179
+ @connection.get_response(endpoint: 'repositories')
180
+ end
181
+
182
+ def list_repository_names
183
+ list_repositories.map { |repo| repo['name'] }
184
+ end
185
+
186
+
187
+ # ---ROUTING-RULES---
188
+ # GET /service/rest/v1/beta/routing-rules
189
+ # POST /service/rest/v1/beta/routing-rules
190
+ # GET /service/rest/v1/beta/routing-rules/{name}
191
+ # PUT /service/rest/v1/beta/routing-rules/{name}
192
+ # DELETE /service/rest/v1/beta/routing-rules/{name}
193
+
194
+
195
+ # ---SCRIPT---
196
+ # GET /service/rest/v1/script
197
+ def list_scripts
198
+ @connection.get_response(endpoint: "script")
199
+ end
200
+
201
+ # POST /service/rest/v1/script
202
+ def upload_script(filename:)
203
+ file = File.read(filename)
204
+ @connection.post(endpoint: "script", parameters: file)
205
+ end
206
+
207
+ # GET /service/rest/v1/script/{name}
208
+ # PUT /service/rest/v1/script/{name}
209
+ # DELETE /service/rest/v1/script/{name}
210
+ def delete_script(name:)
211
+ @connection.delete(endpoint: "script/#{name}")
212
+ end
213
+
214
+ # POST /service/rest/v1/script/{name}/run
215
+ def run_script(name:)
216
+ @connection.post(endpoint: "script/#{name}/run", headers: {'Content-Type' => 'text/plain'})
217
+ end
218
+
219
+
220
+ # ---SEARCH---
221
+ # GET /service/rest/v1/search
222
+
223
+ # GET /service/rest/v1/search/assets
224
+ def search_asset(name:, format: nil, repository: nil, sha1: nil, version: nil, paginate: false)
225
+ repository ||= @team_config.search_repository
226
+ endpoint = "search/assets?q=#{name}"
227
+ endpoint += "&format=#{format}" unless format.nil?
228
+ endpoint += "&repository=#{repository}" unless repository.nil?
229
+ endpoint += "&sha1=#{sha1}" unless sha1.nil?
230
+ endpoint += "&version=#{version}" unless version.nil?
231
+ @connection.get_response(endpoint: endpoint, paginate: paginate)
232
+ end
233
+
234
+ # GET /service/rest/v1/search/assets/download
235
+
236
+
237
+ # ---SECURITY MANAGEMENT---
238
+ # GET /service/rest/v1/beta/security/user-sources
239
+
240
+
241
+ # ---SECURITY MANAGEMENT: USER TOKENS---
242
+ # DELETE /service/rest/v1/beta/security/user-tokens
243
+
244
+
245
+ # ---SECURITY MANAGEMENT: USERS---
246
+ # GET /service/rest/v1/beta/security/users
247
+ # POST /service/rest/v1/beta/security/users
248
+ # PUT /service/rest/v1/beta/security/users/{userId}
249
+ # DELETE /service/rest/v1/beta/security/users/{userId}
250
+ # PUT /service/rest/v1/beta/security/users/{userId}/change-password
251
+ # DELETE /service/rest/v1/beta/security/users/{userId}/user-token
252
+
253
+
254
+ # ---STAGING---
255
+ # POST /service/rest/v1/staging/delete
256
+ # POST /service/rest/v1/staging/move/{destination}
257
+
258
+
259
+ # ---STATUS---
260
+ # GET /service/rest/v1/status
261
+ def status
262
+ @connection.get(endpoint: 'status')
263
+ end
264
+
265
+ # GET /service/rest/v1/status/writable
266
+ def status_writable
267
+ @connection.get(endpoint: 'status/writable')
268
+ end
269
+
270
+
271
+ # ---SUPPORT---
272
+ # POST /service/rest/v1/support/supportzip
273
+
274
+
275
+ # ---TAGS---
276
+ # GET /service/rest/v1/tags
277
+ def list_tags(paginate: false)
278
+ @connection.get_response(endpoint: 'tags', paginate: paginate)
279
+ end
280
+
281
+ # POST /service/rest/v1/tags
282
+ def create_tag(name:)
283
+ parameters = JSON.dump({
284
+ 'name' => name,
285
+ })
286
+ @connection.post(endpoint: 'tags', parameters: parameters)
287
+ end
288
+
289
+ # GET /service/rest/v1/tags/{name}
290
+ # PUT /service/rest/v1/tags/{name}
291
+ # DELETE /service/rest/v1/tags/{name}
292
+ def delete_tag(name:)
293
+ @connection.delete(endpoint: "tags/#{name}")
294
+ end
295
+
296
+ # POST /service/rest/v1/tags/associate/{tagName}
297
+ def associate_tag(name:, sha1:, repository: nil)
298
+ repository ||= @team_config.tag_repository
299
+ search_query =
300
+ "?"\
301
+ "wait=true&"\
302
+ "repository=#{repository}&"\
303
+ "sha1=#{sha1}"
304
+ @connection.post(endpoint: "tags/associate/#{name}" + search_query)
305
+ end
306
+
307
+ # DELETE /service/rest/v1/tags/associate/{tagName}
308
+ def delete_associated_tag(name:, sha1:, repository: nil)
309
+ repository ||= @team_config.tag_repository
310
+ search_query =
311
+ "?"\
312
+ "repository=#{repository}&"\
313
+ "sha1=#{sha1}"
314
+ @connection.delete(endpoint: "tags/associate/#{name}" + search_query)
315
+ end
316
+
317
+
318
+ # ---TASKS---
319
+ # GET /service/rest/v1/tasks
320
+ # GET /service/rest/v1/tasks/{id}
321
+ # POST /service/rest/v1/tasks/{id}/run
322
+ # POST /service/rest/v1/tasks/{id}/stop
323
+
324
+
325
+ # ---CUSTOM FUNCTIONS---
326
+ def get_asset_size(asset_url:)
327
+ @connection.content_length(asset_url: asset_url)
328
+ end
329
+
330
+ def docker_ready?
331
+ if @docker.nil?
332
+ raise 'Docker push and pull endpoints not initialized!'
333
+ return false
334
+ end
335
+ true
336
+ end
337
+
338
+ def download_docker_component(image:, tag:)
339
+ @docker.download(image_name: image, tag: tag) if docker_ready?
340
+ end
341
+
342
+ def upload_docker_component(image:, tag:)
343
+ @docker.upload(image_name: image, tag: tag) if docker_ready?
344
+ end
345
+
346
+ def local_docker_image_exists?(image:, tag:)
347
+ @docker.exists?(image_name: image, tag: tag) if docker_ready?
348
+ end
349
+
350
+ def delete_local_docker_image(image:, tag:)
351
+ @docker.delete(image_name: image, tag: tag) if docker_ready?
352
+ end
353
+
354
+ def download(id:, name: nil)
355
+ asset = list_asset(id: id)
356
+ return false if asset == '' || asset.empty?
357
+ return false if asset["downloadUrl"].nil?
358
+ url = asset["downloadUrl"]
359
+ response = @connection.download(url: url)
360
+ if name
361
+ File.write(name, response.body)
362
+ else
363
+ File.write(url.split('/').last, response.body)
364
+ end
365
+ true
366
+ end
367
+
368
+ def paginate?
369
+ @connection.paginate?
370
+ end
371
+ end
372
+ end
data/nexus_api.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "nexus_api/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'nexus_api'
7
+ spec.version = NexusAPI::VERSION
8
+ spec.date = %q{2019-12-04}
9
+ spec.authors = ['Francis Levesque', 'Gavin Miller']
10
+ spec.email = ['francis.d.levesque@gmail.com', 'me@gavinmiller.io']
11
+
12
+ spec.summary = %q{nexus_api: provides access to Nexus through ruby!}
13
+ spec.homepage = "https://github.com/Cisco-AMP/nexus_api"
14
+ spec.license = 'MIT'
15
+
16
+ # Specify which files should be added to the gem when it is released.
17
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ end
21
+ spec.bindir = 'bin'
22
+ spec.executables = ['nexus_api']
23
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
+ spec.require_paths = ['lib']
25
+
26
+ spec.add_runtime_dependency 'bundler', "~> 2"
27
+ spec.add_runtime_dependency 'docker-api', "~> 1.34.2"
28
+ spec.add_runtime_dependency 'dotenv', "~> 2.7.5"
29
+ spec.add_runtime_dependency 'pry', "~> 0.12.2"
30
+ spec.add_runtime_dependency 'rake', "~> 10.0"
31
+ spec.add_runtime_dependency 'rest-client', "~> 2.1.0"
32
+ spec.add_runtime_dependency 'thor', "~> 0.20.3"
33
+
34
+ spec.add_development_dependency 'rspec', "~> 3.0"
35
+ end
@@ -0,0 +1,6 @@
1
+ # Default set of repos for nexus_api to use
2
+
3
+ # Default repository for Nexus actions
4
+
5
+
6
+ # Default repository for file uploads
@@ -0,0 +1,18 @@
1
+ # Default set of repos for nexus_api to use
2
+ #
3
+ # NOTE: If you don't want to set a default for a certain
4
+ # case simply remove it from your config
5
+
6
+ # Default repository for Nexus actions
7
+ assets: default_assets_repo
8
+ components: default_components_repo
9
+ search: default_search_repo
10
+ tag: default_tag_repo
11
+
12
+ # Default repository for file uploads
13
+ maven: default_maven_repo
14
+ npm: default_npm_repo
15
+ pypi: default_pypi_repo
16
+ raw: default_raw_repo
17
+ rubygems: default_rubygems_repo
18
+ yum: default_yum_repo
metadata ADDED
@@ -0,0 +1,189 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nexus_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Francis Levesque
8
+ - Gavin Miller
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-12-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '2'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '2'
28
+ - !ruby/object:Gem::Dependency
29
+ name: docker-api
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 1.34.2
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 1.34.2
42
+ - !ruby/object:Gem::Dependency
43
+ name: dotenv
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 2.7.5
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 2.7.5
56
+ - !ruby/object:Gem::Dependency
57
+ name: pry
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 0.12.2
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.12.2
70
+ - !ruby/object:Gem::Dependency
71
+ name: rake
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '10.0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '10.0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rest-client
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: 2.1.0
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: 2.1.0
98
+ - !ruby/object:Gem::Dependency
99
+ name: thor
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: 0.20.3
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: 0.20.3
112
+ - !ruby/object:Gem::Dependency
113
+ name: rspec
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '3.0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '3.0'
126
+ description:
127
+ email:
128
+ - francis.d.levesque@gmail.com
129
+ - me@gavinmiller.io
130
+ executables:
131
+ - nexus_api
132
+ extensions: []
133
+ extra_rdoc_files: []
134
+ files:
135
+ - ".env.template"
136
+ - ".gitignore"
137
+ - ".rspec"
138
+ - ".ruby-version"
139
+ - CHANGELOG.md
140
+ - Gemfile
141
+ - Gemfile.lock
142
+ - LICENSE.txt
143
+ - README.md
144
+ - Rakefile
145
+ - bin/nexus_api
146
+ - bin/setup
147
+ - bin/test
148
+ - lib/nexus_api.rb
149
+ - lib/nexus_api/cli.rb
150
+ - lib/nexus_api/cli_commands/commands.rb
151
+ - lib/nexus_api/cli_commands/download.rb
152
+ - lib/nexus_api/cli_commands/list.rb
153
+ - lib/nexus_api/cli_commands/script.rb
154
+ - lib/nexus_api/cli_commands/search.rb
155
+ - lib/nexus_api/cli_commands/tag.rb
156
+ - lib/nexus_api/cli_commands/upload.rb
157
+ - lib/nexus_api/cli_utils.rb
158
+ - lib/nexus_api/config_manager.rb
159
+ - lib/nexus_api/docker_manager.rb
160
+ - lib/nexus_api/docker_shell.rb
161
+ - lib/nexus_api/nexus_connection.rb
162
+ - lib/nexus_api/version.rb
163
+ - nexus_api.gemspec
164
+ - team_configs/default.yaml
165
+ - team_configs/template.yaml
166
+ homepage: https://github.com/Cisco-AMP/nexus_api
167
+ licenses:
168
+ - MIT
169
+ metadata: {}
170
+ post_install_message:
171
+ rdoc_options: []
172
+ require_paths:
173
+ - lib
174
+ required_ruby_version: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ required_rubygems_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ requirements: []
185
+ rubygems_version: 3.0.3
186
+ signing_key:
187
+ specification_version: 4
188
+ summary: 'nexus_api: provides access to Nexus through ruby!'
189
+ test_files: []