├── .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 | [![Video: Run Jenkins on Docker](http://img.youtube.com/vi/LUgF9kOW4u4/0.jpg)](http://www.youtube.com/watch?v=LUgF9kOW4u4) 27 | 28 | [![Video: Upgrade Jenkins version using gradle docker commands](http://img.youtube.com/vi/2JTxROGphdw/0.jpg)](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 | [![Video: Introduction to Jenkins DSL ](http://img.youtube.com/vi/WdSSlQua6bw/0.jpg)](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 | [![Video: How to test Jenkins changes?](http://img.youtube.com/vi/E1_8mz0HCnE/0.jpg)](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 | [![Video: Automate Jenkins configuration from Github repos](http://img.youtube.com/vi/lHgvrFZBqvM/0.jpg)](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 | [![Launch Stack](https://s3.amazonaws.com/cloudformation-examples/cloudformation-launch-stack.png)](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 | --------------------------------------------------------------------------------