nexus_cli_sb 4.0.2

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