// Refactored Jenkinsfile for tte-auth-app // Standardized to match the latest CI/CD template. import groovy.transform.Field import groovy.json.JsonOutput import java.text.SimpleDateFormat // A field to accumulate log messages for the final notification. @Field String pipelineLog = '' /** * Appends a formatted message to the pipeline log for inclusion in the final notification. * @param message The string message to log. */ def appendLog(String message) { echo "[LOG] ${message}" pipelineLog += "\n${message.trim()}" } /** * Sends a rich, formatted notification to a Discord webhook using embeds. * @param params A map of parameters including status, jobName, buildUrl, etc. */ def sendDiscordNotification(Map params) { // Parameter validation ['status', 'jobName', 'buildNumber', 'buildUrl', 'webhookCredentialId'].each { key -> if (!params[key]) { echo "⚠️ sendDiscordNotification: Missing required parameter '${key}'" return } } def colorValue, statusEmoji def statusText = params.status.toUpperCase() def embedTitle // Determine color and emoji based on build status switch (statusText) { case 'SUCCESS': colorValue = 0x28A745; statusEmoji = "✅"; embedTitle = "${statusEmoji} Build Successful: ${params.jobName.split('/').last()}"; break case 'FAILURE': colorValue = 0xDC3545; statusEmoji = "❌"; embedTitle = "${statusEmoji} Build Failed: ${params.jobName.split('/').last()}"; break case 'UNSTABLE': colorValue = 0xFFC107; statusEmoji = "⚠️"; embedTitle = "${statusEmoji} Build Unstable: ${params.jobName.split('/').last()}"; break case 'ABORTED': colorValue = 0x6C757D; statusEmoji = "🚫"; embedTitle = "${statusEmoji} Build Aborted: ${params.jobName.split('/').last()}"; break case 'APPROVAL_REQUEST': colorValue = 0x007BFF; statusEmoji = "🔔"; embedTitle = "${statusEmoji} Approval Required: ${params.jobName.split('/').last()}"; break default: colorValue = 0x17A2B8; statusEmoji = "ℹ️"; embedTitle = "${statusEmoji} Build Notification (${statusText}): ${params.jobName.split('/').last()}"; break } // Special highlighting for the production branch if (params.branchName == 'main') { if (!['FAILURE', 'ABORTED'].contains(statusText.toUpperCase())) { colorValue = 0xFFD700 // Gold color for production } embedTitle = "❗ PRODUCTION: ${embedTitle}" } // Build the main description text def descriptionLines = [] if (params.customMessage) { descriptionLines.add(params.customMessage.stripIndent().trim()) descriptionLines.add("") } descriptionLines.add("**Project:** `${params.jobName.replaceAll("/", " / ")}`") if (params.branchName) { descriptionLines.add("**Branch:** `${params.branchName}`") } descriptionLines.add("**Build:** [#${params.buildNumber}](${params.buildUrl}display/redirect) - **${statusText.toUpperCase()}**") if (params.nodeName) { descriptionLines.add("**Node:** `${params.nodeName}`") } if (params.triggeredBy) { descriptionLines.add("**Triggered by:** ${params.triggeredBy}") } // Add commit details def commitDetails = "" if (params.processedCommits && !params.processedCommits.isEmpty()) { def commitMessages = params.processedCommits.collect { c -> " - `${c.commitId}`: ${c.message} (_${c.author}_)" } commitDetails = "\n\n📜 **Commits:**\n" + commitMessages.join('\n') } descriptionLines.add(commitDetails) def description = descriptionLines.join('\n').take(4000) // Footer with timestamp def footerTimeZone = TimeZone.getTimeZone(params.footerTimeZone ?: 'UTC') def footerTimestamp = new Date(params.buildTimestamp ?: System.currentTimeMillis()).format("yyyy-MM-dd HH:mm:ss z", footerTimeZone) // Construct the final embed payload def embed = [ title: embedTitle.take(250), description: description, color: colorValue, timestamp: new Date(params.buildTimestamp ?: System.currentTimeMillis()).format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC')), url: "${params.buildUrl}display/redirect", footer: [ text: "Jenkins Build #${params.buildNumber} • ${footerTimestamp}", icon_url: params.footerIconUrl ?: params.avatarUrl ] ] if (params.customFields) { embed.fields = params.customFields } def payloadMap = [embeds: [embed]] if (params.username) { payloadMap.username = params.username.take(80) } if (params.avatarUrl) { payloadMap.avatar_url = params.avatarUrl } def jsonPayload = groovy.json.JsonOutput.toJson(payloadMap) withCredentials([string(credentialsId: params.webhookCredentialId, variable: 'DISCORD_WEBHOOK_URL')]) { try { httpRequest(url: DISCORD_WEBHOOK_URL, httpMode: 'POST', contentType: 'APPLICATION_JSON', requestBody: jsonPayload, quiet: true, timeout: 20, validResponseCodes: '200:204') echo "Discord notification sent successfully for status: ${statusText}." } catch (Exception e) { echo "⚠️ Error sending Discord notification: ${e.getMessage()}" } } } pipeline { agent any environment { // --- Configuration for the auth application --- OKD_PROJECT = 'tte-applications-project' BUILD_NAME = 'tte-auth-app' DEPLOYMENT = 'tte-auth-app' OKD_PROJECT_DEV = 'tte-dev-applications-project' BUILD_NAME_DEV = 'tte-dev-auth-apps' DEPLOYMENT_DEV = 'tte-dev-auth-apps' // Notification settings DISCORD_WEBHOOK_CREDENTIAL_ID = 'discord-webhook-auth' DISCORD_BOT_USERNAME = "Jenkins CI" DISCORD_AVATAR_URL = "https://www.jenkins.io/images/logos/jenkins/jenkins.png" FOOTER_TIMEZONE = "Asia/Jakarta" } options { timestamps() timeout(time: 1, unit: 'HOURS') buildDiscarder(logRotator(numToKeepStr: '10')) } stages { stage('Build App for Development') { when { branch 'dev' } steps { script { appendLog("🚀 **Building App for Development** (`${env.BUILD_NAME_DEV}`)...") try { withCredentials([file(credentialsId: 'kubeconfig-file', variable: 'KUBECONFIG_PATH')]) { sh """ export KUBECONFIG="\${KUBECONFIG_PATH}" oc start-build "${BUILD_NAME_DEV}" --follow --wait=true -n "${OKD_PROJECT_DEV}" """ } appendLog("✅ **Development App Build** completed successfully.") } catch (Exception e) { currentBuild.result = 'FAILURE' appendLog("❌ **Development App Build FAILED**.") error("OpenShift build for Development failed: ${e.getMessage()}") } } } } stage('Build App for Production') { when { branch 'main' } steps { script { appendLog("🚀 **Building App for Production** (`${env.BUILD_NAME}`)...") try { withCredentials([file(credentialsId: 'kubeconfig-file', variable: 'KUBECONFIG_PATH')]) { sh """ export KUBECONFIG="\${KUBECONFIG_PATH}" oc start-build "${BUILD_NAME}" --follow --wait=true -n "${OKD_PROJECT}" """ } appendLog("✅ **Production App Build** completed successfully.") } catch (Exception e) { currentBuild.result = 'FAILURE' appendLog("❌ **Production App Build FAILED**.") error("OpenShift build for Production failed: ${e.getMessage()}") } } } } stage('Deploy to Development') { when { branch 'dev' expression { currentBuild.resultIsBetterOrEqualTo('SUCCESS') } } steps { script { appendLog("🚚 **Deploy to Development** starting for `${env.DEPLOYMENT_DEV}`...") try { withCredentials([file(credentialsId: 'kubeconfig-file', variable: 'KUBECONFIG_PATH')]) { sh """ export KUBECONFIG="\${KUBECONFIG_PATH}" oc rollout restart deployment/"${DEPLOYMENT_DEV}" -n "${OKD_PROJECT_DEV}" oc rollout status deployment/"${DEPLOYMENT_DEV}" --watch -n "${OKD_PROJECT_DEV}" """ } appendLog("✅ **Deploy to Development** succeeded.") } catch (Exception e) { currentBuild.result = 'FAILURE' appendLog("❌ **Deploy to Development FAILED**.") error("Deployment to Development failed: ${e.getMessage()}") } } } } stage('Deploy to Production') { when { branch 'main' expression { currentBuild.resultIsBetterOrEqualTo('SUCCESS') } } steps { script { appendLog("🚀 **Deploy to Production** starting for `${env.DEPLOYMENT}`...") try { withCredentials([file(credentialsId: 'kubeconfig-file', variable: 'KUBECONFIG_PATH')]) { sh """ export KUBECONFIG="\${KUBECONFIG_PATH}" oc rollout restart deployment/"${DEPLOYMENT}" -n "${OKD_PROJECT}" oc rollout status deployment/"${DEPLOYMENT}" --watch -n "${OKD_PROJECT}" """ } appendLog("✅ **Deploy to Production** succeeded.") } catch (Exception e) { currentBuild.result = 'FAILURE' appendLog("❌ **Deploy to Production FAILED**.") error("Deployment to Production failed: ${e.getMessage()}") } } } } } post { always { script { def finalStatus = currentBuild.currentResult ?: 'UNKNOWN' def processedCommitsList = [] try { for (changeLogSet in currentBuild.changeSets) { for (entry in changeLogSet.items) { processedCommitsList.add([ commitId: entry.commitId?.take(7) ?: "N/A", message: (entry.msg?.split('\n')[0] ?: "No commit message").take(80), author: entry.author?.displayName ?: "Unknown" ]) } } } catch (e) { echo "Could not get changeset: ${e.getMessage()}" } def customFieldsList = [] if (pipelineLog) { customFieldsList.add([ name: "📋 Log Highlights", value: "```\n${pipelineLog.trim().take(1000)}\n```", inline: false ]) } def triggeredBy = "Unknown" try { triggeredBy = currentBuild.rawBuild.getCauses().collect { it.getShortDescription() }.join(", ") } catch (e) { /* ignore */ } sendDiscordNotification([ status: finalStatus, jobName: env.JOB_NAME, buildNumber: env.BUILD_NUMBER, buildUrl: env.BUILD_URL, webhookCredentialId: env.DISCORD_WEBHOOK_CREDENTIAL_ID, branchName: env.BRANCH_NAME ?: "N/A", nodeName: env.NODE_NAME ?: 'N/A', processedCommits: processedCommitsList, triggeredBy: triggeredBy, username: "${env.DISCORD_BOT_USERNAME} (${env.JOB_NAME.split('/').last()})", avatarUrl: env.DISCORD_AVATAR_URL, footerTimeZone: env.FOOTER_TIMEZONE, buildTimestamp: currentBuild.startTimeInMillis ]) } } } }