github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/test/groovy/DockerExecuteTest.groovy (about) 1 import com.sap.piper.k8s.ContainerMap 2 3 import com.sap.piper.JenkinsUtils 4 import com.sap.piper.SidecarUtils 5 import com.sap.piper.Utils 6 import org.junit.After 7 import org.junit.Before 8 import org.junit.Rule 9 import org.junit.Test 10 import org.junit.rules.RuleChain 11 12 import util.BasePiperTest 13 import util.JenkinsLoggingRule 14 import util.JenkinsReadYamlRule 15 import util.JenkinsShellCallRule 16 import util.JenkinsStepRule 17 import util.PluginMock 18 import util.Rules 19 20 import static org.hamcrest.Matchers.* 21 import static org.junit.Assert.assertThat 22 import static org.junit.Assert.assertTrue 23 import static org.junit.Assert.assertEquals 24 import static org.junit.Assert.assertFalse 25 26 class DockerExecuteTest extends BasePiperTest { 27 private DockerMock docker 28 private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this) 29 private JenkinsStepRule stepRule = new JenkinsStepRule(this) 30 private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this) 31 32 @Rule 33 public RuleChain ruleChain = Rules 34 .getCommonRules(this) 35 .around(new JenkinsReadYamlRule(this)) 36 .around(loggingRule) 37 .around(stepRule) 38 .around(shellRule) 39 40 def bodyExecuted 41 def containerName 42 43 @Before 44 void init() { 45 bodyExecuted = false 46 docker = new DockerMock() 47 JenkinsUtils.metaClass.static.isPluginActive = { def s -> new PluginMock(s).isActive() } 48 binding.setVariable('docker', docker) 49 shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, "docker .*", 0) 50 Utils.metaClass.echo = { def m -> } 51 } 52 53 @After 54 public void tearDown() { 55 Utils.metaClass = null 56 } 57 58 @Test 59 void testExecuteInsideContainerOfExistingPod() throws Exception { 60 List usedDockerEnvVars 61 helper.registerAllowedMethod('container', [String.class, Closure.class], { String container, Closure body -> 62 containerName = container 63 body() 64 }) 65 helper.registerAllowedMethod('withEnv', [List.class, Closure.class], { List envVars, Closure body -> 66 usedDockerEnvVars = envVars 67 body() 68 }) 69 binding.setVariable('env', [POD_NAME: 'testpod', ON_K8S: 'true']) 70 ContainerMap.instance.setMap(['testpod': ['maven:3.5-jdk-8-alpine': 'mavenexec']]) 71 stepRule.step.dockerExecute(script: nullScript, 72 dockerImage: 'maven:3.5-jdk-8-alpine', 73 dockerEnvVars: ['http_proxy': 'http://proxy:8000']) { 74 bodyExecuted = true 75 } 76 assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Container')) 77 assertEquals('mavenexec', containerName) 78 assertEquals(usedDockerEnvVars[0].toString(), "http_proxy=http://proxy:8000") 79 assertTrue(bodyExecuted) 80 } 81 82 @Test 83 void testExecuteInsideNewlyCreatedPod() throws Exception { 84 helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> body() }) 85 binding.setVariable('env', [ON_K8S: 'true']) 86 ContainerMap.instance.setMap(['testpod': ['maven:3.5-jdk-8-alpine': 'mavenexec']]) 87 stepRule.step.dockerExecute(script: nullScript, 88 dockerImage: 'maven:3.5-jdk-8-alpine', 89 dockerEnvVars: ['http_proxy': 'http://proxy:8000']) { 90 bodyExecuted = true 91 } 92 assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod')) 93 assertTrue(bodyExecuted) 94 } 95 96 @Test 97 void testExecuteInsidePodWithEmptyContainerMap() throws Exception { 98 helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> body() }) 99 binding.setVariable('env', [POD_NAME: 'testpod', ON_K8S: 'true']) 100 ContainerMap.instance.setMap([:]) 101 stepRule.step.dockerExecute(script: nullScript, 102 dockerImage: 'maven:3.5-jdk-8-alpine', 103 dockerEnvVars: ['http_proxy': 'http://proxy:8000']) { 104 bodyExecuted = true 105 } 106 assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod')) 107 assertTrue(bodyExecuted) 108 } 109 110 @Test 111 void testExecuteInsidePodWithStageKeyEmptyValue() throws Exception { 112 helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> body() }) 113 binding.setVariable('env', [POD_NAME: 'testpod', ON_K8S: 'true']) 114 ContainerMap.instance.setMap(['testpod': [:]]) 115 stepRule.step.dockerExecute(script: nullScript, 116 dockerImage: 'maven:3.5-jdk-8-alpine', 117 dockerEnvVars: ['http_proxy': 'http://proxy:8000']) { 118 bodyExecuted = true 119 } 120 assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod')) 121 assertTrue(bodyExecuted) 122 } 123 124 @Test 125 void testExecuteInsidePodWithCustomCommandAndShell() throws Exception { 126 Map kubernetesConfig = [:] 127 helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> 128 kubernetesConfig = config 129 return body() 130 }) 131 binding.setVariable('env', [ON_K8S: 'true']) 132 stepRule.step.dockerExecute( 133 script: nullScript, 134 containerCommand: '/busybox/tail -f /dev/null', 135 containerShell: '/busybox/sh', 136 dockerImage: 'maven:3.5-jdk-8-alpine' 137 ) { 138 bodyExecuted = true 139 } 140 assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod')) 141 assertThat(kubernetesConfig.containerCommand, is('/busybox/tail -f /dev/null')) 142 assertThat(kubernetesConfig.containerShell, is('/busybox/sh')) 143 assertTrue(bodyExecuted) 144 } 145 146 @Test 147 void testExecuteInsidePodWithCustomUserShort() throws Exception { 148 Map kubernetesConfig = [:] 149 helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> 150 kubernetesConfig = config 151 return body() 152 }) 153 binding.setVariable('env', [ON_K8S: 'true']) 154 stepRule.step.dockerExecute( 155 script: nullScript, 156 dockerImage: 'maven:3.5-jdk-8-alpine', 157 dockerOptions: ["-u 0:0", "-v foo:bar"] 158 ) { 159 bodyExecuted = true 160 } 161 162 assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod')) 163 assertThat(kubernetesConfig.securityContext, is([ 164 'runAsUser': 0, 165 'runAsGroup': 0 166 ])) 167 assertTrue(bodyExecuted) 168 } 169 170 @Test 171 void testExecuteInsidePodWithCustomUserLong() throws Exception { 172 Map kubernetesConfig = [:] 173 helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> 174 kubernetesConfig = config 175 return body() 176 }) 177 binding.setVariable('env', [ON_K8S: 'true']) 178 stepRule.step.dockerExecute( 179 script: nullScript, 180 dockerImage: 'maven:3.5-jdk-8-alpine', 181 dockerOptions: ["--user 0:0", "-v foo:bar"] 182 ) { 183 bodyExecuted = true 184 } 185 186 assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod')) 187 assertThat(kubernetesConfig.securityContext, is([ 188 'runAsUser': 0, 189 'runAsGroup': 0 190 ])) 191 assertTrue(bodyExecuted) 192 } 193 194 @Test 195 void testExecuteInsidePodWithCustomUserNoGroup() throws Exception { 196 Map kubernetesConfig = [:] 197 helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> 198 kubernetesConfig = config 199 return body() 200 }) 201 binding.setVariable('env', [ON_K8S: 'true']) 202 stepRule.step.dockerExecute( 203 script: nullScript, 204 dockerImage: 'maven:3.5-jdk-8-alpine', 205 dockerOptions: ["-v foo:bar", "-u 0"] 206 ) { 207 bodyExecuted = true 208 } 209 210 assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod')) 211 assertThat(kubernetesConfig.securityContext, is([ 212 'runAsUser': 0 213 ])) 214 assertTrue(bodyExecuted) 215 } 216 217 @Test 218 void testExecuteInsidePodWithCustomUserGroupString() throws Exception { 219 Map kubernetesConfig = [:] 220 helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> 221 kubernetesConfig = config 222 return body() 223 }) 224 binding.setVariable('env', [ON_K8S: 'true']) 225 stepRule.step.dockerExecute( 226 script: nullScript, 227 dockerImage: 'maven:3.5-jdk-8-alpine', 228 dockerOptions: ["-v foo:bar", "-u root:wheel"] 229 ) { 230 bodyExecuted = true 231 } 232 233 assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod')) 234 assertThat(kubernetesConfig.securityContext, is([ 235 'runAsUser': 'root', 236 'runAsGroup': 'wheel' 237 ])) 238 assertTrue(bodyExecuted) 239 } 240 241 @Test 242 void testExecuteInsideDockerContainer() throws Exception { 243 stepRule.step.dockerExecute(script: nullScript, dockerImage: 'maven:3.5-jdk-8-alpine') { 244 bodyExecuted = true 245 } 246 assertEquals('maven:3.5-jdk-8-alpine', docker.getImageNames()[0]) 247 assertTrue(docker.isImagePulled()) 248 assertEquals('--env http_proxy --env https_proxy --env no_proxy --env HTTP_PROXY --env HTTPS_PROXY --env NO_PROXY', docker.getParameters().trim()) 249 assertTrue(bodyExecuted) 250 } 251 252 @Test 253 void testSkipDockerImagePull() throws Exception { 254 nullScript.commonPipelineEnvironment.configuration = [steps: [dockerExecute: [dockerPullImage: false]]] 255 stepRule.step.dockerExecute( 256 script: nullScript, 257 dockerImage: 'maven:3.5-jdk-8-alpine' 258 ) { 259 bodyExecuted = true 260 } 261 assertThat(docker.imagePullCount, is(0)) 262 assertThat(bodyExecuted, is(true)) 263 } 264 265 @Test 266 void testPullSidecarWithDedicatedCredentialsAndRegistry() { 267 nullScript.commonPipelineEnvironment.configuration = 268 [ 269 steps: [ 270 dockerExecute: [ 271 dockerRegistryUrl: 'https://registry.example.org', 272 dockerRegistryCredentialsId: 'mySecrets', 273 sidecarRegistryUrl: 'https://sidecarregistry.example.org', 274 sidecarRegistryCredentialsId: 'mySidecarRegistryCredentials', 275 ] 276 ] 277 ] 278 stepRule.step.dockerExecute( 279 script: nullScript, 280 dockerImage: 'maven:3.5-jdk-8-alpine', 281 dockerRegistryCredentialsId: 'mySecrets', 282 sidecarImage: 'ubuntu', 283 ) { 284 bodyExecuted = true 285 } 286 // not clear which image has been pulled with which registry, but at least 287 // both registries are involved. 288 assertThat(docker.registriesWithCredentials, is([ 289 [ 290 registry: 'https://registry.example.org', 291 credentialsId: 'mySecrets', 292 ], 293 [ 294 registry: 'https://sidecarregistry.example.org', 295 credentialsId: 'mySidecarRegistryCredentials', 296 ] 297 ])) 298 assertThat(docker.imagePullCount, is(2)) 299 assertThat(bodyExecuted, is(true)) 300 } 301 302 @Test 303 void testPullSidecarWithSameCredentialsAndRegistryLikeBaseImageWhenNothingElseIsSpecified() { 304 nullScript.commonPipelineEnvironment.configuration = 305 [ 306 steps: [ 307 dockerExecute: [ 308 dockerRegistryUrl: 'https://registry.example.org', 309 ] 310 ] 311 ] 312 stepRule.step.dockerExecute( 313 script: nullScript, 314 dockerImage: 'maven:3.5-jdk-8-alpine', 315 dockerRegistryCredentialsId: 'mySecrets', 316 sidecarImage: 'ubuntu', 317 ) { 318 bodyExecuted = true 319 } 320 // from getting an empty list we derive withRegistry has not been called 321 // if it would have been called we would have the registry provided above. 322 assertThat(docker.registriesWithCredentials, is([ 323 [ 324 registry: 'https://registry.example.org', 325 credentialsId: 'mySecrets', 326 ], 327 [ 328 registry: 'https://registry.example.org', 329 credentialsId: 'mySecrets', 330 ], 331 ])) 332 assertThat(docker.imagePullCount, is(2)) 333 assertThat(bodyExecuted, is(true)) 334 } 335 336 @Test 337 void testPullWithRegistryOnlyAndNoCredentials() { 338 nullScript.commonPipelineEnvironment.configuration = 339 [ 340 steps: [ 341 dockerExecute: [ 342 dockerRegistryUrl: 'https://registry.example.org', 343 ] 344 ] 345 ] 346 stepRule.step.dockerExecute( 347 script: nullScript, 348 dockerImage: 'maven:3.5-jdk-8-alpine' 349 ) { 350 bodyExecuted = true 351 } 352 // from getting an empty list we derive withRegistry has not been called 353 // if it would have been called we would have the registry provided above. 354 assertThat(docker.registriesWithCredentials, is([ 355 [ 356 registry: 'https://registry.example.org', 357 ] 358 ])) 359 assertThat(docker.imagePullCount, is(1)) 360 assertThat(bodyExecuted, is(true)) 361 } 362 363 @Test 364 void testPullWithCredentials() throws Exception { 365 366 nullScript.commonPipelineEnvironment.configuration = 367 [ 368 steps: [ 369 dockerExecute: [ 370 dockerRegistryUrl: 'https://registry.example.org', 371 dockerRegistryCredentialsId: 'mySecrets', 372 ] 373 ] 374 ] 375 stepRule.step.dockerExecute( 376 script: nullScript, 377 dockerImage: 'maven:3.5-jdk-8-alpine' 378 ) { 379 bodyExecuted = true 380 } 381 assertThat(docker.registriesWithCredentials, is([ 382 [ 383 registry: 'https://registry.example.org', 384 credentialsId: 'mySecrets', 385 ] 386 ])) 387 assertThat(docker.imagePullCount, is(1)) 388 assertThat(bodyExecuted, is(true)) 389 } 390 391 @Test 392 void testSkipSidecarImagePull() throws Exception { 393 stepRule.step.dockerExecute( 394 script: nullScript, 395 dockerName: 'maven', 396 dockerImage: 'maven:3.5-jdk-8-alpine', 397 sidecarEnvVars: ['testEnv': 'testVal'], 398 sidecarImage: 'selenium/standalone-chrome', 399 sidecarVolumeBind: ['/dev/shm': '/dev/shm'], 400 sidecarName: 'testAlias', 401 sidecarPorts: ['4444': '4444', '1111': '1111'], 402 sidecarPullImage: false 403 ) { 404 bodyExecuted = true 405 } 406 assertThat(docker.imagePullCount, is(1)) 407 assertThat(bodyExecuted, is(true)) 408 } 409 410 @Test 411 void testExecuteInsideDockerContainerWithParameters() throws Exception { 412 stepRule.step.dockerExecute(script: nullScript, 413 dockerImage: 'maven:3.5-jdk-8-alpine', 414 dockerOptions: '-description=lorem ipsum', 415 dockerVolumeBind: ['my_vol': '/my_vol'], 416 dockerEnvVars: ['http_proxy': 'http://proxy:8000']) { 417 bodyExecuted = true 418 } 419 assertTrue(docker.getParameters().contains('--env https_proxy ')) 420 assertTrue(docker.getParameters().contains('--env http_proxy=http://proxy:8000')) 421 assertTrue(docker.getParameters().contains('description=lorem\\ ipsum')) 422 assertTrue(docker.getParameters().contains('--volume my_vol:/my_vol')) 423 assertTrue(bodyExecuted) 424 } 425 426 @Test 427 void testExecuteInsideDockerContainerWithDockerOptionsList() throws Exception { 428 stepRule.step.dockerExecute(script: nullScript, 429 dockerImage: 'maven:3.5-jdk-8-alpine', 430 dockerOptions: ['-it', '--network=my-network', 'description=lorem ipsum'], 431 dockerEnvVars: ['http_proxy': 'http://proxy:8000']) { 432 bodyExecuted = true 433 } 434 assertTrue(docker.getParameters().contains('--env http_proxy=http://proxy:8000')) 435 assertTrue(docker.getParameters().contains('-it')) 436 assertTrue(docker.getParameters().contains('--network=my-network')) 437 assertTrue(docker.getParameters().contains('description=lorem\\ ipsum')) 438 } 439 440 @Test 441 void testDockerNotInstalledResultsInLocalExecution() throws Exception { 442 shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, "docker .*", 1) 443 stepRule.step.dockerExecute(script: nullScript, 444 dockerOptions: '-it') { 445 bodyExecuted = true 446 } 447 assertTrue(loggingRule.log.contains('Cannot connect to docker daemon')) 448 assertTrue(loggingRule.log.contains('Running on local environment')) 449 assertTrue(bodyExecuted) 450 assertFalse(docker.isImagePulled()) 451 } 452 453 @Test 454 void testSidecarDefault() { 455 stepRule.step.dockerExecute( 456 script: nullScript, 457 dockerName: 'maven', 458 dockerImage: 'maven:3.5-jdk-8-alpine', 459 sidecarEnvVars: ['testEnv': 'testVal'], 460 sidecarImage: 'selenium/standalone-chrome', 461 sidecarVolumeBind: ['/dev/shm': '/dev/shm'], 462 sidecarName: 'testAlias', 463 sidecarPorts: ['4444': '4444', '1111': '1111'] 464 ) { 465 bodyExecuted = true 466 } 467 468 assertThat(bodyExecuted, is(true)) 469 assertThat(docker.imagePullCount, is(2)) 470 assertThat(docker.sidecarParameters, allOf( 471 containsString('--env testEnv=testVal'), 472 containsString('--volume /dev/shm:/dev/shm'), 473 containsString('--network sidecar-'), 474 containsString('--network-alias testAlias') 475 )) 476 assertThat(docker.parameters, allOf( 477 containsString('--network sidecar-'), 478 containsString('--network-alias maven') 479 )) 480 } 481 482 @Test 483 void testSidecarHealthCheck() { 484 stepRule.step.dockerExecute( 485 script: nullScript, 486 dockerImage: 'maven:3.5-jdk-8-alpine', 487 sidecarImage: 'selenium/standalone-chrome', 488 sidecarName: 'testAlias', 489 sidecarReadyCommand: "isReady.sh" 490 ) {} 491 assertThat(shellRule.shell, hasItem("docker exec uniqueId isReady.sh")) 492 } 493 494 @Test 495 void testSidecarKubernetes() { 496 boolean dockerExecuteOnKubernetesCalled = false 497 binding.setVariable('env', [ON_K8S: 'true']) 498 helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { params, body -> 499 dockerExecuteOnKubernetesCalled = true 500 assertThat(params.dockerImage, is('maven:3.5-jdk-8-alpine')) 501 assertThat(params.containerName, is('maven')) 502 assertThat(params.sidecarEnvVars, is(['testEnv': 'testVal'])) 503 assertThat(params.sidecarName, is('selenium')) 504 assertThat(params.sidecarImage, is('selenium/standalone-chrome')) 505 assertThat(params.containerName, is('maven')) 506 assertThat(params.containerPortMappings['selenium/standalone-chrome'], hasItem(allOf(hasEntry('containerPort', 4444), hasEntry('hostPort', 4444)))) 507 assertThat(params.dockerWorkspace, is('/home/piper')) 508 body() 509 }) 510 stepRule.step.dockerExecute( 511 script: nullScript, 512 containerPortMappings: [ 513 'selenium/standalone-chrome': [[name: 'selPort', containerPort: 4444, hostPort: 4444]] 514 ], 515 dockerImage: 'maven:3.5-jdk-8-alpine', 516 dockerName: 'maven', 517 dockerWorkspace: '/home/piper', 518 sidecarEnvVars: ['testEnv': 'testVal'], 519 sidecarImage: 'selenium/standalone-chrome', 520 sidecarName: 'selenium', 521 sidecarVolumeBind: ['/dev/shm': '/dev/shm'] 522 ) { 523 bodyExecuted = true 524 } 525 assertThat(bodyExecuted, is(true)) 526 assertThat(dockerExecuteOnKubernetesCalled, is(true)) 527 } 528 529 @Test 530 void testSidecarKubernetesHealthCheck() { 531 binding.setVariable('env', [ON_K8S: 'true']) 532 533 helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { params, body -> 534 body() 535 SidecarUtils sidecarUtils = new SidecarUtils(nullScript) 536 sidecarUtils.waitForSidecarReadyOnKubernetes(params.sidecarName, params.sidecarReadyCommand) 537 }) 538 539 def containerCalled = false 540 helper.registerAllowedMethod('container', [Map.class, Closure.class], { params, body -> 541 containerCalled = true 542 assertThat(params.name, is('testAlias')) 543 body() 544 }) 545 546 stepRule.step.dockerExecute( 547 script: nullScript, 548 dockerImage: 'maven:3.5-jdk-8-alpine', 549 sidecarImage: 'selenium/standalone-chrome', 550 sidecarName: 'testAlias', 551 sidecarReadyCommand: "isReady.sh" 552 ) {} 553 554 assertThat(containerCalled, is(true)) 555 assertThat(shellRule.shell, hasItem("isReady.sh")) 556 } 557 558 private class DockerMock { 559 private List imageNames = [] 560 private boolean imagePulled = false 561 private int imagePullCount = 0 562 private String parameters 563 private String sidecarParameters 564 private List registriesWithCredentials = [] 565 private String credentialsId 566 567 DockerMock image(String imageName) { 568 this.imageNames << imageName 569 return this 570 } 571 572 void pull() { 573 imagePullCount++ 574 imagePulled = true 575 } 576 577 void withRegistry(String registry, String credentialsId, Closure c) { 578 this.registriesWithCredentials << [registry: registry, credentialsId: credentialsId] 579 c() 580 } 581 582 void withRegistry(String registry, Closure c) { 583 this.registriesWithCredentials << [registry: registry] 584 c() 585 } 586 587 void inside(String parameters, body) { 588 this.parameters = parameters 589 body() 590 } 591 592 void withRun(String parameters, body) { 593 this.sidecarParameters = parameters 594 body([id: 'uniqueId']) 595 } 596 597 def getImageNames() { 598 return imageNames 599 } 600 601 boolean isImagePulled() { 602 return imagePulled 603 } 604 605 String getParameters() { 606 return parameters 607 } 608 } 609 }