mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-20 05:19:40 +02:00
Add step buildExecute (#527)
This step should serve as generic entry point in pipelines for building artifacts. Build principle: build once. Purpose of the step: - build using a defined build technology - store build result for future use in testing etc.
This commit is contained in:
parent
d84e81f88e
commit
5bb6d59753
23
documentation/docs/steps/buildExecute.md
Normal file
23
documentation/docs/steps/buildExecute.md
Normal file
@ -0,0 +1,23 @@
|
||||
# ${docGenStepName}
|
||||
|
||||
## ${docGenDescription}
|
||||
|
||||
## Prerequsites
|
||||
|
||||
When performing a Docker build you need to maintain the respective credentials in your Jenkins credentials store.<br />
|
||||
Further details
|
||||
|
||||
* for builds when a Docker deamon: see step [containerPushToRegistry](containerPushToRegistry.md)
|
||||
* for builds using Kaniko: see step [kanikoExecute](kanikoExecute.md)
|
||||
|
||||
## Example
|
||||
|
||||
```groovy
|
||||
buildExecute script:this, buildTool: 'maven'
|
||||
```
|
||||
|
||||
## ${docGenParameters}
|
||||
|
||||
## ${docGenConfiguration}
|
||||
|
||||
## ${docJenkinsPluginDependencies}
|
44
documentation/docs/steps/containerPushToRegistry.md
Normal file
44
documentation/docs/steps/containerPushToRegistry.md
Normal file
@ -0,0 +1,44 @@
|
||||
# ${docGenStepName}
|
||||
|
||||
## ${docGenDescription}
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You need to have a valid user with write permissions in the target docker registry.
|
||||
|
||||
Credentials for the target docker registry have been configured in Jenkins with a dedicated Id.
|
||||
|
||||
You can create the credentials in your Jenkins<br />
|
||||
via _Jenkins_ -> _Credentials_ -> _System_ -> _Global credentials (unrestricted)_ -> _Add Credentials_ ->
|
||||
|
||||
* Kind: _Username with Password_
|
||||
* ID: specify id which you then use for the configuration of `dockerCredentialsId` (see below)
|
||||
|
||||
## Example
|
||||
|
||||
Usage of pipeline step:
|
||||
|
||||
**OPTION A:** To pull a Docker image from an existing docker registry and push to a different docker registry:
|
||||
|
||||
```groovy
|
||||
containerPushToRegistry script: this,
|
||||
dockerCredentialsId: 'myTargetRegistryCredentials',
|
||||
sourceRegistryUrl: 'https://mysourceRegistry.url',
|
||||
sourceImage: 'path/to/mySourceImageWith:tag',
|
||||
dockerRegistryUrl: 'https://my.target.docker.registry:50000'
|
||||
```
|
||||
|
||||
**OPTION B:** To push a locally build docker image into the target registry (only possible when a Docker deamon is available on your Jenkins node):
|
||||
|
||||
```groovy
|
||||
containerPushToRegistry script: this,
|
||||
dockerCredentialsId: 'myTargetRegistryCredentials',
|
||||
dockerImage: 'path/to/myImageWith:tag',
|
||||
dockerRegistryUrl: 'https://my.target.docker.registry:50000'
|
||||
```
|
||||
|
||||
## ${docGenParameters}
|
||||
|
||||
## ${docGenConfiguration}
|
||||
|
||||
## ${docJenkinsPluginDependencies}
|
57
src/com/sap/piper/DockerUtils.groovy
Normal file
57
src/com/sap/piper/DockerUtils.groovy
Normal file
@ -0,0 +1,57 @@
|
||||
package com.sap.piper
|
||||
|
||||
class DockerUtils implements Serializable {
|
||||
|
||||
private static Script script
|
||||
|
||||
DockerUtils(Script script) {
|
||||
this.script = script
|
||||
}
|
||||
|
||||
public boolean withDockerDaemon() {
|
||||
def returnCode = script.sh script: 'docker ps -q > /dev/null', returnStatus: true
|
||||
return (returnCode == 0)
|
||||
}
|
||||
|
||||
public boolean onKubernetes() {
|
||||
return (Boolean.valueOf(script.env.ON_K8S))
|
||||
}
|
||||
|
||||
public String getRegistryFromUrl(dockerRegistryUrl) {
|
||||
URL url = new URL(dockerRegistryUrl)
|
||||
return "${url.getHost()}${(url.getPort() != -1) ? ':' + url.getPort() : ''}"
|
||||
}
|
||||
|
||||
public String getProtocolFromUrl(dockerRegistryUrl) {
|
||||
URL url = new URL(dockerRegistryUrl)
|
||||
return url.getProtocol()
|
||||
|
||||
//return dockerRegistryUrl.split(/:\/\//)[0]
|
||||
}
|
||||
|
||||
public void moveImage(Map source, Map target) {
|
||||
//expects source/target in the format [image: '', registryUrl: '', credentialsId: '']
|
||||
def sourceDockerRegistry = source.registryUrl ? "${getRegistryFromUrl(source.registryUrl)}/" : ''
|
||||
def sourceImageFullName = sourceDockerRegistry + source.image
|
||||
def targetDockerRegistry = target.registryUrl ? "${getRegistryFromUrl(target.registryUrl)}/" : ''
|
||||
def targetImageFullName = targetDockerRegistry + target.image
|
||||
|
||||
if (!withDockerDaemon()) {
|
||||
script.withCredentials([script.usernamePassword(
|
||||
credentialsId: target.credentialsId,
|
||||
passwordVariable: 'password',
|
||||
usernameVariable: 'userid'
|
||||
)]) {
|
||||
skopeoMoveImage(sourceImageFullName, targetImageFullName, script.userid, script.password)
|
||||
}
|
||||
}
|
||||
//else not yet implemented here - available directly via containerPushToRegistry
|
||||
|
||||
}
|
||||
|
||||
private void skopeoMoveImage(sourceImageFullName, targetImageFullName, targetUserId, targetPassword) {
|
||||
script.sh "skopeo copy --src-tls-verify=false --dest-tls-verify=false --dest-creds=${BashUtils.quoteAndEscape(targetUserId)}:${BashUtils.quoteAndEscape(targetPassword)} docker://${sourceImageFullName} docker://${targetImageFullName}"
|
||||
}
|
||||
|
||||
|
||||
}
|
230
test/groovy/BuildExecuteTest.groovy
Normal file
230
test/groovy/BuildExecuteTest.groovy
Normal file
@ -0,0 +1,230 @@
|
||||
#!groovy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
import org.junit.rules.RuleChain
|
||||
import util.BasePiperTest
|
||||
import util.JenkinsDockerExecuteRule
|
||||
import util.JenkinsReadYamlRule
|
||||
import util.JenkinsShellCallRule
|
||||
import util.JenkinsStepRule
|
||||
import util.Rules
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString
|
||||
import static org.hamcrest.CoreMatchers.hasItem
|
||||
import static org.hamcrest.CoreMatchers.is
|
||||
import static org.hamcrest.CoreMatchers.nullValue
|
||||
import static org.junit.Assert.assertThat
|
||||
|
||||
class BuildExecuteTest extends BasePiperTest {
|
||||
private ExpectedException exception = ExpectedException.none()
|
||||
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
|
||||
private JenkinsDockerExecuteRule dockerRule = new JenkinsDockerExecuteRule(this)
|
||||
private JenkinsShellCallRule shellCallRule = new JenkinsShellCallRule(this)
|
||||
|
||||
@Rule
|
||||
public RuleChain rules = Rules
|
||||
.getCommonRules(this)
|
||||
.around(new JenkinsReadYamlRule(this))
|
||||
.around(exception)
|
||||
.around(shellCallRule)
|
||||
.around(dockerRule)
|
||||
.around(stepRule)
|
||||
|
||||
def dockerMockArgs = [:]
|
||||
class DockerMock {
|
||||
DockerMock(name){
|
||||
dockerMockArgs.name = name
|
||||
}
|
||||
def build(image, options) {
|
||||
return [image: image, options: options]
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
void init() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultError() {
|
||||
exception.expectMessage(containsString('buildTool not set and no dockerImage & dockerCommand provided'))
|
||||
stepRule.step.buildExecute(
|
||||
script: nullScript,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultWithDockerImage() {
|
||||
stepRule.step.buildExecute(
|
||||
script: nullScript,
|
||||
dockerImage: 'path/to/myImage:tag',
|
||||
dockerCommand: 'myTestCommand'
|
||||
)
|
||||
assertThat(dockerRule.dockerParams.dockerImage, is('path/to/myImage:tag'))
|
||||
assertThat(shellCallRule.shell, hasItem('myTestCommand'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMaven() {
|
||||
def buildToolCalled = false
|
||||
helper.registerAllowedMethod('mavenExecute', [Map.class], {m ->
|
||||
buildToolCalled = true
|
||||
return
|
||||
})
|
||||
stepRule.step.buildExecute(
|
||||
script: nullScript,
|
||||
buildTool: 'maven',
|
||||
)
|
||||
assertThat(buildToolCalled, is(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMta() {
|
||||
def buildToolCalled = false
|
||||
helper.registerAllowedMethod('mtaBuild', [Map.class], {m ->
|
||||
buildToolCalled = true
|
||||
return
|
||||
})
|
||||
stepRule.step.buildExecute(
|
||||
script: nullScript,
|
||||
buildTool: 'mta',
|
||||
)
|
||||
assertThat(buildToolCalled, is(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNpm() {
|
||||
def buildToolCalled = false
|
||||
helper.registerAllowedMethod('npmExecute', [Map.class], {m ->
|
||||
buildToolCalled = true
|
||||
return
|
||||
})
|
||||
stepRule.step.buildExecute(
|
||||
script: nullScript,
|
||||
buildTool: 'npm',
|
||||
)
|
||||
assertThat(buildToolCalled, is(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDocker() {
|
||||
binding.setVariable('docker', new DockerMock('test'))
|
||||
def pushParams= [:]
|
||||
helper.registerAllowedMethod('containerPushToRegistry', [Map.class], {m ->
|
||||
pushParams = m
|
||||
return
|
||||
})
|
||||
stepRule.step.buildExecute(
|
||||
script: nullScript,
|
||||
buildTool: 'docker',
|
||||
dockerImageName: 'path/to/myImage',
|
||||
dockerImageTag: 'myTag',
|
||||
dockerRegistryUrl: 'https://my.registry:55555'
|
||||
)
|
||||
|
||||
assertThat(pushParams.dockerBuildImage.image.toString(), is('path/to/myImage:myTag'))
|
||||
assertThat(pushParams.dockerRegistryUrl.toString(), is('https://my.registry:55555'))
|
||||
assertThat(nullScript.commonPipelineEnvironment.getValue('containerImage').toString(), is('path/to/myImage:myTag'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDockerWithEnv() {
|
||||
nullScript.commonPipelineEnvironment.setArtifactVersion('1.0.0')
|
||||
binding.setVariable('docker', new DockerMock('test'))
|
||||
def pushParams= [:]
|
||||
helper.registerAllowedMethod('containerPushToRegistry', [Map.class], {m ->
|
||||
pushParams = m
|
||||
return
|
||||
})
|
||||
stepRule.step.buildExecute(
|
||||
script: nullScript,
|
||||
buildTool: 'docker',
|
||||
dockerImageName: 'path/to/myImage',
|
||||
dockerRegistryUrl: 'https://my.registry:55555'
|
||||
)
|
||||
|
||||
assertThat(pushParams.dockerBuildImage.image.toString(), is('path/to/myImage:1.0.0'))
|
||||
assertThat(pushParams.dockerRegistryUrl.toString(), is('https://my.registry:55555'))
|
||||
assertThat(nullScript.commonPipelineEnvironment.getValue('containerImage').toString(), is('path/to/myImage:1.0.0'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDockerNoPush() {
|
||||
binding.setVariable('docker', new DockerMock('test'))
|
||||
def pushParams= [:]
|
||||
helper.registerAllowedMethod('containerPushToRegistry', [Map.class], {m ->
|
||||
pushParams = m
|
||||
return
|
||||
})
|
||||
stepRule.step.buildExecute(
|
||||
script: nullScript,
|
||||
buildTool: 'docker',
|
||||
dockerImageName: 'path/to/myImage',
|
||||
dockerImageTag: 'myTag',
|
||||
dockerRegistryUrl: ''
|
||||
)
|
||||
|
||||
assertThat(pushParams.dockerBuildImage, nullValue())
|
||||
assertThat(pushParams.dockerRegistryUrl, nullValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
void testKaniko() {
|
||||
def kanikoParams = [:]
|
||||
helper.registerAllowedMethod('kanikoExecute', [Map.class], {m ->
|
||||
kanikoParams = m
|
||||
return
|
||||
})
|
||||
|
||||
stepRule.step.buildExecute(
|
||||
script: nullScript,
|
||||
buildTool: 'kaniko',
|
||||
dockerImageName: 'path/to/myImage',
|
||||
dockerImageTag: 'myTag',
|
||||
dockerRegistryUrl: 'https://my.registry:55555'
|
||||
)
|
||||
|
||||
assertThat(kanikoParams.containerImageNameAndTag.toString(), is('my.registry:55555/path/to/myImage:myTag'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testKanikoNoPush() {
|
||||
def kanikoParams = [:]
|
||||
helper.registerAllowedMethod('kanikoExecute', [Map.class], {m ->
|
||||
kanikoParams = m
|
||||
return
|
||||
})
|
||||
|
||||
stepRule.step.buildExecute(
|
||||
script: nullScript,
|
||||
buildTool: 'kaniko',
|
||||
dockerImageName: 'path/to/myImage',
|
||||
dockerImageTag: 'myTag',
|
||||
dockerRegistryUrl: ''
|
||||
)
|
||||
|
||||
assertThat(kanikoParams.containerImageNameAndTag, is(''))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSwitchToKaniko() {
|
||||
shellCallRule.setReturnValue('docker ps -q > /dev/null', 1)
|
||||
def kanikoParams = [:]
|
||||
helper.registerAllowedMethod('kanikoExecute', [Map.class], {m ->
|
||||
kanikoParams = m
|
||||
return
|
||||
})
|
||||
|
||||
stepRule.step.buildExecute(
|
||||
script: nullScript,
|
||||
buildTool: 'kaniko',
|
||||
dockerImageName: 'path/to/myImage',
|
||||
dockerImageTag: 'myTag',
|
||||
dockerRegistryUrl: 'https://my.registry:55555'
|
||||
)
|
||||
|
||||
assertThat(kanikoParams.containerImageNameAndTag.toString(), is('my.registry:55555/path/to/myImage:myTag'))
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@ -43,6 +44,11 @@ class ContainerExecuteStructureTestsTest extends BasePiperTest {
|
||||
})
|
||||
}
|
||||
|
||||
@After
|
||||
void cleanup() {
|
||||
nullScript.env = [ON_K8S: null]
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecuteContainterStructureTestsDefault() throws Exception {
|
||||
helper.registerAllowedMethod('readFile', [String.class], {s ->
|
||||
|
264
test/groovy/ContainerPushToRegistryTest.groovy
Normal file
264
test/groovy/ContainerPushToRegistryTest.groovy
Normal file
@ -0,0 +1,264 @@
|
||||
#!groovy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
import org.junit.rules.RuleChain
|
||||
import util.BasePiperTest
|
||||
import util.JenkinsCredentialsRule
|
||||
import util.JenkinsDockerExecuteRule
|
||||
import util.JenkinsReadYamlRule
|
||||
import util.JenkinsShellCallRule
|
||||
import util.JenkinsStepRule
|
||||
import util.Rules
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString
|
||||
import static org.hamcrest.CoreMatchers.hasItem
|
||||
import static org.hamcrest.CoreMatchers.is
|
||||
import static org.hamcrest.CoreMatchers.not
|
||||
import static org.junit.Assert.assertThat
|
||||
|
||||
class ContainerPushToRegistryTest extends BasePiperTest {
|
||||
private ExpectedException exception = ExpectedException.none()
|
||||
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
|
||||
private JenkinsDockerExecuteRule dockerRule = new JenkinsDockerExecuteRule(this)
|
||||
private JenkinsShellCallRule shellCallRule = new JenkinsShellCallRule(this)
|
||||
|
||||
@Rule
|
||||
public RuleChain rules = Rules
|
||||
.getCommonRules(this)
|
||||
.around(new JenkinsReadYamlRule(this))
|
||||
.around(exception)
|
||||
.around(shellCallRule)
|
||||
.around(dockerRule)
|
||||
.around(new JenkinsCredentialsRule(this)
|
||||
.withCredentials('testCredentialsId', 'registryUser', '********')
|
||||
)
|
||||
.around(stepRule)
|
||||
|
||||
def dockerMockArgs = [:]
|
||||
class DockerMock {
|
||||
DockerMock(name){
|
||||
dockerMockArgs.name = name
|
||||
}
|
||||
def withRegistry(paramRegistry, paramCredentials, paramClosure){
|
||||
dockerMockArgs.paramRegistry = paramRegistry
|
||||
dockerMockArgs.paramCredentials = paramCredentials
|
||||
return paramClosure()
|
||||
}
|
||||
def withRegistry(paramRegistry, paramClosure){
|
||||
dockerMockArgs.paramRegistryAnonymous = paramRegistry.toString()
|
||||
return paramClosure()
|
||||
}
|
||||
|
||||
def image(name) {
|
||||
dockerMockArgs.name = name
|
||||
return new ContainerImageMock()
|
||||
}
|
||||
}
|
||||
|
||||
def dockerMockPushes = []
|
||||
def dockerMockPull = false
|
||||
class ContainerImageMock {
|
||||
ContainerImageMock(){}
|
||||
def push(tag){
|
||||
dockerMockPushes.add(tag)
|
||||
}
|
||||
def push(){
|
||||
push('default')
|
||||
}
|
||||
|
||||
def pull(){
|
||||
dockerMockPull = true
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
void init() {
|
||||
binding.setVariable('docker', new DockerMock('test'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNoImageProvided() {
|
||||
exception.expectMessage(containsString('Please provide a dockerImage'))
|
||||
stepRule.step.containerPushToRegistry(
|
||||
script: nullScript,
|
||||
dockerRegistryUrl: 'https://testRegistry',
|
||||
dockerCredentialsId: 'testCredentialsId',
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefault() {
|
||||
stepRule.step.containerPushToRegistry(
|
||||
script: nullScript,
|
||||
dockerRegistryUrl: 'https://testRegistry',
|
||||
dockerCredentialsId: 'testCredentialsId',
|
||||
dockerImage: 'testImage:tag',
|
||||
)
|
||||
|
||||
assertThat(dockerMockArgs.paramRegistry, is('https://testRegistry'))
|
||||
assertThat(dockerMockArgs.paramCredentials, is('testCredentialsId'))
|
||||
assertThat(dockerMockArgs.name, is('testImage:tag'))
|
||||
assertThat(dockerMockPushes, hasItem('default'))
|
||||
assertThat(dockerMockPushes, not(hasItem('latest')))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuildImagePushLatest() {
|
||||
def dockerBuildImage = new ContainerImageMock()
|
||||
stepRule.step.containerPushToRegistry(
|
||||
script: nullScript,
|
||||
dockerRegistryUrl: 'https://testRegistry',
|
||||
dockerCredentialsId: 'testCredentialsId',
|
||||
dockerBuildImage: dockerBuildImage,
|
||||
tagLatest: true
|
||||
)
|
||||
|
||||
assertThat(dockerMockArgs.paramRegistry, is('https://testRegistry'))
|
||||
assertThat(dockerMockArgs.paramCredentials, is('testCredentialsId'))
|
||||
assertThat(dockerMockArgs.paramRegistryAnonymous, is(null))
|
||||
assertThat(dockerMockArgs.name, is('test'))
|
||||
assertThat(dockerMockPushes, hasItem('default'))
|
||||
assertThat(dockerMockPushes, hasItem('latest'))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
void testFromEnv() {
|
||||
nullScript.commonPipelineEnvironment.setValue('containerImage', 'path/testImage:tag')
|
||||
nullScript.commonPipelineEnvironment.setValue('containerRegistryUrl', 'https://testRegistry:55555')
|
||||
|
||||
stepRule.step.containerPushToRegistry(
|
||||
script: nullScript,
|
||||
dockerRegistryUrl: 'https://testRegistry',
|
||||
dockerCredentialsId: 'testCredentialsId',
|
||||
)
|
||||
|
||||
assertThat(dockerMockArgs.paramRegistryAnonymous, is('https://testRegistry:55555'))
|
||||
assertThat(dockerMockArgs.name, is('path/testImage:tag'))
|
||||
assertThat(shellCallRule.shell, hasItem('docker tag testRegistry:55555/path/testImage:tag path/testImage:tag'))
|
||||
assertThat(dockerMockPull, is(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithSourceImageAndRegistry() {
|
||||
stepRule.step.containerPushToRegistry(
|
||||
script: nullScript,
|
||||
dockerCredentialsId: 'testCredentialsId',
|
||||
dockerRegistryUrl: 'https://testRegistry',
|
||||
sourceImage: 'testSourceName:testSourceTag',
|
||||
sourceRegistryUrl: 'http://testSourceRegistry'
|
||||
)
|
||||
|
||||
assertThat(dockerMockArgs.paramRegistryAnonymous, is('http://testSourceRegistry'))
|
||||
assertThat(dockerMockArgs.name, is('testSourceName:testSourceTag'))
|
||||
assertThat(shellCallRule.shell, hasItem('docker tag testSourceRegistry/testSourceName:testSourceTag testSourceName:testSourceTag'))
|
||||
assertThat(dockerMockPull, is(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithSourceAndTarget() {
|
||||
stepRule.step.containerPushToRegistry(
|
||||
script: nullScript,
|
||||
dockerCredentialsId: 'testCredentialsId',
|
||||
dockerImage: 'testImage:tag',
|
||||
dockerRegistryUrl: 'https://testRegistry',
|
||||
sourceImage: 'testSourceName:testSourceTag',
|
||||
sourceRegistryUrl: 'http://testSourceRegistry'
|
||||
)
|
||||
|
||||
assertThat(dockerMockArgs.paramRegistryAnonymous, is('http://testSourceRegistry'))
|
||||
assertThat(dockerMockArgs.name, is('testSourceName:testSourceTag'))
|
||||
assertThat(shellCallRule.shell, hasItem('docker tag testSourceRegistry/testSourceName:testSourceTag testImage:tag'))
|
||||
assertThat(dockerMockPull, is(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testKubernetesMove() {
|
||||
binding.setVariable('docker', null)
|
||||
shellCallRule.setReturnValue('docker ps -q > /dev/null', 1)
|
||||
|
||||
stepRule.step.containerPushToRegistry(
|
||||
script: nullScript,
|
||||
dockerCredentialsId: 'testCredentialsId',
|
||||
dockerImage: 'testImage:tag',
|
||||
dockerRegistryUrl: 'https://my.registry:55555',
|
||||
skopeoImage: 'skopeo:latest',
|
||||
sourceImage: 'sourceImage:sourceTag',
|
||||
sourceRegistryUrl: 'https://my.source.registry:44444'
|
||||
)
|
||||
|
||||
assertThat(shellCallRule.shell, hasItem('skopeo copy --src-tls-verify=false --dest-tls-verify=false --dest-creds=\'registryUser\':\'********\' docker://my.source.registry:44444/sourceImage:sourceTag docker://my.registry:55555/testImage:tag'))
|
||||
assertThat(dockerRule.dockerParams.dockerImage, is('skopeo:latest'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testKubernetesMoveTagLatest() {
|
||||
binding.setVariable('docker', null)
|
||||
shellCallRule.setReturnValue('docker ps -q > /dev/null', 1)
|
||||
|
||||
stepRule.step.containerPushToRegistry(
|
||||
script: nullScript,
|
||||
dockerCredentialsId: 'testCredentialsId',
|
||||
dockerImage: 'testImage:tag',
|
||||
dockerRegistryUrl: 'https://my.registry:55555',
|
||||
sourceImage: 'sourceImage:sourceTag',
|
||||
sourceRegistryUrl: 'https://my.source.registry:44444',
|
||||
tagLatest: true
|
||||
)
|
||||
|
||||
assertThat(shellCallRule.shell, hasItem('skopeo copy --src-tls-verify=false --dest-tls-verify=false --dest-creds=\'registryUser\':\'********\' docker://my.source.registry:44444/sourceImage:sourceTag docker://my.registry:55555/testImage:tag'))
|
||||
assertThat(shellCallRule.shell, hasItem('skopeo copy --src-tls-verify=false --dest-tls-verify=false --dest-creds=\'registryUser\':\'********\' docker://my.source.registry:44444/sourceImage:sourceTag docker://my.registry:55555/testImage:latest'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testKubernetesSourceOnly() {
|
||||
binding.setVariable('docker', null)
|
||||
shellCallRule.setReturnValue('docker ps -q > /dev/null', 1)
|
||||
|
||||
stepRule.step.containerPushToRegistry(
|
||||
script: nullScript,
|
||||
dockerCredentialsId: 'testCredentialsId',
|
||||
dockerRegistryUrl: 'https://my.registry:55555',
|
||||
sourceImage: 'sourceImage:sourceTag',
|
||||
sourceRegistryUrl: 'https://my.source.registry:44444'
|
||||
)
|
||||
|
||||
assertThat(shellCallRule.shell, hasItem('skopeo copy --src-tls-verify=false --dest-tls-verify=false --dest-creds=\'registryUser\':\'********\' docker://my.source.registry:44444/sourceImage:sourceTag docker://my.registry:55555/sourceImage:sourceTag'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testKubernetesSourceRegistryFromEnv() {
|
||||
binding.setVariable('docker', null)
|
||||
shellCallRule.setReturnValue('docker ps -q > /dev/null', 1)
|
||||
|
||||
nullScript.commonPipelineEnvironment.setValue('containerImage', 'sourceImage:sourceTag')
|
||||
nullScript.commonPipelineEnvironment.setValue('containerRegistryUrl', 'https://my.source.registry:44444')
|
||||
|
||||
stepRule.step.containerPushToRegistry(
|
||||
script: nullScript,
|
||||
dockerCredentialsId: 'testCredentialsId',
|
||||
dockerRegistryUrl: 'https://my.registry:55555',
|
||||
sourceImage: 'sourceImage:sourceTag',
|
||||
)
|
||||
|
||||
assertThat(shellCallRule.shell, hasItem('skopeo copy --src-tls-verify=false --dest-tls-verify=false --dest-creds=\'registryUser\':\'********\' docker://my.source.registry:44444/sourceImage:sourceTag docker://my.registry:55555/sourceImage:sourceTag'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testKubernetesPushTar() {
|
||||
binding.setVariable('docker', null)
|
||||
shellCallRule.setReturnValue('docker ps -q > /dev/null', 1)
|
||||
|
||||
exception.expectMessage('Only moving images')
|
||||
stepRule.step.containerPushToRegistry(
|
||||
script: nullScript,
|
||||
dockerCredentialsId: 'testCredentialsId',
|
||||
dockerArchive: 'myImage.tar',
|
||||
dockerImage: 'testImage:tag',
|
||||
dockerRegistryUrl: 'https://my.registry:55555',
|
||||
)
|
||||
}
|
||||
}
|
97
test/groovy/com/sap/piper/DockerUtilsTest.groovy
Normal file
97
test/groovy/com/sap/piper/DockerUtilsTest.groovy
Normal file
@ -0,0 +1,97 @@
|
||||
package com.sap.piper
|
||||
|
||||
import hudson.AbortException
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
import org.junit.rules.RuleChain
|
||||
import util.BasePiperTest
|
||||
import util.JenkinsCredentialsRule
|
||||
import util.JenkinsShellCallRule
|
||||
import util.Rules
|
||||
|
||||
import static org.hamcrest.CoreMatchers.hasItem
|
||||
import static org.hamcrest.CoreMatchers.is
|
||||
import static org.junit.Assert.assertThat
|
||||
|
||||
class DockerUtilsTest extends BasePiperTest {
|
||||
|
||||
public ExpectedException exception = ExpectedException.none()
|
||||
public JenkinsShellCallRule shellCallRule = new JenkinsShellCallRule(this)
|
||||
|
||||
def dockerMockArgs = [:]
|
||||
class DockerMock {
|
||||
def withRegistry(paramRegistry, paramClosure){
|
||||
dockerMockArgs.paramRegistryAnonymous = paramRegistry.toString()
|
||||
return paramClosure()
|
||||
}
|
||||
}
|
||||
|
||||
@Rule
|
||||
public RuleChain ruleChain = Rules.getCommonRules(this)
|
||||
.around(shellCallRule)
|
||||
.around(exception)
|
||||
.around(new JenkinsCredentialsRule(this)
|
||||
.withCredentials('testCredentialsId', 'registryUser', '********')
|
||||
)
|
||||
@Before
|
||||
void init() {
|
||||
nullScript.binding.setVariable('docker', new DockerMock())
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithDockerDaemon() {
|
||||
DockerUtils dockerUtils = new DockerUtils(nullScript)
|
||||
assertThat(dockerUtils.withDockerDaemon(), is(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithoutDockerDaemon() {
|
||||
shellCallRule.setReturnValue('docker ps -q > /dev/null', 1)
|
||||
DockerUtils dockerUtils = new DockerUtils(nullScript)
|
||||
assertThat(dockerUtils.withDockerDaemon(), is(false))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOnKubernetes() {
|
||||
nullScript.env.ON_K8S = 'true'
|
||||
DockerUtils dockerUtils = new DockerUtils(nullScript)
|
||||
assertThat(dockerUtils.onKubernetes(), is(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMoveImageKubernetes() {
|
||||
shellCallRule.setReturnValue('docker ps -q > /dev/null', 1)
|
||||
DockerUtils dockerUtils = new DockerUtils(nullScript)
|
||||
dockerUtils.moveImage(
|
||||
[
|
||||
registryUrl: 'https://my.source.registry:44444',
|
||||
image: 'sourceImage:sourceTag'
|
||||
],
|
||||
[
|
||||
registryUrl: 'https://my.registry:55555',
|
||||
image: 'testImage:tag',
|
||||
credentialsId: 'testCredentialsId'
|
||||
]
|
||||
)
|
||||
|
||||
assertThat(shellCallRule.shell, hasItem('skopeo copy --src-tls-verify=false --dest-tls-verify=false --dest-creds=\'registryUser\':\'********\' docker://my.source.registry:44444/sourceImage:sourceTag docker://my.registry:55555/testImage:tag'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetRegistryFromUrl() {
|
||||
DockerUtils dockerUtils = new DockerUtils(nullScript)
|
||||
assertThat(dockerUtils.getRegistryFromUrl('https://my.registry.com:55555'), is('my.registry.com:55555'))
|
||||
assertThat(dockerUtils.getRegistryFromUrl('http://my.registry.com:55555'), is('my.registry.com:55555'))
|
||||
assertThat(dockerUtils.getRegistryFromUrl('https://my.registry.com'), is('my.registry.com'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetProtocolFromUrl() {
|
||||
DockerUtils dockerUtils = new DockerUtils(nullScript)
|
||||
assertThat(dockerUtils.getProtocolFromUrl('https://my.registry.com:55555'), is('https'))
|
||||
assertThat(dockerUtils.getProtocolFromUrl('http://my.registry.com:55555'), is('http'))
|
||||
}
|
||||
}
|
117
vars/buildExecute.groovy
Normal file
117
vars/buildExecute.groovy
Normal file
@ -0,0 +1,117 @@
|
||||
import com.sap.piper.DockerUtils
|
||||
import com.sap.piper.GenerateDocumentation
|
||||
import com.sap.piper.Utils
|
||||
import com.sap.piper.ConfigurationHelper
|
||||
|
||||
import groovy.text.SimpleTemplateEngine
|
||||
import groovy.transform.Field
|
||||
|
||||
import static com.sap.piper.Prerequisites.checkScript
|
||||
|
||||
@Field String STEP_NAME = getClass().getName()
|
||||
@Field Set GENERAL_CONFIG_KEYS = [
|
||||
/**
|
||||
* Defines the tool used for the build.
|
||||
* @possibleValues `docker`, `kaniko`, `maven`, `mta`, `npm`
|
||||
*/
|
||||
'buildTool',
|
||||
/** For Docker builds only (mandatory): name of the image to be built. */
|
||||
'dockerImageName',
|
||||
/** For Docker builds only: Defines the registry url where the image should be pushed to, incl. the protocol like `https://my.registry.com`. If it is not defined, image will not be pushed to a registry.*/
|
||||
'dockerRegistryUrl',
|
||||
]
|
||||
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
|
||||
|
||||
/** Only for Docker builds on the local deamon: Defines the build options for the build.*/
|
||||
'containerBuildOptions',
|
||||
/** For custom build types: Defines the command to be executed within the `dockerImage` in order to execute the build. */
|
||||
'dockerCommand',
|
||||
/** For custom build types: Image to be used for builds in case they should run inside a custom Docker container */
|
||||
'dockerImage',
|
||||
/** For Docker builds only (mandatory): tag of the image to be built. */
|
||||
'dockerImageTag',
|
||||
])
|
||||
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
|
||||
|
||||
/**
|
||||
* This step serves as generic entry point in pipelines for building artifacts.
|
||||
*
|
||||
* You can use pre-defined `buildTool`s.
|
||||
*
|
||||
* Alternatively you can define a command via `dockerCommand` which should be executed in `dockerImage`.<br />
|
||||
* This allows you to trigger any build tool using a defined Docker container which provides the required build infrastructure.
|
||||
*
|
||||
* When using `buildTool: docker` or `buildTool: kaniko` the created container image is uploaded to a container registry.<br />
|
||||
* You need to make sure that the required credentials are provided to the step.
|
||||
*
|
||||
* For all other `buildTool`s the artifact will just be stored in the workspace and could then be `stash`ed for later use.
|
||||
*
|
||||
*/
|
||||
@GenerateDocumentation
|
||||
void call(Map parameters = [:]) {
|
||||
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) {
|
||||
final script = checkScript(this, parameters) ?: this
|
||||
def utils = parameters.juStabUtils ?: new Utils()
|
||||
// handle deprecated parameters
|
||||
// load default & individual configuration
|
||||
Map config = ConfigurationHelper.newInstance(this)
|
||||
.loadStepDefaults()
|
||||
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
|
||||
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
|
||||
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS)
|
||||
.mixin(parameters, PARAMETER_KEYS)
|
||||
.addIfEmpty('dockerImageTag', script.commonPipelineEnvironment.getArtifactVersion())
|
||||
.use()
|
||||
|
||||
// telemetry reporting
|
||||
utils.pushToSWA([stepParam1: config.buildTool, 'buildTool': config.buildTool], config)
|
||||
|
||||
switch(config.buildTool){
|
||||
case 'maven':
|
||||
mavenExecute script: script
|
||||
break
|
||||
case 'mta':
|
||||
mtaBuild script: script
|
||||
break
|
||||
case 'npm':
|
||||
npmExecute script: script
|
||||
break
|
||||
case ['docker', 'kaniko']:
|
||||
DockerUtils dockerUtils = new DockerUtils(script)
|
||||
if (config.buildTool == 'docker' && !dockerUtils.withDockerDaemon()) {
|
||||
config.buildTool = 'kaniko'
|
||||
echo "[${STEP_NAME}] No Docker daemon available, thus switching to Kaniko build"
|
||||
}
|
||||
|
||||
ConfigurationHelper.newInstance(this, config)
|
||||
.withMandatoryProperty('dockerImageName')
|
||||
.withMandatoryProperty('dockerImageTag')
|
||||
|
||||
def dockerImageNameAndTag = "${config.dockerImageName}:${config.dockerImageTag}"
|
||||
|
||||
if (config.buildTool == 'kaniko') {
|
||||
def containerImageNameAndTag = config.dockerRegistryUrl ? "${dockerUtils.getRegistryFromUrl(config.dockerRegistryUrl)}/${dockerImageNameAndTag}" : ''
|
||||
kanikoExecute script: script, containerImageNameAndTag: containerImageNameAndTag
|
||||
} else {
|
||||
def dockerBuildImage = docker.build(dockerImageNameAndTag, "${config.containerBuildOptions} .")
|
||||
//only push if registry is defined
|
||||
if (config.dockerRegistryUrl) {
|
||||
containerPushToRegistry script: this, dockerBuildImage: dockerBuildImage, dockerRegistryUrl: config.dockerRegistryUrl
|
||||
}
|
||||
}
|
||||
script.commonPipelineEnvironment.setValue('containerImage', dockerImageNameAndTag)
|
||||
break
|
||||
default:
|
||||
if (config.dockerImage && config.dockerCommand) {
|
||||
dockerExecute(
|
||||
script: script,
|
||||
dockerImage: config.dockerImage,
|
||||
) {
|
||||
sh "${config.dockerCommand}"
|
||||
}
|
||||
} else {
|
||||
error "[${STEP_NAME}] buildTool not set and no dockerImage & dockerCommand provided."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
130
vars/containerPushToRegistry.groovy
Normal file
130
vars/containerPushToRegistry.groovy
Normal file
@ -0,0 +1,130 @@
|
||||
import com.sap.piper.GenerateDocumentation
|
||||
import com.sap.piper.Utils
|
||||
import com.sap.piper.ConfigurationHelper
|
||||
import com.sap.piper.DockerUtils
|
||||
import groovy.transform.Field
|
||||
|
||||
import static com.sap.piper.Prerequisites.checkScript
|
||||
|
||||
@Field String STEP_NAME = getClass().getName()
|
||||
@Field Set GENERAL_CONFIG_KEYS = [
|
||||
/**
|
||||
* Defines the id of the Jenkins username/password credentials containing the credentials for the target Docker registry.
|
||||
*/
|
||||
'dockerCredentialsId',
|
||||
/** Defines the registry url where the image should be pushed to, incl. the protocol like `https://my.registry.com`*/
|
||||
'dockerRegistryUrl',
|
||||
]
|
||||
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
|
||||
/** Not supported yet - Docker archive to be pushed to registry*/
|
||||
'dockerArchive',
|
||||
/** For images built locally on the Docker Deamon, reference to the image object resulting from `docker.build` execution */
|
||||
'dockerBuildImage',
|
||||
/** Defines the name (incl. tag) of the target image*/
|
||||
'dockerImage',
|
||||
/**
|
||||
* Only if no Docker daemon available on your Jenkins image: Docker image to be used for [Skopeo](https://github.com/containers/skopeo) calls
|
||||
* Unfortunately no proper image known to be available.
|
||||
* Simple custom Dockerfile could look as follows: <br>
|
||||
* ```
|
||||
* FROM fedora:29
|
||||
* RUN dnf install -y skopeo
|
||||
* ```
|
||||
*/
|
||||
'skopeoImage',
|
||||
/** Defines the name (incl. tag) of the source image to be pushed to a new image defined in `dockerImage`.<br>
|
||||
* This is helpful for moving images from one location to another.
|
||||
*/
|
||||
'sourceImage',
|
||||
/** Defines a registry url from where the image should optionally be pulled from, incl. the protocol like `https://my.registry.com`*/
|
||||
'sourceRegistryUrl',
|
||||
/** Defines if the image should be tagged as `latest`*/
|
||||
'tagLatest'
|
||||
])
|
||||
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
|
||||
|
||||
/**
|
||||
* This step allows you to push a Docker image into a dedicated Container registry.
|
||||
*
|
||||
* By default an image available via the local Docker daemon will be pushed.
|
||||
*
|
||||
* In case you want to pull an existing image from a remote container registry, a source image and source registry needs to be specified.<br />
|
||||
* This makes it possible to move an image from one registry to another.
|
||||
*/
|
||||
@GenerateDocumentation
|
||||
void call(Map parameters = [:]) {
|
||||
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) {
|
||||
final script = checkScript(this, parameters) ?: this
|
||||
|
||||
// load default & individual configuration
|
||||
Map config = ConfigurationHelper.newInstance(this)
|
||||
.loadStepDefaults()
|
||||
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
|
||||
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
|
||||
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS)
|
||||
.mixin(parameters, PARAMETER_KEYS)
|
||||
.addIfEmpty('sourceImage', script.commonPipelineEnvironment.getValue('containerImage'))
|
||||
.addIfEmpty('sourceRegistryUrl', script.commonPipelineEnvironment.getValue('containerRegistryUrl'))
|
||||
.withMandatoryProperty('dockerCredentialsId')
|
||||
.withMandatoryProperty('dockerRegistryUrl')
|
||||
.use()
|
||||
|
||||
DockerUtils dockerUtils = new DockerUtils(script)
|
||||
|
||||
if (config.sourceRegistryUrl) {
|
||||
config.sourceRegistry = dockerUtils.getRegistryFromUrl(config.sourceRegistryUrl)
|
||||
}
|
||||
|
||||
// telemetry reporting
|
||||
new Utils().pushToSWA([
|
||||
step: STEP_NAME
|
||||
], config)
|
||||
|
||||
if (!config.dockerImage)
|
||||
config.dockerImage = config.sourceImage
|
||||
|
||||
if (dockerUtils.withDockerDaemon()) {
|
||||
|
||||
//Prevent NullPointerException in case no dockerImage nor dockerBuildImage is provided
|
||||
if (!config.dockerImage && !config.dockerBuildImage) {
|
||||
error "[${STEP_NAME}] Please provide a dockerImage (either in your config.yml or via step parameter)."
|
||||
}
|
||||
config.dockerBuildImage = config.dockerBuildImage?:docker.image(config.dockerImage)
|
||||
|
||||
if (config.sourceRegistry && config.sourceImage) {
|
||||
|
||||
def sourceBuildImage = docker.image(config.sourceImage)
|
||||
docker.withRegistry(config.sourceRegistryUrl) {
|
||||
sourceBuildImage.pull()
|
||||
}
|
||||
sh "docker tag ${config.sourceRegistry}/${config.sourceImage} ${config.dockerImage}"
|
||||
}
|
||||
|
||||
docker.withRegistry(
|
||||
config.dockerRegistryUrl,
|
||||
config.dockerCredentialsId
|
||||
) {
|
||||
config.dockerBuildImage.push()
|
||||
if (config.tagLatest)
|
||||
config.dockerBuildImage.push('latest')
|
||||
}
|
||||
} else {
|
||||
//handling for Kubernetes case
|
||||
dockerExecute(
|
||||
script: script,
|
||||
dockerImage: config.skopeoImage
|
||||
) {
|
||||
|
||||
if (!config.dockerArchive && !config.dockerBuildImage) {
|
||||
dockerUtils.moveImage([image: config.sourceImage, registryUrl: config.sourceRegistryUrl], [image: config.dockerImage, registryUrl: config.dockerRegistryUrl, credentialsId: config.dockerCredentialsId])
|
||||
if (config.tagLatest) {
|
||||
def latestImage = "${config.dockerImage.split(':')[0]}:latest"
|
||||
dockerUtils.moveImage([image: config.sourceImage, registryUrl: config.sourceRegistryUrl], [image: latestImage, registryUrl: config.dockerRegistryUrl, credentialsId: config.dockerCredentialsId])
|
||||
}
|
||||
} else {
|
||||
error "[${STEP_NAME}] Running on Kubernetes: Only moving images from one registry to another supported."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -67,6 +67,7 @@ void call(Map parameters = [:]) {
|
||||
.mixin(parameters, PARAMETER_KEYS)
|
||||
.use()
|
||||
|
||||
// telemetry reporting
|
||||
new Utils().pushToSWA([
|
||||
step: STEP_NAME
|
||||
], config)
|
||||
|
Loading…
x
Reference in New Issue
Block a user