forked from github/github-services
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rally.rb
170 lines (154 loc) · 7.65 KB
/
rally.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
require 'time'
class Service::Rally < Service
string :server, :username, :workspace, :repository
password :password
white_list :server, :workspace, :repository
attr_accessor :wksp_ref, :user_cache
def receive_push
server = data['server']
username = data['username']
password = data['password']
workspace = data['workspace']
scm_repository = data['repository']
raise_config_error("No Server value specified") if server.nil? or server.strip.length == 0
raise_config_error("No UserName value specified") if username.nil? or username.strip.length == 0
raise_config_error("No Password value specified") if password.nil? or password.strip.length == 0
raise_config_error("No Workspace value specified") if workspace.nil? or workspace.strip.length == 0
branch = payload['ref'].split('/')[-1] # most of the time it'll be refs/heads/master ==> master
repo = payload['repository']['name']
repo_owner = payload['repository']['owner']['name']
repo_uri = 'https://github.com/%s/%s' % [repo_owner, repo]
http.ssl[:verify] = false
if server =~ /^https?:\/\// # if they have http:// or https://, leave server value unchanged
http.url_prefix = "#{server}/slm/webservice/1.30"
else
server = "#{server}.rallydev.com" if server !~ /\./ # leave unchanged if '.' in server
http.url_prefix = "https://#{server}/slm/webservice/1.30"
end
http.basic_auth(username, password)
http.headers['Content-Type'] = 'application/json'
http.headers['X-RallyIntegrationVendor'] = 'Rally'
http.headers['X-RallyIntegrationName'] = 'GitHub-Service'
http.headers['X-RallyIntegrationVersion'] = '1.1'
# create the repo in Rally if it doesn't already exist
@wksp_ref = validateWorkspace(workspace)
repo_ref = getOrCreateRepo(scm_repository, repo, repo_owner)
@user_cache = {}
payload['commits'].each do |commit|
artifact_refs = snarfArtifacts(commit['message'])
addChangeset(commit, repo_ref, artifact_refs, repo_uri, branch)
end
end
def addChangeset(commit, repo_ref, artifact_refs, repo_uri, branch)
author = commit['author']['email']
if !@user_cache.has_key?(author)
user = rallyQuery('User', 'Name,UserName', 'UserName = "%s"' % [author])
user_ref = ""
user_ref = itemRef(user) unless user.nil?
@user_cache[author] = user_ref
end
user_ref = @user_cache[author]
message = commit['message'][0..3999] # message max size is 4000 characters
changeset = { 'SCMRepository' => repo_ref,
'Revision' => commit['id'],
'CommitTimestamp' => Time.iso8601(commit['timestamp']).strftime("%FT%H:%M:%S.00Z"),
'Author' => user_ref,
'Message' => message,
'Uri' => '%s/commit/%s' % [repo_uri, commit['id']],
'Artifacts' => artifact_refs # [{'_ref' => 'defect/1324.js'}, {}...]
}
changeset.delete('Author') if user_ref == ""
begin
changeset_item = rallyCreate('Changeset', changeset)
chgset_ref = itemRef(changeset_item)
rescue Faraday::Error => boom # or some other sort of Faraday::Error::xxxError
raise_config_error("Unable to create Rally Changeset")
# changeset_item = nil
end
return if changeset_item.nil?
# change has changeset_ref, Action, PathAndFilename, Uri
changes = []
commit['added'].each { |add| changes << {'Action' => 'A', 'PathAndFilename' => add } }
commit['modified'].each { |mod| changes << {'Action' => 'M', 'PathAndFilename' => mod } }
commit['removed'].each { |rem| changes << {'Action' => 'R', 'PathAndFilename' => rem } }
changes.each do |change|
change['Changeset'] = chgset_ref
change['Uri'] = '%s/blob/%s/%s' % [repo_uri, branch, change['PathAndFilename']]
chg_item = rallyCreate('Change', change)
end
end
def validateWorkspace(workspace)
all_your_workspaces = rallyWorkspaces()
target_workspace = all_your_workspaces.select {|wksp| wksp['Name'] == workspace and wksp['State'] != 'Closed'}
if target_workspace.length != 1
problem = 'Config Error: target workspace: %s not available in list of workspaces associated with your credentials' % [workspace]
raise_config_error(problem)
end
return itemRef(target_workspace[0])
end
def getOrCreateRepo(scm_repository, repo, repo_owner)
scm_repository = repo if (scm_repository.nil? or scm_repository == "")
repo_item = rallyQuery('SCMRepository', 'Name', 'Name = "%s"' % scm_repository)
return itemRef(repo_item) unless repo_item.nil?
repo_info = { 'Workspace' => @wksp_ref, 'Name' => scm_repository, 'SCMType' => 'GitHub',
'Description' => 'GitHub-Service push changesets',
'Uri' => 'http://github.com/%s/%s' % [repo_owner, repo]
}
repo_item = rallyCreate('SCMRepository', repo_info)
return itemRef(repo_item)
end
def itemRef(item) ref = item['_ref'].split('/')[-2..-1].join('/')[0..-4] end
def rallyWorkspaces()
response = @http.get('Subscription.js?fetch=Name,Workspaces,Workspace&pretty=true')
raise_config_error('Config error: credentials not valid for Rally endpoint') if response.status == 401
raise_config_error('Config error: Unable to obtain your Rally subscription info') unless response.success?
qr = JSON.parse(response.body)
begin
workspaces = qr['Subscription']['Workspaces']
rescue Exception => ex
raise_config_error('Config error: No such workspace for your credentials')
end
return workspaces
end
def rallyQuery(entity, fields, criteria)
target_url = '%s.js?fetch=%s' % [entity.downcase, fields]
target_url += '&query=(%s)' % [criteria] if criteria.length > 0
target_url += '&workspace=%s' % [@wksp_ref]
res = @http.get(target_url)
raise StandardError("Config Error, #{entity} query failed") unless res.success?
qr = JSON.parse(res.body)['QueryResult']
item = qr['TotalResultCount'] > 0 ? qr['Results'][0] : nil
return item
end
def rallyCreate(entity, data)
create_url = "%s/create.js?workspace=%s" % [entity, @wksp_ref]
payload = {"#{entity}" => data}
res = @http.post(create_url, payload.to_json)
raise_config_error("Unable to create the Rally #{entity} for #{data['Name']}") unless res.success?
cr = JSON.parse(res.body)['CreateResult']
item = cr['Object']
return item
end
def snarfArtifacts(message)
art_type = { 'D' => 'defect', 'DE' => 'defect', 'DS' => 'defectsuite',
'TA' => 'task', 'TC' => 'testcase',
'S' => 'hierarchicalrequirement',
'US' => 'hierarchicalrequirement'
}
formatted_id_pattern = '^(%s)\d+[\.:;]?$' % art_type.keys.join('|') # '^(D|DE|DS|TA|TC|S|US)\d+[\.:;]?$'
artifact_detector = Regexp.compile(formatted_id_pattern)
words = message.gsub(',', ' ').gsub('\r\n', '\n').gsub('\n', ' ').gsub('\t', ' ').split(' ')
rally_formatted_ids = words.select { |word| artifact_detector.match(word) }
artifacts = [] # actually, just the refs
rally_formatted_ids.uniq.each do |fmtid|
next unless fmtid =~ /^(([A-Z]{1,2})\d+)[\.:;]?$/
fmtid, prefix = $1, $2
entity = art_type[prefix]
artifact = rallyQuery(entity, 'Name', 'FormattedID = "%s"' % fmtid)
next if artifact.nil?
art_ref = itemRef(artifact)
artifacts << {'_ref' => art_ref}
end
return artifacts
end
end