import groovy.mock.interceptor.MockFor import static groovy.test.GroovyAssert.* import groovy.util.GroovyTestCase import java.io.FileNotFoundException import org.wikimedia.integration.Blubber import org.wikimedia.integration.PipelineRunner import org.wikimedia.integration.Utility class PipelineRunnerTest extends GroovyTestCase { private class WorkflowScript {} // Mock for Jenkins Pipeline workflow context void setUp() { // Mock all static calls to Utility.randomAlphanum Utility.metaClass.static.randomAlphanum = { "randomfoo" } } void tearDown() { // Reset static mocks Utility.metaClass = null } void testConstructor_workflowScript() { new PipelineRunner(new WorkflowScript()) } void testConstructor_workflowScriptAndProperties() { new PipelineRunner(new WorkflowScript(), configPath: "foo") } void testGetConfigFile() { def pipeline = new PipelineRunner(new WorkflowScript(), configPath: "foo") assert pipeline.getConfigFile("bar") == "foo/bar" } void testGetTempFile() { def pipeline = new PipelineRunner(new WorkflowScript(), configPath: "foo") assert pipeline.getTempFile("bar") ==~ /^foo\/bar[a-z0-9]+$/ } void testQualifyRegistryPath() { def pipeline = new PipelineRunner(new WorkflowScript()) def url = pipeline.qualifyRegistryPath("foo") assert url == "docker-registry.wikimedia.org/wikimedia/foo" } void testQualifyRegistryPath_disallowsSlashes() { def pipeline = new PipelineRunner(new WorkflowScript()) shouldFail(AssertionError) { pipeline.qualifyRegistryPath("foo/bar") } } void testBuild_checksWhetherConfigExists() { def mockWorkflow = new MockFor(WorkflowScript) mockWorkflow.demand.fileExists { path -> assert path == ".pipeline/nonexistent.yaml" false } mockWorkflow.use { def runner = new PipelineRunner(new WorkflowScript(), configPath: ".pipeline", blubberConfig: "nonexistent.yaml") shouldFail(FileNotFoundException) { runner.build("foo", [bar: "baz"]) } } } void testBuild_generatesDockerfileAndBuilds() { def mockWorkflow = new MockFor(WorkflowScript) def mockBlubber = new MockFor(Blubber) mockWorkflow.demand.fileExists { true } mockBlubber.demand.generateDockerfile { variant -> assert variant == "foo" "BASE: foo\n" } mockWorkflow.demand.writeFile { args -> assert args.text == "BASE: foo\n" assert args.file ==~ /^\.pipeline\/Dockerfile\.[a-z0-9]+$/ } mockWorkflow.demand.sh { args -> assert args.returnStdout assert args.script ==~ (/^docker build --pull --label 'foo=a' --label 'bar=b' / + /--file '\.pipeline\/Dockerfile\.[a-z0-9]+' \.$/) // Mock `docker build` output to test that we correctly parse the image ID return "Removing intermediate container foo\n" + " ---> bf1e86190382\n" + "Successfully built bf1e86190382\n" } mockWorkflow.use { mockBlubber.use { def runner = new PipelineRunner(new WorkflowScript()) assert runner.build("foo", [foo: "a", bar: "b"]) == "bf1e86190382" } } } void testDeploy_checksConfigForChart() { def mockWorkflow = new MockFor(WorkflowScript) mockWorkflow.demand.readYaml { Map args -> assert args.file == ".foo/bar.yaml" [chart: null] } mockWorkflow.use { def runner = new PipelineRunner(new WorkflowScript(), configPath: ".foo", helmConfig: "bar.yaml") shouldFail(AssertionError) { runner.deploy("foo/name", "footag") } } } void testDeploy_executesHelm() { def mockWorkflow = new MockFor(WorkflowScript) mockWorkflow.demand.readYaml { [chart: "http://an.example/chart.tgz"] } mockWorkflow.demand.sh { cmd -> def expectedCmd = "helm --tiller-namespace='ci' install " + "--namespace='ci' --set " + "'docker.registry=docker-registry.wikimedia.org'," + "'docker.pull_policy=IfNotPresent'," + "'main_app.image=wikimedia/foo/name'," + "'main_app.version=footag' " + "-n 'foo/name-randomfoo' " + "--debug --wait --timeout 120 " + "'http://an.example/chart.tgz'" assert cmd == expectedCmd } mockWorkflow.use { def runner = new PipelineRunner(new WorkflowScript()) runner.deploy("foo/name", "footag") } } void testDeploy_executesHelmWithKubeConfig() { def mockWorkflow = new MockFor(WorkflowScript) mockWorkflow.demand.readYaml { [chart: "http://an.example/chart.tgz"] } mockWorkflow.demand.sh { cmd -> def expectedCmd = "KUBECONFIG='/etc/kubernetes/foo.config' " + "helm --tiller-namespace='ci' install " + "--namespace='ci' --set " + "'docker.registry=docker-registry.wikimedia.org'," + "'docker.pull_policy=IfNotPresent'," + "'main_app.image=wikimedia/foo/name'," + "'main_app.version=footag' " + "-n 'foo/name-randomfoo' " + "--debug --wait --timeout 120 " + "'http://an.example/chart.tgz'" assert cmd == expectedCmd } mockWorkflow.use { def runner = new PipelineRunner(new WorkflowScript(), kubeConfig: "/etc/kubernetes/foo.config") runner.deploy("foo/name", "footag") } } void testPurgeRelease_executesHelm() { def mockWorkflow = new MockFor(WorkflowScript) mockWorkflow.demand.sh { cmd -> def expectedCmd = "helm --tiller-namespace='ci' delete --purge 'foorelease'" assert cmd == expectedCmd } mockWorkflow.use { def runner = new PipelineRunner(new WorkflowScript()) runner.purgeRelease("foorelease") } } void testPurgeRelease_executesHelmWithKubeConfig() { def mockWorkflow = new MockFor(WorkflowScript) mockWorkflow.demand.sh { cmd -> def expectedCmd = "KUBECONFIG='/etc/kubernetes/foo.config' " + "helm --tiller-namespace='ci' delete --purge 'foorelease'" assert cmd == expectedCmd } mockWorkflow.use { def runner = new PipelineRunner(new WorkflowScript(), kubeConfig: "/etc/kubernetes/foo.config") runner.purgeRelease("foorelease") } } void testTestRelease_executesHelm() { def mockWorkflow = new MockFor(WorkflowScript) mockWorkflow.demand.sh { cmd -> def expectedCmd = "helm --tiller-namespace='ci' test --cleanup 'foorelease'" assert cmd == expectedCmd } mockWorkflow.use { def runner = new PipelineRunner(new WorkflowScript()) runner.testRelease("foorelease") } } void testTestRelease_executesHelmWithKubeConfig() { def mockWorkflow = new MockFor(WorkflowScript) mockWorkflow.demand.sh { cmd -> def expectedCmd = "KUBECONFIG='/etc/kubernetes/foo.config' " + "helm --tiller-namespace='ci' test --cleanup 'foorelease'" assert cmd == expectedCmd } mockWorkflow.use { def runner = new PipelineRunner(new WorkflowScript(), kubeConfig: "/etc/kubernetes/foo.config") runner.testRelease("foorelease") } } void testRegisterAs() { def mockWorkflow = new MockFor(WorkflowScript) mockWorkflow.demand.sh { cmd -> assert cmd == "docker tag 'fooID' 'internal.example/foorepo/fooname:footag' && " + "sudo /usr/local/bin/docker-pusher 'internal.example/foorepo/fooname:footag'" } mockWorkflow.use { def runner = new PipelineRunner(new WorkflowScript(), registryInternal: 'internal.example', repository: 'foorepo') runner.registerAs("fooID", "fooname", "footag") } } void testRegisterAs_bailsOnSlashes() { def mockWorkflow = new MockFor(WorkflowScript) mockWorkflow.use { def runner = new PipelineRunner(new WorkflowScript()) shouldFail(AssertionError) { runner.registerAs("fooID", "foo/name", "footag") } } } void testRemoveImage() { def mockWorkflow = new MockFor(WorkflowScript) mockWorkflow.demand.sh { cmd -> assert cmd == "docker rmi --force 'fooID'" } mockWorkflow.use { def runner = new PipelineRunner(new WorkflowScript()) runner.removeImage("fooID") } } void testRun() { def mockWorkflow = new MockFor(WorkflowScript) mockWorkflow.demand.timeout { Map args, Closure c -> assert args.time == 20 assert args.unit == "MINUTES" c() } mockWorkflow.demand.sh { cmd -> assert cmd == "exec docker run --rm 'foo'" } mockWorkflow.use { def runner = new PipelineRunner(new WorkflowScript()) runner.run("foo") } } }