diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7b9272e..6d0555a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -39,7 +39,7 @@ 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 "test/*" -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 "dotnet/*" -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
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index a91c6f5..c1b80de 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -10,7 +10,7 @@ on:
type:
description: Stack type
required: true
- default: 'main'
+ default: 'java'
type: choice
options:
- java
diff --git a/.github/workflows/on-push.yml b/.github/workflows/on-push.yml
index 7d444ae..e87fcf0 100644
--- a/.github/workflows/on-push.yml
+++ b/.github/workflows/on-push.yml
@@ -5,4 +5,4 @@ jobs:
uses: oracle-quickstart/appstack/.github/workflows/build.yml@main
with:
branch: ${{github.ref_name}}
- type: 'java'
+ type: 'dotnet'
diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml
index 39e813f..b332bc0 100644
--- a/.github/workflows/run-test.yml
+++ b/.github/workflows/run-test.yml
@@ -17,7 +17,7 @@ jobs:
uses: ./.github/workflows/build.yml
with:
branch: ${{github.ref_name}}
- type: 'java'
+ type: 'dotnet'
artifact-prefix: ${{github.sha}}_
run-test:
needs: call-workflow-passing-data
@@ -44,8 +44,8 @@ jobs:
OCI_USER_OCID: ${{ secrets.OCI_USER_OCID }}
OCI_PRIVATE_KEY_PEM: ${{ secrets.OCI_PRIVATE_KEY_PEM }}
OCI_FINGERPRINT: ${{ secrets.OCI_FINGERPRINT }}
- OCI_TEST_INPUT_JAVA: ${{ secrets.OCI_TEST_INPUT_JAVA }}
+ OCI_TEST_INPUT_DOTNET: ${{ secrets.OCI_TEST_INPUT_DOTNET }}
run: |
cd test
- echo $OCI_TEST_INPUT_JAVA > input-${{ inputs.type }}.json
+ echo $OCI_TEST_INPUT_DOTNET > input-${{ inputs.type }}.json
java -jar appstack-test.jar appstackfor${{ inputs.type }}.zip input-${{ inputs.type }}.json
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/dotnet/Dockerfile-dotnet.template b/dotnet/Dockerfile-dotnet.template
new file mode 100644
index 0000000..d483a5f
--- /dev/null
+++ b/dotnet/Dockerfile-dotnet.template
@@ -0,0 +1,61 @@
+# Copyright (c) 2023, Oracle and/or its affiliates.
+# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+
+# dockerfile for running the .NET application using Oracle Linux 8 image
+FROM container-registry.oracle.com/os/oraclelinux:8
+
+# install asp.net core runtime and its dependencies
+RUN dnf install -y aspnetcore-runtime-6.0 && \
+ rm -rf /var/cache/dnf
+
+# create a user so to avoid deploying the application in root directory
+RUN useradd -U -d /home/appuser appuser && \
+ mkdir /opt/dotnetapp && \
+ mkdir /opt/dotnetapp/apm && \
+ chown appuser:appuser /opt/dotnetapp /opt/dotnetapp/apm
+
+# switch the user and create a working directory
+USER appuser
+WORKDIR /opt/dotnetapp
+
+# copy application, certificate and wallet folder to working directory
+COPY --chown=appuser:appuser servercert.pfx /https/servercert.pfx
+COPY --chown=appuser:appuser /dotnetapp .
+COPY --chown=appuser:appuser wallet ./wallet
+
+# set environment variables for running the application on HTTPS port
+ENV DOTNET_RUNNING_IN_CONTAINER=true
+ENV ASPNETCORE_URLS="https://+:${exposed_port}"
+ENV ASPNETCORE_Kestrel__Certificates__Default__Password=${keystore_password}
+ENV ASPNETCORE_Kestrel__Certificates__Default__Path=/https/servercert.pfx
+
+# copy apm installer files to working directory
+COPY --chown=appuser:appuser /apm ./apm
+
+# set environment variables for apm
+ENV COR_ENABLE_PROFILING=1
+ENV COR_PROFILER="{918728DD-259F-4A6A-AC2B-B85E1B658318}"
+ENV COR_PROFILER_PATH_64=/opt/dotnetapp/apm/tracer-home/win-x64/OpenTelemetry.AutoInstrumentation.Native.dll
+ENV COR_PROFILER_PATH_32=/opt/dotnetapp/apm/tracer-home/win-x86/OpenTelemetry.AutoInstrumentation.Native.dll
+ENV CORECLR_ENABLE_PROFILING=1
+ENV CORECLR_PROFILER="{918728DD-259F-4A6A-AC2B-B85E1B658318}"
+ENV CORECLR_PROFILER_PATH_64=/opt/dotnetapp/apm/tracer-home/win-x64/OpenTelemetry.AutoInstrumentation.Native.dll
+ENV CORECLR_PROFILER_PATH_32=/opt/dotnetapp/apm/tracer-home/win-x86/OpenTelemetry.AutoInstrumentation.Native.dll
+ENV DOTNET_ADDITIONAL_DEPS=/opt/dotnetapp/apm/tracer-home/AdditionalDeps
+ENV DOTNET_SHARED_STORE=/opt/dotnetapp/apm/tracer-home/store
+ENV DOTNET_STARTUP_HOOKS=/opt/dotnetapp/apm/tracer-home/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll
+ENV OTEL_DOTNET_AUTO_HOME=/apm/tracer-home
+ENV OTEL_DOTNET_AUTO_INTEGRATIONS_FILE=/opt/dotnetapp/apm/tracer-home/integrations.json
+ENV OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES="OpenTelemetry.ODP"
+ENV OTEL_SERVICE_NAME="${application_name}"
+ENV OTEL_LOGS_EXPORTER="none"
+ENV OTEL_DOTNET_AUTO_EXCLUDE_PROCESSES="dotnet.exe,dotnet"
+ENV OTEL_EXPORTER_OTLP_ENDPOINT="${endpoint}/20200101/opentelemetry"
+ENV OTEL_EXPORTER_OTLP_HEADERS="Authorization=dataKey ${private_data_key}"
+ENV ENABLE_BACKGROUND_ODP=true
+ENV ENABLE_CONNECTION_ODP=true
+
+EXPOSE ${exposed_port}
+
+# set the entrypoint of the container to run the application
+ENTRYPOINT ["dotnet", "${dll_name}" ${program_arguments}]
\ No newline at end of file
diff --git a/dotnet/build-artifact.yaml.template b/dotnet/build-artifact.yaml.template
new file mode 100644
index 0000000..c7c62ca
--- /dev/null
+++ b/dotnet/build-artifact.yaml.template
@@ -0,0 +1,71 @@
+# Copyright (c) 2023, Oracle and/or its affiliates.
+# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+# This workflow will build and push a .Net application to OCI based on an artifact
+version: 0.1
+component: build
+timeoutInSeconds: 10000
+shell: bash
+env:
+ vaultVariables:
+ OCI_TOKEN : "${oci_token}"
+ DB_USER_PASSWORD : "${db_user_password}"
+ WALLET_PASSWORD : "${wallet_password}"
+inputArtifacts:
+ - name: dotnetapp
+ type: GENERIC_ARTIFACT
+ artifactId: $${artifactId}
+ registryId: ${registryId}
+ path: ${artifact_path}
+ version: $${artifact_version}
+ location: $${OCI_WORKSPACE_DIR}/${config_repo_name}/${fileName}
+steps:
+ - type: Command
+ name: Unzip wallet
+ timeoutInSeconds: 600
+ failImmediatelyOnError: true
+ command: |
+ cd $${OCI_WORKSPACE_DIR}/${config_repo_name}
+ unzip wallet.zip -d wallet
+ - type: Command
+ name: Download oraclepki and add username and password to wallet
+ timeoutInSeconds: 300
+ failImmediatelyOnError: true
+ command: |
+ cd $${OCI_WORKSPACE_DIR}/${config_repo_name}
+ curl -o oraclepki.jar "https://repo1.maven.org/maven2/com/oracle/database/security/oraclepki/23.2.0.0/oraclepki-23.2.0.0.jar" -L
+ echo -e '#/bin/sh\njava -cp oraclepki.jar oracle.security.pki.OracleSecretStoreTextUI -wrl wallet -createCredential "${db_connection_url}" "${db_username}" "'$${DB_USER_PASSWORD}'" <> add-credential-wallet.sh
+ sh add-credential-wallet.sh
+ - type: Command
+ name: Unzip dotnet app
+ timeoutInSeconds: 600
+ failImmediatelyOnError: true
+ command: |
+ cd $${OCI_WORKSPACE_DIR}/${config_repo_name}
+ unzip ${fileName}
+ cp -r ${artifact_location} dotnetapp
+ - type: Command
+ name: Get dotnet apm agent
+ timeoutInSeconds: 600
+ failImmediatelyOnError: true
+ command: |
+ cd $${OCI_WORKSPACE_DIR}/${config_repo_name}
+ oci os object get --namespace idhph4hmky92 --bucket-name prod-agent-binaries --file apm-dotnet-agent-installer-0.6.0.136.zip --name com/oracle/apm/agent/dotnet/apm-dotnet-agent-installer/0.6.0.136/apm-dotnet-agent-installer-0.6.0.136.zip
+ unzip apm-dotnet-agent-installer-0.6.0.136.zip -d apm
+ - type: Command
+ name: Build Docker image
+ timeoutInSeconds: 600
+ failImmediatelyOnError: true
+ command: |
+ docker build . --file Dockerfile --tag ${image_remote_tag}:${image_tag}-$${artifact_version} --tag ${image_latest_tag}
+ - type: Command
+ name: Login to repo
+ timeoutInSeconds: 900
+ failImmediatelyOnError: true
+ command: |
+ echo $${OCI_TOKEN} | docker login ${container_registry_repo} --username ${login} --password-stdin
+ - type: Command
+ name: Push image
+ timeoutInSeconds: 600
+ failImmediatelyOnError: true
+ command: |
+ docker push ${image_remote_tag} --all-tags
diff --git a/dotnet/build-repo.yaml.template b/dotnet/build-repo.yaml.template
new file mode 100644
index 0000000..4370b58
--- /dev/null
+++ b/dotnet/build-repo.yaml.template
@@ -0,0 +1,92 @@
+# Copyright (c) 2023, Oracle and/or its affiliates.
+# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+# This workflow will build and push a .Net application to OCI when a commit is
+# pushed to your default branch.
+version: 0.1
+component: build
+timeoutInSeconds: 3600
+shell: bash
+env:
+ variables:
+ JAVA_HOME : "/usr/java/latest"
+ vaultVariables:
+ OCI_TOKEN : "${oci_token}"
+ DB_USER_PASSWORD : "${db_user_password}"
+ WALLET_PASSWORD : "${wallet_password}"
+steps:
+ - type: Command
+ name: Install DotNet SDK
+ timeoutInSeconds: 600
+ failImmediatelyOnError: true
+ command: |
+ rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
+ yum install -y dotnet-sdk-6.0
+ onFailure:
+ - type: Command
+ command: |
+ echo $JAVA_HOME
+ timeoutInSeconds: 400
+ - type: Command
+ name: Build application
+ timeoutInSeconds: 600
+ failImmediatelyOnError: true
+ command: |
+ cd $${OCI_WORKSPACE_DIR}/${repo_name}
+ ${build_command}
+ onFailure:
+ - type: Command
+ command: |
+ pwd
+ timeoutInSeconds: 400
+ - type: Command
+ name: Create config files
+ timeoutInSeconds: 600
+ failImmediatelyOnError: true
+ command: |
+ cd $${OCI_WORKSPACE_DIR}/${config_repo_name}
+ unzip wallet.zip -d wallet
+ - type: Command
+ name: Download oraclepki and add username and password to wallet
+ timeoutInSeconds: 300
+ failImmediatelyOnError: true
+ command: |
+ cd $${OCI_WORKSPACE_DIR}/${config_repo_name}
+ curl -o oraclepki.jar "https://repo1.maven.org/maven2/com/oracle/database/security/oraclepki/23.2.0.0/oraclepki-23.2.0.0.jar" -L
+ echo -e '#/bin/sh\njava -cp oraclepki.jar oracle.security.pki.OracleSecretStoreTextUI -wrl wallet -createCredential "${db_connection_url}" "${db_username}" "'$${DB_USER_PASSWORD}'" <> add-credential-wallet.sh
+ sh add-credential-wallet.sh
+ - type: Command
+ name: Copy DotNet App
+ timeoutInSeconds: 600
+ failImmediatelyOnError: true
+ command: |
+ cd $${OCI_WORKSPACE_DIR}/${config_repo_name}
+ cp -r $${OCI_WORKSPACE_DIR}/${repo_name}/${artifact_location} dotnetapp
+ - type: Command
+ name: Get dotnet apm agent
+ timeoutInSeconds: 600
+ failImmediatelyOnError: true
+ command: |
+ cd $${OCI_WORKSPACE_DIR}/${config_repo_name}
+ oci os object get --namespace idhph4hmky92 --bucket-name prod-agent-binaries --file apm-dotnet-agent-installer-0.6.0.136.zip --name com/oracle/apm/agent/dotnet/apm-dotnet-agent-installer/0.6.0.136/apm-dotnet-agent-installer-0.6.0.136.zip
+ unzip apm-dotnet-agent-installer-0.6.0.136.zip -d apm
+ - type: Command
+ name: Build Docker image
+ timeoutInSeconds: 600
+ failImmediatelyOnError: true
+ command: |
+ cd $${OCI_WORKSPACE_DIR}/${repo_name}
+ export commit=$(git rev-list --all --max-count=1 --abbrev-commit)
+ cd $${OCI_WORKSPACE_DIR}/${config_repo_name}
+ docker build . --file Dockerfile --tag ${image_remote_tag}:${image_tag}-$commit --tag ${image_latest_tag}
+ - type: Command
+ name: Login to Container Registry
+ timeoutInSeconds: 900
+ failImmediatelyOnError: true
+ command: |
+ echo $${OCI_TOKEN} | docker login ${container_registry_repo} --username ${login} --password-stdin
+ - type: Command
+ name: Push image
+ timeoutInSeconds: 600
+ failImmediatelyOnError: true
+ command: |
+ docker push ${image_remote_tag} --all-tags
\ No newline at end of file
diff --git a/dotnet/dotnet-config-repo.tf b/dotnet/dotnet-config-repo.tf
new file mode 100644
index 0000000..6516c7e
--- /dev/null
+++ b/dotnet/dotnet-config-repo.tf
@@ -0,0 +1,24 @@
+# Copyright (c) 2023, Oracle and/or its affiliates.
+# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+
+resource "null_resource" "language_specific_files" {
+
+ depends_on = [
+ null_resource.create_config_repo
+ ]
+
+ # copy certificate
+ provisioner "local-exec" {
+ command = "cp server.p12 ./${local.config_repo_name}/servercert.pfx"
+ on_failure = fail
+ working_dir = "${path.module}"
+ }
+
+ # add certificate to git
+ provisioner "local-exec" {
+ command = "git add ./servercert.pfx"
+ on_failure = fail
+ working_dir = "${path.module}/${local.config_repo_name}"
+ }
+ count = (local.use-image ? 0 : 1)
+}
\ No newline at end of file
diff --git a/dotnet/dotnet-datasources.tf b/dotnet/dotnet-datasources.tf
new file mode 100644
index 0000000..b62aea6
--- /dev/null
+++ b/dotnet/dotnet-datasources.tf
@@ -0,0 +1,49 @@
+# Copyright (c) 2023, Oracle and/or its affiliates.
+# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+
+# dockerfile used to create image
+data "template_file" "dockerfile" {
+ template = file("${path.module}/Dockerfile-dotnet.template")
+ vars = {
+ namespace = "${local.namespace}"
+ bucket_name = "${local.bucket_name}"
+ keystore_password = random_password.keystore_password.result
+ application_name = var.application_name
+ private_data_key = data.oci_apm_data_keys.private_key.data_keys[0].value
+ endpoint = oci_apm_apm_domain.app_apm_domain.data_upload_endpoint
+ program_arguments = (var.program_arguments != null && var.program_arguments != "" ? format(", \"%s\" ", replace(trimspace(var.program_arguments), " ", "\", \"")): "")
+ exposed_port = var.exposed_port
+ dll_name = local.dll_name
+ }
+}
+
+# build spec file
+data "template_file" "oci_build_config" {
+ depends_on = [
+ oci_vault_secret.auth_token_secret
+ ]
+ template = "${(local.use-repository ? file("${path.module}/build-repo.yaml.template") : file("${path.module}/build-artifact.yaml.template"))}"
+ vars = {
+ image_remote_tag = "${local.image-remote-tag}"
+ image_latest_tag = "${local.image-latest-tag}"
+ image_tag = "${local.image-name}"
+ container_registry_repo = "${local.container-registry-repo}"
+ login = local.login_container
+ build_command = var.build_command
+ artifact_location = local.output_path
+ artifact_path = (local.use-artifact ? data.oci_artifacts_generic_artifact.app_artifact[0].artifact_path : "")
+ artifact_version = (local.use-artifact ? data.oci_artifacts_generic_artifact.app_artifact[0].version : "")
+ oci_token = local.auth_token_secret
+ repo_name = (local.use-repository ? data.oci_devops_repository.devops_repository[0].name : "")
+ config_repo_name = local.config_repo_name
+ artifactId = (local.use-artifact ? var.artifact_id : "")
+ registryId = (local.use-artifact ? var.registry_id : "")
+ fileName = "app.zip"
+ db_username = local.username
+ db_connection_url = local.escaped_connection_url
+ db_user_password = oci_vault_secret.db_user_password.id
+ wallet_password = oci_vault_secret.db_wallet_password.id
+ }
+}
+
+
diff --git a/dotnet/dotnet-variables.tf b/dotnet/dotnet-variables.tf
new file mode 100644
index 0000000..06cce5b
--- /dev/null
+++ b/dotnet/dotnet-variables.tf
@@ -0,0 +1,19 @@
+# Copyright (c) 2023, Oracle and/or its affiliates.
+# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+
+## .NET specific variables and locals
+locals {
+ # Get output folder path and dll name
+ output_path = "${dirname(var.artifact_location)}/"
+ dll_name = basename(var.artifact_location)
+ # path to the wallet
+ wallet_path = "/opt/dotnetapp/wallet"
+
+ driver_connection_url = (
+ var.use_existing_database
+ ? "${replace(data.oci_database_autonomous_database.autonomous_database.connection_strings[0].profiles[local.conn_url_index].value, "description= ", "description=")}"
+ : "${replace(oci_database_autonomous_database.database[0].connection_strings[0].profiles[local.conn_url_index].value, "description= ", "description=")}"
+ )
+ # Connection URL environment variable
+ connection_url_env = "ENV ${var.connection_url_env}=${local.escaped_connection_url}"
+}
\ No newline at end of file
diff --git a/dotnet/interface-app-config-group.yaml b/dotnet/interface-app-config-group.yaml
new file mode 100644
index 0000000..54e125a
--- /dev/null
+++ b/dotnet/interface-app-config-group.yaml
@@ -0,0 +1,20 @@
+# Copyright (c) 2023, Oracle and/or its affiliates.
+# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+
+ - use_connection_url_env
+ - connection_url_env
+ - use_tns_admin_env
+ - tns_admin_env
+ - use_username_env
+ - username_env
+ - use_password_env
+ - password_env
+ - title: "Other parameters"
+ variables:
+ - other_environment_variables
+ - program_arguments
+ - title: "Application configuration - SSL communication between backends and load balancer"
+ variables:
+ - cert_pem
+ - private_key_pem
+ - ca_pem
diff --git a/dotnet/interface-app-config.yaml b/dotnet/interface-app-config.yaml
new file mode 100644
index 0000000..7e6bde2
--- /dev/null
+++ b/dotnet/interface-app-config.yaml
@@ -0,0 +1,111 @@
+# Copyright (c) 2023, Oracle and/or its affiliates.
+# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+
+ # Application configuration
+ use_connection_url_env:
+ type: boolean
+ title: Set connection URL environment variable
+ default: true
+ description: Assuming that your application can consume an environment variable to configure the URL, this field can be used to specify the name of the environment variable.
+ connection_url_env:
+ type: string
+ title: Connection URL environment variable name
+ description: Specify the name of the environment variable. Its value will be set automatically by the stack.
+ required: true
+ default: "DATASOURCE_URL"
+ visible: use_connection_url_env
+ use_username_env:
+ type: boolean
+ title: Set username environment variable
+ description: Assuming that your application can consume an environment variable to configure the database username, this field can be used to specify the name of the environment variable.
+ default: false
+ visible:
+ eq:
+ - application_source
+ - "IMAGE"
+ username_env:
+ type: string
+ title: Database user environment variable name
+ description: Only the name of the environment variable is needed. The value will be automatically set. If a new database is created, the database ADMIN user will be used.
+ required: true
+ default: "DATASOURCE_USERNAME"
+ visible: use_username_env
+ use_password_env:
+ type: boolean
+ title: Set password environment variable
+ description: Assuming that your application can consume an environment variable to configure the database user's password, this field can be used to specify the name of the environment variable.
+ default: false
+ visible:
+ eq:
+ - application_source
+ - "IMAGE"
+ password_env:
+ type: string
+ title: Database user's password environment variable name
+ description: Specify the name of the environment variable. Its value will be set automatically by the stack. If a new database is created, the database ADMIN user will be used.
+ required: true
+ default: "DATASOURCE_PASSWORD"
+ visible: use_password_env
+ use_tns_admin_env:
+ type: boolean
+ title: Set TNS_ADMIN environment variable
+ description: Assuming that your application can consume an environment variable to configure TNS_ADMIN, this field can be used to specify the name of the environment variable.
+ default: true
+ visible:
+ eq:
+ - application_source
+ - "IMAGE"
+ tns_admin_env:
+ type: string
+ title: TNS_ADMIN environment variable name
+ description: Specify the name of the environment variable (Ex. TNS_ADMIN).
+ required: true
+ default: "TNS_ADMIN"
+ visible:
+ and:
+ - use_tns_admin_env
+ - eq:
+ - application_source
+ - "IMAGE"
+ # SSL properties
+ cert_pem:
+ type: text
+ multiline: true
+ title: SSL certificate
+ required: true
+ visible:
+ eq:
+ - application_source
+ - "IMAGE"
+ private_key_pem:
+ type: text
+ multiline: true
+ title: Private key
+ required: true
+ visible:
+ eq:
+ - application_source
+ - "IMAGE"
+ ca_pem:
+ type: text
+ multiline: true
+ title: CA certificate
+ required: true
+ visible:
+ eq:
+ - application_source
+ - "IMAGE"
+ # Other configuration
+ other_environment_variables:
+ type: string
+ title: Other environment variables
+ description: If your application can be configured through environment variables you can configure them here. Separate variables with semicolon (var1=value1;var2=value2).
+ program_arguments:
+ type: string
+ title: Program arguments
+ description: These space-separated program arguments are passed to the .Net application process at startup.
+ visible:
+ not:
+ - eq:
+ - application_source
+ - "IMAGE"
diff --git a/dotnet/interface-application-group.yaml b/dotnet/interface-application-group.yaml
new file mode 100644
index 0000000..931aa1e
--- /dev/null
+++ b/dotnet/interface-application-group.yaml
@@ -0,0 +1,17 @@
+# Copyright (c) 2023, Oracle and/or its affiliates.
+# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+
+ - title: "Application"
+ variables:
+ - application_name
+ - nb_copies
+ - application_source
+ - devops_compartment
+ - repo_name
+ - branch
+ - build_command
+ - artifact_location
+ - registry_id
+ - artifact_id
+ - image_path
+ - exposed_port
diff --git a/dotnet/interface-application.yaml b/dotnet/interface-application.yaml
new file mode 100644
index 0000000..4e0f8f5
--- /dev/null
+++ b/dotnet/interface-application.yaml
@@ -0,0 +1,106 @@
+# Copyright (c) 2023, Oracle and/or its affiliates.
+# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+
+ application_name:
+ type: string
+ required: true
+ title: Application name
+ description: This name will be used to name other needed resources.
+ nb_copies:
+ type: number
+ required: true
+ title: Number of deployments
+ description: This is the number of container instances that will be deployed.
+ application_source:
+ type: enum
+ title: "Application source"
+ default: "SOURCE_CODE"
+ description: You can deploy an application that is either a container image, a .NET artifact (zip archive) or from the source code.
+ required: true
+ enum:
+ - IMAGE
+ - ARTIFACT
+ - SOURCE_CODE
+ devops_compartment:
+ type: oci:identity:compartment:id
+ required: true
+ title: DevOps compartment
+ description: Compartment containing the DevOps project
+ default: ${compartment_id}
+ visible:
+ not:
+ - eq:
+ - application_source
+ - "SOURCE_CODE"
+ repo_name:
+ type: string
+ required: true
+ title: DevOps repository (OCID)
+ description: OCID of the repository containing the application source code.
+ visible:
+ eq:
+ - application_source
+ - "SOURCE_CODE"
+ branch:
+ type: string
+ required: true
+ title: Branch used for build / deployment
+ description: Name of the branch to be built, deployed and on which a trigger will be installed for continuous deployment.
+ default: main
+ visible:
+ eq:
+ - application_source
+ - "SOURCE_CODE"
+ build_command:
+ type: string
+ required: true
+ title: Application build command
+ description: "Example: dotnet publish {project-path}/{project-name}.csproj -c Release"
+ visible:
+ eq:
+ - application_source
+ - "SOURCE_CODE"
+ artifact_location:
+ type: string
+ required: true
+ title: Output Path
+ description: "Output path of publish folder. Example: bin/{BUILD-CONFIGURATION}/{TFM}/publish/{project-name}.dll"
+ visible:
+ not:
+ - eq:
+ - application_source
+ - "IMAGE"
+ artifact_id:
+ type: string
+ required: true
+ title: Artifact OCID
+ visible:
+ eq:
+ - application_source
+ - "ARTIFACT"
+ registry_id:
+ type: string
+ required: true
+ title: Artifact repository OCID
+ visible:
+ eq:
+ - application_source
+ - "ARTIFACT"
+ image_path:
+ type: string
+ required: true
+ title: Full path to the image in container registry
+ visible:
+ eq:
+ - application_source
+ - "IMAGE"
+ exposed_port:
+ type: string
+ required: true
+ title: Exposed port
+ description: This is the backend port on which the application is listening.
+ default: 8443
+ visible:
+ eq:
+ - application_source
+ - "IMAGE"
diff --git a/dotnet/interface-header.yaml b/dotnet/interface-header.yaml
new file mode 100644
index 0000000..72cf107
--- /dev/null
+++ b/dotnet/interface-header.yaml
@@ -0,0 +1,14 @@
+# Copyright (c) 2023, Oracle and/or its affiliates.
+# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+
+title: "App Stack for .NET"
+description: |
+ The App Stack for .NET can deploy existing .NET applications to serverless Container Instances behind a load balancer in the Oracle Cloud. It supports multiple options: the source code of the application is in DevOps, the application is uploaded as a .NET artifact (zip archive), or as a container image.
+schemaVersion: 1.1.0
+version: "v0.1.4"
+informationalText: |
+ For more information and product documentation please visit the App Stack project page .
+
+logoUrl: "https://cloudmarketplace.oracle.com/marketplace/content?contentId=58352039"
+
+locale: "en"
\ No newline at end of file
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