├── .gitignore
├── 01-gradle-build
├── .gitignore
├── Dockerfile
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── jenkins-home
│ ├── ListPlugins.groovy
│ ├── init.groovy.d
│ └── startup.groovy
│ ├── jobs
│ └── an-example-of-github-project
│ │ └── config.xml
│ ├── plugins.txt
│ └── userContent
│ └── README.md
├── 02-job-dsl
├── .gitignore
├── Dockerfile
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── jenkins-home
│ ├── dsl
│ └── managedJobs.groovy.override
│ ├── init.groovy.d
│ └── startup.groovy
│ ├── jobs
│ └── seed
│ │ └── config.xml.override
│ ├── plugins.txt
│ └── userContent
│ └── README.md
├── 03-integration-tests
├── .gitignore
├── Dockerfile
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jenkins-home
│ ├── dsl
│ │ └── managedJobs.groovy
│ ├── init.groovy.d
│ │ └── startup.groovy
│ ├── jobs
│ │ └── seed
│ │ │ └── config.xml.override
│ ├── plugins.txt
│ └── userContent
│ │ └── README.md
└── src
│ └── integration
│ └── groovy
│ └── com
│ └── ticketfly
│ └── jenkins
│ ├── common
│ └── JenkinsCLIWrapper.groovy
│ └── spec
│ ├── JenkinsSpec.groovy
│ ├── global
│ └── JenkinsInstanceSpec.groovy
│ └── job
│ └── RunJobSpec.groovy
├── 04-job-dsl-for-github-org
├── .gitignore
├── Dockerfile
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── jenkins-home
│ ├── config-file-provider
│ ├── generate-jobs-for-org.groovy
│ └── github-lib-build.gradle
│ ├── dsl
│ └── managedJobs.groovy
│ ├── init.groovy.d
│ └── startup.groovy
│ ├── jobs
│ └── seed
│ │ └── config.xml.override
│ ├── plugins.txt
│ └── userContent
│ └── README.md
├── 05-aws-ecs
├── .gitignore
├── Dockerfile
├── README.md
├── build.gradle
├── cloudformation
│ └── jenkins-ecs-stack.json
├── deploy-stack-to-aws.sh
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jenkins-home
│ ├── dsl
│ │ └── managedJobs.groovy.override
│ ├── init.groovy.d
│ │ └── startup.groovy.override
│ ├── jobs
│ │ └── seed
│ │ │ └── config.xml.override
│ ├── plugins.txt
│ └── userContent
│ │ └── README.md
└── upload-cloudformation-to-s3.sh
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 |
4 | # Ignore Gradle GUI config
5 | gradle-app.setting
6 |
7 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
8 | !gradle-wrapper.jar
9 |
10 | # Cache of project
11 | .gradletasknamecache
12 |
13 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
14 | # gradle/wrapper/gradle-wrapper.properties
15 | .idea
16 |
--------------------------------------------------------------------------------
/01-gradle-build/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 |
4 | # Ignore Gradle GUI config
5 | gradle-app.setting
6 |
7 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
8 | !gradle-wrapper.jar
9 |
10 | # Cache of project
11 | .gradletasknamecache
12 |
13 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
14 | # gradle/wrapper/gradle-wrapper.properties
15 |
--------------------------------------------------------------------------------
/01-gradle-build/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM jenkins:2.19.4-alpine
2 |
3 | ENV JENKINS_REF /usr/share/jenkins/ref
4 |
5 | # install jenkins plugins
6 | COPY jenkins-home/plugins.txt $JENKINS_REF/
7 | RUN /usr/local/bin/plugins.sh $JENKINS_REF/plugins.txt
8 |
9 | ENV JAVA_OPTS -Dorg.eclipse.jetty.server.Request.maxFormContentSize=100000000 \
10 | -Dorg.apache.commons.jelly.tags.fmt.timeZone=America/Los_Angeles \
11 | -Dhudson.diyChunking=false \
12 | -Djenkins.install.runSetupWizard=false
13 |
14 | # copy scripts and ressource files
15 | COPY jenkins-home/*.* $JENKINS_REF/
16 | COPY jenkins-home/userContent $JENKINS_REF/userContent
17 | COPY jenkins-home/jobs $JENKINS_REF/jobs/
18 | COPY jenkins-home/init.groovy.d $JENKINS_REF/init.groovy.d/
19 |
--------------------------------------------------------------------------------
/01-gradle-build/README.md:
--------------------------------------------------------------------------------
1 | # jenkins-example-gradle-build
2 | Jenkins based on a docker image configured as a gradle project.
3 |
4 | This example provides a fully working Jenkins server based on the offial docker image.
5 | Jenkins comes up with a job defined in the docker image (in a config.xml) and a simple groovy startup script.
6 | A set of gradle tasks maps to useful docker commands such as build, run and push.
7 |
8 | Check out the [blog article](https://tech.ticketfly.com/our-journey-to-continuous-delivery-chapter-2-run-jenkins-on-docker-49c32532cb7e) for this example.
9 |
10 | # Project structure
11 | .
12 | ├── jenkins-home # Jenkins files to be deployed in docker image
13 | │ ├── init.groovy.d # groovy scripts executed when Jenkins starts
14 | │ ├── jobs # jobs deployed on Jenkins
15 | │ ├── userContent # these files are served from http://yourhost/jenkins/userContent
16 | │ ├── ListPlugins.groovy # utility script to generate the content for plugins.txt
17 | │ └── plugins.txt # list of Jenkins plugins to install
18 | ├── Dockerfile # command lines to assemble the docker image
19 | └── build.gradle # gradle build file
20 |
21 | # Quick start
22 |
23 | - `./gradlew dockerBuild dockerRun` Build the docker image locally and start Jenkins at http://localhost:8080/
24 |
25 | # Video Tutorials
26 | [](http://www.youtube.com/watch?v=LUgF9kOW4u4)
27 |
28 | [](http://www.youtube.com/watch?v=2JTxROGphdw)
29 |
30 | # Deployment
31 |
32 | ## Deploy using the dockerhub image
33 | The image is hosted at https://hub.docker.com/r/ticketfly/jenkins-example-gradle-build
34 | ```shell
35 | docker run --name jenkins -p 8080:8080 -v /var/jenkins_home ticketfly/jenkins-example-gradle-build
36 | ```
37 |
38 | ## Configuring a volume for Jenkins home
39 | ```shell
40 | #use a directory for which you have permission
41 | JENKINS_HOME='/data/jenkins'
42 | mkdir -p $JENKINS_HOME
43 | #chown required for linux, ignore this line for mac or windows
44 | chown 1000:1000 $JENKINS_HOME
45 | docker run --name jenkins -p 8080:8080 -v $JENKINS_HOME:/var/jenkins_home ticketfly/jenkins-example-gradle-build
46 | ```
47 |
48 | # Docker gradle commands
49 |
50 | This project provides a set of gradle tasks mapping to docker commands.
51 | - `./gradlew dockerBuild` Build the docker image, tag as current version.
52 | - `./gradlew dockerRun` Run the Jenkins docker container locally.
53 | - `./gradlew dockerRun -PdockerDetached=true` detached mode, to run on CI.
54 | - `./gradlew dockerStop` Stop the Jenkins docker container, to run on CI.
55 | - `./gradlew dockerStatus` Display the process status of Jenkins docker container.
56 | - `./gradlew dockerPush` Push the docker image with the current tag.
57 |
58 | # Why Jenkins docker?
59 |
60 | Using the docker image allows to pre-package Jenkins with some desired configuration.
61 | It includes the versions to use for Jenkins and the list of plugins, some startup scripts, and the user content folder.
62 | Once Jenkins is configured in the docker image, the deployment to your server instance is simplified, just install docker and run your image in a container.
63 | This can be achieved by either building your image on your server directly or by pushing the image to a docker repo and pulling it from your server.
64 |
65 | Another great advantage of using docker is the ability to bring up a Jenkins server locally very quickly.
66 | This enables staging the changes before deploying on your Jenkins server.
67 |
68 | # Why Gradle?
69 |
70 | Gradle simplifies the use of docker by providing a list of tasks mapping to Docker commands with arguments.
71 | Running this project on CI is simplified as gradle wrapper will self install itself.
72 | It will allow adding integration tests for Jenkins, see other examples provided by Ticketfly.
73 |
74 | # Dockerhub
75 | Hosted at [ticketfly/jenkins-example-gradle-build](https://hub.docker.com/r/ticketfly/jenkins-example-gradle-build/)
76 |
77 | # Requirements
78 |
79 | - Docker Version 1.10 or higher.
80 |
81 | # Resources
82 |
83 | - [Official Jenkins Docker image](https://github.com/jenkinsci/docker)
84 | - [Ticketfly Tech: Run Jenkins on Docker](https://tech.ticketfly.com/our-journey-to-continuous-delivery-chapter-2-run-jenkins-on-docker-49c32532cb7e)
85 |
--------------------------------------------------------------------------------
/01-gradle-build/build.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | dockerImageName = "ticketfly/$name"
3 | dockerTag = version
4 | jenkinsPort = 8080
5 | jenkinsHost = 'localhost'
6 | jenkinsURL = project.hasProperty('jenkinsURL') ? jenkinsURL : "http://$jenkinsHost:$jenkinsPort"
7 | dockerDetached = project.hasProperty('dockerDetached') ? dockerDetached : 'false'
8 | }
9 |
10 | task wrapper(type: Wrapper) {
11 | gradleVersion = '2.4'
12 | }
13 |
14 | task dockerRun(type: Exec) {
15 | group 'Docker'
16 | description "Run the jenkins docker container locally, use the 'latest' tag"
17 | commandLine 'docker'
18 | args = ['run', dockerDetached == 'true' ? '-d' : '--rm', '--name', 'jenkins', '-p', "$jenkinsPort:$jenkinsPort", '-p', '50000:50000', '-v', '/var/jenkins_home', "$dockerImageName"]
19 | doLast {
20 | try {
21 | if(dockerDetached == 'true'){
22 | waitForHTTPResponse(jenkinsURL, 200)
23 | println "Jenkins server started at $jenkinsURL"
24 | }
25 | } catch (e) {
26 | throw new GradleException("Could not connect to $jenkinsURL", e)
27 | }
28 | }
29 | }
30 |
31 | @groovy.transform.TimedInterrupt(120L)
32 | def void waitForHTTPResponse(String url, int responseCode) {
33 | println "Waiting for HTTP response $responseCode for '$url'"
34 | boolean isConnected = false
35 | while (!isConnected) {
36 | try {
37 | isConnected = url.toURL().openConnection().responseCode == responseCode
38 | } catch (any) {
39 | }
40 | Thread.sleep(500)
41 | }
42 | }
43 |
44 | task dockerStop(type: Exec) {
45 | group 'Docker'
46 | description 'Stop the jenkins docker container'
47 | commandLine 'docker'
48 | args = ['rm', '-f', '-v', 'jenkins']
49 | }
50 |
51 | task dockerStatus(type: Exec) {
52 | group 'Docker'
53 | description 'Display the process status of jenkins docker container'
54 | commandLine 'docker'
55 | args = ['ps', '-a', '-f', "name=jenkins"]
56 | }
57 |
58 | task dockerBuild(type: Exec) {
59 | group 'Docker'
60 | description "Build the docker image, tag as current version and 'as latest'."
61 | commandLine 'docker'
62 | args = ['build', '-t', "$dockerImageName:$dockerTag", '-t', "$dockerImageName:latest", "."]
63 | }
64 |
65 | task dockerPush(type: Exec) {
66 | group 'Docker'
67 | description 'Push the docker image with the current tag'
68 | commandLine 'docker'
69 | args = ['push', "$dockerImageName:$dockerTag"]
70 | }
71 |
--------------------------------------------------------------------------------
/01-gradle-build/gradle.properties:
--------------------------------------------------------------------------------
1 | name=jenkins-example-gradle-build
2 | #for consistency, same as jenkins version
3 | version=2.19.4
4 |
--------------------------------------------------------------------------------
/01-gradle-build/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ticketfly/jenkins-docker-examples/5111bb1e082d0d89193beeeffa260910f0caa70e/01-gradle-build/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/01-gradle-build/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Sep 30 13:57:32 PDT 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-bin.zip
7 |
--------------------------------------------------------------------------------
/01-gradle-build/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/01-gradle-build/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/01-gradle-build/jenkins-home/ListPlugins.groovy:
--------------------------------------------------------------------------------
1 | //to run this script on jenkins, copy and past in script console http://[JENKINS_URL]/script
2 | //after running the script, use the output to update plugins.txt
3 | import hudson.PluginWrapper
4 | import jenkins.model.Jenkins
5 |
6 | def pluginList=Jenkins.getInstance().getPluginManager().getPlugins().collect {
7 | "${it.shortName}:${it.version}"
8 | }.sort()
9 | pluginList.each { println it }
10 |
--------------------------------------------------------------------------------
/01-gradle-build/jenkins-home/init.groovy.d/startup.groovy:
--------------------------------------------------------------------------------
1 | import jenkins.model.Jenkins
2 | import hudson.model.*
3 | import java.util.logging.Logger
4 |
5 | Logger.global.info("[Running] startup script")
6 |
7 | configureSecurity()
8 |
9 | Jenkins.instance.save()
10 |
11 | buildJob('an-example-of-github-project')
12 |
13 | Logger.global.info("[Done] startup script")
14 |
15 | private void configureSecurity() {
16 | Jenkins.getInstance().disableSecurity()
17 | }
18 |
19 | private def buildJob(String jobName) {
20 | Logger.global.info("Building job '$jobName")
21 | def job = Jenkins.instance.getJob(jobName)
22 | Jenkins.instance.queue.schedule(job, 0, new CauseAction(new Cause() {
23 | @Override
24 | String getShortDescription() {
25 | 'Jenkins startup script'
26 | }
27 | }))
28 | }
29 |
--------------------------------------------------------------------------------
/01-gradle-build/jenkins-home/jobs/an-example-of-github-project/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Simple jenkins job generated by the DSL, the project is a small gradle project hosted on github
5 | false
6 |
7 |
8 |
9 | -1
10 | 20
11 | -1
12 | -1
13 |
14 |
15 |
16 | https://github.com/gradle/continuous-delivery-jump-start/
17 |
18 |
19 |
20 |
21 | 2
22 |
23 |
24 | https://github.com/gradle/continuous-delivery-jump-start.git
25 |
26 |
27 |
28 |
29 | master
30 |
31 |
32 | false
33 |
34 | https://github.com/gradle/continuous-delivery-jump-start/
35 |
36 |
37 |
38 |
39 | true
40 | false
41 | false
42 | false
43 |
44 | false
45 |
46 |
47 | ./gradlew build
48 |
49 |
50 |
51 |
52 | build/libs/*.war
53 | false
54 | false
55 | false
56 | true
57 | true
58 |
59 |
60 | build/test-results/**/*.xml
61 | false
62 | 1.0
63 | false
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/01-gradle-build/jenkins-home/plugins.txt:
--------------------------------------------------------------------------------
1 | bouncycastle-api:2.16.0
2 | credentials:2.1.10
3 | display-url-api:0.5
4 | git-client:2.1.0
5 | git:3.0.1
6 | github-api:1.82
7 | github:1.25.0
8 | junit:1.19
9 | mailer:1.18
10 | matrix-project:1.7.1
11 | plain-credentials:1.3
12 | scm-api:1.3
13 | script-security:1.24
14 | ssh-credentials:1.12
15 | structs:1.5
16 | token-macro:2.0
17 | workflow-scm-step:2.3
18 | workflow-step-api:2.6
--------------------------------------------------------------------------------
/01-gradle-build/jenkins-home/userContent/README.md:
--------------------------------------------------------------------------------
1 | all files in this directory will be made available at $JENKINS_URL/userContent
2 |
--------------------------------------------------------------------------------
/02-job-dsl/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 |
4 | # Ignore Gradle GUI config
5 | gradle-app.setting
6 |
7 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
8 | !gradle-wrapper.jar
9 |
10 | # Cache of project
11 | .gradletasknamecache
12 |
13 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
14 | # gradle/wrapper/gradle-wrapper.properties
15 |
--------------------------------------------------------------------------------
/02-job-dsl/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM jenkins:2.19.4-alpine
2 |
3 | ENV JENKINS_REF /usr/share/jenkins/ref
4 |
5 | # install jenkins plugins
6 | COPY jenkins-home/plugins.txt $JENKINS_REF/
7 | RUN /usr/local/bin/plugins.sh $JENKINS_REF/plugins.txt
8 |
9 | ENV JAVA_OPTS -Dorg.eclipse.jetty.server.Request.maxFormContentSize=100000000 \
10 | -Dorg.apache.commons.jelly.tags.fmt.timeZone=America/Los_Angeles \
11 | -Dhudson.diyChunking=false \
12 | -Djenkins.install.runSetupWizard=false
13 |
14 | # copy scripts and ressource files
15 | COPY jenkins-home/*.* $JENKINS_REF/
16 | COPY jenkins-home/userContent $JENKINS_REF/userContent
17 | COPY jenkins-home/jobs $JENKINS_REF/jobs/
18 | COPY jenkins-home/init.groovy.d $JENKINS_REF/init.groovy.d/
19 | COPY jenkins-home/dsl $JENKINS_REF/dsl/
20 |
--------------------------------------------------------------------------------
/02-job-dsl/README.md:
--------------------------------------------------------------------------------
1 | # jenkins-example-job-dsl
2 | This example, based on the [gradle-build example](https://github.com/Ticketfly/jenkins-docker-examples/tree/master/01-gradle-build), uses the [Job DSL Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Job+DSL+Plugin) to deploy the job configuration programmatically.
3 |
4 | Instead of copying over the config.xml file for the managed job, the job is defined in a [groovy script](https://github.com/Ticketfly/jenkins-docker-examples/blob/master/02-job-dsl/jenkins-home/dsl/managedJobs.groovy.override) using the provided DSL.
5 |
6 | Check out the [blog article](https://tech.ticketfly.com/our-journey-to-continuous-delivery-chapter-3-automate-your-configuration-with-jenkins-dsl-1ff14d7de4c4) for this example.
7 |
8 | # How does it work?
9 | 1. When the docker image is built, the seed job and the dsl script are copied over to Jenkins home.
10 | 2. When running the docker image, the startup script builds the seed job.
11 | 3. The seed job runs the DSL script, programmatically creating the job 'an-example-of-github-project'.
12 |
13 | # Files changed (comparing to gradle-build example)
14 | .
15 | ├── jenkins-home
16 | │ ├── init.groovy.d
17 | │ │ └── startup.groovy # run the seed job
18 | │ ├── jobs
19 | │ │ └── seed # seed job definition (should be the only config.xml)
20 | │ ├── dsl
21 | │ │ └── managedJobs.groovy.override # dsl script to create the managed jobs
22 | │ └── plugins.txt # job-dsl plugin was added
23 | └── Dockerfile # copy over the dsl directory
24 |
25 | # Quick start
26 |
27 | - `./gradlew dockerBuild dockerRun` Build the docker image locally and start Jenkins at http://localhost:8080/
28 |
29 | # Video Tutorials
30 | [](http://www.youtube.com/watch?v=WdSSlQua6bw)
31 |
32 | # Why using the DSL?
33 |
34 | The Job DSL Plugin allows the programmatic creation of projects using a DSL.
35 | This method is preferred over managing the job config.xml file(s).
36 | The DSL is more compact and readable compared to the XML format.
37 | Since the DSL is executed as a groovy script, it allows runtime logic (docker run time) whereas the XML file is a static content (docker build time).
38 |
39 | # Dockerhub
40 | Hosted at [ticketfly/jenkins-example-job-dsl](https://hub.docker.com/r/ticketfly/jenkins-example-job-dsl/)
41 |
42 | # Requirements
43 |
44 | - Docker Version 1.10 or higher.
45 |
46 | # Resources
47 |
48 | - [Official Jenkins Docker image](https://github.com/jenkinsci/docker)
49 | - [Job DSL Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Job+DSL+Plugin) ; [API viewer](https://jenkinsci.github.io/job-dsl-plugin/)
50 | - [Ticketfly Tech: Automate your configuration with Jenkins DSL](https://tech.ticketfly.com/our-journey-to-continuous-delivery-chapter-3-automate-your-configuration-with-jenkins-dsl-1ff14d7de4c4)
51 |
--------------------------------------------------------------------------------
/02-job-dsl/build.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | dockerImageName = "ticketfly/$name"
3 | dockerTag = version
4 | jenkinsPort = 8080
5 | jenkinsHost = 'localhost'
6 | jenkinsURL = project.hasProperty('jenkinsURL') ? jenkinsURL : "http://$jenkinsHost:$jenkinsPort"
7 | dockerDetached = project.hasProperty('dockerDetached') ? dockerDetached : 'false'
8 | }
9 |
10 | task wrapper(type: Wrapper) {
11 | gradleVersion = '2.4'
12 | }
13 |
14 | task dockerRun(type: Exec) {
15 | group 'Docker'
16 | description "Run the jenkins docker container locally, use the 'latest' tag"
17 | commandLine 'docker'
18 | args = ['run', dockerDetached == 'true' ? '-d' : '--rm', '--name', 'jenkins', '-p', "$jenkinsPort:$jenkinsPort", '-p', '50000:50000', '-v', '/var/jenkins_home', "$dockerImageName"]
19 | doLast {
20 | try {
21 | if(dockerDetached == 'true'){
22 | waitForHTTPResponse(jenkinsURL, 200)
23 | println "Jenkins server started at $jenkinsURL"
24 | }
25 | } catch (e) {
26 | throw new GradleException("Could not connect to $jenkinsURL", e)
27 | }
28 | }
29 | }
30 |
31 | @groovy.transform.TimedInterrupt(120L)
32 | def void waitForHTTPResponse(String url, int responseCode) {
33 | println "Waiting for HTTP response $responseCode for '$url'"
34 | boolean isConnected = false
35 | while (!isConnected) {
36 | try {
37 | isConnected = url.toURL().openConnection().responseCode == responseCode
38 | } catch (any) {
39 | }
40 | Thread.sleep(500)
41 | }
42 | }
43 |
44 | task dockerStop(type: Exec) {
45 | group 'Docker'
46 | description 'Stop the jenkins docker container'
47 | commandLine 'docker'
48 | args = ['rm', '-f', '-v', 'jenkins']
49 | }
50 |
51 | task dockerStatus(type: Exec) {
52 | group 'Docker'
53 | description 'Display the process status of jenkins docker container'
54 | commandLine 'docker'
55 | args = ['ps', '-a', '-f', "name=jenkins"]
56 | }
57 |
58 | task dockerBuild(type: Exec) {
59 | group 'Docker'
60 | description "Build the docker image, tag as current version and 'as latest'."
61 | commandLine 'docker'
62 | args = ['build', '-t', "$dockerImageName:$dockerTag", '-t', "$dockerImageName:latest", "."]
63 | }
64 |
65 | task dockerPush(type: Exec) {
66 | group 'Docker'
67 | description 'Push the docker image with the current tag'
68 | commandLine 'docker'
69 | args = ['push', "$dockerImageName:$dockerTag"]
70 | }
71 |
--------------------------------------------------------------------------------
/02-job-dsl/gradle.properties:
--------------------------------------------------------------------------------
1 | name=jenkins-example-job-dsl
2 | #for consistency, same as jenkins version
3 | version=2.19.4
4 |
--------------------------------------------------------------------------------
/02-job-dsl/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ticketfly/jenkins-docker-examples/5111bb1e082d0d89193beeeffa260910f0caa70e/02-job-dsl/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/02-job-dsl/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Sep 30 13:57:32 PDT 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-bin.zip
7 |
--------------------------------------------------------------------------------
/02-job-dsl/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/02-job-dsl/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/02-job-dsl/jenkins-home/dsl/managedJobs.groovy.override:
--------------------------------------------------------------------------------
1 | job('an-example-of-github-project') {
2 | description("Simple jenkins job generated by the DSL on ${new Date()}, the project is a small gradle project hosted on github")
3 | label('master')
4 | logRotator {
5 | numToKeep 20
6 | }
7 | scm {
8 | git {
9 | remote {
10 | github('gradle/continuous-delivery-jump-start', 'https')
11 | }
12 | branch('master')
13 | }
14 | }
15 | steps {
16 | shell('./gradlew build')
17 | }
18 | publishers {
19 | archiveArtifacts 'build/libs/*.war'
20 | archiveJunit 'build/test-results/**/*.xml'
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/02-job-dsl/jenkins-home/init.groovy.d/startup.groovy:
--------------------------------------------------------------------------------
1 | import jenkins.model.Jenkins
2 | import hudson.model.*
3 | import java.util.logging.Logger
4 |
5 | Logger.global.info("[Running] startup script")
6 |
7 | configureSecurity()
8 |
9 | Jenkins.instance.save()
10 |
11 | buildJob('seed')
12 |
13 | Logger.global.info("[Done] startup script")
14 |
15 | private void configureSecurity() {
16 | Jenkins.getInstance().disableSecurity()
17 | }
18 |
19 | private def buildJob(String jobName) {
20 | Logger.global.info("Building job '$jobName")
21 | def job = Jenkins.instance.getJob(jobName)
22 | Jenkins.instance.queue.schedule(job, 0, new CauseAction(new Cause() {
23 | @Override
24 | String getShortDescription() {
25 | 'Jenkins startup script'
26 | }
27 | }))
28 | }
29 |
--------------------------------------------------------------------------------
/02-job-dsl/jenkins-home/jobs/seed/config.xml.override:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Generate DSL jobs
5 | false
6 |
7 | master
8 | false
9 | false
10 | false
11 | false
12 | (System)
13 |
14 | false
15 |
16 |
17 | cp $JENKINS_HOME/dsl/*.groovy .
18 |
19 |
20 | *.groovy
21 | false
22 | false
23 | IGNORE
24 | IGNORE
25 | JENKINS_ROOT
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/02-job-dsl/jenkins-home/plugins.txt:
--------------------------------------------------------------------------------
1 | bouncycastle-api:2.16.0
2 | credentials:2.1.10
3 | display-url-api:0.5
4 | git-client:2.1.0
5 | git:3.0.1
6 | github-api:1.82
7 | github:1.25.0
8 | job-dsl:1.54
9 | junit:1.19
10 | mailer:1.18
11 | matrix-project:1.7.1
12 | plain-credentials:1.3
13 | scm-api:1.3
14 | script-security:1.24
15 | ssh-credentials:1.12
16 | structs:1.5
17 | token-macro:2.0
18 | workflow-scm-step:2.3
19 | workflow-step-api:2.6
--------------------------------------------------------------------------------
/02-job-dsl/jenkins-home/userContent/README.md:
--------------------------------------------------------------------------------
1 | all files in this directory will be made available at $JENKINS_URL/userContent
2 |
--------------------------------------------------------------------------------
/03-integration-tests/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 |
4 | # Ignore Gradle GUI config
5 | gradle-app.setting
6 |
7 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
8 | !gradle-wrapper.jar
9 |
10 | # Cache of project
11 | .gradletasknamecache
12 |
13 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
14 | # gradle/wrapper/gradle-wrapper.properties
15 | *.iml
16 | *.ipr
17 | *.iws
18 |
--------------------------------------------------------------------------------
/03-integration-tests/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM jenkins/jenkins:2.75-alpine
2 |
3 | ENV JENKINS_REF /usr/share/jenkins/ref
4 |
5 | # install jenkins plugins
6 | COPY jenkins-home/plugins.txt $JENKINS_REF/
7 | RUN /usr/local/bin/plugins.sh $JENKINS_REF/plugins.txt
8 |
9 | ENV JAVA_OPTS -Dorg.eclipse.jetty.server.Request.maxFormContentSize=100000000 \
10 | -Dorg.apache.commons.jelly.tags.fmt.timeZone=America/Los_Angeles \
11 | -Dhudson.diyChunking=false \
12 | -Djenkins.install.runSetupWizard=false
13 |
14 | # copy scripts and ressource files
15 | COPY jenkins-home/*.* $JENKINS_REF/
16 | COPY jenkins-home/userContent $JENKINS_REF/userContent
17 | COPY jenkins-home/jobs $JENKINS_REF/jobs/
18 | COPY jenkins-home/init.groovy.d $JENKINS_REF/init.groovy.d/
19 | COPY jenkins-home/dsl/managedJobs.groovy $JENKINS_REF/dsl/managedJobs.groovy.override
20 |
--------------------------------------------------------------------------------
/03-integration-tests/README.md:
--------------------------------------------------------------------------------
1 | # jenkins-example-integration-tests
2 | This example is based on the [job-dsl project](https://github.com/Ticketfly/jenkins-docker-examples/tree/master/02-job-dsl).
3 |
4 | The integration tests ensure that Jenkins is running properly, they run some jobs and verify the console output.
5 |
6 | # How does it work?
7 | - Start Jenkins using the docker image.
8 | - Run integration tests, the tests uses the [CLI client](https://github.com/jenkinsci/jenkins/blob/master/cli/src/main/java/hudson/cli/CLI.java) to access Jenkins. The tests consist on invoking groovy scripts on Jenkins and running jobs.
9 | - [JenkinsInstanceSpec](https://github.com/Ticketfly/jenkins-docker-examples/tree/master/03-integration-tests/src/integration/groovy/com/ticketfly/jenkins/spec/global/JenkinsInstanceSpec.groovy): Verify that Jenkins configuration is correct: Jenkins versions, installed plugins, deployed jobs, startup script executed.
10 | - [RunJobSpec](https://github.com/Ticketfly/jenkins-docker-examples/tree/master/03-integration-tests/src/integration/groovy/com/ticketfly/jenkins/spec/job/RunJobSpec.groovy): Run some Jenkins jobs and verify that their console output is correct. Use existing jobs or create jobs on the fly using the DSL.
11 |
12 | # Integration test files
13 | .
14 | ├── src/integration/groovy # Integration tests
15 | │ ├── com.ticketfly.jenkins.common.JenkinsCLIWrapper # Wrapper for the Jenkins CLI
16 | │ ├── com.ticketfly.jenkins.spec.JenkinsSpec # Base Specification
17 | │ ├── com.ticketfly.jenkins.spec.global.JenkinsInstanceSpec # Test Jenkins configuration
18 | │ ├── com.ticketfly.jenkins.spec.job.RunJobSpec # Test Jenkins jobs
19 | └── build.gradle # define integration tasks
20 |
21 | # Quick start
22 |
23 | - `./gradlew runIntegration` Build the docker image, start Jenkins, run integration tests, stop Jenkins.
24 |
25 | - `./gradlew integration` Run integration tests. (Jenkins has to be started)
26 |
27 | - `./gradlew integration -Dintegration.single=JenkinsInstanceSpec` Run a single spec.
28 |
29 | - `curl -sSL "http://localhost:8080/pluginManager/api/xml?depth=1&xpath=/*/*/shortName|/*/*/version&wrapper=plugins" | perl -pe 's/.*?([\w-]+).*?([^<]+)()(<\/\w+>)+/\1 \2\n/g'|sed 's/ /:/' | sort
30 | ` List plugin versions on running Jenkins.
31 |
32 | # Video Tutorials
33 | [](http://www.youtube.com/watch?v=E1_8mz0HCnE)
34 |
35 | # Why having integration tests for Jenkins?
36 |
37 | They are many things that can go wrong when making some changes on the Jenkins docker image:
38 | - Version or plugin upgrade not backward compatible: unfortunately this is not uncommon with Jenkins and its plugin ecosystem. More generally, having integration tests will enable you to upgrade more frequently.
39 | - Error in DSL scripts. Integration tests can verify and run the jobs generated by the DSL, they would fail if the jobs were missing or are incorrect.
40 | - Error during startup script. Like any runtime scripts, the scripts located in [`init.groovy.d`](https://github.com/Ticketfly/jenkins-docker-examples/tree/master/03-integration-tests/jenkins-home/init.groovy.d) can be easy to break and hard to test. In this example, the startup script sets the markup formatter to be HTML. An integration verifies this, the test is called [check configured markup is HTML](https://github.com/Ticketfly/jenkins-docker-examples/tree/master/03-integration-tests/src/integration/groovy/com/ticketfly/jenkins/spec/global/JenkinsInstanceSpec.groovy#L51).
41 |
42 | # Dockerhub
43 | Hosted at [ticketfly/jenkins-example-integration-tests](https://hub.docker.com/r/ticketfly/jenkins-example-integration-tests/)
44 |
45 | # Requirements
46 |
47 | - Docker Version 1.10 or higher.
48 |
49 | # Resources
50 |
51 | - [Official Jenkins Docker image](https://github.com/jenkinsci/docker)
52 | - [Spock Framework](https://github.com/spockframework/spock)
53 |
--------------------------------------------------------------------------------
/03-integration-tests/build.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | dockerImageName = "ticketfly/$name"
3 | dockerTag = version
4 | jenkinsVersion = version
5 | jenkinsPort = 8080
6 | jenkinsHost = 'localhost'
7 | jenkinsURL = project.hasProperty('jenkinsURL') ? jenkinsURL : "http://$jenkinsHost:$jenkinsPort"
8 | dockerDetached = project.hasProperty('dockerDetached') ? dockerDetached : 'false'
9 | }
10 |
11 | apply plugin: 'groovy'
12 |
13 | repositories {
14 | mavenCentral()
15 | jcenter()
16 | maven { url 'http://repo.jenkins-ci.org/releases/' }
17 | }
18 |
19 | configurations {
20 | integrationCompile.extendsFrom testCompile
21 | }
22 |
23 | sourceSets {
24 | integration {
25 | groovy {
26 | srcDir file('src/integration/groovy')
27 | }
28 | }
29 | }
30 |
31 | dependencies {
32 | testCompile 'org.codehaus.groovy:groovy-all:2.4.7'
33 | testCompile 'org.spockframework:spock-core:1.1-groovy-2.4-rc-3'
34 | testCompile "org.jenkins-ci.main:jenkins-core:$jenkinsVersion"
35 | testCompile 'org.jenkins-ci.plugins:job-dsl-core:1.54'
36 | }
37 |
38 | task wrapper(type: Wrapper) {
39 | gradleVersion = '3.2.1'
40 | }
41 |
42 | task dockerRun(type: Exec) {
43 | group 'Docker'
44 | description "Run the jenkins docker container locally, use the 'latest' tag"
45 | commandLine 'docker'
46 | doFirst {
47 | args = ['run', dockerDetached == 'true' ? '-d' : '--rm', '--name', 'jenkins', '-p', "$jenkinsPort:$jenkinsPort", '-p', '50000:50000', '-v', '/var/jenkins_home', "$dockerImageName"]
48 | }
49 | doLast {
50 | try {
51 | if (dockerDetached == 'true') {
52 | waitForHTTPResponse(jenkinsURL, 200)
53 | println "Jenkins server started at $jenkinsURL"
54 | }
55 | } catch (e) {
56 | throw new GradleException("Could not connect to $jenkinsURL", e)
57 | }
58 | }
59 | }
60 |
61 | @groovy.transform.TimedInterrupt(120L)
62 | def void waitForHTTPResponse(String url, int responseCode) {
63 | println "Waiting for HTTP response $responseCode for '$url'"
64 | boolean isConnected = false
65 | while (!isConnected) {
66 | try {
67 | isConnected = url.toURL().openConnection().responseCode == responseCode
68 | } catch (any) {
69 | }
70 | Thread.sleep(500)
71 | }
72 | }
73 |
74 | task dockerStop(type: Exec) {
75 | group 'Docker'
76 | description 'Stop the jenkins docker container'
77 | commandLine 'docker'
78 | args = ['rm', '-f', '-v', 'jenkins']
79 | }
80 |
81 | task dockerStatus(type: Exec) {
82 | group 'Docker'
83 | description 'Display the process status of jenkins docker container'
84 | commandLine 'docker'
85 | args = ['ps', '-a', '-f', "name=jenkins"]
86 | }
87 |
88 | task dockerBuild(type: Exec) {
89 | group 'Docker'
90 | description "Build the docker image, tag as current version and 'as latest'."
91 | commandLine 'docker'
92 | args = ['build', '-t', "$dockerImageName:$dockerTag", '-t', "$dockerImageName:latest", "."]
93 | }
94 |
95 | task dockerPush(type: Exec) {
96 | group 'Docker'
97 | description 'Push the docker image with the current tag'
98 | commandLine 'docker'
99 | args = ['push', "$dockerImageName:$dockerTag"]
100 | }
101 |
102 | task integration(type: Test) {
103 | group 'Test'
104 | testClassesDir = sourceSets.integration.output.classesDir
105 | classpath = sourceSets.integration.runtimeClasspath
106 | systemProperties = [jenkinsURL: jenkinsURL]
107 | testLogging {
108 | events 'passed', 'skipped', 'failed'
109 | }
110 | outputs.upToDateWhen { false }
111 | }
112 |
113 | gradle.taskGraph.whenReady { graph ->
114 | if (graph.hasTask(runIntegration)) {
115 | dockerDetached = 'true'
116 | }
117 | }
118 |
119 | task runIntegration() {
120 | group 'Test'
121 | description 'Run in that order: dockerBuild, dockerRun, integration, dockerStop'
122 | doFirst{
123 | dockerBuild.execute()
124 | dockerRun.execute()
125 | integration.execute()
126 | }
127 | }
128 | runIntegration.finalizedBy dockerStop
129 |
--------------------------------------------------------------------------------
/03-integration-tests/gradle.properties:
--------------------------------------------------------------------------------
1 | name=jenkins-example-integration-tests
2 | #for consistency, same as jenkins version
3 | version=2.75
4 |
--------------------------------------------------------------------------------
/03-integration-tests/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ticketfly/jenkins-docker-examples/5111bb1e082d0d89193beeeffa260910f0caa70e/03-integration-tests/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/03-integration-tests/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Dec 30 15:22:28 PST 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
7 |
--------------------------------------------------------------------------------
/03-integration-tests/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/03-integration-tests/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/03-integration-tests/jenkins-home/dsl/managedJobs.groovy:
--------------------------------------------------------------------------------
1 | job('a-simple-shell-script') {
2 | description("Simple shell job generated by the DSL")
3 | label('master')
4 | logRotator {
5 | numToKeep 20
6 | }
7 | steps {
8 | shell('echo "abc" > output.txt')
9 | }
10 | publishers {
11 | archiveArtifacts 'output.txt'
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/03-integration-tests/jenkins-home/init.groovy.d/startup.groovy:
--------------------------------------------------------------------------------
1 | import jenkins.model.*
2 | import java.util.logging.Logger
3 |
4 | Logger.global.info("[Running] startup script")
5 |
6 | configureSecurity()
7 |
8 | configureMarkup()
9 |
10 | Jenkins.instance.save()
11 |
12 | buildJob('seed')
13 |
14 | Logger.global.info("[Done] startup script")
15 |
16 | private void configureSecurity() {
17 | Jenkins.getInstance().disableSecurity()
18 | }
19 |
20 | private void configureMarkup() {
21 | //configure HTML markup (used with job description and build history label)
22 | Jenkins.instance.setMarkupFormatter(new hudson.markup.RawHtmlMarkupFormatter(false))
23 | }
24 |
25 | private def buildJob(String jobName) {
26 | Logger.global.info("Building job '$jobName")
27 | def job = Jenkins.instance.getJob(jobName)
28 | Jenkins.instance.queue.schedule job, 0, new hudson.model.CauseAction(new hudson.model.Cause() {
29 | @Override
30 | String getShortDescription() {
31 | 'Jenkins startup script'
32 | }
33 | })
34 | }
35 |
--------------------------------------------------------------------------------
/03-integration-tests/jenkins-home/jobs/seed/config.xml.override:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Generate DSL jobs
5 | false
6 |
7 | master
8 | false
9 | false
10 | false
11 | false
12 | (System)
13 |
14 | false
15 |
16 |
17 | cp $JENKINS_HOME/dsl/*.groovy .
18 |
19 |
20 | *.groovy
21 | false
22 | false
23 | IGNORE
24 | IGNORE
25 | JENKINS_ROOT
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/03-integration-tests/jenkins-home/plugins.txt:
--------------------------------------------------------------------------------
1 | ace-editor:1.1
2 | antisamy-markup-formatter:1.5
3 | authentication-tokens:1.3
4 | bouncycastle-api:2.16.2
5 | branch-api:2.0.11
6 | cloudbees-folder:6.1.2
7 | credentials-binding:1.13
8 | credentials:2.1.14
9 | display-url-api:2.0
10 | docker-commons:1.8
11 | docker-workflow:1.12
12 | durable-task:1.14
13 | git-client:2.5.0
14 | git-server:1.7
15 | git:3.5.1
16 | github-api:1.86
17 | github:1.28.0
18 | handlebars:1.1.1
19 | icon-shim:2.0.3
20 | jackson2-api:2.7.3
21 | job-dsl:1.64
22 | jquery-detached:1.2.1
23 | junit:1.21
24 | mailer:1.20
25 | matrix-project:1.11
26 | momentjs:1.1.1
27 | pipeline-build-step:2.5.1
28 | pipeline-graph-analysis:1.5
29 | pipeline-input-step:2.8
30 | pipeline-milestone-step:1.3.1
31 | pipeline-model-api:1.1.9
32 | pipeline-model-declarative-agent:1.1.1
33 | pipeline-model-definition:1.1.9
34 | pipeline-model-extensions:1.1.9
35 | pipeline-rest-api:2.9
36 | pipeline-stage-step:2.2
37 | pipeline-stage-tags-metadata:1.1.9
38 | pipeline-stage-view:2.9
39 | plain-credentials:1.4
40 | scm-api:2.2.1
41 | script-security:1.33
42 | ssh-credentials:1.13
43 | structs:1.10
44 | token-macro:2.2
45 | workflow-aggregator:2.5
46 | workflow-api:2.20
47 | workflow-basic-steps:2.6
48 | workflow-cps-global-lib:2.8
49 | workflow-cps:2.39
50 | workflow-durable-task-step:2.15
51 | workflow-job:2.14.1
52 | workflow-multibranch:2.16
53 | workflow-scm-step:2.6
54 | workflow-step-api:2.12
55 | workflow-support:2.14
--------------------------------------------------------------------------------
/03-integration-tests/jenkins-home/userContent/README.md:
--------------------------------------------------------------------------------
1 | all files in this directory will be made available at $JENKINS_URL/userContent
2 |
--------------------------------------------------------------------------------
/03-integration-tests/src/integration/groovy/com/ticketfly/jenkins/common/JenkinsCLIWrapper.groovy:
--------------------------------------------------------------------------------
1 | package com.ticketfly.jenkins.common
2 |
3 | import groovy.json.JsonSlurper
4 | import groovy.util.slurpersupport.GPathResult
5 | import hudson.cli.CLI
6 |
7 | import java.nio.charset.StandardCharsets
8 |
9 | /**
10 | * Wrapper for the Jenkins CLI.
11 | */
12 | class JenkinsCLIWrapper {
13 | private final CLI cli
14 | private URL jenkinsUrl
15 |
16 | JenkinsCLIWrapper(String jenkinsUrl) {
17 | this.jenkinsUrl = jenkinsUrl.toURL()
18 | this.cli = new CLI(this.jenkinsUrl)
19 | }
20 |
21 | /**
22 | * Console output of a CLI query.
23 | */
24 | static class Output {
25 | int status
26 | ByteArrayOutputStream out
27 | ByteArrayOutputStream err
28 |
29 | String getOut() { return out.toString('UTF-8').trim() }
30 | String getErr() { return err.toString('UTF-8').trim() }
31 | }
32 |
33 | private Output query(List args, String input = null) {
34 | ByteArrayOutputStream out = new ByteArrayOutputStream()
35 | ByteArrayOutputStream err = new ByteArrayOutputStream()
36 | int status = cli.execute(args, input ? new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)): null, out, err)
37 | return new Output([status:status, out:out, err:err])
38 | }
39 |
40 | Output queryGroovyScript(String script) {
41 | return query(['groovy', '='], script)
42 | }
43 |
44 | Object queryGroovyScriptAsJson(String script) {
45 | String output = queryGroovyScript(script).out
46 | return new JsonSlurper().parseText(output)
47 | }
48 |
49 | /**
50 | * Inquires about a specific job. Requires the anonymous user to have hudson.model.Item.ExtendedRead permissions,
51 | * and Jenkins to have the extended-read-permission plugin installed.
52 | */
53 | GPathResult getJob(String jobName) {
54 | def query = query(['get-job', jobName])
55 | String output = query.out
56 |
57 | return new XmlSlurper().parseText(output)
58 | }
59 |
60 | Object getJobApiJson(String jobName) {
61 | String output = "$jenkinsUrl/job/$jobName/api/json".toURL().text
62 | return new JsonSlurper().parseText(output)
63 | }
64 |
65 | /**
66 | * Inquires about a specific view. Requires the anonymous user to have hudson.model.View.Read permissions.
67 | */
68 | GPathResult getView(String viewName) {
69 | def query = query(['get-view', viewName])
70 | String output = query.out
71 |
72 | return new XmlSlurper().parseText(output)
73 | }
74 |
75 | int buildJob(String jobName) {
76 | cli.execute(["build", jobName, '-v', '-w', '-s'])
77 | }
78 |
79 | String getLastBuiltConsoleOutput(String jobName){
80 | "$jenkinsUrl/job/$jobName/lastBuild/consoleText".toURL().text
81 | }
82 |
83 | int createJob(String jobName, String jobXml) {
84 | return execute(["create-job", jobName], jobXml)
85 | }
86 |
87 | int updateJob(String jobName, String jobXml) {
88 | return execute(["update-job", jobName], jobXml)
89 | }
90 |
91 | int deleteJob(String jobName) {
92 | return cli.execute(["delete-job", jobName])
93 | }
94 |
95 | int enableJob(String jobName) {
96 | return cli.execute(["enable-job", jobName])
97 | }
98 |
99 | int disableJob(String jobName) {
100 | return cli.execute(["disable-job", jobName])
101 | }
102 |
103 | int buildJobWithParams(String jobName, Map paramsMap) {
104 | List params = ["build", jobName, '-v', '-w', '-s']
105 | paramsMap.each { k, v ->
106 | params.add('-p')
107 | params.add("$k=$v".toString())
108 | }
109 | cli.execute(params)
110 | }
111 |
112 | private int execute(List args, String input) {
113 | return cli.execute(args,
114 | new ByteArrayInputStream(input.getBytes("UTF-8")),
115 | System.out,
116 | System.err)
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/03-integration-tests/src/integration/groovy/com/ticketfly/jenkins/spec/JenkinsSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.ticketfly.jenkins.spec
2 |
3 | import com.ticketfly.jenkins.common.JenkinsCLIWrapper
4 | import spock.lang.Specification
5 |
6 | /**
7 | * Base class for all Specifications requiring the Jenkins CLI
8 | */
9 | abstract class JenkinsSpec extends Specification {
10 | protected JenkinsCLIWrapper cli = new JenkinsCLIWrapper(System.properties['jenkinsURL'] ?: 'http://localhost:8080')
11 | }
12 |
--------------------------------------------------------------------------------
/03-integration-tests/src/integration/groovy/com/ticketfly/jenkins/spec/global/JenkinsInstanceSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.ticketfly.jenkins.spec.global
2 |
3 | import com.ticketfly.jenkins.common.JenkinsCLIWrapper
4 | import com.ticketfly.jenkins.spec.JenkinsSpec
5 |
6 | /**
7 | * Verify that Jenkins configuration is correct: Jenkins versions, installed plugins, deployed jobs, startup script executed.
8 | */
9 | class JenkinsInstanceSpec extends JenkinsSpec {
10 |
11 | void 'check Jenkins version'() {
12 | given:
13 | JenkinsCLIWrapper.Output output = cli.queryGroovyScript('''
14 | import jenkins.model.Jenkins
15 | println Jenkins.instance.version
16 | '''.stripIndent().trim())
17 |
18 | expect:
19 | output.out == '2.75'
20 | }
21 |
22 | void 'check Jenkins plugins installed'() {
23 | given:
24 | Map output = cli.queryGroovyScriptAsJson('''
25 | import groovy.json.JsonBuilder
26 | import jenkins.model.Jenkins
27 | def plugins = Jenkins.instance.pluginManager.plugins.collectEntries {[(it.shortName): it.version]}
28 | println new JsonBuilder(plugins).toPrettyString()
29 | '''.stripIndent().trim())
30 |
31 | expect:
32 | output.containsKey('github')
33 | output.containsKey('job-dsl')
34 | }
35 |
36 | void 'check seed job has run successfully'() {
37 | when:
38 | def output = cli.getJobApiJson('seed')
39 |
40 | then:
41 | assert output.firstBuild.number == 1
42 | assert output.lastBuild.number == 1
43 | assert output.lastCompletedBuild.number == 1
44 | assert output.lastStableBuild.number == 1
45 | assert output.lastSuccessfulBuild.number == 1
46 | }
47 |
48 | /**
49 | * Verify that the startup script configureMarkup() bloc was executed successfully
50 | */
51 | void "check configured markup is HTML"() {
52 | when:
53 | def output = cli.queryGroovyScript('println jenkins.model.Jenkins.instance.markupFormatter.class.simpleName')
54 |
55 | then:
56 | output.status == 0
57 | output.out == 'RawHtmlMarkupFormatter'
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/03-integration-tests/src/integration/groovy/com/ticketfly/jenkins/spec/job/RunJobSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.ticketfly.jenkins.spec.job
2 |
3 | import com.ticketfly.jenkins.spec.JenkinsSpec
4 | import javaposse.jobdsl.dsl.JobParent
5 | import javaposse.jobdsl.dsl.MemoryJobManagement
6 |
7 | /**
8 | * Run Jenkins job and verify that console output is correct.
9 | * Use existing jobs or create jobs on the fly using the DSL.
10 | */
11 | class RunJobSpec extends JenkinsSpec {
12 |
13 | void "run the deployed job a-simple-shell-script"() {
14 | //this job is defined in /jenkins-home/dsl/managedJobs.groovy and created when running the seed job
15 | given:
16 | String jobName = 'a-simple-shell-script'
17 |
18 | when:
19 | int status = cli.buildJob(jobName)
20 | String consoleOutput = cli.getLastBuiltConsoleOutput(jobName)
21 |
22 | then:
23 | status == 0
24 | consoleOutput.contains('echo abc')
25 | consoleOutput.contains('Archiving artifacts')
26 | consoleOutput.contains('Finished: SUCCESS')
27 | }
28 |
29 | void "create a job on the fly and run it"() {
30 | given:
31 | String jobName = 'a-simple-shell-script-generated-by-test'
32 | String randomText = UUID.randomUUID().toString()
33 |
34 | when:
35 | //create a job xml using the DSL
36 | String jobXml = memoryJobDsl.job(jobName) {
37 | steps {
38 | shell "echo $randomText"
39 | }
40 | }.xml
41 |
42 | int creationStatus = cli.createJob(jobName, jobXml)
43 | int buildStatus = cli.buildJob(jobName)
44 | String consoleOutput = cli.getLastBuiltConsoleOutput(jobName)
45 |
46 | then:
47 | creationStatus == 0
48 | buildStatus == 0
49 | consoleOutput.contains(randomText)
50 |
51 | cleanup:
52 | cli.deleteJob(jobName)
53 | }
54 |
55 | private static JobParent memoryJobDsl = {
56 | def memoryJobManagement = new MemoryJobManagement()
57 | JobParent jp = new JobParent() {
58 | @Override
59 | Object run() {
60 | return null
61 | }
62 | }
63 | jp.setJm(memoryJobManagement)
64 | jp
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 |
4 | # Ignore Gradle GUI config
5 | gradle-app.setting
6 |
7 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
8 | !gradle-wrapper.jar
9 |
10 | # Cache of project
11 | .gradletasknamecache
12 |
13 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
14 | # gradle/wrapper/gradle-wrapper.properties
15 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM jenkins/jenkins:2.71-alpine
2 |
3 | ENV JENKINS_REF /usr/share/jenkins/ref
4 |
5 | # install jenkins plugins
6 | COPY jenkins-home/plugins.txt $JENKINS_REF/
7 | RUN /usr/local/bin/plugins.sh $JENKINS_REF/plugins.txt
8 |
9 | ENV JAVA_OPTS -Dorg.eclipse.jetty.server.Request.maxFormContentSize=100000000 \
10 | -Dorg.apache.commons.jelly.tags.fmt.timeZone=America/Los_Angeles \
11 | -Dhudson.diyChunking=false \
12 | -Djenkins.install.runSetupWizard=false
13 |
14 | # copy scripts and ressource files
15 | COPY jenkins-home/*.* $JENKINS_REF/
16 | COPY jenkins-home/userContent $JENKINS_REF/userContent
17 | COPY jenkins-home/jobs $JENKINS_REF/jobs/
18 | COPY jenkins-home/init.groovy.d $JENKINS_REF/init.groovy.d/
19 | COPY jenkins-home/config-file-provider $JENKINS_REF/config-file-provider/
20 | COPY jenkins-home/dsl $JENKINS_REF/dsl/
21 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/README.md:
--------------------------------------------------------------------------------
1 | # job-dsl-for-github-org
2 | This example uses the [Job DSL Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Job+DSL+Plugin) and the [Java API for GitHub](https://github.com/kohsuke/github-api) to generate Jenkins jobs based on github repos.
3 |
4 | Check out the [blog article](https://tech.ticketfly.com/our-journey-to-continuous-delivery-chapter-3-automate-your-configuration-with-jenkins-dsl-1ff14d7de4c4) for this example.
5 |
6 | # How does it work?
7 | 1. When the docker image is built, the seed job and the dsl script are copied over to Jenkins home.
8 | 2. When running the docker image, the startup script builds the seed job.
9 | 3. The seed job runs the DSL job `managedJobs.groovy` which generates the job `generate-org-jobs`
10 | 4. When running the job `generate-org-jobs` for a github organization, it searches the github organization for gradle files and generate Jenkins jobs for each repo found.
11 |
12 | # Project structure
13 | .
14 | ├── jenkins-home
15 | │ ├── init.groovy.d
16 | │ │ └── startup.groovy # install gradle, deploy config files, run the seed job
17 | │ ├── jobs
18 | │ │ └── seed # seed job definition (should be the only config.xml)
19 | │ ├── config-file-provider
20 | │ │ └── generate-jobs-for-org.groovy # script to generate Jenkins jobs for a given org
21 | │ │ └── github-lib-build.gradle # build file to download github-api jars so it can be used by DSL
22 | │ ├── dsl
23 | │ │ └── managedJobs.groovy # dsl script to create `generate-org-jobs`
24 | │ └── plugins.txt # jenkins plugins
25 | └── Dockerfile # build the Docker image
26 |
27 | # Quick start
28 |
29 | - `./gradlew dockerBuild dockerRun` Build the docker image locally and start Jenkins at http://localhost:8080/
30 |
31 | # Video Tutorials
32 | [](http://www.youtube.com/watch?v=lHgvrFZBqvM)
33 |
34 | # Why generating Jenkins jobs from github repos.
35 |
36 | Using the ability to programmatically generate Jenkins jobs from your github source repos unblocks the ability to fully automate the configuration of your Jenkins.
37 | Instead of having a gate keeper that is manually managing Jenkins when needed, the jobs are automatically updated based on github repos, giving developers the ability to manage their builds.
38 |
39 | Some of the use cases that can be addressed are:
40 | - When creating a new github repo, a Jenkins job needs to be configured -> run the `generate-org-jobs` on a periodic schedule to automatically create the new jobs for new repos.
41 | - Every job configuration becomes slightly different, creating inconsistency across the build pipeline -> all jobs configurations are identical for a project type, defined in `generate-jobs-for-org.groovy`.
42 | - Not every github repo in your organization follows the same conventions when it comes to continuous integration -> defining some common standards on how to build your apps is a pre-requisite for build automation.
43 | - When configuring a plugin, all the existing jobs have to be updated -> perform batch update by re-runing `generate-org-jobs`.
44 | - Some applications need some tools installed to build -> install the tools through the startup script.
45 | - Jenkins crashed and need to be recovered -> all configuration being automated, Jenkins can be deployed on a new instance and the configuration will be regenerated.
46 |
47 | # Dockerhub
48 | Hosted at [ticketfly/jenkins-example-job-dsl-for-github-org](https://hub.docker.com/r/ticketfly/jenkins-example-job-dsl-for-github-org/)
49 |
50 | # Requirements
51 |
52 | - Docker Version 1.10 or higher.
53 |
54 | # Resources
55 |
56 | - [Official Jenkins Docker image](https://github.com/jenkinsci/docker)
57 | - [Job DSL Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Job+DSL+Plugin)
58 | - [Java API for GitHub](https://github.com/kohsuke/github-api)
59 | - [Ticketfly Tech: Automate your configuration with Jenkins DSL](https://tech.ticketfly.com/our-journey-to-continuous-delivery-chapter-3-automate-your-configuration-with-jenkins-dsl-1ff14d7de4c4)
60 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/build.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | dockerImageName = "ticketfly/$name"
3 | dockerTag = version
4 | jenkinsPort = 8080
5 | jenkinsHost = 'localhost'
6 | jenkinsURL = project.hasProperty('jenkinsURL') ? jenkinsURL : "http://$jenkinsHost:$jenkinsPort"
7 | dockerDetached = project.hasProperty('dockerDetached') ? dockerDetached : 'false'
8 | }
9 |
10 | task wrapper(type: Wrapper) {
11 | gradleVersion = '2.4'
12 | }
13 |
14 | task dockerRun(type: Exec) {
15 | group 'Docker'
16 | description "Run the jenkins docker container locally, use the 'latest' tag"
17 | commandLine 'docker'
18 | args = ['run', dockerDetached == 'true' ? '-d' : '--rm', '--name', 'jenkins', '-p', "$jenkinsPort:$jenkinsPort", '-p', '50000:50000', '-v', '/var/jenkins_home', "$dockerImageName"]
19 | doLast {
20 | try {
21 | if(dockerDetached == 'true'){
22 | waitForHTTPResponse(jenkinsURL, 200)
23 | println "Jenkins server started at $jenkinsURL"
24 | }
25 | } catch (e) {
26 | throw new GradleException("Could not connect to $jenkinsURL", e)
27 | }
28 | }
29 | }
30 |
31 | @groovy.transform.TimedInterrupt(120L)
32 | def void waitForHTTPResponse(String url, int responseCode) {
33 | println "Waiting for HTTP response $responseCode for '$url'"
34 | boolean isConnected = false
35 | while (!isConnected) {
36 | try {
37 | isConnected = url.toURL().openConnection().responseCode == responseCode
38 | } catch (any) {
39 | }
40 | Thread.sleep(500)
41 | }
42 | }
43 |
44 | task dockerStop(type: Exec) {
45 | group 'Docker'
46 | description 'Stop the jenkins docker container'
47 | commandLine 'docker'
48 | args = ['rm', '-f', '-v', 'jenkins']
49 | }
50 |
51 | task dockerStatus(type: Exec) {
52 | group 'Docker'
53 | description 'Display the process status of jenkins docker container'
54 | commandLine 'docker'
55 | args = ['ps', '-a', '-f', "name=jenkins"]
56 | }
57 |
58 | task dockerBuild(type: Exec) {
59 | group 'Docker'
60 | description "Build the docker image, tag as current version and 'as latest'."
61 | commandLine 'docker'
62 | args = ['build', '-t', "$dockerImageName:$dockerTag", '-t', "$dockerImageName:latest", "."]
63 | }
64 |
65 | task dockerPush(type: Exec) {
66 | group 'Docker'
67 | description 'Push the docker image with the current tag'
68 | commandLine 'docker'
69 | args = ['push', "$dockerImageName:$dockerTag"]
70 | }
71 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/gradle.properties:
--------------------------------------------------------------------------------
1 | name=jenkins-example-job-dsl-for-github-org
2 | #for consistency, same as jenkins version
3 | version=2.71
4 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ticketfly/jenkins-docker-examples/5111bb1e082d0d89193beeeffa260910f0caa70e/04-job-dsl-for-github-org/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Sep 30 13:57:32 PDT 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-bin.zip
7 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/jenkins-home/config-file-provider/generate-jobs-for-org.groovy:
--------------------------------------------------------------------------------
1 | //generate Jenkins jobs for a given org
2 | //1 - search for repos containing a specified filename within a github org
3 | //2 - for each repo found, generate a Jenkins job
4 |
5 | import org.kohsuke.github.GHContent
6 | import org.kohsuke.github.GitHub
7 | import org.kohsuke.github.GHRepository
8 |
9 | //to support a new job type, add an entry to typeToFileName and implement a method generate$typeJob, see generateGradleJob(..) for an example
10 | def typeToFileName = ['gradle': 'gradlew']
11 |
12 | repoRegex = "$org/(.*)"
13 | searchedFile = typeToFileName[type]
14 |
15 | println("Searching github for repos matching '$repoRegex' containing a file nammed '$searchedFile'. (${githubToken?'using github token':'no github token, public repos only'})")
16 | def results = new GithubSearch(githubToken).searchFiles(org, searchedFile , GithubSearch.isAtRoot, GithubSearch.filterRepo(repoRegex))
17 | def repos = results.collect{it.owner}.unique()
18 |
19 | println "Found ${repos.size()} repo(s)."
20 | if(repos){
21 | folder(org){
22 | description("${repos.size()} jobs - config generated on ${new Date()}")
23 | }
24 | repos.each{repo ->
25 | println "Generating $type job for $repo.fullName"
26 | this."generate${type.capitalize()}Job"(repo)
27 | }
28 | }
29 |
30 | class GithubSearch {
31 |
32 | private String gitToken
33 |
34 | GithubSearch(String gitToken = null) {
35 | this.gitToken = gitToken
36 | }
37 |
38 | private getGithub() {
39 | if (gitToken) {
40 | GitHub.connectToEnterprise('https://api.github.com', gitToken)
41 | } else {
42 | GitHub.connectAnonymously()
43 | }
44 | }
45 |
46 | /**
47 | * Search an organization for repos containing a specific file
48 | * @return list of github results pointing on the searched files.
49 | */
50 | Collection searchFiles(String orgName, String filename, Closure... filters) {
51 | def results = github.searchContent().q(filename).in('path').filename(filename).user(orgName).list().withPageSize(200).collect()
52 |
53 | return results.findAll { result ->
54 | filters.collect { filter -> filter(result) }.every()
55 | } as Collection
56 | }
57 |
58 | static Closure isAtRoot = { GHContent result -> !result.path.contains('/') }
59 |
60 | static Closure filterRepo(String repoRegex) {
61 | return { GHContent result -> result.owner.fullName.matches(repoRegex) }
62 | }
63 |
64 | static Collection resultsForBranch(Collection results, String branch) {
65 | def resultsForBranch = []
66 | results.each {
67 | try {
68 | resultsForBranch.add(it.owner.getFileContent(it.path, branch))
69 | } catch (_) {
70 | //file not found for searched branch
71 | }
72 | }
73 | resultsForBranch
74 | }
75 | }
76 |
77 | void generateGradleJob(GHRepository repo){
78 | job("$repo.fullName") {
79 | description("${repo.description} - config generated on ${new Date()}")
80 | label('master')
81 | scm {
82 | git {
83 | remote {
84 | github("$repo.fullName", 'https')
85 | }
86 | branch('master')
87 | }
88 | }
89 | steps {
90 | shell('./gradlew build')
91 | }
92 | publishers {
93 | archiveArtifacts 'build/**'
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/jenkins-home/config-file-provider/github-lib-build.gradle:
--------------------------------------------------------------------------------
1 | //this build download github-api jars and its dependencies into the local directory 'libs'
2 | //it allows the DSL script 'generate-jobs-for-org.groovy' to use github api libraries
3 | apply plugin: 'groovy'
4 |
5 | repositories {
6 | mavenCentral()
7 | jcenter()
8 | }
9 |
10 | configurations {
11 | libs
12 | }
13 |
14 | dependencies {
15 | libs 'org.kohsuke:github-api:1.85'
16 | }
17 | task cleanLibs(type: Delete) {
18 | delete 'libs'
19 | delete 'build'
20 | }
21 |
22 | task libs(type: Copy) {
23 | into 'libs'
24 | from configurations.libs
25 | }
26 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/jenkins-home/dsl/managedJobs.groovy:
--------------------------------------------------------------------------------
1 | job("generate-org-jobs") {
2 | description("Explore github repos for an organization and generate Jenkins jobs for repos")
3 | label('master')
4 | parameters {
5 | stringParam('org', 'Netflix', 'The name of the github organization to generate jobs for.')
6 | stringParam('githubToken', '', 'To access private repo, provide a github token. Leave empty for public repos.')
7 | choiceParam('type', ['gradle'], 'The type of the jobs to generate, you can implement more in generate-jobs-for-org.groovy.')
8 | }
9 | wrappers {
10 | configFiles {
11 | custom('github-lib-build.gradle') {
12 | targetLocation('build.gradle')
13 | }
14 | custom('generate-jobs-for-org.groovy') {
15 | targetLocation('GenerateJobsForOrg.groovy')
16 | }
17 | }
18 | }
19 | steps {
20 | gradle {
21 | gradleName('gradle')
22 | tasks('libs')
23 | useWrapper(false)
24 | }
25 | dsl {
26 | external 'GenerateJobsForOrg.groovy'
27 | additionalClasspath 'libs/*.jar'
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/jenkins-home/init.groovy.d/startup.groovy:
--------------------------------------------------------------------------------
1 | import jenkins.model.Jenkins
2 | import hudson.model.*
3 | import java.util.logging.Logger
4 | import hudson.plugins.gradle.*
5 | import hudson.tools.*
6 |
7 | Logger.global.info("[Running] startup script")
8 |
9 | configureSecurity()
10 | deployConfigFiles()
11 | installGradle('4.0.2')
12 |
13 | Jenkins.instance.save()
14 |
15 | buildJob('seed')
16 |
17 | Logger.global.info("[Done] startup script")
18 |
19 | private void configureSecurity() {
20 | Jenkins.getInstance().disableSecurity()
21 | }
22 |
23 | private def buildJob(String jobName) {
24 | Logger.global.info("Building job '$jobName")
25 | def job = Jenkins.instance.getJob(jobName)
26 | Jenkins.instance.queue.schedule(job, 0, new CauseAction(new Cause() {
27 | @Override
28 | String getShortDescription() {
29 | 'Jenkins startup script'
30 | }
31 | }))
32 | }
33 |
34 | private def deployConfigFiles() {
35 | //deploy scripts
36 | String jenkinsHome = System.env.get("JENKINS_HOME")
37 | new File("$jenkinsHome/config-file-provider").listFiles().each { file ->
38 | deployConfigFile(file.name, file.text, file.name)
39 | }
40 | }
41 |
42 | private void deployConfigFile(String name, String content, String comment) {
43 | Logger.global.info "Deploy config file '${name}'"
44 | String configProviderId = 'org.jenkinsci.plugins.configfiles.custom.CustomConfig'
45 | def provider = org.jenkinsci.lib.configprovider.ConfigProvider.all().find { it.getProviderId() == configProviderId }
46 | def config = provider.newConfig(name, name, comment, content)
47 | provider.save(config)
48 | }
49 |
50 | private void installGradle(String gradleVersion) {
51 | def gradleInstallationDescriptor =
52 | Jenkins.getActiveInstance().getDescriptorByType(GradleInstallation.DescriptorImpl.class)
53 | GradleInstallation[] installations = [new GradleInstallation("gradle", "", [new InstallSourceProperty([new GradleInstaller(gradleVersion)])])]
54 | gradleInstallationDescriptor.setInstallations(installations)
55 | }
56 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/jenkins-home/jobs/seed/config.xml.override:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Generate DSL jobs
5 | false
6 |
7 | master
8 | false
9 | false
10 | false
11 | false
12 | (System)
13 |
14 | false
15 |
16 |
17 | cp $JENKINS_HOME/dsl/*.groovy .
18 |
19 |
20 | *.groovy
21 | false
22 | false
23 | IGNORE
24 | IGNORE
25 | JENKINS_ROOT
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/jenkins-home/plugins.txt:
--------------------------------------------------------------------------------
1 | bouncycastle-api: 2.16.0
2 | cloudbees-folder: 6.1.0
3 | config-file-provider: 2.16.0
4 | credentials: 2.1.14
5 | display-url-api: 0.5
6 | envinject-api: 1.2
7 | envinject: 2.1.3
8 | git-client: 2.1.0
9 | git: 3.0.1
10 | github-api: 1.82
11 | github: 1.25.0
12 | gradle: 1.27
13 | job-dsl: 1.54
14 | junit: 1.19
15 | mailer: 1.18
16 | matrix-project: 1.7.1
17 | plain-credentials: 1.3
18 | scm-api: 1.3
19 | script-security: 1.24
20 | ssh-credentials: 1.12
21 | structs: 1.9
22 | token-macro: 2.0
23 | workflow-scm-step: 2.3
24 | workflow-step-api: 2.6
--------------------------------------------------------------------------------
/04-job-dsl-for-github-org/jenkins-home/userContent/README.md:
--------------------------------------------------------------------------------
1 | all files in this directory will be made available at $JENKINS_URL/userContent
2 |
--------------------------------------------------------------------------------
/05-aws-ecs/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 |
4 | # Ignore Gradle GUI config
5 | gradle-app.setting
6 |
7 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
8 | !gradle-wrapper.jar
9 |
10 | # Cache of project
11 | .gradletasknamecache
12 |
13 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
14 | # gradle/wrapper/gradle-wrapper.properties
15 |
16 | *.pem
17 | tmp.txt
18 |
--------------------------------------------------------------------------------
/05-aws-ecs/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM jenkins/jenkins:2.71-alpine
2 |
3 | ENV JENKINS_REF /usr/share/jenkins/ref
4 |
5 | # install jenkins plugins
6 | COPY jenkins-home/plugins.txt $JENKINS_REF/
7 | RUN /usr/local/bin/plugins.sh $JENKINS_REF/plugins.txt
8 |
9 | ENV JAVA_OPTS -Dorg.eclipse.jetty.server.Request.maxFormContentSize=100000000 \
10 | -Dorg.apache.commons.jelly.tags.fmt.timeZone=America/Los_Angeles \
11 | -Dhudson.diyChunking=false \
12 | -Djenkins.install.runSetupWizard=false
13 |
14 | # copy scripts and ressource files
15 | COPY jenkins-home/*.* $JENKINS_REF/
16 | COPY jenkins-home/userContent $JENKINS_REF/userContent
17 | COPY jenkins-home/jobs $JENKINS_REF/jobs/
18 | COPY jenkins-home/init.groovy.d $JENKINS_REF/init.groovy.d/
19 | COPY jenkins-home/dsl $JENKINS_REF/dsl/
20 |
--------------------------------------------------------------------------------
/05-aws-ecs/README.md:
--------------------------------------------------------------------------------
1 | # jenkins-example-aws-ecs
2 | This example deploys Jenkins infrastructure to [Amazon EC2 Container Service](https://aws.amazon.com/ecs/) cluster.
3 |
4 | Check out the [blog article](https://tech.ticketfly.com/our-journey-to-continuous-delivery-chapter-4-run-jenkins-infrastructure-on-aws-container-service-ef37e0304b95)) for this example.
5 |
6 | # How does it work?
7 | 1. Run the deploy script, it creates a CloudFormation stack that contains all the infrastructure components. It includes network configuration, security, storage, auto-scaling group and Jenkins deployment.
8 | 2. Once the CloudFormation stack is executed, retrieve the Jenkins URL in the 'Outputs' tab under the property 'JenkinsELB'.
9 | 3. To trigger auto-scaling, Jenkins comes pre-configured with 10 projects, run the project called '_Run_All_Jobs_' to simulate a spike of activity.
10 | 4. As the builds are queued in, the ECS cluster becomes full and auto-scaling kicks off. More EC2 instances are added to the cluster to process the waiting builds.
11 | 5. Once the builds are done, the cluster becomes idle. After 10 minutes of low activity, auto-scaling kicks off again but this time to reduce the size of ECS cluster.
12 |
13 | # How much does it cost?
14 | This example is designed to run on micro instances (t2.micro) and the maximum size of the auto-scaling group has been set to 5 instances.
15 | Including storage and data transfer, it costs approximately $0.10/hour to run this example.
16 |
17 | To prevent any further billing, the CloudFormation stack should be deleted when done.
18 | If you change the instance type and the maximum number of instances, check [EC2 Pricing](https://aws.amazon.com/ec2/pricing/on-demand/).
19 |
20 | # Project structure
21 | .
22 | ├── jenkins-home
23 | │ ├── init.groovy.d
24 | │ │ └── startup.groovy.override # configure Jenkins ECS plugin
25 | │ ├── jobs
26 | │ │ └── seed # seed job definition
27 | │ ├── dsl
28 | │ │ └── managedJobs.groovy.override # dsl script to generate the jenkins projects
29 | │ └── plugins.txt # jenkins plugins (including amazon-ecs)
30 | └── Dockerfile # build the Docker image
31 | └── build.gradle # gradle wrapper tasks to execute docker commands
32 | └── jenkins-ecs-stack.json # CloudFormation template to create the stack on AWS
33 | └── deploy-stack-to-aws.sh # run CloudFormation from command line
34 |
35 | # Quick start
36 |
37 | ## Using the AWS Console
38 | [](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=jenkins-ecs-stack&templateURL=https:%2F%2Fs3.amazonaws.com%2Fremydewolf-public%2Fcloudformation%2Fjenkins-ecs-stack.json)
39 |
40 | ## (or) Running the deploy script (require CLI)
41 | - Run `./deploy-stack-to-aws.sh`
42 |
43 | # Use cases addressed by using AWS to host Jenkins CI.
44 | - Auto-recovery of Jenkins server in case of instance crash, by running it as an ECS Service.
45 | - Scale up and down your build infrastructure based on usage using CloudWatch to control the size of the Auto Scaling group.
46 | - Prevent disk space issues related to growing number of builds in Jenkins by using [EFS]((https://github.com/jenkinsci/docker)).
47 | - Protect Jenkins infrastructure by hosting the resources in a [Virtual Private Cloud](https://aws.amazon.com/vpc/).
48 |
49 | # Dockerhub Image
50 | Hosted at [ticketfly/jenkins-example-aws-ecs](https://hub.docker.com/r/ticketfly/jenkins-example-aws-ecs/)
51 |
52 | # Requirements
53 | - AWS account
54 |
55 | # Resources
56 |
57 | - [Official Jenkins Docker image](https://github.com/jenkinsci/docker)
58 | - [Amazon EC2 Container Service Plugin](https://wiki.jenkins.io/display/JENKINS/Amazon+EC2+Container+Service+Plugin)
59 | - [Job DSL Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Job+DSL+Plugin)
60 | - [Amazon EC2 Container Service](https://aws.amazon.com/ecs/)
61 | - [Amazon Elastic File System](https://aws.amazon.com/efs/)
62 | - [Ticketfly Tech: Run Jenkins Infrastructure on AWS Container Service](https://tech.ticketfly.com/our-journey-to-continuous-delivery-chapter-4-run-jenkins-infrastructure-on-aws-container-service-ef37e0304b95)
63 |
--------------------------------------------------------------------------------
/05-aws-ecs/build.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | dockerImageName = "ticketfly/$name"
3 | dockerTag = version
4 | jenkinsPort = 8080
5 | jenkinsHost = 'localhost'
6 | jenkinsURL = project.hasProperty('jenkinsURL') ? jenkinsURL : "http://$jenkinsHost:$jenkinsPort"
7 | dockerDetached = project.hasProperty('dockerDetached') ? dockerDetached : 'false'
8 | }
9 |
10 | task wrapper(type: Wrapper) {
11 | gradleVersion = '2.4'
12 | }
13 |
14 | task dockerRun(type: Exec) {
15 | group 'Docker'
16 | description "Run the jenkins docker container locally, use the 'latest' tag"
17 | commandLine 'docker'
18 | args = ['run', dockerDetached == 'true' ? '-d' : '--rm', '--name', 'jenkins', '-p', "$jenkinsPort:$jenkinsPort", '-p', '50000:50000', '-v', '/var/jenkins_home', "$dockerImageName"]
19 | doLast {
20 | try {
21 | if(dockerDetached == 'true'){
22 | waitForHTTPResponse(jenkinsURL, 200)
23 | println "Jenkins server started at $jenkinsURL"
24 | }
25 | } catch (e) {
26 | throw new GradleException("Could not connect to $jenkinsURL", e)
27 | }
28 | }
29 | }
30 |
31 | @groovy.transform.TimedInterrupt(120L)
32 | def void waitForHTTPResponse(String url, int responseCode) {
33 | println "Waiting for HTTP response $responseCode for '$url'"
34 | boolean isConnected = false
35 | while (!isConnected) {
36 | try {
37 | isConnected = url.toURL().openConnection().responseCode == responseCode
38 | } catch (any) {
39 | }
40 | Thread.sleep(500)
41 | }
42 | }
43 |
44 | task dockerStop(type: Exec) {
45 | group 'Docker'
46 | description 'Stop the jenkins docker container'
47 | commandLine 'docker'
48 | args = ['rm', '-f', '-v', 'jenkins']
49 | }
50 |
51 | task dockerStatus(type: Exec) {
52 | group 'Docker'
53 | description 'Display the process status of jenkins docker container'
54 | commandLine 'docker'
55 | args = ['ps', '-a', '-f', "name=jenkins"]
56 | }
57 |
58 | task dockerBuild(type: Exec) {
59 | group 'Docker'
60 | description "Build the docker image, tag as current version and 'as latest'."
61 | commandLine 'docker'
62 | args = ['build', '-t', "$dockerImageName:$dockerTag", '-t', "$dockerImageName:latest", "."]
63 | }
64 |
65 | task dockerPush(type: Exec) {
66 | group 'Docker'
67 | description 'Push the docker image with the current tag'
68 | commandLine 'docker'
69 | args = ['push', "$dockerImageName:$dockerTag"]
70 | }
71 |
72 | task dockerPushLatest(type: Exec) {
73 | group 'Docker'
74 | description 'Push the docker image with the latest tag'
75 | commandLine 'docker'
76 | args = ['push', "$dockerImageName:latest"]
77 | }
78 |
--------------------------------------------------------------------------------
/05-aws-ecs/cloudformation/jenkins-ecs-stack.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion": "2010-09-09",
3 | "Description": "Cloudformation stack for Jenkins ECS",
4 | "Parameters": {
5 | "KeyName": {
6 | "Description": "Optional: the EC2 Key Pair to allow SSH access to the instances",
7 | "Type": "String",
8 | "Default": ""
9 | },
10 | "AllowedIPRange": {
11 | "Description": "The public IP address range that can be used to connect to the instances",
12 | "Type": "String",
13 | "MinLength": "9",
14 | "MaxLength": "18",
15 | "Default": "0.0.0.0/0",
16 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
17 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
18 | },
19 | "VPCIPRange": {
20 | "Description": "The private IP address range for allocating IPs within the VPC.",
21 | "Type": "String",
22 | "MinLength": "9",
23 | "MaxLength": "18",
24 | "Default": "10.0.0.0/16",
25 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
26 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
27 | },
28 | "InstanceType": {
29 | "Description": "EC2 instance type",
30 | "Type": "String",
31 | "Default": "t2.micro",
32 | "AllowedValues": [
33 | "t2.micro",
34 | "t2.small",
35 | "t2.medium"
36 | ],
37 | "ConstraintDescription": "Must be a valid EC2 instance type."
38 | },
39 | "DockerImage": {
40 | "Description": "The docker image to use for Jenkins",
41 | "Type": "String",
42 | "Default": "ticketfly/jenkins-example-aws-ecs"
43 | }
44 | },
45 | "Mappings": {
46 | "RegionAmazonECSOptimizedAMIMapping": {
47 | "us-east-2": {
48 | "AMI": "ami-bb8eaede"
49 | },
50 | "us-east-1": {
51 | "AMI": "ami-d61027ad"
52 | },
53 | "us-west-2": {
54 | "AMI": "ami-c6f81abe"
55 | },
56 | "us-west-1": {
57 | "AMI": "ami-514e6431"
58 | },
59 | "eu-west-2": {
60 | "AMI": "ami-0a85946e"
61 | },
62 | "eu-west-1": {
63 | "AMI": "ami-bd7e8dc4"
64 | },
65 | "eu-central-1": {
66 | "AMI": "ami-f15ff69e"
67 | },
68 | "ap-northeast-1": {
69 | "AMI": "ami-ab5ea9cd"
70 | },
71 | "ap-southeast-2": {
72 | "AMI": "ami-c3233ba0"
73 | },
74 | "ap-southeast-1": {
75 | "AMI": "ami-ae0b91cd"
76 | },
77 | "ca-central-1": {
78 | "AMI": "ami-32bb0556"
79 | }
80 | }
81 | },
82 | "Conditions": {
83 | "HasKeyName": {"Fn::Not": [{"Fn::Equals": ["", {"Ref": "KeyName"}]}]}
84 | },
85 | "Resources": {
86 | "VPC": {
87 | "Type": "AWS::EC2::VPC",
88 | "Properties": {
89 | "EnableDnsSupport": "true",
90 | "EnableDnsHostnames": "true",
91 | "CidrBlock": {"Ref": "VPCIPRange"},
92 | "Tags": [
93 | {
94 | "Key": "Application",
95 | "Value": {
96 | "Ref": "AWS::StackId"
97 | }
98 | }
99 | ]
100 | }
101 | },
102 | "InternetGateway": {
103 | "Type": "AWS::EC2::InternetGateway",
104 | "Properties": {
105 | "Tags": [
106 | {
107 | "Key": "Application",
108 | "Value": {
109 | "Ref": "AWS::StackName"
110 | }
111 | },
112 | {
113 | "Key": "Network",
114 | "Value": "Public"
115 | }
116 | ]
117 | }
118 | },
119 | "GatewayToInternet": {
120 | "Type": "AWS::EC2::VPCGatewayAttachment",
121 | "Properties": {
122 | "VpcId": {
123 | "Ref": "VPC"
124 | },
125 | "InternetGatewayId": {
126 | "Ref": "InternetGateway"
127 | }
128 | }
129 | },
130 | "RouteTable": {
131 | "Type": "AWS::EC2::RouteTable",
132 | "Properties": {
133 | "VpcId": {
134 | "Ref": "VPC"
135 | }
136 | }
137 | },
138 | "SubnetRouteTableAssoc": {
139 | "Type": "AWS::EC2::SubnetRouteTableAssociation",
140 | "Properties": {
141 | "RouteTableId": {
142 | "Ref": "RouteTable"
143 | },
144 | "SubnetId": {
145 | "Ref": "Subnet"
146 | }
147 | }
148 | },
149 | "InternetGatewayRoute": {
150 | "Type": "AWS::EC2::Route",
151 | "Properties": {
152 | "DestinationCidrBlock": "0.0.0.0/0",
153 | "RouteTableId": {
154 | "Ref": "RouteTable"
155 | },
156 | "GatewayId": {
157 | "Ref": "InternetGateway"
158 | }
159 | }
160 | },
161 | "Subnet": {
162 | "Type": "AWS::EC2::Subnet",
163 | "Properties": {
164 | "VpcId": {
165 | "Ref": "VPC"
166 | },
167 | "CidrBlock": {"Ref": "VPCIPRange"},
168 | "Tags": [
169 | {
170 | "Key": "Application",
171 | "Value": {
172 | "Ref": "AWS::StackId"
173 | }
174 | }
175 | ]
176 | }
177 | },
178 | "ECSServiceRole":{
179 | "Type":"AWS::IAM::Role",
180 | "Properties":{
181 | "AssumeRolePolicyDocument":{
182 | "Statement":[
183 | {
184 | "Effect":"Allow",
185 | "Principal":{
186 | "Service":[
187 | "ecs.amazonaws.com"
188 | ]
189 | },
190 | "Action":[
191 | "sts:AssumeRole"
192 | ]
193 | }
194 | ]
195 | },
196 | "Path":"/",
197 | "Policies":[
198 | {
199 | "PolicyName":"ecs-service",
200 | "PolicyDocument":{
201 | "Statement":[
202 | {
203 | "Effect":"Allow",
204 | "Action":[
205 | "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
206 | "elasticloadbalancing:DeregisterTargets",
207 | "elasticloadbalancing:Describe*",
208 | "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
209 | "elasticloadbalancing:RegisterTargets",
210 | "ec2:Describe*",
211 | "ec2:AuthorizeSecurityGroupIngress"
212 | ],
213 | "Resource":"*"
214 | }
215 | ]
216 | }
217 | }
218 | ]
219 | }
220 | },
221 | "EC2Role":{
222 | "Type":"AWS::IAM::Role",
223 | "Properties":{
224 | "AssumeRolePolicyDocument":{
225 | "Statement":[
226 | {
227 | "Effect":"Allow",
228 | "Principal":{
229 | "Service":[
230 | "ec2.amazonaws.com"
231 | ]
232 | },
233 | "Action":[
234 | "sts:AssumeRole"
235 | ]
236 | }
237 | ]
238 | },
239 | "Path":"/",
240 | "Policies":[
241 | {
242 | "PolicyName":"ecs-service",
243 | "PolicyDocument":{
244 | "Statement":[
245 | {
246 | "Effect":"Allow",
247 | "Action":[
248 | "ecs:*",
249 | "elasticloadbalancing:Describe*",
250 | "logs:CreateLogStream",
251 | "logs:PutLogEvents"
252 | ],
253 | "Resource":"*"
254 | }
255 | ]
256 | }
257 | }
258 | ]
259 | }
260 | },
261 | "JenkinsECSInstanceProfile": {
262 | "Type": "AWS::IAM::InstanceProfile",
263 | "Properties": {
264 | "Path": "/",
265 | "Roles": [
266 | {
267 | "Ref": "EC2Role"
268 | }
269 | ]
270 | }
271 | },
272 | "JenkinsSecurityGroup": {
273 | "Type": "AWS::EC2::SecurityGroup",
274 | "Properties": {
275 | "GroupDescription": "SecurityGroup for Jenkins instances: master and slaves",
276 | "VpcId": {
277 | "Ref": "VPC"
278 | },
279 | "SecurityGroupIngress": [
280 | {
281 | "IpProtocol": "tcp",
282 | "FromPort": "22",
283 | "ToPort": "22",
284 | "CidrIp": {
285 | "Ref": "AllowedIPRange"
286 | }
287 | },
288 | {
289 | "IpProtocol": "tcp",
290 | "FromPort": "8080",
291 | "ToPort": "8080",
292 | "CidrIp": {
293 | "Ref": "VPCIPRange"
294 | }
295 | },
296 | {
297 | "IpProtocol": "tcp",
298 | "FromPort": "50000",
299 | "ToPort": "50000",
300 | "CidrIp": {
301 | "Ref": "VPCIPRange"
302 | }
303 | }
304 | ]
305 | }
306 | },
307 | "JenkinsELBSecurityGroup": {
308 | "Type": "AWS::EC2::SecurityGroup",
309 | "Properties": {
310 | "GroupDescription": "SecurityGroup for Jenkins ELB",
311 | "VpcId": {
312 | "Ref": "VPC"
313 | },
314 | "SecurityGroupIngress": [
315 | {
316 | "IpProtocol": "tcp",
317 | "FromPort": "80",
318 | "ToPort": "80",
319 | "CidrIp": {
320 | "Ref": "AllowedIPRange"
321 | }
322 | }
323 | ]
324 | }
325 | },
326 | "EFSSecurityGroup": {
327 | "Type": "AWS::EC2::SecurityGroup",
328 | "Properties": {
329 | "GroupDescription": "Security group for EFS mount target",
330 | "VpcId": {
331 | "Ref": "VPC"
332 | },
333 | "SecurityGroupIngress": [
334 | {
335 | "IpProtocol": "tcp",
336 | "FromPort": "2049",
337 | "ToPort": "2049",
338 | "CidrIp": {
339 | "Ref": "VPCIPRange"
340 | }
341 | }
342 | ]
343 | }
344 | },
345 | "JenkinsEFS": {
346 | "Type": "AWS::EFS::FileSystem",
347 | "Properties": {
348 | "FileSystemTags": [
349 | {
350 | "Key": "Name",
351 | "Value": "JenkinsEFS"
352 | }
353 | ]
354 | }
355 | },
356 | "MountTarget": {
357 | "Type": "AWS::EFS::MountTarget",
358 | "Properties": {
359 | "FileSystemId": {
360 | "Ref": "JenkinsEFS"
361 | },
362 | "SubnetId": {
363 | "Ref": "Subnet"
364 | },
365 | "SecurityGroups": [
366 | {
367 | "Ref": "EFSSecurityGroup"
368 | }
369 | ]
370 | }
371 | },
372 | "JenkinsELB": {
373 | "Type": "AWS::ElasticLoadBalancing::LoadBalancer",
374 | "Properties": {
375 | "LoadBalancerName": "jenkins-elb",
376 | "Scheme": "internet-facing",
377 | "Subnets": [
378 | {
379 | "Ref": "Subnet"
380 | }
381 | ],
382 | "SecurityGroups": [
383 | {
384 | "Ref": "JenkinsELBSecurityGroup"
385 | }
386 | ],
387 | "Listeners": [
388 | {
389 | "InstancePort": "8080",
390 | "InstanceProtocol": "HTTP",
391 | "LoadBalancerPort": "80",
392 | "Protocol": "HTTP",
393 | "PolicyNames": [
394 | "JenkinsELBStickiness"
395 | ]
396 | }
397 | ],
398 | "LBCookieStickinessPolicy": [
399 | {
400 | "CookieExpirationPeriod": "3600",
401 | "PolicyName": "JenkinsELBStickiness"
402 | }
403 | ],
404 | "HealthCheck": {
405 | "HealthyThreshold": "3",
406 | "Interval": "20",
407 | "Target": "HTTP:8080/login",
408 | "Timeout": "2",
409 | "UnhealthyThreshold": "10"
410 | }
411 | }
412 | },
413 | "JenkinsCluster": {
414 | "Type": "AWS::ECS::Cluster",
415 | "Properties": {
416 | "ClusterName": "jenkins-cluster"
417 | }
418 | },
419 | "JenkinsMasterTaskDefinition": {
420 | "Type": "AWS::ECS::TaskDefinition",
421 | "Properties": {
422 | "Family": "jenkins-master",
423 | "NetworkMode": "bridge",
424 | "ContainerDefinitions": [
425 | {
426 | "Name": "jenkins-master",
427 | "Image": {
428 | "Ref": "DockerImage"
429 | },
430 | "MountPoints": [
431 | {
432 | "SourceVolume": "data-volume",
433 | "ContainerPath": "/var/jenkins_home"
434 | }
435 | ],
436 | "Essential": true,
437 | "Cpu": 1024,
438 | "MemoryReservation": 992,
439 | "PortMappings": [
440 | {
441 | "HostPort": 8080,
442 | "ContainerPort": 8080,
443 | "Protocol": "tcp"
444 | },
445 | {
446 | "HostPort": 50000,
447 | "ContainerPort": 50000,
448 | "Protocol": "tcp"
449 | }
450 | ]
451 | }
452 | ],
453 | "Volumes": [
454 | {
455 | "Host": {
456 | "SourcePath": "/data/"
457 | },
458 | "Name": "data-volume"
459 | }
460 | ]
461 | }
462 | },
463 | "JenkinsECSService": {
464 | "DependsOn": ["JenkinsELB"],
465 | "Type": "AWS::ECS::Service",
466 | "Properties": {
467 | "Cluster": "jenkins-cluster",
468 | "DesiredCount": 1,
469 | "ServiceName": "jenkins-master",
470 | "TaskDefinition": {
471 | "Ref": "JenkinsMasterTaskDefinition"
472 | },
473 | "Role" : { "Ref" : "ECSServiceRole" },
474 | "LoadBalancers": [
475 | {
476 | "LoadBalancerName": "jenkins-elb",
477 | "ContainerPort": "8080",
478 | "ContainerName": "jenkins-master"
479 | }
480 | ]
481 | }
482 | },
483 | "JenkinsECSLaunchConfiguration": {
484 | "Type": "AWS::AutoScaling::LaunchConfiguration",
485 | "Properties": {
486 | "AssociatePublicIpAddress": true,
487 | "ImageId": {
488 | "Fn::FindInMap": [
489 | "RegionAmazonECSOptimizedAMIMapping",
490 | {
491 | "Ref": "AWS::Region"
492 | },
493 | "AMI"
494 | ]
495 | },
496 | "IamInstanceProfile": {
497 | "Ref": "JenkinsECSInstanceProfile"
498 | },
499 | "InstanceType": {
500 | "Ref": "InstanceType"
501 | },
502 | "KeyName": {"Fn::If": ["HasKeyName", {"Ref": "KeyName"}, {"Ref": "AWS::NoValue"}]},
503 | "SecurityGroups": [
504 | {
505 | "Ref": "JenkinsSecurityGroup"
506 | }
507 | ],
508 | "BlockDeviceMappings": [
509 | {
510 | "DeviceName": "/dev/xvdcz",
511 | "Ebs": {
512 | "VolumeSize": "24",
513 | "DeleteOnTermination": true
514 | }
515 | }
516 | ],
517 | "UserData": {
518 | "Fn::Base64": {
519 | "Fn::Join": [
520 | "",
521 | [
522 | "#!/bin/bash\n",
523 | "echo 'ECS_CLUSTER=jenkins-cluster' >> /etc/ecs/ecs.config\n",
524 | "#Mount EFS volume\n",
525 | "yum install -y nfs-utils\n",
526 | "EC2_AVAIL_ZONE=`curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone`\n",
527 | "EC2_REGION=",
528 | {
529 | "Ref": "AWS::Region"
530 | },
531 | "\n",
532 | "EFS_FILE_SYSTEM_ID=",
533 | {
534 | "Ref": "JenkinsEFS"
535 | },
536 | "\n",
537 | "EFS_PATH=$EC2_AVAIL_ZONE.$EFS_FILE_SYSTEM_ID.efs.$EC2_REGION.amazonaws.com\n",
538 | "mkdir /data\n",
539 | "mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 $EFS_PATH:/ /data\n",
540 | "#Give ownership to jenkins user\n",
541 | "chown 1000 /data\n"
542 | ]
543 | ]
544 | }
545 | }
546 | }
547 | },
548 | "JenkinsECSAutoScaling": {
549 | "Type": "AWS::AutoScaling::AutoScalingGroup",
550 | "DependsOn": ["MountTarget"],
551 | "Properties": {
552 | "VPCZoneIdentifier": [
553 | {
554 | "Ref": "Subnet"
555 | }
556 | ],
557 | "LaunchConfigurationName": {
558 | "Ref": "JenkinsECSLaunchConfiguration"
559 | },
560 | "MinSize": "2",
561 | "MaxSize": "5",
562 | "DesiredCapacity": "2",
563 | "HealthCheckType": "EC2",
564 | "HealthCheckGracePeriod": "400",
565 | "Tags": [
566 | {
567 | "Key": "Name",
568 | "Value": "jenkins-ecs-instance",
569 | "PropagateAtLaunch": "true"
570 | }
571 | ]
572 | }
573 | },
574 |
575 | "JenkinsClusterScaleUpPolicy": {
576 | "Type" : "AWS::AutoScaling::ScalingPolicy",
577 | "Properties" : {
578 | "AdjustmentType" : "ChangeInCapacity",
579 | "AutoScalingGroupName" : { "Ref": "JenkinsECSAutoScaling" },
580 | "EstimatedInstanceWarmup" : 60,
581 | "MetricAggregationType" : "Average",
582 | "PolicyType" : "StepScaling",
583 | "StepAdjustments" : [ {
584 | "MetricIntervalLowerBound" : 0,
585 | "ScalingAdjustment" : 2
586 | }]
587 | }
588 | },
589 |
590 | "JenkinsClusterScaleUpAlarm" : {
591 | "Type" : "AWS::CloudWatch::Alarm",
592 | "Properties" : {
593 | "AlarmDescription" : "CPU utilization peaked at 70% during the last minute",
594 | "AlarmName" : "JenkinsClusterScaleUpAlarm",
595 | "AlarmActions": [ { "Ref": "JenkinsClusterScaleUpPolicy" } ],
596 | "Dimensions" : [{
597 | "Name": "ClusterName",
598 | "Value": "jenkins-cluster"
599 | }],
600 | "MetricName" : "CPUReservation",
601 | "Namespace" : "AWS/ECS",
602 | "ComparisonOperator" : "GreaterThanOrEqualToThreshold",
603 | "Statistic" : "Maximum",
604 | "Threshold" : 70,
605 | "Period" : 60,
606 | "EvaluationPeriods": 1,
607 | "TreatMissingData" : "notBreaching"
608 | }
609 | },
610 |
611 | "JenkinsClusterScaleDownPolicy": {
612 | "Type" : "AWS::AutoScaling::ScalingPolicy",
613 | "Properties" : {
614 | "AdjustmentType" : "PercentChangeInCapacity",
615 | "AutoScalingGroupName" : { "Ref": "JenkinsECSAutoScaling" },
616 | "Cooldown" : "120",
617 | "ScalingAdjustment" : "-50"
618 | }
619 | },
620 |
621 | "JenkinsClusterScaleDownAlarm" : {
622 | "Type" : "AWS::CloudWatch::Alarm",
623 | "Properties" : {
624 | "AlarmDescription" : "CPU utilization is under 50% for the last 10 min (change 10 min to 45 min for prod use as you pay by the hour )",
625 | "AlarmName" : "JenkinsClusterScaleDownAlarm",
626 | "AlarmActions": [ { "Ref": "JenkinsClusterScaleDownPolicy" } ],
627 | "Dimensions" : [{
628 | "Name": "ClusterName",
629 | "Value": "jenkins-cluster"
630 | }],
631 | "MetricName" : "CPUReservation",
632 | "Namespace" : "AWS/ECS",
633 | "ComparisonOperator" : "LessThanThreshold",
634 | "Statistic" : "Maximum",
635 | "Threshold" : 50,
636 | "Period" : 600,
637 | "EvaluationPeriods": 1,
638 | "TreatMissingData" : "notBreaching"
639 | }
640 | }
641 |
642 | },
643 | "Outputs" : {
644 | "JenkinsELB" : {
645 | "Description": "Jenkins URL",
646 | "Value" : {"Fn::Join": ["", ["http://", { "Fn::GetAtt" : [ "JenkinsELB", "DNSName" ]}] ]}
647 | }
648 | }
649 | }
650 |
--------------------------------------------------------------------------------
/05-aws-ecs/deploy-stack-to-aws.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #make sure you have the AWS CLI installed and configured
4 | #http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html
5 |
6 | #update as needed
7 | #export AWS_DEFAULT_PROFILE=dev
8 | export AWS_DEFAULT_REGION=us-west-2
9 |
10 | KeyName='ec2-user'
11 | StackName='jenkins-ecs-stack'
12 | VPCIPRange='10.0.0.0/16'
13 | AllowedIPRange='0.0.0.0/0'
14 | DockerImage='ticketfly/jenkins-example-aws-ecs'
15 | InstanceType='t2.micro'
16 |
17 | echo "Looking for key '$KeyName'..."
18 | aws ec2 describe-key-pairs --key-name $KeyName
19 | if [ $? -ne 0 ]; then
20 | echo "Key $KeyName not found, creating it."
21 | aws ec2 create-key-pair --key-name $KeyName --query 'KeyMaterial' --output text > ${KeyName}.pem
22 | chmod 400 ${KeyName}.pem
23 | else
24 | echo "Key $KeyName found, it will be used to configure ECS instances."
25 | fi
26 |
27 | echo "Running cloudformation to create the stack '$StackName'..."
28 | aws cloudformation create-stack --stack-name $StackName \
29 | --template-body file://cloudformation/jenkins-ecs-stack.json \
30 | --parameters ParameterKey=KeyName,ParameterValue=$KeyName \
31 | ParameterKey=VPCIPRange,ParameterValue=$VPCIPRange \
32 | ParameterKey=AllowedIPRange,ParameterValue=$AllowedIPRange \
33 | ParameterKey=DockerImage,ParameterValue=$DockerImage \
34 | ParameterKey=InstanceType,ParameterValue=$InstanceType \
35 | --capabilities CAPABILITY_NAMED_IAM
36 |
--------------------------------------------------------------------------------
/05-aws-ecs/gradle.properties:
--------------------------------------------------------------------------------
1 | name=jenkins-example-aws-ecs
2 | #for consistency, same as jenkins version
3 | version=2.71
4 |
--------------------------------------------------------------------------------
/05-aws-ecs/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ticketfly/jenkins-docker-examples/5111bb1e082d0d89193beeeffa260910f0caa70e/05-aws-ecs/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/05-aws-ecs/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Sep 30 13:57:32 PDT 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-bin.zip
7 |
--------------------------------------------------------------------------------
/05-aws-ecs/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/05-aws-ecs/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/05-aws-ecs/jenkins-home/dsl/managedJobs.groovy.override:
--------------------------------------------------------------------------------
1 |
2 | def numberOfCopies = getEnvProperty('NumberOfCopies', '1').toInteger()
3 | def generatedJobs = new java.util.LinkedList()
4 |
5 | (1..numberOfCopies).each{ i->
6 |
7 | job(generateJobName(generatedJobs, "square-retrofit-java", i)) {
8 | description("Type-safe HTTP client for Android and Java by Square, Inc.")
9 | label('ecs-java')
10 | scm {
11 | git {
12 | remote {
13 | github('square/retrofit', 'https')
14 | }
15 | branch('master')
16 | }
17 | }
18 | steps {
19 | shell('''
20 | mvn clean install
21 | echo "Pause to simulate longer running build to trigger auto-scaling of instances"
22 | sleep 60
23 | ''')
24 | }
25 | publishers {
26 | archiveArtifacts 'retrofit/target/*.jar'
27 | archiveJunit 'retrofit/target/surefire-reports/*.xml'
28 | }
29 | }
30 |
31 | job(generateJobName(generatedJobs, "jaredhanson-passport-javascript", i)) {
32 | description("A declarative, efficient, and flexible JavaScript library for building user interfaces..")
33 | label('ecs-javascript')
34 | scm {
35 | git {
36 | remote {
37 | github('jaredhanson/passport', 'https')
38 | }
39 | branch('master')
40 | }
41 | }
42 | steps {
43 | shell('''
44 | npm install
45 | make test
46 | echo "Pause to simulate longer running build to trigger auto-scaling of instances"
47 | sleep 180
48 | ''')
49 | }
50 | }
51 |
52 | }
53 |
54 | //create job to run all the generated jobs above and trigger it
55 | def runAllName = '_Run_All_Jobs_'
56 | println "Creating job to run all jobs: $generatedJobs"
57 | job(runAllName) {
58 | description "Run all the generated jobs to test auto-scaling feature"
59 | publishers {
60 | downstream generatedJobs
61 | }
62 | }
63 |
64 | private String generateJobName(List generatedJobs, String projectName, int i){
65 | String jobName = "$projectName-$i"
66 | generatedJobs.add(jobName)
67 | jobName
68 | }
69 |
70 | private def getEnvProperty(String name, def defaultValue) {
71 | try {
72 | getProperty(name)
73 | } catch (MissingPropertyException e) {
74 | defaultValue
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/05-aws-ecs/jenkins-home/init.groovy.d/startup.groovy.override:
--------------------------------------------------------------------------------
1 | import com.amazonaws.ClientConfiguration
2 | import com.amazonaws.regions.RegionUtils
3 | import com.amazonaws.services.ecs.AmazonECSClient
4 | import com.amazonaws.util.EC2MetadataUtils
5 | import com.amazonaws.services.elasticloadbalancing.*
6 | import com.amazonaws.services.elasticloadbalancing.model.*
7 | import com.cloudbees.jenkins.plugins.amazonecs.ECSCloud
8 | import com.cloudbees.jenkins.plugins.amazonecs.ECSTaskTemplate
9 | import hudson.model.*
10 | import hudson.plugins.gradle.*
11 | import hudson.tools.*
12 | import jenkins.model.*
13 | import jenkins.model.Jenkins
14 | import jenkins.model.JenkinsLocationConfiguration
15 |
16 | import java.util.logging.Logger
17 |
18 | Logger.global.info("[Running] startup script")
19 |
20 | configureJenkinsURL()
21 | configureSecurity()
22 | configureCloud()
23 |
24 | Jenkins.instance.save()
25 |
26 | buildJob('seed', new ParametersAction(new StringParameterValue('NumberOfCopies', "5")))
27 |
28 | Logger.global.info("[Done] startup script")
29 |
30 | private configureJenkinsURL() {
31 | String jenkinsURL = queryJenkinsURL()
32 | Logger.global.info("Set Jenkins URL to $jenkinsURL")
33 | def config = JenkinsLocationConfiguration.get()
34 | config.url = jenkinsURL
35 | config.save()
36 | }
37 |
38 | private void configureSecurity() {
39 | Jenkins.instance.disableSecurity()
40 | //this port is fixed so it can be configured in the security group
41 | Jenkins.instance.setSlaveAgentPort(50000)
42 | }
43 |
44 | private def buildJob(String jobName, def params = null) {
45 | Logger.global.info("Building job '$jobName")
46 | def job = Jenkins.instance.getJob(jobName)
47 | Jenkins.instance.queue.schedule(job, 0, new CauseAction(new Cause() {
48 | @Override
49 | String getShortDescription() {
50 | 'Jenkins startup script'
51 | }
52 | }), params)
53 | }
54 |
55 | private getClientConfiguration() {
56 | new ClientConfiguration()
57 | }
58 |
59 | private String queryElbDNSName() {
60 | AmazonElasticLoadBalancingClient client = new AmazonElasticLoadBalancingClient(clientConfiguration);
61 | client.setRegion(RegionUtils.getRegion(region))
62 | DescribeLoadBalancersRequest request = new DescribeLoadBalancersRequest()
63 | .withLoadBalancerNames('jenkins-elb');
64 | DescribeLoadBalancersResult result = client.describeLoadBalancers(request);
65 | result.loadBalancerDescriptions.first().DNSName
66 | }
67 |
68 | private String getPrivateIP() {
69 | EC2MetadataUtils.networkInterfaces.first().localIPv4s.first()
70 | }
71 |
72 | private String getInstanceUrl() {
73 | "http://${privateIP}:8080/"
74 | }
75 |
76 | private String queryJenkinsURL() {
77 | //assume default port 80
78 | "http://${queryElbDNSName()}/"
79 | }
80 |
81 |
82 | private String getRegion() {
83 | EC2MetadataUtils.instanceInfo.region
84 | }
85 |
86 | private String queryJenkinsClusterArn(String regionName) {
87 | AmazonECSClient client = new AmazonECSClient(clientConfiguration)
88 | client.setRegion(RegionUtils.getRegion(regionName))
89 | client.listClusters().getClusterArns().find { it.endsWith('jenkins-cluster') }
90 | }
91 |
92 | private void configureCloud() {
93 | try {
94 | Logger.global.info("Creating ECS Template")
95 | def ecsTemplates = templates = Arrays.asList(
96 | //a t2.micro has 992 memory units & 1024 CPU units
97 | createECSTaskTemplate('ecs-java', 'cloudbees/jnlp-slave-with-java-build-tools', 992, 1024),
98 | createECSTaskTemplate('ecs-javascript', 'cloudbees/jnlp-slave-with-java-build-tools', 496, 512)
99 | )
100 | String clusterArn = queryJenkinsClusterArn(region)
101 |
102 | Logger.global.info("Creating ECS Cloud for $clusterArn")
103 | def ecsCloud = new ECSCloud(
104 | name = "jenkins_cluster",
105 | templates = ecsTemplates,
106 | credentialsId = '',
107 | cluster = clusterArn,
108 | regionName = region,
109 | jenkinsUrl = instanceUrl,
110 | slaveTimoutInSeconds = 60
111 | )
112 |
113 | Jenkins.instance.clouds.clear()
114 | Jenkins.instance.clouds.add(ecsCloud)
115 | } catch (com.amazonaws.SdkClientException e) {
116 | Logger.global.severe({ e.message })
117 | Logger.global.severe("ERROR: Could not create ECS config, are you running this container in AWS?")
118 | }
119 | }
120 |
121 | //cloudbees/jnlp-slave-with-java-build-tools
122 | private ECSTaskTemplate createECSTaskTemplate(String label, String image, int softMemory, int cpu) {
123 | Logger.global.info("Creating ECS Template '$label' for image '$image' (memory: softMemory, cpu: $cpu)")
124 | new ECSTaskTemplate(
125 | templateName = label,
126 | label = label,
127 | image = image,
128 | remoteFSRoot = "/home/jenkins",
129 | //memory reserved
130 | memory = 0,
131 | //soft memory
132 | memoryReservation = softMemory,
133 | cpu = cpu,
134 | privileged = false,
135 | logDriverOptions = null,
136 | environments = null,
137 | extraHosts = null,
138 | mountPoints = null
139 | )
140 | }
141 |
--------------------------------------------------------------------------------
/05-aws-ecs/jenkins-home/jobs/seed/config.xml.override:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Generate DSL jobs
5 | false
6 |
7 |
8 |
9 |
10 | NumberOfCopies
11 | For demo purpose, create some copies for each generated job, this will allow testing auto scaling by running all the jobs.
12 | 5
13 |
14 |
15 |
16 |
17 |
18 | master
19 | false
20 | false
21 | false
22 | false
23 |
24 | false
25 |
26 |
27 | cp $JENKINS_HOME/dsl/*.groovy .
28 |
29 |
30 | *.groovy
31 | false
32 | false
33 | false
34 | false
35 | false
36 | IGNORE
37 | IGNORE
38 | JENKINS_ROOT
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/05-aws-ecs/jenkins-home/plugins.txt:
--------------------------------------------------------------------------------
1 | amazon-ecs:1.11
2 | aws-credentials:1.21
3 | aws-java-sdk:1.11.119
4 | bouncycastle-api:2.16.0
5 | cloudbees-folder:6.1.0
6 | config-file-provider:2.16.0
7 | credentials-binding:1.13
8 | credentials:2.1.14
9 | display-url-api:0.5
10 | envinject-api:1.2
11 | envinject:2.1.3
12 | git-client:2.1.0
13 | git:3.0.1
14 | github-api:1.82
15 | github:1.25.0
16 | gradle:1.27
17 | jackson2-api:2.7.3
18 | job-dsl:1.54
19 | junit:1.19
20 | mailer:1.18
21 | matrix-project:1.7.1
22 | plain-credentials:1.3
23 | scm-api:1.3
24 | script-security:1.24
25 | ssh-credentials:1.12
26 | structs:1.9
27 | token-macro:2.0
28 | workflow-scm-step:2.3
29 | workflow-step-api:2.12
--------------------------------------------------------------------------------
/05-aws-ecs/jenkins-home/userContent/README.md:
--------------------------------------------------------------------------------
1 | all files in this directory will be made available at $JENKINS_URL/userContent
2 |
--------------------------------------------------------------------------------
/05-aws-ecs/upload-cloudformation-to-s3.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #upload cloudformation script to public s3 bucket, the s3 URL is used in tech blog article
4 | aws s3 cp cloudformation/jenkins-ecs-stack.json s3://remydewolf-public/cloudformation/ --acl public-read
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Ticketfly
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 | # jenkins-docker-examples
2 | Selection of examples on how to use Jenkins with docker.
3 |
4 | 1. [gradle build](./01-gradle-build) Jenkins based on a docker image configured as a gradle project
5 | - Dockerhub image [ticketfly/jenkins-example-gradle-build](https://hub.docker.com/r/ticketfly/jenkins-example-gradle-build/)
6 | 2. [job dsl](./02-job-dsl) Deploy the job configuration programmatically using Job DSL Plugin
7 | - Dockerhub image [ticketfly/jenkins-example-job-dsl](https://hub.docker.com/r/ticketfly/jenkins-example-job-dsl)
8 | 3. [integration tests](./03-integration-tests) Integration tests using the Jenkins CLI
9 | - Dockerhub image [ticketfly/jenkins-example-integration-tests](https://hub.docker.com/r/ticketfly/jenkins-example-integration-tests)
10 | 4. [job dsl for github](./04-job-dsl-for-github-org) Generate Jenkins jobs based on github repos
11 | - Dockerhub image [ticketfly/jenkins-example-job-dsl-for-github-org](https://hub.docker.com/r/ticketfly/jenkins-example-job-dsl-for-github-org)
12 | 5. [AWS ECS example](./05-aws-ecs) Deploy Jenkins infrastructure to Amazon EC2 Container Service
13 | - Dockerhub image [ticketfly/jenkins-example-aws-ecs](https://hub.docker.com/r/ticketfly/jenkins-example-aws-ecs)
14 |
15 | To get more context, check out our blog series [Our Journey to Continuous Delivery](https://tech.ticketfly.com/our-journey-to-continuous-delivery-chapter-1-it-begins-with-a-road-3ab175b80d42).
16 |
--------------------------------------------------------------------------------