jira-remotelinker 0.1.1

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.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ # Local additions and development tools go here.
5
+ if File.exists? "#{__FILE__}.local"
6
+ eval(File.read("#{__FILE__}.local"), binding)
7
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Charlie Sharpsteen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,74 @@
1
+ # jira-remotelinker
2
+
3
+ A small command line tool for creating bidirectional remote links between JIRA instances through REST API calls.
4
+
5
+ ## Usage
6
+
7
+ To use this tool, you will need to first connect your JIRA instances through [Application Links](https://confluence.atlassian.com/display/JIRA/Linking+to+Another+Application).
8
+ Then, create a YAML file describing the JIRA instances along with the links that connect them:
9
+
10
+ ```yaml
11
+ public:
12
+ base_url: 'https://jira6-public.example.com'
13
+ name: 'Name of Application Link for Private instance'
14
+ uuid: 'UUID of Application Link for Private instance'
15
+ username: 'some user with create link permissions'
16
+ password: 'password for user'
17
+ private:
18
+ base_url: 'https://jira6-private.example.com'
19
+ name: 'Name of Application Link for Public instance'
20
+ uuid: 'UUID of Application Link for Public instance'
21
+ username: 'some user with create link permissions'
22
+ password: 'password for user'
23
+ ```
24
+
25
+ Discovering the UUID associated with an application link is a bit tricky as this value does not appear to be publicly exposed.
26
+ However, the UUID is required for the creation of fully functioning cross-application links.
27
+ One way to extract the `uuid` associated with an application link is through the following database query (tested on JIRA 6.1.x backed by PostgreSQL):
28
+
29
+ ```sql
30
+ /* Retrieve Application Link names and UUIDs From JIRA
31
+ * Based on a query described in the Atlassian troubleshooting docs:
32
+ *
33
+ * https://confluence.atlassian.com/display/JIRAKB/How+to+remove+Application+link+directly+through+database
34
+ *
35
+ */
36
+ SELECT
37
+ propertystring.propertyvalue AS "name",
38
+ SUBSTR(propertyentry.property_key,16,36) AS "uuid"
39
+ FROM
40
+ propertyentry, propertystring
41
+ WHERE
42
+ propertyentry.id = propertystring.id AND
43
+ propertyentry.property_key like 'applinks.admin%name';
44
+
45
+ ```
46
+
47
+ The second piece of required input is a CSV file containing the issue link data.
48
+ The required pieces of data are:
49
+
50
+ - **source_instance:** The name of a JIRA instance defined in the YAML configuration file.
51
+ - **source_id:** The global numeric id of the source ticket.
52
+ - **source_key:** The project-specific id of the source ticket (i.e. "PUB-2").
53
+ - **source_relation:** The relationship between the source ticket and the target ticket (i.e. "blocks").
54
+ - **target_instance:** The name of a JIRA instance defined in the YAML configuration file.
55
+ - **target_id:** The global numeric id of the target ticket.
56
+ - **target_key:** The project-specific id of the target ticket (i.e. "PRIV-20").
57
+ - **target_relation:** The relationship between the target ticket and the source ticket (i.e. "blocked by").
58
+
59
+ These fields are laid in a CSV file in the following order:
60
+
61
+ source_instance | source_id | source_key | source_relation | target_instance | target_id | target_key | target_relation
62
+ --- | --- | --- | --- | --- | --- | --- | ---
63
+ public | 10020 | PUB-2 | blocks | private | 11234 | PRIV-20 | blocked by
64
+ public | 10042 | PUB-7 | duplicated by | private | 11555 | PRIV-42 | duplicates
65
+
66
+
67
+ Once the YAML and CSV files have been created, the tool can be invoked:
68
+
69
+ jira-remotelinker jira_instances.yaml link_data.csv
70
+
71
+ ---
72
+ <p align="center">
73
+ <img src="http://i.imgur.com/iDFAxAM.jpg" />
74
+ </p>
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'methadone'
5
+ require 'progressbar'
6
+
7
+ require 'yaml'
8
+ require 'csv'
9
+
10
+ require 'jira-remotelinker'
11
+
12
+ class App
13
+ include Methadone::Main
14
+ include Methadone::CLILogging
15
+
16
+ description 'A small command line tool for creating remote links through the JIRA REST API'
17
+ version JiraRemotelinker::VERSION
18
+
19
+ use_log_level_option
20
+
21
+ arg :jira_config, 'A YAML file containing configuration and API access info for the target JIRA instances'
22
+ arg :link_data, 'A CSV file containing issue link data'
23
+
24
+ main do |jira_config, link_data|
25
+ config = YAML.load_file jira_config
26
+
27
+ # Create a REST client for each JIRA instance defined in the config.
28
+ config.each do |_, instance|
29
+ instance['client'] = JiraRemotelinker::REST.factory instance['base_url'], instance['username'], instance['password'],
30
+ instance['name'], instance['uuid']
31
+ end
32
+
33
+ links = CSV.table link_data
34
+ pbar = ProgressBar.new 'making links', links.length
35
+
36
+ links.each do |row|
37
+ # Create link from source JIRA -> target JIRA
38
+ config[row[:source_instance]]['client'].post_issue_remotelink row[:source_key], row[:source_relation],
39
+ config[row[:target_instance]]['client'], row[:target_key], row[:target_id]
40
+
41
+ # Create reciprocal link from target JIRA -> source JIRA
42
+ config[row[:target_instance]]['client'].post_issue_remotelink row[:target_key], row[:target_relation],
43
+ config[row[:source_instance]]['client'], row[:source_key], row[:source_id]
44
+
45
+ pbar.inc
46
+ end
47
+
48
+ pbar.finish
49
+ end
50
+
51
+ go!
52
+ end
@@ -0,0 +1,14 @@
1
+ /* Retrieve Application Link names and UUIDs From JIRA
2
+ * Based on a query retrieved from the Atlassian troubleshooting docs:
3
+ *
4
+ * https://confluence.atlassian.com/display/JIRAKB/How+to+remove+Application+link+directly+through+database
5
+ *
6
+ */
7
+ SELECT
8
+ propertystring.propertyvalue AS "name",
9
+ SUBSTR(propertyentry.property_key,16,36) AS "uuid"
10
+ FROM
11
+ propertyentry, propertystring
12
+ WHERE
13
+ propertyentry.id = propertystring.id AND
14
+ propertyentry.property_key like 'applinks.admin%name';
@@ -0,0 +1,91 @@
1
+ /* Huge, hairy SELECT statement that copies link data to CSV */
2
+ COPY (
3
+ WITH
4
+
5
+ /* This defines the group of projects which are migrating to a new JIRA
6
+ * instance
7
+ */
8
+ migrated_projects AS (
9
+ VALUES
10
+ ('FACT'),
11
+ ('PP'),
12
+ ('HI'),
13
+ ('OS'),
14
+ ('MCO'),
15
+ ('PDB'),
16
+ ('GTO'),
17
+ ('RAZOR')
18
+ ),
19
+
20
+ /* JIRA 6.1 replaced the pkey column which held the issue key, "PP-22", with
21
+ * the issuenum column which just holds the issue number: "22". This view
22
+ * re-constitutes the issue keys, plus saves the project identifier, "PP", so
23
+ * that it can be later checked against the list defined above.
24
+ */
25
+ munged_issues AS (
26
+ SELECT
27
+ jiraissue.id,
28
+ project.pkey || '-' || jiraissue.issuenum AS "key",
29
+ project.pkey
30
+ FROM
31
+ jiraissue,
32
+ project
33
+ WHERE
34
+ jiraissue.project = project.id
35
+ ),
36
+
37
+ /* This selects all links that originate OR terminate in the group of
38
+ * migrated projects but do not originate AND terminate in the group of
39
+ * migrated projects.
40
+ */
41
+ munged_links AS (
42
+ SELECT
43
+ issuelink.*
44
+ FROM
45
+ issuelink
46
+ JOIN
47
+ munged_issues AS source_issue
48
+ ON issuelink.source = source_issue.id
49
+ JOIN
50
+ munged_issues AS dest_issue
51
+ ON issuelink.destination = dest_issue.id
52
+ WHERE
53
+ (
54
+ source_issue.pkey IN (SELECT * FROM migrated_projects) AND
55
+ dest_issue.pkey NOT IN (SELECT * FROM migrated_projects)
56
+ ) OR
57
+ (
58
+ source_issue.pkey NOT IN (SELECT * FROM migrated_projects) AND
59
+ dest_issue.pkey IN (SELECT * FROM migrated_projects)
60
+ )
61
+ )
62
+
63
+ /* This selection gathers link data targetted by the munged_links query and
64
+ * exports it to CSV in the format expected by the jira-remotelinker tool.
65
+ */
66
+ SELECT
67
+ CASE
68
+ WHEN source_issue.pkey IN (SELECT * FROM migrated_projects) THEN 'public'
69
+ ELSE 'private' END
70
+ AS "source_instance",
71
+ source AS "source_id",
72
+ source_issue.key AS "source_key",
73
+ outward AS "source_relation",
74
+ CASE
75
+ WHEN dest_issue.pkey NOT IN (SELECT * FROM migrated_projects) THEN 'private'
76
+ ELSE 'public' END
77
+ AS "target_instance",
78
+ destination AS "target_id",
79
+ dest_issue.key AS "target_key",
80
+ inward AS "target_relation"
81
+ FROM munged_links AS issuelink
82
+ JOIN
83
+ issuelinktype AS type
84
+ ON issuelink.linktype = type.id
85
+ JOIN
86
+ munged_issues AS source_issue
87
+ ON issuelink.source = source_issue.id
88
+ JOIN
89
+ munged_issues AS dest_issue
90
+ ON issuelink.destination = dest_issue.id )
91
+ TO '/tmp/links_to_migrate.csv' WITH CSV HEADER;
@@ -0,0 +1,13 @@
1
+ public:
2
+ base_url: 'https://jira6-public.example.com'
3
+ # See get_applink_uuid.sql for a method of retrieving the Name and UUID
4
+ name: 'Name of Application Link for Private instance'
5
+ uuid: 'UUID of Application Link for Private instance'
6
+ username: 'some user with create link permissions'
7
+ password: 'password for user'
8
+ private:
9
+ base_url: 'https://jira6-private.example.com'
10
+ name: 'Name of Application Link for Public instance'
11
+ uuid: 'UUID of Application Link for Public instance'
12
+ username: 'some user with create link permissions'
13
+ password: 'password for user'
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jira-remotelinker/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'jira-remotelinker'
8
+ spec.version = JiraRemotelinker::VERSION
9
+ spec.authors = ['Charlie Sharpsteen']
10
+ spec.email = ['source@sharpsteen.net']
11
+ spec.description = %q{A small command line tool for creating remote links through the JIRA REST API}
12
+ spec.summary = %q{This tool reads in issue link information from CSV and creates remote links using v2 of the JIRA REST API.}
13
+ spec.homepage = 'https://github.com/Sharpie/jira-remotelinker'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.require_paths = ['lib']
18
+ spec.bindir = 'bin'
19
+ spec.executables = ['jira-remotelinker']
20
+
21
+ spec.add_runtime_dependency 'methadone', '~> 1.3.0'
22
+ spec.add_runtime_dependency 'httparty', '~> 0.12.0'
23
+ spec.add_runtime_dependency 'progressbar'
24
+ end
@@ -0,0 +1,5 @@
1
+ module JiraRemotelinker
2
+ end
3
+
4
+ require 'jira-remotelinker/version'
5
+ require 'jira-remotelinker/rest'
@@ -0,0 +1,65 @@
1
+ require 'httparty'
2
+
3
+ module JiraRemotelinker
4
+ module REST
5
+ # An abstract template class that talks to v2 of the JIRA API.
6
+ # Instantiate concrete subclasses using the module function `factory`.
7
+ class AbstractV2
8
+ API_PATH = 'rest/api/2'
9
+ include HTTParty
10
+ format :json
11
+
12
+ # Sets three instance variables that represent a JIRA Application Link
13
+ class << self
14
+ attr_accessor :jira_type, :jira_name, :jira_uuid, :jira_url
15
+ end
16
+
17
+ def self.get_issue(issue_key)
18
+ get "/issue/#{issue_key}"
19
+ end
20
+
21
+ def self.get_issue_remotelinks(issue_key)
22
+ get "/issue/#{issue_key}/remotelink"
23
+ end
24
+
25
+ def self.post_issue_remotelink(issue_key, relationship, target_jira, target_key, target_id)
26
+ link_data = {
27
+ 'globalId' => "appId=#{target_jira.jira_uuid}&issueId=#{target_id}",
28
+ 'application' => {
29
+ 'type' => target_jira.jira_type,
30
+ 'name' => target_jira.jira_name
31
+ },
32
+ 'relationship' => relationship,
33
+ 'object' => {
34
+ 'url' => "#{target_jira.jira_url}/browse/#{target_key}",
35
+ 'title' => target_key
36
+ }
37
+ }
38
+
39
+ post "/issue/#{issue_key}/remotelink", {:body => link_data.to_json, :headers => {'Content-Type' => 'application/json'}}
40
+ end
41
+
42
+ def self.delete_issue_remotelink(issue_key, link_id)
43
+ delete "/issue/#{issue_key}/remotelink/#{link_id}"
44
+ end
45
+ end
46
+
47
+ module_function
48
+
49
+ # Create a concrete class that talks to a particular JIRA instance using the V2 API.
50
+ def factory(url, username, password, name, uuid, type = 'com.atlassian.jira')
51
+ Class.new(AbstractV2) do |klass|
52
+ klass.base_uri(File.join(url, AbstractV2::API_PATH))
53
+ klass.basic_auth(username, password)
54
+
55
+ # These are used during Remote Link creation and allow JIRA instances
56
+ # to pull issue data from each other and ensure that users are properly
57
+ # authorized to view such data.
58
+ klass.jira_name = name
59
+ klass.jira_uuid = uuid
60
+ klass.jira_type = type
61
+ klass.jira_url = url
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ module JiraRemotelinker
2
+ VERSION = "0.1.1"
3
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jira-remotelinker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Charlie Sharpsteen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: methadone
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.3.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.3.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: httparty
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.12.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.12.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: progressbar
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: A small command line tool for creating remote links through the JIRA
63
+ REST API
64
+ email:
65
+ - source@sharpsteen.net
66
+ executables:
67
+ - jira-remotelinker
68
+ extensions: []
69
+ extra_rdoc_files: []
70
+ files:
71
+ - .gitignore
72
+ - Gemfile
73
+ - LICENSE
74
+ - README.md
75
+ - bin/jira-remotelinker
76
+ - examples/get_applink_uuid.sql
77
+ - examples/get_links_to_migrate.sql
78
+ - examples/jira_config.yaml
79
+ - jira-remotelinker.gemspec
80
+ - lib/jira-remotelinker.rb
81
+ - lib/jira-remotelinker/rest.rb
82
+ - lib/jira-remotelinker/version.rb
83
+ homepage: https://github.com/Sharpie/jira-remotelinker
84
+ licenses:
85
+ - MIT
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 1.8.23
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: This tool reads in issue link information from CSV and creates remote links
108
+ using v2 of the JIRA REST API.
109
+ test_files: []
110
+ has_rdoc: