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
@@ -87,7 +87,11 @@ module Artifactory
87
87
  #
88
88
  def update_configuration(xml, options = {})
89
89
  client = extract_client!(options)
90
- client.post('/api/system/configuration', xml)
90
+
91
+ # The Artifactory api requires a content type of 'application/xml'.
92
+ # See http://bit.ly/1l2IvZY
93
+ headers = { 'Content-Type' => 'application/xml' }
94
+ client.post('/api/system/configuration', xml, headers)
91
95
  end
92
96
 
93
97
  #
@@ -43,57 +43,10 @@ module Artifactory
43
43
 
44
44
  response = client.get("/api/security/users/#{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 user from the given URL.
52
- #
53
- # @example Create an user object from the given URL
54
- # User.from_url('/security/users/readers') #=> #<Resource::User>
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 user from
60
- #
61
- # @return [Resource::User]
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 user from a hash
73
- # User.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::User]
81
- #
82
- def from_hash(hash, options = {})
83
- client = extract_client!(options)
84
-
85
- new.tap do |instance|
86
- instance.admin = !!hash['admin']
87
- instance.email = hash['email']
88
- instance.groups = Array(hash['groups'])
89
- instance.internal_password_disabled = hash['internalPasswordDisabled']
90
- instance.last_logged_in = hash['lastLoggedIn']
91
- instance.name = hash['name']
92
- instance.password = hash['password']
93
- instance.profile_updatable = !!hash['profileUpdatable']
94
- instance.realm = hash['realm']
95
- end
96
- end
97
50
  end
98
51
 
99
52
  attribute :admin, false
@@ -114,8 +67,9 @@ module Artifactory
114
67
  # true if the object was deleted successfully, false otherwise
115
68
  #
116
69
  def delete
117
- !!client.delete(api_path)
118
- rescue Error::NotFound
70
+ client.delete(api_path)
71
+ true
72
+ rescue Error::HTTPError => e
119
73
  false
120
74
  end
121
75
 
@@ -129,34 +83,6 @@ module Artifactory
129
83
  true
130
84
  end
131
85
 
132
- #
133
- # The hash format for this user.
134
- #
135
- # @return [Hash]
136
- #
137
- def to_hash
138
- {
139
- 'admin' => admin,
140
- 'email' => email,
141
- 'groups' => groups,
142
- 'internalPasswordDisabled' => internal_password_disabled,
143
- 'lastLoggedIn' => last_logged_in,
144
- 'name' => name,
145
- 'password' => password,
146
- 'profileUpdatable' => profile_updatable,
147
- 'realm' => realm,
148
- }
149
- end
150
-
151
- #
152
- # The JSON representation of this resource.
153
- #
154
- # @return [String]
155
- #
156
- def to_json
157
- JSON.fast_generate(to_hash)
158
- end
159
-
160
86
  private
161
87
 
162
88
  #
@@ -29,12 +29,18 @@ module Artifactory
29
29
  #
30
30
  # @return [String]
31
31
  #
32
- def camelize(string)
33
- string
32
+ def camelize(string, lowercase = false)
33
+ result = string
34
34
  .to_s
35
35
  .split('_')
36
36
  .map { |e| e.capitalize }
37
37
  .join
38
+
39
+ if lowercase
40
+ result[0,1].downcase + result[1..-1]
41
+ else
42
+ result
43
+ end
38
44
  end
39
45
 
40
46
  #
@@ -1,3 +1,3 @@
1
1
  module Artifactory
2
- VERSION = '1.1.0'
2
+ VERSION = '1.2.0'
3
3
  end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ module Artifactory
4
+ describe Resource::Layout, :integration do
5
+ describe '.all' do
6
+ it 'returns an array of Layouts' do
7
+ results = described_class.all
8
+ expect(results).to be_a(Array)
9
+ expect(results.first).to be_a(described_class)
10
+ end
11
+ end
12
+
13
+ describe '.find' do
14
+ it 'finds a layout by name' do
15
+ mvn_layout = described_class.find('maven-2-default')
16
+
17
+ expect(mvn_layout).to be_a(described_class)
18
+ expect(mvn_layout.name).to eq('maven-2-default')
19
+ end
20
+ end
21
+ end
22
+ end
@@ -9,5 +9,12 @@ module Artifactory
9
9
  expect(results.first).to be_a(described_class)
10
10
  end
11
11
  end
12
+
13
+ describe '#save' do
14
+ it 'saves the repository to the server' do
15
+ repository = described_class.new(key: 'libs-testing-local')
16
+ expect(repository.save).to be_true
17
+ end
18
+ end
12
19
  end
13
20
  end
@@ -23,10 +23,10 @@ module Artifactory
23
23
  describe '.update_configuration' do
24
24
  let(:tempfile) { Tempfile.new(['config', 'xml']) }
25
25
  let(:content) do
26
- """.strip.gsub(/^ {8}/, '')
27
- <?xml version='1.0' encoding='UTF-8' standalone='yes'?>
28
- <newConfig>true</newConfig>
29
- """
26
+ <<-EOH.strip.gsub(/^ {10}/, '')
27
+ <?xml version='1.0' encoding='UTF-8' standalone='yes'?>
28
+ <newConfig>true</newConfig>
29
+ EOH
30
30
  end
31
31
 
32
32
  after do
@@ -70,6 +70,11 @@ module Artifactory
70
70
  'rclass' => 'local'
71
71
  })
72
72
  end
73
+
74
+ app.put('/api/repositories/libs-testing-local') do
75
+ content_type 'text/plain'
76
+ "Repository libs-resting-local created successfully!\n"
77
+ end
73
78
  end
74
79
  end
75
80
  end
@@ -17,6 +17,24 @@ module Artifactory
17
17
  xmlns='http://artifactory.jfrog.org/xsd/1.5.3'
18
18
  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
19
19
  <offlineMode>false</offlineMode>
20
+ <repoLayouts>
21
+ <repoLayout>
22
+ <name>maven-2-default</name>
23
+ <artifactPathPattern>[orgPath]/[module]/[baseRev](-[folderItegRev])/[module]-[baseRev](-[fileItegRev])(-[classifier]).[ext]</artifactPathPattern>
24
+ <distinctiveDescriptorPathPattern>true</distinctiveDescriptorPathPattern>
25
+ <descriptorPathPattern>[orgPath]/[module]/[baseRev](-[folderItegRev])/[module]-[baseRev](-[fileItegRev])(-[classifier]).pom</descriptorPathPattern>
26
+ <folderIntegrationRevisionRegExp>SNAPSHOT</folderIntegrationRevisionRegExp>
27
+ <fileIntegrationRevisionRegExp>SNAPSHOT|(?:(?:[0-9]{8}.[0-9]{6})-(?:[0-9]+))</fileIntegrationRevisionRegExp>
28
+ </repoLayout>
29
+ <repoLayout>
30
+ <name>gradle-default</name>
31
+ <artifactPathPattern>[orgPath]/[module]/[baseRev](-[folderItegRev])/[module]-[baseRev](-[fileItegRev])(-[classifier]).[ext]</artifactPathPattern>
32
+ <distinctiveDescriptorPathPattern>true</distinctiveDescriptorPathPattern>
33
+ <descriptorPathPattern>[orgPath]/[module]/[baseRev](-[folderItegRev])/[module]-[baseRev](-[fileItegRev])(-[classifier]).pom</descriptorPathPattern>
34
+ <folderIntegrationRevisionRegExp>SNAPSHOT</folderIntegrationRevisionRegExp>
35
+ <fileIntegrationRevisionRegExp>SNAPSHOT|(?:(?:[0-9]{8}.[0-9]{6})-(?:[0-9]+))</fileIntegrationRevisionRegExp>
36
+ </repoLayout>
37
+ </repoLayouts>
20
38
  </config>
21
39
  EOH
22
40
  end
@@ -41,134 +41,53 @@ module Artifactory
41
41
  end
42
42
  end
43
43
 
44
- describe '#agent' do
45
- it 'acts like an HTTPClient' do
46
- expect(client.agent).to be_a(HTTPClient)
47
- end
48
-
49
- it 'caches the agent' do
50
- agent = Artifactory.client.agent
51
- expect(agent).to be(Artifactory.client.agent)
52
- end
53
-
54
- it 'sets basic auth if given' do
55
- subject.username = 'admin'
56
- subject.password = 'password'
57
- expect(subject.agent.www_auth.basic_auth).to be_a(HTTPClient::BasicAuth)
58
- end
59
-
60
- it 'sets the proxy if given' do
61
- subject.proxy = 'http://admin:password@localhost'
62
- expect(subject.agent.proxy.to_s).to eq('http://admin:password@localhost')
63
- end
64
- end
65
-
66
44
  describe '#get' do
67
45
  it 'delegates to the #request method' do
68
- expect(subject).to receive(:request).with(:get, '/foo')
46
+ expect(subject).to receive(:request).with(:get, '/foo', {}, {})
69
47
  subject.get('/foo')
70
48
  end
71
49
  end
72
50
 
73
51
  describe '#post' do
52
+ let(:data) { double }
53
+
74
54
  it 'delegates to the #request method' do
75
- expect(subject).to receive(:request).with(:post, '/foo')
76
- subject.post('/foo')
55
+ expect(subject).to receive(:request).with(:post, '/foo', data, {})
56
+ subject.post('/foo', data)
77
57
  end
78
58
  end
79
59
 
80
60
  describe '#put' do
61
+ let(:data) { double }
62
+
81
63
  it 'delegates to the #request method' do
82
- expect(subject).to receive(:request).with(:put, '/foo')
83
- subject.put('/foo')
64
+ expect(subject).to receive(:request).with(:put, '/foo', data, {})
65
+ subject.put('/foo', data)
84
66
  end
85
67
  end
86
68
 
87
69
  describe '#patch' do
70
+ let(:data) { double }
71
+
88
72
  it 'delegates to the #request method' do
89
- expect(subject).to receive(:request).with(:patch, '/foo')
90
- subject.patch('/foo')
73
+ expect(subject).to receive(:request).with(:patch, '/foo', data, {})
74
+ subject.patch('/foo', data)
91
75
  end
92
76
  end
93
77
 
94
78
  describe '#delete' do
95
79
  it 'delegates to the #request method' do
96
- expect(subject).to receive(:request).with(:delete, '/foo')
80
+ expect(subject).to receive(:request).with(:delete, '/foo', {}, {})
97
81
  subject.delete('/foo')
98
82
  end
99
83
  end
100
84
 
101
- describe '#head' do
102
- it 'delegates to the #request method' do
103
- expect(subject).to receive(:request).with(:head, '/foo')
104
- subject.head('/foo')
105
- end
106
- end
107
-
108
85
  describe '#request' do
109
- # it 'transforms relative URLs' do
110
- # request = client.request(:get, '/api/system/info')
111
- # expect(request.url).to eq("#{client.endpoint}/api/system/info")
112
- # end
113
-
114
- # it 'does not transform absolute URLs' do
115
- # request = client.request(:get, 'https://github.com')
116
- # expect(request.url).to eq('https://github.com')
117
- # end
118
-
119
86
  context 'when the response is a 400' do
120
- before { stub_request(:get, /.+/).to_return(status: 400) }
121
-
122
- it 'raises a BadRequest error' do
123
- expect { subject.request(:get, '/') }.to raise_error(Error::BadRequest)
124
- end
125
- end
126
-
127
- context 'when the response is a 401' do
128
- before { stub_request(:get, /.+/).to_return(status: 401) }
129
-
130
- it 'raises a Unauthorized error' do
131
- expect { subject.request(:get, '/') }.to raise_error(Error::Unauthorized)
132
- end
133
- end
134
-
135
- context 'when the response is a 403' do
136
- before { stub_request(:get, /.+/).to_return(status: 403) }
137
-
138
- it 'raises a Forbidden error' do
139
- expect { subject.request(:get, '/') }.to raise_error(Error::Forbidden)
140
- end
141
- end
142
-
143
- context 'when the response is a 404' do
144
- before { stub_request(:get, /.+/).to_return(status: 404) }
145
-
146
- it 'raises a NotFound error' do
147
- expect { subject.request(:get, '/') }.to raise_error(Error::NotFound)
148
- end
149
- end
150
-
151
- context 'when the response is a 405' do
152
- before { stub_request(:get, /.+/).to_return(status: 405) }
153
-
154
- it 'raises a MethodNotAllowed error' do
155
- expect { subject.request(:get, '/') }.to raise_error(Error::MethodNotAllowed)
156
- end
157
- end
158
-
159
- context 'when the response is a 500' do
160
- before { stub_request(:get, /.+/).to_return(status: 500) }
161
-
162
- it 'raises a ConnectionError error' do
163
- expect { subject.request(:get, '/') }.to raise_error(Error::ConnectionError)
164
- end
165
- end
166
-
167
- context 'when something really bad happens' do
168
- before { stub_request(:get, /.+/).to_raise(SocketError) }
87
+ before { stub_request(:get, /.+/).to_return(status: 5000, body: 'No!') }
169
88
 
170
- it 'converts to a ConnectionError' do
171
- expect { subject.request(:get, '/') }.to raise_error(Error::ConnectionError)
89
+ it 'raises an HTTPError error' do
90
+ expect { subject.request(:get, '/') }.to raise_error(Error::HTTPError)
172
91
  end
173
92
  end
174
93
  end
@@ -34,6 +34,92 @@ module Artifactory
34
34
  end
35
35
  end
36
36
 
37
+ describe '#upload' do
38
+ let(:client) { double(put: {}) }
39
+ before do
40
+ subject.client = client
41
+ end
42
+
43
+ context 'when the artifact is a File' do
44
+ it 'PUTs the file to the server' do
45
+ file = double(file)
46
+ File.stub(:new).and_return(file)
47
+ expect(client).to receive(:put).with('libs-release-local/remote/path', file, {})
48
+
49
+ subject.upload('libs-release-local', file, '/remote/path')
50
+ end
51
+ end
52
+
53
+ context 'when the artifact is a file path' do
54
+ it 'PUTs the file at the path to the server' do
55
+ file = double(file)
56
+ path = '/fake/path'
57
+ File.stub(:new).with('/fake/path').and_return(file)
58
+ expect(client).to receive(:put).with('libs-release-local/remote/path', file, {})
59
+
60
+ subject.upload('libs-release-local', path, '/remote/path')
61
+ end
62
+ end
63
+
64
+ context 'when matrix properties are given' do
65
+ it 'converts the hash into matrix properties' do
66
+ file = double(file)
67
+ File.stub(:new).and_return(file)
68
+ expect(client).to receive(:put).with('libs-release-local;branch=master;user=Seth%20Vargo/remote/path', file, {})
69
+
70
+ subject.upload('libs-release-local', file, '/remote/path',
71
+ branch: 'master',
72
+ user: 'Seth Vargo',
73
+ )
74
+ end
75
+ end
76
+
77
+ context 'when custom headers are given' do
78
+ it 'passes the headers to the client' do
79
+ headers = { 'Content-Type' => 'text/plain' }
80
+ file = double(file)
81
+ File.stub(:new).and_return(file)
82
+ expect(client).to receive(:put).with('libs-release-local/remote/path', file, headers)
83
+
84
+ subject.upload('libs-release-local', file, '/remote/path', {}, headers)
85
+ end
86
+ end
87
+ end
88
+
89
+ describe '#upload_with_checksum' do
90
+ it 'delegates to #upload' do
91
+ expect(subject).to receive(:upload).with(
92
+ 'libs-release-local',
93
+ '/local/file',
94
+ '/remote/path',
95
+ { branch: 'master' },
96
+ {
97
+ 'X-Checksum-Deploy' => true,
98
+ 'X-Checksum-Sha1' => 'ABCD1234',
99
+ },
100
+ )
101
+ subject.upload_with_checksum('libs-release-local', '/local/file', '/remote/path',
102
+ 'ABCD1234',
103
+ { branch: 'master' },
104
+ )
105
+ end
106
+ end
107
+
108
+ describe '#upload_from_archive' do
109
+ it 'delegates to #upload' do
110
+ expect(subject).to receive(:upload).with(
111
+ 'libs-release-local',
112
+ '/local/file',
113
+ '/remote/path',
114
+ {},
115
+ {
116
+ 'X-Explode-Archive' => true,
117
+ },
118
+ )
119
+ subject.upload_from_archive('libs-release-local', '/local/file', '/remote/path')
120
+ end
121
+ end
122
+
37
123
  describe '.gavc_search' do
38
124
  let(:response) { { 'results' => [] } }
39
125
 
@@ -161,7 +247,7 @@ module Artifactory
161
247
  end
162
248
 
163
249
  it 'returns an empty array when the server responses with a 404' do
164
- client.stub(:get).and_raise(Error::NotFound)
250
+ client.stub(:get).and_raise(Error::HTTPError.new('status' => 404))
165
251
 
166
252
  result = described_class.versions
167
253
  expect(result).to be_a(Array)
@@ -207,7 +293,7 @@ module Artifactory
207
293
  end
208
294
 
209
295
  it 'returns an nil when the server responses with a 404' do
210
- client.stub(:get).and_raise(Error::NotFound)
296
+ client.stub(:get).and_raise(Error::HTTPError.new('status' => 404))
211
297
 
212
298
  expect(described_class.latest_version).to be_nil
213
299
  end
@@ -242,13 +328,13 @@ module Artifactory
242
328
  }
243
329
  end
244
330
 
245
- it 'creates a new instnace' do
331
+ it 'creates a new instance' do
246
332
  instance = described_class.from_hash(hash)
247
333
  expect(instance).to be_a(described_class)
248
- expect(instance.api_path).to eq('http://localhost:8080/artifactory/api/storage/libs-release-local/org/acme/lib/ver/lib-ver.pom')
334
+ expect(instance.uri).to eq('http://localhost:8080/artifactory/api/storage/libs-release-local/org/acme/lib/ver/lib-ver.pom')
249
335
  expect(instance.client).to be(client)
250
336
  expect(instance.created).to eq(Time.parse('2014-01-01 10:00 UTC'))
251
- expect(instance.download_path).to eq('http://localhost:8080/artifactory/libs-release-local/org/acme/lib/ver/lib-ver.pom')
337
+ expect(instance.download_uri).to eq('http://localhost:8080/artifactory/libs-release-local/org/acme/lib/ver/lib-ver.pom')
252
338
  expect(instance.last_modified).to eq(Time.parse('2014-01-01 11:00 UTC'))
253
339
  expect(instance.last_updated).to eq(Time.parse('2014-01-01 12:00 UTC'))
254
340
  expect(instance.md5).to eq('MD5123')
@@ -274,7 +360,7 @@ module Artifactory
274
360
 
275
361
  it 'sends DELETE to the client' do
276
362
  subject.client = client
277
- subject.download_path = '/artifact.deb'
363
+ subject.download_uri = '/artifact.deb'
278
364
 
279
365
  expect(client).to receive(:delete)
280
366
  subject.delete
@@ -300,15 +386,15 @@ module Artifactory
300
386
  { 'properties' => properties }
301
387
  end
302
388
  let(:client) { double(get: response) }
303
- let(:api_path) { '/artifact.deb' }
389
+ let(:uri) { '/artifact.deb' }
304
390
 
305
391
  before do
306
392
  subject.client = client
307
- subject.api_path = api_path
393
+ subject.uri = uri
308
394
  end
309
395
 
310
396
  it 'gets the properties from the server' do
311
- expect(client).to receive(:get).with(api_path, properties: nil).once
397
+ expect(client).to receive(:get).with(uri, properties: nil).once
312
398
  expect(subject.properties).to eq(properties)
313
399
  end
314
400
 
@@ -323,11 +409,11 @@ module Artifactory
323
409
  { 'licenses' => [{ 'name' => 'LGPL v3' }] }
324
410
  end
325
411
  let(:client) { double(get: compliance) }
326
- let(:api_path) { '/artifact.deb' }
412
+ let(:uri) { '/artifact.deb' }
327
413
 
328
414
  before do
329
415
  subject.client = client
330
- subject.api_path = api_path
416
+ subject.uri = uri
331
417
  end
332
418
 
333
419
  it 'gets the compliance from the server' do
@@ -349,7 +435,7 @@ module Artifactory
349
435
  before { described_class.send(:public, :relative_path) }
350
436
 
351
437
  it 'parses the relative path' do
352
- subject.api_path = '/api/storage/foo/bar/zip'
438
+ subject.uri = '/api/storage/foo/bar/zip'
353
439
  expect(subject.relative_path).to eq('/foo/bar/zip')
354
440
  end
355
441
  end
@@ -360,7 +446,7 @@ module Artifactory
360
446
  described_class.send(:public, :copy_or_move)
361
447
 
362
448
  subject.client = client
363
- subject.api_path = '/api/storage/foo/bar/artifact.deb'
449
+ subject.uri = '/api/storage/foo/bar/artifact.deb'
364
450
  end
365
451
 
366
452
  it 'sends POST to the client with parsed params' do