Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
quovadis
Product and Topic Expert
Product and Topic Expert


























SAP Build Apps pro and community edition is a build-apps platforms featuring Composer Pro - a browser-based application designer with an integrated build service.




This brief is to explain how to make use of SAP Build Apps build service and have your SAP Build Apps web applications deployed directly to SAP BTP, Kyma runtime. With little effort.

That is leveraging k8s volumes and a public SAP Approuter image deployed to a kyma cluster and acting as a multi-tenant web application server.

There is a number of build-apps solutions on the market that allow to create and build (export) static web applications content.

In particular, SAP Build Apps offers an integrated build service which features both manual and automated deployment capability into SAP BTP CF runtime environment as well as it enables custom deployments to third-party hosting services.

Necessity is the mother of invention


I am a SAP BTP solution architect and am mostly working on business solutions that involve k8s/kyma runtime environments. And, I am eagerly using SAP Build Apps services in my daily work as well.

As I wanted to have my frontends and backends on the same side of the fence, I needed both simple and reliable way of deploying my SAP Build apps into SAP Kyma.








Q. What have I done?

A. In a nutshell, I extended the aforementioned SAP build Apps custom deployment pattern  to SAP BTP, Kyma runtime.  And that, without any reliance on additional BTP services.

I opted for a native k8s/kyma volume binding mechansim with SAP Approuter acting as an application server.

Once downloaded from the SAP Build Apps build service, a static web app is "injected" directly into a running SAP approuter context via a standard k8s volume binding mechanism.

Here goes my story...










Table of Contents

  1. Build-apps solutions with SAP BTP, Kyma runtime

  2. Solution brief.

  3. Deploying SAP Build apps into SAP Kyma.

  4. Solution highlights

  5. Conclusion

  6. Appendix.



Build-apps with SAP Kyma runtime.


The promise of build-apps (low/no/pro-code) is to enable developers to craft business-oriented applications. And that, with whatever runtime engine under the bonnet.

As of such, tools like SAP Build Apps can be quite prominent when it comes to help crafting such fine business apps, without being a car mechanic...

However, build-apps is not only about tools. It is also a powerful design paradigm behind many software solutions,

In particular this bodes well with the kubernetes environments where the desired state of a deployed solution is represented by a bunch of manifest templates - text files - which are a notary contract between a solution designer and k8s cluster.

Solution brief.


a. We need a static web application (build-app) and an application web server (approuter) to server it. Both are to be deployed to the same kyma cluster.

b. One can use SAP Build Apps designer to create a build-app and we deploy an approuter using the public SAPSE approuter docker image.

c. Then, one can use SAP Build Apps build service to download (to a local disk storage) a build-app packaged as a ZIP file.

d. Once downloaded, the ZIP file can either be deployed to an external hosting service (github,  firebase , BTP HTML5 repo) or bound as a volume of a SAP Approuter running on Kyma.

  • In order to be able to use a ZIP file as a volume, first the file needs to be uploaded into a cloud storage service, for instance into an objectstore.

  • However, I've chosen to upload it into a private github repository (cf. the following community post here.)


Let's summarize the list of the recipe ingredients:

Deploying SAP Build apps to SAP Kyma.


There are many ways to upload static content into kubernetes clusters and workloads.

(An obvious solution would be to leverage a SAP BTP HTML5 repository service. However, that would imply having a public internet route towards the HTML5 repo content. And that's not what I wanted with a SAP approuter deployed to a kyma environment.)

From the moment, a ZIP file is uploaded to a cloud storage, it can be made available to a kyma cluster using:

a. either Pod's in-memory pod ephemeral storage as a volume to host a static web application package developed with SAP Build Apps (index.html)

The following initContainers snippet demonstrates how to populate an emptyDir volume with data coming from a secure private github repository used as a cloud storage to an approuter:
      initContainers:
- name: install
image: alpine/curl
securityContext:
runAsUser: 1337
command:
- sh
- -c
- >-
curl -H 'Authorization: token {{ $token }}' -H 'Accept: application/vnd.github.v4.raw' -o /app/resources-dir/{{ $webappname }} -L {{ $image }}{{ $webappname }} &&

unzip /app/resources-dir/{{ $webappname }} -d /app/resources-dir
volumeMounts:
- name: resources-dir
mountPath: /app/resources-dir
subPath: resources-dir
volumes:
- name: resources-dir
emptyDir:
medium: "Memory"
sizeLimit: 50Mi

The build-app package is stored in a private github repository and is being pulled programmatically from there using the Github REST API to download a zip archive for a repository. (please refer to the approuter deployment file for more details.)

b. or ReadWriteMany persistent volume claims (for now, this is only supported on AZURE Kyma clusters)

values.yaml
clusterDomain: 
gateway:
ttlDaysAfterFinished: 0

services:
app:
name: faas-appgyver
appgyver:
webappname: app-*****_web_build-****.zip
token: ghp_*******************************
webapppath: https://github.com/api/v3/repos/<>/<>/contents/appgyver/

pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: resources-dir
labels:
{{- include "app.labels" . | nindent 4 }}
app: {{ .Values.services.app.name }}

spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: 'files'
volumeMode: Filesystem

job.yaml
{{- $deployment := .Values.services | default dict }}
{{- $image := $deployment.appgyver.webapppath | default "/" }}
{{- $webappname := $deployment.appgyver.webappname | default "app-*****_web_build-****.zip" }}
{{- $token := $deployment.appgyver.token | default "token" }}

apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Values.services.app.name }}
labels:
{{- include "app.labels" . | nindent 4 }}
app: {{ .Values.services.app.name }}
spec:
ttlSecondsAfterFinished: 100 ## {{ mul .Values.ttlDaysAfterFinished 24 60 60 }}
parallelism: 1
completions: 1
manualSelector: false

template:
metadata:
labels:
{{- include "app.selectorLabels" . | nindent 8 }}
sidecar.istio.io/inject: 'false'

spec:
restartPolicy: OnFailure

containers:
- image: busybox
name: busybox
command: ['sh', '-c', 'echo The job is running! && sleep 1']

volumeMounts:
- name: resources-dir
mountPath: /app/resources-dir
subPath: resources-dir

initContainers:
- name: install000
image: alpine
command:
- sh
- -c
- >-

chmod -R 777 /app && ls -lh -d /app/resources-dir

volumeMounts:
- name: resources-dir
mountPath: /app/resources-dir
subPath: resources-dir

- name: install001
image: alpine/curl

command:
- sh
- -c
- >-

curl -H 'Authorization: token {{ $token }}' -H 'Accept: application/vnd.github.v4.raw' -o /app/resources-dir/{{ $webappname }} -L {{ $image }}{{ $webappname }} &&

ls -l app/resources-dir &&

unzip -o /app/resources-dir/{{ $webappname }} -d /app/resources-dir &&

ls -l /app/resources-dir &&
ls -lh -d /app/resources-dir

volumeMounts:
- name: resources-dir
mountPath: /app/resources-dir
subPath: resources-dir

volumes:
- name: resources-dir
persistentVolumeClaim:
claimName: resources-dir

 

Solution highlights



  • deployments of static web applications directly to an approuter running on kyma clusters is done with a bunch of manifest text files with a few lines of github API automation code inside

  • deployments can be fully automated for instance with the github actions

  • no need to build custom docker images

  • no downtime when updating the static build-app content

  • build-app static content can be mixed up with sone other local static content (implemented as a config map to serve a default.html page, cf appendix for details). That would not be possible in the HTML5 repo scenario as documented here


Conclusion


Any caveats? As of now, there is not  much way to automate the SAP Build Apps build service download option. On the other hand, once a ZIP file has been downloaded and then committed to a github repository, the commit itself can trigger a github action to start an automated deployment to kyma.

Nonetheless, I am truly pleased with the outcome. Now, anyone can deploy build apps straight to k8s/kyma environments with little effort.

Last but not least, I hope you have enjoyed reading this blog. For the sake of time, I have offloaded the implementation notes to the appendix section below.

Feel free to provide your feedback and comments.

 




Appendix












Table of Contents

  1. Implementation and troubleshooting notes.

    1. Building web application packages with SAP Build Apps community edition platform.

      1. Examples of SAP Build Apps frontends on Kyma

      2. Integration with SAP Workzone and MS Teams



    2. SAP Build Apps web applications build service

    3. SAP Approuter - a multi-tenant web application server

    4. Approuter deployment manifest

    5. Pre-requisites/Disclaimers/Additional resources/Who am I? section





Implementation and troubleshooting notes


Building web application packages with SAP Build Apps community edition platform


SAP Build Apps development team has eventually published an iFrame component (for “WebView support for web”) that allows for easy iframe embedding in web applications.

Let's create a custom component which integrates the iFrame component. That way one can create fairly easily compositions of iframe-embedded widgets.

To make it simple and easy to consume I created a shared container as depicted below:


Published! Share token: ewDn4jhGHlkbzGbhAP1F7Q








Then, one can start using this component with a simple drag-and-drop in the legacy SAP Appgyver community editions apps.

On a side note the iFrame web component can be used to inject other React Native components, as explained in this blogpost:

SAP Build Apps on Kyma


Just a couple of examples of good looking apps running direclty on kyma.








Your virtual Musee du Louvre visit and mug painting powered by DALL·E:



or, eventually, an excursion (if ever), to Mars:



Last but not least, why not having a business application leveraging SAP HANA and SAP Analytics Cloud as well. All on Kyma folks.


Integration with SAP Build Workzone and MS Teams


That may be an interesting publishing option to many of you, especially it does not require much effort.






This is a SAP Fiori Launchpad. embedded into MS Teams as Teams app, where each tile is a SAP Build web-app running on Kyma


 



 

SAP Build Apps web applications build service


Please find below a comprehensive summary of SAP Build Apps build service capabilities.











SAP Build Apps can produce ready-to-deploy MTAR files which can be deployed to a BTP sub-account's HTML5 repository.




Headline:

  • MTAR deployments can be either manual or fully automated. CF runtime only, SAP managed approuter only.
















SAP Build Apps can produce ready-to-deploy static web applications content packaged as ZIP files.




Headlines:

  • ZIP deployments are manual only. SAP BTP CF and Kyma runtime environments are supported as well as any 3rd party hosting service (Github, Firebase, etc).

  • Both SAP managed (CF-only) and self-managed, multi-tenant (CF&Kyma) SAP Approuters supported.


Subsequently, static web-app ZIP files can be:











  • embedded as s static resource into a self-managed SAP Approuter. That is the option I've chosen by leveraging the kubernetes volume mechanism which allowed to use the public approuter docker image (instead of having to create my custom approuter docker image)



SAP Approuter as a multi-tenant web application server


SAP Approuter can be used not only as an application router. It can also act as a multi-tenant web application server by serving either:

It is also complementing the subscription-based SAP managed approuter , namely:

  • it can be deployed to either CF or Kyma runtime environments or both at a time

  • it does not require a BTP subscription to a Launchpad/Portal/SAP Build Workzone service

  • it does support a BTP [sub-account based] multi-tenancy model with custom domains

  • it can act as a web application hosting static web app resources

  • it is available as a ready-to-deploy SAPSE public docker file.


Good to know:

  • SAP approuter could be replaced by any other web-app hosting and routing service....For instance, (Google Firebase or Github hosting or any other third party hosting service)

  • However, one prominent advantage the SAP Approuter has is that it supports destinations including SAP BTP destinations, as well as it does support the BTP multi-tenancy model.

  • That means, one can get a multi-tenant web-app on SAP BTP platform out-of the-box with the full access to the entitled BTP services (with the BTP platform built-in authentication and authorization mechanism at hand).

  • Last but not least, any static web-app can be deployed to a BTP sub-account HTML5 repo as well, for instance with the help of the public SAP HTML5 deployer.



Approuter deployment manifest


SAPSE Approuter xs-app.yaml and deployment.yaml manifests:

xs-app.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.services.app.xsapp }}
data:
xs-app.json: |-
{
"welcomeFile": "index.html",
"authenticationMethod": "route",
"routes": [
{
"source": "^/user-api(.*)",
"target": "$1",
"service": "sap-approuter-userapi",
"authenticationType": "xsuaa",
"scope": "$XSAPPNAME.Admin"
}
,
{
"source": "^/dynamic_dest/([^/]+)/(.*)$",
"target": "$2",
"authenticationType": "xsuaa",
"preferLocal": true,
"destination": "$1"
}
,
{
"source": "/(.*)",
"authenticationType": "xsuaa",
"scope": "$XSAPPNAME.User",
"localDir": "resources-dir",
"cacheControl": "public, max-age=1000,must-revalidate"
}
]
}

deployment.yaml
{{- $deployment := .Values.services | default dict }}
{{- $image := $deployment.appgyver.webapppath | default "/" }}
{{- $webappname := $deployment.appgyver.webappname | default "app-*****_web_build-****.zip" }}
{{- $token := $deployment.appgyver.token | default "token" }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.services.app.name }}
labels:
{{- include "app.labels" . | nindent 4 }}
app: {{ .Values.services.app.name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "app.selectorLabels" $ | nindent 6 }}

template:
metadata:
labels:
{{- include "app.selectorLabels" . | nindent 8 }}

spec:
{{- if (include "app.ha" .) }}
topologySpreadConstraints:

{{- range $constraint := .Values.availability.topologySpreadConstraints }}
- maxSkew: {{ $constraint.maxSkew }}
topologyKey: {{ $constraint.topologyKey }}
whenUnsatisfiable: {{ $constraint.whenUnsatisfiable }}
labelSelector:
matchLabels:
{{- include "app.selectorLabels" $ | nindent 12 }}

{{- end }}
{{- end }}

containers:
- image: "{{ .Values.services.app.image.dockerID }}/{{ .Values.services.app.image.repository }}:{{ .Values.services.app.image.tag }}"
name: {{ .Values.services.app.name }}
imagePullPolicy: {{ .Values.services.app.image.pullPolicy }}
resources:
limits:
memory: 512Mi
cpu: "1"
requests:
memory: 128Mi
cpu: "0.1"
ports:
- name: http
containerPort: {{ .Values.services.app.image.port }}
env:
- name: SERVICE_BINDING_ROOT
value: /bindings
- name: PORT
value: '{{ .Values.services.app.image.port }}'

- name: XS_APP_LOG_LEVEL
value: debug

- name: DEBUG
value: '*' ## xssec:* ### https://www.npmjs.com/package/@sap/xssec

- name: COOKIES
value: "{ \"SameSite\":\"None\" }" ### https://me.sap.com/notes/0002953730
- name: SEND_XFRAMEOPTIONS
value: 'false' ###https://www.npmjs.com/package/@sap/approuter#x-frame-options-configuration
- name: ENABLE_X_FORWARDED_HOST_VALIDATION
value: 'true' ### https://www.npmjs.com/package/@sap/approuter#configurations
- name: INCOMING_CONNECTION_TIMEOUT
value: '1800000' # 180 seconds, the default value is 120 seconds
- name: CORS
value: |-
[
{
"uriPattern": "^(.*)$",
"allowedOrigin": [
{"host":"*", "protocol":"https"}
],
"allowedMethods": ["GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE"],
"allowedHeaders": ["Origin", "Accept", "X-Requested-With", "Content-Type", "Access-Control-Request-Method", "Access-Control-Request-Headers", "Authorization", "X-Sap-Cid", "X-Csrf-Token", "Accept-Language"],
"exposeHeaders": ["Accept", "Authorization", "X-Requested-With", "X-Sap-Cid", "Access-Control-Allow-Origin", "Access-Control-Allow-Credentials", "X-Csrf-Token", "Content-Type"]
}
]

envFrom:
- configMapRef:
name: {{ .Values.services.app.name }}
volumeMounts:
- name: resources-dir
mountPath: /app/resources-dir
subPath: resources-dir

- name: xs-app
mountPath: "/app/xs-app.json"
subPath: "xs-app.json"
readOnly: true
- name: resources
mountPath: "/app/resources-dir/default.html" ##"/app/resources/default.html"
subPath: "default.html"
readOnly: true
- name: faas-uaa
mountPath: "/bindings/faas-uaa"
readOnly: true
- name: faas-dest
mountPath: "/bindings/faas-dest"
readOnly: true

initContainers:
- name: install
image: alpine/curl
securityContext:
runAsUser: 1337
command:
- sh
- -c
- >-
curl -H 'Authorization: token {{ $token }}' -H 'Accept: application/vnd.github.v4.raw' -o /app/resources-dir/{{ $webappname }} -L {{ $image }}{{ $webappname }} &&

ls -l app/resources-dir &&

unzip /app/resources-dir/{{ $webappname }} -d /app/resources-dir &&

ls -l /app/resources-dir &&
ls -lh -d /app/resources-dir
volumeMounts:
- name: resources-dir
mountPath: /app/resources-dir
subPath: resources-dir

volumes:
- name: resources-dir
emptyDir:
medium: "Memory"
sizeLimit: 70Mi

- name: xs-app
configMap:
name: {{ .Values.services.app.xsapp }}
- name: resources
configMap:
name: {{ .Values.services.app.resources }}
- name: faas-uaa
secret:
secretName: {{ .Values.services.uaa.bindingSecretName }}
- name: faas-dest
secret:
secretName: {{ .Values.services.dest.bindingSecretName }}

Please note the usage of topology constraints in the above manifest. This is to make sure at least one replica is deployed to all three Availability Zones of a kyma cluster.







Per aspera ad astra. Who am I?


You can follow me in SAP Community: piotr.tesny

Pre-requisites:

  • Access to SAP Build Apps community edition

  • Access to SAP BTP global account with Kyma runtime environment (a SAP BTP-free tier or trial account will do.)

  • Access to a private github repository


Disclaimer:

  • The ideas presented in this blog are personal insights thus not necessarily endorsed by SAP.

  • I have no affiliation with any of the non-SAP brand names quoted in this brief.

  • Please note all the code snippets or gists are provided “as is”.

  • Images/data in this blog post is from SAP internal sandbox, sample data, or demo systems. Any resemblance to real data is purely coincidental.

  • Access to some online resources referenced in this blog may be subject to a contractual relationship with SAP and a S-user login may be required. Always refer to T&C.


Additional resources




 
5 Comments