diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44929b5a..63bfc4af 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/README.md b/README.md
index b122e3ff..052010eb 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,6 @@ Jenkins Pipeline Shared library, that contains additional features for Git, Mave
-
- [Usage](#usage)
- [Syntax completion](#syntax-completion)
- [Maven](#maven)
@@ -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)
@@ -63,6 +64,10 @@ 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)
@@ -70,6 +75,8 @@ Jenkins Pipeline Shared library, that contains additional features for Git, Mave
- [findHostName](#findhostname)
- [isBuildSuccessful](#isbuildsuccessful)
- [findVulnerabilitiesWithTrivy](#findvulnerabilitieswithtrivy)
+ - [Simple examples](#simple-examples)
+ - [Ignore / allowlist](#ignore--allowlist)
- [Examples](#examples)
@@ -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
@@ -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.
@@ -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
@@ -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.
@@ -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
diff --git a/pom.xml b/pom.xml
index 0dcf5fb5..5326ae81 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,7 @@
com.cloudogu.cesces-build-libces-build-lib
- 2.4.0
+ 2.5.0
diff --git a/src/com/cloudogu/ces/cesbuildlib/Docker.groovy b/src/com/cloudogu/ces/cesbuildlib/Docker.groovy
index 6afcf60a..9937d91d 100644
--- a/src/com/cloudogu/ces/cesbuildlib/Docker.groovy
+++ b/src/com/cloudogu/ces/cesbuildlib/Docker.groovy
@@ -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
@@ -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()
}
@@ -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()
}
@@ -309,21 +309,26 @@ 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 "
@@ -331,15 +336,17 @@ class Docker implements Serializable {
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
}
@@ -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"
}
+
}
-}
\ No newline at end of file
+}
diff --git a/src/com/cloudogu/ces/cesbuildlib/Git.groovy b/src/com/cloudogu/ces/cesbuildlib/Git.groovy
index 05258679..5d62fe45 100644
--- a/src/com/cloudogu/ces/cesbuildlib/Git.groovy
+++ b/src/com/cloudogu/ces/cesbuildlib/Git.groovy
@@ -365,7 +365,7 @@ 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
@@ -373,7 +373,7 @@ class Git implements Serializable {
*/
void pull(String refSpec = '', String authorName = commitAuthorName, String authorEmail = commitAuthorEmail) {
withAuthorAndEmail(authorName, authorEmail) {
- executeGitWithCredentials "pull ${refSpec}"
+ executeGitWithCredentials "pull --rebase ${refSpec}"
}
}
diff --git a/src/com/cloudogu/ces/cesbuildlib/GradleInDockerBase.groovy b/src/com/cloudogu/ces/cesbuildlib/GradleInDockerBase.groovy
index c14ae382..0a69aab2 100644
--- a/src/com/cloudogu/ces/cesbuildlib/GradleInDockerBase.groovy
+++ b/src/com/cloudogu/ces/cesbuildlib/GradleInDockerBase.groovy
@@ -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
@@ -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()
+ }
}
}
diff --git a/src/com/cloudogu/ces/cesbuildlib/GradleWrapperInDocker.groovy b/src/com/cloudogu/ces/cesbuildlib/GradleWrapperInDocker.groovy
index 2da91def..573ad0ed 100644
--- a/src/com/cloudogu/ces/cesbuildlib/GradleWrapperInDocker.groovy
+++ b/src/com/cloudogu/ces/cesbuildlib/GradleWrapperInDocker.groovy
@@ -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
@@ -20,4 +21,5 @@ class GradleWrapperInDocker extends GradleInDockerBase {
gradlew(closure.call(), printStdOut)
}
}
+
}
diff --git a/src/com/cloudogu/ces/cesbuildlib/MavenInDocker.groovy b/src/com/cloudogu/ces/cesbuildlib/MavenInDocker.groovy
index a78b5d0d..fdb55c8f 100644
--- a/src/com/cloudogu/ces/cesbuildlib/MavenInDocker.groovy
+++ b/src/com/cloudogu/ces/cesbuildlib/MavenInDocker.groovy
@@ -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}"
+ }
}
diff --git a/src/com/cloudogu/ces/cesbuildlib/MavenInDockerBase.groovy b/src/com/cloudogu/ces/cesbuildlib/MavenInDockerBase.groovy
index f5ce23ef..d3bb65e9 100644
--- a/src/com/cloudogu/ces/cesbuildlib/MavenInDockerBase.groovy
+++ b/src/com/cloudogu/ces/cesbuildlib/MavenInDockerBase.groovy
@@ -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
@@ -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);
@@ -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()
+ }
}
-
}
diff --git a/src/com/cloudogu/ces/cesbuildlib/MavenWrapperInDocker.groovy b/src/com/cloudogu/ces/cesbuildlib/MavenWrapperInDocker.groovy
index 3385ae5f..75b8eb6b 100644
--- a/src/com/cloudogu/ces/cesbuildlib/MavenWrapperInDocker.groovy
+++ b/src/com/cloudogu/ces/cesbuildlib/MavenWrapperInDocker.groovy
@@ -12,9 +12,10 @@ class MavenWrapperInDocker extends MavenInDockerBase {
private String imageId
@SuppressWarnings("GrDeprecatedAPIUsage") // MavenWrapper will become protected constructor that is no longer deprecated
- MavenWrapperInDocker(script, String imageId) {
+ MavenWrapperInDocker(script, String imageId, String credentialsId = null ) {
super(script)
this.imageId = imageId
+ this.credentialsId = credentialsId
}
@Override
@@ -23,4 +24,4 @@ class MavenWrapperInDocker extends MavenInDockerBase {
mvnw(closure.call(), printStdOut)
}
}
-}
\ No newline at end of file
+}
diff --git a/test/com/cloudogu/ces/cesbuildlib/DockerMock.groovy b/test/com/cloudogu/ces/cesbuildlib/DockerMock.groovy
index 2736151f..84c9edb8 100644
--- a/test/com/cloudogu/ces/cesbuildlib/DockerMock.groovy
+++ b/test/com/cloudogu/ces/cesbuildlib/DockerMock.groovy
@@ -3,19 +3,23 @@ package com.cloudogu.ces.cesbuildlib
import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.Answer
-import static org.mockito.ArgumentMatchers.*
+import static org.mockito.ArgumentMatchers.any
+import static org.mockito.ArgumentMatchers.anyBoolean
+import static org.mockito.ArgumentMatchers.anyString
import static org.mockito.Mockito.mock
import static org.mockito.Mockito.when
class DockerMock {
-
- static Docker create(String imageTag = "") {
- Docker dockerMock = mock(Docker.class)
- Docker.Image imageMock = mock(Docker.Image.class)
+ Docker mock
+ Docker.Image imageMock
+
+ DockerMock(String imageTag = "") {
+ mock = mock(Docker.class)
+ imageMock = mock(Docker.Image.class)
if (imageTag == "") {
- when(dockerMock.image(anyString())).thenReturn(imageMock)
+ when(mock.image(anyString())).thenReturn(imageMock)
} else {
- when(dockerMock.image(imageTag)).thenReturn(imageMock)
+ when(mock.image(imageTag)).thenReturn(imageMock)
}
when(imageMock.mountJenkinsUser()).thenReturn(imageMock)
when(imageMock.mountJenkinsUser(anyBoolean())).thenReturn(imageMock)
@@ -28,6 +32,16 @@ class DockerMock {
closure.call()
}
})
- return dockerMock
+ when(mock.withRegistry(any(), any(), any())).thenAnswer(new Answer