zk-jira-ruby 2.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.travis.yml +9 -0
- data/Gemfile +14 -0
- data/Guardfile +14 -0
- data/LICENSE +19 -0
- data/README.md +427 -0
- data/Rakefile +31 -0
- data/example.rb +232 -0
- data/http-basic-example.rb +113 -0
- data/jira-ruby.gemspec +35 -0
- data/lib/jira/base.rb +525 -0
- data/lib/jira/base_factory.rb +46 -0
- data/lib/jira/client.rb +315 -0
- data/lib/jira/has_many_proxy.rb +42 -0
- data/lib/jira/http_client.rb +112 -0
- data/lib/jira/http_error.rb +14 -0
- data/lib/jira/jwt_client.rb +67 -0
- data/lib/jira/oauth_client.rb +114 -0
- data/lib/jira/railtie.rb +10 -0
- data/lib/jira/request_client.rb +31 -0
- data/lib/jira/resource/agile.rb +79 -0
- data/lib/jira/resource/applinks.rb +39 -0
- data/lib/jira/resource/attachment.rb +50 -0
- data/lib/jira/resource/board.rb +91 -0
- data/lib/jira/resource/board_configuration.rb +9 -0
- data/lib/jira/resource/comment.rb +12 -0
- data/lib/jira/resource/component.rb +8 -0
- data/lib/jira/resource/createmeta.rb +44 -0
- data/lib/jira/resource/field.rb +83 -0
- data/lib/jira/resource/filter.rb +15 -0
- data/lib/jira/resource/issue.rb +141 -0
- data/lib/jira/resource/issuelink.rb +20 -0
- data/lib/jira/resource/issuelinktype.rb +14 -0
- data/lib/jira/resource/issuetype.rb +8 -0
- data/lib/jira/resource/priority.rb +8 -0
- data/lib/jira/resource/project.rb +41 -0
- data/lib/jira/resource/rapidview.rb +67 -0
- data/lib/jira/resource/remotelink.rb +26 -0
- data/lib/jira/resource/resolution.rb +8 -0
- data/lib/jira/resource/serverinfo.rb +18 -0
- data/lib/jira/resource/sprint.rb +105 -0
- data/lib/jira/resource/sprint_report.rb +8 -0
- data/lib/jira/resource/status.rb +8 -0
- data/lib/jira/resource/transition.rb +29 -0
- data/lib/jira/resource/user.rb +30 -0
- data/lib/jira/resource/version.rb +66 -0
- data/lib/jira/resource/watcher.rb +35 -0
- data/lib/jira/resource/webhook.rb +37 -0
- data/lib/jira/resource/worklog.rb +14 -0
- data/lib/jira/tasks.rb +0 -0
- data/lib/jira/version.rb +3 -0
- data/lib/jira-ruby.rb +49 -0
- data/lib/tasks/generate.rake +18 -0
- data/spec/integration/attachment_spec.rb +32 -0
- data/spec/integration/comment_spec.rb +52 -0
- data/spec/integration/component_spec.rb +39 -0
- data/spec/integration/field_spec.rb +32 -0
- data/spec/integration/issue_spec.rb +93 -0
- data/spec/integration/issuelinktype_spec.rb +26 -0
- data/spec/integration/issuetype_spec.rb +24 -0
- data/spec/integration/priority_spec.rb +24 -0
- data/spec/integration/project_spec.rb +49 -0
- data/spec/integration/rapidview_spec.rb +74 -0
- data/spec/integration/resolution_spec.rb +26 -0
- data/spec/integration/status_spec.rb +24 -0
- data/spec/integration/transition_spec.rb +49 -0
- data/spec/integration/user_spec.rb +41 -0
- data/spec/integration/version_spec.rb +39 -0
- data/spec/integration/watcher_spec.rb +62 -0
- data/spec/integration/webhook.rb +25 -0
- data/spec/integration/worklog_spec.rb +51 -0
- data/spec/jira/base_factory_spec.rb +45 -0
- data/spec/jira/base_spec.rb +598 -0
- data/spec/jira/client_spec.rb +291 -0
- data/spec/jira/has_many_proxy_spec.rb +46 -0
- data/spec/jira/http_client_spec.rb +328 -0
- data/spec/jira/http_error_spec.rb +24 -0
- data/spec/jira/jwt_uri_builder_spec.rb +59 -0
- data/spec/jira/oauth_client_spec.rb +162 -0
- data/spec/jira/request_client_spec.rb +41 -0
- data/spec/jira/resource/agile_spec.rb +135 -0
- data/spec/jira/resource/attachment_spec.rb +138 -0
- data/spec/jira/resource/board_spec.rb +224 -0
- data/spec/jira/resource/createmeta_spec.rb +258 -0
- data/spec/jira/resource/field_spec.rb +85 -0
- data/spec/jira/resource/filter_spec.rb +97 -0
- data/spec/jira/resource/issue_spec.rb +227 -0
- data/spec/jira/resource/issuelink_spec.rb +14 -0
- data/spec/jira/resource/project_factory_spec.rb +11 -0
- data/spec/jira/resource/project_spec.rb +123 -0
- data/spec/jira/resource/sprint_spec.rb +90 -0
- data/spec/jira/resource/user_factory_spec.rb +31 -0
- data/spec/jira/resource/worklog_spec.rb +22 -0
- data/spec/mock_responses/board/1.json +33 -0
- data/spec/mock_responses/board/1_issues.json +62 -0
- data/spec/mock_responses/component/10000.invalid.put.json +5 -0
- data/spec/mock_responses/component/10000.json +39 -0
- data/spec/mock_responses/component/10000.put.json +39 -0
- data/spec/mock_responses/component.post.json +28 -0
- data/spec/mock_responses/empty_issues.json +8 -0
- data/spec/mock_responses/field/1.json +15 -0
- data/spec/mock_responses/field.json +32 -0
- data/spec/mock_responses/issue/10002/attachments/10000.json +20 -0
- data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
- data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
- data/spec/mock_responses/issue/10002/comment.json +65 -0
- data/spec/mock_responses/issue/10002/comment.post.json +29 -0
- data/spec/mock_responses/issue/10002/transitions.json +49 -0
- data/spec/mock_responses/issue/10002/transitions.post.json +1 -0
- data/spec/mock_responses/issue/10002/watchers.json +13 -0
- data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
- data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
- data/spec/mock_responses/issue/10002/worklog.json +98 -0
- data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
- data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
- data/spec/mock_responses/issue/10002.json +126 -0
- data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
- data/spec/mock_responses/issue.json +1108 -0
- data/spec/mock_responses/issue.post.json +5 -0
- data/spec/mock_responses/issueLinkType/10000.json +7 -0
- data/spec/mock_responses/issueLinkType.json +25 -0
- data/spec/mock_responses/issuetype/5.json +8 -0
- data/spec/mock_responses/issuetype.json +42 -0
- data/spec/mock_responses/jira/rest/webhooks/1.0/webhook/2.json +11 -0
- data/spec/mock_responses/jira/rest/webhooks/1.0/webhook.json +11 -0
- data/spec/mock_responses/priority/1.json +8 -0
- data/spec/mock_responses/priority.json +42 -0
- data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
- data/spec/mock_responses/project/SAMPLEPROJECT.json +84 -0
- data/spec/mock_responses/project.json +12 -0
- data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.full.json +276 -0
- data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.json +111 -0
- data/spec/mock_responses/rapidview/SAMPLEPROJECT.json +6 -0
- data/spec/mock_responses/rapidview.json +10 -0
- data/spec/mock_responses/resolution/1.json +7 -0
- data/spec/mock_responses/resolution.json +15 -0
- data/spec/mock_responses/sprint/1_issues.json +125 -0
- data/spec/mock_responses/status/1.json +7 -0
- data/spec/mock_responses/status.json +37 -0
- data/spec/mock_responses/user_username=admin.json +17 -0
- data/spec/mock_responses/version/10000.invalid.put.json +5 -0
- data/spec/mock_responses/version/10000.json +11 -0
- data/spec/mock_responses/version/10000.put.json +7 -0
- data/spec/mock_responses/version.post.json +7 -0
- data/spec/mock_responses/webhook/webhook.json +11 -0
- data/spec/mock_responses/webhook.json +11 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/clients_helper.rb +16 -0
- data/spec/support/matchers/have_attributes.rb +11 -0
- data/spec/support/matchers/have_many.rb +9 -0
- data/spec/support/matchers/have_one.rb +5 -0
- data/spec/support/shared_examples/integration.rb +177 -0
- metadata +491 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
module JIRA
|
2
|
+
# This is the base class for all the JIRA resource factory instances.
|
3
|
+
class BaseFactory
|
4
|
+
attr_reader :client
|
5
|
+
|
6
|
+
def initialize(client)
|
7
|
+
@client = client
|
8
|
+
end
|
9
|
+
|
10
|
+
# Return the name of the class which this factory generates, i.e.
|
11
|
+
# JIRA::Resource::FooFactory creates JIRA::Resource::Foo instances.
|
12
|
+
def target_class
|
13
|
+
# Need to do a little bit of work here as Module.const_get doesn't work
|
14
|
+
# with nested class names, i.e. JIRA::Resource::Foo.
|
15
|
+
#
|
16
|
+
# So create a method chain from the class components. This code will
|
17
|
+
# unroll to:
|
18
|
+
# Module.const_get('JIRA').const_get('Resource').const_get('Foo')
|
19
|
+
#
|
20
|
+
target_class_name = self.class.name.sub(/Factory$/, '')
|
21
|
+
class_components = target_class_name.split('::')
|
22
|
+
|
23
|
+
class_components.inject(Module) do |mod, const_name|
|
24
|
+
mod.const_get(const_name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.delegate_to_target_class(*method_names)
|
29
|
+
method_names.each do |method_name|
|
30
|
+
define_method method_name do |*args|
|
31
|
+
target_class.send(method_name, @client, *args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# The principle purpose of this class is to delegate methods to the corresponding
|
37
|
+
# non-factory class and automatically prepend the client argument to the argument
|
38
|
+
# list.
|
39
|
+
delegate_to_target_class :all, :find, :collection_path, :singular_path, :jql, :get_backlog_issues, :get_board_issues, :get_sprints, :get_sprint_issues, :get_projects, :get_projects_full
|
40
|
+
|
41
|
+
# This method needs special handling as it has a default argument value
|
42
|
+
def build(attrs = {})
|
43
|
+
target_class.build(@client, attrs)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/jira/client.rb
ADDED
@@ -0,0 +1,315 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module JIRA
|
6
|
+
# This class is the main access point for all JIRA::Resource instances.
|
7
|
+
#
|
8
|
+
# The client must be initialized with an options hash containing
|
9
|
+
# configuration options. The available options are:
|
10
|
+
#
|
11
|
+
# :site => 'http://localhost:2990',
|
12
|
+
# :context_path => '/jira',
|
13
|
+
# :signature_method => 'RSA-SHA1',
|
14
|
+
# :request_token_path => "/plugins/servlet/oauth/request-token",
|
15
|
+
# :authorize_path => "/plugins/servlet/oauth/authorize",
|
16
|
+
# :access_token_path => "/plugins/servlet/oauth/access-token",
|
17
|
+
# :private_key => nil,
|
18
|
+
# :private_key_file => "rsakey.pem",
|
19
|
+
# :rest_base_path => "/rest/api/2",
|
20
|
+
# :consumer_key => nil,
|
21
|
+
# :consumer_secret => nil,
|
22
|
+
# :ssl_verify_mode => OpenSSL::SSL::VERIFY_PEER,
|
23
|
+
# :ssl_version => nil,
|
24
|
+
# :use_ssl => true,
|
25
|
+
# :username => nil,
|
26
|
+
# :password => nil,
|
27
|
+
# :auth_type => :oauth,
|
28
|
+
# :proxy_address => nil,
|
29
|
+
# :proxy_port => nil,
|
30
|
+
# :proxy_username => nil,
|
31
|
+
# :proxy_password => nil,
|
32
|
+
# :use_cookies => nil,
|
33
|
+
# :additional_cookies => nil,
|
34
|
+
# :default_headers => {},
|
35
|
+
# :use_client_cert => false,
|
36
|
+
# :read_timeout => nil,
|
37
|
+
# :http_debug => false,
|
38
|
+
# :shared_secret => nil,
|
39
|
+
# :cert_path => nil,
|
40
|
+
# :key_path => nil,
|
41
|
+
# :ssl_client_cert => nil,
|
42
|
+
# :ssl_client_key => nil
|
43
|
+
#
|
44
|
+
# See the JIRA::Base class methods for all of the available methods on these accessor
|
45
|
+
# objects.
|
46
|
+
|
47
|
+
class Client
|
48
|
+
extend Forwardable
|
49
|
+
|
50
|
+
# The OAuth::Consumer instance returned by the OauthClient
|
51
|
+
#
|
52
|
+
# The authenticated client instance returned by the respective client type
|
53
|
+
# (Oauth, Basic)
|
54
|
+
attr_accessor :consumer, :request_client, :http_debug, :cache
|
55
|
+
|
56
|
+
# The configuration options for this client instance
|
57
|
+
attr_reader :options
|
58
|
+
|
59
|
+
def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token, :access_token, :authenticated?
|
60
|
+
|
61
|
+
DEFINED_OPTIONS = [
|
62
|
+
:site,
|
63
|
+
:context_path,
|
64
|
+
:signature_method,
|
65
|
+
:request_token_path,
|
66
|
+
:authorize_path,
|
67
|
+
:access_token_path,
|
68
|
+
:private_key,
|
69
|
+
:private_key_file,
|
70
|
+
:rest_base_path,
|
71
|
+
:consumer_key,
|
72
|
+
:consumer_secret,
|
73
|
+
:ssl_verify_mode,
|
74
|
+
:ssl_version,
|
75
|
+
:use_ssl,
|
76
|
+
:username,
|
77
|
+
:password,
|
78
|
+
:auth_type,
|
79
|
+
:proxy_address,
|
80
|
+
:proxy_port,
|
81
|
+
:proxy_username,
|
82
|
+
:proxy_password,
|
83
|
+
:use_cookies,
|
84
|
+
:additional_cookies,
|
85
|
+
:default_headers,
|
86
|
+
:use_client_cert,
|
87
|
+
:read_timeout,
|
88
|
+
:http_debug,
|
89
|
+
:issuer,
|
90
|
+
:base_url,
|
91
|
+
:shared_secret,
|
92
|
+
:cert_path,
|
93
|
+
:key_path,
|
94
|
+
:ssl_client_cert,
|
95
|
+
:ssl_client_key
|
96
|
+
].freeze
|
97
|
+
|
98
|
+
DEFAULT_OPTIONS = {
|
99
|
+
site: 'http://localhost:2990',
|
100
|
+
context_path: '/jira',
|
101
|
+
rest_base_path: '/rest/api/2',
|
102
|
+
ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
|
103
|
+
use_ssl: true,
|
104
|
+
use_client_cert: false,
|
105
|
+
auth_type: :oauth,
|
106
|
+
http_debug: false,
|
107
|
+
default_headers: {}
|
108
|
+
}.freeze
|
109
|
+
|
110
|
+
def initialize(options = {})
|
111
|
+
options = DEFAULT_OPTIONS.merge(options)
|
112
|
+
@options = options
|
113
|
+
@options[:rest_base_path] = @options[:context_path] + @options[:rest_base_path]
|
114
|
+
|
115
|
+
unknown_options = options.keys.reject { |o| DEFINED_OPTIONS.include?(o) }
|
116
|
+
raise ArgumentError, "Unknown option(s) given: #{unknown_options}" unless unknown_options.empty?
|
117
|
+
|
118
|
+
if options[:use_client_cert]
|
119
|
+
@options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(@options[:cert_path])) if @options[:cert_path]
|
120
|
+
@options[:ssl_client_key] = OpenSSL::PKey::RSA.new(File.read(@options[:key_path])) if @options[:key_path]
|
121
|
+
|
122
|
+
raise ArgumentError, 'Options: :cert_path or :ssl_client_cert must be set when :use_client_cert is true' unless @options[:ssl_client_cert]
|
123
|
+
raise ArgumentError, 'Options: :key_path or :ssl_client_key must be set when :use_client_cert is true' unless @options[:ssl_client_key]
|
124
|
+
end
|
125
|
+
|
126
|
+
case options[:auth_type]
|
127
|
+
when :oauth, :oauth_2legged
|
128
|
+
@request_client = OauthClient.new(@options)
|
129
|
+
@consumer = @request_client.consumer
|
130
|
+
when :jwt
|
131
|
+
@request_client = JwtClient.new(@options)
|
132
|
+
when :basic
|
133
|
+
@request_client = HttpClient.new(@options)
|
134
|
+
when :cookie
|
135
|
+
raise ArgumentError, 'Options: :use_cookies must be true for :cookie authorization type' if @options.key?(:use_cookies) && !@options[:use_cookies]
|
136
|
+
@options[:use_cookies] = true
|
137
|
+
@request_client = HttpClient.new(@options)
|
138
|
+
@request_client.make_cookie_auth_request
|
139
|
+
@options.delete(:username)
|
140
|
+
@options.delete(:password)
|
141
|
+
else
|
142
|
+
raise ArgumentError, 'Options: ":auth_type" must be ":oauth",":oauth_2legged", ":cookie" or ":basic"'
|
143
|
+
end
|
144
|
+
|
145
|
+
@http_debug = @options[:http_debug]
|
146
|
+
|
147
|
+
@options.freeze
|
148
|
+
|
149
|
+
@cache = OpenStruct.new
|
150
|
+
end
|
151
|
+
|
152
|
+
def Project # :nodoc:
|
153
|
+
JIRA::Resource::ProjectFactory.new(self)
|
154
|
+
end
|
155
|
+
|
156
|
+
def Issue # :nodoc:
|
157
|
+
JIRA::Resource::IssueFactory.new(self)
|
158
|
+
end
|
159
|
+
|
160
|
+
def Filter # :nodoc:
|
161
|
+
JIRA::Resource::FilterFactory.new(self)
|
162
|
+
end
|
163
|
+
|
164
|
+
def Component # :nodoc:
|
165
|
+
JIRA::Resource::ComponentFactory.new(self)
|
166
|
+
end
|
167
|
+
|
168
|
+
def User # :nodoc:
|
169
|
+
JIRA::Resource::UserFactory.new(self)
|
170
|
+
end
|
171
|
+
|
172
|
+
def Issuetype # :nodoc:
|
173
|
+
JIRA::Resource::IssuetypeFactory.new(self)
|
174
|
+
end
|
175
|
+
|
176
|
+
def Priority # :nodoc:
|
177
|
+
JIRA::Resource::PriorityFactory.new(self)
|
178
|
+
end
|
179
|
+
|
180
|
+
def Status # :nodoc:
|
181
|
+
JIRA::Resource::StatusFactory.new(self)
|
182
|
+
end
|
183
|
+
|
184
|
+
def Resolution # :nodoc:
|
185
|
+
JIRA::Resource::ResolutionFactory.new(self)
|
186
|
+
end
|
187
|
+
|
188
|
+
def Comment # :nodoc:
|
189
|
+
JIRA::Resource::CommentFactory.new(self)
|
190
|
+
end
|
191
|
+
|
192
|
+
def Attachment # :nodoc:
|
193
|
+
JIRA::Resource::AttachmentFactory.new(self)
|
194
|
+
end
|
195
|
+
|
196
|
+
def Worklog # :nodoc:
|
197
|
+
JIRA::Resource::WorklogFactory.new(self)
|
198
|
+
end
|
199
|
+
|
200
|
+
def Version # :nodoc:
|
201
|
+
JIRA::Resource::VersionFactory.new(self)
|
202
|
+
end
|
203
|
+
|
204
|
+
def Transition # :nodoc:
|
205
|
+
JIRA::Resource::TransitionFactory.new(self)
|
206
|
+
end
|
207
|
+
|
208
|
+
def Field # :nodoc:
|
209
|
+
JIRA::Resource::FieldFactory.new(self)
|
210
|
+
end
|
211
|
+
|
212
|
+
def Board
|
213
|
+
JIRA::Resource::BoardFactory.new(self)
|
214
|
+
end
|
215
|
+
|
216
|
+
def BoardConfiguration
|
217
|
+
JIRA::Resource::BoardConfigurationFactory.new(self)
|
218
|
+
end
|
219
|
+
|
220
|
+
def RapidView
|
221
|
+
JIRA::Resource::RapidViewFactory.new(self)
|
222
|
+
end
|
223
|
+
|
224
|
+
def Sprint
|
225
|
+
JIRA::Resource::SprintFactory.new(self)
|
226
|
+
end
|
227
|
+
|
228
|
+
def SprintReport
|
229
|
+
JIRA::Resource::SprintReportFactory.new(self)
|
230
|
+
end
|
231
|
+
|
232
|
+
def ServerInfo
|
233
|
+
JIRA::Resource::ServerInfoFactory.new(self)
|
234
|
+
end
|
235
|
+
|
236
|
+
def Createmeta
|
237
|
+
JIRA::Resource::CreatemetaFactory.new(self)
|
238
|
+
end
|
239
|
+
|
240
|
+
def ApplicationLink
|
241
|
+
JIRA::Resource::ApplicationLinkFactory.new(self)
|
242
|
+
end
|
243
|
+
|
244
|
+
def Watcher
|
245
|
+
JIRA::Resource::WatcherFactory.new(self)
|
246
|
+
end
|
247
|
+
|
248
|
+
def Webhook
|
249
|
+
JIRA::Resource::WebhookFactory.new(self)
|
250
|
+
end
|
251
|
+
|
252
|
+
def Issuelink
|
253
|
+
JIRA::Resource::IssuelinkFactory.new(self)
|
254
|
+
end
|
255
|
+
|
256
|
+
def Issuelinktype
|
257
|
+
JIRA::Resource::IssuelinktypeFactory.new(self)
|
258
|
+
end
|
259
|
+
|
260
|
+
def Remotelink
|
261
|
+
JIRA::Resource::RemotelinkFactory.new(self)
|
262
|
+
end
|
263
|
+
|
264
|
+
def Agile
|
265
|
+
JIRA::Resource::AgileFactory.new(self)
|
266
|
+
end
|
267
|
+
|
268
|
+
# HTTP methods without a body
|
269
|
+
def delete(path, headers = {})
|
270
|
+
request(:delete, path, nil, merge_default_headers(headers))
|
271
|
+
end
|
272
|
+
|
273
|
+
def get(path, headers = {})
|
274
|
+
request(:get, path, nil, merge_default_headers(headers))
|
275
|
+
end
|
276
|
+
|
277
|
+
def head(path, headers = {})
|
278
|
+
request(:head, path, nil, merge_default_headers(headers))
|
279
|
+
end
|
280
|
+
|
281
|
+
# HTTP methods with a body
|
282
|
+
def post(path, body = '', headers = {})
|
283
|
+
headers = { 'Content-Type' => 'application/json' }.merge(headers)
|
284
|
+
request(:post, path, body, merge_default_headers(headers))
|
285
|
+
end
|
286
|
+
|
287
|
+
def post_multipart(path, file, headers = {})
|
288
|
+
puts "post multipart: #{path} - [#{file}]" if @http_debug
|
289
|
+
@request_client.request_multipart(path, file, headers)
|
290
|
+
end
|
291
|
+
|
292
|
+
def put(path, body = '', headers = {})
|
293
|
+
headers = { 'Content-Type' => 'application/json' }.merge(headers)
|
294
|
+
request(:put, path, body, merge_default_headers(headers))
|
295
|
+
end
|
296
|
+
|
297
|
+
# Sends the specified HTTP request to the REST API through the
|
298
|
+
# appropriate method (oauth, basic).
|
299
|
+
def request(http_method, path, body = '', headers = {})
|
300
|
+
puts "#{http_method}: #{path} - [#{body}]" if @http_debug
|
301
|
+
@request_client.request(http_method, path, body, headers)
|
302
|
+
end
|
303
|
+
|
304
|
+
# Stops sensitive client information from being displayed in logs
|
305
|
+
def inspect
|
306
|
+
"#<JIRA::Client:#{object_id}>"
|
307
|
+
end
|
308
|
+
|
309
|
+
protected
|
310
|
+
|
311
|
+
def merge_default_headers(headers)
|
312
|
+
{ 'Accept' => 'application/json' }.merge(@options[:default_headers]).merge(headers)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#
|
2
|
+
# Whenever a collection from a has_many relationship is accessed, an instance
|
3
|
+
# of this class is returned. This instance wraps the Array of instances in
|
4
|
+
# the collection with an extra build method, which allows new instances to be
|
5
|
+
# built on the collection with the correct properties.
|
6
|
+
#
|
7
|
+
# In practice, instances of this class behave exactly like an Array.
|
8
|
+
#
|
9
|
+
class JIRA::HasManyProxy
|
10
|
+
attr_reader :target_class, :parent
|
11
|
+
attr_accessor :collection
|
12
|
+
|
13
|
+
def initialize(parent, target_class, collection = [])
|
14
|
+
@parent = parent
|
15
|
+
@target_class = target_class
|
16
|
+
@collection = collection
|
17
|
+
end
|
18
|
+
|
19
|
+
# Builds an instance of this class with the correct parent.
|
20
|
+
# For example, issue.comments.build(attrs) will initialize a
|
21
|
+
# comment as follows:
|
22
|
+
#
|
23
|
+
# JIRA::Resource::Comment.new(issue.client,
|
24
|
+
# :attrs => attrs,
|
25
|
+
# :issue => issue)
|
26
|
+
def build(attrs = {})
|
27
|
+
resource = target_class.new(parent.client, :attrs => attrs, parent.to_sym => parent)
|
28
|
+
collection << resource
|
29
|
+
resource
|
30
|
+
end
|
31
|
+
|
32
|
+
# Forces an HTTP request to fetch all instances of the target class that
|
33
|
+
# are associated with the parent
|
34
|
+
def all
|
35
|
+
target_class.all(parent.client, parent.to_sym => parent)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Delegate any missing methods to the collection that this proxy wraps
|
39
|
+
def method_missing(method_name, *args, &block)
|
40
|
+
collection.send(method_name, *args, &block)
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/https'
|
3
|
+
require 'cgi/cookie'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module JIRA
|
7
|
+
class HttpClient < RequestClient
|
8
|
+
DEFAULT_OPTIONS = {
|
9
|
+
username: nil,
|
10
|
+
password: nil
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
attr_reader :options
|
14
|
+
|
15
|
+
def initialize(options)
|
16
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
17
|
+
@cookies = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def make_cookie_auth_request
|
21
|
+
body = { username: @options[:username].to_s, password: @options[:password].to_s }.to_json
|
22
|
+
@options.delete(:username)
|
23
|
+
@options.delete(:password)
|
24
|
+
make_request(:post, @options[:context_path] + '/rest/auth/1/session', body, 'Content-Type' => 'application/json')
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_request(http_method, url, body = '', headers = {})
|
28
|
+
# When a proxy is enabled, Net::HTTP expects that the request path omits the domain name
|
29
|
+
path = request_path(url)
|
30
|
+
request = Net::HTTP.const_get(http_method.to_s.capitalize).new(path, headers)
|
31
|
+
request.body = body unless body.nil?
|
32
|
+
|
33
|
+
execute_request(request)
|
34
|
+
end
|
35
|
+
|
36
|
+
def make_multipart_request(url, body, headers = {})
|
37
|
+
path = request_path(url)
|
38
|
+
request = Net::HTTP::Post::Multipart.new(path, body, headers)
|
39
|
+
|
40
|
+
execute_request(request)
|
41
|
+
end
|
42
|
+
|
43
|
+
def basic_auth_http_conn
|
44
|
+
http_conn(uri)
|
45
|
+
end
|
46
|
+
|
47
|
+
def http_conn(uri)
|
48
|
+
if @options[:proxy_address]
|
49
|
+
http_class = Net::HTTP::Proxy(@options[:proxy_address], @options[:proxy_port] || 80, @options[:proxy_username], @options[:proxy_password])
|
50
|
+
else
|
51
|
+
http_class = Net::HTTP
|
52
|
+
end
|
53
|
+
http_conn = http_class.new(uri.host, uri.port)
|
54
|
+
http_conn.use_ssl = @options[:use_ssl]
|
55
|
+
if @options[:use_client_cert]
|
56
|
+
http_conn.cert = @options[:ssl_client_cert]
|
57
|
+
http_conn.key = @options[:ssl_client_key]
|
58
|
+
end
|
59
|
+
http_conn.verify_mode = @options[:ssl_verify_mode]
|
60
|
+
http_conn.ssl_version = @options[:ssl_version] if @options[:ssl_version]
|
61
|
+
http_conn.read_timeout = @options[:read_timeout]
|
62
|
+
http_conn
|
63
|
+
end
|
64
|
+
|
65
|
+
def uri
|
66
|
+
URI.parse(@options[:site])
|
67
|
+
end
|
68
|
+
|
69
|
+
def authenticated?
|
70
|
+
@authenticated
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def execute_request(request)
|
76
|
+
add_cookies(request) if options[:use_cookies]
|
77
|
+
request.basic_auth(@options[:username], @options[:password]) if @options[:username] && @options[:password]
|
78
|
+
|
79
|
+
response = basic_auth_http_conn.request(request)
|
80
|
+
@authenticated = response.is_a? Net::HTTPOK
|
81
|
+
store_cookies(response) if options[:use_cookies]
|
82
|
+
|
83
|
+
response
|
84
|
+
end
|
85
|
+
|
86
|
+
def request_path(url)
|
87
|
+
parsed_uri = URI(url)
|
88
|
+
|
89
|
+
return url unless parsed_uri.is_a?(URI::HTTP)
|
90
|
+
|
91
|
+
parsed_uri.request_uri
|
92
|
+
end
|
93
|
+
|
94
|
+
def store_cookies(response)
|
95
|
+
cookies = response.get_fields('set-cookie')
|
96
|
+
if cookies
|
97
|
+
cookies.each do |cookie|
|
98
|
+
data = CGI::Cookie.parse(cookie)
|
99
|
+
data.delete('Path')
|
100
|
+
@cookies.merge!(data)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_cookies(request)
|
106
|
+
cookie_array = @cookies.values.map { |cookie| "#{cookie.name}=#{cookie.value[0]}" }
|
107
|
+
cookie_array += Array(@options[:additional_cookies]) if @options.key?(:additional_cookies)
|
108
|
+
request.add_field('Cookie', cookie_array.join('; ')) if cookie_array.any?
|
109
|
+
request
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|