artifactory 1.1.0 → 1.2.0

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