├── .gitignore ├── LICENSE ├── README.md └── src └── org └── lonkar └── jenkinsutils ├── AnsiText.groovy ├── GcloudHelper.groovy ├── HelmHelper.groovy ├── KubectlHelper.groovy ├── PipelineUtils.groovy ├── SubnetUtils.groovy ├── TerraformHelper.groovy └── constants └── GCP.groovy /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yogesh Lonkar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # awesome-jenkins-utils 2 | 3 | Refere [Wiki for Documentation and Usage](https://github.com/yogeshlonkar/awesome-jenkins-utils/wiki) 4 | -------------------------------------------------------------------------------- /src/org/lonkar/jenkinsutils/AnsiText.groovy: -------------------------------------------------------------------------------- 1 | package org.lonkar.jenkinsutils 2 | 3 | @Grab('org.fusesource.jansi:jansi:1.17.1') 4 | import org.fusesource.jansi.* 5 | import static org.fusesource.jansi.Ansi.* 6 | import java.io.Serializable 7 | 8 | /** 9 | * 10 | * Utility class for generating Ansi text using Fluent interface. 11 | * Credits to https://github.com/fusesource/jansi. 12 | * However {@link Ansi} class somehow returns {@link NoAnsi} instance, 13 | * Which does not have all helper methods from {@link Ansi}. 14 | * 15 | * Will require ansicolor plugin. 16 | * 17 | */ 18 | public class AnsiText implements Serializable { 19 | 20 | private int tabToKeep 21 | 22 | private transient Ansi ansi 23 | 24 | public AnsiText() { 25 | this.ansi = new Ansi() 26 | } 27 | 28 | public AnsiText(Ansi parent) { 29 | this.ansi = new Ansi(parent) 30 | } 31 | 32 | public AnsiText(int size) { 33 | this.ansi = new Ansi(size) 34 | } 35 | 36 | public AnsiText(StringBuilder builder) { 37 | this.ansi = new Ansi(builder) 38 | } 39 | 40 | def AnsiText fg(Color color) { 41 | this.ansi.fg(color) 42 | return this 43 | } 44 | 45 | def AnsiText fgBlack() { 46 | this.ansi.fg(Color.BLACK) 47 | return this 48 | } 49 | 50 | def AnsiText fgBlue() { 51 | this.ansi.fg(Color.BLUE) 52 | return this 53 | } 54 | 55 | def AnsiText fgCyan() { 56 | this.ansi.fg(Color.CYAN) 57 | return this 58 | } 59 | 60 | def AnsiText fgDefault() { 61 | this.ansi.fg(Color.DEFAULT) 62 | return this 63 | } 64 | 65 | def AnsiText fgGreen() { 66 | this.ansi.fg(Color.GREEN) 67 | return this 68 | } 69 | 70 | def AnsiText fgMagenta() { 71 | this.ansi.fg(Color.MAGENTA) 72 | return this 73 | } 74 | 75 | def AnsiText fgRed() { 76 | this.ansi.fg(Color.RED) 77 | return this 78 | } 79 | 80 | def AnsiText fgYellow() { 81 | this.ansi.fg(Color.YELLOW) 82 | return this 83 | } 84 | 85 | def AnsiText bg(Color color) { 86 | this.ansi.bg(color) 87 | return this 88 | } 89 | 90 | def AnsiText bgCyan() { 91 | this.ansi.fg(Color.CYAN) 92 | return this 93 | } 94 | 95 | def AnsiText bgDefault() { 96 | this.ansi.bg(Color.DEFAULT) 97 | return this 98 | } 99 | 100 | def AnsiText bgGreen() { 101 | this.ansi.bg(Color.GREEN) 102 | return this 103 | } 104 | 105 | def AnsiText bgMagenta() { 106 | this.ansi.bg(Color.MAGENTA) 107 | return this 108 | } 109 | 110 | def AnsiText bgRed() { 111 | this.ansi.bg(Color.RED) 112 | return this 113 | } 114 | 115 | def AnsiText bgYellow() { 116 | this.ansi.bg(Color.YELLOW) 117 | return this 118 | } 119 | 120 | def AnsiText fgBrightBlack() { 121 | this.ansi.fgBright(Color.BLACK) 122 | return this 123 | } 124 | 125 | def AnsiText fgBrightBlue() { 126 | this.ansi.fgBright(Color.BLUE) 127 | return this 128 | } 129 | 130 | def AnsiText fgBrightCyan() { 131 | this.ansi.fgBright(Color.CYAN) 132 | return this 133 | } 134 | 135 | def AnsiText fgBrightDefault() { 136 | this.ansi.fgBright(Color.DEFAULT) 137 | return this 138 | } 139 | 140 | def AnsiText fgBrightGreen() { 141 | this.ansi.fgBright(Color.GREEN) 142 | return this 143 | } 144 | 145 | def AnsiText fgBrightMagenta() { 146 | this.ansi.fgBright(Color.MAGENTA) 147 | return this 148 | } 149 | 150 | def AnsiText fgBrightRed() { 151 | this.ansi.fgBright(Color.RED) 152 | return this 153 | } 154 | 155 | def AnsiText fgBrightYellow() { 156 | this.ansi.fgBright(Color.YELLOW) 157 | return this 158 | } 159 | 160 | def AnsiText bgBrightCyan() { 161 | this.ansi.fgBright(Color.CYAN) 162 | return this 163 | } 164 | 165 | def AnsiText bgBrightDefault() { 166 | this.ansi.bgBright(Color.DEFAULT) 167 | return this 168 | } 169 | 170 | def AnsiText bgBrightGreen() { 171 | this.ansi.bgBright(Color.GREEN) 172 | return this 173 | } 174 | 175 | def AnsiText bgBrightMagenta() { 176 | this.ansi.bg(Color.MAGENTA) 177 | return this 178 | } 179 | 180 | def AnsiText bgBrightRed() { 181 | this.ansi.bgBright(Color.RED) 182 | return this 183 | } 184 | 185 | def AnsiText bgBrightYellow() { 186 | this.ansi.bgBright(Color.YELLOW) 187 | return this 188 | } 189 | 190 | def AnsiText reset() { 191 | this.ansi.a(Attribute.RESET) 192 | return this 193 | } 194 | 195 | def AnsiText bold() { 196 | this.ansi.a(Attribute.INTENSITY_BOLD) 197 | return this 198 | } 199 | 200 | def AnsiText boldOff() { 201 | this.ansi.a(Attribute.INTENSITY_BOLD_OFF) 202 | return this 203 | } 204 | 205 | def AnsiText a(value) { 206 | this.ansi.a(value) 207 | return this 208 | } 209 | 210 | def AnsiText a(char[] value, int offset, int len) { 211 | this.ansi.a(value, offset, len) 212 | return this 213 | } 214 | 215 | def AnsiText a(CharSequence value, int start, int end) { 216 | this.ansi.a(value, start, end) 217 | return this 218 | } 219 | 220 | def AnsiText newline() { 221 | this.ansi.newline() 222 | this.tab(this.tabToKeep) 223 | return this 224 | } 225 | 226 | /** 227 | * Add tab in text 228 | * 229 | * @return 230 | */ 231 | def AnsiText tab() { 232 | this.ansi.a("\t") 233 | return this 234 | } 235 | 236 | /** 237 | * Add tab n times in text 238 | * 239 | * @param n 240 | * @return 241 | */ 242 | def AnsiText tab(int n) { 243 | while (n > 0) { 244 | this.ansi.a("\t") 245 | n-- 246 | } 247 | return this 248 | } 249 | 250 | /** 251 | * Keep tab as a prefix to each new line n times in text every time new text is added 252 | * 253 | * @param n 254 | * @return 255 | */ 256 | def AnsiText keepTabbed(int n) { 257 | this.tabToKeep = n 258 | return this 259 | } 260 | 261 | /** 262 | * reset tab prefix 263 | * 264 | * @return 265 | */ 266 | def AnsiText resetTab() { 267 | this.tabToKeep = 0 268 | return this 269 | } 270 | 271 | /** 272 | * italicize text 273 | * 274 | * @return 275 | */ 276 | def AnsiText italicize() { 277 | this.ansi.a(Attribute.ITALIC) 278 | return this 279 | } 280 | 281 | /** 282 | * turn of italics 283 | * 284 | * @return 285 | */ 286 | def AnsiText italicizeOff() { 287 | this.ansi.a(Attribute.ITALIC_OFF) 288 | return this 289 | } 290 | 291 | /** @return */ 292 | def AnsiText underline() { 293 | this.ansi.a(Attribute.UNDERLINE) 294 | return this 295 | } 296 | 297 | /** @return */ 298 | def AnsiText underlineOff() { 299 | this.ansi.a(Attribute.UNDERLINE_OFF) 300 | return this 301 | } 302 | 303 | @Override 304 | def String toString() { 305 | this.reset() 306 | return this.ansi.toString() 307 | } 308 | 309 | } 310 | -------------------------------------------------------------------------------- /src/org/lonkar/jenkinsutils/GcloudHelper.groovy: -------------------------------------------------------------------------------- 1 | 2 | package org.lonkar.jenkinsutils 3 | 4 | import java.io.Serializable 5 | import hudson.* 6 | import hudson.model.* 7 | import hudson.slaves.* 8 | import hudson.tools.* 9 | import groovy.json.* 10 | import org.jenkinsci.plugins.structs.* 11 | import com.cloudbees.jenkins.plugins.customtools.* 12 | import com.synopsys.arc.jenkinsci.plugins.customtools.versions.* 13 | 14 | 15 | /** 16 | * Gcloud Helper to install and setup gcloud in PATH variable using targeted binaries 17 | * from gcloud versioned archives 18 | * 19 | */ 20 | class GcloudHelper implements Serializable { 21 | 22 | private def pipeline 23 | private def utils 24 | 25 | /** 26 | * Instantiate GcloudHelper using WorkflowScript object 27 | * @see CpsScript.java 28 | * 29 | * @param pipeline - WorkflowScript 30 | */ 31 | GcloudHelper(pipeline) { 32 | this.pipeline = pipeline 33 | this.utils = new PipelineUtils(pipeline) 34 | } 35 | 36 | /** 37 | * Installs terraform if not exists already using CustomTool and adds it to PATH 38 | * 39 | * @param version of terraform to use 40 | */ 41 | def use(version = '233.0.0') { 42 | def os = utils.currentOS() 43 | def arch = utils.currentArchitecture().replace('i','') 44 | if (arch == 'amd64') { 45 | arch = 'x86_64' 46 | } 47 | def extension = pipeline.isUnix() ? '.tar.gz' : 'bundled-python.zip' 48 | def gcloudVersionPath = "${pipeline.env.JENKINS_HOME}/tools/gcloud/${version}" 49 | List properties = [ 50 | new InstallSourceProperty([ 51 | new ZipExtractionInstaller(null, "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-${version}-${os}-${arch}${extension}", "${gcloudVersionPath}/google-cloud-sdk/bin/") 52 | ].toList()) 53 | ].toList() 54 | 55 | def tool = new CustomTool("gcloud.${version}", gcloudVersionPath, properties, '', null, ToolVersionConfig.DEFAULT, null) 56 | def currNode = pipeline.getContext Node.class 57 | def currListener = pipeline.getContext TaskListener.class 58 | def gcloudPath = tool.forNode(currNode, currListener).getHome() 59 | pipeline.env.PATH = "${gcloudPath}:${pipeline.env.PATH}" 60 | pipeline.echo "using gcloud ${version}" 61 | } 62 | } -------------------------------------------------------------------------------- /src/org/lonkar/jenkinsutils/HelmHelper.groovy: -------------------------------------------------------------------------------- 1 | 2 | package org.lonkar.jenkinsutils 3 | 4 | import java.io.Serializable 5 | import hudson.* 6 | import hudson.model.* 7 | import hudson.slaves.* 8 | import hudson.tools.* 9 | import groovy.json.* 10 | import org.jenkinsci.plugins.structs.* 11 | import com.cloudbees.jenkins.plugins.customtools.* 12 | import com.synopsys.arc.jenkinsci.plugins.customtools.versions.* 13 | 14 | 15 | /** 16 | * Helm Helper to install and setup helm in PATH variable using targeted binaries 17 | * from HELM release repository 18 | * 19 | */ 20 | class HelmHelper implements Serializable { 21 | 22 | private def pipeline 23 | private def utils 24 | 25 | /** 26 | * Instantiate HelmHelper using WorkflowScript object 27 | * @see CpsScript.java 28 | * 29 | * @param pipeline - WorkflowScript 30 | */ 31 | HelmHelper(pipeline) { 32 | this.pipeline = pipeline 33 | this.utils = new PipelineUtils(pipeline) 34 | } 35 | 36 | /** 37 | * Installs terraform if not exists already using CustomTool and adds it to PATH 38 | * 39 | * @param version of terraform to use 40 | */ 41 | def use(version = '2.12.3') { 42 | if (!(version ==~ /^v.*$/)) { 43 | version = "v${version}" 44 | } 45 | def os = utils.currentOS() 46 | def arch = utils.currentArchitecture().replace('i','') 47 | def extension = pipeline.isUnix() ? 'tar.gz' : 'zip' 48 | def helmVersionPath = "${pipeline.env.JENKINS_HOME}/tools/helm/${version}" 49 | List properties = [ 50 | new InstallSourceProperty([ 51 | new ZipExtractionInstaller(null, "https://storage.googleapis.com/kubernetes-helm/helm-${version}-${os}-${arch}.${extension}", "${helmVersionPath}/${os}-${arch}") 52 | ].toList()) 53 | ].toList() 54 | def tool = new CustomTool("helm.${version}", helmVersionPath, properties, '', null, ToolVersionConfig.DEFAULT, null) 55 | def currNode = pipeline.getContext Node.class 56 | def currListener = pipeline.getContext TaskListener.class 57 | def helmPath = tool.forNode(currNode, currListener).getHome() 58 | pipeline.env.PATH = "${helmPath}:${pipeline.env.PATH}" 59 | pipeline.echo "using helm ${version}" 60 | } 61 | } -------------------------------------------------------------------------------- /src/org/lonkar/jenkinsutils/KubectlHelper.groovy: -------------------------------------------------------------------------------- 1 | 2 | package org.lonkar.jenkinsutils 3 | 4 | import java.io.Serializable 5 | import hudson.* 6 | import hudson.model.* 7 | import hudson.slaves.* 8 | import hudson.tools.* 9 | import groovy.json.* 10 | import org.jenkinsci.plugins.structs.* 11 | import com.cloudbees.jenkins.plugins.customtools.* 12 | import com.synopsys.arc.jenkinsci.plugins.customtools.versions.* 13 | 14 | /** 15 | * Kubectl Helper to install and setup Kubectl in PATH variable using targeted binaries 16 | * 17 | */ 18 | class KubectlHelper implements Serializable { 19 | 20 | private def pipeline 21 | private def utils 22 | 23 | /** 24 | * Instantiate KubectlHelper using WorkflowScript object 25 | * @see CpsScript.java 26 | * 27 | * @param pipeline - WorkflowScript 28 | */ 29 | KubectlHelper(pipeline) { 30 | this.pipeline = pipeline 31 | this.utils = new PipelineUtils(pipeline) 32 | } 33 | 34 | /** 35 | * Installs kubectl if not exists already using bash/bat and add it to path 36 | * 37 | * @param version of kubectl to use 38 | */ 39 | def use(version = false) { 40 | if (!version && pipeline.isUnix()) { 41 | version = utils.silentBash(script: 'curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt', returnStdout: true).trim() 42 | } else if (!version) { 43 | version = pipeline.bat(script: 'curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt', returnStdout: true).trim() 44 | } else if (!(version ==~ /^v.*$/)) { 45 | version = "v${version}" 46 | } 47 | def kubectlHome = "${pipeline.env.JENKINS_HOME}/tools/kubectl/${version}" 48 | if (pipeline.isUnix()) { 49 | def exists = utils.silentBash(script: "#!/bin/bash -e\n[[ -e ${kubectlHome}/kubectl ]]", returnStatus: true) 50 | if (exists != 0) { 51 | utils.silentBash script: """ 52 | mkdir -p ${kubectlHome} 53 | cd ${kubectlHome} 54 | curl -LO https://storage.googleapis.com/kubernetes-release/release/${version}/bin/linux/amd64/kubectl 55 | chmod +x kubectl 56 | """ 57 | } 58 | } else { 59 | def exists = pipeline.bat(script: "if exist ${kubectlHome}/kubectl.exec ( rem true ) else ( rem false )", returnStdout: true).trim() 60 | if (exists == 'false') { 61 | pipeline.bat script: """ 62 | @echo off 63 | setlocal EnableExtensions DisableDelayedExpansion 64 | set "Directory=${kubectlHome}" 65 | if not exist "%Directory%\\*" md "%Directory%" || pause & goto :EOF 66 | rem Other commands after successful creation of the directory. 67 | endlocal 68 | cd ${kubectlHome} 69 | curl -LO https://storage.googleapis.com/kubernetes-release/release/${version}/bin/windows/amd64/kubectl.exe 70 | """ 71 | } 72 | } 73 | pipeline.env.PATH = "${kubectlHome}:${pipeline.env.PATH}" 74 | pipeline.echo "using kubectl ${version}" 75 | } 76 | } -------------------------------------------------------------------------------- /src/org/lonkar/jenkinsutils/PipelineUtils.groovy: -------------------------------------------------------------------------------- 1 | package org.lonkar.jenkinsutils 2 | 3 | import org.jenkinsci.plugins.pipeline.modeldefinition.Utils 4 | import groovy.json.* 5 | import hudson.model.* 6 | import java.io.Serializable 7 | 8 | /** 9 | * Common utilities for using in scripted pipeline 10 | * 11 | */ 12 | class PipelineUtils implements Serializable { 13 | 14 | private static String CommonHeaderStyle = 'font-family: Roboto, sans-serif !important;text-align: center;margin: 10px 0 0;font-weight: bold;' 15 | private static String GlobalSeparatorStyle = 'display: none;' 16 | private static String GlobalHeaderDangerStyle = CommonHeaderStyle + 'background: #f8d7da;color: #721c24;' 17 | private static String GlobalHeaderSuccessStyle = CommonHeaderStyle + 'background: #d4edda;color: #155724;' 18 | private static String GlobalHeaderInfoStyle = CommonHeaderStyle + 'background: #d1ecf1;color: #0c5460;' 19 | private static String GlobalHeaderSecondaryStyle = CommonHeaderStyle + 'background: #ccc;color: #000;' 20 | 21 | private def pipeline 22 | private def exceptionInBuild 23 | private boolean hasAnsiSupport 24 | private boolean disableAnsi; 25 | 26 | /** 27 | * Instantiate PipelineUtils using WorkflowScript object 28 | * @see CpsScript.java 29 | * 30 | * @param pipeline - WorkflowScript 31 | */ 32 | PipelineUtils(pipeline, disableAnsi = false) { 33 | this.pipeline = pipeline 34 | this.disableAnsi = disableAnsi 35 | try { 36 | Class.forName('hudson.plugins.ansicolor.AnsiColorBuildWrapper', false, pipeline.getClass().getClassLoader()) 37 | this.hasAnsiSupport = true 38 | } catch (java.lang.ClassNotFoundException e) { 39 | this.hasAnsiSupport = false 40 | } 41 | } 42 | 43 | /** 44 | * A Stage that can be skipped based on execute condition. 45 | * The stage is wrapped in AnsiColorBuildWrapper 46 | * 47 | * @param name of stage 48 | * @param execute boolean flag 49 | * @param block stage block code 50 | * @return stage object 51 | */ 52 | def stage(name, boolean execute = true, block) { 53 | if (hasAnsiSupport && !disableAnsi) { 54 | if (execute) { 55 | return this.pipeline.wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'xterm']) { 56 | pipeline.echo new AnsiText().bold().a('Executing stage ').fgGreen().a(name).toString() 57 | pipeline.stage(name, block) 58 | pipeline.echo new AnsiText().bold().a('Stage ').fgGreen().a(name).fgBlack().a(' completed').toString() 59 | } 60 | } else { 61 | return this.pipeline.wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'xterm']) { 62 | pipeline.stage(name, { 63 | pipeline.echo new AnsiText().bold().a('Skipped stage ').fgYellow().a(name).toString() 64 | Utils.markStageSkippedForConditional(name) 65 | }) 66 | } 67 | } 68 | } else { 69 | if (execute) { 70 | return pipeline.stage(name, block) 71 | } else { 72 | return pipeline.stage(name, { 73 | pipeline.echo "Skipped stage ${name}" 74 | Utils.markStageSkippedForConditional(name) 75 | }) 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * mark exception in build for sending failure notification with slack 82 | */ 83 | def void markExceptionInBuild() { 84 | exceptionInBuild = true 85 | } 86 | 87 | /** 88 | * get the change log aggregated message in newline separated string 89 | * @return aggregated change log 90 | */ 91 | @NonCPS 92 | def String getChangeLogMessage() { 93 | def changeLogSets = pipeline.currentBuild.changeSets 94 | def changeLogMessage = "${pipeline.currentBuild.changeSets.size()} Repository change(s)\n" 95 | def totalCommits = 0 96 | def fileChanges = 0 97 | pipeline 98 | .currentBuild 99 | .changeSets 100 | .each { changeLogSet -> 101 | totalCommits += changeLogSet.items.size() 102 | changeLogSet.items.each { changeSet -> 103 | fileChanges += changeSet.affectedFiles.size() 104 | } 105 | } 106 | changeLogMessage += "${totalCommits} commit(s)\n" 107 | changeLogMessage += "${fileChanges} file change(s)\n" 108 | return changeLogMessage 109 | } 110 | 111 | /** 112 | * Send slack notification using slackSend required jenkins-slack-plugin 113 | * 114 | * @param config object 115 | * @param config.buildStatus optional string expected values STARTED | SUCCESS | UNSTABLE | ABORTED | FAILURE | PROGRESS 116 | * @param config.buildMessage optional string to append header on slack message 117 | * @param config.changeLogMessage?: optional string for custom change log message to attach 118 | * @param config.channel?: optional string for sending notification to @individual or #group 119 | */ 120 | def void slackIt(config) { 121 | this.slackIt(true, config) 122 | } 123 | 124 | /** 125 | * Send slack notification using slackSend required jenkins-slack-plugin 126 | * 127 | * @param execute boolean if false slack notification is not send 128 | * @param config object 129 | * @param config.buildStatus optional string expected values STARTED | SUCCESS | UNSTABLE | ABORTED | FAILURE | PROGRESS 130 | * @param config.buildMessage optional string to append header on slack message 131 | * @param config.changeLogMessage?: optional string for custom change log message to attach 132 | * @param config.channel?: optional string for sending notification to @individual or #group 133 | * @param config.extraAttachements optional array of attachment object refer https://api.slack.com/docs/message-attachments#fields 134 | */ 135 | def void slackIt(boolean execute = true, config = [:]) { 136 | if (!execute) { 137 | return 138 | } 139 | if (!config.changeLogMessage) { 140 | config.changeLogMessage = this.getChangeLogMessage() 141 | } 142 | def buildStatus 143 | if (exceptionInBuild) { 144 | buildStatus = 'FAILURE' 145 | } else if (config.buildStatus){ 146 | buildStatus = config.buildStatus 147 | } else { 148 | buildStatus = pipeline.currentBuild.currentResult 149 | } 150 | def message = config.buildMessage ? config.buildMessage : "after ${pipeline.currentBuild.durationString.replace('and counting','')}" 151 | def colorCode 152 | switch(buildStatus) { 153 | case 'STARTED': 154 | message = "${pipeline.currentBuild.rawBuild.getCauses().get(0).getShortDescription()}" 155 | colorCode = '#ccc' 156 | break 157 | case 'PROGRESS': 158 | message = "In-progress ${message}" 159 | colorCode = '#007bff' 160 | break 161 | case 'SUCCESS': 162 | message = "Success ${message}" 163 | colorCode = 'good' 164 | break 165 | case 'UNSTABLE': 166 | message = "Unstable ${message}" 167 | colorCode = 'warning' 168 | break 169 | case 'ABORTED': 170 | message = "Aborted ${message}" 171 | colorCode = '#ccc' 172 | break 173 | default: 174 | buildStatus = 'FAILURE' 175 | colorCode = 'danger' 176 | message = "Failed ${message}" 177 | } 178 | def attachmentPayload = [[ 179 | fallback: "${pipeline.env.JOB_NAME} execution #${pipeline.env.BUILD_NUMBER} - ${buildStatus}", 180 | author_link: "", 181 | author_icon: "", 182 | title: "${pipeline.env.JOB_NAME}", 183 | title_link: "${pipeline.env.JOB_URL}", 184 | color: colorCode, 185 | fields: [ 186 | [ 187 | value: "Build <${pipeline.env.RUN_DISPLAY_URL}| #${pipeline.env.BUILD_NUMBER}> - ${message}", 188 | short: false 189 | ] 190 | ], 191 | footer: "<${pipeline.env.JENKINS_URL}| Jenkins>", 192 | ts: new Date().time / 1000 193 | ]] 194 | def totalCommits = pipeline.currentBuild.changeSets.size() 195 | if (buildStatus == 'FAILURE' && totalCommits > 0) { 196 | attachmentPayload[0].fields.add([ 197 | title: "Change log", 198 | value: "${config.changeLogMessage}\n<${pipeline.env.BUILD_URL}/changes| Details>", 199 | short: false 200 | ]) 201 | } 202 | if (config.extraAttachements) { 203 | config.extraAttachements.each { extraAttachments -> 204 | attachmentPayload[0].fields.add(extraAttachments) 205 | } 206 | } 207 | pipeline.slackSend(channel: config.channel, color: colorCode, attachments: new JsonBuilder(attachmentPayload).toPrettyString()) 208 | } 209 | 210 | /** 211 | * get root workspace of job. Generally /var/lib/jenkins/jobs/JOB_NAME/workspace 212 | * 213 | * @return workspace root path 214 | */ 215 | def String workspaceRootPath() { 216 | return pipeline.pwd().replaceFirst(/(.*workspace)@?.*/, '$1') 217 | } 218 | 219 | /** 220 | * get Jenkinsfile script path. . Generally /var/lib/jenkins/jobs/JOB_NAME/workspace@script 221 | * 222 | * @return workspace script path 223 | */ 224 | def String workspaceScriptPath() { 225 | return pipeline.pwd().replaceFirst(/(.*workspace)@?.*/, '$1@script') 226 | } 227 | 228 | /** 229 | * Create and
tag to differentiate sets of parameters or group them as bootstrap-4 SUCCESS styling 230 | * Requires Parameter Separator 231 | * 232 | * @param sectionHeader string or param group name 233 | * @return Parameter 234 | */ 235 | def successParamSeparator(sectionHeader) { 236 | return this.paramSeparator(sectionHeader, GlobalSeparatorStyle, GlobalHeaderSuccessStyle) 237 | } 238 | 239 | /** 240 | * Create and
tag to differentiate sets of parameters or group them as bootstrap-4 DANGER styling 241 | * Requires Parameter Separator 242 | * 243 | * @param sectionHeader string or param group name 244 | * @return Parameter 245 | */ 246 | def dangerParamSeparator(sectionHeader) { 247 | return this.paramSeparator(sectionHeader, GlobalSeparatorStyle, GlobalHeaderDangerStyle) 248 | } 249 | 250 | /** 251 | * Create and
tag to differentiate sets of parameters or group them as bootstrap-4 INFO styling 252 | * Requires Parameter Separator 253 | * 254 | * @param sectionHeader string or param group name 255 | * @return Parameter 256 | */ 257 | def infoParamSeparator(sectionHeader) { 258 | return this.paramSeparator(sectionHeader, GlobalSeparatorStyle, GlobalHeaderInfoStyle) 259 | } 260 | 261 | /** 262 | * Create and
tag to differentiate sets of parameters or group them as bootstrap-4 SECONDARY styling 263 | * Requires Parameter Separator 264 | * 265 | * @param sectionHeader string or param group name 266 | * @return Parameter 267 | */ 268 | def secondaryParamSeparator(sectionHeader) { 269 | return this.paramSeparator(sectionHeader, GlobalSeparatorStyle, GlobalHeaderSecondaryStyle) 270 | } 271 | 272 | /** 273 | * Create and
tag to differentiate sets of parameters or group them on build parameter page 274 | * Requires Parameter Separator 275 | * 276 | * @param sectionHeader string or param group name 277 | * @param separatorStyle string for separator CSS style 278 | * @param sectionHeaderStyle string containing CSS style for section header 279 | * @return Parameter 280 | */ 281 | def paramSeparator(sectionHeader, separatorStyle, sectionHeaderStyle) { 282 | return [ 283 | $class: 'ParameterSeparatorDefinition', 284 | name: 'separator_section', 285 | sectionHeader: sectionHeader, 286 | separatorStyle: separatorStyle, 287 | sectionHeaderStyle: sectionHeaderStyle 288 | ] 289 | } 290 | 291 | /** 292 | * Get JSON pretty string of object 293 | * 294 | * @param obj to stringify 295 | * @return json string 296 | */ 297 | @NonCPS 298 | def String jsonPrettyString(obj) { 299 | return new JsonBuilder(obj).toPrettyString() 300 | } 301 | 302 | 303 | /** 304 | * Get current node/slaves Operating system Architecture 305 | * 306 | * @return possible values for *nix amd64 | i386 | arm For windows 32 | 64 307 | */ 308 | def String currentArchitecture() { 309 | if (pipeline.isUnix()) { 310 | def arch = this.silentBash(script: 'uname -m', returnStdout: true).trim() 311 | if (arch == 'x86_64') { 312 | return 'amd64' 313 | } else { 314 | return arch 315 | } 316 | } else { 317 | def arch = pipeline.bat(script: '%PROCESSOR_ARCHITECTURE%', returnStdout: true).trim() 318 | if (arch == 'x86') { 319 | return '32' 320 | } else { 321 | return '64' 322 | } 323 | } 324 | } 325 | 326 | /** 327 | * Get current os 328 | * 329 | * @return possible values darwin | freebsd | openbsd | solaris | linux | windows 330 | */ 331 | def String currentOS() { 332 | if (pipeline.isUnix()) { 333 | def uname = this.silentBash(script: 'uname', returnStdout: true).trim() 334 | if (uname.startsWith('Darwin')) { 335 | return 'darwin' 336 | } else if (uname.startsWith('FreeBSD')) { 337 | return 'freebsd' 338 | } else if (uname.startsWith('OpenBSD')) { 339 | return 'openbsd' 340 | } else if (uname.startsWith('SunOS')) { 341 | return 'solaris' 342 | } else { 343 | return 'linux' 344 | } 345 | } else { 346 | return 'windows' 347 | } 348 | } 349 | 350 | /** 351 | * Don't log bash commands in build while executing 352 | * 353 | * @param args string or arguments similar to https://jenkins.io/doc/pipeline/steps/workflow-durable-task-step/#sh-shell-script 354 | * @return 355 | */ 356 | def silentBash(args) { 357 | def shArgs = [:] 358 | if (args instanceof Map) { 359 | shArgs = args 360 | } else { 361 | shArgs.script = args 362 | } 363 | shArgs.script = "#!/bin/bash -e\n${shArgs.script}" 364 | return pipeline.sh(shArgs) 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /src/org/lonkar/jenkinsutils/SubnetUtils.groovy: -------------------------------------------------------------------------------- 1 | package org.lonkar.jenkinsutils 2 | 3 | @Grab('commons-net:commons-net:3.3') 4 | import org.apache.commons.net.util.SubnetUtils 5 | import java.io.Serializable 6 | 7 | /** 8 | * Class for initializing org.apache.commons.net.util.SubnetUtils 9 | * 10 | */ 11 | class SubnetUtils implements Serializable { 12 | 13 | /** 14 | * @returns SubnetUtils.SubnetInfo 15 | * @throws java.lang.IllegalArgumentException if invalid CIDR block is provided 16 | */ 17 | static def parse(cidr) { 18 | return new org.apache.commons.net.util.SubnetUtils(cidr).getInfo() 19 | } 20 | } -------------------------------------------------------------------------------- /src/org/lonkar/jenkinsutils/TerraformHelper.groovy: -------------------------------------------------------------------------------- 1 | package org.lonkar.jenkinsutils 2 | 3 | import java.io.Serializable 4 | import hudson.* 5 | import hudson.model.* 6 | import hudson.slaves.* 7 | import hudson.tools.* 8 | import groovy.json.* 9 | import org.jenkinsci.plugins.structs.* 10 | import com.cloudbees.jenkins.plugins.customtools.* 11 | import com.synopsys.arc.jenkinsci.plugins.customtools.versions.* 12 | 13 | /** 14 | * Terraform Helper to install and setup terraform in PATH variable using targeted binaries 15 | * from Terraform release repository. This utility can also help generate override.tf.json file containing 16 | * terraform input with value of default set using build parameters 17 | * 18 | */ 19 | class TerraformHelper implements Serializable { 20 | 21 | private def pipeline 22 | private def transient varMapping 23 | private def transient jsonVarMapping 24 | 25 | /** 26 | * Instantiate TerraformHelper using WorkflowScript object 27 | * @see CpsScript.java 28 | * 29 | * @param pipeline - WorkflowScript 30 | */ 31 | TerraformHelper(pipeline) { 32 | this.pipeline = pipeline 33 | this.varMapping = [:] 34 | this.jsonVarMapping = [:] 35 | } 36 | 37 | /** 38 | * Installs terraform if not exists already using CustomTool and adds it to path 39 | * 40 | * @param version of terraform 41 | */ 42 | def void use(version = '0.11.11') { 43 | def utils = new PipelineUtils(pipeline) 44 | def os = utils.currentOS() 45 | def arch = utils.currentArchitecture().replace('i','') 46 | def tfVersionPath = "${pipeline.env.JENKINS_HOME}/tools/terraform/${version}" 47 | List properties = [ 48 | new InstallSourceProperty([ 49 | new ZipExtractionInstaller('', "https://releases.hashicorp.com/terraform/${version}/terraform_${version}_${os}_${arch}.zip", '') 50 | ].toList()) 51 | ].toList() 52 | def tool = new CustomTool("terraform.${version}", tfVersionPath, properties, tfVersionPath, null, ToolVersionConfig.DEFAULT, null) 53 | def currNode = pipeline.getContext Node.class 54 | def currListener = pipeline.getContext TaskListener.class 55 | def tfPath = tool.forNode(currNode, currListener).getHome() 56 | pipeline.env.PATH = "${tfPath}:${pipeline.env.PATH}" 57 | pipeline.echo "using terraform ${version}" 58 | } 59 | 60 | /** 61 | * Map build parameter to terraform variable. Currently string, boolean types are only supported. 62 | * Once terraform version 0.12 is released better additional types such as list and map can be supported 63 | * @see Terraform v0.12 64 | * 65 | * @param buildParamName string build parameter name 66 | * @param tfVar terraform variable name 67 | * @param jsonType not yet supported 68 | */ 69 | def void map(buildParamName, tfVar, jsonType = false) { 70 | if (jsonType) { 71 | jsonVarMapping[buildParamName] = [ 72 | type: jsonType, 73 | tfVar: tfVar 74 | ] 75 | } else { 76 | varMapping[buildParamName] = tfVar 77 | } 78 | } 79 | 80 | /** 81 | * Generate terraformInput object 82 | * 83 | * @param tfvars array of string containing terraform variable names that only need to be parsed/mapped. 84 | * if empty all mapped parameters will be return in terraformInput 85 | * @return object with terraform variable names as keys and object with default with build parameter value as value 86 | */ 87 | @NonCPS 88 | def Object buildParamsToTFVars(tfvars = []) { 89 | def build = pipeline.currentBuild.rawBuild 90 | def terraformInput = [:] 91 | def jsonSlurper = new JsonSlurperClassic() 92 | build.actions.find{ it instanceof ParametersAction }?.parameters.each { buildParam -> 93 | def tfVar = varMapping[buildParam.name] 94 | def jsontfVar = jsonVarMapping[buildParam.name] 95 | if (tfvars.size() > 0 && !tfvars.contains(tfVar) && !tfvars.contains(jsontfVar)) { 96 | return 97 | } 98 | if (tfVar) { 99 | terraformInput[tfVar] = [ 100 | 'default': buildParam.value 101 | ] 102 | } else if (jsonVarMapping[buildParam.name]) { 103 | tfVar = jsonVarMapping[buildParam.name].tfVar 104 | terraformInput[tfVar] = [ 105 | type: jsonVarMapping[buildParam.name].type, 106 | 'default': jsonSlurper.parseText(buildParam.value) 107 | ] 108 | } 109 | } 110 | return terraformInput 111 | } 112 | 113 | /** 114 | * stringify object to match terraform variable.tf.json format 115 | * @param terraformInput 116 | * @return JSON pretty string 117 | */ 118 | @NonCPS 119 | def overrideTFJsonString(terraformInput) { 120 | return new JsonBuilder([ variable: terraformInput ]).toPrettyString() 121 | } 122 | } -------------------------------------------------------------------------------- /src/org/lonkar/jenkinsutils/constants/GCP.groovy: -------------------------------------------------------------------------------- 1 | package org.lonkar.jenkinsutils.constants; 2 | import java.io.Serializable; 3 | 4 | /** 5 | * Google cloud platform constants for using in jenkins pipeline. 6 | * 7 | */ 8 | class GCP implements Serializable { 9 | static def Regions = [ 10 | "us-west1", 11 | "asia-east1", 12 | "asia-east2", 13 | "asia-northeast1", 14 | "asia-south1", 15 | "asia-southeast1", 16 | "australia-southeast1", 17 | "europe-north1", 18 | "europe-west1", 19 | "europe-west2", 20 | "europe-west3", 21 | "europe-west4", 22 | "northamerica-northeast1", 23 | "southamerica-east1", 24 | "us-central1", 25 | "us-east1", 26 | "us-east4", 27 | "us-west1", 28 | "us-west2" 29 | ]; 30 | 31 | static def Zones = [ 32 | "us-east1-b", 33 | "us-east1-c", 34 | "us-east1-d", 35 | "us-east4-c", 36 | "us-east4-b", 37 | "us-east4-a", 38 | "us-central1-c", 39 | "us-central1-a", 40 | "us-central1-f", 41 | "us-central1-b", 42 | "us-west1-b", 43 | "us-west1-c", 44 | "us-west1-a", 45 | "europe-west4-a", 46 | "europe-west4-b", 47 | "europe-west4-c", 48 | "europe-west1-b", 49 | "europe-west1-d", 50 | "europe-west1-c", 51 | "europe-west3-c", 52 | "europe-west3-a", 53 | "europe-west3-b", 54 | "europe-west2-c", 55 | "europe-west2-b", 56 | "europe-west2-a", 57 | "asia-east1-b", 58 | "asia-east1-a", 59 | "asia-east1-c", 60 | "asia-southeast1-b", 61 | "asia-southeast1-a", 62 | "asia-southeast1-c", 63 | "asia-northeast1-b", 64 | "asia-northeast1-c", 65 | "asia-northeast1-a", 66 | "asia-south1-c", 67 | "asia-south1-b", 68 | "asia-south1-a", 69 | "australia-southeast1-b", 70 | "australia-southeast1-c", 71 | "australia-southeast1-a", 72 | "southamerica-east1-b", 73 | "southamerica-east1-c", 74 | "southamerica-east1-a", 75 | "asia-east2-a", 76 | "asia-east2-b", 77 | "asia-east2-c", 78 | "europe-north1-a", 79 | "europe-north1-b", 80 | "europe-north1-c", 81 | "northamerica-northeast1-a", 82 | "northamerica-northeast1-b", 83 | "northamerica-northeast1-c", 84 | "us-west2-a", 85 | "us-west2-b", 86 | "us-west2-c" 87 | ]; 88 | 89 | static def MachineTypes = [ 90 | "n1-standard-2", 91 | "f1-micro", 92 | "g1-small", 93 | "n1-standard-1", 94 | "n1-standard-2", 95 | "n1-standard-4", 96 | "n1-standard-8", 97 | "n1-standard-16", 98 | "n1-standard-32", 99 | "n1-highmem-2", 100 | "n1-highmem-4", 101 | "n1-highmem-8", 102 | "n1-highmem-16", 103 | "n1-highmem-32", 104 | "n1-highcpu-2", 105 | "n1-highcpu-4", 106 | "n1-highcpu-8", 107 | "n1-highcpu-16", 108 | "n1-highcpu-32" 109 | ]; 110 | 111 | static def K8sMasterVersions = [ 112 | "1.11.6-gke.6", 113 | "1.11.6-gke.3", 114 | "1.11.6-gke.2", 115 | "1.11.6-gke.0", 116 | "1.11.5-gke.5", 117 | "1.10.12-gke.1", 118 | "1.10.12-gke.0", 119 | "1.10.11-gke.1" 120 | ]; 121 | } 122 | --------------------------------------------------------------------------------