1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00
sap-jenkins-library/test/groovy/DockerExecuteTest.groovy
Pavel Busko caee8db407
feat(dockerExecute): Infer Kubernetes securityContext from dockerOptions (#4557)
* Allow running as different user on Kubernetes

Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
Co-authored-by: Johannes Dillmann <j.dillmann@sap.com>
Co-authored-by: Pavel Busko <pavel.busko@sap.com>

* infer securityContext from dockerOptions

Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
Co-authored-by: Pavel Busko <pavel.busko@sap.com>

* verify --user flag value

---------

Co-authored-by: Johannes Dillmann <j.dillmann@sap.com>
Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
Co-authored-by: Anil Keshav <anil.keshav@sap.com>
2023-09-18 13:05:01 +02:00

610 lines
22 KiB
Groovy

import com.sap.piper.k8s.ContainerMap
import com.sap.piper.JenkinsUtils
import com.sap.piper.SidecarUtils
import com.sap.piper.Utils
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import util.BasePiperTest
import util.JenkinsLoggingRule
import util.JenkinsReadYamlRule
import util.JenkinsShellCallRule
import util.JenkinsStepRule
import util.PluginMock
import util.Rules
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertThat
import static org.junit.Assert.assertTrue
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertFalse
class DockerExecuteTest extends BasePiperTest {
private DockerMock docker
private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this)
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
@Rule
public RuleChain ruleChain = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(loggingRule)
.around(stepRule)
.around(shellRule)
def bodyExecuted
def containerName
@Before
void init() {
bodyExecuted = false
docker = new DockerMock()
JenkinsUtils.metaClass.static.isPluginActive = { def s -> new PluginMock(s).isActive() }
binding.setVariable('docker', docker)
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, "docker .*", 0)
Utils.metaClass.echo = { def m -> }
}
@After
public void tearDown() {
Utils.metaClass = null
}
@Test
void testExecuteInsideContainerOfExistingPod() throws Exception {
List usedDockerEnvVars
helper.registerAllowedMethod('container', [String.class, Closure.class], { String container, Closure body ->
containerName = container
body()
})
helper.registerAllowedMethod('withEnv', [List.class, Closure.class], { List envVars, Closure body ->
usedDockerEnvVars = envVars
body()
})
binding.setVariable('env', [POD_NAME: 'testpod', ON_K8S: 'true'])
ContainerMap.instance.setMap(['testpod': ['maven:3.5-jdk-8-alpine': 'mavenexec']])
stepRule.step.dockerExecute(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
bodyExecuted = true
}
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Container'))
assertEquals('mavenexec', containerName)
assertEquals(usedDockerEnvVars[0].toString(), "http_proxy=http://proxy:8000")
assertTrue(bodyExecuted)
}
@Test
void testExecuteInsideNewlyCreatedPod() throws Exception {
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> body() })
binding.setVariable('env', [ON_K8S: 'true'])
ContainerMap.instance.setMap(['testpod': ['maven:3.5-jdk-8-alpine': 'mavenexec']])
stepRule.step.dockerExecute(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
bodyExecuted = true
}
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
assertTrue(bodyExecuted)
}
@Test
void testExecuteInsidePodWithEmptyContainerMap() throws Exception {
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> body() })
binding.setVariable('env', [POD_NAME: 'testpod', ON_K8S: 'true'])
ContainerMap.instance.setMap([:])
stepRule.step.dockerExecute(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
bodyExecuted = true
}
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
assertTrue(bodyExecuted)
}
@Test
void testExecuteInsidePodWithStageKeyEmptyValue() throws Exception {
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> body() })
binding.setVariable('env', [POD_NAME: 'testpod', ON_K8S: 'true'])
ContainerMap.instance.setMap(['testpod': [:]])
stepRule.step.dockerExecute(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
bodyExecuted = true
}
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
assertTrue(bodyExecuted)
}
@Test
void testExecuteInsidePodWithCustomCommandAndShell() throws Exception {
Map kubernetesConfig = [:]
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body ->
kubernetesConfig = config
return body()
})
binding.setVariable('env', [ON_K8S: 'true'])
stepRule.step.dockerExecute(
script: nullScript,
containerCommand: '/busybox/tail -f /dev/null',
containerShell: '/busybox/sh',
dockerImage: 'maven:3.5-jdk-8-alpine'
) {
bodyExecuted = true
}
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
assertThat(kubernetesConfig.containerCommand, is('/busybox/tail -f /dev/null'))
assertThat(kubernetesConfig.containerShell, is('/busybox/sh'))
assertTrue(bodyExecuted)
}
@Test
void testExecuteInsidePodWithCustomUserShort() throws Exception {
Map kubernetesConfig = [:]
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body ->
kubernetesConfig = config
return body()
})
binding.setVariable('env', [ON_K8S: 'true'])
stepRule.step.dockerExecute(
script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerOptions: ["-u 0:0", "-v foo:bar"]
) {
bodyExecuted = true
}
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
assertThat(kubernetesConfig.securityContext, is([
'runAsUser': 0,
'runAsGroup': 0
]))
assertTrue(bodyExecuted)
}
@Test
void testExecuteInsidePodWithCustomUserLong() throws Exception {
Map kubernetesConfig = [:]
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body ->
kubernetesConfig = config
return body()
})
binding.setVariable('env', [ON_K8S: 'true'])
stepRule.step.dockerExecute(
script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerOptions: ["--user 0:0", "-v foo:bar"]
) {
bodyExecuted = true
}
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
assertThat(kubernetesConfig.securityContext, is([
'runAsUser': 0,
'runAsGroup': 0
]))
assertTrue(bodyExecuted)
}
@Test
void testExecuteInsidePodWithCustomUserNoGroup() throws Exception {
Map kubernetesConfig = [:]
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body ->
kubernetesConfig = config
return body()
})
binding.setVariable('env', [ON_K8S: 'true'])
stepRule.step.dockerExecute(
script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerOptions: ["-v foo:bar", "-u 0"]
) {
bodyExecuted = true
}
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
assertThat(kubernetesConfig.securityContext, is([
'runAsUser': 0
]))
assertTrue(bodyExecuted)
}
@Test
void testExecuteInsidePodWithCustomUserGroupString() throws Exception {
Map kubernetesConfig = [:]
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body ->
kubernetesConfig = config
return body()
})
binding.setVariable('env', [ON_K8S: 'true'])
stepRule.step.dockerExecute(
script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerOptions: ["-v foo:bar", "-u root:wheel"]
) {
bodyExecuted = true
}
assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
assertThat(kubernetesConfig.securityContext, is([
'runAsUser': 'root',
'runAsGroup': 'wheel'
]))
assertTrue(bodyExecuted)
}
@Test
void testExecuteInsideDockerContainer() throws Exception {
stepRule.step.dockerExecute(script: nullScript, dockerImage: 'maven:3.5-jdk-8-alpine') {
bodyExecuted = true
}
assertEquals('maven:3.5-jdk-8-alpine', docker.getImageNames()[0])
assertTrue(docker.isImagePulled())
assertEquals('--env http_proxy --env https_proxy --env no_proxy --env HTTP_PROXY --env HTTPS_PROXY --env NO_PROXY', docker.getParameters().trim())
assertTrue(bodyExecuted)
}
@Test
void testSkipDockerImagePull() throws Exception {
nullScript.commonPipelineEnvironment.configuration = [steps: [dockerExecute: [dockerPullImage: false]]]
stepRule.step.dockerExecute(
script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine'
) {
bodyExecuted = true
}
assertThat(docker.imagePullCount, is(0))
assertThat(bodyExecuted, is(true))
}
@Test
void testPullSidecarWithDedicatedCredentialsAndRegistry() {
nullScript.commonPipelineEnvironment.configuration =
[
steps: [
dockerExecute: [
dockerRegistryUrl: 'https://registry.example.org',
dockerRegistryCredentialsId: 'mySecrets',
sidecarRegistryUrl: 'https://sidecarregistry.example.org',
sidecarRegistryCredentialsId: 'mySidecarRegistryCredentials',
]
]
]
stepRule.step.dockerExecute(
script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerRegistryCredentialsId: 'mySecrets',
sidecarImage: 'ubuntu',
) {
bodyExecuted = true
}
// not clear which image has been pulled with which registry, but at least
// both registries are involved.
assertThat(docker.registriesWithCredentials, is([
[
registry: 'https://registry.example.org',
credentialsId: 'mySecrets',
],
[
registry: 'https://sidecarregistry.example.org',
credentialsId: 'mySidecarRegistryCredentials',
]
]))
assertThat(docker.imagePullCount, is(2))
assertThat(bodyExecuted, is(true))
}
@Test
void testPullSidecarWithSameCredentialsAndRegistryLikeBaseImageWhenNothingElseIsSpecified() {
nullScript.commonPipelineEnvironment.configuration =
[
steps: [
dockerExecute: [
dockerRegistryUrl: 'https://registry.example.org',
]
]
]
stepRule.step.dockerExecute(
script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerRegistryCredentialsId: 'mySecrets',
sidecarImage: 'ubuntu',
) {
bodyExecuted = true
}
// from getting an empty list we derive withRegistry has not been called
// if it would have been called we would have the registry provided above.
assertThat(docker.registriesWithCredentials, is([
[
registry: 'https://registry.example.org',
credentialsId: 'mySecrets',
],
[
registry: 'https://registry.example.org',
credentialsId: 'mySecrets',
],
]))
assertThat(docker.imagePullCount, is(2))
assertThat(bodyExecuted, is(true))
}
@Test
void testPullWithRegistryOnlyAndNoCredentials() {
nullScript.commonPipelineEnvironment.configuration =
[
steps: [
dockerExecute: [
dockerRegistryUrl: 'https://registry.example.org',
]
]
]
stepRule.step.dockerExecute(
script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine'
) {
bodyExecuted = true
}
// from getting an empty list we derive withRegistry has not been called
// if it would have been called we would have the registry provided above.
assertThat(docker.registriesWithCredentials, is([
[
registry: 'https://registry.example.org',
]
]))
assertThat(docker.imagePullCount, is(1))
assertThat(bodyExecuted, is(true))
}
@Test
void testPullWithCredentials() throws Exception {
nullScript.commonPipelineEnvironment.configuration =
[
steps: [
dockerExecute: [
dockerRegistryUrl: 'https://registry.example.org',
dockerRegistryCredentialsId: 'mySecrets',
]
]
]
stepRule.step.dockerExecute(
script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine'
) {
bodyExecuted = true
}
assertThat(docker.registriesWithCredentials, is([
[
registry: 'https://registry.example.org',
credentialsId: 'mySecrets',
]
]))
assertThat(docker.imagePullCount, is(1))
assertThat(bodyExecuted, is(true))
}
@Test
void testSkipSidecarImagePull() throws Exception {
stepRule.step.dockerExecute(
script: nullScript,
dockerName: 'maven',
dockerImage: 'maven:3.5-jdk-8-alpine',
sidecarEnvVars: ['testEnv': 'testVal'],
sidecarImage: 'selenium/standalone-chrome',
sidecarVolumeBind: ['/dev/shm': '/dev/shm'],
sidecarName: 'testAlias',
sidecarPorts: ['4444': '4444', '1111': '1111'],
sidecarPullImage: false
) {
bodyExecuted = true
}
assertThat(docker.imagePullCount, is(1))
assertThat(bodyExecuted, is(true))
}
@Test
void testExecuteInsideDockerContainerWithParameters() throws Exception {
stepRule.step.dockerExecute(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerOptions: '-description=lorem ipsum',
dockerVolumeBind: ['my_vol': '/my_vol'],
dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
bodyExecuted = true
}
assertTrue(docker.getParameters().contains('--env https_proxy '))
assertTrue(docker.getParameters().contains('--env http_proxy=http://proxy:8000'))
assertTrue(docker.getParameters().contains('description=lorem\\ ipsum'))
assertTrue(docker.getParameters().contains('--volume my_vol:/my_vol'))
assertTrue(bodyExecuted)
}
@Test
void testExecuteInsideDockerContainerWithDockerOptionsList() throws Exception {
stepRule.step.dockerExecute(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerOptions: ['-it', '--network=my-network', 'description=lorem ipsum'],
dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
bodyExecuted = true
}
assertTrue(docker.getParameters().contains('--env http_proxy=http://proxy:8000'))
assertTrue(docker.getParameters().contains('-it'))
assertTrue(docker.getParameters().contains('--network=my-network'))
assertTrue(docker.getParameters().contains('description=lorem\\ ipsum'))
}
@Test
void testDockerNotInstalledResultsInLocalExecution() throws Exception {
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, "docker .*", 1)
stepRule.step.dockerExecute(script: nullScript,
dockerOptions: '-it') {
bodyExecuted = true
}
assertTrue(loggingRule.log.contains('Cannot connect to docker daemon'))
assertTrue(loggingRule.log.contains('Running on local environment'))
assertTrue(bodyExecuted)
assertFalse(docker.isImagePulled())
}
@Test
void testSidecarDefault() {
stepRule.step.dockerExecute(
script: nullScript,
dockerName: 'maven',
dockerImage: 'maven:3.5-jdk-8-alpine',
sidecarEnvVars: ['testEnv': 'testVal'],
sidecarImage: 'selenium/standalone-chrome',
sidecarVolumeBind: ['/dev/shm': '/dev/shm'],
sidecarName: 'testAlias',
sidecarPorts: ['4444': '4444', '1111': '1111']
) {
bodyExecuted = true
}
assertThat(bodyExecuted, is(true))
assertThat(docker.imagePullCount, is(2))
assertThat(docker.sidecarParameters, allOf(
containsString('--env testEnv=testVal'),
containsString('--volume /dev/shm:/dev/shm'),
containsString('--network sidecar-'),
containsString('--network-alias testAlias')
))
assertThat(docker.parameters, allOf(
containsString('--network sidecar-'),
containsString('--network-alias maven')
))
}
@Test
void testSidecarHealthCheck() {
stepRule.step.dockerExecute(
script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
sidecarImage: 'selenium/standalone-chrome',
sidecarName: 'testAlias',
sidecarReadyCommand: "isReady.sh"
) {}
assertThat(shellRule.shell, hasItem("docker exec uniqueId isReady.sh"))
}
@Test
void testSidecarKubernetes() {
boolean dockerExecuteOnKubernetesCalled = false
binding.setVariable('env', [ON_K8S: 'true'])
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { params, body ->
dockerExecuteOnKubernetesCalled = true
assertThat(params.dockerImage, is('maven:3.5-jdk-8-alpine'))
assertThat(params.containerName, is('maven'))
assertThat(params.sidecarEnvVars, is(['testEnv': 'testVal']))
assertThat(params.sidecarName, is('selenium'))
assertThat(params.sidecarImage, is('selenium/standalone-chrome'))
assertThat(params.containerName, is('maven'))
assertThat(params.containerPortMappings['selenium/standalone-chrome'], hasItem(allOf(hasEntry('containerPort', 4444), hasEntry('hostPort', 4444))))
assertThat(params.dockerWorkspace, is('/home/piper'))
body()
})
stepRule.step.dockerExecute(
script: nullScript,
containerPortMappings: [
'selenium/standalone-chrome': [[name: 'selPort', containerPort: 4444, hostPort: 4444]]
],
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerName: 'maven',
dockerWorkspace: '/home/piper',
sidecarEnvVars: ['testEnv': 'testVal'],
sidecarImage: 'selenium/standalone-chrome',
sidecarName: 'selenium',
sidecarVolumeBind: ['/dev/shm': '/dev/shm']
) {
bodyExecuted = true
}
assertThat(bodyExecuted, is(true))
assertThat(dockerExecuteOnKubernetesCalled, is(true))
}
@Test
void testSidecarKubernetesHealthCheck() {
binding.setVariable('env', [ON_K8S: 'true'])
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { params, body ->
body()
SidecarUtils sidecarUtils = new SidecarUtils(nullScript)
sidecarUtils.waitForSidecarReadyOnKubernetes(params.sidecarName, params.sidecarReadyCommand)
})
def containerCalled = false
helper.registerAllowedMethod('container', [Map.class, Closure.class], { params, body ->
containerCalled = true
assertThat(params.name, is('testAlias'))
body()
})
stepRule.step.dockerExecute(
script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
sidecarImage: 'selenium/standalone-chrome',
sidecarName: 'testAlias',
sidecarReadyCommand: "isReady.sh"
) {}
assertThat(containerCalled, is(true))
assertThat(shellRule.shell, hasItem("isReady.sh"))
}
private class DockerMock {
private List imageNames = []
private boolean imagePulled = false
private int imagePullCount = 0
private String parameters
private String sidecarParameters
private List registriesWithCredentials = []
private String credentialsId
DockerMock image(String imageName) {
this.imageNames << imageName
return this
}
void pull() {
imagePullCount++
imagePulled = true
}
void withRegistry(String registry, String credentialsId, Closure c) {
this.registriesWithCredentials << [registry: registry, credentialsId: credentialsId]
c()
}
void withRegistry(String registry, Closure c) {
this.registriesWithCredentials << [registry: registry]
c()
}
void inside(String parameters, body) {
this.parameters = parameters
body()
}
void withRun(String parameters, body) {
this.sidecarParameters = parameters
body([id: 'uniqueId'])
}
def getImageNames() {
return imageNames
}
boolean isImagePulled() {
return imagePulled
}
String getParameters() {
return parameters
}
}
}