nexus_cli_sb 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +4 -0
  4. data/CHANGELOG.md +14 -0
  5. data/Gemfile +42 -0
  6. data/Guardfile +27 -0
  7. data/LICENSE +15 -0
  8. data/README.md +121 -0
  9. data/Rakefile +1 -0
  10. data/Thorfile +66 -0
  11. data/VERSION +1 -0
  12. data/bin/nexus-cli +10 -0
  13. data/data/pom.xml.erb +13 -0
  14. data/features/nexus_oss.feature +251 -0
  15. data/features/pro/nexus_custom_metadata.feature +116 -0
  16. data/features/pro/nexus_pro.feature +101 -0
  17. data/features/step_definitions/cli_steps.rb +105 -0
  18. data/features/support/env.rb +64 -0
  19. data/lib/nexus_cli/artifact.rb +44 -0
  20. data/lib/nexus_cli/base_remote.rb +16 -0
  21. data/lib/nexus_cli/cli.rb +7 -0
  22. data/lib/nexus_cli/configuration.rb +102 -0
  23. data/lib/nexus_cli/connection.rb +84 -0
  24. data/lib/nexus_cli/errors.rb +259 -0
  25. data/lib/nexus_cli/mixins/artifact_actions.rb +194 -0
  26. data/lib/nexus_cli/mixins/global_settings_actions.rb +64 -0
  27. data/lib/nexus_cli/mixins/logging_actions.rb +45 -0
  28. data/lib/nexus_cli/mixins/pro/custom_metadata_actions.rb +176 -0
  29. data/lib/nexus_cli/mixins/pro/smart_proxy_actions.rb +219 -0
  30. data/lib/nexus_cli/mixins/repository_actions.rb +245 -0
  31. data/lib/nexus_cli/mixins/user_actions.rb +125 -0
  32. data/lib/nexus_cli/n3_metadata.rb +77 -0
  33. data/lib/nexus_cli/remote/oss_remote.rb +11 -0
  34. data/lib/nexus_cli/remote/pro_remote.rb +59 -0
  35. data/lib/nexus_cli/remote_factory.rb +30 -0
  36. data/lib/nexus_cli/tasks.rb +496 -0
  37. data/lib/nexus_cli/version.rb +6 -0
  38. data/lib/nexus_cli.rb +44 -0
  39. data/nexus_cli.gemspec +31 -0
  40. data/spec/fixtures/metadata_search.xml +10 -0
  41. data/spec/fixtures/nexus.config +4 -0
  42. data/spec/spec_helper.rb +22 -0
  43. data/spec/unit/nexus_cli/artifact_spec.rb +82 -0
  44. data/spec/unit/nexus_cli/configuration_spec.rb +137 -0
  45. data/spec/unit/nexus_cli/mixins/pro/custom_metadata_actions_spec.rb +21 -0
  46. data/spec/unit/nexus_cli/oss_remote_spec.rb +78 -0
  47. data/spec/unit/nexus_cli/pro_remote_spec.rb +110 -0
  48. data/spec/unit/nexus_cli/remote_factory_spec.rb +42 -0
  49. metadata +259 -0
@@ -0,0 +1,259 @@
1
+ require 'json'
2
+
3
+ module NexusCli
4
+ class NexusCliError < StandardError
5
+ class << self
6
+ def status_code(code)
7
+ define_method(:status_code) { code }
8
+ end
9
+ end
10
+ end
11
+
12
+ class ArtifactMalformedException < NexusCliError
13
+ def message
14
+ "Please submit your request using 4 colon-separated values. `groupId:artifactId:version:extension`"
15
+ end
16
+ status_code(100)
17
+ end
18
+
19
+ class ArtifactNotFoundException < NexusCliError
20
+ def message
21
+ "The artifact you requested information for could not be found. Please ensure it exists inside the Nexus."
22
+ end
23
+ status_code(101)
24
+ end
25
+
26
+ class InvalidSettingsException < NexusCliError
27
+ def initialize(errors)
28
+ @errors = errors
29
+ end
30
+
31
+ def message
32
+ "Your configuration has an error: #{@errors}"
33
+ end
34
+ status_code(102)
35
+ end
36
+
37
+ class MissingSettingsFileException < NexusCliError
38
+ def message
39
+ "The .nexus_cli file is missing or corrupt. You can either fix the .nexus_cli file or pass the --overrides hash."
40
+ end
41
+ status_code(103)
42
+ end
43
+
44
+ class NonSecureConnectionException < NexusCliError
45
+ def message
46
+ "Your communication with a server using an SSL certificate failed during validation. You may want to try the --insecure option."
47
+ end
48
+ status_code(104)
49
+ end
50
+
51
+ class CouldNotConnectToNexusException < NexusCliError
52
+ def message
53
+ "Could not connect to Nexus. Please ensure the url you are using is reachable."
54
+ end
55
+ status_code(105)
56
+ end
57
+
58
+ class PermissionsException < NexusCliError
59
+ def message
60
+ "Your request was denied by the Nexus server due to a permissions error. You will need to administer the Nexus or use a different user/password in .nexus_cli."
61
+ end
62
+ status_code(106)
63
+ end
64
+
65
+ class BadUploadRequestException < NexusCliError
66
+ def message
67
+ %{Your request was denied by the Nexus server due to a bad request and your artifact has not been uploaded.
68
+ This could mean several things:
69
+ Your .nexus_cli['repository'] is invalid.
70
+ The artifact with this identifier already exists inside the repository and that repository does not allow multiple deployments.}
71
+ end
72
+ status_code(107)
73
+ end
74
+
75
+ class NotNexusProException < NexusCliError
76
+ def message
77
+ "You cannot use this feature unless you are using Nexus Professional."
78
+ end
79
+ status_code(108)
80
+ end
81
+
82
+ class SearchParameterMalformedException < NexusCliError
83
+ def message
84
+ "Submit your search request specifying one or more 3 colon-separated values: `key:type:value`. The available search types are `equal`, `matches`, `bounded`, and `notequal`."
85
+ end
86
+ status_code(109)
87
+ end
88
+
89
+ class BadSearchRequestException < NexusCliError
90
+ def message
91
+ "Your request was denied by the Nexus server due to a bad request. Check that your search parameters contain valid values."
92
+ end
93
+ status_code(110)
94
+ end
95
+
96
+ class BadSettingsException < NexusCliError
97
+ def initialize(body)
98
+ @server_response = JSON.pretty_generate(JSON.parse(body))
99
+ end
100
+
101
+ def message
102
+ %{Your global_settings.json file is malformed and could not be uploaded to Nexus.
103
+ The output from the server was:
104
+ #{@server_response}}
105
+ end
106
+ status_code(111)
107
+ end
108
+
109
+ class CreateRepsitoryException < NexusCliError
110
+ def initialize(body)
111
+ @server_response = JSON.pretty_generate(JSON.parse(body))
112
+ end
113
+
114
+ def message
115
+ %{Your create repository command failed due to the following:
116
+ #{@server_response}}
117
+ end
118
+ status_code(112)
119
+ end
120
+
121
+ class RepositoryDoesNotExistException < NexusCliError
122
+ def message
123
+ "The repository you are trying to delete does not exist."
124
+ end
125
+ status_code(113)
126
+ end
127
+
128
+ class RepositoryNotFoundException < NexusCliError
129
+ def message
130
+ "The repository you provided could not be found. Please ensure the repository exists."
131
+ end
132
+ status_code(114)
133
+ end
134
+
135
+ class UnexpectedStatusCodeException < NexusCliError
136
+ def initialize(code)
137
+ @code = code
138
+ end
139
+
140
+ def message
141
+ "The server responded with a #{@code} status code which is unexpected. Please submit a bug."
142
+ end
143
+ status_code(115)
144
+ end
145
+
146
+ class N3ParameterMalformedException < NexusCliError
147
+ def message
148
+ "Submit your tag request specifying one or more 2 colon-separated values: `key:value`. The key can only consist of alphanumeric characters."
149
+ end
150
+ status_code(116)
151
+ end
152
+
153
+ class CreateUserException < NexusCliError
154
+ def initialize(body)
155
+ @server_response = JSON.pretty_generate(JSON.parse(body))
156
+ end
157
+
158
+ def message
159
+ %{Your create user command failed due to the following:
160
+ #{@server_response}}
161
+ end
162
+ status_code(117)
163
+ end
164
+
165
+ class UserNotFoundException < NexusCliError
166
+ def initialize(id)
167
+ @id = id
168
+ end
169
+
170
+ def message
171
+ "A user with the ID of #{@id} could not be found. Please ensure it exists."
172
+ end
173
+ status_code(118)
174
+ end
175
+
176
+ class UpdateUserException < NexusCliError
177
+ def initialize(body)
178
+ @server_response = JSON.pretty_generate(JSON.parse(body))
179
+ end
180
+
181
+ def message
182
+ %{Your update user command failed due to the following:
183
+ #{@server_response}}
184
+ end
185
+ status_code(119)
186
+ end
187
+
188
+ class InvalidCredentialsException < NexusCliError
189
+ def message
190
+ "Invalid Credentials were supplied. Please make sure you are passing the correct values."
191
+ end
192
+ status_code(120)
193
+ end
194
+
195
+ class NotProxyRepositoryException < NexusCliError
196
+ def initialize(repository_id)
197
+ @repository_id = repository_id
198
+ end
199
+
200
+ def message
201
+ "The #{@repository_id} repository is not a Proxy repository and cannot subscribe to artifact updates."
202
+ end
203
+ status_code(121)
204
+ end
205
+
206
+ class LicenseInstallFailure < NexusCliError
207
+ def message
208
+ "Either your Nexus already has a license installed or there was a problem with the file you uploaded."
209
+ end
210
+ status_code(122)
211
+ end
212
+
213
+ class InvalidLoggingLevelException < NexusCliError
214
+ def message
215
+ "Logging level must be set to one of either INFO, DEBUG, or ERROR."
216
+ end
217
+ status_code(123)
218
+ end
219
+
220
+ class N3NotFoundException < NexusCliError
221
+ def message
222
+ "The artifact does not have any custom metadata added yet."
223
+ end
224
+ status_code(124)
225
+ end
226
+
227
+ class SSLException < NexusCliError
228
+ def message
229
+ "You are attempting to communicate securely with a server that has an untrusted certificate. Please ensure your certificate is correct or set ssl_verify to false."
230
+ end
231
+ status_code(125)
232
+ end
233
+
234
+ class RepositoryInGroupException < NexusCliError
235
+ def message
236
+ "You are attempting to add a repository that is already a part of this group."
237
+ end
238
+ status_code(126)
239
+ end
240
+
241
+ class RepositoryNotInGroupException < NexusCliError
242
+ def message
243
+ "You are attempting to remove a repository that isn't a part of the group."
244
+ end
245
+ status_code(127)
246
+ end
247
+
248
+ class NexusHTTP404 < NexusCliError
249
+ def initialize(body)
250
+ @server_response = body
251
+ end
252
+
253
+ def message
254
+ %{Your command failed and the server returned an error code. The output of the response was:
255
+ #{@server_response}}
256
+ end
257
+ status_code(128)
258
+ end
259
+ end
@@ -0,0 +1,194 @@
1
+ require 'erb'
2
+ require 'tempfile'
3
+
4
+ module NexusCli
5
+ # @author Kyle Allan <kallan@riotgames.com>
6
+ module ArtifactActions
7
+
8
+ # Retrieves a file from the Nexus server using the given [String]
9
+ # coordinates. Optionally provide a destination [String].
10
+ #
11
+ # @param [String] coordinates
12
+ # @param [String] destination
13
+ #
14
+ # @return [Hash] Some information about the artifact that was pulled.
15
+ def pull_artifact(coordinates, destination=nil)
16
+ artifact = Artifact.new(coordinates)
17
+
18
+ if artifact.version.casecmp("latest")
19
+ artifact.version = REXML::Document.new(get_artifact_info(coordinates)).elements["//version"].text
20
+ end
21
+
22
+ file_name = artifact.file_name
23
+ destination = File.join(File.expand_path(destination || "."), file_name)
24
+ query = {:g => artifact.group_id, :a => artifact.artifact_id, :e => artifact.extension, :v => artifact.version, :r => configuration['repository']}
25
+ query.merge!({:c => artifact.classifier}) unless artifact.classifier.nil?
26
+ response = nexus.get(nexus_url("service/local/artifact/maven/redirect"), :query => query)
27
+ case response.status
28
+ when 301, 307
29
+ # Follow redirect and stream in chunks.
30
+ artifact_file = File.open(destination, "wb") do |io|
31
+ nexus.get(response.content.gsub(/If you are not automatically redirected use this url: /, "")) do |chunk|
32
+ io.write(chunk)
33
+ end
34
+ end
35
+ when 404
36
+ raise ArtifactNotFoundException
37
+ else
38
+ raise UnexpectedStatusCodeException.new(response.status)
39
+ end
40
+ {
41
+ :file_name => file_name,
42
+ :file_path => File.expand_path(destination),
43
+ :version => artifact.version,
44
+ :size => File.size(File.expand_path(destination))
45
+ }
46
+ end
47
+
48
+ # Pushes the given [file] to the Nexus server
49
+ # under the given [artifact] identifier.
50
+ #
51
+ # @param coordinates [String] the Maven identifier
52
+ # @param file [type] the path to the file
53
+ #
54
+ # @return [Boolean] returns true when successful
55
+ def push_artifact(coordinates, file)
56
+ artifact = Artifact.new(coordinates)
57
+ put_string = "content/repositories/#{configuration['repository']}/#{artifact.group_id.gsub(".", "/")}/#{artifact.artifact_id.gsub(".", "/")}/#{artifact.version}/#{artifact.file_name}"
58
+ response = nexus.put(nexus_url(put_string), File.open(file))
59
+
60
+ case response.status
61
+ when 201
62
+ pom_name = "#{artifact.artifact_id}-#{artifact.version}.pom"
63
+ put_string = "content/repositories/#{configuration['repository']}/#{artifact.group_id.gsub(".", "/")}/#{artifact.artifact_id.gsub(".", "/")}/#{artifact.version}/#{pom_name}"
64
+ pom_file = generate_fake_pom(pom_name, artifact)
65
+ nexus.put(nexus_url(put_string), File.open(pom_file))
66
+ delete_string = "/service/local/metadata/repositories/#{configuration['repository']}/content/#{artifact.group_id.gsub(".", "/")}/#{artifact.artifact_id.gsub(".", "/")}"
67
+ nexus.delete(nexus_url(delete_string))
68
+ return true
69
+ when 400
70
+ raise BadUploadRequestException
71
+ when 401
72
+ raise PermissionsException
73
+ when 403
74
+ raise PermissionsException
75
+ when 404
76
+ raise NexusHTTP404.new(response.content)
77
+ else
78
+ raise UnexpectedStatusCodeException.new(response.status)
79
+ end
80
+ end
81
+
82
+ def delete_artifact(coordinates)
83
+ artifact = Artifact.new(coordinates)
84
+ response = nexus.delete(nexus_url("content/repositories/#{configuration['repository']}/#{artifact.group_id.gsub(".", "/")}/#{artifact.artifact_id.gsub(".", "/")}/#{artifact.version}"))
85
+ case response.status
86
+ when 204
87
+ return true
88
+ else
89
+ raise UnexpectedStatusCodeException.new(response.status)
90
+ end
91
+ end
92
+
93
+
94
+ # Retrieves information about the given [artifact] and returns
95
+ # it in as a [String] of XML.
96
+ #
97
+ # @param coordinates [String] the Maven identifier
98
+ #
99
+ # @return [String] A string of XML data about the desired artifact
100
+ def get_artifact_info(coordinates)
101
+ artifact = Artifact.new(coordinates)
102
+ query = {:g => artifact.group_id, :a => artifact.artifact_id, :e => artifact.extension, :v => artifact.version, :r => configuration['repository']}
103
+ query.merge!({:c => artifact.classifier}) unless artifact.classifier.nil?
104
+ response = nexus.get(nexus_url("service/local/artifact/maven/resolve"), query)
105
+ case response.status
106
+ when 200
107
+ return response.content
108
+ when 404
109
+ raise ArtifactNotFoundException
110
+ when 503
111
+ raise CouldNotConnectToNexusException
112
+ else
113
+ raise UnexpectedStatusCodeException.new(response.status)
114
+ end
115
+ end
116
+
117
+
118
+ # Searches for an artifact using the given identifier.
119
+ #
120
+ # @param coordinates [String] the Maven identifier
121
+ # @example com.artifact:my-artifact
122
+ #
123
+ # @return [Array<String>] a formatted Array of results
124
+ # @example
125
+ # 1.0.0 `nexus-cli pull com.artifact:my-artifact:tgz:1.0.0`
126
+ # 2.0.0 `nexus-cli pull com.artifact:my-artifact:tgz:2.0.0`
127
+ # 3.0.0 `nexus-cli pull com.artifact:my-artifact:tgz:3.0.0`
128
+ def search_for_artifacts(coordinates)
129
+ group_id, artifact_id = coordinates.split(":")
130
+ response = nexus.get(nexus_url("service/local/data_index"), :query => {:g => group_id, :a => artifact_id})
131
+ case response.status
132
+ when 200
133
+ return response.content
134
+ else
135
+ raise UnexpectedStatusCodeException.new(response.status)
136
+ end
137
+ end
138
+
139
+ def transfer_artifact(coordinates, from_repository, to_repository)
140
+ do_transfer_artifact(coordinates, from_repository, to_repository)
141
+ end
142
+
143
+ private
144
+
145
+ # Formats the given XML into an [Array<String>] so it
146
+ # can be displayed nicely.
147
+ #
148
+ # @param doc [REXML::Document] the xml search results
149
+ # @param group_id [String] the group id
150
+ # @param artifact_id [String] the artifact id
151
+ #
152
+ # @return [type] [description]
153
+ def format_search_results(doc, group_id, artifact_id)
154
+
155
+ versions = []
156
+ REXML::XPath.each(doc, "//version") { |matched_version| versions << matched_version.text }
157
+ if versions.length > 0
158
+ indent_size = versions.max{|a,b| a.length <=> b.length}.size+4
159
+ formated_results = ['Found Versions:']
160
+ versions.inject(formated_results) do |array,version|
161
+ temp_version = version + ":"
162
+ array << "#{temp_version.ljust(indent_size)} `nexus-cli pull #{group_id}:#{artifact_id}:#{version}:tgz`"
163
+ end
164
+ else
165
+ formated_results = ['No Versions Found.']
166
+ end
167
+ end
168
+
169
+ # Transfers an artifact from one repository
170
+ # to another. Sometimes called a `promotion`
171
+ #
172
+ # @param coordinates [String] a Maven identifier
173
+ # @param from_repository [String] the name of the from repository
174
+ # @param to_repository [String] the name of the to repository
175
+ #
176
+ # @return [Boolean] returns true when successful
177
+ def do_transfer_artifact(coordinates, from_repository, to_repository)
178
+ Dir.mktmpdir do |temp_dir|
179
+ configuration["repository"] = sanitize_for_id(from_repository)
180
+ artifact_file = pull_artifact(coordinates, temp_dir)
181
+ configuration["repository"] = sanitize_for_id(to_repository)
182
+ push_artifact(coordinates, artifact_file[:file_path])
183
+ end
184
+ end
185
+
186
+ def generate_fake_pom(pom_name, artifact)
187
+ Tempfile.open(pom_name) do |file|
188
+ template_path = File.join(NexusCli.root, "data", "pom.xml.erb")
189
+ file.puts ERB.new(File.read(template_path)).result(binding)
190
+ file
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,64 @@
1
+ require 'json'
2
+
3
+ module NexusCli
4
+ # @author Kyle Allan <kallan@riotgames.com>
5
+ module GlobalSettingsActions
6
+
7
+ # Retrieves the global settings of the Nexus server
8
+ #
9
+ # @return [File] a File with the global settings.
10
+ def get_global_settings
11
+ json = get_global_settings_json
12
+ pretty_json = JSON.pretty_generate(JSON.parse(json))
13
+ Dir.mkdir(File.expand_path("~/.nexus")) unless Dir.exists?(File.expand_path("~/.nexus"))
14
+ destination = File.join(File.expand_path("~/.nexus"), "global_settings.json")
15
+ artifact_file = File.open(destination, 'wb') do |file|
16
+ file.write(pretty_json)
17
+ end
18
+ end
19
+
20
+ def get_global_settings_json
21
+ response = nexus.get(nexus_url("service/local/global_settings/current"), :header => DEFAULT_ACCEPT_HEADER)
22
+ case response.status
23
+ when 200
24
+ return response.content
25
+ else
26
+ raise UnexpectedStatusCodeException.new(response.status)
27
+ end
28
+ end
29
+
30
+ def upload_global_settings(json=nil)
31
+ global_settings = nil
32
+ if json == nil
33
+ global_settings = File.read(File.join(File.expand_path("~/.nexus"), "global_settings.json"))
34
+ else
35
+ global_settings = json
36
+ end
37
+ response = nexus.put(nexus_url("service/local/global_settings/current"), :body => global_settings, :header => DEFAULT_CONTENT_TYPE_HEADER)
38
+ case response.status
39
+ when 204
40
+ return true
41
+ when 400
42
+ raise BadSettingsException.new(response.content)
43
+ end
44
+ end
45
+
46
+ def reset_global_settings
47
+ response = nexus.get(nexus_url("service/local/global_settings/default"), :header => DEFAULT_ACCEPT_HEADER)
48
+ case response.status
49
+ when 200
50
+ default_json = response.content
51
+ else
52
+ raise UnexpectedStatusCodeException.new(response.status)
53
+ end
54
+
55
+ response = nexus.put(nexus_url("service/local/global_settings/current"), :body => default_json, :header => DEFAULT_CONTENT_TYPE_HEADER)
56
+ case response.status
57
+ when 204
58
+ return true
59
+ else
60
+ raise UnexpectedStatusCodeException.new(response.status)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,45 @@
1
+ module NexusCli
2
+ # @author Kyle Allan <kallan@riotgames.com>
3
+ module LoggingActions
4
+
5
+ # Gets information about the current logging
6
+ # levels in Nexus.
7
+ #
8
+ #
9
+ # @return [String] a String of JSON representing the current logging levels of Nexus
10
+ def get_logging_info
11
+ response = nexus.get(nexus_url("service/local/log/config"), :header => DEFAULT_ACCEPT_HEADER)
12
+ case response.status
13
+ when 200
14
+ return response.content
15
+ else
16
+ raise UnexpectedStatusCodeException.new(response.status)
17
+ end
18
+ end
19
+
20
+
21
+ # Sets the logging level of Nexus to one of
22
+ # "INFO", "DEBUG", or "ERROR".
23
+ #
24
+ # @param level [String] the logging level to set
25
+ #
26
+ # @return [Boolean] true if the logging level has been set, false otherwise
27
+ def set_logger_level(level)
28
+ raise InvalidLoggingLevelException unless ["INFO", "DEBUG", "ERROR"].include?(level.upcase)
29
+ response = nexus.put(nexus_url("service/local/log/config"), :body => create_logger_level_json(level), :header => DEFAULT_CONTENT_TYPE_HEADER)
30
+ case response.status
31
+ when 200
32
+ return true
33
+ else
34
+ raise UnexpectedStatusCodeException.new(response.status)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def create_logger_level_json(level)
41
+ params = {:rootLoggerLevel => level.upcase}
42
+ JSON.dump(:data => params)
43
+ end
44
+ end
45
+ end