blob: 46278319b02317951ef3aa27ea23db208ac0eb87 [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)
Sergey Otpuschennikov8d1aec12020-06-17 00:54:07 +0400176 * @param gerritPort gerrit port (usually GERRIT_PORT property, default 29418)
Jakub Josef50c9c3a2017-04-10 14:32:35 +0200177 * @param gerritChangeNumber gerrit change number (usually GERRIT_CHANGE_NUMBER property)
178 * @param credentialsId jenkins credentials id for gerrit
Jakub Josef5aa33de2017-06-16 12:23:45 +0200179 * @param includeCurrentPatchset do you want to include current (last) patchset
Jakub Josef50c9c3a2017-04-10 14:32:35 +0200180 * @return gerrit change object
181 */
Sergey Otpuschennikov8d1aec12020-06-17 00:54:07 +0400182def getGerritChange(gerritName, gerritHost, gerritChangeNumber, credentialsId, includeCurrentPatchset = false, gerritPort = '29418'){
Jakub Josef50c9c3a2017-04-10 14:32:35 +0200183 def common = new com.mirantis.mk.Common()
184 def ssh = new com.mirantis.mk.Ssh()
185 ssh.prepareSshAgentKey(credentialsId)
Sergey Otpuschennikov8d1aec12020-06-17 00:54:07 +0400186 ssh.ensureKnownHosts("${gerritHost}:${gerritPort}")
Jakub Josef5aa33de2017-06-16 12:23:45 +0200187 def curPatchset = "";
188 if(includeCurrentPatchset){
189 curPatchset = "--current-patch-set"
190 }
Dmitry Burmistrov066f3d42022-10-28 15:49:33 +0400191 return common.parseJSON(ssh.agentSh(String.format("ssh -p %s %s@%s gerrit query ${curPatchset} --commit-message --format=JSON change:%s", gerritPort, gerritName, gerritHost, gerritChangeNumber)))
Jakub Josef50c9c3a2017-04-10 14:32:35 +0200192}
193
Jakub Josef4edd7432017-05-10 17:58:56 +0200194/**
195 * Returns list of Gerrit trigger requested builds
196 * @param allBuilds list of all builds of some job
197 * @param gerritChange gerrit change number
198 * @param excludePatchset gerrit patchset number which will be excluded from builds, optional null
199 */
200@NonCPS
201def getGerritTriggeredBuilds(allBuilds, gerritChange, excludePatchset = null){
202 return allBuilds.findAll{job ->
203 def cause = job.causes[0]
Jakub Josefec5098f2017-06-15 18:15:32 +0200204 if(cause instanceof GerritCause &&
Denis Egorenkof0568dd2019-01-16 13:53:34 +0400205 (cause.getEvent() instanceof com.sonymobile.tools.gerrit.gerritevents.dto.events.PatchsetCreated ||
206 cause.getEvent() instanceof com.sonymobile.tools.gerrit.gerritevents.dto.events.CommentAdded)) {
Jakub Josef4edd7432017-05-10 17:58:56 +0200207 if(excludePatchset == null || excludePatchset == 0){
208 return cause.event.change.number.equals(String.valueOf(gerritChange))
209 }else{
210 return cause.event.change.number.equals(String.valueOf(gerritChange)) && !cause.event.patchSet.number.equals(String.valueOf(excludePatchset))
211 }
212 }
213 return false
214 }
215}
Jakub Josef62899fd2017-06-15 18:53:46 +0200216/**
Jakub Josef5aa33de2017-06-16 12:23:45 +0200217 * Returns boolean result of test given gerrit patchset for given approval type and value
218 * @param patchset gerrit patchset
Jakub Josef62899fd2017-06-15 18:53:46 +0200219 * @param approvalType type of tested approval (optional, default Verified)
Jakub Josef798bfc52017-06-16 12:44:23 +0200220 * @param approvalValue value of tested approval (optional, default empty string which means any value)
Jakub Josef62899fd2017-06-15 18:53:46 +0200221 * @return boolean result
Jakub Josef5aa33de2017-06-16 12:23:45 +0200222 * @example patchsetHasApproval(gerrit.getGerritChange(*,*,*,*, true).currentPatchSet)
Jakub Josef62899fd2017-06-15 18:53:46 +0200223 */
Jakub Josef5aa33de2017-06-16 12:23:45 +0200224@NonCPS
Jakub Josef798bfc52017-06-16 12:44:23 +0200225def patchsetHasApproval(patchSet, approvalType="Verified", approvalValue = ""){
Jakub Josef5aa33de2017-06-16 12:23:45 +0200226 if(patchSet && patchSet.approvals){
227 for(int i=0; i < patchSet.approvals.size();i++){
228 def approval = patchSet.approvals.get(i)
Jakub Josef798bfc52017-06-16 12:44:23 +0200229 if(approval.type.equals(approvalType)){
230 if(approvalValue.equals("") || approval.value.equals(approvalValue)){
231 return true
Jakub Josef996ada82017-06-16 12:59:43 +0200232 }else if(approvalValue.equals("+") && Integer.parseInt(approval.value) > 0) {
233 return true
234 }else if(approvalValue.equals("-") && Integer.parseInt(approval.value) < 0) {
235 return true
Jakub Josef798bfc52017-06-16 12:44:23 +0200236 }
Jakub Josef5aa33de2017-06-16 12:23:45 +0200237 }
238 }
Jakub Josef62899fd2017-06-15 18:53:46 +0200239 }
240 return false
241}
Jakub Josef4edd7432017-05-10 17:58:56 +0200242
Jakub Josefd383f392017-03-29 16:52:04 +0200243@NonCPS
244def _getGerritParamsFromUrl(gitUrl){
245 def gitUrlPattern = Pattern.compile("(.+):\\/\\/(.+)@(.+):(.+)\\/(.+)")
246 def gitUrlMatcher = gitUrlPattern.matcher(gitUrl)
247 if(gitUrlMatcher.find() && gitUrlMatcher.groupCount() == 5){
248 return [gitUrlMatcher.group(1),gitUrlMatcher.group(2),gitUrlMatcher.group(3),gitUrlMatcher.group(4),gitUrlMatcher.group(5)]
249 }
250 return []
251}
252
Dmitry Pyzhov169f8122017-12-06 14:44:41 +0300253def _getInvalidGerritParams(LinkedHashMap config){
254 def requiredParams = ["gerritScheme", "gerritName", "gerritHost", "gerritPort", "gerritProject", "gerritBranch"]
255 def missedParams = requiredParams - config.keySet()
256 def badParams = config.subMap(requiredParams).findAll{it.value in [null, '']}.keySet()
257 return badParams + missedParams
vnaumov37b735d2018-08-27 16:55:07 +0400258}
Denis Egorenko3253f462018-12-05 19:05:41 +0400259
260/**
261 * Post Gerrit comment from CI user
262 *
263 * @param config map which contains next params:
264 * gerritName - gerrit user name (usually GERRIT_NAME property)
265 * gerritHost - gerrit host (usually GERRIT_HOST property)
266 * gerritChangeNumber - gerrit change number (usually GERRIT_CHANGE_NUMBER property)
267 * gerritPatchSetNumber - gerrit patch set number (usually GERRIT_PATCHSET_NUMBER property)
268 * message - message to send to gerrit review patch
269 * credentialsId - jenkins credentials id for gerrit
270 */
271def postGerritComment(LinkedHashMap config) {
272 def common = new com.mirantis.mk.Common()
273 def ssh = new com.mirantis.mk.Ssh()
274 String gerritName = config.get('gerritName')
275 String gerritHost = config.get('gerritHost')
276 String gerritChangeNumber = config.get('gerritChangeNumber')
277 String gerritPatchSetNumber = config.get('gerritPatchSetNumber')
278 String message = config.get('message')
279 String credentialsId = config.get('credentialsId')
280
281 ssh.prepareSshAgentKey(credentialsId)
282 ssh.ensureKnownHosts(gerritHost)
283 ssh.agentSh(String.format("ssh -p 29418 %s@%s gerrit review %s,%s -m \"'%s'\" --code-review 0", gerritName, gerritHost, gerritChangeNumber, gerritPatchSetNumber, message))
284}
Denis Egorenko26da6c12018-11-16 14:38:42 +0400285
286/**
287 * Return map of dependent patches info for current patch set
288 * based on commit message hints: Depends-On: https://gerrit_address/_CHANGE_NUMBER_
289 * @param changeInfo Map Info about current patch set, such as:
290 * gerritName Gerrit user name (usually GERRIT_NAME property)
291 * gerritHost Gerrit host (usually GERRIT_HOST property)
292 * gerritChangeNumber Gerrit change number (usually GERRIT_CHANGE_NUMBER property)
293 * credentialsId Jenkins credentials id for gerrit
294 * @return map of dependent patches info
295 */
296LinkedHashMap getDependentPatches(LinkedHashMap changeInfo) {
297 def dependentPatches = [:]
Vladimir Khlyunev8091f632023-01-13 12:55:47 +0000298 def currentChange = getGerritChange(changeInfo.gerritName, changeInfo.gerritHost, changeInfo.gerritChangeNumber, changeInfo.credentialsId, true)
299 def dependentCommits = currentChange.commitMessage.tokenize('\n').findAll { it ==~ /Depends-On: \b[^ ]+\b(\/)?/ }
Denis Egorenko26da6c12018-11-16 14:38:42 +0400300 if (dependentCommits) {
301 dependentCommits.each { commit ->
302 def patchLink = commit.tokenize(' ')[1]
303 def changeNumber = patchLink.tokenize('/')[-1].trim()
304 def dependentCommit = getGerritChange(changeInfo.gerritName, changeInfo.gerritHost, changeNumber, changeInfo.credentialsId, true)
305 if (dependentCommit.status == "NEW") {
306 dependentPatches[dependentCommit.project] = [
307 'number': dependentCommit.number,
308 'ref': dependentCommit.currentPatchSet.ref,
309 'branch': dependentCommit.branch,
310 ]
311 }
312 }
313 }
314 return dependentPatches
315}
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300316
317/**
318 * Find Gerrit change(s) according to various input parameters like owner, topic, etc.
319 * @param gerritAuth A map containing information about Gerrit. Should include
320 * HOST, PORT and USER
321 * @param changeParams Parameters to identify Geriit change e.g.: owner, topic,
322 * status, branch, project
Alexandr Lovtsov5d22b2d2020-12-16 14:52:57 +0300323 * @param extraFlags Additional flags for gerrit querry for example
324 * '--current-patch-set' or '--comments' as a simple string
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300325 */
azvyagintseva00870c2023-12-07 12:59:12 +0200326def findGerritChange(credentialsId, LinkedHashMap gerritAuth, LinkedHashMap changeParams, String extraFlags = '', String sshOpts = '') {
piudin335f1bc2025-09-18 22:37:13 +0400327 def scriptText = """
azvyagintseva00870c2023-12-07 12:59:12 +0200328 ssh ${sshOpts} -p ${gerritAuth['PORT']} ${gerritAuth['USER']}@${gerritAuth['HOST']} \
Alexandr Lovtsov5d22b2d2020-12-16 14:52:57 +0300329 gerrit query ${extraFlags} \
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300330 --format JSON \
331 """
piudin335f1bc2025-09-18 22:37:13 +0400332 def jsonChange
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300333 changeParams.each {
334 scriptText += " ${it.key}:${it.value}"
335 }
336 scriptText += " | fgrep -v runTimeMilliseconds || :"
337 sshagent([credentialsId]) {
338 jsonChange = sh(
azvyagintseva00870c2023-12-07 12:59:12 +0200339 script: scriptText,
340 returnStdout: true,
341 ).trim()
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300342 }
343 return jsonChange
344}
345
346/**
347 * Download Gerrit review by number
348 *
349 * @param credentialsId credentials ID
350 * @param virtualenv virtualenv path
351 * @param repoDir repository directory
352 * @param gitRemote the value of git remote
353 * @param changeNum the number of change to download
354 */
355def getGerritChangeByNum(credentialsId, virtualEnv, repoDir, gitRemote, changeNum) {
356 def python = new com.mirantis.mk.Python()
357 sshagent([credentialsId]) {
358 dir(repoDir) {
359 python.runVirtualenvCommand(virtualEnv, "git review -r ${gitRemote} -d ${changeNum}")
360 }
361 }
362}
363
364/**
365 * Post Gerrit review
366 * @param credentialsId credentials ID
367 * @param virtualenv virtualenv path
368 * @param repoDir repository directory
369 * @param gitName committer name
370 * @param gitEmail committer email
371 * @param gitRemote the value of git remote
372 * @param gitTopic the name of the topic
373 * @param gitBranch the name of git branch
374 */
375def postGerritReview(credentialsId, virtualEnv, repoDir, gitName, gitEmail, gitRemote, gitTopic, gitBranch) {
376 def python = new com.mirantis.mk.Python()
Dmitry Teselkin3bb1c032019-12-12 19:33:58 +0300377 def common = new com.mirantis.mk.Common()
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300378 def cmdText = """
379 GIT_COMMITTER_NAME=${gitName} \
380 GIT_COMMITTER_EMAIL=${gitEmail} \
381 git review -r ${gitRemote} \
382 -t ${gitTopic} \
383 ${gitBranch}
384 """
385 sshagent([credentialsId]) {
386 dir(repoDir) {
Dmitry Teselkin3bb1c032019-12-12 19:33:58 +0300387 res = python.runVirtualenvCommand(virtualEnv, cmdText)
388 common.infoMsg(res)
Oleksii Grudeva64e5b22019-06-11 11:21:02 +0300389 }
390 }
391}
Denis Egorenkof35e00b2019-11-13 16:29:50 +0400392
393/**
394 * Prepare and upload Gerrit commit from prepared repo
395 * @param LinkedHashMap params dict with parameters
396 * venvDir - Absolute path to virtualenv dir
397 * gerritCredentials - credentialsId
398 * gerritHost - gerrit host
399 * gerritPort - gerrit port
400 * repoDir - path to repo dir
401 * repoProject - repo name
402 * repoBranch - repo branch
403 * changeCommitComment - comment for commit message
404 * changeAuthorName - change author
405 * changeAuthorEmail - author email
406 * changeTopic - change topic
407 * gitRemote - git remote
408 * returnChangeInfo - whether to return info about uploaded change
409 *
410 * @return map with change info if returnChangeInfo set to true
411*/
412def prepareGerritAutoCommit(LinkedHashMap params) {
413 def common = new com.mirantis.mk.Common()
414 def git = new com.mirantis.mk.Git()
415 String venvDir = params.get('venvDir')
416 String gerritCredentials = params.get('gerritCredentials')
417 String gerritHost = params.get('gerritHost', 'gerrit.mcp.mirantis.net')
418 String gerritPort = params.get('gerritPort', '29418')
419 String gerritUser = common.getCredentialsById(gerritCredentials, 'sshKey').username
420 String repoDir = params.get('repoDir')
421 String repoProject = params.get('repoProject')
422 String repoBranch = params.get('repoBranch', 'master')
423 String changeCommitComment = params.get('changeCommitComment')
424 String changeAuthorName = params.get('changeAuthorName', 'MCP-CI')
425 String changeAuthorEmail = params.get('changeAuthorEmail', 'mcp-ci-jenkins@ci.mcp.mirantis.net')
426 String changeTopic = params.get('changeTopic', 'auto_ci')
427 Boolean returnChangeInfo = params.get('returnChangeInfo', false)
428 String gitRemote = params.get('gitRemote', '')
429 if (! gitRemote) {
430 dir(repoDir) {
431 gitRemote = sh(
432 script:
433 'git remote -v | head -n1 | cut -f1',
434 returnStdout: true,
435 ).trim()
436 }
437 }
438 def gerritAuth = ['PORT': gerritPort, 'USER': gerritUser, 'HOST': gerritHost ]
439 def changeParams = ['owner': gerritUser, 'status': 'open', 'project': repoProject, 'branch': repoBranch, 'topic': changeTopic]
440 // find if there is old commit present
441 def gerritChange = findGerritChange(gerritCredentials, gerritAuth, changeParams)
442 def changeId = ''
443 if (gerritChange) {
444 try {
445 def jsonChange = readJSON text: gerritChange
446 changeId = "Change-Id: ${jsonChange['id']}".toString()
447 } catch (Exception error) {
448 common.errorMsg("Can't parse ouput from Gerrit. Check that user ${changeAuthorName} does not have several \
449 open commits to ${repoProject} repo and ${repoBranch} branch with topic ${changeTopic}")
450 throw error
451 }
452 }
453 def commitMessage =
454 """${changeCommitComment}
455
456 |${changeId}
457 """.stripMargin()
458 git.commitGitChanges(repoDir, commitMessage, changeAuthorEmail, changeAuthorName, false)
459 //post change
460 postGerritReview(gerritCredentials, venvDir, repoDir, changeAuthorName, changeAuthorEmail, gitRemote, changeTopic, repoBranch)
461 if (returnChangeInfo) {
462 gerritChange = findGerritChange(gerritCredentials, gerritAuth, changeParams)
463 jsonChange = readJSON text: gerritChange
464 return getGerritChange(gerritUser, gerritHost, jsonChange['number'], gerritCredentials, true)
465 }
466}
Mykyta Karpin0373e492020-04-17 11:30:19 +0300467
468/**
Mykyta Karpin82a2e2a2021-07-09 17:53:01 +0300469 * Get email and username from credentials id
470 * @param credentialsId Credentials ID to use for source Git
471 */
472def getGerritUserOptions(credentialsId) {
473 def changeAuthorName = "MCP-CI"
474 def changeAuthorEmail = "mcp-ci-jenkins@ci.mcp.mirantis.net"
475
476 switch (credentialsId) {
477 case 'mos-ci':
478 changeAuthorName = "MOS-CI"
479 changeAuthorEmail = "infra+mos-ci@mirantis.com"
480 break;
481 case 'mcp-ci-gerrit':
482 changeAuthorName = "MCP-CI"
483 changeAuthorEmail = "mcp-ci-jenkins@ci.mcp.mirantis.net"
484 break;
Vasyl Saienko56d08f42024-08-01 12:53:04 +0300485 case 'product-ci':
486 changeAuThorName = "Product CI"
487 changeAuthorEmail = "infra+product-ci@mirantis.com"
488 break;
Vasyl Saienko8ee26a02024-08-02 19:13:51 +0300489 case 'services-ci':
490 changeAuThorName = "Services CI"
491 changeAuthorEmail = "infra+services-ci@mirantis.com"
492 break;
Mykyta Karpin82a2e2a2021-07-09 17:53:01 +0300493 }
494 return ["changeAuthorName": changeAuthorName,
495 "changeAuthorEmail": changeAuthorEmail]
496}
497
498/**
Mykyta Karpin0373e492020-04-17 11:30:19 +0300499 * Download change from gerrit, if needed repository maybe pre cloned
500 *
501 * @param path Directory to checkout repository to
502 * @param url Source Gerrit repository URL
503 * @param reference Gerrit reference to download (e.g refs/changes/77/66777/16)
504 * @param type type of gerrit download
505 * @param credentialsId Credentials ID to use for source Git
506 * @param gitCheckoutParams map with additional parameters for git.checkoutGitRepository method e.g:
507 * [ branch: 'master', poll: true, timeout: 10, depth: 0 ]
508 * @param doGitClone boolean whether to pre clone and do some checkout
509 */
510def downloadChange(path, url, reference, credentialsId, type = 'cherry-pick', doGitClone = true, Map gitCheckoutParams = [:]){
Mykyta Karpin32cafd62021-07-01 17:28:03 +0300511 def common = new com.mirantis.mk.Common()
Mykyta Karpin0373e492020-04-17 11:30:19 +0300512 def git = new com.mirantis.mk.Git()
513 def ssh = new com.mirantis.mk.Ssh()
514 def cmd
Mykyta Karpin32cafd62021-07-01 17:28:03 +0300515 def credentials = common.getCredentialsById(credentialsId)
Mykyta Karpin82a2e2a2021-07-09 17:53:01 +0300516 Map gerritUserOptions = getGerritUserOptions(credentialsId)
Mykyta Karpin0ff68782021-07-09 18:38:43 +0300517 String gitCommiterOpts = "GIT_COMMITTER_EMAIL=\'${gerritUserOptions['changeAuthorEmail']}\' GIT_COMMITTER_NAME=\'${gerritUserOptions['changeAuthorName']}\'"
Mykyta Karpin0373e492020-04-17 11:30:19 +0300518 switch(type) {
519 case 'cherry-pick':
Mykyta Karpin82a2e2a2021-07-09 17:53:01 +0300520 cmd = "git fetch ${url} ${reference} && ${gitCommiterOpts} git cherry-pick FETCH_HEAD"
Mykyta Karpin0373e492020-04-17 11:30:19 +0300521 break;
522 case 'format-patch':
523 cmd = "git fetch ${url} ${reference} && git format-patch -1 --stdout FETCH_HEAD"
524 break;
525 case 'pull':
526 cmd = "git pull ${url} ${reference}"
527 break;
528 default:
529 error("Unsupported gerrit download type")
530 }
531 if (doGitClone) {
532 def branch = gitCheckoutParams.get('branch', 'master')
533 def poll = gitCheckoutParams.get('poll', true)
534 def timeout = gitCheckoutParams.get('timeout', 10)
535 def depth = gitCheckoutParams.get('depth', 0)
536 git.checkoutGitRepository(path, url, branch, credentialsId, poll, timeout, depth, '')
537 }
Mykyta Karpin32cafd62021-07-01 17:28:03 +0300538 sshagent (credentials: [credentialsId]) {
539 dir(path){
540 sh(script: "GIT_SSH_COMMAND='ssh -l ${credentials.username}' ${cmd}", returnStdout:true)
541 }
Mykyta Karpin0373e492020-04-17 11:30:19 +0300542 }
543}
544
545/**
546 * Parse gerrit event text and if Workflow +1 is detected,
547 * return true
548 *
549 * @param gerritEventCommentText gerrit event comment text
550 * @param gerritEventType type of gerrit event
551 */
552def isGate(gerritEventCommentText, gerritEventType) {
553 def common = new com.mirantis.mk.Common()
554 def gerritEventCommentTextStr = ''
555 def res = false
556 if (gerritEventCommentText) {
557 try{
558 gerritEventCommentTextStr = new String(gerritEventCommentText.decodeBase64())
559 } catch (e) {
560 gerritEventCommentTextStr = gerritEventCommentText
561 }
562 common.infoMsg("GERRIT_EVENT_COMMENT_TEXT is ${gerritEventCommentTextStr}")
563 }
Mykyta Karpin4d03a402022-12-28 15:21:24 +0200564 def gateComment = gerritEventCommentTextStr.split('\n')[0]
Mykyta Karpinf96210a2022-12-29 09:30:50 +0200565 if (gerritEventType == 'comment-added' && gateComment =~ /^Patch Set \d+:\s.*Workflow\+1.*$/) {
Mykyta Karpin0373e492020-04-17 11:30:19 +0300566 common.infoMsg("Running in gate mode")
567 res = true
568 }
569 return res
570}
Ihor Hryshchenko4801bd52023-11-29 18:07:02 +0200571
572/**
573 * Get gerrit change based on a given refspec.
574 *
575 * @param gerritAuth A map containing Gerrit authentication details (credentialsId, USER, HOST, PORT).
576 * @param repoName The name of the repository.
577 * @param refSpec The refspec or commit hash.
578 * @return A map containing information about the Gerrit change.
579 */
580def getGerritChangeInfoByRefspec(Map gerritAuth, String repoName, String refSpec) {
Ihor Hryshchenkof9221412024-12-03 12:53:29 +0200581 def common = new com.mirantis.mk.Common()
582 String commitHash = ''
583 if (refSpec.startsWith('refs/') || refSpec == 'master') {
584 // why we have retry here? bz infra is awesome and stable!
585 common.retry(15, 10) {
586 sshagent([gerritAuth['credentialsId']]) {
587 commitHash = sh(script: "git ls-remote ssh://${gerritAuth['USER']}@${gerritAuth['HOST']}:${gerritAuth['PORT']}/${repoName} ${refSpec}", returnStdout: true).trim().split('\\t')[0]
588 }
589 }
590 } else {
591 commitHash = refSpec
Ihor Hryshchenko4801bd52023-11-29 18:07:02 +0200592 }
Ihor Hryshchenkof9221412024-12-03 12:53:29 +0200593 if (!commitHash) {
594 common.errorMsg("Could not get commit hash for refspec '${refSpec}'")
595 return [:]
596 }
597 LinkedHashMap gerritQuery = [
598 'commit': commitHash,
599 ]
600 String gerritFlags = '--current-patch-set'
601 String patchsetRawJson = findGerritChange(gerritAuth['credentialsId'], gerritAuth, gerritQuery, gerritFlags)
602 if (!patchsetRawJson) {
603 // WARNING(alexz): gerrit search only by CR. so merge event,like https://gerrit.mcp.mirantis.com/plugins/gitiles/kaas/si-tests/+/cabb45ea73ac538c653d62cb0a2ffb4802521251
604 // will not be catched here. In that case, return dummy raw_commit_hash only
605 common.errorMsg("Could not find gerrit change as refspec '${refSpec}' (commit: ${commitHash})")
606 return ['raw_commit_hash': commitHash]
607 }
608 try {
609 // It can (and should) return only one json for given commitHash
610 return readYaml(text: patchsetRawJson)
611 } catch (Exception e) {
612 common.errorMsg("Could not parse JSON: ${patchsetRawJson}\nError: ${e}")
613 return [:]
614 }
Ihor Hryshchenko4801bd52023-11-29 18:07:02 +0200615}
Ihor Hryshchenkof9221412024-12-03 12:53:29 +0200616
617/**
618 * Get gerrit repository tags with gerrit API.
619 *
620 * @param repoName The name of the repository.
621 * @param credentialsId jenkins credentials id.
622 * @param trimTag Whether to trim the leading 'v' from tags.
623 * @return A list containing all repository tags.
624 */
625def listRepoTags(String repoName, String credentialsId, Boolean trimTag = false) {
626 def common = new com.mirantis.mk.Common()
627 def replacePattern = trimTag ? 'refs/tags/v' : 'refs/tags/'
628 def coreTags = ''
629 def tags = []
630 withCredentials([usernamePassword(credentialsId: credentialsId,
631 usernameVariable: 'GERRIT_USERNAME',
632 passwordVariable: 'GERRIT_PASSWORD')]) {
633 common.retry(15, 10) {
634 coreTags = sh(script: "echo \${GERRIT_PASSWORD} | curl -u ${GERRIT_USERNAME} https://gerrit.mcp.mirantis.com/a/projects/${repoName}/tags", returnStdout: true).trim()
635 }
636 }
637 def jsonResponse = coreTags.split('\n').last()
638 def jsonTags = readJSON text: jsonResponse
639 jsonTags.each { tagData ->
640 def tag = tagData['ref']?.replace(replacePattern, '')
641 if (tag) {
642 tags.add(tag)
643 }
644 }
645
646 return tags
647}