jira-ruby 2.1.3
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.
- 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 +224 -0
- data/http-basic-example.rb +113 -0
- data/jira-ruby.gemspec +35 -0
- data/lib/jira-ruby.rb +49 -0
- data/lib/jira/base.rb +525 -0
- data/lib/jira/base_factory.rb +46 -0
- data/lib/jira/client.rb +308 -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 +8 -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/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.post.json +28 -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/empty_issues.json +8 -0
- data/spec/mock_responses/field.json +32 -0
- data/spec/mock_responses/field/1.json +15 -0
- data/spec/mock_responses/issue.json +1108 -0
- data/spec/mock_responses/issue.post.json +5 -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/10002/attachments/10000.json +20 -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/comment/10000.json +29 -0
- data/spec/mock_responses/issue/10002/comment/10000.put.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.json +98 -0
- data/spec/mock_responses/issue/10002/worklog.post.json +30 -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/issueLinkType.json +25 -0
- data/spec/mock_responses/issueLinkType/10000.json +7 -0
- data/spec/mock_responses/issuetype.json +42 -0
- data/spec/mock_responses/issuetype/5.json +8 -0
- data/spec/mock_responses/jira/rest/webhooks/1.0/webhook.json +11 -0
- data/spec/mock_responses/jira/rest/webhooks/1.0/webhook/2.json +11 -0
- data/spec/mock_responses/priority.json +42 -0
- data/spec/mock_responses/priority/1.json +8 -0
- data/spec/mock_responses/project.json +12 -0
- data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
- data/spec/mock_responses/project/SAMPLEPROJECT.json +84 -0
- data/spec/mock_responses/rapidview.json +10 -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/resolution.json +15 -0
- data/spec/mock_responses/resolution/1.json +7 -0
- data/spec/mock_responses/sprint/1_issues.json +125 -0
- data/spec/mock_responses/status.json +37 -0
- data/spec/mock_responses/status/1.json +7 -0
- data/spec/mock_responses/user_username=admin.json +17 -0
- data/spec/mock_responses/version.post.json +7 -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/webhook.json +11 -0
- data/spec/mock_responses/webhook/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,308 @@
|
|
|
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
|
+
# :additional_cookies => nil,
|
|
33
|
+
# :default_headers => {},
|
|
34
|
+
# :use_client_cert => false,
|
|
35
|
+
# :read_timeout => nil,
|
|
36
|
+
# :http_debug => false,
|
|
37
|
+
# :shared_secret => nil,
|
|
38
|
+
# :cert_path => nil,
|
|
39
|
+
# :key_path => nil,
|
|
40
|
+
# :ssl_client_cert => nil,
|
|
41
|
+
# :ssl_client_key => nil
|
|
42
|
+
#
|
|
43
|
+
# See the JIRA::Base class methods for all of the available methods on these accessor
|
|
44
|
+
# objects.
|
|
45
|
+
|
|
46
|
+
class Client
|
|
47
|
+
extend Forwardable
|
|
48
|
+
|
|
49
|
+
# The OAuth::Consumer instance returned by the OauthClient
|
|
50
|
+
#
|
|
51
|
+
# The authenticated client instance returned by the respective client type
|
|
52
|
+
# (Oauth, Basic)
|
|
53
|
+
attr_accessor :consumer, :request_client, :http_debug, :cache
|
|
54
|
+
|
|
55
|
+
# The configuration options for this client instance
|
|
56
|
+
attr_reader :options
|
|
57
|
+
|
|
58
|
+
def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token, :access_token, :authenticated?
|
|
59
|
+
|
|
60
|
+
DEFINED_OPTIONS = [
|
|
61
|
+
:site,
|
|
62
|
+
:context_path,
|
|
63
|
+
:signature_method,
|
|
64
|
+
:request_token_path,
|
|
65
|
+
:authorize_path,
|
|
66
|
+
:access_token_path,
|
|
67
|
+
:private_key,
|
|
68
|
+
:private_key_file,
|
|
69
|
+
:rest_base_path,
|
|
70
|
+
:consumer_key,
|
|
71
|
+
:consumer_secret,
|
|
72
|
+
:ssl_verify_mode,
|
|
73
|
+
:ssl_version,
|
|
74
|
+
:use_ssl,
|
|
75
|
+
:username,
|
|
76
|
+
:password,
|
|
77
|
+
:auth_type,
|
|
78
|
+
:proxy_address,
|
|
79
|
+
:proxy_port,
|
|
80
|
+
:proxy_username,
|
|
81
|
+
:proxy_password,
|
|
82
|
+
:additional_cookies,
|
|
83
|
+
:default_headers,
|
|
84
|
+
:use_client_cert,
|
|
85
|
+
:read_timeout,
|
|
86
|
+
:http_debug,
|
|
87
|
+
:issuer,
|
|
88
|
+
:base_url,
|
|
89
|
+
:shared_secret,
|
|
90
|
+
:cert_path,
|
|
91
|
+
:key_path,
|
|
92
|
+
:ssl_client_cert,
|
|
93
|
+
:ssl_client_key
|
|
94
|
+
].freeze
|
|
95
|
+
|
|
96
|
+
DEFAULT_OPTIONS = {
|
|
97
|
+
site: 'http://localhost:2990',
|
|
98
|
+
context_path: '/jira',
|
|
99
|
+
rest_base_path: '/rest/api/2',
|
|
100
|
+
ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
|
|
101
|
+
use_ssl: true,
|
|
102
|
+
use_client_cert: false,
|
|
103
|
+
auth_type: :oauth,
|
|
104
|
+
http_debug: false,
|
|
105
|
+
default_headers: {}
|
|
106
|
+
}.freeze
|
|
107
|
+
|
|
108
|
+
def initialize(options = {})
|
|
109
|
+
options = DEFAULT_OPTIONS.merge(options)
|
|
110
|
+
@options = options
|
|
111
|
+
@options[:rest_base_path] = @options[:context_path] + @options[:rest_base_path]
|
|
112
|
+
|
|
113
|
+
unknown_options = options.keys.reject { |o| DEFINED_OPTIONS.include?(o) }
|
|
114
|
+
raise ArgumentError, "Unknown option(s) given: #{unknown_options}" unless unknown_options.empty?
|
|
115
|
+
|
|
116
|
+
if options[:use_client_cert]
|
|
117
|
+
@options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(@options[:cert_path])) if @options[:cert_path]
|
|
118
|
+
@options[:ssl_client_key] = OpenSSL::PKey::RSA.new(File.read(@options[:key_path])) if @options[:key_path]
|
|
119
|
+
|
|
120
|
+
raise ArgumentError, 'Options: :cert_path or :ssl_client_cert must be set when :use_client_cert is true' unless @options[:ssl_client_cert]
|
|
121
|
+
raise ArgumentError, 'Options: :key_path or :ssl_client_key must be set when :use_client_cert is true' unless @options[:ssl_client_key]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
case options[:auth_type]
|
|
125
|
+
when :oauth, :oauth_2legged
|
|
126
|
+
@request_client = OauthClient.new(@options)
|
|
127
|
+
@consumer = @request_client.consumer
|
|
128
|
+
when :jwt
|
|
129
|
+
@request_client = JwtClient.new(@options)
|
|
130
|
+
when :basic
|
|
131
|
+
@request_client = HttpClient.new(@options)
|
|
132
|
+
when :cookie
|
|
133
|
+
raise ArgumentError, 'Options: :use_cookies must be true for :cookie authorization type' if @options.key?(:use_cookies) && !@options[:use_cookies]
|
|
134
|
+
@options[:use_cookies] = true
|
|
135
|
+
@request_client = HttpClient.new(@options)
|
|
136
|
+
@request_client.make_cookie_auth_request
|
|
137
|
+
@options.delete(:username)
|
|
138
|
+
@options.delete(:password)
|
|
139
|
+
else
|
|
140
|
+
raise ArgumentError, 'Options: ":auth_type" must be ":oauth",":oauth_2legged", ":cookie" or ":basic"'
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
@http_debug = @options[:http_debug]
|
|
144
|
+
|
|
145
|
+
@options.freeze
|
|
146
|
+
|
|
147
|
+
@cache = OpenStruct.new
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def Project # :nodoc:
|
|
151
|
+
JIRA::Resource::ProjectFactory.new(self)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def Issue # :nodoc:
|
|
155
|
+
JIRA::Resource::IssueFactory.new(self)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def Filter # :nodoc:
|
|
159
|
+
JIRA::Resource::FilterFactory.new(self)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def Component # :nodoc:
|
|
163
|
+
JIRA::Resource::ComponentFactory.new(self)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def User # :nodoc:
|
|
167
|
+
JIRA::Resource::UserFactory.new(self)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def Issuetype # :nodoc:
|
|
171
|
+
JIRA::Resource::IssuetypeFactory.new(self)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def Priority # :nodoc:
|
|
175
|
+
JIRA::Resource::PriorityFactory.new(self)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def Status # :nodoc:
|
|
179
|
+
JIRA::Resource::StatusFactory.new(self)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def Resolution # :nodoc:
|
|
183
|
+
JIRA::Resource::ResolutionFactory.new(self)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def Comment # :nodoc:
|
|
187
|
+
JIRA::Resource::CommentFactory.new(self)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def Attachment # :nodoc:
|
|
191
|
+
JIRA::Resource::AttachmentFactory.new(self)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def Worklog # :nodoc:
|
|
195
|
+
JIRA::Resource::WorklogFactory.new(self)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def Version # :nodoc:
|
|
199
|
+
JIRA::Resource::VersionFactory.new(self)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def Transition # :nodoc:
|
|
203
|
+
JIRA::Resource::TransitionFactory.new(self)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def Field # :nodoc:
|
|
207
|
+
JIRA::Resource::FieldFactory.new(self)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def Board
|
|
211
|
+
JIRA::Resource::BoardFactory.new(self)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def BoardConfiguration
|
|
215
|
+
JIRA::Resource::BoardConfigurationFactory.new(self)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def RapidView
|
|
219
|
+
JIRA::Resource::RapidViewFactory.new(self)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def Sprint
|
|
223
|
+
JIRA::Resource::SprintFactory.new(self)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def SprintReport
|
|
227
|
+
JIRA::Resource::SprintReportFactory.new(self)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def ServerInfo
|
|
231
|
+
JIRA::Resource::ServerInfoFactory.new(self)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def Createmeta
|
|
235
|
+
JIRA::Resource::CreatemetaFactory.new(self)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def ApplicationLink
|
|
239
|
+
JIRA::Resource::ApplicationLinkFactory.new(self)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def Watcher
|
|
243
|
+
JIRA::Resource::WatcherFactory.new(self)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def Webhook
|
|
247
|
+
JIRA::Resource::WebhookFactory.new(self)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def Issuelink
|
|
251
|
+
JIRA::Resource::IssuelinkFactory.new(self)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def Issuelinktype
|
|
255
|
+
JIRA::Resource::IssuelinktypeFactory.new(self)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def Remotelink
|
|
259
|
+
JIRA::Resource::RemotelinkFactory.new(self)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def Agile
|
|
263
|
+
JIRA::Resource::AgileFactory.new(self)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# HTTP methods without a body
|
|
267
|
+
def delete(path, headers = {})
|
|
268
|
+
request(:delete, path, nil, merge_default_headers(headers))
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def get(path, headers = {})
|
|
272
|
+
request(:get, path, nil, merge_default_headers(headers))
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def head(path, headers = {})
|
|
276
|
+
request(:head, path, nil, merge_default_headers(headers))
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# HTTP methods with a body
|
|
280
|
+
def post(path, body = '', headers = {})
|
|
281
|
+
headers = { 'Content-Type' => 'application/json' }.merge(headers)
|
|
282
|
+
request(:post, path, body, merge_default_headers(headers))
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def post_multipart(path, file, headers = {})
|
|
286
|
+
puts "post multipart: #{path} - [#{file}]" if @http_debug
|
|
287
|
+
@request_client.request_multipart(path, file, headers)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def put(path, body = '', headers = {})
|
|
291
|
+
headers = { 'Content-Type' => 'application/json' }.merge(headers)
|
|
292
|
+
request(:put, path, body, merge_default_headers(headers))
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Sends the specified HTTP request to the REST API through the
|
|
296
|
+
# appropriate method (oauth, basic).
|
|
297
|
+
def request(http_method, path, body = '', headers = {})
|
|
298
|
+
puts "#{http_method}: #{path} - [#{body}]" if @http_debug
|
|
299
|
+
@request_client.request(http_method, path, body, headers)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
protected
|
|
303
|
+
|
|
304
|
+
def merge_default_headers(headers)
|
|
305
|
+
{ 'Accept' => 'application/json' }.merge(@options[:default_headers]).merge(headers)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
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
|