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.ces ces-build-lib ces-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() { + @Override + Object answer(InvocationOnMock invocation) throws Throwable { + Closure closure = invocation.getArgument(2) + closure.call() + } + }) + } + + static Docker create(String imageTag = "") { + return new DockerMock(imageTag).mock } } diff --git a/test/com/cloudogu/ces/cesbuildlib/DockerTest.groovy b/test/com/cloudogu/ces/cesbuildlib/DockerTest.groovy index 5fdce8db..876f084c 100644 --- a/test/com/cloudogu/ces/cesbuildlib/DockerTest.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/DockerTest.groovy @@ -205,6 +205,24 @@ class DockerTest { assertEquals('expectedClosure', args[1].call()) } + @Test + void imageInsideWithAdditionalRunArgs() { + + Docker docker = createWithImage(mockedImageMethodInside()) + + docker.script += [ + env: [ + ADDITIONAL_DOCKER_RUN_ARGS: '-u 0:0' + ] + ] + + def args = docker.image(expectedImage).inside('') { return 'expectedClosure' } + + assert args[0].contains('-u 0:0') + assertEquals('expectedClosure', args[1].call()) + } + + @Test void imageInsideWithEntrypoint() { Docker docker = createWithImage(mockedImageMethodInside()) @@ -504,7 +522,9 @@ class DockerTest { }, pwd: { return expectedHome }, writeFile: { Map args -> actualWriteFileArgs.put(args['file'], args['text']) }, - error: { String arg -> throw new RuntimeException(arg) } + error: { String arg -> throw new RuntimeException(arg) + }, + env: [] ] return new Docker(mockedScript) diff --git a/test/com/cloudogu/ces/cesbuildlib/GitTest.groovy b/test/com/cloudogu/ces/cesbuildlib/GitTest.groovy index 61964ff1..15324f11 100644 --- a/test/com/cloudogu/ces/cesbuildlib/GitTest.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/GitTest.groovy @@ -255,7 +255,7 @@ class GitTest { @Test void pull() { - def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull' + def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull --rebase' scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, 0) scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name ') git = new Git(scriptMock, 'creds') @@ -405,7 +405,7 @@ class GitTest { void "pushAndPullOnFailure with empty refspec"() { def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push' scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, [1, 0]) - scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull', 0) + scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull --rebase', 0) scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name ') git = new Git(scriptMock, 'creds') @@ -416,7 +416,7 @@ class GitTest { assert scriptMock.actualShMapArgs.size() == 5 assert scriptMock.actualShMapArgs.get(2).trim() == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push' - assert scriptMock.actualShMapArgs.get(3).trim() == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull' + assert scriptMock.actualShMapArgs.get(3).trim() == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull --rebase' assert scriptMock.actualShMapArgs.get(4).trim() == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push' } @@ -424,7 +424,7 @@ class GitTest { void "pushAndPullOnFailure master"() { def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master' scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, [1, 0]) - scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull origin master', 0) + scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull --rebase origin master', 0) scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name ') git = new Git(scriptMock, 'creds') @@ -435,7 +435,7 @@ class GitTest { assert scriptMock.actualShMapArgs.size() == 5 assert scriptMock.actualShMapArgs.get(2) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master' - assert scriptMock.actualShMapArgs.get(3) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull origin master' + assert scriptMock.actualShMapArgs.get(3) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull --rebase origin master' assert scriptMock.actualShMapArgs.get(4) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master' } @@ -443,7 +443,7 @@ class GitTest { void "pushAndPullOnFailure origin master"() { def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master' scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, [1, 0]) - scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull origin master', 0) + scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull --rebase origin master', 0) scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name ') git = new Git(scriptMock, 'creds') @@ -454,7 +454,7 @@ class GitTest { assert scriptMock.actualShMapArgs.size() == 5 assert scriptMock.actualShMapArgs.get(2) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master' - assert scriptMock.actualShMapArgs.get(3) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull origin master' + assert scriptMock.actualShMapArgs.get(3) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull --rebase origin master' assert scriptMock.actualShMapArgs.get(4) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push origin master' } @@ -462,7 +462,7 @@ class GitTest { void "pushAndPullOnFailure upstream master"() { def expectedGitCommandWithCredentials = 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push upstream master' scriptMock.expectedShRetValueForScript.put(expectedGitCommandWithCredentials, [1, 0]) - scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull upstream master', 0) + scriptMock.expectedShRetValueForScript.put('git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull --rebase upstream master', 0) scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', 'User Name ') git = new Git(scriptMock, 'creds') @@ -473,7 +473,7 @@ class GitTest { assert scriptMock.actualShMapArgs.size() == 5 assert scriptMock.actualShMapArgs.get(2) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push upstream master' - assert scriptMock.actualShMapArgs.get(3) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull upstream master' + assert scriptMock.actualShMapArgs.get(3) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" pull --rebase upstream master' assert scriptMock.actualShMapArgs.get(4) == 'git -c credential.helper="!f() { echo username=\'$GIT_AUTH_USR\'; echo password=\'$GIT_AUTH_PSW\'; }; f" push upstream master' } diff --git a/test/com/cloudogu/ces/cesbuildlib/GradleInDockerBaseTest.groovy b/test/com/cloudogu/ces/cesbuildlib/GradleInDockerBaseTest.groovy new file mode 100644 index 00000000..872b5f4a --- /dev/null +++ b/test/com/cloudogu/ces/cesbuildlib/GradleInDockerBaseTest.groovy @@ -0,0 +1,76 @@ +package com.cloudogu.ces.cesbuildlib + +import org.junit.Test + +import static org.assertj.core.api.Assertions.assertThat +import static org.mockito.ArgumentMatchers.any +import static org.mockito.ArgumentMatchers.eq +import static org.mockito.Mockito.verify + +class GradleInDockerBaseTest { + + + static final IMAGE_ID = 'adoptopenjdk/openjdk11:jdk-11.0.1.13-alpine' + def scriptMock = new GradleWrapperInDockerBaseScriptMock() + DockerMock docker = new DockerMock(IMAGE_ID) + + static final ORIGINAL_USER_HOME = "/home/jenkins" + static final String EXPECTED_JENKINS_USER_FROM_ETC_PASSWD = + "jenkins:x:1000:1000:Jenkins,,,:" + ORIGINAL_USER_HOME + ":/bin/bash" + static final EXPECTED_GROUP_ID = "999" + static final EXPECTED_GROUP_FROM_ETC_GROUP = "docker:x:$EXPECTED_GROUP_ID:jenkins" + // Expected output of pwd, print working directory + static final EXPECTED_PWD = "/home/jenkins/workspaces/NAME" + static final SOME_WHITESPACES = " \n " + + @Test + void inDockerWithRegistry() { + def gradle = new GradleInDockerBaseForTest(scriptMock) + gradle.docker = docker.mock + scriptMock.expectedDefaultShRetValue = '' + gradle.credentialsId = 'myCreds' + boolean closureCalled = false + + gradle.inDocker(IMAGE_ID, { + closureCalled = true + }) + + assertThat(closureCalled).isTrue() + verify(docker.mock).withRegistry(eq("https://$IMAGE_ID".toString()), eq('myCreds'), any()) + } + + class GradleWrapperInDockerBaseScriptMock extends ScriptMock { + GradleWrapperInDockerBaseScriptMock() { + expectedPwd = EXPECTED_PWD + } + + @Override + String sh(Map params) { + super.sh(params) + // Add some whitespaces + String script = params.get("script") + if (script == "cat /etc/passwd | grep jenkins") { + return EXPECTED_JENKINS_USER_FROM_ETC_PASSWD + SOME_WHITESPACES + } else if (script == "cat /etc/group | grep docker") { + return EXPECTED_GROUP_FROM_ETC_GROUP + SOME_WHITESPACES + } else if (script.contains(EXPECTED_GROUP_FROM_ETC_GROUP)) { + return EXPECTED_GROUP_ID + } + "" + } + } + + class GradleInDockerBaseForTest extends GradleInDockerBase { + + GradleInDockerBaseForTest(script) { + super(script) + } + + def call(Closure closure, boolean printStdOut) { + inDocker(IMAGE_ID) { + closure.call() + } + } + } + +} diff --git a/test/com/cloudogu/ces/cesbuildlib/MavenInDockerBaseTest.groovy b/test/com/cloudogu/ces/cesbuildlib/MavenInDockerBaseTest.groovy index 7a920687..4c405b5a 100644 --- a/test/com/cloudogu/ces/cesbuildlib/MavenInDockerBaseTest.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/MavenInDockerBaseTest.groovy @@ -1,16 +1,19 @@ package com.cloudogu.ces.cesbuildlib +import org.junit.Before import org.junit.Test -import static junit.framework.TestCase.assertEquals -import static org.mockito.ArgumentMatchers.anyBoolean -import static org.mockito.Mockito.* +import static org.assertj.core.api.Assertions.assertThat +import static org.junit.Assert.assertEquals +import static org.mockito.ArgumentMatchers.any +import static org.mockito.ArgumentMatchers.eq +import static org.mockito.Mockito.verify class MavenInDockerBaseTest { static final ORIGINAL_USER_HOME = "/home/jenkins" static final String EXPECTED_JENKINS_USER_FROM_ETC_PASSWD = - "jenkins:x:1000:1000:Jenkins,,,:" + ORIGINAL_USER_HOME + ":/bin/bash" + "jenkins:x:1000:1000:Jenkins,,,:" + ORIGINAL_USER_HOME + ":/bin/bash" static final EXPECTED_GROUP_ID = "999" static final EXPECTED_GROUP_FROM_ETC_GROUP = "docker:x:$EXPECTED_GROUP_ID:jenkins" // Expected output of pwd, print working directory @@ -19,9 +22,15 @@ class MavenInDockerBaseTest { static final IMAGE_ID = 'maven:3.5.0-jdk8' def scriptMock = new MavenInDockerScriptMock() + DockerMock docker = new DockerMock(IMAGE_ID) - def mvn = new MavenInDockerTest(scriptMock) + def mvn = new MavenInDockerForTest(scriptMock) + @Before + void setup() { + mvn.docker = docker.mock + } + @Test void testCreateDockerRunArgsDefault() { assertEquals("", mvn.createDockerRunArgs()) @@ -30,17 +39,11 @@ class MavenInDockerBaseTest { @Test void testDockerHostEnabled() { mvn.enableDockerHost = true - Docker dockerMock = mock(Docker.class) - Docker.Image imageMock = mock(Docker.Image.class) - when(dockerMock.image(IMAGE_ID)).thenReturn(imageMock) - when(imageMock.mountJenkinsUser(anyBoolean())).thenReturn(imageMock) - when(imageMock.mountDockerSocket(anyBoolean())).thenReturn(imageMock) - mvn.docker = dockerMock mvn 'test' - verify(imageMock).mountDockerSocket(true) - verify(imageMock).mountJenkinsUser(true) + verify(docker.imageMock).mountDockerSocket(true) + verify(docker.imageMock).mountJenkinsUser(true) } @Test @@ -52,7 +55,21 @@ class MavenInDockerBaseTest { assert mvn.createDockerRunArgs().contains(expectedMavenRunArgs) assert scriptMock.actualShMapArgs.size() == 1 - assert scriptMock.actualShMapArgs.get(0) == 'mkdir -p $HOME/.m2' + assert scriptMock.actualShMapArgs.get(0) == 'mkdir -p $HOME/.m2' + } + + @Test + void inDockerWithRegistry() { + + mvn.credentialsId = 'myCreds' + boolean closureCalled = false + + mvn.inDocker(IMAGE_ID, { + closureCalled = true + }) + + assertThat(closureCalled).isTrue() + verify(docker.mock).withRegistry(eq("https://$IMAGE_ID".toString()), eq('myCreds'), any()) } class MavenInDockerScriptMock extends ScriptMock { @@ -61,7 +78,7 @@ class MavenInDockerBaseTest { } @Override - String sh(Map params) { + String sh(Map params) { super.sh(params) // Add some whitespaces String script = params.get("script") @@ -76,9 +93,9 @@ class MavenInDockerBaseTest { } } - class MavenInDockerTest extends MavenInDockerBase { + class MavenInDockerForTest extends MavenInDockerBase { - MavenInDockerTest(Object script) { + MavenInDockerForTest(Object script) { super(script) } diff --git a/test/com/cloudogu/ces/cesbuildlib/MavenInDockerTest.groovy b/test/com/cloudogu/ces/cesbuildlib/MavenInDockerTest.groovy index 86d68fe6..9bfdc7ea 100644 --- a/test/com/cloudogu/ces/cesbuildlib/MavenInDockerTest.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/MavenInDockerTest.groovy @@ -17,4 +17,14 @@ class MavenInDockerTest { assert scriptMock.actualShStringArgs[0].trim().contains('clean install') verify(docker).image('maven:3.5.0-jdk8') } -} \ No newline at end of file + + @Test + void customMavenImageTest() { + def mvn = new MavenInDocker(scriptMock, 'maven:latest') + Docker docker = setupDockerMock(mvn) + mvn 'clean install' + + verify(docker).image('maven:latest') + } + +} diff --git a/test/com/cloudogu/ces/cesbuildlib/ScriptMock.groovy b/test/com/cloudogu/ces/cesbuildlib/ScriptMock.groovy index 18c3c77f..783b6d47 100644 --- a/test/com/cloudogu/ces/cesbuildlib/ScriptMock.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/ScriptMock.groovy @@ -66,7 +66,7 @@ class ScriptMock { allActualArgs.add("called deleteDir()") } - String sh(Map args) { + String sh(Map args) { actualShMapArgs.add(args.script.toString()) allActualArgs.add(args.script.toString()) diff --git a/vars/findVulnerabilitiesWithTrivy.groovy b/vars/findVulnerabilitiesWithTrivy.groovy index d88f7c45..60241fb9 100644 --- a/vars/findVulnerabilitiesWithTrivy.groovy +++ b/vars/findVulnerabilitiesWithTrivy.groovy @@ -10,7 +10,7 @@ ArrayList call (Map args) { if(args.containsKey('allowList')) error "Arg allowList is deprecated, please use .trivyignore file" def imageName = args.imageName - def trivyVersion = args.trivyVersion ? args.trivyVersion : '0.41.0' + def trivyVersion = args.trivyVersion ? args.trivyVersion : '0.55.2' def severityFlag = args.severity ? "--severity=${args.severity.join(',')}" : '' def additionalFlags = args.additionalFlags ? args.additionalFlags : '' println(severityFlag)