jira-ruby 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
6
+ .rspec
7
+ *.pem
8
+ .DS_STORE
9
+ doc
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in jira_api.gemspec
4
+ gemspec
5
+ gem 'rspec'
data/README.markdown ADDED
@@ -0,0 +1,81 @@
1
+ Jira 5 API Gem
2
+ ==============
3
+
4
+ Links to JIRA REST API documentation
5
+ ------------------------------------
6
+ * [Overview](https://developer.atlassian.com/display/JIRADEV/JIRA+REST+APIs)
7
+ * [Reference](http://docs.atlassian.com/jira/REST/5.0-rc1/)
8
+
9
+
10
+ Setting up the JIRA SDK
11
+ -----------------------
12
+ On Mac OS,
13
+
14
+ brew install atlassian-plugin-sdk
15
+
16
+ Otherwise:
17
+
18
+ * Download the SDK from https://developer.atlassian.com/ (You will need
19
+ an Atlassian login for this)
20
+ * Unpack the dowloaded archive
21
+ * From within the archive directory, run:
22
+
23
+ ./bin/atlas-run-standalone --product jira --version 5.0-rc2
24
+
25
+ Once this is running, you should be able to connect to
26
+ [http://localhost:2990/] and login to the JIRA admin system using `admin:admin`
27
+
28
+ You'll need to create a dummy project and probably some issues to test using
29
+ this library.
30
+
31
+ Configuring JIRA to use OAuth
32
+ -----------------------------
33
+ From the Jira API tutorial
34
+
35
+ > The first step is to register a new consumer in JIRA. This is done through
36
+ > the Application Links administration screens in JIRA. Create a new
37
+ > Application Link.
38
+ > [Administration/Plugins/Application Links](http://localhost:2990/jira/plugins/servlet/applinks/listApplicationLinks)
39
+ >
40
+ > When creating the Application Link use a placeholder URL or the correct URL
41
+ > to your client (e.g. `http://localhost:3000`), if your client can be reached
42
+ > via HTTP and choose the Generic Application type. After this Application Link
43
+ > has been created, edit the configuration and go to the incoming
44
+ > authentication configuration screen and select OAuth. Enter in this the
45
+ > public key and the consumer key which your client will use when making
46
+ > requests to JIRA.
47
+
48
+ This public key and consumer key will need to be generated by the Gem user, using OpenSSL
49
+ or similar to generate the public key and the provided rake task to generate the consumer
50
+ key.
51
+
52
+ > After you have entered all the information click OK and ensure OAuth authentication is
53
+ > enabled.
54
+
55
+ Using the API Gem in your application
56
+ -------------------------------------
57
+ The JiraApi gem requires the consumer key and public certificate file (which are generated in their respective rake tasks) to initialize an access token for using the Jira API. These two pieces of information are applied globally to your application, with separate JiraApi::Client objects created on a per-user basis.
58
+
59
+ An example initializer which sets the key and certificate filename in a pair of globals is shown below, myapp/config/initializers/jira\_api.rb:
60
+
61
+ $CONSUMER_KEY = 'cbaec507669c65979b6b6eefdb1c5bb0' #Your consumer key. Can be generated by rake jira_api:generate_public_cert
62
+ $PUBLIC_CERT_FILE = 'rsakey.pem' #Location of the Private Key File (generated by rake jira_api:generate_public_cert
63
+
64
+ This allows acces to the variables when a session is being set up.
65
+ The following sample controller shows how to set up and initialize an access token for a particular user session.
66
+ (Note that the callback url is defined in the Jira application link interface, and can be placed wherever suits you best in your application. The session#callback method is simply an example)
67
+
68
+ \#TODO cannot pass params hash straight into init\_access\_token method - errors with a missing parameter exception
69
+
70
+ class SessionsController < ApplicationController
71
+ def create
72
+ session[:client] = JiraApi::Client.new($CONSUMER_KEY, '', :private_key_file => $PUBLIC_CERT_FILE)
73
+ session[:request_token] = session[:client].request_token #Generate the request token
74
+ redirect_to session[:request_token].authorize_url #Redirect to Jira to authorize the token
75
+ end
76
+
77
+ def callback
78
+ session[:client].init_access_token(:oauth_verifier => params[:oauth_verifier]) #Initialize the access token
79
+ redirect_to root_url #Redirect to the desired page after initializing the access token
80
+ end
81
+ end
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rubygems'
4
+ require 'rspec/core/rake_task'
5
+
6
+ task :default => [:spec]
7
+
8
+ desc "Run RSpec tests"
9
+ RSpec::Core::RakeTask.new(:spec)
data/example.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'pp'
2
+ require './lib/jira'
3
+
4
+ options = {
5
+ :private_key_file => "rsakey.pem"
6
+ }
7
+
8
+ CONSUMER_KEY = 'test'
9
+
10
+ client = Jira::Client.new(CONSUMER_KEY, '', options)
11
+
12
+ if ARGV.length == 0
13
+ # If not passed any command line arguments, open a browser and prompt the
14
+ # user for the OAuth verifier.
15
+ request_token = client.request_token
16
+ puts "Opening #{request_token.authorize_url}"
17
+ system "open #{request_token.authorize_url}"
18
+
19
+ puts "Enter the oauth_verifier: "
20
+ oauth_verifier = gets.strip
21
+
22
+ access_token = client.init_access_token(:oauth_verifier => oauth_verifier)
23
+ puts "Access token: #{access_token.token} secret: #{access_token.secret}"
24
+ elsif ARGV.length == 2
25
+ # Otherwise assume the arguments are a previous access token and secret.
26
+ access_token = client.set_access_token(ARGV[0], ARGV[1])
27
+ else
28
+ # Script must be passed 0 or 2 arguments
29
+ raise "Usage: #{$0} [ token secret ]"
30
+ end
31
+
32
+ # Show all projects
33
+ projects = client.Project.all
34
+
35
+ projects.each do |project|
36
+ puts "Project -> key: #{project.key}, name: #{project.name}"
37
+ end
38
+ issue = client.Issue.find('SAMPLEPROJECT-1')
39
+ pp issue
40
+
41
+ # # Find a specific project by key
42
+ # # ------------------------------
43
+ # project = client.Project.find('SAMPLEPROJECT')
44
+ # pp project
45
+ #
46
+ # # Delete an issue
47
+ # # ---------------
48
+ # issue = client.Issue.find('SAMPLEPROJECT-2')
49
+ # if issue.delete
50
+ # puts "Delete of issue SAMPLEPROJECT-2 sucessful"
51
+ # else
52
+ # puts "Delete of issue SAMPLEPROJECT-2 failed"
53
+ # end
54
+ #
55
+ # # Create an issue
56
+ # # ---------------
57
+ # issue = client.Issue.build
58
+ # issue.save({"fields"=>{"summary"=>"blarg from in example.rb","project"=>{"id"=>"10001"},"issuetype"=>{"id"=>"3"}}})
59
+ # issue.fetch
60
+ # pp issue
61
+ #
62
+ # Update an issue
63
+ # ---------------
64
+ # issue = client.Issue.find("10002")
65
+ # issue.save({"fields"=>{"summary"=>"EVEN MOOOOOOARRR NINJAAAA!"}})
66
+ # pp issue
data/jira-ruby.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "jira/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "jira-ruby"
7
+ s.version = Jira::VERSION
8
+ s.authors = ["Trineo Ltd"]
9
+ s.homepage = "http://trineo.co.nz"
10
+ s.summary = %q{Ruby Gem for use with the Atlassian Jira 5 REST API}
11
+ s.description = %q{API for Jira 5}
12
+
13
+ s.rubyforge_project = "jira-ruby"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ # specify any dependencies here; for example:
21
+ # s.add_development_dependency "rspec"
22
+ # s.add_runtime_dependency "rest-client"
23
+ s.add_runtime_dependency "oauth"
24
+ s.add_development_dependency "oauth"
25
+ s.add_runtime_dependency "railties"
26
+ s.add_development_dependency "railties"
27
+ s.add_development_dependency "webmock"
28
+ end
data/lib/jira.rb ADDED
@@ -0,0 +1,12 @@
1
+ Dir[File.join(File.dirname(__FILE__),'tasks/*.rake')].each { |f| load f } if defined?(Rake)
2
+
3
+ $: << File.expand_path(File.dirname(__FILE__))
4
+ require 'jira/resource/base'
5
+ require 'jira/resource/base_factory'
6
+ require 'jira/resource/http_error'
7
+
8
+ require 'jira/resource/issue'
9
+ require 'jira/resource/project'
10
+ require 'jira/resource/component'
11
+
12
+ require 'jira/client'
@@ -0,0 +1,105 @@
1
+ require 'oauth'
2
+ require 'json'
3
+ require 'forwardable'
4
+
5
+ module Jira
6
+ class Client
7
+
8
+ extend Forwardable
9
+
10
+ class UninitializedAccessTokenError < StandardError
11
+ def message
12
+ "init_access_token must be called before using the client"
13
+ end
14
+ end
15
+
16
+ attr_accessor :consumer
17
+ attr_reader :options
18
+ delegate [:key, :secret, :get_request_token] => :consumer
19
+
20
+ DEFAULT_OPTIONS = {
21
+ :site => 'http://localhost:2990',
22
+ :signature_method => 'RSA-SHA1',
23
+ :request_token_path => "/jira/plugins/servlet/oauth/request-token",
24
+ :authorize_path => "/jira/plugins/servlet/oauth/authorize",
25
+ :access_token_path => "/jira/plugins/servlet/oauth/access-token",
26
+ :private_key_file => "rsakey.pem",
27
+ :rest_base_path => "/jira/rest/api/2"
28
+ }
29
+
30
+ def initialize(consumer_key, consumer_secret, options={})
31
+ options = DEFAULT_OPTIONS.merge(options)
32
+
33
+ @options = options
34
+ @options.freeze
35
+ @consumer = OAuth::Consumer.new(consumer_key,consumer_secret,options)
36
+ end
37
+
38
+ def Project
39
+ Jira::Resource::ProjectFactory.new(self)
40
+ end
41
+
42
+ def Issue
43
+ Jira::Resource::IssueFactory.new(self)
44
+ end
45
+
46
+ def Component
47
+ Jira::Resource::ComponentFactory.new(self)
48
+ end
49
+
50
+ def request_token
51
+ @request_token ||= get_request_token
52
+ end
53
+
54
+ def set_request_token(token, secret)
55
+ @request_token = OAuth::RequestToken.new(@consumer, token, secret)
56
+ end
57
+
58
+ def init_access_token(params)
59
+ @access_token = request_token.get_access_token(params)
60
+ end
61
+
62
+ def set_access_token(token, secret)
63
+ @access_token = OAuth::AccessToken.new(@consumer, token, secret)
64
+ end
65
+
66
+ def access_token
67
+ raise UninitializedAccessTokenError.new unless @access_token
68
+ @access_token
69
+ end
70
+
71
+ # HTTP methods without a body
72
+ def delete(path, headers = {})
73
+ request(:delete, path, merge_default_headers(headers))
74
+ end
75
+ def get(path, headers = {})
76
+ request(:get, path, merge_default_headers(headers))
77
+ end
78
+ def head(path, headers = {})
79
+ request(:head, path, merge_default_headers(headers))
80
+ end
81
+
82
+ # HTTP methods with a body
83
+ def post(path, body = '', headers = {})
84
+ headers = {'Content-Type' => 'application/json'}.merge(headers)
85
+ request(:post, path, body, merge_default_headers(headers))
86
+ end
87
+ def put(path, body = '', headers = {})
88
+ headers = {'Content-Type' => 'application/json'}.merge(headers)
89
+ request(:put, path, body, merge_default_headers(headers))
90
+ end
91
+
92
+ def request(http_method, path, *arguments)
93
+ response = access_token.request(http_method, path, *arguments)
94
+ raise Resource::HTTPError.new(response) unless response.kind_of?(Net::HTTPSuccess)
95
+ response
96
+ end
97
+
98
+ protected
99
+
100
+ def merge_default_headers(headers)
101
+ {'Accept' => 'application/json'}.merge(headers)
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,148 @@
1
+ module Jira
2
+ module Resource
3
+
4
+ class Base
5
+
6
+ attr_reader :client
7
+ attr_accessor :expanded, :deleted, :attrs
8
+ alias :expanded? :expanded
9
+ alias :deleted? :deleted
10
+
11
+ def initialize(client, options = {})
12
+ @client = client
13
+ @attrs = options[:attrs] || {}
14
+ @expanded = options[:expanded] || false
15
+ @deleted = false
16
+ end
17
+
18
+ # The class methods are never called directly, they are always
19
+ # invoked from a BaseFactory subclass instance.
20
+ def self.all(client)
21
+ response = client.get(rest_base_path(client))
22
+ json = parse_json(response.body)
23
+ json.map do |attrs|
24
+ self.new(client, :attrs => attrs)
25
+ end
26
+ end
27
+
28
+ def self.find(client, key)
29
+ instance = self.new(client)
30
+ instance.attrs[key_attribute.to_s] = key
31
+ instance.fetch
32
+ instance
33
+ end
34
+
35
+ def self.build(client, attrs)
36
+ self.new(client, :attrs => attrs)
37
+ end
38
+
39
+ def self.rest_base_path(client)
40
+ client.options[:rest_base_path] + '/' + self.endpoint_name
41
+ end
42
+
43
+ def self.endpoint_name
44
+ self.name.split('::').last.downcase
45
+ end
46
+
47
+ def self.key_attribute
48
+ :key
49
+ end
50
+
51
+ def self.parse_json(string)
52
+ JSON.parse(string)
53
+ end
54
+
55
+ def respond_to?(method_name)
56
+ if attrs.keys.include? method_name.to_s
57
+ true
58
+ else
59
+ super(method_name)
60
+ end
61
+ end
62
+
63
+ def method_missing(method_name, *args, &block)
64
+ if attrs.keys.include? method_name.to_s
65
+ attrs[method_name.to_s]
66
+ else
67
+ super(method_name)
68
+ end
69
+ end
70
+
71
+ def rest_base_path
72
+ # Just proxy this to the class method
73
+ self.class.rest_base_path(client)
74
+ end
75
+
76
+ def fetch(reload = false)
77
+ return if expanded? && !reload
78
+ response = client.get(url)
79
+ set_attrs_from_response(response)
80
+ @expanded = true
81
+ end
82
+
83
+ def save(attrs)
84
+ http_method = new_record? ? :post : :put
85
+ response = client.send(http_method, url, attrs.to_json)
86
+ set_attrs(attrs, false)
87
+ set_attrs_from_response(response)
88
+ @expanded = false
89
+ true
90
+ end
91
+
92
+ def set_attrs_from_response(response)
93
+ unless response.body.nil? or response.body.length < 2
94
+ json = self.class.parse_json(response.body)
95
+ set_attrs(json)
96
+ end
97
+ end
98
+
99
+ # Set the current attributes from a hash. If clobber is true, any existing
100
+ # hash values will be clobbered by the new hash, otherwise the hash will
101
+ # be deeply merged into attrs. The target paramater is for internal use only
102
+ # and should not be used.
103
+ def set_attrs(hash, clobber=true, target = nil)
104
+ target ||= @attrs
105
+ if clobber
106
+ target.merge!(hash)
107
+ hash
108
+ else
109
+ hash.each do |k, v|
110
+ if v.is_a?(Hash)
111
+ set_attrs(v, clobber, target[k])
112
+ else
113
+ target[k] = v
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ def delete
120
+ client.delete(url)
121
+ @deleted = true
122
+ end
123
+
124
+ def url
125
+ if @attrs['self']
126
+ @attrs['self']
127
+ elsif @attrs[self.class.key_attribute.to_s]
128
+ rest_base_path + "/" + @attrs[self.class.key_attribute.to_s].to_s
129
+ else
130
+ rest_base_path
131
+ end
132
+ end
133
+
134
+ def to_s
135
+ "#<#{self.class.name}:#{object_id} @attrs=#{@attrs.inspect}>"
136
+ end
137
+
138
+ def to_json
139
+ attrs.to_json
140
+ end
141
+
142
+ def new_record?
143
+ @attrs['id'].nil?
144
+ end
145
+ end
146
+
147
+ end
148
+ end