[[nightlies]]
= Nightly Deploys

:Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF 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.
:page-partial:

This page describes how we deploy the xref:docs:starters:simpleapp.adoc[], xref:docs:starters:helloworld.adoc[HelloWorld App] and the xref:docs:referenceapp:about.adoc[] each night, to a Azure App Container Apps (a PaaS based on top of k8s).

The actual CI jobs can be found in the link:https://github.com/apache-causeway-committers/causeway-nightly-deploys[apache-causeway-committers/causeway-nightly-deploys] repo.

== Azure Container Apps

Following link:https://learn.microsoft.com/en-gb/azure/container-apps/get-started?ocid=AID3042118&tabs=bash[azure docs] for ACA

=== Prereqs

* link:https://learn.microsoft.com/en-us/cli/azure/install-azure-cli[install Azure CLI]

=== Authenticate

Authenticate and prepare working environment

* login
+
[source,bash]
----
az login
----
+
Confirm using web browser.

* add ACA extension
+
[source,bash]
----
az extension add --name containerapp --upgrade
----
+
This takes a minute or two.

* register the `Microsoft.OperationalInsights` provider for the Azure Monitor Log Analytics workspace
+
[source,bash]
----
az provider register --namespace Microsoft.OperationalInsights
----
+
(as have not used it before).



=== Setup the resource group and ACA environment

In Azure, all resources live in resource groups.
We will use a single resource group.

One of the resources to be set up is an ACA environment, which creates a secure boundary around a group of container apps.
Container Apps deployed to the same environment are deployed in the same virtual network and write logs to the same Log Analytics workspace.
We will use a single ACA environment.

Create a resource group to organize the services related to the container apps.

* set these environment variables:
+
[source,bash]
----
RESOURCE_GROUP="causeway-nightlies-rg"
LOCATION="uksouth"
CONTAINERAPPS_ENVIRONMENT="causeway-nightlies-aca-env"
----

* create the resource group
+
[source,bash]
----
az group create \
  --name $RESOURCE_GROUP \
  --location $LOCATION
----
+
resulting in:
+
[source,bash]
----
{
  "id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/causeway-nightlies-rg",
  "location": "ukwest",
  "managedBy": null,
  "name": "causeway-nightlies-rg",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": null,
  "type": "Microsoft.Resources/resourceGroups"
}
----

* next, create the ACA environment:
+
[source,bash]
----
az containerapp env create \
  --name $CONTAINERAPPS_ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION
----
+
resulting in:
+
[source,bash]
----
No Log Analytics workspace provided.
Generating a Log Analytics workspace with name "workspace-causewaynightliesrgT0WN"

Container Apps environment created. To deploy a container app, use: az containerapp create --help

{
  "id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/causeway-nightlies-rg/providers/Microsoft.App/managedEnvironments/causeway-nightlies-aca-env",
  "location": "uksouth",
  "name": "causeway-nightlies-aca-env",
  "properties": {
    "appLogsConfiguration": {
      "destination": "log-analytics",
      "logAnalyticsConfiguration": {
        "customerId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
      }
    },
    "customDomainConfiguration": {
      "customDomainVerificationId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    },
    "defaultDomain": "graytree-90c75749.uksouth.azurecontainerapps.io",
    "provisioningState": "Succeeded",
    "staticIp": "20.108.221.167",
    "zoneRedundant": false
  },
  "resourceGroup": "causeway-nightlies-rg",
  "sku": {
    "name": "Consumption"
  },
  "systemData": {
    "createdAt": "2023-01-02T06:54:25.6911574",
    "createdBy": "dan@haywood-associates.co.uk",
    "createdByType": "User",
    "lastModifiedAt": "2023-01-02T06:54:25.6911574",
    "lastModifiedBy": "dan@haywood-associates.co.uk",
    "lastModifiedByType": "User"
  },
  "type": "Microsoft.App/managedEnvironments"
}
----


== Create Container App

We will create a container app manually.
Later, in the <<#automation,automation>> section, we will simply update the container (replacing the image with a new one).

We'll start with the link:https://hub.docker.com/repository/docker/apache/causeway-app-demo-jpa[apache/causeway-app-demo-jpa] Docker image:

* set further environment variables:
+
[source,bash]
----
APP_NAME="demo-wicket-jpa"
IMAGE_NAME="apache/causeway-demo-jpa"
IMAGE_VERSION="2.0.0-M9.20221018-1911-d3980668"
----

* create the app
+
include::partial$az-containerapp-create.adoc[]
+
resulting in:
+
[source,bash]
----
Container app created. Access your app at https://demo-wicket-jpa.graytree-90c75749.uksouth.azurecontainerapps.io/
----

* Change scaling from default (1-10) to just a single instance.
+
image::nightly-deploys/image-2023-01-03-08-31-58-018.png[width=600]


== Use Lets Encrypt for SSL Certs

as per: link:https://dev.to/shibayan/how-to-quickly-setup-a-lets-encrypt-certificate-in-azure-container-apps-3nd7[this blog post] blog, the link:https://github.com/shibayan/containerapps-acmebot[shibayan/containerapps-acmebot] repo provides an Azure deployment that handles the LetsEncrypt certificates.

Following its link:https://github.com/shibayan/containerapps-acmebot/wiki/Getting-Started[Getting-Started] README:

=== DNS management

Created DNS zone for `causeway.dev`, and then a child zone `apps.causeway.dev`.

NOTE: the intention is to change this to a more suitable domain.


=== Deploy the Services

* from the link:https://github.com/shibayan/containerapps-acmebot/wiki/Getting-Started#1-deploy-acmebot[README]:
+
image::nightly-deploys/image-2023-01-03-17-58-33-718.png[width=600]

* add params:
+
image::nightly-deploys/image-2023-01-03-18-07-57-499.png[width=800]
+
results in:
+
image::nightly-deploys/image-2023-01-03-18-10-33-567.png[width=800]


=== Enable App Service Authentication & Access control (IAM)

Continuing with the link:https://github.com/shibayan/containerapps-acmebot/wiki/Getting-Started#3-enable-app-service-authentication[README]:

* add an identity provider to the function app:
+
image::nightly-deploys/image-2023-01-03-18-16-01-614.png[width=800]

* add contributor role assignment:
+
image::nightly-deploys/image-2023-01-03-18-22-59-542.png[width=1000]


=== Access function app

Continuing further with the link:https://github.com/shibayan/containerapps-acmebot/wiki/Getting-Started#5-access-to-function-app[README]:

* locate the name of the function app (`func-acmebot-y3a6` above)

* navigate to link:https://func-acmebot-y3a6.azurewebsites.net/add-certificate[], and grant permissions to access the site:
+
image::nightly-deploys/image-2023-01-03-18-27-32-832.png[width=300]


* complete dialog:
+
image::nightly-deploys/image-2023-01-04-06-23-41-099.png[width=600]
+
and `Submit`.

* The docs say "after a few tens of seconds, the certificate will be issued".
Indeed so:
+
image::nightly-deploys/image-2023-01-04-06-26-42-960.png[width=400]
+
In the DNS zone, it creates this `TXT` record:
+
image::nightly-deploys/image-2023-01-04-06-29-02-083.png[width=600]

* Also create a `CNAME` for the app:
+
image::nightly-deploys/image-2023-01-04-06-25-56-926.png[width=400]

* Confirm that the app can be accessed:
+
image::nightly-deploys/image-2023-01-04-06-31-36-293.png[width=800]


== Deploy Remaining Apps

For each app, the steps are:

* Set common environment variables
+
[source,bash]
----
RESOURCE_GROUP="causeway-nightlies-rg"
CONTAINERAPPS_ENVIRONMENT="causeway-nightlies-aca-env"
----

* Set app-specific environment variables

* Create the container app
* Manually configure scaling
* Manually setup `CNAME` record in `apps.causeway.dev` DNS zone
* Using the acmebot function app, link:https://func-acmebot-y3a6.azurewebsites.net/add-certificate[add the certificate] for the container app


=== demo-wicket-jpa

Done previously

=== demo-wicket-jpa-snapshot

Steps:

* Environment variables
+
[source,bash]
----
APP_NAME="demo-wicket-jpa-snapshot"
IMAGE_NAME="apache/causeway-demo-jpa"
IMAGE_VERSION="latest"
----

* Deploy
+
include::partial$az-containerapp-create.adoc[]

* Manually Configure Scaling

* Manually setup DNS
+
to `demo-wicket-jpa-snapshot.apps.causeway.dev`

* Register with LetsEncrypt Function App




=== demo-wicket-jdo

Steps:

* Environment variables
+
[source,bash]
----
APP_NAME="demo-wicket-jdo"
IMAGE_NAME="apache/causeway-demo-jdo"
IMAGE_VERSION="2.0.0-M9.20221018-1911-d3980668"
----

* Deploy
+
include::partial$az-containerapp-create.adoc[]

* Manually Configure Scaling

* Manually setup DNS
+
to `demo-wicket-jdo.apps.causeway.dev`

* Register with LetsEncrypt Function App


=== demo-wicket-jdo-snapshot

Steps:

* Environment variables
+
[source,bash]
----
APP_NAME="demo-wicket-jdo-snapshot"
IMAGE_NAME="apache//causeway-demo-jdo"
IMAGE_VERSION="latest"
----

* Deploy
+
include::partial$az-containerapp-create.adoc[]

* Manually Configure Scaling

* Manually setup DNS
+
to `demo-wicket-jdo-snapshot.apps.causeway.dev`

* Register with LetsEncrypt Function App


=== simpleapp (jpa)

Steps:

* Environment variables
+
[source,bash]
----
APP_NAME="simpleapp-jpa"
IMAGE_NAME="apache/causeway-app-simpleapp"
IMAGE_VERSION="jpa"
----

* Deploy
+
include::partial$az-containerapp-create.adoc[]

* Manually Configure Scaling

* Manually setup DNS
+
to `simpleapp-jpa.apps.causeway.dev`

* Register with LetsEncrypt Function App

=== simpleapp (jpa-SNAPSHOT)

Steps:

* Environment variables
+
[source,bash]
----
APP_NAME="simpleapp-jpa-snapshot"
IMAGE_NAME="apache/causeway-app-simpleapp"
IMAGE_VERSION="jpa-SNAPSHOT"
----

* Deploy
+
include::partial$az-containerapp-create.adoc[]

* Manually Configure Scaling

* Manually setup DNS
+
to `simpleapp-jpa-snapshot.apps.causeway.dev`

* Register with LetsEncrypt Function App


=== simpleapp (jdo)

Steps:

* Environment variables
+
[source,bash]
----
APP_NAME="simpleapp-jdo"
IMAGE_NAME="apache/causeway-app-simpleapp"
IMAGE_VERSION="jdo"
----

* Deploy
+
include::partial$az-containerapp-create.adoc[]

* Manually Configure Scaling

* Manually setup DNS
+
to `simpleapp-jdo.apps.causeway.dev`

* Register with LetsEncrypt Function App

=== simpleapp (jdo-SNAPSHOT)

Steps:

* Environment variables
+
[source,bash]
----
APP_NAME="simpleapp-jdo-snapshot"
IMAGE_NAME="apache/causeway-app-simpleapp"
IMAGE_VERSION="jdo-SNAPSHOT"
----

* Deploy
+
include::partial$az-containerapp-create.adoc[]

* Manually Configure Scaling

* Manually setup DNS
+
to `simpleapp-jdo.apps.causeway.dev`

* Register with LetsEncrypt Function App

=== helloworld (jpa)

Steps:

* Environment variables
+
[source,bash]
----
APP_NAME="helloworld-jpa"
IMAGE_NAME="apache/causeway-app-helloworld"
IMAGE_VERSION="jpa"
----

* Deploy
+
include::partial$az-containerapp-create.adoc[]

* Manually Configure Scaling

* Manually setup DNS
+
to `helloworld-jpa.apps.causeway.dev`

* Register with LetsEncrypt Function App

=== helloworld (jpa-SNAPSHOT)

Steps:

* Environment variables
+
[source,bash]
----
APP_NAME="helloworld-jpa-snapshot"
IMAGE_NAME="apache/causeway-app-helloworld"
IMAGE_VERSION="jpa-SNAPSHOT"
----

* Deploy
+
include::partial$az-containerapp-create.adoc[]

* Manually Configure Scaling

* Manually setup DNS
+
to `helloworld-jpa-snapshot.apps.causeway.dev`

* Register with LetsEncrypt Function App


=== helloworld (jdo)

Steps:

* Environment variables
+
[source,bash]
----
APP_NAME="helloworld-jdo"
IMAGE_NAME="apache/causeway-app-helloworld"
IMAGE_VERSION="jdo"
----

* Deploy
+
include::partial$az-containerapp-create.adoc[]

* Manually Configure Scaling

* Manually setup DNS
+
to `helloworld-jdo.apps.causeway.dev`

* Register with LetsEncrypt Function App

=== helloworld (jdo-SNAPSHOT)

Steps:

* Environment variables
+
[source,bash]
----
APP_NAME="helloworld-jdo-snapshot"
IMAGE_NAME="apache/causeway-app-helloworld"
IMAGE_VERSION="jdo-SNAPSHOT"
----

* Deploy
+
include::partial$az-containerapp-create.adoc[]

* Manually Configure Scaling

* Manually setup DNS
+
to `helloworld-jdo-snapshot.apps.causeway.dev`

* Register with LetsEncrypt Function App




== Automation

With all of the applications created above, the nightly automation simply needs to update the apps with new images, using `az container revision copy`.
There is *_no need_* to create ACA environments etc from scratch.

=== Prereqs

We require an Azure Service Principal for the CI/CD job to use.

Following https://dev.to/erudinsky/how-to-deploy-to-azure-with-gitlab-authentication-credentials-store-runner-config-yaml-pipeline-example-37bp[this blog post] and also https://learn.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli#password-based-authentication[MS docs]:

* https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#register-an-application-with-azure-ad-and-create-a-service-principal[register an application]
+
My guess is that this is a 'deploy' application rather than the application being deployed.
+
image::nightly-deploys/image-2023-01-05-11-44-09-855.png[width=800]

// * https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#assign-a-role-to-the-application[assign a role to the application]

* lookup your subscriptionId

* as per the MS doc:
+
[source,powershell]
----
az ad sp create-for-rbac --name CausewayNightliesDeployPrincipal --role Owner --scopes "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
----
+
WARNING: must use powershell here.
+
should result in:
+
[source]
----
Found an existing application instance: (id) XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. We will patch it.
Creating 'Owner' role assignment under scope '/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
The output includes credentials that you must protect. Be sure that you do not include these credentials in your code or check the credentials into your source control. For more information, see https://aka.ms/azadsp-cli
{
  "appId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "displayName": "CausewayNightliesDeployPrincipal",
  "password": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "tenant": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
}
----

=== CI Jobs

The actual CI jobs can be found in the link:https://github.com/apache-causeway-committers/causeway-nightly-deploys[apache-causeway-committers/causeway-nightly-deploys] repo.

The main script consists of these commands:

[source,bash]
----
# ... derive $APPNAME
# ... derive $IMAGE

TIMESTAMP=$(date "+%Y%m%d-%H%M%S")
REVISION_SUFFIX="r${TIMESTAMP}"

az login --service-principal \
  --username "$AZ_APP_ID" \
  --password "$AZ_PASSWORD" \
  --tenant "$AZ_TENANT_ID"

az containerapp revision copy \
  --name "$APPNAME" \
  --resource-group "$RESOURCE_GROUP" \
  --image "$IMAGE" \
  --cpu "$CPU" \
  --memory "$MEMORY" \
  --revision-suffix "$REVISION_SUFFIX"
----
