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,57 @@
1
+ module Artifactory
2
+ class Resource::User < Resource::Base
3
+ class << self
4
+ #
5
+ #
6
+ #
7
+ def all(options = {})
8
+ client = extract_client!(options)
9
+ client.get('/api/security/users').map do |hash|
10
+ from_url(hash['uri'], client: client)
11
+ end
12
+ end
13
+
14
+ #
15
+ #
16
+ #
17
+ def find(options = {})
18
+ client = extract_client!(options)
19
+ username = URI.escape(options[:username])
20
+ response = client.get("/api/security/users/#{username}")
21
+ from_hash(response, client: client)
22
+ rescue Error::NotFound
23
+ nil
24
+ end
25
+
26
+ #
27
+ #
28
+ #
29
+ def from_url(url, options = {})
30
+ client = extract_client!(options)
31
+ from_hash(client.get(url), client: client)
32
+ end
33
+
34
+ #
35
+ #
36
+ #
37
+ def from_hash(hash, options = {})
38
+ client = extract_client!(options)
39
+
40
+ new(client).tap do |instance|
41
+ instance.admin = hash['admin']
42
+ instance.email = hash['email']
43
+ instance.last_login = Time.parse(hash['lastLoggedIn']) rescue nil
44
+ instance.name = hash['name']
45
+ instance.realm = hash['realm']
46
+ end
47
+ end
48
+ end
49
+
50
+ attribute :admin, false
51
+ attribute :email
52
+ attribute :last_login
53
+ attribute :name, ->{ raise 'Name missing!' }
54
+ attribute :password
55
+ attribute :realm
56
+ end
57
+ end
@@ -0,0 +1,93 @@
1
+ module Artifactory
2
+ module Util
3
+ extend self
4
+
5
+ #
6
+ # Covert the given CaMelCaSeD string to under_score. Graciously borrowed
7
+ # from http://stackoverflow.com/questions/1509915.
8
+ #
9
+ # @param [String] string
10
+ # the string to use for transformation
11
+ #
12
+ # @return [String]
13
+ #
14
+ def underscore(string)
15
+ string
16
+ .to_s
17
+ .gsub(/::/, '/')
18
+ .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
19
+ .gsub(/([a-z\d])([A-Z])/,'\1_\2')
20
+ .tr('-', '_')
21
+ .downcase
22
+ end
23
+
24
+ #
25
+ # Convert an underscored string to it's camelcase equivalent constant.
26
+ #
27
+ # @param [String] string
28
+ # the string to convert
29
+ #
30
+ # @return [String]
31
+ #
32
+ def camelize(string)
33
+ string
34
+ .to_s
35
+ .split('_')
36
+ .map { |e| e.capitalize }
37
+ .join
38
+ end
39
+
40
+ #
41
+ # Truncate the given string to a certain number of characters.
42
+ #
43
+ # @param [String] string
44
+ # the string to truncate
45
+ # @param [Hash] options
46
+ # the list of options (such as +length+)
47
+ #
48
+ def truncate(string, options = {})
49
+ length = options[:length] || 30
50
+
51
+ if string.length > length
52
+ string[0..length-3] + '...'
53
+ else
54
+ string
55
+ end
56
+ end
57
+
58
+ #
59
+ # Rename a list of keys to the given map.
60
+ #
61
+ # @example Rename the given keys
62
+ # rename_keys(hash, foo: :bar, zip: :zap)
63
+ #
64
+ # @param [Hash] options
65
+ # the options to map
66
+ # @param [Hash] map
67
+ # the map of keys to map
68
+ #
69
+ # @return [Hash]
70
+ #
71
+ def rename_keys(options, map = {})
72
+ Hash[options.map { |k, v| [map[k] || k, v] }]
73
+ end
74
+
75
+ #
76
+ # Slice the given list of options with the given keys.
77
+ #
78
+ # @param [Hash] options
79
+ # the list of options to slice
80
+ # @param [Array<Object>] keys
81
+ # the keys to slice
82
+ #
83
+ # @return [Hash]
84
+ # the sliced hash
85
+ #
86
+ def slice(options, *keys)
87
+ keys.inject({}) do |hash, key|
88
+ hash[key] = options[key] if options[key]
89
+ hash
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,3 +1,3 @@
1
1
  module Artifactory
2
- VERSION = "0.0.1"
2
+ VERSION = '1.0.0'
3
3
  end
@@ -0,0 +1,27 @@
1
+ en:
2
+ artifactory:
3
+ errors:
4
+ bad_request: >
5
+ The Artifactory server declined to server the URL `%{url}'. Here is
6
+ more information from the server:
7
+
8
+ %{body}
9
+ connection_error: >
10
+ The Artifactory server at `%{url}' threw an unexpected error. We do not
11
+ have any additional information to offer, sorry. Here is what the server
12
+ said:
13
+
14
+ %{body}
15
+ forbidden: >
16
+ You are not permitted to access `%{url}'. The Artifactory server
17
+ responded with a 403 Forbidden error.
18
+ method_not_allowed: >
19
+ That HTTP method is not allowed!
20
+ not_found: >
21
+ The requested URL `%{url}' does not exist on the Artifactory server.
22
+ Please confirm you have spelled everything correctly.
23
+ unauthorized: >
24
+ You are not authorized to access `%{url}'. The Artifactory server
25
+ responded with a 401 Unauthorzed. The requested endpoint requires
26
+ authentication - try adding basic authentication to your request and
27
+ try again.
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ module Artifactory
4
+ describe Resource::Artifact, :integration do
5
+ shared_examples 'an artifact search endpoint' do |name, options = {}|
6
+ it "finds artifacts by #{name}" do
7
+ response = described_class.send(name, options)
8
+ expect(response).to be_a(Array)
9
+ expect(response.size).to eq(2)
10
+
11
+ artifact_1 = response[0]
12
+ expect(artifact_1).to be_a(described_class)
13
+ expect(artifact_1.repo).to eq('libs-release-local')
14
+
15
+ artifact_2 = response[1]
16
+ expect(artifact_2).to be_a(described_class)
17
+ expect(artifact_2.repo).to eq('ext-release-local')
18
+ end
19
+
20
+ it "finds artifacts by repo" do
21
+ response = described_class.send(name, options.merge(repos: 'libs-release-local'))
22
+ expect(response).to be_a(Array)
23
+ expect(response.size).to eq(1)
24
+
25
+ artifact = response.first
26
+ expect(artifact).to be_a(described_class)
27
+ expect(artifact.repo).to eq('libs-release-local')
28
+ end
29
+ end
30
+
31
+ describe '.search' do
32
+ it_behaves_like 'an artifact search endpoint', :search, name: 'artifact.deb'
33
+ end
34
+
35
+ describe '.gavc_search' do
36
+ it_behaves_like 'an artifact search endpoint', :gavc_search, {
37
+ group: 'org.acme',
38
+ name: 'artifact.deb',
39
+ version: '1.0',
40
+ classifier: 'sources',
41
+ }
42
+ end
43
+
44
+ describe '.property_search' do
45
+ it_behaves_like 'an artifact search endpoint', :property_search, {
46
+ branch: 'master',
47
+ committer: 'Seth Vargo',
48
+ }
49
+ end
50
+
51
+ describe '.checksum_search' do
52
+ it_behaves_like 'an artifact search endpoint', :checksum_search, md5: 'abcd1234'
53
+ end
54
+
55
+ describe '.versions' do
56
+ it 'returns an array of versions' do
57
+ response = described_class.versions
58
+ expect(response).to be_a(Array)
59
+ expect(response.size).to eq(3)
60
+
61
+ expect(response[0]['version']).to eq('1.2')
62
+ end
63
+ end
64
+
65
+ describe '.latest_version' do
66
+ it 'finds the latest version of the artifact' do
67
+ response = described_class.latest_version
68
+ expect(response).to be_a(String)
69
+ expect(response).to eq('1.0-201203131455-2')
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ module Artifactory
4
+ describe Resource::Build, :integration do
5
+ describe '.all' do
6
+ it 'returns an array of build objects' do
7
+ pending 'Need more information about what the Build API returns'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ module Artifactory
4
+ describe Resource::Repository, :integration do
5
+ describe '.all' do
6
+ it 'returns an array of repository objects' 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
+ end
13
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ module Artifactory
4
+ describe Resource::System, :integration do
5
+ describe '.info' do
6
+ it 'gets the system information' do
7
+ expect(described_class.info).to eq('This is some serious system info right here')
8
+ end
9
+ end
10
+
11
+ describe '.ping' do
12
+ it 'returns ok' do
13
+ expect(described_class.ping).to be_true
14
+ end
15
+ end
16
+
17
+ describe '.configuration' do
18
+ it 'returns the system configuration' do
19
+ expect(described_class.configuration).to be_a(REXML::Document)
20
+ end
21
+ end
22
+
23
+ describe '.update_configuration' do
24
+ let(:tempfile) { Tempfile.new(['config', 'xml']) }
25
+ let(:content) do
26
+ """.strip.gsub(/^ {8}/, '')
27
+ <?xml version='1.0' encoding='UTF-8' standalone='yes'?>
28
+ <newConfig>true</newConfig>
29
+ """
30
+ end
31
+
32
+ after do
33
+ tempfile.close
34
+ tempfile.unlink
35
+ end
36
+
37
+ it 'posts the new configuration to the server' do
38
+ tempfile.write(content)
39
+ tempfile.rewind
40
+
41
+ expect(described_class.update_configuration(tempfile)).to eq(content)
42
+ end
43
+ end
44
+
45
+ describe '.version' do
46
+ it 'gets the version information' do
47
+ expect(described_class.version).to eq({
48
+ 'version' => '3.1.0',
49
+ 'revision' => '30062',
50
+ 'addons' => [
51
+ 'ldap',
52
+ 'license',
53
+ 'yum'
54
+ ]
55
+ })
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,40 @@
1
+ require 'bundler/setup'
2
+ require 'rspec'
3
+ require 'webmock/rspec'
4
+
5
+ # Require our main library
6
+ require 'artifactory'
7
+
8
+ # Require helpers
9
+ require_relative 'support/api_server'
10
+
11
+ RSpec.configure do |config|
12
+ # Custom helper modules and extensions
13
+
14
+ # Prohibit using the should syntax
15
+ config.expect_with :rspec do |spec|
16
+ spec.syntax = :expect
17
+ end
18
+
19
+ # Allow tests to isolate a specific test using +focus: true+. If nothing
20
+ # is focused, then all tests are executed.
21
+ config.filter_run(focus: true)
22
+ config.run_all_when_everything_filtered = true
23
+ config.treat_symbols_as_metadata_keys_with_true_values = true
24
+
25
+ # Stuff to do on each run
26
+ config.before(:each) { Artifactory.reset! }
27
+ config.after(:each) { Artifactory.reset! }
28
+
29
+ #
30
+ config.before(:each, :integration) do
31
+ Artifactory.endpoint = 'http://localhost:8889'
32
+ stub_request(:any, /#{Artifactory.endpoint}/).to_rack(Artifactory::APIServer)
33
+ end
34
+
35
+ # Run specs in random order to surface order dependencies. If you find an
36
+ # order dependency and want to debug it, you can fix the order by providing
37
+ # the seed, which is printed after each run.
38
+ # --seed 1234
39
+ config.order = 'random'
40
+ end
@@ -0,0 +1,41 @@
1
+ require 'sinatra/base'
2
+
3
+ module Artifactory
4
+ #
5
+ # An in-memory, fully-API compliant Artifactory server. The data is all
6
+ # fake, but it is based off of real responses from the Artifactory server.
7
+ #
8
+ class APIServer < Sinatra::Base
9
+ require_relative 'api_server/artifact_endpoints'
10
+ require_relative 'api_server/build_endpoints'
11
+ require_relative 'api_server/repository_endpoints'
12
+ require_relative 'api_server/status_endpoints'
13
+ require_relative 'api_server/system_endpoints'
14
+
15
+ register APIServer::ArtifactEndpoints
16
+ register APIServer::BuildEndpoints
17
+ register APIServer::RepositoryEndpoints
18
+ register APIServer::StatusEndpoints
19
+ register APIServer::SystemEndpoints
20
+
21
+ private
22
+
23
+ #
24
+ # This server's URL, returned as a {Pathname} for easy joining.
25
+ #
26
+ # @example Construct a server url
27
+ # server_url.join('libs-release-local', 'artifact.deb')
28
+ #
29
+ # @return [Pathname]
30
+ #
31
+ def server_url
32
+ @server_url ||= begin
33
+ scheme = request.env['rack.url_scheme']
34
+ address = request.env['SERVER_NAME']
35
+ port = request.env['SERVER_PORT']
36
+
37
+ Pathname.new("#{scheme}://#{address}:#{port}")
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,122 @@
1
+ module Artifactory
2
+ module APIServer::ArtifactEndpoints
3
+ def self.registered(app)
4
+ app.get('/api/search/artifact') do
5
+ content_type 'application/vnd.org.jfrog.artifactory.search.ArtifactSearchResult+json'
6
+ artifacts_for_conditions do
7
+ params['name'] == 'artifact.deb'
8
+ end
9
+ end
10
+
11
+ app.get('/api/search/gavc') do
12
+ content_type 'application/vnd.org.jfrog.artifactory.search.GavcSearchResult+json'
13
+ artifacts_for_conditions do
14
+ params['g'] == 'org.acme' &&
15
+ params['a'] == 'artifact.deb' &&
16
+ params['v'] == '1.0' &&
17
+ params['c'] == 'sources'
18
+ end
19
+ end
20
+
21
+ app.get('/api/search/prop') do
22
+ content_type 'application/vnd.org.jfrog.artifactory.search.MetadataSearchResult+json'
23
+ artifacts_for_conditions do
24
+ params['branch'] == 'master' && params['committer'] == 'Seth Vargo'
25
+ end
26
+ end
27
+
28
+ app.get('/api/search/checksum') do
29
+ content_type 'application/vnd.org.jfrog.artifactory.search.ChecksumSearchResult+json'
30
+ artifacts_for_conditions do
31
+ params['md5'] == 'abcd1234'
32
+ end
33
+ end
34
+
35
+ app.get('/api/search/versions') do
36
+ content_type 'application/vnd.org.jfrog.artifactory.search.ArtifactVersionsResult+json'
37
+ JSON.fast_generate(
38
+ 'results' => [
39
+ { 'version' => '1.2', 'integration' => false },
40
+ { 'version' => '1.0-SNAPSHOT', 'integration' => true },
41
+ { 'version' => '1.0', 'integration' => false },
42
+ ]
43
+ )
44
+ end
45
+
46
+ app.get('/api/search/latestVersion') do
47
+ '1.0-201203131455-2'
48
+ end
49
+
50
+ app.get('/api/storage/libs-release-local/org/acme/artifact.deb') do
51
+ content_type 'application/vnd.org.jfrog.artifactory.storage.FileInfo+json'
52
+ JSON.fast_generate(
53
+ 'uri' => server_url.join('/api/storage/libs-release-local/org/acme/artifact.deb'),
54
+ 'downloadUri' => server_url.join('/artifactory/libs-release-local/org/acme/artifact.deb'),
55
+ 'repo' => 'libs-release-local',
56
+ 'path' => '/org/acme/artifact.deb',
57
+ 'created' => Time.parse('1991-07-23 12:07am'),
58
+ 'createdBy' => 'schisamo',
59
+ 'lastModified' => Time.parse('2013-12-24 11:50pm'),
60
+ 'modifiedBy' => 'sethvargo',
61
+ 'lastUpdated' => Time.parse('2014-01-01 1:00pm'),
62
+ 'size' => '1024',
63
+ 'mimeType' => 'application/tgz',
64
+ 'checksums' => {
65
+ 'md5' => 'MD5123',
66
+ 'sha' => 'SHA456'
67
+ },
68
+ 'originalChecksums' => {
69
+ 'md5' => 'MD5123',
70
+ 'sha' => 'SHA456'
71
+ }
72
+ )
73
+ end
74
+
75
+ app.get('/api/storage/ext-release-local/org/acme/artifact.deb') do
76
+ content_type 'application/vnd.org.jfrog.artifactory.storage.FileInfo+json'
77
+ JSON.fast_generate(
78
+ 'uri' => server_url.join('/api/storage/ext-release-local/org/acme/artifact.deb'),
79
+ 'downloadUri' => server_url.join('/artifactory/ext-release-local/org/acme/artifact.deb'),
80
+ 'repo' => 'ext-release-local',
81
+ 'path' => '/org/acme/artifact.deb',
82
+ 'created' => Time.parse('1995-04-11 11:05am'),
83
+ 'createdBy' => 'yzl',
84
+ 'lastModified' => Time.parse('2013-11-10 10:10pm'),
85
+ 'modifiedBy' => 'schisamo',
86
+ 'lastUpdated' => Time.parse('2014-02-02 2:00pm'),
87
+ 'size' => '1024',
88
+ 'mimeType' => 'application/tgz',
89
+ 'checksums' => {
90
+ 'md5' => 'MD5789',
91
+ 'sha' => 'SHA101'
92
+ },
93
+ 'originalChecksums' => {
94
+ 'md5' => 'MD5789',
95
+ 'sha' => 'SHA101'
96
+ }
97
+ )
98
+ end
99
+
100
+ app.class_eval do
101
+ def artifacts_for_conditions(&block)
102
+ if block.call
103
+ if params['repos'] == 'libs-release-local'
104
+ JSON.fast_generate(
105
+ 'results' => [
106
+ { 'uri' => server_url.join('/api/storage/libs-release-local/org/acme/artifact.deb') },
107
+ ]
108
+ )
109
+ else
110
+ JSON.fast_generate(
111
+ 'results' => [
112
+ { 'uri' => server_url.join('/api/storage/libs-release-local/org/acme/artifact.deb') },
113
+ { 'uri' => server_url.join('/api/storage/ext-release-local/org/acme/artifact.deb') },
114
+ ]
115
+ )
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end