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
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -4,20 +4,25 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'artifactory/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "artifactory"
7
+ spec.name = 'artifactory'
8
8
  spec.version = Artifactory::VERSION
9
- spec.authors = ["Sascha Bates", "Fletcher Nichol"]
10
- spec.email = ["sascha@brattyredhead.com", "fnichol@nichol.ca"]
11
- spec.description = %q{A Ruby interface for Artifactory}
12
- spec.summary = spec.description
13
- spec.homepage = "http://ProjectXY.github.io/artifactory"
14
- spec.license = "MIT"
9
+ spec.author = 'Seth Vargo'
10
+ spec.email = 'sethvargo@gmail.com'
11
+ spec.description = 'A Ruby client for Artifactory'
12
+ spec.summary = 'Artifactory is a simple, lightweight Ruby client for ' \
13
+ 'interacting with the Artifactory and Artifactory Pro ' \
14
+ 'APIs.'
15
+ spec.homepage = 'https://github.com/opscode/artifactory-client'
16
+ spec.license = 'Apache 2.0'
15
17
 
16
18
  spec.files = `git ls-files`.split($/)
17
19
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
21
+ spec.require_paths = ['lib']
20
22
 
21
- spec.add_development_dependency "bundler", "~> 1.3"
22
- spec.add_development_dependency "rake"
23
+ spec.add_dependency 'httpclient', '~> 2.3'
24
+ spec.add_dependency 'i18n', '~> 0.5'
25
+
26
+ spec.add_development_dependency 'bundler'
27
+ spec.add_development_dependency 'rake'
23
28
  end
@@ -1,5 +1,77 @@
1
- require "artifactory/version"
1
+ require 'artifactory/version'
2
2
 
3
3
  module Artifactory
4
- # Your code goes here...
4
+ autoload :Client, 'artifactory/client'
5
+ autoload :Configurable, 'artifactory/configurable'
6
+ autoload :Defaults, 'artifactory/defaults'
7
+ autoload :Error, 'artifactory/errors'
8
+ autoload :Util, 'artifactory/util'
9
+
10
+ module Collection
11
+ autoload :Artifact, 'artifactory/collections/artifact'
12
+ autoload :Base, 'artifactory/collections/base'
13
+ end
14
+
15
+ module Resource
16
+ autoload :Artifact, 'artifactory/resources/artifact'
17
+ autoload :Base, 'artifactory/resources/base'
18
+ autoload :Build, 'artifactory/resources/build'
19
+ autoload :Plugin, 'artifactory/resources/plugin'
20
+ autoload :Repository, 'artifactory/resources/repository'
21
+ autoload :System, 'artifactory/resources/system'
22
+ autoload :User, 'artifactory/resources/user'
23
+ end
24
+
25
+ class << self
26
+ include Artifactory::Configurable
27
+
28
+ #
29
+ # The root of the Artifactory gem. This method is useful for finding files
30
+ # relative to the root of the repository.
31
+ #
32
+ # @return [Pathname]
33
+ #
34
+ def root
35
+ @root ||= Pathname.new(File.expand_path('../../', __FILE__))
36
+ end
37
+
38
+ #
39
+ # API client object based off the configured options in {Configurable}.
40
+ #
41
+ # @return [Artifactory::Client]
42
+ #
43
+ def client
44
+ unless defined?(@client) && @client.same_options?(options)
45
+ @client = Artifactory::Client.new(options)
46
+ end
47
+
48
+ @client
49
+ end
50
+
51
+ #
52
+ # Delegate all methods to the client object, essentially making the module
53
+ # object behave like a {Client}.
54
+ #
55
+ def method_missing(m, *args, &block)
56
+ if client.respond_to?(m)
57
+ client.send(m, *args, &block)
58
+ else
59
+ super
60
+ end
61
+ end
62
+
63
+ #
64
+ # Delegating +respond_to+ to the {Client}.
65
+ #
66
+ def respond_to_missing?(m, include_private = false)
67
+ client.respond_to?(m) || super
68
+ end
69
+ end
5
70
  end
71
+
72
+ require 'i18n'
73
+ I18n.enforce_available_locales = false
74
+ I18n.load_path << Dir[Artifactory.root.join('locales', '*.yml').to_s]
75
+
76
+ # Load the initial default values
77
+ Artifactory.setup
@@ -0,0 +1,222 @@
1
+ require 'httpclient'
2
+ require 'json'
3
+ require 'rexml/document'
4
+ require 'uri'
5
+
6
+ module Artifactory
7
+ #
8
+ # Client for the Artifactory API.
9
+ #
10
+ # @see http://www.jfrog.com/confluence/display/RTF/Artifactory+REST+API
11
+ #
12
+ class Client
13
+ class << self
14
+ #
15
+ # @private
16
+ #
17
+ def proxy(klass)
18
+ namespace = klass.name.split('::').last.downcase
19
+ klass.singleton_methods(false).each do |name|
20
+ define_method("#{namespace}_#{name}") do |*args|
21
+ if args.last.is_a?(Hash)
22
+ args.last[:client] = self
23
+ else
24
+ args << { client: self }
25
+ end
26
+
27
+ klass.send(name, *args)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ include Artifactory::Configurable
34
+
35
+ proxy Resource::Artifact
36
+ proxy Resource::Repository
37
+ proxy Resource::User
38
+ proxy Resource::System
39
+
40
+ #
41
+ # Create a new Artifactory Client with the given options. Any options
42
+ # given take precedence over the default options.
43
+ #
44
+ # @return [Artifactory::Client]
45
+ #
46
+ def initialize(options = {})
47
+ # Use any options given, but fall back to the defaults set on the module
48
+ Artifactory::Configurable.keys.each do |key|
49
+ value = if options[key].nil?
50
+ Artifactory.instance_variable_get(:"@#{key}")
51
+ else
52
+ options[key]
53
+ end
54
+
55
+ instance_variable_set(:"@#{key}", value)
56
+ end
57
+ end
58
+
59
+ #
60
+ # Determine if the given options are the same as ours.
61
+ #
62
+ # @return [Boolean]
63
+ #
64
+ def same_options?(opts)
65
+ opts.hash == options.hash
66
+ end
67
+
68
+ #
69
+ # Make a HTTP GET request
70
+ #
71
+ # @param [String] path
72
+ # the path to get, relative to {Defaults.endpoint}
73
+ #
74
+ def get(path, *args, &block)
75
+ request(:get, path, *args, &block)
76
+ end
77
+
78
+ #
79
+ # Make a HTTP POST request
80
+ #
81
+ # @param [String] path
82
+ # the path to post, relative to {Defaults.endpoint}
83
+ #
84
+ def post(path, *args, &block)
85
+ request(:post, path, *args, &block)
86
+ end
87
+
88
+ #
89
+ # Make a HTTP PUT request
90
+ #
91
+ # @param [String] path
92
+ # the path to put, relative to {Defaults.endpoint}
93
+ #
94
+ def put(path, *args, &block)
95
+ request(:put, path, *args, &block)
96
+ end
97
+
98
+ #
99
+ # Make a HTTP PATCH request
100
+ #
101
+ # @param [String] path
102
+ # the path to patch, relative to {Defaults.endpoint}
103
+ #
104
+ def patch(path, *args, &block)
105
+ request(:patch, path, *args, &block)
106
+ end
107
+
108
+ #
109
+ # Make a HTTP DELETE request
110
+ #
111
+ # @param [String] path
112
+ # the path to delete, relative to {Defaults.endpoint}
113
+ #
114
+ def delete(path, *args, &block)
115
+ request(:delete, path, *args, &block)
116
+ end
117
+
118
+ #
119
+ # Make a HTTP HEAD request
120
+ #
121
+ # @param [String] path
122
+ # the path to head, relative to {Defaults.endpoint}
123
+ #
124
+ def head(path, *args, &block)
125
+ request(:head, path, *args, &block)
126
+ end
127
+
128
+ #
129
+ # The actually HTTPClient agent.
130
+ #
131
+ # @return [HTTPClient]
132
+ #
133
+ def agent
134
+ @agent ||= begin
135
+ agent = HTTPClient.new(endpoint)
136
+
137
+ agent.agent_name = user_agent
138
+
139
+ # Check if authentication was given
140
+ if username && password
141
+ agent.set_auth(endpoint, username, password)
142
+
143
+ # https://github.com/nahi/httpclient/issues/63#issuecomment-2377919
144
+ agent.www_auth.basic_auth.challenge(endpoint)
145
+ end
146
+
147
+ # Check if proxy settings were given
148
+ if proxy
149
+ agent.proxy = proxy
150
+ end
151
+
152
+ agent
153
+ end
154
+ end
155
+
156
+ #
157
+ # Make an HTTP reequest with the given verb and path.
158
+ #
159
+ # @param [String, Symbol] verb
160
+ # the HTTP verb to use
161
+ # @param [String] path
162
+ # the absolute or relative URL to use, expanded relative to {Defaults.endpoint}
163
+ #
164
+ # @return [Object]
165
+ #
166
+ def request(verb, path, *args, &block)
167
+ url = URI.parse(path)
168
+
169
+ # Don't merge absolute URLs
170
+ unless url.absolute?
171
+ url = URI.parse(File.join(endpoint, path)).to_s
172
+ end
173
+
174
+ # Covert the URL back into a string
175
+ url = url.to_s
176
+
177
+ # Make the actual request
178
+ response = agent.send(verb, url, *args, &block)
179
+
180
+ case response.status.to_i
181
+ when 200..399
182
+ parse_response(response)
183
+ when 400
184
+ raise Error::BadRequest.new(url: url, body: response.body)
185
+ when 401
186
+ raise Error::Unauthorized.new(url: url)
187
+ when 403
188
+ raise Error::Forbidden.new(url: url)
189
+ when 404
190
+ raise Error::NotFound.new(url: url)
191
+ when 405
192
+ raise Error::MethodNotAllowed.new(url: url)
193
+ when 500..600
194
+ raise Error::ConnectionError.new(url: url, body: response.body)
195
+ end
196
+ rescue SocketError, Errno::ECONNREFUSED, EOFError
197
+ raise Error::ConnectionError.new(url: url, body: <<-EOH.gsub(/^ {8}/, ''))
198
+ The server is not currently accepting connections.
199
+ EOH
200
+ end
201
+
202
+
203
+ #
204
+ # Parse the response object and manipulate the result based on the given
205
+ # +Content-Type+ header. For now, this method only parses JSON, but it
206
+ # could be expanded in the future to accept other content types.
207
+ #
208
+ # @param [HTTP::Message] response
209
+ # the response object from the request
210
+ #
211
+ # @return [String, Hash]
212
+ # the parsed response, as an object
213
+ #
214
+ def parse_response(response)
215
+ if response.headers['Content-Type'].include?('json')
216
+ JSON.parse(response.body)
217
+ else
218
+ response.body
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,12 @@
1
+ module Artifactory
2
+ class Collection::Artifact < Collection::Base
3
+ #
4
+ # Create a new artifact collection.
5
+ #
6
+ # @param (see Collection::Base#initialize)
7
+ #
8
+ def initialize(parent, options = {}, &block)
9
+ super(Resource::Artifact, parent, options, &block)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,49 @@
1
+ module Artifactory
2
+ class Collection::Base
3
+ #
4
+ # Create a new collection object (proxy).
5
+ #
6
+ # @param [Class] klass
7
+ # the child class object
8
+ # @param [Object] parent
9
+ # the parent object who created the collection
10
+ # @param [Hash] options
11
+ # the list of options given by the parent
12
+ # @param [Proc] block
13
+ # the block to evaluate for the instance
14
+ #
15
+ def initialize(klass, parent, options = {}, &block)
16
+ @klass = klass
17
+ @parent = parent
18
+ @options = options
19
+ @block = block
20
+ end
21
+
22
+ #
23
+ # Use method missing to delegate methods to the class object or instance
24
+ # object.
25
+ #
26
+ def method_missing(m, *args, &block)
27
+ if klass.respond_to?(m)
28
+ if args.last.is_a?(Hash)
29
+ args.last.merge(options)
30
+ end
31
+
32
+ klass.send(m, *args, &block)
33
+ else
34
+ instance.send(m, *args, &block)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :klass
41
+ attr_reader :parent
42
+ attr_reader :options
43
+ attr_reader :block
44
+
45
+ def instance
46
+ @instance ||= parent.instance_eval(&block)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,73 @@
1
+ module Artifactory
2
+ #
3
+ # A re-usable class containing configuration information for the {Client}. See
4
+ # {Defaults} for a list of default values.
5
+ #
6
+ module Configurable
7
+ class << self
8
+ #
9
+ # The list of configurable keys.
10
+ #
11
+ # @return [Array<Symbol>]
12
+ #
13
+ def keys
14
+ @keys ||= [
15
+ :endpoint,
16
+ :username,
17
+ :password,
18
+ :proxy,
19
+ :user_agent,
20
+ ]
21
+ end
22
+ end
23
+
24
+ #
25
+ # Create one attribute getter and setter for each key.
26
+ #
27
+ Artifactory::Configurable.keys.each do |key|
28
+ attr_accessor key
29
+ end
30
+
31
+ #
32
+ # Set the configuration for this config, using a block.
33
+ #
34
+ # @example Configure the API endpoint
35
+ # Artifactory.configure do |config|
36
+ # config.endpoint = "http://www.my-artifactory-server.com/artifactory"
37
+ # end
38
+ #
39
+ def configure
40
+ yield self
41
+ end
42
+
43
+ #
44
+ # Reset all configuration options to their default values.
45
+ #
46
+ # @example Reset all settings
47
+ # Artifactory.reset!
48
+ #
49
+ # @return [self]
50
+ #
51
+ def reset!
52
+ Artifactory::Configurable.keys.each do |key|
53
+ instance_variable_set(:"@#{key}", Defaults.options[key])
54
+ end
55
+ self
56
+ end
57
+ alias_method :setup, :reset!
58
+
59
+ private
60
+
61
+ #
62
+ # The list of configurable keys, as an options hash.
63
+ #
64
+ # @return [Hash]
65
+ #
66
+ def options
67
+ map = Artifactory::Configurable.keys.map do |key|
68
+ [key, instance_variable_get(:"@#{key}")]
69
+ end
70
+ Hash[map]
71
+ end
72
+ end
73
+ end