artifactory 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -16
  3. data/.travis.yml +8 -0
  4. data/CHANGELOG.md +9 -0
  5. data/Gemfile +6 -2
  6. data/LICENSE +202 -0
  7. data/README.md +216 -17
  8. data/Rakefile +6 -1
  9. data/artifactory.gemspec +15 -10
  10. data/lib/artifactory.rb +74 -2
  11. data/lib/artifactory/client.rb +222 -0
  12. data/lib/artifactory/collections/artifact.rb +12 -0
  13. data/lib/artifactory/collections/base.rb +49 -0
  14. data/lib/artifactory/configurable.rb +73 -0
  15. data/lib/artifactory/defaults.rb +67 -0
  16. data/lib/artifactory/errors.rb +22 -0
  17. data/lib/artifactory/resources/artifact.rb +481 -0
  18. data/lib/artifactory/resources/base.rb +145 -0
  19. data/lib/artifactory/resources/build.rb +27 -0
  20. data/lib/artifactory/resources/plugin.rb +22 -0
  21. data/lib/artifactory/resources/repository.rb +251 -0
  22. data/lib/artifactory/resources/system.rb +114 -0
  23. data/lib/artifactory/resources/user.rb +57 -0
  24. data/lib/artifactory/util.rb +93 -0
  25. data/lib/artifactory/version.rb +1 -1
  26. data/locales/en.yml +27 -0
  27. data/spec/integration/resources/artifact_spec.rb +73 -0
  28. data/spec/integration/resources/build_spec.rb +11 -0
  29. data/spec/integration/resources/repository_spec.rb +13 -0
  30. data/spec/integration/resources/system_spec.rb +59 -0
  31. data/spec/spec_helper.rb +40 -0
  32. data/spec/support/api_server.rb +41 -0
  33. data/spec/support/api_server/artifact_endpoints.rb +122 -0
  34. data/spec/support/api_server/build_endpoints.rb +22 -0
  35. data/spec/support/api_server/repository_endpoints.rb +75 -0
  36. data/spec/support/api_server/status_endpoints.rb +11 -0
  37. data/spec/support/api_server/system_endpoints.rb +44 -0
  38. data/spec/unit/artifactory_spec.rb +73 -0
  39. data/spec/unit/client_spec.rb +176 -0
  40. data/spec/unit/resources/artifact_spec.rb +377 -0
  41. data/spec/unit/resources/base_spec.rb +140 -0
  42. data/spec/unit/resources/build_spec.rb +34 -0
  43. data/spec/unit/resources/plugin_spec.rb +25 -0
  44. data/spec/unit/resources/repository_spec.rb +180 -0
  45. data/spec/unit/resources/system_spec.rb +88 -0
  46. metadata +106 -36
  47. data/LICENSE.txt +0 -22
@@ -0,0 +1,145 @@
1
+ module Artifactory
2
+ class Resource::Base
3
+ class << self
4
+ #
5
+ # @macro attribute
6
+ # @method $1
7
+ # Return this object's +$1+
8
+ #
9
+ # @return [Object]
10
+ #
11
+ #
12
+ # @method $1=(value)
13
+ # Set this object's +$1+
14
+ #
15
+ # @param [Object] value
16
+ # the value to set for +$1+
17
+ # @param [Object] default
18
+ # the default value for this attribute
19
+ #
20
+ # @method $1?
21
+ # Determines if the +$1+ value exists and is truthy
22
+ #
23
+ # @return [Boolean]
24
+ #
25
+ def attribute(key, default = nil)
26
+ define_method(key) do
27
+ value = instance_variable_get("@#{key}")
28
+ return value unless value.nil?
29
+
30
+ if default.nil?
31
+ value
32
+ elsif default.is_a?(Proc)
33
+ default.call
34
+ else
35
+ default
36
+ end
37
+ end
38
+
39
+ define_method("#{key}?") do
40
+ !!send(key)
41
+ end
42
+
43
+ define_method("#{key}=") do |value|
44
+ instance_variable_set("@#{key}", value)
45
+ end
46
+ end
47
+
48
+ #
49
+ # Get the client (connection) object from the given options. If the
50
+ # +:client+ key is preset in the hash, it is assumed to contain the
51
+ # connection object to use for the request. If the +:client+ key is not
52
+ # present, the default {Artifactory.client} is used.
53
+ #
54
+ # Warning, the value of {Artifactory.client} is **not** threadsafe! If
55
+ # multiple threads or processes are modifying the connection information,
56
+ # the same request _could_ use a different client object. If you use the
57
+ # {Artifactory::Client} proxy methods, this is handled for you.
58
+ #
59
+ # Warning, this method will **remove** the +:client+ key from the hash if
60
+ # it exists.
61
+ #
62
+ # @param [Hash] options
63
+ # the list of options passed to the method
64
+ #
65
+ # @option options [Artifactory::Client] :client
66
+ # the client object to use for requests
67
+ #
68
+ def extract_client!(options)
69
+ options.delete(:client) || Artifactory.client
70
+ end
71
+
72
+ #
73
+ # Format the repos list from the given options. This method will modify
74
+ # the given Hash parameter!
75
+ #
76
+ # Warning, this method will modify the given hash if it exists.
77
+ #
78
+ # @param [Hash] options
79
+ # the list of options to extract the repos from
80
+ #
81
+ def format_repos!(options)
82
+ return options if options[:repos].nil? || options[:repos].empty?
83
+ options[:repos] = Array(options[:repos]).compact.join(',')
84
+ options
85
+ end
86
+
87
+ #
88
+ # Generate a URL-safe string from the given value.
89
+ #
90
+ # @param [#to_s] value
91
+ # the value to sanitize
92
+ #
93
+ # @return [String]
94
+ # the URL-safe version of the string
95
+ #
96
+ def url_safe(value)
97
+ URI.escape(value.to_s)
98
+ end
99
+ end
100
+
101
+ attribute :client, ->{ Artifactory.client }
102
+
103
+ #
104
+ # Create a new instance
105
+ #
106
+ def initialize(attributes = {})
107
+ attributes.each { |key, value| send(:"#{key}=", value) }
108
+ end
109
+
110
+ # @see {Resource::Base.extract_client!}
111
+ def extract_client!(options)
112
+ self.class.extract_client!(options)
113
+ end
114
+
115
+ # @see {Resource::Base.format_repos!}
116
+ def format_repos!(options)
117
+ self.class.format_repos!(options)
118
+ end
119
+
120
+ # @see {Resource::Base.url_safe}
121
+ def url_safe(value)
122
+ self.class.url_safe(value)
123
+ end
124
+
125
+ def to_s
126
+ "#<#{self.class.name}>"
127
+ end
128
+
129
+ def inspect
130
+ map = instance_variables.map do |k|
131
+ unless k == :@client
132
+ value = instance_variable_get(k)
133
+
134
+ if !value.nil?
135
+ "#{k.to_s.gsub(/^@/, '')}: #{value.inspect}"
136
+ else
137
+ nil
138
+ end
139
+ end
140
+ end.compact
141
+
142
+ "#<#{self.class.name} #{map.join(', ')}>"
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,27 @@
1
+ module Artifactory
2
+ class Resource::Build < Resource::Base
3
+ class << self
4
+ #
5
+ # Search for all builds in the system.
6
+ #
7
+ # @param [Hash] options
8
+ # the list of options
9
+ #
10
+ # @option options [Artifactory::Client] :client
11
+ # the client object to make the request with
12
+ #
13
+ # @return [Array<Resource::Build>]
14
+ # the list of builds
15
+ #
16
+ def all(options = {})
17
+ client = extract_client!(options)
18
+ client.get('/api/build')
19
+ rescue Error::NotFound
20
+ # Artifactory returns a 404 instead of an empty list when there are no
21
+ # builds. Whoever decided that was a good idea clearly doesn't
22
+ # understand the point of REST interfaces...
23
+ []
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ module Artifactory
2
+ class Resource::Plugin < Resource::Base
3
+ class << self
4
+ #
5
+ # Get a list of all plugins in the system.
6
+ #
7
+ # @param [Hash] options
8
+ # the list of options
9
+ #
10
+ # @option options [Artifactory::Client] :client
11
+ # the client object to make the request with
12
+ #
13
+ # @return [Array<Resource::Plugin>]
14
+ # the list of builds
15
+ #
16
+ def all(options = {})
17
+ client = extract_client!(options)
18
+ client.get('/api/plugins')
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,251 @@
1
+ module Artifactory
2
+ class Resource::Repository < Resource::Base
3
+ class << self
4
+ #
5
+ # Get a list of all repositories in the system.
6
+ #
7
+ # @param [Hash] options
8
+ # the list of options
9
+ #
10
+ # @option options [Artifactory::Client] :client
11
+ # the client object to make the request with
12
+ #
13
+ # @return [Array<Resource::Repository>]
14
+ # the list of builds
15
+ #
16
+ def all(options = {})
17
+ client = extract_client!(options)
18
+ client.get('/api/repositories').map do |hash|
19
+ find(name: hash['key'], client: client)
20
+ end.compact
21
+ end
22
+
23
+ #
24
+ # Find (fetch) a repository by name.
25
+ #
26
+ # @example Find a respository by named key
27
+ # Repository.find(name: 'libs-release-local') #=> #<Resource::Artifact>
28
+ #
29
+ # @param [Hash] options
30
+ # the list of options
31
+ #
32
+ # @option options [String] :name
33
+ # the name of the repository to find
34
+ # @option options [Artifactory::Client] :client
35
+ # the client object to make the request with
36
+ #
37
+ # @return [Resource::Repository, nil]
38
+ # an instance of the repository that matches the given name, or +nil+
39
+ # if one does not exist
40
+ #
41
+ def find(options = {})
42
+ client = extract_client!(options)
43
+ name = options[:name]
44
+
45
+ result = client.get("/api/repositories/#{url_safe(name)}")
46
+ from_hash(result, client: client)
47
+ rescue Error::NotFound
48
+ nil
49
+ end
50
+
51
+ #
52
+ # Create a instance from the given Hash. This method extracts the "safe"
53
+ # information from the hash and adds them to the instance.
54
+ #
55
+ # @example Create a new resource from a hash
56
+ # Repository.from_hash('downloadUri' => '...', 'size' => '...')
57
+ #
58
+ # @param [Artifactory::Client] client
59
+ # the client object to make the request with
60
+ # @param [Hash] hash
61
+ # the hash to create the instance from
62
+ #
63
+ # @return [Resource::Repository]
64
+ #
65
+ def from_hash(hash, options = {})
66
+ client = extract_client!(options)
67
+
68
+ new.tap do |instance|
69
+ instance.blacked_out = hash['blackedOut']
70
+ instance.description = hash['description']
71
+ instance.checksum_policy = hash['checksumPolicyType']
72
+ instance.excludes_pattern = hash['excludesPattern']
73
+ instance.handle_releases = hash['handleReleases']
74
+ instance.handle_snapshots = hash['handleSnapshots']
75
+ instance.includes_pattern = hash['includesPattern']
76
+ instance.key = hash['key']
77
+ instance.maximum_unique_snapshots = hash['maxUniqueSnapshots']
78
+ instance.notes = hash['notes']
79
+ instance.property_sets = hash['propertySets']
80
+ instance.snapshot_version_behavior = hash['snapshotVersionBehavior']
81
+ instance.suppress_pom_checks = hash['suppressPomConsistencyChecks']
82
+ instance.type = hash['rclass']
83
+ end
84
+ end
85
+ end
86
+
87
+ attribute :blacked_out, false
88
+ attribute :description
89
+ attribute :checksum_policy, 'client-checksums'
90
+ attribute :excludes_pattern, ''
91
+ attribute :handle_releases, true
92
+ attribute :handle_snapshots, true
93
+ attribute :includes_pattern, '**/*'
94
+ attribute :key, ->{ raise 'Key is missing!' }
95
+ attribute :maximum_unique_snapshots, 0
96
+ attribute :notes
97
+ attribute :property_sets, []
98
+ attribute :snapshot_version_behavior, 'non-unique'
99
+ attribute :suppress_pom_checks, false
100
+ attribute :type
101
+
102
+ #
103
+ # Upload an artifact into the repository. If the first parameter is a File
104
+ # object, that file descriptor is passed to the uploader. If the first
105
+ # parameter is a string, it is assumed to be the path to a local file on
106
+ # disk. This method will automatically construct the File object from the
107
+ # given path.
108
+ #
109
+ # @see bit.ly/1dhJRMO Artifactory Matrix Properties
110
+ #
111
+ # @example Upload an artifact from a File instance
112
+ # file = File.new('/local/path/to/file.deb')
113
+ # repo = Repository.new('libs-release-local')
114
+ # repo.upload(file, 'file.deb')
115
+ #
116
+ # @example Upload an artifact from a path
117
+ # repo = Repository.new('libs-release-local')
118
+ # repo.upload('/local/path/to/file.deb', 'file.deb')
119
+ #
120
+ # @example Upload an artifact with matrix properties
121
+ # repo = Repository.new('libs-release-local')
122
+ # repo.upload('/local/path/to/file.deb', 'file.deb', {
123
+ # status: 'DEV',
124
+ # rating: 5,
125
+ # branch: 'master'
126
+ # })
127
+ #
128
+ # @param [String, File] path_or_io
129
+ # the file or path to the file to upload
130
+ # @param [String] path
131
+ # the path where this resource will live in the remote artifactory
132
+ # repository, relative to the current repo
133
+ # @param [Hash] headers
134
+ # the list of headers to send with the request
135
+ # @param [Hash] properties
136
+ # a list of matrix properties
137
+ #
138
+ def upload(path_or_io, path, properties = {}, headers = {})
139
+ file = if path_or_io.is_a?(File)
140
+ path_or_io
141
+ else
142
+ File.new(File.expand_path(path_or_io))
143
+ end
144
+
145
+ matrix = to_matrix_properties(properties)
146
+ endpoint = File.join("#{url_safe_key}#{matrix}", path)
147
+
148
+ client.put(endpoint, { file: file }, headers)
149
+ end
150
+
151
+ #
152
+ # Upload an artifact with the given SHA checksum. Consult the artifactory
153
+ # documentation for the possible responses when the checksums fail to
154
+ # match.
155
+ #
156
+ # @see Repository#upload More syntax examples
157
+ #
158
+ # @example Upload an artifact with a checksum
159
+ # repo = Repository.new('libs-release-local')
160
+ # repo.upload_with_checksum('/local/file', '/remote/path', 'ABCD1234')
161
+ #
162
+ # @param (see Repository#upload)
163
+ # @param [String] checksum
164
+ # the SHA1 checksum of the artifact to upload
165
+ #
166
+ def upload_with_checksum(path_or_io, path, checksum, properties = {})
167
+ upload(path_or_io, path, properties,
168
+ 'X-Checksum-Deploy' => true,
169
+ 'X-Checksum-Sha1' => checksum,
170
+ )
171
+ end
172
+
173
+ #
174
+ # Upload an artifact with the given archive. Consult the artifactory
175
+ # documentation for the format of the archive to upload.
176
+ #
177
+ # @see Repository#upload More syntax examples
178
+ #
179
+ # @example Upload an artifact with a checksum
180
+ # repo = Repositor.new('libs-release-local')
181
+ # repo.upload_from_archive('/local/archive', '/remote/path')#
182
+ #
183
+ # @param (see Repository#upload)
184
+ #
185
+ def upload_from_archive(path_or_io, path, properties = {})
186
+ upload(path_or_io, path, properties,
187
+ 'X-Explode-Archive' => true,
188
+ )
189
+ end
190
+
191
+ #
192
+ # The list of artifacts in this repository on the remote artifactory
193
+ # server.
194
+ #
195
+ # @see Artifact.search Search syntax examples
196
+ #
197
+ # @example Get the list of artifacts for a repository
198
+ # repo = Repository.new('libs-release-local')
199
+ # repo.artifacts #=> [#<Resource::Artifacts>, ...]
200
+ #
201
+ # @return [Collection::Artifact]
202
+ # the list of artifacts
203
+ #
204
+ def artifacts
205
+ @artifacts ||= Collection::Artifact.new(self, repos: key) do
206
+ Resource::Artifact.search(name: '.*', repos: key)
207
+ end
208
+ end
209
+
210
+ #
211
+ #
212
+ #
213
+ def files
214
+ response = get("/api/storage/#{url_safe_key}", {
215
+ deep: 0,
216
+ listFolders: 0,
217
+ mdTimestamps: 0,
218
+ includeRootPath: 0,
219
+ })
220
+
221
+ response['children']
222
+ end
223
+
224
+ private
225
+
226
+ #
227
+ #
228
+ #
229
+ def url_safe_key
230
+ @url_safe_key ||= URI.escape(key.to_s)
231
+ end
232
+
233
+ #
234
+ # @todo Make this a mixin or reusable?
235
+ #
236
+ def to_matrix_properties(hash = {})
237
+ properties = hash.map do |k, v|
238
+ key = URI.escape(k.to_s)
239
+ value = URI.escape(v.to_s)
240
+
241
+ "#{key}=#{value}"
242
+ end
243
+
244
+ if properties.empty?
245
+ nil
246
+ else
247
+ ";#{properties.join(';')}"
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,114 @@
1
+ module Artifactory
2
+ class Resource::System < Resource::Base
3
+ class << self
4
+ #
5
+ # Get general system information.
6
+ #
7
+ # @example Get the system information
8
+ # System.info #=> "..."
9
+ #
10
+ # @param [Hash] options
11
+ # the list of options
12
+ #
13
+ # @option options [Artifactory::Client] :client
14
+ # the client object to make the request with
15
+ #
16
+ # @return [String]
17
+ # a "table" of the system information as returned by the API
18
+ #
19
+ def info(options = {})
20
+ client = extract_client!(options)
21
+ client.get('/api/system')
22
+ end
23
+
24
+ #
25
+ # Check the status of the Artifactory server and API. This method will
26
+ # always return a boolean response, so it's safe to call without
27
+ # exception handling.
28
+ #
29
+ # @example Wait until the Artifactory server is ready
30
+ # until System.ping
31
+ # sleep(0.5)
32
+ # print '.'
33
+ # end
34
+ #
35
+ # @param [Hash] options
36
+ # the list of options
37
+ #
38
+ # @option options [Artifactory::Client] :client
39
+ # the client object to make the request with
40
+ #
41
+ # @return [Boolean]
42
+ # true if the Artifactory server is ready, false otherwise
43
+ #
44
+ def ping(options = {})
45
+ client = extract_client!(options)
46
+ !!client.get('/api/system/ping')
47
+ rescue Error::ConnectionError
48
+ false
49
+ end
50
+
51
+ #
52
+ # Get the current system configuration as XML.
53
+ #
54
+ # @example Get the current configuration
55
+ # System.configuration
56
+ #
57
+ # @param [Hash] options
58
+ # the list of options
59
+ #
60
+ # @option options [Artifactory::Client] :client
61
+ # the client object to make the request with
62
+ #
63
+ # @return [REXML::Document]
64
+ # the parsed XML document
65
+ #
66
+ def configuration(options = {})
67
+ client = extract_client!(options)
68
+ response = client.get('/api/system/configuration')
69
+
70
+ REXML::Document.new(response)
71
+ end
72
+
73
+ #
74
+ # Update the configuration with the given XML.
75
+ #
76
+ # @example Update the configuration
77
+ # new_config = File.new('/path/to/new.xml')
78
+ # System.update_configuration(new_config)
79
+ #
80
+ # @param [Hash] options
81
+ # the list of options
82
+ # @param [File] xml
83
+ # a pointer to the file descriptor of the XML to upload
84
+ #
85
+ # @option options [Artifactory::Client] :client
86
+ # the client object to make the request with
87
+ #
88
+ def update_configuration(xml, options = {})
89
+ client = extract_client!(options)
90
+ client.post('/api/system/configuration', xml)
91
+ end
92
+
93
+ #
94
+ # Get the version information from the server.
95
+ #
96
+ # @example Get the version information
97
+ # System.version #=> { ... }
98
+ #
99
+ # @param [Hash] options
100
+ # the list of options
101
+ #
102
+ # @option options [Artifactory::Client] :client
103
+ # the client object to make the request with
104
+ #
105
+ # @return [Hash]
106
+ # the parsed JSON from the response
107
+ #
108
+ def version(options = {})
109
+ client = extract_client!(options)
110
+ client.get('/api/system/version')
111
+ end
112
+ end
113
+ end
114
+ end