blob: b65432dc5c239473405a33b1237adf37d7eb27cd [file] [log] [blame]
Jakub Josef1b75ca82017-02-20 16:08:13 +01001package com.mirantis.mk
Jakub Josefc70c2a32017-03-29 16:38:30 +02002import java.util.regex.Pattern
Jakub Josefec5098f2017-06-15 18:15:32 +02003import com.cloudbees.groovy.cps.NonCPS
4import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritCause
Jakub Josef1b75ca82017-02-20 16:08:13 +01005/**
6 * Gerrit functions
7 *
8 */
9
10/**
11 * Execute git clone and checkout stage from gerrit review
12 *
13 * @param config LinkedHashMap
14 * config includes next parameters:
15 * - credentialsId, id of user which should make checkout
Jakub Josefbccd7862017-05-30 14:27:15 +020016 * - withMerge, merge master before build
17 * - withLocalBranch, prevent detached mode in repo
Jakub Josef1b75ca82017-02-20 16:08:13 +010018 * - withWipeOut, wipe repository and force clone
azvyagintseved1d63e2018-09-10 14:25:13 +030019 * - GerritTriggerBuildChooser - use magic GerritTriggerBuildChooser class from gerrit-trigger-plugin.
20 * By default,enabled.
Jakub Josefbccd7862017-05-30 14:27:15 +020021 * Gerrit properties like GERRIT_SCHEMA can be passed in config as gerritSchema or will be obtained from env
22 * @param extraScmExtensions list of extra scm extensions which will be used for checkout (optional)
Jakub Josefc70c2a32017-03-29 16:38:30 +020023 * @return boolean result
Jakub Josef1b75ca82017-02-20 16:08:13 +010024 *
25 * Usage example:
26 * //anonymous gerrit checkout
27 * def gitFunc = new com.mirantis.mcp.Git()
28 * gitFunc.gerritPatchsetCheckout([
29 * withMerge : true
30 * ])
31 *
32 * def gitFunc = new com.mirantis.mcp.Git()
33 * gitFunc.gerritPatchsetCheckout([
34 * credentialsId : 'mcp-ci-gerrit',
35 * withMerge : true
36 * ])
37 */
Jakub Josefbccd7862017-05-30 14:27:15 +020038def gerritPatchsetCheckout(LinkedHashMap config, List extraScmExtensions = []) {
Jakub Josef1b75ca82017-02-20 16:08:13 +010039 def merge = config.get('withMerge', false)
40 def wipe = config.get('withWipeOut', false)
Jakub Josefbccd7862017-05-30 14:27:15 +020041 def localBranch = config.get('withLocalBranch', false)
Jakub Josef1b75ca82017-02-20 16:08:13 +010042 def credentials = config.get('credentialsId','')
Jakub Josef71c46a62017-03-29 14:55:33 +020043 def gerritScheme = config.get('gerritScheme', env["GERRIT_SCHEME"] ? env["GERRIT_SCHEME"] : "")
44 def gerritRefSpec = config.get('gerritRefSpec', env["GERRIT_REFSPEC"] ? env["GERRIT_REFSPEC"] : "")
45 def gerritName = config.get('gerritName', env["GERRIT_NAME"] ? env["GERRIT_NAME"] : "")
46 def gerritHost = config.get('gerritHost', env["GERRIT_HOST"] ? env["GERRIT_HOST"] : "")
47 def gerritPort = config.get('gerritPort', env["GERRIT_PORT"] ? env["GERRIT_PORT"] : "")
48 def gerritProject = config.get('gerritProject', env["GERRIT_PROJECT"] ? env["GERRIT_PROJECT"] : "")
49 def gerritBranch = config.get('gerritBranch', env["GERRIT_BRANCH"] ? env["GERRIT_BRANCH"] : "")
chnyda96a1e8a2017-03-28 16:02:13 +020050 def path = config.get('path', "")
chnyda7d25fc92017-03-29 10:51:59 +020051 def depth = config.get('depth', 0)
52 def timeout = config.get('timeout', 20)
azvyagintseved1d63e2018-09-10 14:25:13 +030053 def GerritTriggerBuildChooser = config.get('useGerritTriggerBuildChooser', true)
Jakub Josef1b75ca82017-02-20 16:08:13 +010054
Dmitry Pyzhov169f8122017-12-06 14:44:41 +030055 def invalidParams = _getInvalidGerritParams(config)
56 if (invalidParams.isEmpty()) {
Jakub Josefc70c2a32017-03-29 16:38:30 +020057 // default parameters
58 def scmExtensions = [
59 [$class: 'CleanCheckout'],
Jakub Josefc70c2a32017-03-29 16:38:30 +020060 [$class: 'CheckoutOption', timeout: timeout],
61 [$class: 'CloneOption', depth: depth, noTags: false, reference: '', shallow: depth > 0, timeout: timeout]
62 ]
63 def scmUserRemoteConfigs = [
64 name: 'gerrit',
Jakub Josefc70c2a32017-03-29 16:38:30 +020065 ]
Jakub Josef30fc9212017-04-04 11:47:19 +020066 if(gerritRefSpec && gerritRefSpec != ""){
67 scmUserRemoteConfigs.put('refspec', gerritRefSpec)
68 }
Jakub Josef1b75ca82017-02-20 16:08:13 +010069
Jakub Josefc70c2a32017-03-29 16:38:30 +020070 if (credentials == '') {
71 // then try to checkout in anonymous mode
72 scmUserRemoteConfigs.put('url',"${gerritScheme}://${gerritHost}/${gerritProject}")
73 } else {
74 // else use ssh checkout
75 scmUserRemoteConfigs.put('url',"ssh://${gerritName}@${gerritHost}:${gerritPort}/${gerritProject}.git")
76 scmUserRemoteConfigs.put('credentialsId',credentials)
77 }
Jakub Josef1b75ca82017-02-20 16:08:13 +010078
azvyagintseved1d63e2018-09-10 14:25:13 +030079 // Usefull, if we only need to clone branch. W\o any refspec magic
80 if (GerritTriggerBuildChooser) {
81 scmExtensions.add([$class: 'BuildChooserSetting', buildChooser: [$class: 'GerritTriggerBuildChooser']],)
82 }
83
Jakub Josefc70c2a32017-03-29 16:38:30 +020084 // if we need to "merge" code from patchset to GERRIT_BRANCH branch
85 if (merge) {
vnaumov37b735d2018-08-27 16:55:07 +040086 scmExtensions.add([$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: 'gerrit', mergeStrategy: 'DEFAULT', mergeTarget: gerritBranch]])
Jakub Josefc70c2a32017-03-29 16:38:30 +020087 }
88 // we need wipe workspace before checkout
89 if (wipe) {
90 scmExtensions.add([$class: 'WipeWorkspace'])
91 }
Jakub Josef1b75ca82017-02-20 16:08:13 +010092
Jakub Josefbccd7862017-05-30 14:27:15 +020093 if(localBranch){
94 scmExtensions.add([$class: 'LocalBranch', localBranch: gerritBranch])
95 }
96
97 if(!extraScmExtensions.isEmpty()){
98 scmExtensions.addAll(extraScmExtensions)
99 }
Jakub Josefc70c2a32017-03-29 16:38:30 +0200100 if (path == "") {
chnyda96a1e8a2017-03-28 16:02:13 +0200101 checkout(
102 scm: [
103 $class: 'GitSCM',
104 branches: [[name: "${gerritBranch}"]],
105 extensions: scmExtensions,
106 userRemoteConfigs: [scmUserRemoteConfigs]
107 ]
108 )
Jakub Josefc70c2a32017-03-29 16:38:30 +0200109 } else {
110 dir(path) {
111 checkout(
112 scm: [
113 $class: 'GitSCM',
114 branches: [[name: "${gerritBranch}"]],
115 extensions: scmExtensions,
116 userRemoteConfigs: [scmUserRemoteConfigs]
117 ]
118 )
119 }
chnyda96a1e8a2017-03-28 16:02:13 +0200120 }
Jakub Josefc70c2a32017-03-29 16:38:30 +0200121 return true
Jakub Josef73d62142017-03-29 17:07:18 +0200122 }else{
Dmitry Pyzhov169f8122017-12-06 14:44:41 +0300123 throw new Exception("Cannot perform gerrit checkout, missed config options: " + invalidParams)
chnyda96a1e8a2017-03-28 16:02:13 +0200124 }
Jakub Josef30fc9212017-04-04 11:47:19 +0200125 return false
Jakub Josefc70c2a32017-03-29 16:38:30 +0200126}
127/**
128 * Execute git clone and checkout stage from gerrit review
129 *
Jakub Josefad34dbf2017-03-29 17:52:31 +0200130 * @param gerritUrl gerrit url with scheme
Jakub Josefc70c2a32017-03-29 16:38:30 +0200131 * "${GERRIT_SCHEME}://${GERRIT_NAME}@${GERRIT_HOST}:${GERRIT_PORT}/${GERRIT_PROJECT}.git
Jakub Josefad34dbf2017-03-29 17:52:31 +0200132 * @param gerritRef gerrit ref spec
133 * @param gerritBranch gerrit branch
134 * @param credentialsId jenkins credentials id
Dmitry Pyzhove832b0a2017-12-06 14:43:25 +0300135 * @param path checkout path, optional, default is empty string which means workspace root
Jakub Josefc70c2a32017-03-29 16:38:30 +0200136 * @return boolean result
137 */
Dmitry Pyzhove832b0a2017-12-06 14:43:25 +0300138def gerritPatchsetCheckout(gerritUrl, gerritRef, gerritBranch, credentialsId, path="") {
Jakub Josefad34dbf2017-03-29 17:52:31 +0200139 def gerritParams = _getGerritParamsFromUrl(gerritUrl)
Jakub Josefd383f392017-03-29 16:52:04 +0200140 if(gerritParams.size() == 5){
Dmitry Pyzhove832b0a2017-12-06 14:43:25 +0300141 if (path==""){
142 gerritPatchsetCheckout([
143 credentialsId : credentialsId,
144 gerritBranch: gerritBranch,
145 gerritRefSpec: gerritRef,
146 gerritScheme: gerritParams[0],
147 gerritName: gerritParams[1],
148 gerritHost: gerritParams[2],
149 gerritPort: gerritParams[3],
150 gerritProject: gerritParams[4]
151 ])
152 return true
153 } else {
154 dir(path) {
155 gerritPatchsetCheckout([
156 credentialsId : credentialsId,
157 gerritBranch: gerritBranch,
158 gerritRefSpec: gerritRef,
159 gerritScheme: gerritParams[0],
160 gerritName: gerritParams[1],
161 gerritHost: gerritParams[2],
162 gerritPort: gerritParams[3],
163 gerritProject: gerritParams[4]
164 ])
165 return true
166 }
167 }
Jakub Josefc70c2a32017-03-29 16:38:30 +0200168 }
169 return false
170}
171
Jakub Josef50c9c3a2017-04-10 14:32:35 +0200172/**
173 * Return gerrit change object from gerrit API
Jakub Josef50c9c3a2017-04-10 14:32:35 +0200174 * @param gerritName gerrit user name (usually GERRIT_NAME property)
Jakub Josefb735dd42017-04-10 15:31:19 +0200175 * @param gerritHost gerrit host (usually GERRIT_HOST property)
Jakub Josef50c9c3a2017-04-10 14:32:35 +0200176 * @param gerritChangeNumber gerrit change number (usually GERRIT_CHANGE_NUMBER property)
177 * @param credentialsId jenkins credentials id for gerrit
Jakub Josef5aa33de2017-06-16 12:23:45 +0200178 * @param includeCurrentPatchset do you want to include current (last) patchset
Jakub Josef50c9c3a2017-04-10 14:32:35 +0200179 * @return gerrit change object
180 */
Jakub Josef5aa33de2017-06-16 12:23:45 +0200181def getGerritChange(gerritName, gerritHost, gerritChangeNumber, credentialsId, includeCurrentPatchset = false){
Jakub Josef50c9c3a2017-04-10 14:32:35 +0200182 def common = new com.mirantis.mk.Common()
183 def ssh = new com.mirantis.mk.Ssh()
184 ssh.prepareSshAgentKey(credentialsId)
185 ssh.ensureKnownHosts(gerritHost)
Jakub Josef5aa33de2017-06-16 12:23:45 +0200186 def curPatchset = "";
187 if(includeCurrentPatchset){
188 curPatchset = "--current-patch-set"
189 }
190 return common.parseJSON(ssh.agentSh(String.format("ssh -p 29418 %s@%s gerrit query ${curPatchset} --format=JSON change:%s", gerritName, gerritHost, gerritChangeNumber)))
Jakub Josef50c9c3a2017-04-10 14:32:35 +0200191}
192
Jakub Josef4edd7432017-05-10 17:58:56 +0200193/**
194 * Returns list of Gerrit trigger requested builds
195 * @param allBuilds list of all builds of some job
196 * @param gerritChange gerrit change number
197 * @param excludePatchset gerrit patchset number which will be excluded from builds, optional null
198 */
199@NonCPS
200def getGerritTriggeredBuilds(allBuilds, gerritChange, excludePatchset = null){
201 return allBuilds.findAll{job ->
202 def cause = job.causes[0]
Jakub Josefec5098f2017-06-15 18:15:32 +0200203 if(cause instanceof GerritCause &&
Denis Egorenkof0568dd2019-01-16 13:53:34 +0400204 (cause.getEvent() instanceof com.sonymobile.tools.gerrit.gerritevents.dto.events.PatchsetCreated ||
205 cause.getEvent() instanceof com.sonymobile.tools.gerrit.gerritevents.dto.events.CommentAdded)) {
Jakub Josef4edd7432017-05-10 17:58:56 +0200206 if(excludePatchset == null || excludePatchset == 0){
207 return cause.event.change.number.equals(String.valueOf(gerritChange))
208 }else{
209 return cause.event.change.number.equals(String.valueOf(gerritChange)) && !cause.event.patchSet.number.equals(String.valueOf(excludePatchset))
210 }
211 }
212 return false
213 }
214}
Jakub Josef62899fd2017-06-15 18:53:46 +0200215/**
Jakub Josef5aa33de2017-06-16 12:23:45 +0200216 * Returns boolean result of test given gerrit patchset for given approval type and value
217 * @param patchset gerrit patchset
Jakub Josef62899fd2017-06-15 18:53:46 +0200218 * @param approvalType type of tested approval (optional, default Verified)
Jakub Josef798bfc52017-06-16 12:44:23 +0200219 * @param approvalValue value of tested approval (optional, default empty string which means any value)
Jakub Josef62899fd2017-06-15 18:53:46 +0200220 * @return boolean result
Jakub Josef5aa33de2017-06-16 12:23:45 +0200221 * @example patchsetHasApproval(gerrit.getGerritChange(*,*,*,*, true).currentPatchSet)
Jakub Josef62899fd2017-06-15 18:53:46 +0200222 */
Jakub Josef5aa33de2017-06-16 12:23:45 +0200223@NonCPS
Jakub Josef798bfc52017-06-16 12:44:23 +0200224def patchsetHasApproval(patchSet, approvalType="Verified", approvalValue = ""){
Jakub Josef5aa33de2017-06-16 12:23:45 +0200225 if(patchSet && patchSet.approvals){
226 for(int i=0; i < patchSet.approvals.size();i++){
227 def approval = patchSet.approvals.get(i)
Jakub Josef798bfc52017-06-16 12:44:23 +0200228 if(approval.type.equals(approvalType)){
229 if(approvalValue.equals("") || approval.value.equals(approvalValue)){
230 return true
Jakub Josef996ada82017-06-16 12:59:43 +0200231 }else if(approvalValue.equals("+") && Integer.parseInt(approval.value) > 0) {
232 return true
233 }else if(approvalValue.equals("-") && Integer.parseInt(approval.value) < 0) {
234 return true
Jakub Josef798bfc52017-06-16 12:44:23 +0200235 }
Jakub Josef5aa33de2017-06-16 12:23:45 +0200236 }
237 }
Jakub Josef62899fd2017-06-15 18:53:46 +0200238 }
239 return false
240}
Jakub Josef4edd7432017-05-10 17:58:56 +0200241
Jakub Josefd383f392017-03-29 16:52:04 +0200242@NonCPS
243def _getGerritParamsFromUrl(gitUrl){
244 def gitUrlPattern = Pattern.compile("(.+):\\/\\/(.+)@(.+):(.+)\\/(.+)")
245 def gitUrlMatcher = gitUrlPattern.matcher(gitUrl)
246 if(gitUrlMatcher.find() && gitUrlMatcher.groupCount() == 5){
247 return [gitUrlMatcher.group(1),gitUrlMatcher.group(2),gitUrlMatcher.group(3),gitUrlMatcher.group(4),gitUrlMatcher.group(5)]
248 }
249 return []
250}
251
Dmitry Pyzhov169f8122017-12-06 14:44:41 +0300252def _getInvalidGerritParams(LinkedHashMap config){
253 def requiredParams = ["gerritScheme", "gerritName", "gerritHost", "gerritPort", "gerritProject", "gerritBranch"]
254 def missedParams = requiredParams - config.keySet()
255 def badParams = config.subMap(requiredParams).findAll{it.value in [null, '']}.keySet()
256 return badParams + missedParams
vnaumov37b735d2018-08-27 16:55:07 +0400257}
Denis Egorenko3253f462018-12-05 19:05:41 +0400258
259/**
260 * Post Gerrit comment from CI user
261 *
262 * @param config map which contains next params:
263 * gerritName - gerrit user name (usually GERRIT_NAME property)
264 * gerritHost - gerrit host (usually GERRIT_HOST property)
265 * gerritChangeNumber - gerrit change number (usually GERRIT_CHANGE_NUMBER property)
266 * gerritPatchSetNumber - gerrit patch set number (usually GERRIT_PATCHSET_NUMBER property)
267 * message - message to send to gerrit review patch
268 * credentialsId - jenkins credentials id for gerrit
269 */
270def postGerritComment(LinkedHashMap config) {
271 def common = new com.mirantis.mk.Common()
272 def ssh = new com.mirantis.mk.Ssh()
273 String gerritName = config.get('gerritName')
274 String gerritHost = config.get('gerritHost')
275 String gerritChangeNumber = config.get('gerritChangeNumber')
276 String gerritPatchSetNumber = config.get('gerritPatchSetNumber')
277 String message = config.get('message')
278 String credentialsId = config.get('credentialsId')
279
280 ssh.prepareSshAgentKey(credentialsId)
281 ssh.ensureKnownHosts(gerritHost)
282 ssh.agentSh(String.format("ssh -p 29418 %s@%s gerrit review %s,%s -m \"'%s'\" --code-review 0", gerritName, gerritHost, gerritChangeNumber, gerritPatchSetNumber, message))
283}
Denis Egorenko26da6c12018-11-16 14:38:42 +0400284
285/**
286 * Return map of dependent patches info for current patch set
287 * based on commit message hints: Depends-On: https://gerrit_address/_CHANGE_NUMBER_
288 * @param changeInfo Map Info about current patch set, such as:
289 * gerritName Gerrit user name (usually GERRIT_NAME property)
290 * gerritHost Gerrit host (usually GERRIT_HOST property)
291 * gerritChangeNumber Gerrit change number (usually GERRIT_CHANGE_NUMBER property)
292 * credentialsId Jenkins credentials id for gerrit
293 * @return map of dependent patches info
294 */
295LinkedHashMap getDependentPatches(LinkedHashMap changeInfo) {
296 def dependentPatches = [:]
297 def currentChange = getGerritChange(changeInfo.gerritName, changeInfo.gerritHost, changeInfo.gerritChangeNumber, changeInfo.credentialsId, true)
Denis Egorenko8b24fd42018-12-12 12:43:04 +0400298 def dependentCommits = currentChange.commitMessage.tokenize('\n').findAll { it ==~ /Depends-On: \b[^ ]+\b(\/)?/ }
Denis Egorenko26da6c12018-11-16 14:38:42 +0400299 if (dependentCommits) {
300 dependentCommits.each { commit ->
301 def patchLink = commit.tokenize(' ')[1]
302 def changeNumber = patchLink.tokenize('/')[-1].trim()
303 def dependentCommit = getGerritChange(changeInfo.gerritName, changeInfo.gerritHost, changeNumber, changeInfo.credentialsId, true)
304 if (dependentCommit.status == "NEW") {
305 dependentPatches[dependentCommit.project] = [
306 'number': dependentCommit.number,
307 'ref': dependentCommit.currentPatchSet.ref,
308 'branch': dependentCommit.branch,
309 ]
310 }
311 }
312 }
313 return dependentPatches
314}
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300315
316/**
317 * Find Gerrit change(s) according to various input parameters like owner, topic, etc.
318 * @param gerritAuth A map containing information about Gerrit. Should include
319 * HOST, PORT and USER
320 * @param changeParams Parameters to identify Geriit change e.g.: owner, topic,
321 * status, branch, project
322 */
323def findGerritChange(credentialsId, LinkedHashMap gerritAuth, LinkedHashMap changeParams) {
324 scriptText = """
325 ssh -p ${gerritAuth['PORT']} ${gerritAuth['USER']}@${gerritAuth['HOST']} \
326 gerrit query \
327 --format JSON \
328 """
329 changeParams.each {
330 scriptText += " ${it.key}:${it.value}"
331 }
332 scriptText += " | fgrep -v runTimeMilliseconds || :"
333 sshagent([credentialsId]) {
334 jsonChange = sh(
335 script:scriptText,
336 returnStdout: true,
337 ).trim()
338 }
339 return jsonChange
340}
341
342/**
343 * Download Gerrit review by number
344 *
345 * @param credentialsId credentials ID
346 * @param virtualenv virtualenv path
347 * @param repoDir repository directory
348 * @param gitRemote the value of git remote
349 * @param changeNum the number of change to download
350 */
351def getGerritChangeByNum(credentialsId, virtualEnv, repoDir, gitRemote, changeNum) {
352 def python = new com.mirantis.mk.Python()
353 sshagent([credentialsId]) {
354 dir(repoDir) {
355 python.runVirtualenvCommand(virtualEnv, "git review -r ${gitRemote} -d ${changeNum}")
356 }
357 }
358}
359
360/**
361 * Post Gerrit review
362 * @param credentialsId credentials ID
363 * @param virtualenv virtualenv path
364 * @param repoDir repository directory
365 * @param gitName committer name
366 * @param gitEmail committer email
367 * @param gitRemote the value of git remote
368 * @param gitTopic the name of the topic
369 * @param gitBranch the name of git branch
370 */
371def postGerritReview(credentialsId, virtualEnv, repoDir, gitName, gitEmail, gitRemote, gitTopic, gitBranch) {
372 def python = new com.mirantis.mk.Python()
373 def cmdText = """
374 GIT_COMMITTER_NAME=${gitName} \
375 GIT_COMMITTER_EMAIL=${gitEmail} \
376 git review -r ${gitRemote} \
377 -t ${gitTopic} \
378 ${gitBranch}
379 """
380 sshagent([credentialsId]) {
381 dir(repoDir) {
382 python.runVirtualenvCommand(virtualEnv, cmdText)
383 }
384 }
385}