artifactory 1.1.0 → 1.2.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +11 -7
  3. data/CHANGELOG.md +18 -0
  4. data/README.md +25 -4
  5. data/Rakefile +5 -2
  6. data/artifactory.gemspec +0 -3
  7. data/lib/artifactory.rb +2 -4
  8. data/lib/artifactory/client.rb +210 -85
  9. data/lib/artifactory/configurable.rb +6 -1
  10. data/lib/artifactory/defaults.rb +52 -3
  11. data/lib/artifactory/errors.rb +21 -14
  12. data/lib/artifactory/resources/artifact.rb +132 -58
  13. data/lib/artifactory/resources/base.rb +120 -6
  14. data/lib/artifactory/resources/build.rb +2 -1
  15. data/lib/artifactory/resources/group.rb +5 -72
  16. data/lib/artifactory/resources/layout.rb +106 -0
  17. data/lib/artifactory/resources/repository.rb +62 -114
  18. data/lib/artifactory/resources/system.rb +5 -1
  19. data/lib/artifactory/resources/user.rb +5 -79
  20. data/lib/artifactory/util.rb +8 -2
  21. data/lib/artifactory/version.rb +1 -1
  22. data/spec/integration/resources/layout_spec.rb +22 -0
  23. data/spec/integration/resources/repository_spec.rb +7 -0
  24. data/spec/integration/resources/system_spec.rb +4 -4
  25. data/spec/support/api_server/repository_endpoints.rb +5 -0
  26. data/spec/support/api_server/system_endpoints.rb +18 -0
  27. data/spec/unit/client_spec.rb +17 -98
  28. data/spec/unit/resources/artifact_spec.rb +99 -13
  29. data/spec/unit/resources/build_spec.rb +1 -1
  30. data/spec/unit/resources/group_spec.rb +1 -1
  31. data/spec/unit/resources/layout_spec.rb +61 -0
  32. data/spec/unit/resources/repository_spec.rb +31 -47
  33. data/spec/unit/resources/system_spec.rb +4 -2
  34. data/spec/unit/resources/user_spec.rb +1 -1
  35. metadata +16 -40
  36. data/locales/en.yml +0 -27
@@ -16,10 +16,11 @@ module Artifactory
16
16
  def all(options = {})
17
17
  client = extract_client!(options)
18
18
  client.get('/api/build')
19
- rescue Error::NotFound
19
+ rescue Error::HTTPError => e
20
20
  # Artifactory returns a 404 instead of an empty list when there are no
21
21
  # builds. Whoever decided that was a good idea clearly doesn't
22
22
  # understand the point of REST interfaces...
23
+ raise unless e.code == 404
23
24
  []
24
25
  end
25
26
  end
@@ -43,54 +43,10 @@ module Artifactory
43
43
 
44
44
  response = client.get("/api/security/groups/#{url_safe(name)}")
45
45
  from_hash(response, client: client)
46
- rescue Error::NotFound
46
+ rescue Error::HTTPError => e
47
+ raise unless e.code == 404
47
48
  nil
48
49
  end
49
-
50
- #
51
- # Construct a group from the given URL.
52
- #
53
- # @example Create an group object from the given URL
54
- # Group.from_url('/security/groups/readers') #=> #<Resource::Group>
55
- #
56
- # @param [Artifactory::Client] client
57
- # the client object to make the request with
58
- # @param [String] url
59
- # the URL to find the group from
60
- #
61
- # @return [Resource::Group]
62
- #
63
- def from_url(url, options = {})
64
- client = extract_client!(options)
65
- from_hash(client.get(url), client: client)
66
- end
67
-
68
- #
69
- # Create a instance from the given Hash. This method extracts the "safe"
70
- # information from the hash and adds them to the instance.
71
- #
72
- # @example Create a new group from a hash
73
- # Group.from_hash('realmAttributes' => '...', 'name' => '...')
74
- #
75
- # @param [Artifactory::Client] client
76
- # the client object to make the request with
77
- # @param [Hash] hash
78
- # the hash to create the instance from
79
- #
80
- # @return [Resource::Group]
81
- #
82
- def from_hash(hash, options = {})
83
- client = extract_client!(options)
84
-
85
- new.tap do |instance|
86
- instance.auto_join = hash['autoJoin']
87
- instance.client = client
88
- instance.description = hash['description']
89
- instance.name = hash['name']
90
- instance.realm = hash['realm']
91
- instance.realm_attributes = hash['realmAttributes']
92
- end
93
- end
94
50
  end
95
51
 
96
52
  attribute :auto_join
@@ -107,8 +63,9 @@ module Artifactory
107
63
  # true if the object was deleted successfully, false otherwise
108
64
  #
109
65
  def delete
110
- !!client.delete(api_path)
111
- rescue Error::NotFound
66
+ client.delete(api_path)
67
+ true
68
+ rescue Error::HTTPError
112
69
  false
113
70
  end
114
71
 
@@ -122,30 +79,6 @@ module Artifactory
122
79
  true
123
80
  end
124
81
 
125
- #
126
- # The hash format for this group.
127
- #
128
- # @return [Hash]
129
- #
130
- def to_hash
131
- {
132
- 'autoJoin' => auto_join,
133
- 'description' => description,
134
- 'name' => name,
135
- 'realm' => realm,
136
- 'realmAttributes' => realm_attributes,
137
- }
138
- end
139
-
140
- #
141
- # The JSON representation of this resource.
142
- #
143
- # @return [String]
144
- #
145
- def to_json
146
- JSON.fast_generate(to_hash)
147
- end
148
-
149
82
  private
150
83
 
151
84
  #
@@ -0,0 +1,106 @@
1
+ require 'rexml/document'
2
+
3
+ module Artifactory
4
+ class Resource::Layout < Resource::Base
5
+ class << self
6
+ #
7
+ # Get a list of all repository layouts in the system.
8
+ #
9
+ # @param [Hash] options
10
+ # the list of options
11
+ #
12
+ # @option options [Artifactory::Client] :client
13
+ # the client object to make the request with
14
+ #
15
+ # @return [Array<Resource::Layout>]
16
+ # the list of layouts
17
+ #
18
+ def all(options = {})
19
+ config = Resource::System.configuration(options)
20
+ list_from_config('config/repoLayouts/repoLayout', config, options)
21
+ end
22
+
23
+ #
24
+ # Find (fetch) a layout by its name.
25
+ #
26
+ # @example Find a layout by its name
27
+ # Layout.find('maven-2-default') #=> #<Layout name: 'maven-2-default' ...>
28
+ #
29
+ # @param [String] name
30
+ # the name of the layout to find
31
+ # @param [Hash] options
32
+ # the list of options
33
+ #
34
+ # @option options [Artifactory::Client] :client
35
+ # the client object to make the request with
36
+ #
37
+ # @return [Resource::Layout, nil]
38
+ # an instance of the layout that matches the given name, or +nil+
39
+ # if one does not exist
40
+ #
41
+ def find(name, options = {})
42
+ config = Resource::System.configuration(options)
43
+ find_from_config("config/repoLayouts/repoLayout/name[text()='#{name}']", config, options)
44
+ rescue Error::HTTPError => e
45
+ raise unless e.code == 404
46
+ nil
47
+ end
48
+
49
+ private
50
+ #
51
+ # List all the child text elements in the Artifactory configuration file
52
+ # of a node matching the specified xpath
53
+ #
54
+ # @param [String] xpath
55
+ # xpath expression for the parent element whose children are to be listed
56
+ #
57
+ # @param [REXML] config
58
+ # Artifactory config as an REXML file
59
+ #
60
+ # @param [Hash] options
61
+ # the list of options
62
+ #
63
+ def list_from_config(xpath, config, options = {})
64
+ REXML::XPath.match(config, xpath).map do |r|
65
+ hash = {}
66
+
67
+ r.each_element_with_text do |l|
68
+ hash[l.name] = l.get_text
69
+ end
70
+ from_hash(hash, options)
71
+ end
72
+ end
73
+
74
+ #
75
+ # Find all the sibling text elements in the Artifactory configuration file
76
+ # of a node matching the specified xpath
77
+ #
78
+ # @param [String] xpath
79
+ # xpath expression for the element whose siblings are to be found
80
+ #
81
+ # @param [REXML] config
82
+ # Artifactory configuration file as an REXML doc
83
+ #
84
+ # @param [Hash] options
85
+ # the list of options
86
+ #
87
+ def find_from_config(xpath, config, options = {})
88
+ name_node = REXML::XPath.match(config, xpath)
89
+ return nil if name_node.empty?
90
+ properties = {}
91
+ name_node[0].parent.each_element_with_text do |e|
92
+ properties[e.name] = e.text
93
+ end
94
+
95
+ from_hash(properties, options)
96
+ end
97
+ end
98
+
99
+ attribute :name, ->{ raise 'Name missing!' }
100
+ attribute :artifact_path_pattern
101
+ attribute :distinctive_descriptor_path_pattern, 'true'
102
+ attribute :descriptor_path_pattern
103
+ attribute :folder_integration_revision_reg_exp
104
+ attribute :file_integration_revision_reg_exp
105
+ end
106
+ end
@@ -16,7 +16,7 @@ module Artifactory
16
16
  def all(options = {})
17
17
  client = extract_client!(options)
18
18
  client.get('/api/repositories').map do |hash|
19
- find(name: hash['key'], client: client)
19
+ find(hash['key'], client: client)
20
20
  end.compact
21
21
  end
22
22
 
@@ -38,50 +38,15 @@ module Artifactory
38
38
  # an instance of the repository that matches the given name, or +nil+
39
39
  # if one does not exist
40
40
  #
41
- def find(options = {})
41
+ def find(name, options = {})
42
42
  client = extract_client!(options)
43
- name = options[:name]
44
43
 
45
- result = client.get("/api/repositories/#{url_safe(name)}")
46
- from_hash(result, client: client)
47
- rescue Error::NotFound
44
+ response = client.get("/api/repositories/#{url_safe(name)}")
45
+ from_hash(response, client: client)
46
+ rescue Error::HTTPError => e
47
+ raise unless e.code == 400
48
48
  nil
49
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
50
  end
86
51
 
87
52
  attribute :blacked_out, false
@@ -95,57 +60,26 @@ module Artifactory
95
60
  attribute :maximum_unique_snapshots, 0
96
61
  attribute :notes
97
62
  attribute :property_sets, []
63
+ attribute :repo_layout_ref, 'maven-2-default'
64
+ attribute :rclass, 'local'
98
65
  attribute :snapshot_version_behavior, 'non-unique'
99
66
  attribute :suppress_pom_checks, false
100
- attribute :type
67
+
68
+ def save
69
+ client.put(api_path, to_json, headers)
70
+ true
71
+ end
101
72
 
102
73
  #
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.
74
+ # Upload to a given repository
108
75
  #
109
- # @see bit.ly/1dhJRMO Artifactory Matrix Properties
76
+ # @see Artifact#upload Upload syntax examples
110
77
  #
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
78
+ # @return [Resource::Artifact]
137
79
  #
138
80
  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)
81
+ artifact = Resource::Artifact.new
82
+ artifact.upload(key, path_or_io, path, properties, headers)
149
83
  end
150
84
 
151
85
  #
@@ -153,15 +87,7 @@ module Artifactory
153
87
  # documentation for the possible responses when the checksums fail to
154
88
  # match.
155
89
  #
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
90
+ # @see Artifact#upload More syntax examples
165
91
  #
166
92
  def upload_with_checksum(path_or_io, path, checksum, properties = {})
167
93
  upload(path_or_io, path, properties,
@@ -174,13 +100,7 @@ module Artifactory
174
100
  # Upload an artifact with the given archive. Consult the artifactory
175
101
  # documentation for the format of the archive to upload.
176
102
  #
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)
103
+ # @see Artifact#upload More syntax examples
184
104
  #
185
105
  def upload_from_archive(path_or_io, path, properties = {})
186
106
  upload(path_or_io, path, properties,
@@ -211,7 +131,7 @@ module Artifactory
211
131
  #
212
132
  #
213
133
  def files
214
- response = get("/api/storage/#{url_safe_key}", {
134
+ response = client.get("/api/storage/#{url_safe(key)}", {
215
135
  deep: 0,
216
136
  listFolders: 0,
217
137
  mdTimestamps: 0,
@@ -221,30 +141,58 @@ module Artifactory
221
141
  response['children']
222
142
  end
223
143
 
144
+ #
145
+ # Delete this repository from artifactory, suppressing any +ResourceNotFound+
146
+ # exceptions might occur.
147
+ #
148
+ # @return [Boolean]
149
+ # true if the object was deleted successfully, false otherwise
150
+ #
151
+ def delete
152
+ client.delete(api_path)
153
+ true
154
+ rescue Error::HTTPError => e
155
+ false
156
+ end
157
+
224
158
  private
225
159
 
226
160
  #
161
+ # The path to this repository on the server.
227
162
  #
163
+ # @return [String]
228
164
  #
229
- def url_safe_key
230
- @url_safe_key ||= URI.escape(key.to_s)
165
+ def api_path
166
+ "/api/repositories/#{url_safe(key)}"
231
167
  end
232
168
 
233
169
  #
234
- # @todo Make this a mixin or reusable?
170
+ # The default headers for this object. This includes the +Content-Type+.
235
171
  #
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
172
+ # @return [Hash]
173
+ #
174
+ def headers
175
+ @headers ||= {
176
+ 'Content-Type' => content_type
177
+ }
178
+ end
243
179
 
244
- if properties.empty?
245
- nil
180
+ #
181
+ # The default Content-Type for this repository. It varies based on the
182
+ # repository type.
183
+ #
184
+ # @return [String]
185
+ #
186
+ def content_type
187
+ case rclass.to_s.downcase
188
+ when 'local'
189
+ 'application/vnd.org.jfrog.artifactory.repositories.LocalRepositoryConfiguration+json'
190
+ when 'remote'
191
+ 'application/vnd.org.jfrog.artifactory.repositories.RemoteRepositoryConfiguration+json'
192
+ when 'virtual'
193
+ 'application/vnd.org.jfrog.artifactory.repositories.VirtualRepositoryConfiguration+json'
246
194
  else
247
- ";#{properties.join(';')}"
195
+ raise "Unknown Repository type `#{rclass}'!"
248
196
  end
249
197
  end
250
198
  end