commit 248499c8f664c73f08fc62ae87eae8b6e44dec41 Author: Benjamin Muschko Date: Sun Nov 17 16:40:48 2019 -0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/exercises/01-jenkins-installation/instructions.md b/exercises/01-jenkins-installation/instructions.md new file mode 100644 index 0000000..b3364c4 --- /dev/null +++ b/exercises/01-jenkins-installation/instructions.md @@ -0,0 +1,15 @@ +# Exercise 1 + +In this exercise, you will practice installing and configuring Jenkins on your machine. + +## Installing Jenkins + +1. Download the [latest stable Jenkins WAR file](http://mirrors.jenkins.io/war-stable/latest/jenkins.war). To download from the command line, use `wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war`. +2. Open a terminal and navigate to the directory containing the WAR file. +3. Run the command `java -jar jenkins.war` to start Jenkins on port 8080. Provide the `--httpPort` option if you experience a port conflict e.g. `java -jar jenkins.war --httpPort=9999`. After a couple of seconds you should see the message "Jenkins is fully up and running" in the log output. +4. Open a browser of your choice and navigate to `localhost:`. The default is `localhost:8080`. +5. In the screen named "Unlock Jenkins", enter the password from the console output. Press the "Continue" button. +6. In the screen named "Customize Jenkins", select the option "Install suggested plugins". +7. In the screen named "Create First Admin User", enter all fields with credentials for your Jenkins administrator. Press the "Save and Continue" button. +8. In the screen named "Instance Configuration", keep the default value. Press the "Save and Finish" button. +9. You should see the message "Jenkins is ready!". Press the "Start using Jenkins" button. You'll be redirected to the Jenkins dashboard. \ No newline at end of file diff --git a/exercises/01-jenkins-installation/solution/images/customize-jenkins.png b/exercises/01-jenkins-installation/solution/images/customize-jenkins.png new file mode 100644 index 0000000..f82e2b2 Binary files /dev/null and b/exercises/01-jenkins-installation/solution/images/customize-jenkins.png differ diff --git a/exercises/01-jenkins-installation/solution/images/first-admin-user.png b/exercises/01-jenkins-installation/solution/images/first-admin-user.png new file mode 100644 index 0000000..9cd70e8 Binary files /dev/null and b/exercises/01-jenkins-installation/solution/images/first-admin-user.png differ diff --git a/exercises/01-jenkins-installation/solution/images/instance-configuration.png b/exercises/01-jenkins-installation/solution/images/instance-configuration.png new file mode 100644 index 0000000..7104c6a Binary files /dev/null and b/exercises/01-jenkins-installation/solution/images/instance-configuration.png differ diff --git a/exercises/01-jenkins-installation/solution/images/jenkins-dashboard.png b/exercises/01-jenkins-installation/solution/images/jenkins-dashboard.png new file mode 100644 index 0000000..018a351 Binary files /dev/null and b/exercises/01-jenkins-installation/solution/images/jenkins-dashboard.png differ diff --git a/exercises/01-jenkins-installation/solution/images/jenkins-ready.png b/exercises/01-jenkins-installation/solution/images/jenkins-ready.png new file mode 100644 index 0000000..08f83d2 Binary files /dev/null and b/exercises/01-jenkins-installation/solution/images/jenkins-ready.png differ diff --git a/exercises/01-jenkins-installation/solution/images/plugin-installation-progress.png b/exercises/01-jenkins-installation/solution/images/plugin-installation-progress.png new file mode 100644 index 0000000..7640fa1 Binary files /dev/null and b/exercises/01-jenkins-installation/solution/images/plugin-installation-progress.png differ diff --git a/exercises/01-jenkins-installation/solution/images/unlock-jenkins.png b/exercises/01-jenkins-installation/solution/images/unlock-jenkins.png new file mode 100644 index 0000000..27ca0c1 Binary files /dev/null and b/exercises/01-jenkins-installation/solution/images/unlock-jenkins.png differ diff --git a/exercises/01-jenkins-installation/solution/solution.md b/exercises/01-jenkins-installation/solution/solution.md new file mode 100644 index 0000000..c678ef0 --- /dev/null +++ b/exercises/01-jenkins-installation/solution/solution.md @@ -0,0 +1,145 @@ +# Solution + +Download the WAR file with the help of `wget`. + +```bash +wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war +--2019-08-09 16:06:23-- http://mirrors.jenkins.io/war-stable/latest/jenkins.war +Resolving mirrors.jenkins.io (mirrors.jenkins.io)... 52.202.51.185 +Connecting to mirrors.jenkins.io (mirrors.jenkins.io)|52.202.51.185|:80... connected. +HTTP request sent, awaiting response... 302 Found +Location: http://mirror.xmission.com/jenkins/war-stable/2.176.2/jenkins.war [following] +--2019-08-09 16:06:23-- http://mirror.xmission.com/jenkins/war-stable/2.176.2/jenkins.war +Resolving mirror.xmission.com (mirror.xmission.com)... 2607:fa18:0:3::13, 198.60.22.13 +Connecting to mirror.xmission.com (mirror.xmission.com)|2607:fa18:0:3::13|:80... connected. +HTTP request sent, awaiting response... 200 OK +Length: 77379386 (74M) [application/java-archive] +Saving to: ‘jenkins.war’ + +jenkins.war 100%[=================================================>] 73.79M 6.46MB/s in 14s + +2019-08-09 16:06:37 (5.36 MB/s) - ‘jenkins.war’ saved [77379386/77379386] +``` + +First, start the Jenkins server. Copy the password printed in the log output to the clipboard. + +```bash +$ java -jar jenkins.war +Running from: /Users/bmuschko/dev/tools/jenkins-2.150.1/jenkins.war +webroot: $user.home/.jenkins +Jul 16, 2019 4:59:30 PM org.eclipse.jetty.util.log.Log initialized +INFO: Logging initialized @501ms to org.eclipse.jetty.util.log.JavaUtilLog +Jul 16, 2019 4:59:30 PM winstone.Logger logInternal +INFO: Beginning extraction from war file +Jul 16, 2019 4:59:32 PM org.eclipse.jetty.server.handler.ContextHandler setContextPath +WARNING: Empty contextPath +Jul 16, 2019 4:59:32 PM org.eclipse.jetty.server.Server doStart +INFO: jetty-9.4.z-SNAPSHOT; built: 2019-02-15T16:53:49.381Z; git: eb70b240169fcf1abbd86af36482d1c49826fa0b; jvm 1.8.0_181-b13 +Jul 16, 2019 4:59:32 PM org.eclipse.jetty.webapp.StandardDescriptorProcessor visitServlet +INFO: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet +Jul 16, 2019 4:59:32 PM org.eclipse.jetty.server.session.DefaultSessionIdManager doStart +INFO: DefaultSessionIdManager workerName=node0 +Jul 16, 2019 4:59:32 PM org.eclipse.jetty.server.session.DefaultSessionIdManager doStart +INFO: No SessionScavenger set, using defaults +Jul 16, 2019 4:59:32 PM org.eclipse.jetty.server.session.HouseKeeper startScavenging +INFO: node0 Scavenging every 660000ms +Jenkins home directory: /Users/bmuschko/.jenkins found at: $user.home/.jenkins +Jul 16, 2019 4:59:34 PM org.eclipse.jetty.server.handler.ContextHandler doStart +INFO: Started w.@21ec5d87{Jenkins v2.176.1,/,file:///Users/bmuschko/.jenkins/war/,AVAILABLE}{/Users/bmuschko/.jenkins/war} +Jul 16, 2019 4:59:34 PM org.eclipse.jetty.server.AbstractConnector doStart +INFO: Started ServerConnector@388ffbc2{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} +Jul 16, 2019 4:59:34 PM org.eclipse.jetty.server.Server doStart +INFO: Started @4486ms +Jul 16, 2019 4:59:34 PM winstone.Logger logInternal +INFO: Winstone Servlet Engine v4.0 running: controlPort=disabled +Jul 16, 2019 4:59:35 PM jenkins.InitReactorRunner$1 onAttained +INFO: Started initialization +Jul 16, 2019 4:59:35 PM jenkins.InitReactorRunner$1 onAttained +INFO: Listed all plugins +Jul 16, 2019 4:59:36 PM jenkins.InitReactorRunner$1 onAttained +INFO: Prepared all plugins +Jul 16, 2019 4:59:36 PM jenkins.InitReactorRunner$1 onAttained +INFO: Started all plugins +Jul 16, 2019 4:59:36 PM jenkins.InitReactorRunner$1 onAttained +INFO: Augmented all extensions +Jul 16, 2019 4:59:37 PM jenkins.InitReactorRunner$1 onAttained +INFO: Loaded all jobs +Jul 16, 2019 4:59:37 PM hudson.model.AsyncPeriodicWork$1 run +INFO: Started Download metadata +Jul 16, 2019 4:59:37 PM hudson.util.Retrier start +INFO: Attempt #1 to do the action check updates server +Jul 16, 2019 4:59:38 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh +INFO: Refreshing org.springframework.web.context.support.StaticWebApplicationContext@54fe6e54: display name [Root WebApplicationContext]; startup date [Tue Jul 16 16:59:38 MDT 2019]; root of context hierarchy +Jul 16, 2019 4:59:38 PM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory +INFO: Bean factory for application context [org.springframework.web.context.support.StaticWebApplicationContext@54fe6e54]: org.springframework.beans.factory.support.DefaultListableBeanFactory@3e77b308 +Jul 16, 2019 4:59:38 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons +INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@3e77b308: defining beans [authenticationManager]; root of factory hierarchy +Jul 16, 2019 4:59:38 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh +INFO: Refreshing org.springframework.web.context.support.StaticWebApplicationContext@2b22ca16: display name [Root WebApplicationContext]; startup date [Tue Jul 16 16:59:38 MDT 2019]; root of context hierarchy +Jul 16, 2019 4:59:38 PM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory +INFO: Bean factory for application context [org.springframework.web.context.support.StaticWebApplicationContext@2b22ca16]: org.springframework.beans.factory.support.DefaultListableBeanFactory@28d4e2d4 +Jul 16, 2019 4:59:38 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons +INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@28d4e2d4: defining beans [filter,legacy]; root of factory hierarchy +Jul 16, 2019 4:59:38 PM jenkins.install.SetupWizard init +INFO: + +************************************************************* +************************************************************* +************************************************************* + +Jenkins initial setup is required. An admin user has been created and a password generated. +Please use the following password to proceed to installation: + +34155b26571e4a2eb6eca68ddc4d5749 + +This may also be found at: /Users/bmuschko/.jenkins/secrets/initialAdminPassword + +************************************************************* +************************************************************* +************************************************************* + +Jul 16, 2019 4:59:42 PM hudson.model.UpdateSite updateData +INFO: Obtained the latest update center data file for UpdateSource default +Jul 16, 2019 4:59:42 PM hudson.model.UpdateSite updateData +INFO: Obtained the latest update center data file for UpdateSource default +Jul 16, 2019 4:59:43 PM jenkins.InitReactorRunner$1 onAttained +INFO: Completed initialization +Jul 16, 2019 4:59:43 PM hudson.model.DownloadService$Downloadable load +INFO: Obtained the updated data file for hudson.tasks.Maven.MavenInstaller +Jul 16, 2019 4:59:43 PM hudson.util.Retrier start +INFO: Performed the action check updates server successfully at the attempt #1 +Jul 16, 2019 4:59:43 PM hudson.model.AsyncPeriodicWork$1 run +INFO: Finished Download metadata. 5,856 ms +Jul 16, 2019 4:59:43 PM hudson.UDPBroadcastThread run +INFO: Cannot listen to UDP port 33,848, skipping: java.net.SocketException: Can't assign requested address +Jul 16, 2019 4:59:43 PM hudson.WebAppMain$3 run +INFO: Jenkins is fully up and running +``` + +Paste the password from the clipboard into the password field. + +![Unlock Jenkins](./images/unlock-jenkins.png) + +Select the option to install recommended plugins. + +![Customize Jenkins](./images/customize-jenkins.png) + +The installation process will run for a couple of minutes. + +![Plugin Installation Progress](./images/plugin-installation-progress.png) + +Enter credentials for your first admin user. + +![First Admin User](./images/first-admin-user.png) + +Skip over the instance configuration step. + +![Instance Configuration](./images/instance-configuration.png) + +Jenkins indicates that it is ready for use. + +![Jenkins Is Ready](./images/jenkins-ready.png) + +The installation process is complete. You should land on the dashboard. + +![Jenkins Dashboard](./images/jenkins-dashboard.png) \ No newline at end of file diff --git a/exercises/02-job-creation/instructions.md b/exercises/02-job-creation/instructions.md new file mode 100644 index 0000000..1ec5d9a --- /dev/null +++ b/exercises/02-job-creation/instructions.md @@ -0,0 +1,14 @@ +# Exercise 2 + +In this exercise, you will create a new freestyle job and configure it to build with parameters. You'll also have a look at the Jenkins home directory to inspect the directory structure representing the job. + +## Defining, Configuring and Organizing a Job + +1. From the dashboard, click the "New Item" button. +2. Enter the item name "my-freestyle-job" and select "Freestyle project". Press the "OK" button. +3. In the job configuration, define the option to only keep the last 2 builds. Provide the description "A simple freestyle job". Upon building the project, a String parameter named `MESSAGE` should be provided. Press the "Save" button. +4. Trigger a new build by pressing the "Build with Parameters" button. Enter a value for the `MESSAGE` parameter. The build should finish successfully. Locate the provided parameter value in the build information. +5. Run the build two more times. What do you see? +6. Create a new view named "test". Add the job to the view. +7. Create a new folder named "freestyle" as part of the view. Move the job into the folder. +8. Locate the build information in `$JENKINS_HOME`. Inspect the directory structure. \ No newline at end of file diff --git a/exercises/02-job-creation/solution/images/build-history.png b/exercises/02-job-creation/solution/images/build-history.png new file mode 100644 index 0000000..1522e25 Binary files /dev/null and b/exercises/02-job-creation/solution/images/build-history.png differ diff --git a/exercises/02-job-creation/solution/images/build-with-params.png b/exercises/02-job-creation/solution/images/build-with-params.png new file mode 100644 index 0000000..53393f9 Binary files /dev/null and b/exercises/02-job-creation/solution/images/build-with-params.png differ diff --git a/exercises/02-job-creation/solution/images/job-configuration.png b/exercises/02-job-creation/solution/images/job-configuration.png new file mode 100644 index 0000000..9a9750d Binary files /dev/null and b/exercises/02-job-creation/solution/images/job-configuration.png differ diff --git a/exercises/02-job-creation/solution/images/job-in-folder.png b/exercises/02-job-creation/solution/images/job-in-folder.png new file mode 100644 index 0000000..4136e97 Binary files /dev/null and b/exercises/02-job-creation/solution/images/job-in-folder.png differ diff --git a/exercises/02-job-creation/solution/images/job-in-view.png b/exercises/02-job-creation/solution/images/job-in-view.png new file mode 100644 index 0000000..a0a3981 Binary files /dev/null and b/exercises/02-job-creation/solution/images/job-in-view.png differ diff --git a/exercises/02-job-creation/solution/images/move-job.png b/exercises/02-job-creation/solution/images/move-job.png new file mode 100644 index 0000000..37b4eca Binary files /dev/null and b/exercises/02-job-creation/solution/images/move-job.png differ diff --git a/exercises/02-job-creation/solution/images/new-folder.png b/exercises/02-job-creation/solution/images/new-folder.png new file mode 100644 index 0000000..9d54bd6 Binary files /dev/null and b/exercises/02-job-creation/solution/images/new-folder.png differ diff --git a/exercises/02-job-creation/solution/images/new-freestyle-job.png b/exercises/02-job-creation/solution/images/new-freestyle-job.png new file mode 100644 index 0000000..46e158c Binary files /dev/null and b/exercises/02-job-creation/solution/images/new-freestyle-job.png differ diff --git a/exercises/02-job-creation/solution/images/new-view.png b/exercises/02-job-creation/solution/images/new-view.png new file mode 100644 index 0000000..b46df4a Binary files /dev/null and b/exercises/02-job-creation/solution/images/new-view.png differ diff --git a/exercises/02-job-creation/solution/solution.md b/exercises/02-job-creation/solution/solution.md new file mode 100644 index 0000000..8b3fd1d --- /dev/null +++ b/exercises/02-job-creation/solution/solution.md @@ -0,0 +1,66 @@ +# Solution + +We'll start by creating the new freestyle job. + +![New Freestyle Job](./images/new-freestyle-job.png) + +Configure the job as follows. + +![Job Configuration](./images/job-configuration.png) + +The build will ask for a parameter value when triggered. + +![Build with Parameters](./images/build-with-params.png) + +The build history only stores the previous two builds. + +![Build History](./images/build-history.png) + +Create a new view. + +![New View](./images/new-view.png) + +After adding the job to the view, it will show up in a separate tab. + +![Job in View](./images/job-in-view.png) + +Create a new folder. + +![New Folder](./images/new-folder.png) + +The job became a child of the folder after moving it there. + +![Job In Folder](./images/job-in-folder.png) + +Navigating to the `job` directory under the Jenkins Home reveals the build history. + +```bash +$ cd /Users/bmuschko/.jenkins/jobs/freestyle/jobs +$ tree my-freestyle-job +my-freestyle-job +├── builds +│   ├── 1 +│   │   ├── build.xml +│   │   ├── changelog.xml +│   │   └── log +│   ├── 2 +│   │   ├── build.xml +│   │   ├── changelog.xml +│   │   └── log +│   ├── 3 +│   │   ├── build.xml +│   │   ├── changelog.xml +│   │   └── log +│   ├── lastFailedBuild -> -1 +│   ├── lastStableBuild -> 3 +│   ├── lastSuccessfulBuild -> 3 +│   ├── lastUnstableBuild -> -1 +│   ├── lastUnsuccessfulBuild -> -1 +│   └── legacyIds +├── config.xml +├── lastStable -> builds/lastStableBuild +├── lastSuccessful -> builds/lastSuccessfulBuild +└── nextBuildNumber + +8 directories, 15 files +``` \ No newline at end of file diff --git a/exercises/03-build-trigger-and-steps/instructions.md b/exercises/03-build-trigger-and-steps/instructions.md new file mode 100644 index 0000000..5c3ef14 --- /dev/null +++ b/exercises/03-build-trigger-and-steps/instructions.md @@ -0,0 +1,12 @@ +# Exercise 3 + +In this exercise, you will configure the job to build based on a cron definition. Moreover, you will create downstream job that should be built automatically if the upstream job is successful. + +## Configuring Build Triggers and Steps for a Job + +1. Add a default value for the build parameter named `MESSAGE` e.g. `Hello World!`. +2. Create a build trigger that builds the project every minute. +3. Add a build step that executes the shell command `echo "Message: $MESSAGE"`. The message is value of the parameter. +4. After a minute the first execution should have been triggered. Check the log output of the build and find the rendered message. +5. Create another freestyle project named `downstream-job` in the same folder. +6. Configure the initial job to execute the `downstream-job` if it was stable. \ No newline at end of file diff --git a/exercises/03-build-trigger-and-steps/solution/images/both-jobs-in-folder.png b/exercises/03-build-trigger-and-steps/solution/images/both-jobs-in-folder.png new file mode 100644 index 0000000..dd320d4 Binary files /dev/null and b/exercises/03-build-trigger-and-steps/solution/images/both-jobs-in-folder.png differ diff --git a/exercises/03-build-trigger-and-steps/solution/images/build-downstream.png b/exercises/03-build-trigger-and-steps/solution/images/build-downstream.png new file mode 100644 index 0000000..e86ca3e Binary files /dev/null and b/exercises/03-build-trigger-and-steps/solution/images/build-downstream.png differ diff --git a/exercises/03-build-trigger-and-steps/solution/images/build-trigger-and-step.png b/exercises/03-build-trigger-and-steps/solution/images/build-trigger-and-step.png new file mode 100644 index 0000000..1996cad Binary files /dev/null and b/exercises/03-build-trigger-and-steps/solution/images/build-trigger-and-step.png differ diff --git a/exercises/03-build-trigger-and-steps/solution/images/executed-downstream-job.png b/exercises/03-build-trigger-and-steps/solution/images/executed-downstream-job.png new file mode 100644 index 0000000..92c2f1f Binary files /dev/null and b/exercises/03-build-trigger-and-steps/solution/images/executed-downstream-job.png differ diff --git a/exercises/03-build-trigger-and-steps/solution/images/string-parameter.png b/exercises/03-build-trigger-and-steps/solution/images/string-parameter.png new file mode 100644 index 0000000..ce0f93f Binary files /dev/null and b/exercises/03-build-trigger-and-steps/solution/images/string-parameter.png differ diff --git a/exercises/03-build-trigger-and-steps/solution/solution.md b/exercises/03-build-trigger-and-steps/solution/solution.md new file mode 100644 index 0000000..c32956c --- /dev/null +++ b/exercises/03-build-trigger-and-steps/solution/solution.md @@ -0,0 +1,33 @@ +# Solution + +Modify the existing build parameter. + +![String Parameter](./images/string-parameter.png) + +Add the cron build trigger and the build step. + +![Build Trigger And Step](./images/build-trigger-and-step.png) + +The output render the interpolated value of the `echo` command. + +```bash +Started by timer +Running as SYSTEM +Building in workspace /Users/bmuschko/.jenkins/workspace/freestyle/my-freestyle-job +[my-freestyle-job] $ /bin/sh -xe /var/folders/02/3dgzjkqj4kz0g7lnrk0w93c00000gn/T/jenkins3548490840940668236.sh ++ echo "Message: Hello World!" +Message: Hello World! +Finished: SUCCESS +``` + +Create a new job in the same folder. + +![Both Jobs In Folder](./images/both-jobs-in-folder.png) + +Configure the downstream job from the initial job. + +![Downstream Job Configuration](./images/build-downstream.png) + +The downstream job indicates the upstream job. + +![Downstream Build Information](./images/executed-downstream-job.png) \ No newline at end of file diff --git a/exercises/04-scm-configuration/instructions.md b/exercises/04-scm-configuration/instructions.md new file mode 100644 index 0000000..1830e67 --- /dev/null +++ b/exercises/04-scm-configuration/instructions.md @@ -0,0 +1,10 @@ +# Exercise 4 + +In this exercise, you will create a new job that pulls the source code from a repository on GitHub. Upon triggering a build, the job will execute a Gradle build. + +## Configuring a GitHub Repository + +1. Create a new freestyle job named `gradle-initializr`. +2. Configure Git as the SCM and use the repository URL `git@github.com:bmuschko/gradle-initializr.git`. Only build from the branch `master`. +3. Add a build step to run the Gradle command `clean build` using the Wrapper. +4. Trigger a build and look at the output. \ No newline at end of file diff --git a/exercises/04-scm-configuration/solution/images/git-scm.png b/exercises/04-scm-configuration/solution/images/git-scm.png new file mode 100644 index 0000000..a64c5ea Binary files /dev/null and b/exercises/04-scm-configuration/solution/images/git-scm.png differ diff --git a/exercises/04-scm-configuration/solution/images/gradle-build-step.png b/exercises/04-scm-configuration/solution/images/gradle-build-step.png new file mode 100644 index 0000000..9b9a66c Binary files /dev/null and b/exercises/04-scm-configuration/solution/images/gradle-build-step.png differ diff --git a/exercises/04-scm-configuration/solution/images/new-job.png b/exercises/04-scm-configuration/solution/images/new-job.png new file mode 100644 index 0000000..795117b Binary files /dev/null and b/exercises/04-scm-configuration/solution/images/new-job.png differ diff --git a/exercises/04-scm-configuration/solution/solution.md b/exercises/04-scm-configuration/solution/solution.md new file mode 100644 index 0000000..799a7da --- /dev/null +++ b/exercises/04-scm-configuration/solution/solution.md @@ -0,0 +1,71 @@ +# Solution + +Create a new job. + +![Freestyle Job](./images/new-job.png) + +Configure the Git SCM and point the proper URL. The default is the `master` branch. + +![Git SCM](./images/git-scm.png) + +Create the Gradle build step. + +![Git SCM](./images/gradle-build-step.png) + +The build log should look similar to the output below. + +```bash +Started by user Admin +Running as SYSTEM +Building in workspace /Users/bmuschko/.jenkins/workspace/gradle-initializr +No credentials specified +Cloning the remote Git repository +Cloning repository git@github.com:bmuschko/gradle-initializr.git + > git init /Users/bmuschko/.jenkins/workspace/gradle-initializr # timeout=10 +Fetching upstream changes from git@github.com:bmuschko/gradle-initializr.git + > git --version # timeout=10 + > git fetch --tags --force --progress git@github.com:bmuschko/gradle-initializr.git +refs/heads/*:refs/remotes/origin/* + > git config remote.origin.url git@github.com:bmuschko/gradle-initializr.git # timeout=10 + > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10 + > git config remote.origin.url git@github.com:bmuschko/gradle-initializr.git # timeout=10 +Fetching upstream changes from git@github.com:bmuschko/gradle-initializr.git + > git fetch --tags --force --progress git@github.com:bmuschko/gradle-initializr.git +refs/heads/*:refs/remotes/origin/* + > git rev-parse refs/remotes/origin/master^{commit} # timeout=10 + > git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10 +Checking out Revision 8e725ea3507f5ac0f8251234e1ff0f214b228d3d (refs/remotes/origin/master) + > git config core.sparsecheckout # timeout=10 + > git checkout -f 8e725ea3507f5ac0f8251234e1ff0f214b228d3d +Commit message: "Update docs" +First time build. Skipping changelog. +[Gradle] - Launching build. +[gradle-initializr] $ gradle clean build +Starting a Gradle Daemon (subsequent builds will be faster) +> Task :clean UP-TO-DATE +> Task :compileJava +> Task :compileGroovy NO-SOURCE +> Task :processResources +> Task :classes +> Task :bootJar +> Task :jar SKIPPED +> Task :assemble +> Task :compileTestJava NO-SOURCE +> Task :compileTestGroovy +> Task :processTestResources NO-SOURCE +> Task :testClasses +> Task :test +> Task :compileIntegrationTestJava NO-SOURCE +> Task :compileIntegrationTestGroovy +> Task :processIntegrationTestResources NO-SOURCE +> Task :integrationTestClasses +> Task :integrationTest +2019-07-17 10:07:48.803 INFO 67741 --- [ Thread-6] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor' +> Task :check +> Task :build + +BUILD SUCCESSFUL in 30s +8 actionable tasks: 7 executed, 1 up-to-date +Build step 'Invoke Gradle script' changed build result to SUCCESS +Finished: SUCCESS +``` + +As a side note: The GitHub plugin is [currently broken](https://issues.jenkins-ci.org/browse/JENKINS-11337) if you wanted to build multiple branches with a single job. You will have to model it as a multi-branch pipeline job. \ No newline at end of file diff --git a/exercises/05-test-execution-and-reporting/instructions.md b/exercises/05-test-execution-and-reporting/instructions.md new file mode 100644 index 0000000..3741896 --- /dev/null +++ b/exercises/05-test-execution-and-reporting/instructions.md @@ -0,0 +1,14 @@ +# Exercise 5 + +In this exercise, you will configure the existing job to execute tests. Furthermore, you'll enhance the job to process the test results and code coverage metrics. + +## Displaying JUnit and JaCoCo Test Results + +1. Configure the `gradle-initializr` project to parse the JUnit XML results for unit test. The files can be found in the directory `build/test-results/test`. +2. Execute the build twice to generate a graph. Have a look at the executed tests in the test results. +3. Include all test results (unit and integration tests) in the reporting. +4. Have a look at how the test result trend changes. +5. Install the JaCoCo plugin. +6. Reconfigure the Gradle build step to also generate JaCoCo reports: `clean build jacocoTestReport jacocoIntegrationTestReport`. +7. Add a post-build action for publish the JaCoCo reports. Use the following path to look for results: `build/jacoco/**.exec`. +8. Execute the build twice to generate a graph. Have a look at the code coverage results. \ No newline at end of file diff --git a/exercises/05-test-execution-and-reporting/solution/images/all-test-result-trend.png b/exercises/05-test-execution-and-reporting/solution/images/all-test-result-trend.png new file mode 100644 index 0000000..34bebbb Binary files /dev/null and b/exercises/05-test-execution-and-reporting/solution/images/all-test-result-trend.png differ diff --git a/exercises/05-test-execution-and-reporting/solution/images/jacoco-config.png b/exercises/05-test-execution-and-reporting/solution/images/jacoco-config.png new file mode 100644 index 0000000..5d0bd19 Binary files /dev/null and b/exercises/05-test-execution-and-reporting/solution/images/jacoco-config.png differ diff --git a/exercises/05-test-execution-and-reporting/solution/images/jacoco-plugin.png b/exercises/05-test-execution-and-reporting/solution/images/jacoco-plugin.png new file mode 100644 index 0000000..4c6ab1d Binary files /dev/null and b/exercises/05-test-execution-and-reporting/solution/images/jacoco-plugin.png differ diff --git a/exercises/05-test-execution-and-reporting/solution/images/jacoco-report.png b/exercises/05-test-execution-and-reporting/solution/images/jacoco-report.png new file mode 100644 index 0000000..83b5177 Binary files /dev/null and b/exercises/05-test-execution-and-reporting/solution/images/jacoco-report.png differ diff --git a/exercises/05-test-execution-and-reporting/solution/images/jacoco-tasks.png b/exercises/05-test-execution-and-reporting/solution/images/jacoco-tasks.png new file mode 100644 index 0000000..21ce025 Binary files /dev/null and b/exercises/05-test-execution-and-reporting/solution/images/jacoco-tasks.png differ diff --git a/exercises/05-test-execution-and-reporting/solution/images/jacoco-trend.png b/exercises/05-test-execution-and-reporting/solution/images/jacoco-trend.png new file mode 100644 index 0000000..33a29bb Binary files /dev/null and b/exercises/05-test-execution-and-reporting/solution/images/jacoco-trend.png differ diff --git a/exercises/05-test-execution-and-reporting/solution/images/publish-all-tests.png b/exercises/05-test-execution-and-reporting/solution/images/publish-all-tests.png new file mode 100644 index 0000000..90bd436 Binary files /dev/null and b/exercises/05-test-execution-and-reporting/solution/images/publish-all-tests.png differ diff --git a/exercises/05-test-execution-and-reporting/solution/images/publish-unit-tests.png b/exercises/05-test-execution-and-reporting/solution/images/publish-unit-tests.png new file mode 100644 index 0000000..844c9cc Binary files /dev/null and b/exercises/05-test-execution-and-reporting/solution/images/publish-unit-tests.png differ diff --git a/exercises/05-test-execution-and-reporting/solution/images/unit-test-result-trend.png b/exercises/05-test-execution-and-reporting/solution/images/unit-test-result-trend.png new file mode 100644 index 0000000..c08f839 Binary files /dev/null and b/exercises/05-test-execution-and-reporting/solution/images/unit-test-result-trend.png differ diff --git a/exercises/05-test-execution-and-reporting/solution/solution.md b/exercises/05-test-execution-and-reporting/solution/solution.md new file mode 100644 index 0000000..4c77f5d --- /dev/null +++ b/exercises/05-test-execution-and-reporting/solution/solution.md @@ -0,0 +1,37 @@ +# Solution + +Publish the JUnit reports by pointing to the exact directory containing the XML files. + +![Publish JUnit Results](./images/publish-unit-tests.png) + +After executing the build twice you will see a test result trend graph. + +![JUnit Test Trend](./images/unit-test-result-trend.png) + +Publish the JUnit reporting by using a wild card. + +![Publish All JUnit Results](./images/publish-all-tests.png) + +The trend changes accordingly. + +![Changed Test Result Trend](./images/all-test-result-trend.png) + +Install the JaCoCo plugin. + +![JaCoCo Plugin](./images/jacoco-plugin.png) + +Change the task list executed by the Gradle build step. + +![JaCoCo Tasks](./images/jacoco-tasks.png) + +Configure JaCoCo reporting. + +![JaCoCo Configuration](./images/jacoco-config.png) + +The JaCoCo coverage trend renders after executing the build twice. + +![JaCoCo Coverage Report Trend](./images/jacoco-trend.png) + +You can drill into the details of the report. + +![JaCoCo Coverage Report Trend](./images/jacoco-report.png) \ No newline at end of file diff --git a/exercises/07-artifacts/instructions.md b/exercises/07-artifacts/instructions.md new file mode 100644 index 0000000..96f89cd --- /dev/null +++ b/exercises/07-artifacts/instructions.md @@ -0,0 +1,15 @@ +# Exercise 7 + +You will enhance the existing job to generate a JAR file and store it on Jenkins. The job should record the MD5 hash for the artifact. Later, you will configure a downstream job that should be able to reuse the existing artifact. + +## Storing and Fingerprinting Artifacts + +1. Create a post-build action for archiving JAR files with the pattern `build/libs/*.jar`. Enable the fingerprinting option. +2. Execute the build. The build should list the artifact `gradle-initializr-1.0.0.jar`. +3. Have a look at the recorded fingerprints of this build. +4. Render the MD5 hash of the artifact and the usage of the artifact. +5. Install the Copy Artifacts plugin. +6. Create a downstream job named `consumer`. +7. Configure the downstream job to use the artifact produced by the upstream job. +8. Run the the build for the job `gradle-initializr`. +9. Have a look at the fingerprints of the downstream job. \ No newline at end of file diff --git a/exercises/07-artifacts/solution/images/archive-artifacts.png b/exercises/07-artifacts/solution/images/archive-artifacts.png new file mode 100644 index 0000000..1c500a3 Binary files /dev/null and b/exercises/07-artifacts/solution/images/archive-artifacts.png differ diff --git a/exercises/07-artifacts/solution/images/build-artifact.png b/exercises/07-artifacts/solution/images/build-artifact.png new file mode 100644 index 0000000..b1e3f42 Binary files /dev/null and b/exercises/07-artifacts/solution/images/build-artifact.png differ diff --git a/exercises/07-artifacts/solution/images/copy-artifacts-plugin.png b/exercises/07-artifacts/solution/images/copy-artifacts-plugin.png new file mode 100644 index 0000000..3e5bab2 Binary files /dev/null and b/exercises/07-artifacts/solution/images/copy-artifacts-plugin.png differ diff --git a/exercises/07-artifacts/solution/images/copy-artifacts.png b/exercises/07-artifacts/solution/images/copy-artifacts.png new file mode 100644 index 0000000..c65dc4f Binary files /dev/null and b/exercises/07-artifacts/solution/images/copy-artifacts.png differ diff --git a/exercises/07-artifacts/solution/images/fingerprint-details.png b/exercises/07-artifacts/solution/images/fingerprint-details.png new file mode 100644 index 0000000..3c00ff9 Binary files /dev/null and b/exercises/07-artifacts/solution/images/fingerprint-details.png differ diff --git a/exercises/07-artifacts/solution/images/fingerprints-upstream.png b/exercises/07-artifacts/solution/images/fingerprints-upstream.png new file mode 100644 index 0000000..9658098 Binary files /dev/null and b/exercises/07-artifacts/solution/images/fingerprints-upstream.png differ diff --git a/exercises/07-artifacts/solution/images/recorded-fingerprint.png b/exercises/07-artifacts/solution/images/recorded-fingerprint.png new file mode 100644 index 0000000..d005d75 Binary files /dev/null and b/exercises/07-artifacts/solution/images/recorded-fingerprint.png differ diff --git a/exercises/07-artifacts/solution/images/trigger-upstream.png b/exercises/07-artifacts/solution/images/trigger-upstream.png new file mode 100644 index 0000000..b589854 Binary files /dev/null and b/exercises/07-artifacts/solution/images/trigger-upstream.png differ diff --git a/exercises/07-artifacts/solution/solution.md b/exercises/07-artifacts/solution/solution.md new file mode 100644 index 0000000..6d55e3a --- /dev/null +++ b/exercises/07-artifacts/solution/solution.md @@ -0,0 +1,29 @@ +# Solution + +In the job configuration, archive the artifact produced by the build. Check the box for generating fingerprints. + +![Archive Artifacts](./images/archive-artifacts.png) + +Execute the build and find the archived artifact. + +![Archived Artifact](./images/build-artifact.png) + +Have a look at the details of the fingerprinting. + +![Fingerprint Details](./images/fingerprint-details.png) + +Install the Copy Artifacts plugin. + +![Copy Artifacts Plugin](./images/copy-artifacts-plugin.png) + +Trigger the downstream job upon success. + +![Trigger Downstream](./images/trigger-upstream.png) + +Copy artifacts from the upstream job. + +![Copy Artifact From Upstream](./images/copy-artifacts.png) + +You can see the full usage of the artifact in upstream and downstream jobs. + +![Fingerprint Usage](./images/fingerprints-upstream.png) \ No newline at end of file diff --git a/exercises/08-matrix-security/instructions.md b/exercises/08-matrix-security/instructions.md new file mode 100644 index 0000000..8118001 --- /dev/null +++ b/exercises/08-matrix-security/instructions.md @@ -0,0 +1,11 @@ +# Exercise 8 + +In this exercise, you'll create a new user and assign read permissions. You will verify permissions for the admin user and the new user by logging into Jenkins with the appropriate credentials. + +## Creating a User and Setting Permissions + +1. Ensure that you are logged in as admin user. +2. Create a new user named `miller`. Provide the relevant information. +3. Configure Matrix Security by adding the admin user and the newly created user. The admin user should have all permissions. The `miller` user should only have read permissions for jobs and views. +4. Log out of the system. +5. Log in with the user `miller` and navigate to one of the jobs. Notice that you cannot edit the configuration or trigger a new build. \ No newline at end of file diff --git a/exercises/08-matrix-security/solution/images/create-user.png b/exercises/08-matrix-security/solution/images/create-user.png new file mode 100644 index 0000000..c43c865 Binary files /dev/null and b/exercises/08-matrix-security/solution/images/create-user.png differ diff --git a/exercises/08-matrix-security/solution/images/job-overview.png b/exercises/08-matrix-security/solution/images/job-overview.png new file mode 100644 index 0000000..33b0c2c Binary files /dev/null and b/exercises/08-matrix-security/solution/images/job-overview.png differ diff --git a/exercises/08-matrix-security/solution/images/matrix-security-permissions.png b/exercises/08-matrix-security/solution/images/matrix-security-permissions.png new file mode 100644 index 0000000..174d663 Binary files /dev/null and b/exercises/08-matrix-security/solution/images/matrix-security-permissions.png differ diff --git a/exercises/08-matrix-security/solution/images/user-login.png b/exercises/08-matrix-security/solution/images/user-login.png new file mode 100644 index 0000000..fd93efe Binary files /dev/null and b/exercises/08-matrix-security/solution/images/user-login.png differ diff --git a/exercises/08-matrix-security/solution/images/user-overview.png b/exercises/08-matrix-security/solution/images/user-overview.png new file mode 100644 index 0000000..91b776c Binary files /dev/null and b/exercises/08-matrix-security/solution/images/user-overview.png differ diff --git a/exercises/08-matrix-security/solution/solution.md b/exercises/08-matrix-security/solution/solution.md new file mode 100644 index 0000000..4c0b9ff --- /dev/null +++ b/exercises/08-matrix-security/solution/solution.md @@ -0,0 +1,21 @@ +# Solution + +Create the new user. + +![Create User](./images/create-user.png) + +The new user is now listed. + +![User Overview](./images/user-overview.png) + +Configure users and permissions. + +![Matrix Security](./images/matrix-security-permissions.png) + +Log in with new user. + +![Matrix Security](./images/user-login.png) + +The job doesn't allow any editing or build triggering operations. + +![Job Overview](./images/job-overview.png) \ No newline at end of file diff --git a/exercises/09-rest-api/instructions.md b/exercises/09-rest-api/instructions.md new file mode 100644 index 0000000..ae12b42 --- /dev/null +++ b/exercises/09-rest-api/instructions.md @@ -0,0 +1,17 @@ +# Exercise 9 + +This exercise explores the use of the REST API to trigger a build for an existing job. You will configure security features as necessary. + +## Using the REST API for Common Operations + +1. Create a new user named `buildbot`. +2. Add adminstration permissions for the user. +3. Log in with user `buildbot`. +4. Generate the API token for the user. +5. Generate a Jenkins crumb from the command line. +6. Export the environment variables `JENKINS_CRUMB` and `JENKINS_API_TOKEN` with the correct values. +7. Trigger a build of the job `gradle-initializr` with a `curl` command using the REST API. +8. Disable the job `gradle-initializr` with a `curl` command using the REST API. +9. Reenable the job `gradle-initializr` with a `curl` command using the REST API. +10. Download the Jenkins CLI client. +11. Trigger the build of the job `gradle-initializr` with a `curl` command using the Jenkins CLI. \ No newline at end of file diff --git a/exercises/09-rest-api/solution/images/api-token.png b/exercises/09-rest-api/solution/images/api-token.png new file mode 100644 index 0000000..e69934a Binary files /dev/null and b/exercises/09-rest-api/solution/images/api-token.png differ diff --git a/exercises/09-rest-api/solution/images/build-by-user.png b/exercises/09-rest-api/solution/images/build-by-user.png new file mode 100644 index 0000000..43cc137 Binary files /dev/null and b/exercises/09-rest-api/solution/images/build-by-user.png differ diff --git a/exercises/09-rest-api/solution/images/create-user.png b/exercises/09-rest-api/solution/images/create-user.png new file mode 100644 index 0000000..5260b7f Binary files /dev/null and b/exercises/09-rest-api/solution/images/create-user.png differ diff --git a/exercises/09-rest-api/solution/images/disabled-job.png b/exercises/09-rest-api/solution/images/disabled-job.png new file mode 100644 index 0000000..4b3baa1 Binary files /dev/null and b/exercises/09-rest-api/solution/images/disabled-job.png differ diff --git a/exercises/09-rest-api/solution/images/user-permissions.png b/exercises/09-rest-api/solution/images/user-permissions.png new file mode 100644 index 0000000..6a3c56e Binary files /dev/null and b/exercises/09-rest-api/solution/images/user-permissions.png differ diff --git a/exercises/09-rest-api/solution/solution.md b/exercises/09-rest-api/solution/solution.md new file mode 100644 index 0000000..88ed23b --- /dev/null +++ b/exercises/09-rest-api/solution/solution.md @@ -0,0 +1,50 @@ +# Solution + +Create the new user. + +![Create User](./images/create-user.png) + +Add user permissions. + +![User Permissions](./images/user-permissions.png) + +Generate the API token. + +![API Token](./images/api-token.png) + +Generate the Jenkins crumb from the CLI. + +```bash +$ curl -u "buildbot:pwd" 'http://localhost:8080/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)' +Jenkins-Crumb:890d0b4c9c1b111deb55b196813a0ae1 +$ export JENKINS_CRUMB=Jenkins-Crumb:890d0b4c9c1b111deb55b196813a0ae1 +$ export JENKINS_API_TOKEN=11e2f3c68399b6bc3a28bc06e002be104d +``` + +Trigger a build with the `curl` command. + +```bash +$ curl -X POST -H "$JENKINS_CRUMB" http://buildbot:$JENKINS_API_TOKEN@localhost:8080/job/gradle-initializr/build +``` + +Disable the job via the REST API. You will see that the job indicated its status. + +```bash +$ curl -X POST -H "$JENKINS_CRUMB" http://buildbot:$JENKINS_API_TOKEN@localhost:8080/job/gradle-initializr/disable +``` + +![Disabled Job](./images/disabled-job.png) + +Reenable the job. + +```bash +$ curl -X POST -H "$JENKINS_CRUMB" http://buildbot:$JENKINS_API_TOKEN@localhost:8080/job/gradle-initializr/enable +``` + +Download the Jenkins URL by calling the URL `localhost:8080/jnlpJars/jenkins-cli.jar` from the browser. + +In the terminal, navigate to the directory that contains the Jenkins CLI JAR file. Use the Jenkins CLI to trigger a build with the correct command. This simply provide the password instead of the API token. + +```bash +$ java -jar jenkins-cli.jar -s http://localhost:8080 -auth buildbot:pwd build gradle-initializr +``` \ No newline at end of file diff --git a/exercises/10-distributed-builds/Vagrantfile b/exercises/10-distributed-builds/Vagrantfile new file mode 100644 index 0000000..339755e --- /dev/null +++ b/exercises/10-distributed-builds/Vagrantfile @@ -0,0 +1,35 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. +Vagrant.configure("2") do |config| + config.vm.box = "ubuntu/trusty64" + + (1..2).each do |number| + config.vm.define "jenkins-agent-#{number}" do |node| + node.vm.network "forwarded_port", guest: 22, host: "909#{number}" + node.vm.network "private_network", ip: "192.168.99.20#{number}" + node.vm.hostname = "jenkins-agent-#{number}" + end + end + + config.vm.provider "virtualbox" do |vb| + vb.memory = "2048" + end + + config.vm.provision "shell", inline: <<-SHELL + sudo add-apt-repository ppa:openjdk-r/ppa -y + sudo apt-get update + sudo apt-get -y install openjdk-8-jdk + sudo update-alternatives --config java + sudo apt-get -y install git + sudo useradd -m jenkins + echo "jenkins:jenkins" | sudo chpasswd + sudo keytool -importkeystore -srckeystore /etc/ssl/certs/java/cacerts -destkeystore /etc/ssl/certs/java/cacerts.jks -deststoretype JKS -srcstorepass changeit -deststorepass changeit -noprompt + sudo mv /etc/ssl/certs/java/cacerts.jks /etc/ssl/certs/java/cacerts + sudo /var/lib/dpkg/info/ca-certificates-java.postinst configure + SHELL +end diff --git a/exercises/10-distributed-builds/instructions.md b/exercises/10-distributed-builds/instructions.md new file mode 100644 index 0000000..8043a74 --- /dev/null +++ b/exercises/10-distributed-builds/instructions.md @@ -0,0 +1,17 @@ +# Exercise 10 + +You will start a VM using Vagrant as practice environment to set up a distributed build with 2 agents. + +## Configuring and Executing Jobs in a Distributed Build + +1. Go to "Manage Jenkins" > "Manage Nodes". You should see a single `master` node. +2. Configure the `master` node by setting the # of executor value to 0. That will take care of never using the `master` for job workload. +3. Have other physical or virtual machines ready that can act as agent nodes. Log into the machine as `root` user and create a new user named `jenkins` with the commands `useradd -d /var/lib/jenkins jenkins` and `passwd jenkins`. From the `master` node, copy the contents of the `id_rsa.key` file to the clipboard. Paste the contents of the clipboard to the file `/var/lib/jenkins/.ssh/authorized_keys`. +4. Add new nodes by clicking "New Nodes". Enter an appropriate name and select the option "Permanent Agent". Use the remote directory `/home/jenkins/jenkins_slave` and set the # of executors to 2. Enter the host and provide credentials by selecting "SSH Username with private key". To keep things easy select "Non verifying Verification Strategy". +4. Trigger a build. You should see that the build is only executed on the agent and not the `master` node. +5. Add at least one more agent. +6. Trigger a build. You should see that the build can be executed on any of the agents. +7. Configure one of the agents to only build jobs with a specific label e.g. `java`. Change the "Usage" field to "Only build jobs with label expressions matching this node". +8. Configure the `gradle-initializr` job and assign the label `java`. +9. Trigger a build of the `gradle-initializr` job. It is only executed by the dedicated agent. +10. Trigger other jobs that do not have the label `java` assigned to them. They are waiting for an agent that can execute the build. \ No newline at end of file diff --git a/exercises/10-distributed-builds/solution/images/agent-config.png b/exercises/10-distributed-builds/solution/images/agent-config.png new file mode 100644 index 0000000..df42c4a Binary files /dev/null and b/exercises/10-distributed-builds/solution/images/agent-config.png differ diff --git a/exercises/10-distributed-builds/solution/images/agent-label-config.png b/exercises/10-distributed-builds/solution/images/agent-label-config.png new file mode 100644 index 0000000..f44ffa5 Binary files /dev/null and b/exercises/10-distributed-builds/solution/images/agent-label-config.png differ diff --git a/exercises/10-distributed-builds/solution/images/build-for-label.png b/exercises/10-distributed-builds/solution/images/build-for-label.png new file mode 100644 index 0000000..a04673b Binary files /dev/null and b/exercises/10-distributed-builds/solution/images/build-for-label.png differ diff --git a/exercises/10-distributed-builds/solution/images/job-label-config.png b/exercises/10-distributed-builds/solution/images/job-label-config.png new file mode 100644 index 0000000..a19e32b Binary files /dev/null and b/exercises/10-distributed-builds/solution/images/job-label-config.png differ diff --git a/exercises/10-distributed-builds/solution/images/master-config.png b/exercises/10-distributed-builds/solution/images/master-config.png new file mode 100644 index 0000000..5feded6 Binary files /dev/null and b/exercises/10-distributed-builds/solution/images/master-config.png differ diff --git a/exercises/10-distributed-builds/solution/images/node-overview.png b/exercises/10-distributed-builds/solution/images/node-overview.png new file mode 100644 index 0000000..0dcb8c6 Binary files /dev/null and b/exercises/10-distributed-builds/solution/images/node-overview.png differ diff --git a/exercises/10-distributed-builds/solution/images/queued-job.png b/exercises/10-distributed-builds/solution/images/queued-job.png new file mode 100644 index 0000000..11edf76 Binary files /dev/null and b/exercises/10-distributed-builds/solution/images/queued-job.png differ diff --git a/exercises/10-distributed-builds/solution/solution.md b/exercises/10-distributed-builds/solution/solution.md new file mode 100644 index 0000000..44f4f98 --- /dev/null +++ b/exercises/10-distributed-builds/solution/solution.md @@ -0,0 +1,29 @@ +# Solution + +Configure the `master` node. + +![Master Configuration](./images/master-config.png) + +Add a new agent node. + +![Agent Configuration](./images/agent-config.png) + +You will see that the `master` node isn't even listed anymore in the executor overview. + +![Node Overview](./images/node-overview.png) + +Reconfigure the agent node to only build jobs with a specific label. + +![Agent Label Configuration](./images/agent-label-config.png) + +Reconfigure the job to only use agents that can handle a specific label. + +![Job Label Configuration](./images/job-label-config.png) + +A build of the job is now only handled by an agent with the assigned label. + +![Build For Labeled Agent](./images/build-for-label.png) + +Other jobs sit in a queue waiting for an agent that can handle the execution criteria. + +![Build For Labeled Agent](./images/queued-job.png) \ No newline at end of file diff --git a/exercises/11-pipeline-job/instructions.md b/exercises/11-pipeline-job/instructions.md new file mode 100644 index 0000000..d6b4d9c --- /dev/null +++ b/exercises/11-pipeline-job/instructions.md @@ -0,0 +1,14 @@ +# Exercise 11 + +In this exercise, you will set up a new multi-branch pipeline job and point it to an existing repository on GitHub. You will visualize the pipeline with the standard view and the Blue Ocean plugin. + +## Creating a Pipeline Job + +1. Have a look at the [repository](https://github.com/bmuschko/todo-spring-boot) `bmuschko/todo-spring-boot` on GitHub. The repository already contains the build definition in the form of a `Jenkinsfile`. Identify each of the steps. +2. In Jenkins, set up the credentials `SONARCLOUD_TOKEN` and `HEROKU_API_KEY` if they don't exist yet. +3. Create a new multi-branch pipeline job for the repository. +4. The initial build is triggered automatically. Have a look at the pipeline in the standard view and its console output. +5. Install the Blue Ocean plugin. +6. Open the Blue Ocean pipeline visualization for the job. Manually trigger the deployment step. Open a browser with the deployed application on Heroku. +7. Create a new branch named `bugfix` and push it to the remote repository. The code should be based off of `master`. +8. Select "Scan Multibranch Pipeline Now". The job should build the new branch. \ No newline at end of file diff --git a/exercises/11-pipeline-job/solution/images/blue-ocean-manual-step.png b/exercises/11-pipeline-job/solution/images/blue-ocean-manual-step.png new file mode 100644 index 0000000..62f4192 Binary files /dev/null and b/exercises/11-pipeline-job/solution/images/blue-ocean-manual-step.png differ diff --git a/exercises/11-pipeline-job/solution/images/blue-ocean-plugin.png b/exercises/11-pipeline-job/solution/images/blue-ocean-plugin.png new file mode 100644 index 0000000..1bd4249 Binary files /dev/null and b/exercises/11-pipeline-job/solution/images/blue-ocean-plugin.png differ diff --git a/exercises/11-pipeline-job/solution/images/bugfix-branch.png b/exercises/11-pipeline-job/solution/images/bugfix-branch.png new file mode 100644 index 0000000..83579ce Binary files /dev/null and b/exercises/11-pipeline-job/solution/images/bugfix-branch.png differ diff --git a/exercises/11-pipeline-job/solution/images/console-manual-step.png b/exercises/11-pipeline-job/solution/images/console-manual-step.png new file mode 100644 index 0000000..96be5b2 Binary files /dev/null and b/exercises/11-pipeline-job/solution/images/console-manual-step.png differ diff --git a/exercises/11-pipeline-job/solution/images/credentials.png b/exercises/11-pipeline-job/solution/images/credentials.png new file mode 100644 index 0000000..287c072 Binary files /dev/null and b/exercises/11-pipeline-job/solution/images/credentials.png differ diff --git a/exercises/11-pipeline-job/solution/images/finished-build.png b/exercises/11-pipeline-job/solution/images/finished-build.png new file mode 100644 index 0000000..20b64b5 Binary files /dev/null and b/exercises/11-pipeline-job/solution/images/finished-build.png differ diff --git a/exercises/11-pipeline-job/solution/images/job-only-master.png b/exercises/11-pipeline-job/solution/images/job-only-master.png new file mode 100644 index 0000000..3b9f68e Binary files /dev/null and b/exercises/11-pipeline-job/solution/images/job-only-master.png differ diff --git a/exercises/11-pipeline-job/solution/images/job-scm.png b/exercises/11-pipeline-job/solution/images/job-scm.png new file mode 100644 index 0000000..946dca9 Binary files /dev/null and b/exercises/11-pipeline-job/solution/images/job-scm.png differ diff --git a/exercises/11-pipeline-job/solution/images/multi-branch-pipeline-job.png b/exercises/11-pipeline-job/solution/images/multi-branch-pipeline-job.png new file mode 100644 index 0000000..7c2ab14 Binary files /dev/null and b/exercises/11-pipeline-job/solution/images/multi-branch-pipeline-job.png differ diff --git a/exercises/11-pipeline-job/solution/images/standard-pipeline.png b/exercises/11-pipeline-job/solution/images/standard-pipeline.png new file mode 100644 index 0000000..37518f8 Binary files /dev/null and b/exercises/11-pipeline-job/solution/images/standard-pipeline.png differ diff --git a/exercises/11-pipeline-job/solution/images/triggered-manual-step.png b/exercises/11-pipeline-job/solution/images/triggered-manual-step.png new file mode 100644 index 0000000..4644484 Binary files /dev/null and b/exercises/11-pipeline-job/solution/images/triggered-manual-step.png differ diff --git a/exercises/11-pipeline-job/solution/solution.md b/exercises/11-pipeline-job/solution/solution.md new file mode 100644 index 0000000..add10ec --- /dev/null +++ b/exercises/11-pipeline-job/solution/solution.md @@ -0,0 +1,97 @@ +# Solution + +The created credentials required for the job. + +![Credentials](./images/credentials.png) + +Create a new item from the dashboard. + +![Multi-Branch Pipeline Job](./images/multi-branch-pipeline-job.png) + +Point the job to the SCM. + +![Multi-Branch Pipeline Job](./images/job-scm.png) + +Upon pressing the OK button, the job will scan all available branches in the repository. + +```bash +Started +[Thu Jul 18 09:23:53 MDT 2019] Starting branch indexing... + > git --version # timeout=10 + > git ls-remote --symref git@github.com:bmuschko/todo-spring-boot.git # timeout=10 +Creating git repository in /Users/bmuschko/.jenkins/caches/git-a68a5e08a54549aaef01872e9adb6218 + > git init /Users/bmuschko/.jenkins/caches/git-a68a5e08a54549aaef01872e9adb6218 # timeout=10 +Setting origin to git@github.com:bmuschko/todo-spring-boot.git + > git config remote.origin.url git@github.com:bmuschko/todo-spring-boot.git # timeout=10 +Fetching & pruning origin... +Listing remote references... + > git config --get remote.origin.url # timeout=10 + > git --version # timeout=10 + > git ls-remote -h git@github.com:bmuschko/todo-spring-boot.git # timeout=10 +Fetching upstream changes from origin + > git config --get remote.origin.url # timeout=10 + > git fetch --tags --force --progress origin +refs/heads/*:refs/remotes/origin/* --prune +Checking branches... + Checking branch master + ‘Jenkinsfile’ found + Met criteria +Scheduled build for branch: master +Processed 1 branches +[Thu Jul 18 09:23:57 MDT 2019] Finished branch indexing. Indexing took 4 sec +Finished: SUCCESS +``` + +You can see the different stages of the pipeline in the standard view. + +![Standard Pipeline](./images/standard-pipeline.png) + +You can see the different stages of the pipeline in the standard view. + +![Standard Pipeline](./images/standard-pipeline.png) + +To console output allows for triggering or aborting the manual deployment step. + +![Console Manual Step](./images/console-manual-step.png) + +Install the Blue Ocean plugin. + +![Blue Ocean Plugin](./images/blue-ocean-plugin.png) + +The Blue Ocean pipeline view offers a UI element for triggering a manual step. + +![Blue Ocean Manual Step](./images/blue-ocean-manual-step.png) + +Press the button for processing with the manual step. + +![Blue Ocean Triggered Manual Step](./images/triggered-manual-step.png) + +The finished pipeline in Blue Ocean. + +![Blue Ocean Finished Pipeline](./images/finished-build.png) + +Check out the repository and push a new branch. + +```bash +$ git clone git@github.com:bmuschko/todo-spring-boot.git +Cloning into 'todo-spring-boot'... +remote: Enumerating objects: 230, done. +remote: Total 230 (delta 0), reused 0 (delta 0), pack-reused 230 +Receiving objects: 100% (230/230), 108.69 KiB | 452.00 KiB/s, done. +Resolving deltas: 100% (105/105), done. +$ cd todo-spring-boot +$ git branch bugfix +$ git checkout bugfix +Switched to branch 'bugfix' +$ git push origin bugfix +Total 0 (delta 0), reused 0 (delta 0) +remote: +remote: Create a pull request for 'bugfix' on GitHub by visiting: +remote: https://github.com/bmuschko/todo-spring-boot/pull/new/bugfix +remote: +To github.com:bmuschko/todo-spring-boot.git + * [new branch] bugfix -> bugfix +``` + +After scanning the repository, the new branch will be available and was triggered to build automatically. + +![Bugfix Branch](./images/bugfix-branch.png) \ No newline at end of file diff --git a/exercises/12-basic-jenkinsfile/instructions.md b/exercises/12-basic-jenkinsfile/instructions.md new file mode 100644 index 0000000..2b29603 --- /dev/null +++ b/exercises/12-basic-jenkinsfile/instructions.md @@ -0,0 +1,17 @@ +# Exercise 12 + +In this exercise, you'll create a declarative pipeline for a Go-based project. The `Jenkinsfile` will use some of the basic features of the pipeline DSL. + +## Writing a Basic Jenkinsfile + +1. Create a new GitHub repository named `go-on-jenkins`. +2. Create a new `Jenkinsfile` in the root directory of the repository as well as a simple `main.go` file. The main function just prints "Hello World!". Initialize the project via Go Modules. +3. Commit the files and push them to the remote repository. +4. Set up a new pipeline job for this repository in Jenkins. +5. Install the [Jenkins Go plugin](https://plugins.jenkins.io/golang). +6. Configure the latest Go runtime as global tool. +7. Enhance the `Jenkinsfile` based on the following requirements. The Jenkinsfile should use the declarative syntax. + * The job can run on all agents. + * The job sets the environment variable `GO111MODULES=on`. + * The job uses the Go runtime from the global tool definition. + * The job specifies one build stage named "Build". The build stage executes the shell command `go build`. \ No newline at end of file diff --git a/exercises/12-basic-jenkinsfile/solution/images/declarative-pipeline.png b/exercises/12-basic-jenkinsfile/solution/images/declarative-pipeline.png new file mode 100644 index 0000000..11f19da Binary files /dev/null and b/exercises/12-basic-jenkinsfile/solution/images/declarative-pipeline.png differ diff --git a/exercises/12-basic-jenkinsfile/solution/images/go-global-tool.png b/exercises/12-basic-jenkinsfile/solution/images/go-global-tool.png new file mode 100644 index 0000000..10602f1 Binary files /dev/null and b/exercises/12-basic-jenkinsfile/solution/images/go-global-tool.png differ diff --git a/exercises/12-basic-jenkinsfile/solution/images/go-plugin.png b/exercises/12-basic-jenkinsfile/solution/images/go-plugin.png new file mode 100644 index 0000000..ec62a6a Binary files /dev/null and b/exercises/12-basic-jenkinsfile/solution/images/go-plugin.png differ diff --git a/exercises/12-basic-jenkinsfile/solution/images/job-scm.png b/exercises/12-basic-jenkinsfile/solution/images/job-scm.png new file mode 100644 index 0000000..c93b695 Binary files /dev/null and b/exercises/12-basic-jenkinsfile/solution/images/job-scm.png differ diff --git a/exercises/12-basic-jenkinsfile/solution/images/new-job.png b/exercises/12-basic-jenkinsfile/solution/images/new-job.png new file mode 100644 index 0000000..ecb0b06 Binary files /dev/null and b/exercises/12-basic-jenkinsfile/solution/images/new-job.png differ diff --git a/exercises/12-basic-jenkinsfile/solution/solution.md b/exercises/12-basic-jenkinsfile/solution/solution.md new file mode 100644 index 0000000..08889c8 --- /dev/null +++ b/exercises/12-basic-jenkinsfile/solution/solution.md @@ -0,0 +1,54 @@ +# Solution + +Create a new job. + +![New Job](./images/new-job.png) + +Configure the appropriate SCM. + +![Job SCM](./images/job-scm.png) + +Install the Go plugin. + +![Go Plugin](./images/go-plugin.png) + +Configure a Go runtime as global tool. + +![Go Global Tool](./images/go-global-tool.png) + +The `main.go` file could similar to the one below. + +```go +package main + +import "fmt" + +func main() { + fmt.Println("hello world") +} +``` + +The final `Jenkinsfile` looks similar to the solution below. + +```groovy +pipeline { + agent any + tools { + go 'go-1.12' + } + environment { + GO111MODULE = 'on' + } + stages { + stage('Build') { + steps { + sh 'go build' + } + } + } +} +``` + +A build of the job installs the Go runtime and executes the build step. + +![Declarative Pipeline](./images/declarative-pipeline.png) \ No newline at end of file diff --git a/exercises/13-advanced-jenkinsfile/instructions.md b/exercises/13-advanced-jenkinsfile/instructions.md new file mode 100644 index 0000000..ae8501b --- /dev/null +++ b/exercises/13-advanced-jenkinsfile/instructions.md @@ -0,0 +1,20 @@ +# Exercise 13 + +We'll want to enhance the pipeline by additional stages and implement a release workflow. The project is going to use an external tool called [GoReleaser](https://goreleaser.com/) to publish cross-compiled artifacts to [GitHub Releases](https://help.github.com/en/github/administering-a-repository/creating-releases). The binaries should only be released if the commit has been tagged. + +## Enhancing a Pipeline With Advanced Features + +1. Add stage named `Test` that executes the Go `test` command. + * Add a build step that runs the shell command `go test ./...`. + * Generate code coverage metrics by adding the option `-coverprofile=coverage.txt` to the build step. + * Publish the code coverage metrics to CodeCov by sending a curl command `curl -s https://codecov.io/bash | bash -s -`. + * Log into [CodeCov](https://codecov.io/), determine the CodeCov token for the repository (aka Repository Upload Token) and set it up as credential in Jenkins. + * Retrieve the credential and set the value as environment variable named `CODECOV_TOKEN`. +2. Add a stage named `Code Analysis` that uses [golangci-lint](https://github.com/golangci/golangci-lint) to detect issues with the code. + * Add a build step for installing the `golangci-lint` with the shell command `curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.17.1` + * Add a build step that runs `golangci-lint` with the shell command `golangci-lint run`. +3. Add a stage named `Release` that uses [GoReleaser](https://github.com/goreleaser/goreleaser). + * Only build this step if the commit has been tagged. + * Set up a credential in Jenkins named `github_token` and set your GitHub token. + * Retrieve the credential and set the value as environment variable named `GITHUB_TOKEN`. + * Add a build step that runs the shell command `curl -sL https://git.io/goreleaser | bash`. \ No newline at end of file diff --git a/exercises/13-advanced-jenkinsfile/solution/images/codecov_token_credentials.png b/exercises/13-advanced-jenkinsfile/solution/images/codecov_token_credentials.png new file mode 100644 index 0000000..a1b813a Binary files /dev/null and b/exercises/13-advanced-jenkinsfile/solution/images/codecov_token_credentials.png differ diff --git a/exercises/13-advanced-jenkinsfile/solution/images/github_token_credentials.png b/exercises/13-advanced-jenkinsfile/solution/images/github_token_credentials.png new file mode 100644 index 0000000..2a70d40 Binary files /dev/null and b/exercises/13-advanced-jenkinsfile/solution/images/github_token_credentials.png differ diff --git a/exercises/13-advanced-jenkinsfile/solution/solution.md b/exercises/13-advanced-jenkinsfile/solution/solution.md new file mode 100644 index 0000000..f7557f6 --- /dev/null +++ b/exercises/13-advanced-jenkinsfile/solution/solution.md @@ -0,0 +1,50 @@ +# Solution + +Create the credentials for the CodeCov token. + +![CodeCov Credentials](./images/codecov_token_credentials.png) + +You can implement the "Test" stage as follows. + +```groovy +stage('Test') { + environment { + CODECOV_TOKEN = credentials('CODECOV_TOKEN') + } + steps { + sh 'go test ./... -coverprofile=coverage.txt' + sh "curl -s https://codecov.io/bash | bash -s -" + } +} +``` + +You can implement the "Code Analysis" stage as follows. + +```groovy +stage('Code Analysis') { + steps { + sh 'curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.17.1' + sh 'golangci-lint run' + } +} +``` + +Create the credentials for the GitHub token. + +![GitHub Credentials](./images/github_token_credentials.png) + +You can implement the "Release" stage as follows. + +```groovy +stage('Release') { + when { + buildingTag() + } + environment { + GITHUB_TOKEN = credentials('GITHUB_TOKEN') + } + steps { + sh 'curl -sL https://git.io/goreleaser | bash' + } +} +``` \ No newline at end of file diff --git a/exercises/14-shared-library/instructions.md b/exercises/14-shared-library/instructions.md new file mode 100644 index 0000000..301035f --- /dev/null +++ b/exercises/14-shared-library/instructions.md @@ -0,0 +1,13 @@ +# Exercise 14 + +In this exercise, you'll take the existing pipeline definition from the previous exercise and turn it into a reusable shared library. + +## Writing and Using a Shared Library + +1. Set up a new GitHub repository named `jenkins-standard-go-pipeline`. It will define a standard pipeline definition for Go projects implemented as shared library. +2. Add the file `vars/standard.groovy` that defines the declarative pipeline as global variable. Make the Go tool name and golang-ci version configurable with the help of parameters. +3. Push the changes to the `master` branch and configure the shared library in Jenkins. +4. Configure the shared library for consumption in Jenkins with the name `go-pipeline`. +5. Set up a new GitHub repository named `go-project-by-template`. Initialize a new Go project by running `go mod init github.com/bmuschko/hello-world` and adding a simple `main.go` file. +6. Add a new `Jenkinsfile`. Consume the shared library and call the global variable. +7. Trigger a build and visualize the pipeline in Jenkins. \ No newline at end of file diff --git a/exercises/14-shared-library/solution/images/pipeline-view.png b/exercises/14-shared-library/solution/images/pipeline-view.png new file mode 100644 index 0000000..e95697e Binary files /dev/null and b/exercises/14-shared-library/solution/images/pipeline-view.png differ diff --git a/exercises/14-shared-library/solution/images/shared-library-config.png b/exercises/14-shared-library/solution/images/shared-library-config.png new file mode 100644 index 0000000..bf3aea4 Binary files /dev/null and b/exercises/14-shared-library/solution/images/shared-library-config.png differ diff --git a/exercises/14-shared-library/solution/solution.md b/exercises/14-shared-library/solution/solution.md new file mode 100644 index 0000000..1423182 --- /dev/null +++ b/exercises/14-shared-library/solution/solution.md @@ -0,0 +1,87 @@ +# Solution + +The directory structure of shared library repository should have the following structure. + +``` +. +└── vars + └── standard.groovy + +1 directory, 1 file +``` + +Define the pipeline as global variable in the file `standard.groovy`. + +```groovy +def call(String goToolName = 'go-1.12', String golangCiVersion = 'v1.12.5') { + pipeline { + agent any + tools { + go "$goToolName" + } + environment { + GO111MODULE = 'on' + } + stages { + stage('Compile') { + steps { + sh 'go build' + } + } + stage('Test') { + environment { + CODECOV_TOKEN = credentials('CODECOV_TOKEN') + } + steps { + sh 'go test ./... -coverprofile=coverage.txt' + sh "curl -s https://codecov.io/bash | bash -s -" + } + } + stage('Code Analysis') { + steps { + sh "curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin $golangCiVersion" + sh 'golangci-lint run' + } + } + stage('Release') { + when { + buildingTag() + } + environment { + GITHUB_TOKEN = credentials('GITHUB_TOKEN') + } + steps { + sh 'curl -sL https://git.io/goreleaser | bash' + } + } + } + } +} +``` + +Configure the shared library under _Manage Jenkins > Configure System_. + +![Shared Library Configuration](./images/shared-library-config.png) + +The directory structure should look as shown below. + +``` +. +├── Jenkinsfile +├── go.mod +└── main.go + +0 directories, 3 files +``` + +The `Jenkinsfile` uses the shared library and calls the global variable. Optionally, you can configure the pipeline by passing in parameters. + +```groovy +@Library('go-pipeline') _ + +standard() +``` + +The resulting build will go through all the pipeline stages defined in the shared library. + +![Pipeline View](./images/pipeline-view.png) \ No newline at end of file diff --git a/prerequisites/instructions.md b/prerequisites/instructions.md new file mode 100644 index 0000000..e69de29