├── .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 |
--------------------------------------------------------------------------------