diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index bfb3a6cbd..df39f87be 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -77,6 +77,10 @@ execute the following from the directory at the root of this project: `./mvnw test -Dtest=DummyStepTests` +To run one single test, execute the following from the directory at the root of +this project, separating test name from test file with an `#`: + +`./mvnw test -Dtest=DummyStepTests#testName` To run tests and print additional debug output to the console, use the `-Pdebug` flag: diff --git a/src/test/groovy/DockerImageExistsStepTests.groovy b/src/test/groovy/DockerImageExistsStepTests.groovy new file mode 100644 index 000000000..08349cc3d --- /dev/null +++ b/src/test/groovy/DockerImageExistsStepTests.groovy @@ -0,0 +1,171 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import org.junit.Before +import org.junit.Test +import static org.junit.Assert.assertFalse +import static org.junit.Assert.assertTrue + +class DockerImageExistsStepTests extends ApmBasePipelineTest { + + @Override + @Before + void setUp() throws Exception { + super.setUp() + helper.registerAllowedMethod('isInstalled', [Map.class], { return true }) + script = loadScript('vars/dockerImageExists.groovy') + } + + @Test + void testLinux_DockerIsNotInstalled() throws Exception { + helper.registerAllowedMethod('isInstalled', [Map.class], { return false }) + try { + script.call(image: 'hello-world:latest') + } catch(e){ + //NOOP + } + printCallStack() + assertTrue(assertMethodCallContainsPattern('error', 'dockerImageExists: Docker is not installed')) + assertJobStatusFailure() + } + + @Test + void testLinux_ImageDoesNotExists() throws Exception { + helper.registerAllowedMethod('cmd', [Map.class], { m -> + if (m.script.contains('docker manifest inspect')) { + return 0 + } + return 1 + }) + def ret = script.call(image: 'hello-world:latest') + printCallStack() + assertTrue(ret) + assertTrue(assertMethodCallContainsPattern('cmd', 'docker inspect -f "{{.Id}}" hello-world:latest')) + assertTrue(assertMethodCallContainsPattern('log', 'hello-world:latest does not exist in the Docker host: checking registry')) + assertTrue(assertMethodCallContainsPattern('cmd', 'docker manifest inspect hello-world:latest')) + assertTrue(assertMethodCallContainsPattern('log', 'hello-world:latest exists in the Docker registry')) + assertJobStatusSuccess() + } + + @Test + void testLinux_ImageExists() throws Exception { + helper.registerAllowedMethod('cmd', [Map.class], { m -> 0 }) + def ret = script.call(image: 'hello-world:latest') + printCallStack() + assertTrue(ret) + assertTrue(assertMethodCallContainsPattern('cmd', 'docker inspect -f "{{.Id}}" hello-world:latest')) + assertTrue(assertMethodCallContainsPattern('log', 'hello-world:latest exists in the Docker host')) + assertJobStatusSuccess() + } + + @Test + void testLinux_ImageIsNotSet() throws Exception { + try { + script.call() + } catch(e){ + //NOOP + } + printCallStack() + assertTrue(assertMethodCallContainsPattern('error', 'dockerImageExists: image parameter is required')) + assertJobStatusFailure() + } + + @Test + void testLinux_NotExists() throws Exception { + helper.registerAllowedMethod('cmd', [Map.class], { m -> 1 }) + def ret = script.call(image: 'hello-world:latest') + printCallStack() + assertFalse(ret) + assertTrue(assertMethodCallContainsPattern('cmd', 'docker inspect -f "{{.Id}}" hello-world:latest')) + assertTrue(assertMethodCallContainsPattern('log', 'hello-world:latest does not exist in the Docker host: checking registry')) + assertTrue(assertMethodCallContainsPattern('cmd', 'docker manifest inspect hello-world:latest')) + assertTrue(assertMethodCallContainsPattern('log', 'hello-world:latest does not exist at all')) + assertJobStatusSuccess() + } + + @Test + void testWindows_DockerIsNotInstalled() throws Exception { + helper.registerAllowedMethod('isUnix', [], { false }) + helper.registerAllowedMethod('isInstalled', [Map.class], { return false }) + try { + script.call(image: 'hello-world:latest') + } catch(e){ + //NOOP + } + printCallStack() + assertTrue(assertMethodCallContainsPattern('error', 'dockerImageExists: Docker is not installed')) + assertJobStatusFailure() + } + + @Test + void testWindows_ImageDoesNotExists_Pull() throws Exception { + helper.registerAllowedMethod('isUnix', [], { false }) + helper.registerAllowedMethod('cmd', [Map.class], { m -> + if (m.script.contains('docker manifest inspect')) { + return 0 + } + return 1 + }) + def ret = script.call(image: 'hello-world:latest') + printCallStack() + assertTrue(ret) + assertTrue(assertMethodCallContainsPattern('cmd', 'docker inspect -f "{{.Id}}" hello-world:latest')) + assertTrue(assertMethodCallContainsPattern('log', 'hello-world:latest does not exist in the Docker host: checking registry')) + assertTrue(assertMethodCallContainsPattern('cmd', 'docker manifest inspect hello-world:latest')) + assertTrue(assertMethodCallContainsPattern('log', 'hello-world:latest exists in the Docker registry')) + assertJobStatusSuccess() + } + + @Test + void testWindows_ImageExists() throws Exception { + helper.registerAllowedMethod('isUnix', [], { false }) + helper.registerAllowedMethod('cmd', [Map.class], { m -> 0 }) + def ret = script.call(image: 'hello-world:latest') + printCallStack() + assertTrue(ret) + assertTrue(assertMethodCallContainsPattern('cmd', 'docker inspect -f "{{.Id}}" hello-world:latest')) + assertTrue(assertMethodCallContainsPattern('log', 'hello-world:latest exists in the Docker host')) + assertJobStatusSuccess() + } + + @Test + void testWindows_ImageIsNotSet() throws Exception { + helper.registerAllowedMethod('isUnix', [], { false }) + try { + script.call() + } catch(e){ + //NOOP + } + printCallStack() + assertTrue(assertMethodCallContainsPattern('error', 'dockerImageExists: image parameter is required')) + assertJobStatusFailure() + } + + @Test + void testWindows_NotExists() throws Exception { + helper.registerAllowedMethod('isUnix', [], { false }) + helper.registerAllowedMethod('cmd', [Map.class], { m -> 1 }) + def ret = script.call(image: 'hello-world:latest') + printCallStack() + assertFalse(ret) + assertTrue(assertMethodCallContainsPattern('cmd', 'docker inspect -f "{{.Id}}" hello-world:latest')) + assertTrue(assertMethodCallContainsPattern('log', 'hello-world:latest does not exist in the Docker host: checking registry')) + assertTrue(assertMethodCallContainsPattern('cmd', 'docker manifest inspect hello-world:latest')) + assertTrue(assertMethodCallContainsPattern('log', 'hello-world:latest does not exist at all')) + assertJobStatusSuccess() + } +} diff --git a/vars/README.md b/vars/README.md index e682ed953..efe747d7c 100644 --- a/vars/README.md +++ b/vars/README.md @@ -344,6 +344,15 @@ Generate the details URL to be added to the GitHub notifications. When possible * tab: What kind of details links will be used. Enum type: tests, changes, artifacts, pipeline or an ``). Default `pipeline`. * isBlueOcean: Whether to use the BlueOcean URLs. Default `false`. +## dockerImageExists +Checks if the given Docker image exists. + +``` +dockerImageExists(image: 'hello-world:latest') +``` + +* image: Fully qualified name of the image + ## dockerLogin Login to hub.docker.com with an authentication credentials from a Vault secret. The vault secret contains `user` and `password` fields with the authentication details. diff --git a/vars/dockerImageExists.groovy b/vars/dockerImageExists.groovy new file mode 100644 index 000000000..b4ece9b0c --- /dev/null +++ b/vars/dockerImageExists.groovy @@ -0,0 +1,45 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + Checks if the given Docker image exists. + + whenTrue(dockerImageExists(image: 'hello-world:latest')) { + ... + } +*/ +def call(Map args = [:]) { + def image = args.containsKey('image') ? args.image : error('dockerImageExists: image parameter is required') + + if (!isInstalled(tool: 'docker', flag: '--version')) { + error 'dockerImageExists: Docker is not installed' + } + + if (cmd(returnStatus: true, script: "docker inspect -f \"{{.Id}}\" ${image}") == 0) { + log(level: 'DEBUG', text: "${image} exists in the Docker host") + return true + } + + log(level: 'DEBUG', text: "${image} does not exist in the Docker host: checking registry") + if (cmd(returnStdout: true, returnStatus: true, script: "docker manifest inspect ${image}") == 0) { + log(level: 'DEBUG', text: "${image} exists in the Docker registry") + return true + } + + log(level: 'DEBUG', text: "${image} does not exist at all") + return false +} diff --git a/vars/dockerImageExists.txt b/vars/dockerImageExists.txt new file mode 100644 index 000000000..755981f06 --- /dev/null +++ b/vars/dockerImageExists.txt @@ -0,0 +1,7 @@ +Checks if the given Docker image exists. + +``` +dockerImageExists(image: 'hello-world:latest') +``` + +* image: Fully qualified name of the image