added files

This commit is contained in:
Your Name
2026-08-26 02:02:24 +05:30
parent 58ee8a276a
commit 3419cfba0c
200 changed files with 22132 additions and 0 deletions
+164
View File
@@ -0,0 +1,164 @@
package com.meesho.stages
def run(Map config){
stage('Build docker image'){
container('dockerpush') {
def env = "dev" //write if condition according to branch if branch is dev/staging or any other branch then master it will be dev if master prod
def ecrImagePod = config.ecrImagePod
def repo_name = config.repo_name
def dockerBuildVersion = config.dockerBuildVersion ?: ""
def version = getVersion()
def artifactId = getArtifactId()
def modules = getModules()
def excludedModules = config.excludedModules ?: []
j = 0
for (module in modules){
if (excludedModules.contains(module)){
echo "===== NOT Building container for Module - ${module} ====="
}else {
def dockerfileRef = getDockerfile("${dockerBuildVersion}","${repo_name}","${version}","${module}","NO")
j+=1
sh """
echo "===== ${j}. Creating Container for Module - ${module} ======"
if [ -f ${module}/target/*.jar ]
then
echo "docker build -t ${ecrImagePod}/${repo_name}/${module} ${dockerfileRef}; rm -f Dockerfile-${module}"
else
echo "No jar file found."
exit 1
fi
"""
}
}
}
}
}
def getCommitid(){
return sh(returnStdout: true, script: 'git log -1 --format=%h').trim()
}
def registry(String buildenv){
switch(env) {
case "prod":
return 'asia-southeast1-docker.pkg.dev/meesho-devops-admin-0622/prod/'
break;
case "dev":
return 'asia-southeast1-docker.pkg.dev/meesho-devops-admin-0622/dev/'
break;
default:
return 'asia-southeast1-docker.pkg.dev/meesho-devops-admin-0622'
}
}
def getVersion(){
// if (fileExists("pom.xml")) {
// return sh(returnStdout: true, script: 'xq -r .project.version pom.xml').trim()
// }else if (fileExists("package.json")) {
// return sh(returnStdout: true, script: 'jq -r .version package.json').trim()
// }
return "2.0"
}
def getModules(){
// if (fileExists("pom.xml")) {
// modules = sh(returnStdout: true, script: 'xq -r .project.modules.module[] pom.xml 2>/dev/null || xq -r .project.modules.module pom.xml 2>/dev/null || echo empty').trim()
// if (modules == 'empty' || modules == 'null') {
// return null
// }
// modules = modules.split("\n")
// return modules
// }
// else {
// return null
// }
return ["server"]
}
def getArtifactId(){
// if (fileExists("pom.xml")) {
// return sh(returnStdout: true, script: 'xq -r .project.artifactId pom.xml').trim()
// } else if (fileExists("package.json")) {
// return sh(returnStdout: true, script: 'jq -r .name package.json').trim()
// }
return "external-payment-gateway"
}
def getTag(){
def buildenv = "${env.BUILD_ENV}"
echo "========get version========"
def version = getVersion()
echo "========commitID========"
def commitID = getCommitid()
echo "========tag========"
def tag = "v${version}-${commitID}"
return ${tag}
}
def release(Map config){
stage('Push docker image'){
def repo_name = config.repo_name
def branch_name = "${env.BRANCH_NAME}"
def ecrImagePod = config.ecrImagePod
def bu = config.BU
def buildenv = "${env.BUILD_ENV}"
echo "========registry========"
def registry = registry("${buildenv}")
echo "========get version========"
def version = getVersion()
echo "========commitID========"
def commitID = getCommitid()
echo "========tag========"
echo "registry is ${registry}"
def tag = "v${version}-${commitID}"
echo "${registry}"
echo "tag is ${tag}"
def artifactId = getArtifactId()
def modules = getModules()
def excludedModules = config.excludedModules ?: []
log.info("########################### Pushing Docker Images ###########################")
if (modules == null){
echo "docker tag ${ecrImagePod}/${repo_name} ${registry}/${bu}/${ecrImagePod}/${repo_name}:${tag}"
echo "gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet"
echo "docker push ${registry}/${bu}/${ecrImagePod}/${repo_name}:${tag}"
}else {
j = 0
for (module in modules){
if (excludedModules.contains(module)){
echo "===== NOT Pushing container for Module - ${common} ====="
}else {
j+=1
sh """
echo "===== ${j}. Pushing Container for Module - ${module} ======"
echo "docker tag ${ecrImagePod}/${repo_name}/${module} ${registry}/${bu}/${ecrImagePod}/${repo_name}/${module}:${tag}"
echo "gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet"
echo "docker push ${registry}/${bu}/${ecrImagePod}/${repo_name}/${module}:${tag}"
"""
}
}
}
}
}
def getDockerfile(String dockerBuildVersion, String repo_name, String version, String module, String doBuild) {
if (dockerBuildVersion) {
if(dockerBuildVersion == "maven-3.3-jdk-8"){
def scriptcontents = libraryResource "org/meesho/templates/${dockerBuildVersion}.sh"
writeFile file: "${dockerBuildVersion}.sh", text: scriptcontents
sh "chmod a+x ./${dockerBuildVersion}.sh;./${dockerBuildVersion}.sh ${module} ${version} ${doBuild} ${repo_name}"
return "-f Dockerfile-${module} ."
}else if(dockerBuildVersion == "node-12.22"){
def scriptcontents = libraryResource "org/meesho/templates/${dockerBuildVersion}.sh"
writeFile file: "${dockerBuildVersion}.sh", text: scriptcontents
sh "chmod a+x ./${dockerBuildVersion}.sh;./${dockerBuildVersion}.sh ${module}"
return "-f Dockerfile-${module} ."
}
} else {
return "-f Dockerfile ."
}
}
+43
View File
@@ -0,0 +1,43 @@
import com.meesho.stages.checkOut
import com.meesho.stages.buildObjHelper
import com.meesho.stages.notify
import com.meesho.stages.securityScan
import com.meesho.stages.automationTest
import com.meesho.utilities.constructParam
def call(Map param) {
def constructParam = new constructParam()
def checkObj = new checkOut()
def buildObjHelper = new buildObjHelper()
def buildObj = buildObjHelper.run(param.build_tool)
def notify = new notify()
def securityScan = new securityScan()
def automationTest = new automationTest()
node('slave02') {
env.msg = 'Job Passed'
timestamps {
ansiColor('xterm') {
try {
constructParam.run(param)
log.info(param)
checkObj.run(param)
// securityScan.run(param)
buildObj.run(param)
if (param.containsKey('run_automation') && env.CHANGE_ID) {
automationTest.run(param)
}
}
catch (Exception e) {
if (env.msg == 'Job Passed') {
log.error(e.toString())
currentBuild.result = env.FAILURE
env.msg = 'Job Failed. Error: ' + e.toString()
}
}
finally {
notify.run(param)
}
}
}
}
}
+195
View File
@@ -0,0 +1,195 @@
// cdHookRunner — standalone Jenkins job that runs turbo-turtle deploy-phase
// CI/CD hooks (pre_deploy / post_deploy) in an isolated agent pod.
//
// turbo-turtle triggers this parameterized job during the CD workflow (see
// TriggerCdHookPipelineActivity). It checks out the target repo, reads the
// deploy-phase hooks declared under `environment.<TT_ENV>.hooks.<HOOK_PHASE>` in
// the per-app deployment file (deployments/<APP_NAME>.yaml), executes them via
// the shared runHooks execution core, and POSTs an aggregate completion callback
// to /api/v1/cd/hook/callback which signals the waiting workflow. A blocking hook
// failure makes success=false so the workflow can fail the deploy; advisory
// (blocking:false) failures keep success=true.
//
// Required parameters (Jenkins job params, read from params/env):
// REPO_URL, TT_REPO, APP_NAME, BRANCH, COMMIT, TT_ENV, HOOK_PHASE,
// IMAGE_TAG, TT_IS_HOTFIX, TT_PR_NUMBER, TT_WORKFLOW_ID, TT_RUN_ID
def call(Map jobParams = [:]) {
String repoUrl = param('REPO_URL', jobParams)
String repoName = param('TT_REPO', jobParams)
String appName = param('APP_NAME', jobParams)
String branch = param('BRANCH', jobParams)
String commit = param('COMMIT', jobParams)
String ttEnv = param('TT_ENV', jobParams)
String phase = param('HOOK_PHASE', jobParams)
String workflowID = param('TT_WORKFLOW_ID', jobParams)
String runID = param('TT_RUN_ID', jobParams)
if (!repoUrl && repoName) {
repoUrl = "https://github.com/Meesho/${repoName}"
}
// TT_WORKFLOW_ID / TT_RUN_ID are required: the completion callback correlates
// to the waiting Temporal run by them. If either is missing (e.g. incomplete
// job-param registration), fail before checkout/hook execution rather than run
// hooks whose callback can never be matched (workflow would wait to timeout).
if (!repoName || !appName || !ttEnv || !phase || !workflowID || !runID) {
error("cdHookRunner: TT_REPO, APP_NAME, TT_ENV, HOOK_PHASE, TT_WORKFLOW_ID and TT_RUN_ID are required parameters.")
}
// Make env available to stageName / the hook context.
env.cicd_environment = ttEnv
String podyaml = "org/meesho/${env.INFRA_ENV ?: 'prd'}-pod.yaml"
podTemplate(yaml: libraryResource(podyaml)) {
node(POD_LABEL) {
container('devops-tools') {
boolean success = true
String errMsg = ''
try {
stage(stageName("cd-hook checkout: ${repoName}")) {
deleteDir()
dir(repoName) {
checkout([
$class: 'GitSCM',
branches: [[name: commit ?: "*/${branch}"]],
userRemoteConfigs: [[url: repoUrl, credentialsId: 'svc-devops-meesho']],
extensions: [[$class: 'CloneOption', shallow: false, noTags: false]],
])
}
}
def hooks = resolveHooks(repoName, appName, ttEnv, phase)
if (!hooks) {
log.info("cdHookRunner: no ${phase} hooks declared for env ${ttEnv} in ${repoName}/deployments/${appName}.yaml — nothing to run.")
} else {
List<String> ctxEnv = deployPhaseContextEnv(repoName, ttEnv, jobParams)
runHooks.executeHooks(repoName, hooks, phase, ctxEnv)
}
} catch (Exception e) {
success = false
errMsg = e.toString()
log.error("cdHookRunner: ${phase} hook run failed for ${repoName} (env ${ttEnv}): ${errMsg}")
} finally {
postCdHookCallback(repoName, ttEnv, phase, success, errMsg, workflowID, runID)
}
if (!success) {
// Surface as a build failure too (the workflow reads the callback,
// but a red build aids debugging).
currentBuild.result = 'FAILURE'
error("cdHookRunner: ${phase} hooks failed for ${repoName}: ${errMsg}")
}
}
}
}
}
// resolveHooks reads the per-app deployment file (deployments/<appName>.yaml) and
// returns the deploy-phase hook list for env+phase (or null). Deploy-phase hooks
// live in deployment.yaml, not config.yaml.
def resolveHooks(String repoName, String appName, String ttEnv, String phase) {
dir(repoName) {
String deployFile = "deployments/${appName}.yaml"
if (!fileExists(deployFile)) {
error("cdHookRunner: ${deployFile} not found in ${repoName}.")
}
def deployment = readYaml file: deployFile
def envBlock = deployment?.environment?.get(ttEnv)
def hooksBlock = envBlock?.hooks
if (!(hooksBlock instanceof Map)) {
return null
}
def hooks = hooksBlock[phase]
return (hooks instanceof List && !hooks.isEmpty()) ? hooks : null
}
}
// deployPhaseContextEnv builds the TT_* context contract from the job params.
def deployPhaseContextEnv(String repoName, String ttEnv, Map jobParams) {
return [
"TT_REPO_NAME=${repoName}",
"TT_ENV=${ttEnv}",
"TT_EVENT=push",
"TT_IS_HOTFIX=${param('TT_IS_HOTFIX', jobParams) ?: 'false'}",
"TT_BRANCH=${param('BRANCH', jobParams) ?: ''}",
"TT_TARGET_BRANCH=",
"TT_PR_NUMBER=${param('TT_PR_NUMBER', jobParams) ?: ''}",
"TT_COMMIT_SHA=${param('COMMIT', jobParams) ?: ''}",
"TT_IMAGE_TAG=${param('IMAGE_TAG', jobParams) ?: ''}",
]
}
// postCdHookCallback POSTs the aggregate result to turbo-turtle, which signals
// the waiting CD workflow. Env-routed exactly like the Jenkins CI callback.
def postCdHookCallback(String repoName, String ttEnv, String phase, boolean success, String errMsg, String workflowID, String runID) {
String baseUrl
switch (ttEnv) {
case 'prd':
case 'int':
baseUrl = 'http://turbo-turtle.meeshogcp.in'
break
default:
baseUrl = 'http://turbo-turtle.admin.meeshogcp.in'
}
Map payload = [
repo_name : repoName,
env : ttEnv,
phase : phase,
success : success,
error : errMsg,
build_url : env.BUILD_URL ?: '',
workflow_id: workflowID,
run_id : runID,
]
String url = baseUrl + "/api/v1/cd/hook/callback"
String jsonFilePath = "cd_hook_callback_${env.BUILD_NUMBER}.json"
int maxAttempts = 3
// The callback is turbo-turtle's ONLY completion signal for the waiting run.
// Retry delivery (bounded, with connect/request timeouts) and, if every attempt
// fails, fail the job so the failure is visible — otherwise the job goes green
// while the workflow waits until its backstop timeout. Duplicate delivery is
// safe: turbo-turtle correlates callbacks by workflow/run/repo/env/phase.
try {
writeFile(file: jsonFilePath, text: writeJSON(returnText: true, json: payload))
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
boolean delivered = false
try {
final def(String response, String code) = sh(
returnStdout: true,
script: """
curl -s --connect-timeout 10 --max-time 30 -X POST \\
-H 'Content-Type: application/json' \\
-w '\\n%{response_code}' \\
$url \\
-d @$jsonFilePath
"""
).trim().tokenize("\n")
if (code == "200") {
log.info("cdHookRunner: callback delivered (attempt ${attempt}/${maxAttempts})")
delivered = true
} else {
log.warn("cdHookRunner: callback attempt ${attempt}/${maxAttempts} failed code=${code} response=${response}")
}
} catch (Exception e) {
log.warn("cdHookRunner: callback attempt ${attempt}/${maxAttempts} error: ${e}")
}
if (delivered) {
return
}
if (attempt < maxAttempts) {
sleep(time: attempt * 5, unit: 'SECONDS')
}
}
error("cdHookRunner: callback POST to ${url} failed after ${maxAttempts} attempts; turbo-turtle will not receive completion for repo=${repoName} env=${ttEnv} phase=${phase}")
} finally {
sh(script: "rm -f ${jsonFilePath}", returnStatus: true)
}
}
// param reads a job parameter, preferring an explicit map, then params, then env.
def param(String key, Map jobParams) {
if (jobParams?.containsKey(key)) {
return jobParams[key]?.toString()
}
if (params?.containsKey(key) && params[key] != null) {
return params[key].toString()
}
return env[key]?.toString()
}
+30
View File
@@ -0,0 +1,30 @@
pipeline {
agent none
environment {
K8S_LABEL = 'cloud-function-cicd-agent'
GITHUB_CRED = 'cicd-github-app'
}
podTemplate(yaml: libraryResource('org/meesho/pod-cloud-function.yaml')) {
node(POD_LABEL) {
container('devops-tools') {
cloudFunctionCICDFlow()
}
}
}
}
def cloudFunctionCICDFlow() {
stages {
stage {
steps {
script {
sh 'ls -al'
echo 'Hello World'
}
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
import com.meesho.stages.helmGenerator
import com.meesho.stages.validateBuTeam
def call(Map params) {
podTemplate(yaml: libraryResource("org/meesho/${env.INFRA_ENV}-pod.yaml")) {
node(POD_LABEL) {
container('devops-tools') {
timestamps {
ansiColor('xterm') {
env.GITHUB_CRED = 'svc-devops-meesho'
// Initialize objects
def validateObj = new validateBuTeam()
def hemlGenObj = new helmGenerator()
// validate parameters
validateObj.run(params.bu, params.team, params.module)
// Create helm file
hemlGenObj.run(params)
}
}
}
}
}
}
+150
View File
@@ -0,0 +1,150 @@
import com.meesho.stages.hotFix
import com.meesho.stages.checkOut
import com.meesho.stages.buildObjHelper
import com.meesho.stages.notify
import com.meesho.utilities.getYamlParameter
import com.meesho.utilities.constructParam
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatterBuilder
def call(Map repo) {
ansiColor('xterm') {
def allowedUsers = ['turbo-turtle', 'ringmaster-bot', 'yeleswaram.teja@meesho.com', 'mahak.jain@meesho.com', 'naveen.vellingiri@meesho.com', 'vignesh.ganesan@meesho.com', 'siddharth.g@meesho.com','aryaman.parida@meesho.com','shatwik.pandey@meesho.com' ,'toolchain-jenkins']
def userId = null
try {
def cause = currentBuild.rawBuild.getCause(hudson.model.Cause.UserIdCause)
if (cause) {
userId = cause.getUserId()
}
} catch (Exception e) {
log.error("Failed to get user ID: ${e.message}")
}
if (userId && !allowedUsers.contains(userId)) {
echo "\u001B[1;31m========================================\n[ERROR] Build triggered by unauthorized user: ${userId}\n\nPlease use Ringmaster to trigger builds and deployments: https://ringmaster.meeshogcp.in/applications/cicd/home\n========================================\u001B[0m"
error("Build triggered by unauthorized user: ${userId}.")
} else if (!userId) {
echo "\u001B[1;31m========================================\n[ERROR] Could not determine the user who triggered the build\n\nThis might be a scheduled or system-triggered build.\n========================================\u001B[0m"
error("Could not determine the user who triggered the build")
} else {
echo "Starting the build....."
}
}
env.STARTTIME = getDateTime()
env.FAILURE = 'FAILURE'
echo "CLOUD_PROVIDER : ${env.CLOUD_PROVIDER}"
echo "INFRA_ENV : ${env.INFRA_ENV}"
switch (env.CLOUD_PROVIDER) {
case 'GCP':
echo 'Running on GCP'
gcpInfra(repo)
break
case 'AWS':
echo 'Running on AWS'
awsInfra(repo)
break
default:
log.error('Not running on AWS or GCP')
currentBuild.result = env.FAILURE
def msg = 'Job Failed. Error: Not running on AWS or GCP'
notify.run(msg)
}
}
def gcpInfra (Map repo) {
def isSidecarNeeded = repo.get('useSidecar', false)
def yamlName = isSidecarNeeded ? "${env.INFRA_ENV}-sidecar-pod.yaml" : "${env.INFRA_ENV}-pod.yaml"
def podyaml = "org/meesho/${yamlName}"
echo "Architecture Check: useSidecar=${isSidecarNeeded}. Loading ${podyaml}"
podTemplate(yaml: libraryResource(podyaml)) {
node(POD_LABEL) {
container('devops-tools') {
commonCICDFlow(repo)
}
}
}
}
def awsInfra (Map repo) {
node('EKS') {
commonCICDFlow(repo)
}
}
def commonCICDFlow (Map repo) {
def constructParam = new constructParam()
def ymlObj = new getYamlParameter()
def checkObj = new checkOut()
def buildObjHelper = new buildObjHelper()
def hotFixObj = new hotFix()
def notify = new notify()
def param = [:]
def msg = 'Job Passed'
env.msg = msg
env.error_msg_to_db = ''
timestamps {
ansiColor('xterm') {
try {
env.deploymentStartTime = new Date().format('yyyy-MM-dd HH:mm:ss')
checkObj.run(repo)
param = ymlObj.getParam(repo.repo_name)
def maintainer = param.maintainer ?: 'jenkins-user'
def buildObj = buildObjHelper.run(param.build_tool)
constructParam.run(param)
param['skip_notify'] = env.skip_notify
log.info(param)
hotFixObj.run(param.repo_name)
// Pre-build hooks: user scripts declared under
// environment.<env>.hooks.pre_build in the repo's config.yaml.
// constructParam has merged the resolved env block into param,
// so param.hooks holds the current environment's hooks. A
// blocking hook failure fails the build before the docker build.
runHooks(param, 'pre_build')
buildObj.run(param)
// Post-build hooks: run after a successful build.
runHooks(param, 'post_build')
// Auto-trigger the standalone ai-blitz-jobs (coverage-only) job
// once the per-repo CI has succeeded. Internally gated on
// PR / hot-fix / toolchain / mainline-branch (see helper), and
// fire-and-forget — coverage observability must never block
// or fail this build. Repos opt out via skip_coverage_trigger
// in config.yaml.
triggerCoverageOnly(param)
}
catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) {
currentBuild.result = 'ABORTED'
log.info(currentBuild.result)
env.msg = 'Job Aborted'
env.error_msg_to_db = 'Job ABORTED by the User'
}
catch (Exception e) {
if (env.msg == msg) {
log.error(e.toString())
currentBuild.result = env.FAILURE
env.msg = 'Job Failed. Error: ' + e.toString()
}
}
finally {
env.deploymentEndTime = new Date().format('yyyy-MM-dd HH:mm:ss')
notify.run(param)
}
}
}
}
@NonCPS
def getDateTime() {
// Get the current date and time in IST
def currentDateTime = ZonedDateTime.now()
// Create a formatter for the desired pattern
def formatter = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
.appendOffset('+HH:mm', '+00:00')
.toFormatter()
// Format the current date and time using the formatter
def formattedDateTime = currentDateTime.format(formatter)
return formattedDateTime
}
+278
View File
@@ -0,0 +1,278 @@
import com.meesho.utilities.gitActions
import com.meesho.utilities.buTeamMapping
import com.meesho.utilities.getYamlParameter
import com.meesho.utilities.constructTemplate
import com.meesho.stages.multiBranchPipeline
import com.meesho.stages.deployArgoCD
def call(Map params){
podTemplate(yaml: libraryResource('org/meesho/pod.yaml')) {
node(POD_LABEL) {
container('devops-tools') {
timestamps{
ansiColor("xterm"){
env.GITHUB_CRED = 'svc-devops-meesho'
def gitObj = new gitActions()
def bu_team_obj = new buTeamMapping()
def constructObj = new constructTemplate()
def application_info_map = [:]
def repo_bu = ""
stage('App Repo: cloning the repo'){
gitObj.clone("${WORKSPACE}", params.repo_name, params.repo_main_branch)
}
stage('APP Repo: Create Jenkinsfile(gcp-main)'){
if (!params.skip_jenkinsfile){
def repo_branch_name = "gcp-main"
def config = ["repo_name":params.repo_name]
gitObj.branchCheckOut(params.repo_name, repo_branch_name)
dir(params.repo_name){
sh "chmod 777 Jenkinsfile"
constructObj.renderTemplate(config, 'Jenkinsfile', 'Jenkinsfile')
gitObj.add('.', 'Jenkinsfile')
}
def commit_status = gitObj.codeCommit(repo_name, repo_branch_name, 'Generating build and deployments files')
if (commit_status == 0) {
gitObj.codePush(repo_name, repo_branch_name)
}
}
else {
log.info("skipping this stage")
}
}
stage('Helm Repo: Update values_property file'){
def yaml_obj = new getYamlParameter()
gitObj.clone("${WORKSPACE}","devops-helm-charts","main")
def helm_repo_name = "devops-helm-charts"
def helm_checkout_branch = "gcp-migration-${params.repo_name}-${env.BUILD_NUMBER}"
def repo_branch_name = "gcp-main"
gitObj.branchCheckOut("devops-helm-charts", helm_checkout_branch)
gitObj.branchCheckOut(params.repo_name, repo_branch_name)
def build_config = yaml_obj.getParam(params.repo_name)
if (build_config['copy_file'] || (build_config['environment'] && build_config['environment']['prd']['copy_file'])){
// voilating naming convention willing to avoid confilct with deployment variables
log.info("Checking copy_file path")
def buSrt = bu_team_obj.get_bu_initials(build_config["bu"])
def teamSrt = bu_team_obj.get_team_initials(build_config["team"])
def copy_path = (build_config['copy_file'])? build_config.copy_file.path : build_config.environment.prd.copy_file.path
if (copy_path.startsWith("gs:")){
log.info("Copy file path already pointing to gcs")
} else {
def file_name = copy_path.split('/')[-1]
def new_path = "gs://gcs-${buSrt}-${teamSrt}-config-prd/${params.repo_name}/${file_name}"
if(build_config.copy_file){
build_config.copy_file.path = new_path
} else {
build_config.environment.prd.copy_file.path = new_path
}
dir(params.repo_name){
sh "chmod 777 config.yaml"
writeYaml file: 'config.yaml', data: build_config, overwrite: true
}
gitObj.add(params.repo_name, 'config.yaml')
def config_commit = gitObj.codeCommit(repo_name, repo_branch_name, 'Changed copy_file path to gcs')
if (config_commit == 0) {
gitObj.codePush(repo_name, repo_branch_name)
}
log.warning("Path for copy_file has been changed to gcs. Please upload the contents to ${new_path} from ${copy_path}")
}
}
if (build_config.containsKey('environment')) {
Map envrionment_config = build_config['environment'].collectEntries { key, value -> "prd".matches(key)? value: [:]}
build_config.remove('environment')
build_config.putAll(envrionment_config)
}
repo_bu = build_config.bu
env.skip_user_input = params.skip_userinput
def wait_obj = new deployArgoCD()
def deployment_order = build_config.deployment_order
timeout(unit: 'SECONDS', time: 300) {
userInput = wait_obj.wait_for_user_input(build_config.deployment_order)
}
if (userInput == '') {
log.info('No Deployments selected. Running remaining steps.')
return
}
else if (!userInput.contains('All')) {
deployment_order = userInput.split(',') as List
}
for (deployment in deployment_order){
def application_config = yaml_obj.getParam(params.repo_name,"deployments/${deployment}.yaml")
def bu_short = bu_team_obj.get_bu_initials(application_config["bu"])
def BU = application_config["bu"]
def team_short = bu_team_obj.get_team_initials(application_config["team"])
//def app_name_short = application_config.app_name.substring(0, Math.min(originalString.length(), 15))
def app_name_short = application_config.app_name
def values_version = env.cicd_environment == 'prd' ? 'values_v3' : 'values_v2'
def value_properties_path = "${values_version}/${bu_short}/${team_short}/${deployment}/values_properties.yaml"
def value_properties_content = yaml_obj.getParam(helm_repo_name,value_properties_path)
def canary_default = ['progressDeadlineSeconds': 300,'analysisInterval': '120s','analysisThreshold': 5,'analysisMaxWeight': 5,'analysisStepWeight': 5,'analysisMetrics':['thresholdRangeMin': 0.99,'interval': '1m'],'skipAnalysis': true]
// Set/change value_properties values for migration
value_properties_content['as_min']= 1
log.info("Setting canary skipAnalysis to true")
if (value_properties_content['canary']){
value_properties_content['canary']['skipAnalysis'] = true
} else {
value_properties_content['canary'] = canary_default
}
log.info("Canary skipAnalysis has been set to true")
log.info("set min and nodeselector")
if (value_properties_content['ingress_class']!= "contour-external"){
if(value_properties_content['host']){
value_properties_content.host = application_config['app_name']+".prd.meesho.int"
}
else if(value_properties_content.hosts){
for (host_arr in value_properties_content.hosts){
host_arr.host = application_config['app_name']+".prd.meesho.int"
}
}
if (application_config.priority_v2 == "up0" || application_config.priority_v2 == "sp0" || application_config.priority_v2 == "cp0"){
value_properties_content.ingress_class = "contour-internal-0"
}
else{
value_properties_content.ingress_class = "contour-internal-1"
}
}
else if (value_properties_content['ingress_class'] == "external"){
value_properties_content.ingress_class = "contour-external"
}
log.info("set host")
if (value_properties_content.serviceAccount){
value_properties_content.serviceAccount.annotations.remove('eks.amazonaws.com/role-arn')
value_properties_content.serviceAccount.annotations['iam.gke.io/gcp-service-account'] = "sa-${bu_short}-prd-${app_name_short}@meesho-${BU}-prd-0622.iam.gserviceaccount.com"
}
log.info("set service account")
dir(helm_repo_name){
sh "chmod 777 ${value_properties_path}"
writeYaml file: value_properties_path, data: value_properties_content, overwrite: true
}
def host = (value_properties_content.host)? value_properties_content.host : value_properties_content.hosts[0].host
log.info(host)
application_info_map[application_config.app_name]=[
"bu": application_config.bu,
"team": application_config.team,
"host": host,
"ingress_class": value_properties_content.ingress_class
]
log.info(application_info_map)
gitObj.add(helm_repo_name,value_properties_path)
}
commit_status = gitObj.codeCommit(helm_repo_name, helm_checkout_branch, "Modified all application of ${params.repo_name} repo")
if (commit_status == 0) {
try{
gitObj.codePush(helm_repo_name, helm_checkout_branch)
}
catch(Exception e){
log.error("Error in code push")
return
}
try{
pr_num = gitObj.createPR(params.repo_name, helm_repo_name, "main", helm_checkout_branch, "Merge ${params.repo_name} changes")
gitObj.mergePR(helm_repo_name, pr_num, helm_checkout_branch)
gitObj.deleteBranch(helm_repo_name, "main", helm_checkout_branch)
}
catch(Exception ex){
log.error("error in code push")
log.error(ex)
log.info("Deleting the branch")
gitObj.deleteBranch(helm_repo_name, "main", helm_checkout_branch)
}
}
}
stage('Jenkins: Create Jenkins job'){
def job_obj = new multiBranchPipeline()
job_obj.applicationOnboard(repo_bu,params.repo_name)
log.info("Jenkins job created")
}
// stage('GCP: Create DNS record'){
// def zone="pvt-meesho-admin-prd-meesho-int"
// def project="meesho-admin-prd-0622"
// def app_name_list = application_info_map.collect{ it.key }
// log.info(app_name_list)
// for (app_name in app_name_list) {
// def host=application_info_map[app_name].host
// def ingress_class=application_info_map[app_name].ingress_class
// def BU=application_info_map[app_name].bu
// def dns_status= sh(script: "gcloud dns --project=${project} record-sets describe ${host} --zone=${zone} --type='CNAME' ",returnStatus: true)
// if( dns_status != 0 ){
// dns_status = sh(script: "gcloud dns --project=${project} record-sets create ${host} --zone=${zone} --type=CNAME --ttl='300' --rrdatas='${ingress_class}.${BU}.prd.prd.meesho.int.' ",returnStatus: true)
// if(dns_status == 0 ){
// log.info("DNS recordSet ${host} created successfully")
// }
// else{
// log.info("DNS recordset failed with status ${dns_status}")
// }
// }
// }
// }
// stage('GCP: Create workload identity'){
// def bu_short=bu_team_obj.get_bu_initials(application_info_map[app_name].bu)
// def BU = application_info_map[app_name].bu
// def project="meesho-${BU}-prd-0622"
// def app_name_list = application_info_map.collect{ it.key }
// for (app_name in app_name_list){
// if (params.provide_service_role){
// //def app_name_short= app_name.substring(0, Math.min(originalString.length(), 15))
// def app_name_short=app_name
// def sa = "sa-${bu_short}-prd-${app_name_short}"
// sh(script: "gcloud iam service-accounts create ${sa} --display-name='service account for ${app_name}' --project=${project} ")
// sh(script: "gcloud iam service-accounts add-iam-policy-binding --role roles/iam.workloadIdentityUser --member 'serviceAccount:${sa}@meesho-${BU}-prd-0622.iam.gserviceaccount.com' --project=meesho-${BU}-prd-0622")
// sh(script: "gcloud iam service-accounts add-iam-policy-binding --role roles/${params.SA_role} --member 'serviceAccount:${sa}@meesho-${BU}-prd-0622.iam.gserviceaccount.com' --project=meesho-${BU}-prd-0622")
// }
// }
// }
stage('CoreDNS: Add entry for svc-svc'){
def yaml_obj = new getYamlParameter()
def coredns_repo_name = "devops-infra-helm-charts"
def coredns_checkout_branch = "gcp-migration-${params.repo_name}-${env.BUILD_NUMBER}"
gitObj.clone("${WORKSPACE}",coredns_repo_name,"main")
gitObj.branchCheckOut(coredns_repo_name, coredns_checkout_branch)
def coredns_path= "helm-templates/coredns/values.yaml"
def coredns_content = yaml_obj.getParam(coredns_repo_name,coredns_path)
def app_name_list = application_info_map.collect{ it.key }
for (app_name in app_name_list) {
def host=application_info_map[app_name].host
def ingress_class=application_info_map[app_name].ingress_class
def BU=application_info_map[app_name].bu
coredns_content.rewrites[host]="${ingress_class}-${BU}-prd"
log.info("coredns entry for ${host} is set")
dir(coredns_repo_name){
sh "chmod 777 ${coredns_path}"
writeYaml file: coredns_path, data: coredns_content, overwrite: true
}
gitObj.add(coredns_repo_name,coredns_path)
}
commit_status = gitObj.codeCommit(coredns_repo_name, coredns_checkout_branch, "Modified all application of ${params.repo_name} repo")
if (commit_status == 0) {
try{
gitObj.codePush(coredns_repo_name, coredns_checkout_branch)
}
catch(Exception e){
log.error("Error in code push")
return
}
try{
pr_num = gitObj.createPR(params.repo_name, coredns_repo_name, "main", coredns_checkout_branch, "Merge ${params.repo_name} changes")
gitObj.mergePR(coredns_repo_name, pr_num, coredns_checkout_branch)
gitObj.deleteBranch(coredns_repo_name, "main", coredns_checkout_branch)
}
catch(Exception ex){
log.error("error in code push")
log.error(ex)
log.info("Deleting the branch")
gitObj.deleteBranch(coredns_repo_name, "main", coredns_checkout_branch)
}
}
}
}
}
}
}
}
}
+202
View File
@@ -0,0 +1,202 @@
import com.meesho.stages.buildDocker
import com.meesho.stages.notify
def Codecall(Map stepParams) {
def buildDocker = new buildDocker()
def notify = new notify()
checkOutCode()
config = readYaml file: "${stepParams.file}"
def maintainer = config.maintainer ?: "jenkins-user"
def msg = "Job Passed"
try{
mvnBuild()
dockerBuild(config)
}
catch(Exception e){
log.error(e.toString())
currentBuild.result = env.FAILURE
msg = "Job Failed. Error: "+ e.toString()
}
finally{
log.info(msg)
notify.run(msg,maintainer)
}
}
def checkOutCode () {
stage('Checking Out Code') {
checkout scm
}
}
def mvnBuild() {
stage('Maven Build') {
container('maven') {
sh "mvn clean install -DskipTests"
}
}
}
def Podcall(Map stepParams) {
podTemplate(
yaml: libraryResource('org/meesho/pod.yaml')) {
node(POD_LABEL) {
Codecall(file: "$stepParams.file")
}
}
}
def dockerBuild(Map config) {
// container('dockerpush') {
buildDockerGroovyGke.run(config)
// buildDockerGroovyGke.release(config)
// }
}
// container('tools') {
// stage('Dockerfile creation') {
// if (config.dockerfilePresent == "Yes"){
// sh ''' docker build -t "${config.gcrAddress}/${config.repo_name} -f ${config.dockerFileName}" '''
// }
// else{
// def artifactId = config.artifactId
// def version = config.version
// def doBuild = "NO"
// sh "ls -al"
// echo "chmod after creating dockerfile"
// sh "cd server ; ls"
// // sh "cp /root/.m2/repository/com/meesho/payment/gateway/server/2.9.1/server-2.9.1.jar ."
// def dockerfile = buildDocker.getDockerfile(config.dockerBuildVersion, config.repo_name, version, artifactId, doBuild)
// echo "ls after creating dockerfile"
// sh "ls "
// def tag = config.version
// sh "gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet"
// sh "docker build -t asia-southeast1-docker.pkg.dev/supply-poc-351106/meesho-devops/external-payment-gateway:${tag} ${dockerfile}"
// sh "docker push asia-southeast1-docker.pkg.dev/supply-poc-351106/meesho-devops/external-payment-gateway:${tag}"
// // echo "docker build -t ${config.gcrImagePod}/${config.repo_name} ${dockerfile}"
// // echo "docker tag ${config.gcrImagePod}/${config.repo_name} ${config.gcrAddress}/${config.gcrImagePod}/${config.repo_name}:${tag}"
// // echo "docker push ${config.gcrAddress}/${config.gcrImagePod}/${config.repo_name}:${tag}"
// }
// }
// }
// }
+24
View File
@@ -0,0 +1,24 @@
def RED = '\033[1;31m'
def GREEN = '\033[1;32m'
def BLUE = '\033[1;34m'
def BLACK = '\033[0m'
def CYAN_BG = '\033[1;46m'
def YELLOW_BG = '\033[1;43m'
def info(message) {
def GREEN = '\033[1;32m'
def BLACK = '\033[0m'
echo "${GREEN}INFO: ${message}${BLACK}"
}
def warning(message) {
def RED = '\033[1;31m'
def BLACK = '\033[0m'
echo "${RED}WARNING: ${message}${BLACK}"
}
def error(message){
def RED = '\033[1;31m'
def BLACK = '\033[0m'
echo "${RED}ERROR: ${message}${BLACK}"
}
+115
View File
@@ -0,0 +1,115 @@
pipeline {
agent {
kubernetes {
yamlFile "resources/org/meesho/${env.INFRA_ENV}-pod.yaml"
defaultContainer 'devops-tools'
}
}
environment {
GITHUB_CRED = 'svc-devops-meesho'
}
parameters {
string(name: 'repo_name', defaultValue: '', trim: true, description: 'Please enter repo name.')
string(name: 'branch_name', defaultValue: 'main', trim: true, description: 'Please enter branch name.')
choice(name: 'jdk_version', choices: ['jdk8', 'jdk11', 'jdk17', 'jdk21', 'jdk25'], description: 'Please select JDK Version. For adding new JDK Version, please contact DevOps Team.')
booleanParam(name: 'skip_tests', defaultValue: true, description: 'This option will skip tests while mvn build.')
booleanParam(name: 'sonar_scan', defaultValue: true, description: 'This option will perform static analysis of code via SonarQube.')
booleanParam(name: 'sub_modules', defaultValue: false, description: 'This option will enable the use of Git submodules.')
}
stages {
stage('Validate Params') {
steps {
script {
echo '####################### Validating Parameters ###########################'
echo "Repo Name: ${params.repo_name}"
echo "Branch Name: ${params.branch_name}"
echo "JDK Version: ${params.jdk_version}"
echo "Skip Tests: ${params.skip_tests}"
echo "Sonar Scan: ${params.sonar_scan}"
echo "Sub module: ${params.sub_modules}"
}
}
}
stage('Build') {
steps {
ansiColor('xterm') {
script {
sh 'whoami'
def extra_args = ''
if (env.INFRA_ENV == 'prd') {
jfrog_repo = '-DuseProdRepo=true'
}
else {
jfrog_repo = '-DuseTestRepo=true'
}
if (skip_tests.toBoolean()) {
extra_args = '-DskipTests'
}
withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) {
sh "git clone -b ${branch_name} https://github.com/Meesho/${repo_name}.git"
}
if (sub_modules.toBoolean()) {
dir("${repo_name}") {
withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) {
sh(script:'git submodule init && git submodule update && git submodule update --recursive --remote')
}
}
}
def effective_jdk = params.jdk_version
dir("${repo_name}") {
if (fileExists('config.yaml')) {
def config = readYaml file: 'config.yaml'
def dv = config?.dockerBuildVersion?.toString() ?: ''
def matcher = (dv =~ /maven-.*-jdk-(\d+)/)
if (matcher) {
effective_jdk = "jdk${matcher[0][1]}"
echo "Using JDK from config.yaml dockerBuildVersion: ${dv} -> ${effective_jdk}"
}
}
}
if (effective_jdk == 'jdk25') {
env.JAVA_HOME = '/usr/lib/jvm/java-25-openjdk-amd64/'
}
else if (effective_jdk == 'jdk21') {
env.JAVA_HOME = '/usr/lib/jvm/java-21-openjdk-amd64/'
}
else if (effective_jdk == 'jdk17') {
env.JAVA_HOME = '/usr/lib/jvm/java-17-openjdk-amd64/'
}
else if (effective_jdk == 'jdk11') {
env.JAVA_HOME = '/usr/lib/jvm/java-11-openjdk-amd64/'
}
else {
env.JAVA_HOME = '/usr/lib/jvm/java-8-openjdk-amd64/'
}
echo "Resolved JAVA_HOME: ${env.JAVA_HOME} (effective_jdk: ${effective_jdk})"
dir("${repo_name}") {
echo '####################### Building Artifacts ###########################'
sh """
pwd
ls -al
mvn -v
mvn clean install ${extra_args}
"""
if (sonar_scan.toBoolean()) {
withSonarQubeEnv('sonarqube-test') {
echo '####################### Performing Sonar Scan ###########################'
sh(script: "JAVA_HOME='${env.JAVA_HOME}' mvn sonar:sonar -Dsonar.branch.name=${branch_name} -Dsonar.projectName=${repo_name}")
}
}
else {
echo '####################### Skipping Sonar Scan ###########################'
}
echo '####################### Pushing Artifacts to Jfrog ###########################'
sh(script: "mvn package deploy -DskipTests=${skip_tests} ${jfrog_repo}")
}
}
}
}
}
}
}
+160
View File
@@ -0,0 +1,160 @@
// runHooks — generic per-environment CI/CD hook runner (execution core).
//
// Executes user-provided scripts (shell or python) that live in the service
// repo and are declared under `environment.<env>.hooks.<phase>` in the repo's
// config.yaml. The platform stays generic: it only locates, contextualises
// (via TT_* env vars) and runs each script — all use-case logic lives in the
// script itself.
//
// Build-phase entry (call from commonCICDFlow, repo already checked out):
// runHooks(param, 'pre_build') // before docker build
// runHooks(param, 'post_build') // after a successful build
//
// Deploy-phase re-use (from cdHookRunner, after it checks out the repo):
// runHooks.executeHooks(repoName, hooks, phase, ctxEnv)
//
// By the time the build-phase entry runs, constructParam has merged the
// resolved environment block into `param` top-level, so the current env's hooks
// are at `param.hooks`. The repo is checked out into `${repoName}`, so scripts
// run with CWD at the repo root and can reach any repo file.
def call(Map param, String phase) {
def hooksBlock = param?.hooks
if (!(hooksBlock instanceof Map)) {
return
}
def hooks = hooksBlock[phase]
if (!(hooks instanceof List) || hooks.isEmpty()) {
return
}
String repoName = param?.repo_name
if (!repoName) {
error("runHooks: param.repo_name is missing — cannot locate hook scripts.")
}
executeHooks(repoName, hooks, phase, buildPhaseContextEnv(repoName, phase))
}
// executeHooks runs each hook spec in `hooks` inside the repo working directory,
// injecting `ctxEnv` (the TT_* context contract) plus a per-hook TT_HOOK_NAME.
// Reused by both the build-phase entry and the deploy-phase cdHookRunner.
def executeHooks(String repoName, def hooks, String phase, List<String> ctxEnv) {
hooks.eachWithIndex { hook, idx ->
runOneHook(repoName, phase, idx, hook, ctxEnv)
}
}
// runOneHook executes a single hook spec inside the repo working directory.
def runOneHook(String repoName, String phase, int idx, def hook, List<String> ctxEnv) {
String name = (hook?.name ?: "${phase}-${idx}").toString()
String script = hook?.script?.toString()
String interpreter = hook?.interpreter?.toString()
String requirements = hook?.requirements?.toString()
// Blocking by default; only an explicit `blocking: false` demotes to advisory.
boolean blocking = !(hook?.blocking?.toString() == 'false')
int timeoutSeconds = 600
if (hook?.timeout_seconds) {
try { timeoutSeconds = hook.timeout_seconds.toString().toInteger() } catch (ignored) { timeoutSeconds = 600 }
}
if (!script) {
error("runHooks: ${phase}[${idx}] '${name}' has no 'script' path in config.yaml.")
}
assertRepoRelative(script, "${phase}[${idx}] '${name}' script")
if (requirements) {
assertRepoRelative(requirements, "${phase}[${idx}] '${name}' requirements")
}
stage(stageName("${phase}: ${name}")) {
dir(repoName) {
if (!fileExists(script)) {
error("runHooks: ${phase} hook '${name}' script not found in repo: ${script}")
}
List<String> hookEnv = []
hookEnv.addAll(ctxEnv)
hookEnv.add("TT_HOOK_PHASE=${phase}")
hookEnv.add("TT_HOOK_NAME=${name}")
String runCmd = buildRunCommand(script, interpreter, requirements)
log.info("runHooks: executing ${phase} hook '${name}' (${script}), blocking=${blocking}, timeout=${timeoutSeconds}s")
withEnv(hookEnv) {
try {
timeout(time: timeoutSeconds, unit: 'SECONDS') {
sh(script: runCmd)
}
} catch (Exception e) {
if (blocking) {
log.error("runHooks: blocking ${phase} hook '${name}' failed: ${e}")
throw e
}
// Advisory hook — record and continue without failing the build.
log.warning("runHooks: advisory ${phase} hook '${name}' failed (non-blocking): ${e}")
currentBuild.description = (currentBuild.description ? currentBuild.description + " | " : "") + "hook(${name}) advisory-failed"
}
}
}
}
}
// buildRunCommand resolves the interpreter (explicit > extension > shebang) and,
// when a python requirements file is given, provisions an ephemeral venv.
def buildRunCommand(String script, String interpreter, String requirements) {
String interp = interpreter
if (!interp) {
if (script.endsWith('.py')) {
interp = 'python3'
} else if (script.endsWith('.sh')) {
interp = 'bash'
}
}
boolean isPython = (interp == 'python3' || interp == 'python' || script.endsWith('.py'))
if (requirements && isPython) {
String py = interp ?: 'python3'
return """
set -e
${py} -m venv .tt_hook_venv
. .tt_hook_venv/bin/activate
pip install --quiet --disable-pip-version-check -r ${requirements}
${py} ${script}
""".stripIndent().trim()
}
if (interp) {
return "set -e\n${interp} ${script}"
}
// No interpreter resolved — rely on the script's shebang.
return "set -e\nchmod +x ${script}\n./${script}"
}
// buildPhaseContextEnv builds the standard TT_* env-var contract for build-phase
// hooks from the pipeline env already populated by constructParam / the GitHub
// Branch Source plugin.
def buildPhaseContextEnv(String repoName, String phase) {
boolean isPR = (env.CHANGE_ID ? true : false)
String branch = isPR ? (env.CHANGE_BRANCH ?: env.BRANCH_NAME ?: '') : (env.BRANCH_NAME ?: '')
boolean isHotfix = (env.hot_fix == 'true' || env.hot_fix == true)
return [
"TT_REPO_NAME=${repoName}",
"TT_ENV=${env.cicd_environment ?: ''}",
"TT_EVENT=${isPR ? 'pr' : 'push'}",
"TT_IS_HOTFIX=${isHotfix}",
"TT_BRANCH=${branch}",
"TT_TARGET_BRANCH=${env.CHANGE_TARGET ?: ''}",
"TT_PR_NUMBER=${env.CHANGE_ID ?: ''}",
"TT_COMMIT_SHA=${env.GIT_COMMIT ?: ''}",
"TT_IMAGE_TAG=${env.image_tag ?: ''}",
]
}
// assertRepoRelative rejects absolute paths and parent-directory traversal so a
// hook can only execute code that lives inside the checked-out repo.
def assertRepoRelative(String p, String what) {
if (p.startsWith('/')) {
error("runHooks: ${what} path '${p}' must be repo-relative, not absolute.")
}
if (p == '..' || p.startsWith('../') || p.contains('/../')) {
error("runHooks: ${what} path '${p}' must not traverse outside the repo ('..').")
}
}
+16
View File
@@ -0,0 +1,16 @@
def call(String description) {
// Initialize or increment a global step counter kept in the environment
def current = (env.STEP_COUNTER ?: '0').trim()
int next
try {
next = current.toInteger() + 1
} catch (Throwable ignored) {
next = 1
}
env.STEP_COUNTER = next.toString()
// Derive environment name from common env vars
def stageEnv = env.cicd_environment ?: env.INFRA_ENV ?: env.BUILD_ENV ?: env.ENVIRONMENT ?: 'ftr'
return "[${stageEnv}] [Step ${env.STEP_COUNTER}] ${description}"
}
+92
View File
@@ -0,0 +1,92 @@
// Best-effort fire of the standalone ai-blitz-jobs (coverage-only) Jenkins
// job after a successful per-repo CI build. ai-blitz-jobs runs the
// coverage-only.Jenkinsfile from devops-lib@coverage-only-pipeline against
// the repo+branch we just built, so coverage and test-quality metrics flow
// to novaviz without anyone having to trigger the job manually.
//
// Gates — skip when triggering would waste compute or pollute the data:
// - PR builds (CHANGE_ID set; coverage on PRs is noise)
// - hot-fix builds (env.hot_fix == true)
// - toolchain image rebuilds (env.INFRA_ENV == 'toolchain')
// - explicit opt-out (skip_coverage_trigger: true in config.yaml)
// - non-develop branches (current policy: ONLY 'develop' fires. main /
// master / gcp-main / gcp-master are
// deploy-only branches at Meesho — tests
// already ran on the develop merge that
// produced their content, so re-running
// coverage on them would be duplicate
// spend. Expand this allowlist if a repo
// ships from a non-develop branch.)
//
// Failure is swallowed — observability must never block prod CI. The
// ai-blitz-jobs Jenkins job being renamed/disabled/missing logs a warning
// and continues; the parent build stays green.
def call(Map param) {
if (env.CHANGE_ID) {
log.info("triggerCoverageOnly: skipping PR build (CHANGE_ID=${env.CHANGE_ID}).")
return
}
if (env.hot_fix == 'true' || env.hot_fix == true) {
log.info("triggerCoverageOnly: skipping hot-fix build.")
return
}
if ((env.INFRA_ENV ?: '') == 'toolchain') {
log.info("triggerCoverageOnly: skipping toolchain (image-rebuild) build.")
return
}
if (param?.skip_coverage_trigger?.toString() == 'true') {
log.info("triggerCoverageOnly: skipping — repo opted out via config.yaml skip_coverage_trigger=true.")
return
}
String branch = (env.BRANCH_NAME ?: '').trim()
Set<String> allowed = ['develop']
if (!allowed.contains(branch)) {
log.info("triggerCoverageOnly: skipping branch '${branch}' (only ${allowed} triggers coverage today).")
return
}
String repoName = param?.repo_name
if (!repoName) {
log.warning("triggerCoverageOnly: param.repo_name missing — cannot construct REPO_URL. Skipping.")
return
}
// Meesho convention: every backend service lives at github.com/Meesho/<repo>.
// ai-blitz-jobs' coverage-only.Jenkinsfile takes the full URL as REPO_URL.
String repoUrl = "https://github.com/Meesho/${repoName}"
// Absolute path. Per-repo CI jobs may live inside Jenkins folders
// (e.g. /Meesho/order-service); a relative 'ai-blitz-jobs' would try
// the parent folder first and 404 there. The leading slash anchors at
// the Jenkins root, where ai-blitz-jobs lives (confirmed by the URL
// shape http://jenkins-dev.../job/ai-blitz-jobs/<N>/).
String targetJob = '/ai-blitz-jobs'
try {
log.info("triggerCoverageOnly: launching ${targetJob} for ${repoName} @ ${branch} (fire-and-forget).")
// The `build` step is in-process Jenkins RPC — no API tokens or
// credentials needed. It runs with the parent build's identity
// (UpstreamCause), and ai-blitz-jobs' coverage-only.Jenkinsfile
// calls coverageOnly() directly (NOT eksCICD), so the
// allowedUsers gate in eksCICD doesn't apply to this path. Bot
// and upstream-triggered builds both work.
// wait:false → parent build queues the downstream and moves on
// immediately. It never reads the downstream result, so `propagate`
// is a no-op here and intentionally omitted. The try/catch below is
// what shields the parent from step-level errors (job not found,
// bad params, queue full).
build(
job: targetJob,
parameters: [
string(name: 'REPO_URL', value: repoUrl),
string(name: 'BRANCH', value: branch)
],
wait: false,
quietPeriod: 0
)
} catch (Exception e) {
// ai-blitz-jobs renamed/disabled/queue-blocked/whatever — log and
// continue. We never block the parent build on observability.
log.warning("triggerCoverageOnly: build(job: '${targetJob}', …) threw: ${e}. Continuing — parent CI stays green.")
}
}