diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b15f649..1226082 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,6 +12,11 @@ on:
required: true
default: 'java'
type: string
+ artifact-prefix:
+ description: Artifact prefix
+ required: false
+ default: ''
+ type: string
jobs:
build:
runs-on: ubuntu-latest
@@ -34,10 +39,10 @@ jobs:
rm interface-application.yaml
sed -i -e "/###APP_CONFIG###/r interface-app-config.yaml" -e "//d" interface.yaml
rm interface-app-config.yaml
- zip -r ./appstackfor${{ inputs.type }}.zip . -x "*.git*" -x "java/*" -x "images/*" -x "listing/*" -x ".github/*" -x "*.md" -x "troubleshooting/*" -x "tutorials/*" -x "screenshots/*" -x "*.md"
+ zip -r ./appstackfor${{ inputs.type }}.zip . -x "*.git*" -x "java/*" -x "test/*" -x "images/*" -x "listing/*" -x ".github/*" -x "*.md" -x "troubleshooting/*" -x "tutorials/*" -x "screenshots/*" -x "*.md"
ls -lai
- name: upload-artifact
uses: actions/upload-artifact@v3
with:
- name: appstackfor${{ inputs.type }}
+ name: ${{ inputs.artifact-prefix }}appstackfor${{ inputs.type }}
path: ./appstackfor${{ inputs.type }}.zip
diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml
new file mode 100644
index 0000000..403b373
--- /dev/null
+++ b/.github/workflows/run-test.yml
@@ -0,0 +1,49 @@
+name: 'Test appstack'
+on:
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: Branch name
+ required: true
+ default: 'main'
+ type: string
+ type:
+ description: Stack type
+ required: true
+ default: 'java'
+ type: string
+jobs:
+ call-workflow-passing-data:
+ uses: ./.github/workflows/build.yml
+ with:
+ branch: ${{github.ref_name}}
+ type: 'java'
+ artifact-prefix: ${{github.sha}}_
+ run-test:
+ needs: call-workflow-passing-data
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ inputs.branch }}
+ - name: download-artifact
+ uses: actions/download-artifact@v2
+ with:
+ name: ${{github.sha}}_appstackfor${{ inputs.type }}
+ path: ./test
+ - name: install-java
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'oracle'
+ java-version: '17'
+ - name: run-test
+ env:
+ OCI_TENANCY_OCID: ${{ secrets.OCI_TENANCY_OCID }}
+ OCI_COMPARTMENT_OCID: ${{ secrets.OCI_COMPARTMENT_OCID }}
+ OCI_USER_OCID: ${{ secrets.OCI_USER_OCID }}
+ OCI_PRIVATE_KEY_PEM: ${{ secrets.OCI_PRIVATE_KEY_PEM }}
+ OCI_FINGERPRINT: ${{ secrets.OCI_FINGERPRINT }}
+ run: |
+ cd test
+ java -jar appstack-test.jar appstackfor${{ inputs.type }}.zip input-${{ inputs.type }}.json
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 496ee2c..7cc3c0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
-.DS_Store
\ No newline at end of file
+.DS_Store
+appstack-test/target/**
+appstack-test/.vscode/**
diff --git a/appstack-test/pom.xml b/appstack-test/pom.xml
new file mode 100644
index 0000000..8313111
--- /dev/null
+++ b/appstack-test/pom.xml
@@ -0,0 +1,124 @@
+
+
+
+ 4.0.0
+
+ oracle.appstack
+ appstack-test
+ 1.0-SNAPSHOT
+
+ appstack-test
+
+ http://www.example.com
+
+
+ UTF-8
+ 3.25.2
+
+
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-common
+ ${oci.version}
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-core
+ ${oci.version}
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-resourcemanager
+ ${oci.version}
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-common-httpclient-jersey
+ ${oci.version}
+
+
+ com.oracle.oci.sdk
+ oci-java-sdk-artifacts
+ ${oci.version}
+
+
+ jakarta.json
+ jakarta.json-api
+ 2.1.2
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+ 11
+
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ package
+
+ single
+
+
+
+
+
+ jar-with-dependencies
+
+
+
+ oracle.appstack.App
+
+
+
+
+
+
+
diff --git a/appstack-test/src/main/java/oracle/appstack/App.java b/appstack-test/src/main/java/oracle/appstack/App.java
new file mode 100644
index 0000000..a9e9da5
--- /dev/null
+++ b/appstack-test/src/main/java/oracle/appstack/App.java
@@ -0,0 +1,32 @@
+package oracle.appstack;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Base64;
+
+public class App {
+ public static void main(String[] args) {
+ if (args.length != 2) {
+ System.out.println("Wrong number of parameter.");
+ System.exit(-2);
+ }
+ try (FileInputStream zipFileInputStream = new FileInputStream(
+ args[0])) {
+ String testInput = args[1];
+ byte[] bytes = zipFileInputStream.readAllBytes();
+ String zipFileBase64Encoded = Base64.getEncoder().encodeToString(bytes);
+
+ TestRunner testRunner = new TestRunner(zipFileBase64Encoded);
+ String deployResult = testRunner.runTestSuite(testInput);
+ System.out.println(deployResult);
+ if (deployResult != "SUCCEDED") {
+ System.exit(-1);
+ }
+
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ System.exit(-1);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/appstack-test/src/main/java/oracle/appstack/TestInput.java b/appstack-test/src/main/java/oracle/appstack/TestInput.java
new file mode 100644
index 0000000..9f5b79d
--- /dev/null
+++ b/appstack-test/src/main/java/oracle/appstack/TestInput.java
@@ -0,0 +1,55 @@
+package oracle.appstack;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jakarta.json.JsonObject;
+import jakarta.json.JsonValue;
+import jakarta.json.JsonValue.ValueType;
+import jakarta.json.JsonString;
+
+public class TestInput {
+ private Map variables;
+ private List testUrls;
+ private String testName;
+
+ private TestInput() {
+ variables = new HashMap<>();
+ testUrls = new ArrayList<>();
+ }
+
+ public Map getVariables() {
+ return variables;
+ }
+
+ public List getTestUrls() {
+ return testUrls;
+ }
+
+ public String getTestName() {
+ return this.testName;
+ }
+
+ public static TestInput fromJsonObject(JsonObject jsonObject) {
+ TestInput testInput = new TestInput();
+ if (jsonObject.containsKey("test-name")) {
+ testInput.testName = jsonObject.getString("test-name");
+ }
+ if (jsonObject.containsKey("variables")) {
+ JsonObject variables = jsonObject.getJsonObject("variables");
+ for (String key : variables.keySet()) {
+ testInput.getVariables().put(key, variables.getString(key));
+ }
+ }
+ if (jsonObject.containsKey("test_urls")) {
+ for (JsonValue item : jsonObject.getJsonArray("test_urls")) {
+ if (item.getValueType() == ValueType.STRING)
+ testInput.getTestUrls().add(((JsonString) item).getString());
+ }
+ }
+ return testInput;
+ }
+
+}
\ No newline at end of file
diff --git a/appstack-test/src/main/java/oracle/appstack/TestInputList.java b/appstack-test/src/main/java/oracle/appstack/TestInputList.java
new file mode 100644
index 0000000..600fb29
--- /dev/null
+++ b/appstack-test/src/main/java/oracle/appstack/TestInputList.java
@@ -0,0 +1,43 @@
+package oracle.appstack;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import jakarta.json.Json;
+import jakarta.json.JsonValue;
+import jakarta.json.stream.JsonParser;
+import jakarta.json.stream.JsonParser.Event;
+
+public class TestInputList {
+
+ private List input;
+
+ private TestInputList() {
+ input = new ArrayList<>();
+ }
+
+ public static TestInputList fromJsonFile(String fileName) throws IOException {
+ TestInputList testInputList = new TestInputList();
+ try (FileInputStream fileInputStream = new FileInputStream(fileName);
+ JsonParser jsonParser = Json.createParser(fileInputStream)) {
+ if (jsonParser.hasNext()) {
+ if (jsonParser.next() == Event.START_ARRAY) {
+ for (JsonValue item : jsonParser.getArray()) {
+ TestInput testInput = TestInput.fromJsonObject(item.asJsonObject());
+ testInputList.input.add(testInput);
+ }
+ }
+ }
+ return testInputList;
+ } catch (IOException ex) {
+ throw ex;
+ }
+ }
+
+ public List getTestInputList() {
+ return this.input;
+ }
+
+}
diff --git a/appstack-test/src/main/java/oracle/appstack/TestRunner.java b/appstack-test/src/main/java/oracle/appstack/TestRunner.java
new file mode 100644
index 0000000..4b7ea84
--- /dev/null
+++ b/appstack-test/src/main/java/oracle/appstack/TestRunner.java
@@ -0,0 +1,338 @@
+package oracle.appstack;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import com.oracle.bmc.Region;
+import com.oracle.bmc.artifacts.ArtifactsClient;
+import com.oracle.bmc.artifacts.model.GenericArtifactSummary;
+import com.oracle.bmc.artifacts.requests.DeleteGenericArtifactRequest;
+import com.oracle.bmc.artifacts.requests.ListGenericArtifactsRequest;
+import com.oracle.bmc.artifacts.responses.DeleteGenericArtifactResponse;
+import com.oracle.bmc.artifacts.responses.ListGenericArtifactsResponse;
+import com.oracle.bmc.auth.AuthenticationDetailsProvider;
+import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider;
+import com.oracle.bmc.auth.StringPrivateKeySupplier;
+import com.oracle.bmc.resourcemanager.ResourceManagerClient;
+import com.oracle.bmc.resourcemanager.model.CreateApplyJobOperationDetails;
+import com.oracle.bmc.resourcemanager.model.CreateDestroyJobOperationDetails;
+import com.oracle.bmc.resourcemanager.model.CreateJobDetails;
+import com.oracle.bmc.resourcemanager.model.CreateStackDetails;
+import com.oracle.bmc.resourcemanager.model.CreateZipUploadConfigSourceDetails;
+import com.oracle.bmc.resourcemanager.model.DestroyJobOperationDetails;
+import com.oracle.bmc.resourcemanager.model.Job;
+import com.oracle.bmc.resourcemanager.model.Stack;
+import com.oracle.bmc.resourcemanager.model.ApplyJobOperationDetails.ExecutionPlanStrategy;
+import com.oracle.bmc.resourcemanager.model.Job.Operation;
+import com.oracle.bmc.resourcemanager.requests.CreateJobRequest;
+import com.oracle.bmc.resourcemanager.requests.CreateStackRequest;
+import com.oracle.bmc.resourcemanager.requests.GetJobRequest;
+import com.oracle.bmc.resourcemanager.requests.GetStackTfStateRequest;
+import com.oracle.bmc.resourcemanager.responses.CreateJobResponse;
+import com.oracle.bmc.resourcemanager.responses.CreateStackResponse;
+import com.oracle.bmc.resourcemanager.responses.GetJobResponse;
+import com.oracle.bmc.resourcemanager.responses.GetStackTfStateResponse;
+
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.json.stream.JsonParser;
+
+public class TestRunner {
+
+ // OCI configuration
+ private static final String TENANCY_SECRET = "OCI_TENANCY_OCID";
+ private static final String COMPARTMENT_SECRET = "OCI_COMPARTMENT_OCID";
+ private static final String USER_SECRET = "OCI_USER_OCID";
+ private static final String FINGERPRINT_SECRET = "OCI_FINGERPRINT";
+ private static final String PRIVATE_KEY_SECRET = "OCI_PRIVATE_KEY_PEM";
+
+ // Stack configuration
+ private final String zipFileBase64Encoded;
+
+ // OCI SDK
+ private final AuthenticationDetailsProvider provider;
+ private final ResourceManagerClient client;
+
+ public TestRunner(String zipFileBase64Encoded) {
+ this.zipFileBase64Encoded = zipFileBase64Encoded;
+
+ String tenancy_ocid = System.getenv(TENANCY_SECRET);
+ String user_ocid = System.getenv(USER_SECRET);
+ String private_key = System.getenv(PRIVATE_KEY_SECRET);
+ String fingerprint = System.getenv(FINGERPRINT_SECRET);
+
+ provider = SimpleAuthenticationDetailsProvider.builder()
+ .tenantId(tenancy_ocid)
+ .userId(user_ocid)
+ .fingerprint(fingerprint)
+ .privateKeySupplier(new StringPrivateKeySupplier(private_key))
+ .build();
+
+ client = ResourceManagerClient.builder().region(Region.US_PHOENIX_1).build(provider);
+
+ }
+
+ public String runTestSuite(String testFile) {
+ try {
+ String status = "FAILED";
+ TestInputList testInputList = TestInputList.fromJsonFile(testFile);
+ for (TestInput testInput : testInputList.getTestInputList()) {
+ status = run(testInput);
+ if (status == "FAILED") {
+ break;
+ }
+ }
+ return status;
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ return "FAILED";
+ }
+ }
+
+ public String run(TestInput testInput) {
+
+ System.out.println("Running : " + testInput.getTestName());
+ Stack stack = createStack(testInput.getTestName(), testInput.getVariables());
+ CreateJobResponse createJobResponse = createApplyJob(stack.getId());
+ String status = waitForJobCompleted(createJobResponse);
+ System.out.println("Create Stack:" + status);
+ Map terraformState = null;
+ if (status == "SUCCEDED") {
+ try {
+ terraformState = getTerraformState(stack.getId());
+ String url = terraformState.get("url");
+ System.out.println(url);
+ for (String path : testInput.getTestUrls()) {
+ status = checkUrl(url + path);
+ System.out.println("Check url(" + url + path + "): " + status);
+ if (status == "FAILED") {
+ break;
+ }
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ status = "FAILED";
+ }
+
+ if (terraformState != null) {
+ // delete artifact
+ String artifact_registry_id = terraformState.get("application_repository_id");
+ System.out.println(artifact_registry_id);
+ String compartment_id = terraformState.get("compartment_id");
+ System.out.println(compartment_id);
+ status = deleteArtifact(artifact_registry_id, compartment_id);
+ System.out.println("Delete Artifact:" + status);
+ // destroy stack
+ CreateJobResponse destroyJobResponse = createDestroyJob(stack.getId());
+ status = waitForJobCompleted(destroyJobResponse);
+ System.out.println(status);
+ System.out.println("Delete Stack:" + status);
+ }
+
+ }
+
+ return status;
+ }
+
+ public String deleteArtifact(String artifact_registry_id, String compartment_id) {
+
+ ArtifactsClient client = ArtifactsClient.builder().region(Region.US_PHOENIX_1).build(provider);
+ ListGenericArtifactsRequest listGenericArtifactsRequest = ListGenericArtifactsRequest.builder()
+ .compartmentId(compartment_id)
+ .repositoryId(artifact_registry_id)
+ .build();
+
+ ListGenericArtifactsResponse response = client.listGenericArtifacts(listGenericArtifactsRequest);
+ for (GenericArtifactSummary item : response.getGenericArtifactCollection().getItems()) {
+ DeleteGenericArtifactRequest deleteGenericArtifactRequest = DeleteGenericArtifactRequest.builder()
+ .artifactId(item.getId())
+ .opcRequestId(UUID.randomUUID().toString()).build();
+ DeleteGenericArtifactResponse deleteResponse = client.deleteGenericArtifact(deleteGenericArtifactRequest);
+ int statusCode = deleteResponse.get__httpStatusCode__();
+ if (statusCode < 200 || statusCode > 300) {
+ return "FAILED";
+ }
+ }
+ return "SUCCEDED";
+ }
+
+ public Map getTerraformState(String stackId) throws IOException {
+ Map values = new HashMap<>();
+ try {
+ GetStackTfStateRequest getStackTfStateRequest = GetStackTfStateRequest.builder()
+ .stackId(stackId)
+ .opcRequestId(UUID.randomUUID().toString()).build();
+
+ /* Send request to the Client */
+ GetStackTfStateResponse response = client.getStackTfState(getStackTfStateRequest);
+
+ // Parse JSON
+ JsonParser jsonParser = Json.createParser(response.getInputStream());
+
+ while (jsonParser.hasNext()) {
+ JsonParser.Event next = jsonParser.next();
+ if (next == JsonParser.Event.START_OBJECT) {
+ JsonObject object = jsonParser.getObject();
+ if (object.containsKey("outputs")) {
+ String url = object.getJsonObject("outputs").getJsonObject("app_url").getString("value");
+ values.put("url", url);
+ System.out.println("Got url");
+ }
+ if (object.containsKey("resources")) {
+ JsonArray resources = object.getJsonArray("resources");
+ for (int i = 0; i < resources.size(); i++) {
+ JsonObject resourceObject = resources.get(i).asJsonObject();
+ if (resourceObject.getString("name").equals("application_repository")) {
+ String id = resourceObject.getJsonArray("instances").get(0).asJsonObject().getJsonObject("attributes")
+ .getString("id");
+ values.put("application_repository_id", id);
+ System.out.println("Got application repository OCID");
+
+ String compartment_id = resourceObject.getJsonArray("instances").get(0).asJsonObject()
+ .getJsonObject("attributes")
+ .getString("compartment_id");
+ values.put("compartment_id", compartment_id);
+ System.out.println("Got compartment OCID");
+ break;
+ }
+ }
+ }
+ }
+ }
+ return values;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ throw ex;
+ }
+ }
+
+ public String checkUrl(String urlString) {
+ try {
+ URL url = new URL(urlString);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("GET");
+ int statusCode = connection.getResponseCode();
+ if (statusCode < 200 || statusCode > 300) {
+ System.out.println("Status code: " + statusCode);
+ return "FAILED";
+ }
+ return "SUCCEDED";
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return "FAILED";
+ }
+ }
+
+ private String waitForJobCompleted(CreateJobResponse createJobResponse) {
+
+ if (createJobResponse != null && createJobResponse.getJob() != null) {
+ Job.LifecycleState state = createJobResponse.getJob().getLifecycleState();
+ System.out.println("Job state : " + state.toString());
+
+ while (state != Job.LifecycleState.Succeeded && state != Job.LifecycleState.Failed
+ && state != Job.LifecycleState.Canceled) {
+ try {
+ TimeUnit.MINUTES.sleep(3);
+ state = getJobStatus(createJobResponse.getJob().getId(), createJobResponse.getOpcRequestId());
+ } catch (InterruptedException e) {
+ System.out.println("Sleep error");
+ }
+
+ System.out.println("Job state : " + state.toString());
+ }
+
+ return state == Job.LifecycleState.Succeeded ? "SUCCEDED" : "FAILED";
+ } else {
+ return "FAILED";
+ }
+ }
+
+ private CreateJobResponse createApplyJob(String stackId) {
+ CreateJobDetails createJobDetails = CreateJobDetails.builder()
+ .stackId(stackId)
+ .displayName("app-stack-test-apply-job-" + UUID.randomUUID().toString())
+ .operation(Operation.Apply)
+ .jobOperationDetails(CreateApplyJobOperationDetails.builder()
+ .executionPlanStrategy(ExecutionPlanStrategy.AutoApproved)
+ .build())
+ .build();
+
+ CreateJobRequest createJobRequest = CreateJobRequest.builder()
+ .createJobDetails(createJobDetails)
+ .opcRequestId("app-stack-test-apply-job-request-" + UUID.randomUUID()
+ .toString())
+ .opcRetryToken("app-stack-test-apply-job-retry-" + UUID.randomUUID().toString())
+ .build();
+
+ /* Send request to the Client */
+ return client.createJob(createJobRequest);
+
+ }
+
+ private CreateJobResponse createDestroyJob(String stackId) {
+ CreateJobDetails createJobDetails = CreateJobDetails.builder()
+ .stackId(stackId)
+ .displayName("app-stack-test-destroy-job-" + UUID.randomUUID().toString())
+ .operation(Operation.Destroy)
+ .jobOperationDetails(CreateDestroyJobOperationDetails.builder()
+ .executionPlanStrategy(DestroyJobOperationDetails.ExecutionPlanStrategy.AutoApproved)
+ .build())
+ .build();
+
+ CreateJobRequest createJobRequest = CreateJobRequest.builder()
+ .createJobDetails(createJobDetails)
+ .opcRequestId("app-stack-test-destroy-job-request-" + UUID.randomUUID()
+ .toString())
+ .opcRetryToken("app-stack-test-destroy-job-retry-" + UUID.randomUUID().toString())
+ .build();
+
+ /* Send request to the Client */
+ return client.createJob(createJobRequest);
+
+ }
+
+ private Job.LifecycleState getJobStatus(String jobId, String opcRequestId) {
+ GetJobRequest getJobRequest = GetJobRequest.builder()
+ .jobId(jobId)
+ .opcRequestId(opcRequestId).build();
+
+ GetJobResponse response = client.getJob(getJobRequest);
+ return response.getJob() == null ? Job.LifecycleState.Failed : response.getJob().getLifecycleState();
+
+ }
+
+ private Stack createStack(String name, Map variables) {
+
+ String compartment_id = System.getenv(COMPARTMENT_SECRET);
+
+ CreateStackDetails createStackDetails = CreateStackDetails.builder()
+ .compartmentId(compartment_id)
+ .displayName(LocalDateTime.now().toString() + name)
+ .description(name)
+ .configSource(CreateZipUploadConfigSourceDetails.builder()
+ .zipFileBase64Encoded(zipFileBase64Encoded).build())
+ .variables(variables)
+ .build();
+
+ CreateStackRequest createStackRequest = CreateStackRequest.builder()
+ .createStackDetails(createStackDetails)
+ .opcRequestId("app-stack-test-create-stack-request-"
+ + UUID.randomUUID()
+ .toString())
+ .opcRetryToken("app-stack-test-create-stack-retry-token-" + UUID.randomUUID().toString())
+ .build();
+
+ /* Send request to the Client */
+ CreateStackResponse response = client.createStack(createStackRequest);
+ return response.getStack();
+
+ }
+
+}
diff --git a/test/appstack-test.jar b/test/appstack-test.jar
new file mode 100644
index 0000000..0cd5bfd
Binary files /dev/null and b/test/appstack-test.jar differ