From c86b7ad1fd39274e7a2c6807da9e5be33d11e510 Mon Sep 17 00:00:00 2001 From: Luis Toledo Date: Mon, 1 Apr 2024 14:27:18 -0300 Subject: [PATCH] refactor to execute multiples test classes --- .../functional/BasicIntegrationSpec.groovy | 12 +- .../PluginGroupIntegrationSpec.groovy | 16 +- .../groovy/functional/RundeckCompose.groovy | 144 ------------------ .../functional/TestConfiguration.groovy | 62 -------- .../base/BaseTestConfiguration.groovy | 134 ++++++++++++++++ .../functional/base/RundeckCompose.groovy | 58 +++++++ .../groovy/functional/util/TestUtil.groovy | 24 +++ 7 files changed, 224 insertions(+), 226 deletions(-) delete mode 100644 functional-test/src/test/groovy/functional/RundeckCompose.groovy delete mode 100644 functional-test/src/test/groovy/functional/TestConfiguration.groovy create mode 100644 functional-test/src/test/groovy/functional/base/BaseTestConfiguration.groovy create mode 100644 functional-test/src/test/groovy/functional/base/RundeckCompose.groovy diff --git a/functional-test/src/test/groovy/functional/BasicIntegrationSpec.groovy b/functional-test/src/test/groovy/functional/BasicIntegrationSpec.groovy index 646179e7..963cf556 100644 --- a/functional-test/src/test/groovy/functional/BasicIntegrationSpec.groovy +++ b/functional-test/src/test/groovy/functional/BasicIntegrationSpec.groovy @@ -1,23 +1,19 @@ package functional +import functional.base.BaseTestConfiguration import functional.util.TestUtil import org.rundeck.client.api.model.JobRun -import spock.lang.Shared import org.testcontainers.spock.Testcontainers @Testcontainers -class BasicIntegrationSpec extends TestConfiguration { - - @Shared - public static RundeckCompose rundeckEnvironment = new RundeckCompose(new File("src/test/resources/docker/docker-compose.yml").toURI()) - +class BasicIntegrationSpec extends BaseTestConfiguration { static String PROJ_NAME = 'ansible-test' def setupSpec() { - rundeckEnvironment.startCompose() - client = rundeckEnvironment.configureRundeck(PROJ_NAME) + startCompose() + configureRundeck(PROJ_NAME) } def "test simple inline playbook"(){ diff --git a/functional-test/src/test/groovy/functional/PluginGroupIntegrationSpec.groovy b/functional-test/src/test/groovy/functional/PluginGroupIntegrationSpec.groovy index 64d8b5fd..09ca61cf 100644 --- a/functional-test/src/test/groovy/functional/PluginGroupIntegrationSpec.groovy +++ b/functional-test/src/test/groovy/functional/PluginGroupIntegrationSpec.groovy @@ -1,27 +1,19 @@ package functional +import functional.base.BaseTestConfiguration import functional.util.TestUtil -import org.rundeck.client.api.RundeckApi import org.rundeck.client.api.model.JobRun -import org.rundeck.client.util.Client import org.testcontainers.spock.Testcontainers -import spock.lang.Shared @Testcontainers -class PluginGroupIntegrationSpec extends TestConfiguration { - - @Shared - public static RundeckCompose rundeckEnvironment = new RundeckCompose(new File("src/test/resources/docker/docker-compose.yml").toURI()) - - @Shared - Client client +class PluginGroupIntegrationSpec extends BaseTestConfiguration { static String PROJ_NAME = 'ansible-plugin-group-test' def setupSpec() { - rundeckEnvironment.startCompose() - client = rundeckEnvironment.configureRundeck(PROJ_NAME) + startCompose() + configureRundeck(PROJ_NAME) } def "test simple inline playbook"(){ diff --git a/functional-test/src/test/groovy/functional/RundeckCompose.groovy b/functional-test/src/test/groovy/functional/RundeckCompose.groovy deleted file mode 100644 index 7c398aae..00000000 --- a/functional-test/src/test/groovy/functional/RundeckCompose.groovy +++ /dev/null @@ -1,144 +0,0 @@ -package functional - -import com.jcraft.jsch.JSch -import com.jcraft.jsch.KeyPair -import functional.util.TestUtil -import okhttp3.RequestBody -import org.rundeck.client.RundeckClient -import org.rundeck.client.api.RundeckApi -import org.rundeck.client.api.model.ProjectItem -import org.rundeck.client.util.Client -import org.testcontainers.containers.DockerComposeContainer -import org.testcontainers.containers.wait.strategy.Wait - -import java.nio.file.Files -import java.nio.file.attribute.PosixFilePermission -import java.time.Duration - -class RundeckCompose extends DockerComposeContainer { - - public static final String RUNDECK_IMAGE = System.getenv("RUNDECK_TEST_IMAGE") ?: System.getProperty("RUNDECK_TEST_IMAGE") - public static final String NODE_USER_PASSWORD = "testpassword123" - public static final String NODE_KEY_PASSPHRASE = "testpassphrase123" - public static final String USER_VAULT_PASSWORD = "vault123" - - RundeckCompose(URI composeFilePath) { - super(new File(composeFilePath)) - - withExposedService("rundeck", 4440, - Wait.forHttp("/api/41/system/info").forStatusCode(403).withStartupTimeout(Duration.ofMinutes(5)) - ) - withEnv("RUNDECK_IMAGE", RUNDECK_IMAGE) - withEnv("NODE_USER_PASSWORD", NODE_USER_PASSWORD) - } - - - def startCompose() { - //generate SSH private key for node authentication - File keyPath = new File("src/test/resources/docker/keys") - generatePrivateKey(keyPath.getAbsolutePath(),"id_rsa") - generatePrivateKey(keyPath.getAbsolutePath(),"id_rsa_passphrase", NODE_KEY_PASSPHRASE) - - start() - } - - - Client configureRundeck(String projectName){ - - //configure rundeck api - String address = getServiceHost("rundeck",4440) - Integer port = getServicePort("rundeck",4440) - def rdUrl = "http://${address}:${port}/api/41" - System.err.println("rdUrl: $rdUrl") - Client client = RundeckClient.builder().with { - baseUrl rdUrl - passwordAuth('admin', 'admin') - logger(new TestLogger()) - build() - } - //add private key - RequestBody requestBody = RequestBody.create(new File("src/test/resources/docker/keys/id_rsa"), Client.MEDIA_TYPE_OCTET_STREAM) - def keyResult = client.apiCall {api-> api.createKeyStorage("project/$projectName/ssh-node.key", requestBody)} - - //add private key with passphrase - requestBody = RequestBody.create(new File("src/test/resources/docker/keys/id_rsa_passphrase"), Client.MEDIA_TYPE_OCTET_STREAM) - keyResult = client.apiCall {api-> api.createKeyStorage("project/$projectName/ssh-node-passphrase.key", requestBody)} - - //add passphrase - requestBody = RequestBody.create(NODE_KEY_PASSPHRASE.getBytes(), Client.MEDIA_TYPE_X_RUNDECK_PASSWORD) - keyResult = client.apiCall {api-> api.createKeyStorage("project/$projectName/ssh-node-passphrase.pass", requestBody)} - - //add node user ssh-password - requestBody = RequestBody.create(NODE_USER_PASSWORD.getBytes(), Client.MEDIA_TYPE_X_RUNDECK_PASSWORD) - keyResult = client.apiCall {api-> api.createKeyStorage("project/$projectName/ssh-node.pass", requestBody)} - - //user vault password - requestBody = RequestBody.create(USER_VAULT_PASSWORD.getBytes(), Client.MEDIA_TYPE_X_RUNDECK_PASSWORD) - keyResult = client.apiCall {api-> api.createKeyStorage("project/$projectName/vault-user.pass", requestBody)} - - //create project - def projList = client.apiCall(api -> api.listProjects()) - - if (!projList*.name.contains(projectName)) { - def project = client.apiCall(api -> api.createProject(new ProjectItem(name: projectName))) - } - - //import project - File projectFile = TestUtil.createArchiveJarFile(projectName, new File("src/test/resources/project-import/" + projectName)) - RequestBody body = RequestBody.create(projectFile, Client.MEDIA_TYPE_ZIP) - client.apiCall(api -> - api.importProjectArchive(projectName, "preserve", true, true, true, true, true, true, true, [:], body) - ) - - //wait for node to be available - def result = client.apiCall {api-> api.listNodes(projectName,".*")} - def count =0 - - while(result.get("ssh-node")==null && count<5){ - sleep(2000) - result = client.apiCall {api-> api.listNodes(projectName,".*")} - count++ - } - - return client - } - - static def generatePrivateKey(String filePath, String keyName, String passphrase = null){ - JSch jsch=new JSch() - KeyPair keyPair=KeyPair.genKeyPair(jsch, KeyPair.RSA) - if(passphrase){ - keyPair.writePrivateKey(filePath + File.separator + keyName, passphrase.getBytes()) - }else{ - keyPair.writePrivateKey(filePath + File.separator + keyName) - } - - keyPair.writePublicKey(filePath + File.separator + keyName + ".pub", "test private key") - - keyPair.dispose() - - File privateKey = new File(filePath + File.separator + keyName) - Set perms = new HashSet() - perms.add(PosixFilePermission.OWNER_READ) - perms.add(PosixFilePermission.OWNER_WRITE) - Files.setPosixFilePermissions(privateKey.toPath(), perms) - } - - - static class TestLogger implements Client.Logger { - @Override - void output(String out) { - println(out) - } - - @Override - void warning(String warn) { - System.err.println(warn) - } - - @Override - void error(String err) { - System.err.println(err) - } - } - -} diff --git a/functional-test/src/test/groovy/functional/TestConfiguration.groovy b/functional-test/src/test/groovy/functional/TestConfiguration.groovy deleted file mode 100644 index 32fffc73..00000000 --- a/functional-test/src/test/groovy/functional/TestConfiguration.groovy +++ /dev/null @@ -1,62 +0,0 @@ -package functional - -import org.rundeck.client.api.RundeckApi -import org.rundeck.client.api.model.ExecLog -import org.rundeck.client.api.model.ExecOutput -import org.rundeck.client.api.model.ExecutionStateResponse -import org.rundeck.client.util.Client -import spock.lang.Shared -import spock.lang.Specification - -class TestConfiguration extends Specification{ - - @Shared - Client client - - ExecutionStateResponse waitForJob(String executionId){ - def finalStatus = [ - 'aborted', - 'failed', - 'succeeded', - 'timedout', - 'other' - ] - - while(true) { - ExecutionStateResponse result=client.apiCall { api-> api.getExecutionState(executionId)} - if (finalStatus.contains(result?.getExecutionState()?.toLowerCase())) { - return result - } else { - sleep (10000) - } - } - - } - - - List getLogs(String executionId){ - def offset = 0 - def maxLines = 1000 - def lastmod = 0 - boolean isCompleted = false - - List logs = [] - - while (!isCompleted){ - ExecOutput result = client.apiCall { api -> api.getOutput(executionId, offset,lastmod, maxLines)} - isCompleted = result.completed - offset = result.offset - lastmod = result.lastModified - - logs.addAll(result.entries) - - if(result.unmodified){ - sleep(5000) - }else{ - sleep(2000) - } - } - - return logs - } -} diff --git a/functional-test/src/test/groovy/functional/base/BaseTestConfiguration.groovy b/functional-test/src/test/groovy/functional/base/BaseTestConfiguration.groovy new file mode 100644 index 00000000..fd0e2243 --- /dev/null +++ b/functional-test/src/test/groovy/functional/base/BaseTestConfiguration.groovy @@ -0,0 +1,134 @@ +package functional.base + +import functional.util.TestUtil +import okhttp3.RequestBody +import org.rundeck.client.api.RundeckApi +import org.rundeck.client.api.model.ExecLog +import org.rundeck.client.api.model.ExecOutput +import org.rundeck.client.api.model.ExecutionStateResponse +import org.rundeck.client.api.model.ProjectItem +import org.rundeck.client.util.Client +import spock.lang.Shared +import spock.lang.Specification + +class BaseTestConfiguration extends Specification{ + + @Shared + Client client + + @Shared + public static RundeckCompose rundeckEnvironment + + public static final String NODE_USER_PASSWORD = "testpassword123" + public static final String NODE_KEY_PASSPHRASE = "testpassphrase123" + public static final String USER_VAULT_PASSWORD = "vault123" + + def startCompose() { + if(rundeckEnvironment==null){ + //generate SSH private key for node authentication + File keyPath = new File("src/test/resources/docker/keys") + TestUtil.generatePrivateKey(keyPath.getAbsolutePath(),"id_rsa") + TestUtil.generatePrivateKey(keyPath.getAbsolutePath(),"id_rsa_passphrase", NODE_KEY_PASSPHRASE) + + rundeckEnvironment = new RundeckCompose(new File("src/test/resources/docker/docker-compose.yml").toURI()) + rundeckEnvironment.start() + } + + client = rundeckEnvironment.getClient() + } + + ExecutionStateResponse waitForJob(String executionId){ + def finalStatus = [ + 'aborted', + 'failed', + 'succeeded', + 'timedout', + 'other' + ] + + while(true) { + ExecutionStateResponse result=client.apiCall { api-> api.getExecutionState(executionId)} + if (finalStatus.contains(result?.getExecutionState()?.toLowerCase())) { + return result + } else { + sleep (10000) + } + } + + } + + + List getLogs(String executionId){ + def offset = 0 + def maxLines = 1000 + def lastmod = 0 + boolean isCompleted = false + + List logs = [] + + while (!isCompleted){ + ExecOutput result = client.apiCall { api -> api.getOutput(executionId, offset,lastmod, maxLines)} + isCompleted = result.completed + offset = result.offset + lastmod = result.lastModified + + logs.addAll(result.entries) + + if(result.unmodified){ + sleep(5000) + }else{ + sleep(2000) + } + } + + return logs + } + + def configureRundeck(String projectName){ + + //add private key + RequestBody requestBody = RequestBody.create(new File("src/test/resources/docker/keys/id_rsa"), Client.MEDIA_TYPE_OCTET_STREAM) + def keyResult = client.apiCall {api-> api.createKeyStorage("project/$projectName/ssh-node.key", requestBody)} + + //add private key with passphrase + requestBody = RequestBody.create(new File("src/test/resources/docker/keys/id_rsa_passphrase"), Client.MEDIA_TYPE_OCTET_STREAM) + keyResult = client.apiCall {api-> api.createKeyStorage("project/$projectName/ssh-node-passphrase.key", requestBody)} + + //add passphrase + requestBody = RequestBody.create(NODE_KEY_PASSPHRASE.getBytes(), Client.MEDIA_TYPE_X_RUNDECK_PASSWORD) + keyResult = client.apiCall {api-> api.createKeyStorage("project/$projectName/ssh-node-passphrase.pass", requestBody)} + + //add node user ssh-password + requestBody = RequestBody.create(NODE_USER_PASSWORD.getBytes(), Client.MEDIA_TYPE_X_RUNDECK_PASSWORD) + keyResult = client.apiCall {api-> api.createKeyStorage("project/$projectName/ssh-node.pass", requestBody)} + + //user vault password + requestBody = RequestBody.create(USER_VAULT_PASSWORD.getBytes(), Client.MEDIA_TYPE_X_RUNDECK_PASSWORD) + keyResult = client.apiCall {api-> api.createKeyStorage("project/$projectName/vault-user.pass", requestBody)} + + //create project + def projList = client.apiCall(api -> api.listProjects()) + + if (!projList*.name.contains(projectName)) { + def project = client.apiCall(api -> api.createProject(new ProjectItem(name: projectName))) + } + + //import project + File projectFile = TestUtil.createArchiveJarFile(projectName, new File("src/test/resources/project-import/" + projectName)) + RequestBody body = RequestBody.create(projectFile, Client.MEDIA_TYPE_ZIP) + client.apiCall(api -> + api.importProjectArchive(projectName, "preserve", true, true, true, true, true, true, true, [:], body) + ) + + //wait for node to be available + def result = client.apiCall {api-> api.listNodes(projectName,".*")} + def count =0 + + while(result.get("ssh-node")==null && count<5){ + sleep(2000) + result = client.apiCall {api-> api.listNodes(projectName,".*")} + count++ + } + + } +} diff --git a/functional-test/src/test/groovy/functional/base/RundeckCompose.groovy b/functional-test/src/test/groovy/functional/base/RundeckCompose.groovy new file mode 100644 index 00000000..3f843b3a --- /dev/null +++ b/functional-test/src/test/groovy/functional/base/RundeckCompose.groovy @@ -0,0 +1,58 @@ +package functional.base + +import org.rundeck.client.RundeckClient +import org.rundeck.client.api.RundeckApi +import org.rundeck.client.util.Client +import org.testcontainers.containers.DockerComposeContainer +import org.testcontainers.containers.wait.strategy.Wait +import java.time.Duration + +class RundeckCompose extends DockerComposeContainer { + + public static final String RUNDECK_IMAGE = System.getenv("RUNDECK_TEST_IMAGE") ?: System.getProperty("RUNDECK_TEST_IMAGE") + + + RundeckCompose(URI composeFilePath) { + super(new File(composeFilePath)) + + withExposedService("rundeck", 4440, + Wait.forHttp("/api/41/system/info").forStatusCode(403).withStartupTimeout(Duration.ofMinutes(5)) + ) + withEnv("RUNDECK_IMAGE", RUNDECK_IMAGE) + withEnv("NODE_USER_PASSWORD", BaseTestConfiguration.NODE_USER_PASSWORD) + } + + Client getClient(){ + //configure rundeck api + String address = getServiceHost("rundeck",4440) + Integer port = getServicePort("rundeck",4440) + def rdUrl = "http://${address}:${port}/api/41" + System.err.println("rdUrl: $rdUrl") + Client client = RundeckClient.builder().with { + baseUrl rdUrl + passwordAuth('admin', 'admin') + logger(new TestLogger()) + build() + } + return client + } + + + static class TestLogger implements Client.Logger { + @Override + void output(String out) { + println(out) + } + + @Override + void warning(String warn) { + System.err.println(warn) + } + + @Override + void error(String err) { + System.err.println(err) + } + } + +} diff --git a/functional-test/src/test/groovy/functional/util/TestUtil.groovy b/functional-test/src/test/groovy/functional/util/TestUtil.groovy index 63904f0e..44d5354d 100644 --- a/functional-test/src/test/groovy/functional/util/TestUtil.groovy +++ b/functional-test/src/test/groovy/functional/util/TestUtil.groovy @@ -1,7 +1,11 @@ package functional.util +import com.jcraft.jsch.JSch +import com.jcraft.jsch.KeyPair import org.rundeck.client.api.model.ExecLog +import java.nio.file.Files +import java.nio.file.attribute.PosixFilePermission import java.text.SimpleDateFormat import java.util.jar.JarEntry import java.util.jar.JarOutputStream @@ -65,4 +69,24 @@ class TestUtil { return listNodeResultStatus.stream().collect(Collectors.toMap(s -> s.toString().split("=")[0], s -> Integer.valueOf(s.toString().split("=")[1]))) } + static def generatePrivateKey(String filePath, String keyName, String passphrase = null){ + JSch jsch=new JSch() + KeyPair keyPair=KeyPair.genKeyPair(jsch, KeyPair.RSA) + if(passphrase){ + keyPair.writePrivateKey(filePath + File.separator + keyName, passphrase.getBytes()) + }else{ + keyPair.writePrivateKey(filePath + File.separator + keyName) + } + + keyPair.writePublicKey(filePath + File.separator + keyName + ".pub", "test private key") + + keyPair.dispose() + + File privateKey = new File(filePath + File.separator + keyName) + Set perms = new HashSet() + perms.add(PosixFilePermission.OWNER_READ) + perms.add(PosixFilePermission.OWNER_WRITE) + Files.setPosixFilePermissions(privateKey.toPath(), perms) + } + }