Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
schnatterer committed Oct 14, 2024
2 parents f3fde9e + ef10879 commit 3987fcc
Show file tree
Hide file tree
Showing 18 changed files with 291 additions and 85 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [2.5.0](https://github.com/cloudogu/ces-build-lib/releases/tag/2.5.0) - 2024-10-14

### Added
- Custom maven image support
- Registry credentials for maven and gradle
- Additional Docker run args

### Changed
- `Git.pull()` uses rebase strategy to avoid error `fatal: Need to specify how to reconcile divergent branches.`
- Updated Trivy version

## [2.4.0](https://github.com/cloudogu/ces-build-lib/releases/tag/2.4.0) - 2024-09-18
### Changed
- Relicense to AGPL-3.0-only
Expand Down
33 changes: 26 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ Jenkins Pipeline Shared library, that contains additional features for Git, Mave
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Usage](#usage)
- [Syntax completion](#syntax-completion)
- [Maven](#maven)
Expand All @@ -21,9 +20,11 @@ Jenkins Pipeline Shared library, that contains additional features for Git, Mave
- [Advanced Maven in Docker features](#advanced-maven-in-docker-features)
- [Maven starts new containers](#maven-starts-new-containers)
- [Local repo](#local-repo)
- [Containers](#containers)
- [Without Containers](#without-containers)
- [Maven in Docker](#maven-in-docker-1)
- [Set image and credentials](#set-image-and-credentials)
- [Maven without Docker](#maven-without-docker)
- [Lazy evaluation / execute more steps inside container](#lazy-evaluation--execute-more-steps-inside-container)
- [Mirrors](#mirrors)
- [Repository Credentials](#repository-credentials)
- [Deploying to Nexus repository](#deploying-to-nexus-repository)
- [Deploying artifacts](#deploying-artifacts)
Expand Down Expand Up @@ -63,13 +64,19 @@ Jenkins Pipeline Shared library, that contains additional features for Git, Mave
- [K3d](#k3d)
- [DoguRegistry](#doguregistry)
- [Bats](#bats)
- [Makefile](#makefile)
- [Markdown](#markdown)
- [DockerLint (Deprecated)](#dockerlint-deprecated)
- [ShellCheck](#shellcheck)
- [Steps](#steps)
- [mailIfStatusChanged](#mailifstatuschanged)
- [isPullRequest](#ispullrequest)
- [findEmailRecipients](#findemailrecipients)
- [findHostName](#findhostname)
- [isBuildSuccessful](#isbuildsuccessful)
- [findVulnerabilitiesWithTrivy](#findvulnerabilitieswithtrivy)
- [Simple examples](#simple-examples)
- [Ignore / allowlist](#ignore--allowlist)
- [Examples](#examples)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Expand Down Expand Up @@ -253,10 +260,18 @@ mvn.useLocalRepoFromJenkins = true
This speed speeds up the first build and uses less memory.
However, concurrent builds of multi module projects building the same version (e.g. a SNAPSHOT), might overwrite their dependencies, causing non-deterministic build failures.
##### Set image and credentials
It is possible to set credentials for a registry login by setting a credentialsId and custom image with registry prefix.
```groovy
Maven mvn = new MavenInDocker(this, "3.5.0-jdk-8") // uses image: maven:3.5.0-jdk-8 from DockerHub
Maven mvn1 = new MavenInDocker(this, "mirror.gcr.io/maven:latest") // uses image: maven:latest from Google
Maven mvn2 = new MavenInDocker(this, "3.5.0-jdk-8" , credentialsId) // loads the username and password credentials from jenkins
```

##### Maven without Docker

The default is the default maven behavior `/home/jenkins/.m2` is used.
If you want to use a separate maven repo per Workspace (e.g. in order to avoid concurrent builds overwriting
If you want to use a separate maven repo per Workspace (e.g. to avoid concurrent builds overwriting
dependencies of multi module projects building the same version (e.g. a SNAPSHOT) the following will work:

```groovy
Expand Down Expand Up @@ -478,6 +493,8 @@ stage('Build') {
Since Oracle's announcement of shorter free JDK support, plenty of JDK images have appeared on public container image
registries, where `adoptopenjdk` is just one option. The choice is yours.

See [Maven in Docker](#set-image-and-credentials) for passing credentials to the registry.

# Git

An extension to the `git` step, that provides an API for some commonly used git commands and utilities.
Expand Down Expand Up @@ -652,7 +669,7 @@ Example from Jenkinsfile:
* `push(String tagName = image().parsedId.tag, boolean force = true)`: Pushes an image to the registry after tagging it
as with the tag method. For example, you can use `image().push 'latest'` to publish it as the latest version in its
repository.

## Additional features provided by the `Docker.Image` class

* `repoDigests()`: Returns the repo digests, a content addressable unique digest of an image that was pushed
Expand Down Expand Up @@ -685,8 +702,7 @@ Example from Jenkinsfile:
On this, however, [other people say](http://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/), you
should not do this at all. So lets stick to mounting the socket, which seems to cause less problems.

This is also used by [MavenInDocker](src/com/cloudogu/ces/cesbuildlib/MavenInDocker.groovy)

This is also used by [MavenInDocker](src/com/cloudogu/ces/cesbuildlib/MavenInDocker.groovy)
* `installDockerClient(String version)`: Installs the docker client with the specified version inside the container.
If no version parameter is passed, the lib tries to query the server version by calling `docker version`.
This can be called in addition to mountDockerSocket(), when the "docker" CLI is required on the PATH.
Expand Down Expand Up @@ -721,6 +737,9 @@ new Docker(this).image('kkarczmarczyk/node-yarn:8.0-wheezy')
sh 'docker run hello-world' // Would fail without mountDockerSocket = true & installDockerClient()
}
```
* If you should need to add addition arguments to `docker run` you can do so globally by setting
`ADDITIONAL_DOCKER_RUN_ARGS` as global properties at `https://your-jenkins/manage/configure#global-properties`.
This can be used to globally fix certain bugs in Jenkins agents or their docker config.

# Dockerfile

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<groupId>com.cloudogu.ces</groupId>
<artifactId>ces-build-lib</artifactId>
<name>ces-build-lib</name>
<version>2.4.0</version>
<version>2.5.0</version>


<properties>
Expand Down
40 changes: 24 additions & 16 deletions src/com/cloudogu/ces/cesbuildlib/Docker.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Docker implements Serializable {
String findIp(container) {
sh.returnStdOut "docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${container.id}"
}

/**
* @return the IP address in the current context: the docker host ip (when outside of a container) or the ip of the
* container this is running in
Expand Down Expand Up @@ -238,11 +238,11 @@ class Docker implements Serializable {
void tag(String tagName, boolean force) {
image().tag(tagName, force)
}

void tag(String tagName) {
image().tag(tagName)
}

void tag() {
image().tag()
}
Expand All @@ -254,11 +254,11 @@ class Docker implements Serializable {
void push(String tagName, boolean force) {
image().push(tagName, force)
}

void push(String tagName) {
image().push(tagName)
}

void push() {
image().push()
}
Expand Down Expand Up @@ -309,37 +309,44 @@ class Docker implements Serializable {
/**
* Returns the repo digests, a content addressable unique digest of an image that was pushed to or pulled from
* a repository.
*
*
* @return If the image was built locally and not pushed, returns an empty list.
* If the image was pulled from or pushed to a repo, returns a list containing one item.
* If the image was pulled from or pushed to multiple repos, might also contain more than one digest.
*/
List repoDigests() {
def split = sh.returnStdOut(
"docker image inspect ${imageIdString} -f '{{range .RepoDigests}}{{printf \"%s\\n\" .}}{{end}}'")
.split('\n')
"docker image inspect ${imageIdString} -f '{{range .RepoDigests}}{{printf \"%s\\n\" .}}{{end}}'")
.split('\n')
// Remove empty lines, e.g. the superflous last linebreak
return split - ''
return split - ''
}

private extendArgs(String args) {
String extendedArgs = args

if (script.env.ADDITIONAL_DOCKER_RUN_ARGS) {
extendedArgs += " ${script.env.ADDITIONAL_DOCKER_RUN_ARGS} "
}

if (mountJenkinsUser) {
String passwdPath = writePasswd()
extendedArgs += " -v ${script.pwd()}/${passwdPath}:/etc/passwd:ro "
}
if (mountDockerSocket) {
String groupPath = writeGroup()
extendedArgs +=
// Mount the docker socket
" -v /var/run/docker.sock:/var/run/docker.sock " +
// Mount the docker group
"-v ${script.pwd()}/${groupPath}:/etc/group:ro --group-add ${readDockerGroupId()} "
// Mount the docker socket
" -v /var/run/docker.sock:/var/run/docker.sock " +
// Mount the docker group
"-v ${script.pwd()}/${groupPath}:/etc/group:ro --group-add ${readDockerGroupId()} "
}
if (!dockerClientVersionToInstall.isEmpty()) {
doInstallDockerClient()
extendedArgs += " -v ${script.pwd()}/${DOCKER_CLIENT_PATH}/docker:/usr/bin/docker"
}


extendedArgs = workAroundEntrypointIssues(extendedArgs)
return extendedArgs
}
Expand Down Expand Up @@ -418,14 +425,15 @@ class Docker implements Serializable {
private void doInstallDockerClient() {
// Installs statically linked docker binary
String url = "https://download.docker.com/linux/static/stable/x86_64/docker-${dockerClientVersionToInstall}.tgz"

// Keep compatibility with old URLs
if (dockerClientVersionToInstall.matches('^(17|18.03|18.06).*') &&
!dockerClientVersionToInstall.endsWith('-ce')) {
!dockerClientVersionToInstall.endsWith('-ce')) {
url = url.replace('.tgz', '-ce.tgz')
}

script.sh "cd ${script.pwd()}/.jenkins && wget -qc ${url} -O - | tar -xz"
}

}
}
}
4 changes: 2 additions & 2 deletions src/com/cloudogu/ces/cesbuildlib/Git.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -365,15 +365,15 @@ class Git implements Serializable {
}

/**
* Pulls to local from remote repo.
* Pulls to local from remote repo, using the rebase strategy.
*
* @param refSpec branch or tag name
* @param authorName
* @param authorEmail
*/
void pull(String refSpec = '', String authorName = commitAuthorName, String authorEmail = commitAuthorEmail) {
withAuthorAndEmail(authorName, authorEmail) {
executeGitWithCredentials "pull ${refSpec}"
executeGitWithCredentials "pull --rebase ${refSpec}"
}
}

Expand Down
23 changes: 17 additions & 6 deletions src/com/cloudogu/ces/cesbuildlib/GradleInDockerBase.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ abstract class GradleInDockerBase extends Gradle {

/** Setting this to {@code true} allows the Gradle build to access the docker host, i.e. to start other containers.*/
boolean enableDockerHost = false
String credentialsId = null

Docker docker

Expand All @@ -16,19 +17,29 @@ abstract class GradleInDockerBase extends Gradle {

@Override
def gradle(String args, boolean printStdOut = true) {
call ({ args }, printStdOut)
call({ args }, printStdOut)
}

abstract def call(Closure closure, boolean printStdOut);

protected void inDocker(String imageId, Closure closure) {
if (this.credentialsId) {
docker.withRegistry("https://${imageId}", this.credentialsId) {
dockerImageBuilder(imageId, closure)
}
} else {
dockerImageBuilder(imageId, closure)
}
}

protected void dockerImageBuilder(String imageId , closure) {
docker.image(imageId)
// Mount user and set HOME, which results in the workspace being user.home. Otherwise '?' might be the user.home.
.mountJenkinsUser(true)
.mountDockerSocket(enableDockerHost)
.inside("") {
closure.call()
}
.mountJenkinsUser(true)
.mountDockerSocket(enableDockerHost)
.inside("") {
closure.call()
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ class GradleWrapperInDocker extends GradleInDockerBase {
private String imageId

@SuppressWarnings("GrDeprecatedAPIUsage") // GradleWrapper will become protected constructor that is no longer deprecated
GradleWrapperInDocker(script, String imageId) {
GradleWrapperInDocker(script, String imageId, String credentialsId = null) {
super(script)
this.imageId = imageId
this.credentialsId = credentialsId
}

@Override
Expand All @@ -20,4 +21,5 @@ class GradleWrapperInDocker extends GradleInDockerBase {
gradlew(closure.call(), printStdOut)
}
}

}
16 changes: 11 additions & 5 deletions src/com/cloudogu/ces/cesbuildlib/MavenInDocker.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,27 @@ package com.cloudogu.ces.cesbuildlib
class MavenInDocker extends MavenInDockerBase {

/** The version of the maven docker image to use, e.g. {@code maven:3.5.0-jdk-8} **/
String dockerBaseImageVersion
String mavenImage

/**
* @param script the Jenkinsfile instance ({@code this} in Jenkinsfile)
* @param dockerBaseImageVersion the version of the maven docker image to use, e.g. {@code 3.5.0-jdk-8}
* @param mavenImage the version of the maven docker image to use, e.g. {@code 3.5.0-jdk-8}
*/
MavenInDocker(script, String dockerBaseImageVersion) {
MavenInDocker(script, String mavenImage, String credentialsId = null) {
super(script)
this.dockerBaseImageVersion = dockerBaseImageVersion
this.mavenImage = mavenImage
this.credentialsId = credentialsId
}

@Override
def call(Closure closure, boolean printStdOut) {
inDocker("maven:$dockerBaseImageVersion") {
inDocker(getMavenImage()) {
sh("mvn ${createCommandLineArgs(closure.call())}", printStdOut)
}
}

//allowing downward compatibility for the old workflow only specifying the tag
def getMavenImage() {
return mavenImage.contains(':') ? mavenImage : "maven:${mavenImage}"
}
}
25 changes: 18 additions & 7 deletions src/com/cloudogu/ces/cesbuildlib/MavenInDockerBase.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package com.cloudogu.ces.cesbuildlib
*/
abstract class MavenInDockerBase extends Maven {

public String credentialsId = null

/** Setting this to {@code true} allows the maven build to access the docker host, i.e. to start other containers.*/
boolean enableDockerHost = false

Expand All @@ -22,7 +24,7 @@ abstract class MavenInDockerBase extends Maven {

@Override
def mvn(String args, boolean printStdOut = true) {
call ({ args }, printStdOut)
call({ args }, printStdOut)
}

abstract def call(Closure closure, boolean printStdOut);
Expand All @@ -43,13 +45,22 @@ abstract class MavenInDockerBase extends Maven {
}

protected void inDocker(String imageId, Closure closure) {
if (this.credentialsId) {
docker.withRegistry("https://${imageId}", this.credentialsId) {
dockerImageBuilder(imageId, closure)
}
} else {
dockerImageBuilder(imageId, closure)
}
}

protected void dockerImageBuilder(String imageId , closure) {
docker.image(imageId)
// Mount user and set HOME, which results in the workspace being user.home. Otherwise '?' might be the user.home.
.mountJenkinsUser(true)
.mountDockerSocket(enableDockerHost)
.inside(createDockerRunArgs()) {
closure.call()
}
.mountJenkinsUser(true)
.mountDockerSocket(enableDockerHost)
.inside(createDockerRunArgs()) {
closure.call()
}
}

}
Loading

0 comments on commit 3987fcc

Please sign in to comment.