https://raw.githubusercontent.com/ajmaradiaga/feeds/main/scmt/topics/Serverless-Computing-blog-posts.xml SAP Community - Serverless Computing 2024-05-20T11:14:12.676204+00:00 python-feedgen Serverless Computing blog posts in SAP Community https://community.sap.com/t5/technology-blogs-by-sap/event-driven-architecture-now-available-for-sap-ecc-users/ba-p/13452636 Event-driven architecture – now available for SAP ECC users 2020-07-01T10:30:19+02:00 martin_bachmann https://community.sap.com/t5/user/viewprofilepage/user-id/182107 <H2 id="toc-hId-933422122">Overview</H2><BR /> <A href="https://en.wikipedia.org/wiki/Event-driven_architecture" target="_blank" rel="nofollow noopener noreferrer">Wikipedia</A> defines an event as a “significant change in state”. If for example a Material Master is being updated, other processes in other systems need to be informed. This whole journey of generating the event, channeling and processing is called Event-driven architecture (EDA).<BR /> <BR /> According to industry analysts, customers and user group feedback, event-driven architecture is already an important topic and will most likely become even more central in the future. As an example I still remember the last DSAG annual meeting in Mannheim (sitting in the keynote together with 1000nds of people…), listening to the great <A href="https://www.youtube.com/watch?v=sJBJVXWzCQA" target="_blank" rel="nofollow noopener noreferrer">Keynote</A> from Steffen Pietsch (in German only, from 0:32:00 on).<BR /> <H2 id="toc-hId-736908617">Event-driven architecture with SAP</H2><BR /> In the last couple of years, SAP has put a lot of effort into providing holistic support for EDA, just to mention a few highlights:<BR /> <UL><BR /> <LI>Enabled Backend Systems<BR /> <UL><BR /> <LI>S/4HANA Enterprise Events<BR /> <UL><BR /> <LI><A href="https://help.sap.com/viewer/810dfd34f2cc4f39aa8d946b5204fd9c/1909.001/en-US/c200f98fadb64ff1828ed5696c86fca2.html" target="_blank" rel="noopener noreferrer">On Premise</A></LI><BR /> <LI><A href="https://help.sap.com/viewer/a630d57fc5004c6383e7a81efee7a8bb/2005.500/en-US/56cf82e75f2a42de827b5dc30e48db64.html" target="_blank" rel="noopener noreferrer">Cloud</A></LI><BR /> </UL><BR /> </LI><BR /> <LI><A href="https://blogs.sap.com/2019/01/17/sap-cloud-platform-enterprise-messaging-as-an-eventbus-for-successfactors/" target="_blank" rel="noopener noreferrer">SAP SuccessFactors</A></LI><BR /> <LI><A href="https://api.sap.com/package/SAPSubscriptionBillingBusinessEvents?section=Artifacts" target="_blank" rel="noopener noreferrer">SAP Subscription Billing</A></LI><BR /> <LI><A href="https://blogs.sap.com/2019/07/04/how-to-connect-sap-cloud-platform-enterprise-messaging-to-sap-marketing-cloud/" target="_blank" rel="noopener noreferrer">SAP Marketing Cloud</A></LI><BR /> </UL><BR /> </LI><BR /> <LI>Central Event Bus<BR /> <UL><BR /> <LI><A href="https://help.sap.com/viewer/product/SAP_ENTERPRISE_MESSAGING/Cloud/en-US" target="_blank" rel="noopener noreferrer">SAP Cloud Platform Enterprise Messaging</A> as the central Event-Bus<BR /> <UL><BR /> <LI><A href="https://help.sap.com/viewer/368c481cd6954bdfa5d0435479fd4eaf/Cloud/en-US/5cc1a71231cb4cbfbfedb0cdc63e1488.html" target="_blank" rel="noopener noreferrer">AMQP Adapter for SAP Cloud Platform Integration</A></LI><BR /> </UL><BR /> </LI><BR /> <LI><A href="https://api.sap.com/themes/Events" target="_blank" rel="noopener noreferrer">SAP API Business Hub</A></LI><BR /> </UL><BR /> </LI><BR /> <LI>Connected Systems<BR /> <UL><BR /> <LI>AMQP or Cloud Events Trigger at Cloud Functions inside the<A href="https://help.sap.com/viewer/bf7b2ff68518427c85b30ac3184ad215/Cloud/en-US/9e0b187eb42941e6949a5b9e37102683.html" target="_blank" rel="noopener noreferrer"> SAP Cloud Platform Extension Factory, serverless runtime</A></LI><BR /> <LI><A href="https://blogs.sap.com/2020/03/03/sap-cloud-application-programming-model-and-enterprise-messaging-1-intro/" target="_blank" rel="noopener noreferrer">SAP Cloud Application Programming Model</A></LI><BR /> </UL><BR /> </LI><BR /> </UL><BR /> One missing – but important – stakeholder was the group of the SAP ECC customers. To let this group also participate on event-driven architecture we just released the ABAP Add-On&nbsp;<STRONG>SAP NetWeaver, add-on for event enablement</STRONG>. This Add-On works as an SDK, customers will be able to enable objects with just a few clicks, generating their own content (events). Of course the events generated in an SAP ECC System will follow the same standard (<A href="https://cloudevents.io/" target="_blank" rel="nofollow noopener noreferrer">https://cloudevents.io/</A>) as events coming from an S/4HANA System.<BR /> <H2 id="toc-hId-540395112">What is the benefit, especially for SAP ECC customers?</H2><BR /> Event-driven architecture in general is offering a lot of advantages like decoupling or the avoiding of polling. With this new Add-On, SAP ECC customers particularly, will be able to leverage SAP Cloud Platform as an extension platform even more – as now the event-centric pattern is also supported in addition to REST / OData via SAP Gateway. In combination with SAP S/4HANA this will open up some great new opportunities:<BR /> <UL><BR /> <LI>In order to serialize activities during the SAP S/4HANA Migration, it is now easier to port existing event-driven extensions from SAP ECC to SAP Cloud Platform. A guide on the general topic of side-by-side extensions is available <A href="https://www.sap.com/documents/2020/03/ceeea71f-8a7d-0010-87a3-c30de2ffd8ff.html" target="_blank" rel="noopener noreferrer">here</A></LI><BR /> <LI>This will bring back your SAP ECC System closer to the standard – and this will then reduce complexity during the migration to SAP S/4HANA</LI><BR /> <LI>As SAP S/4HANA already supports events, the switch to SAP S/4HANA will be easy – if you already designed the cloud extension with the SAP S/4HANA Events and OData services accordingly.</LI><BR /> </UL><BR /> Several missions are available showing in more detail on how to extend an S/4HANA System based on event-driven architecture (e.g <A href="https://discovery-center.cloud.sap/#/missiondetail/3156/3192" target="_blank" rel="nofollow noopener noreferrer">https://discovery-center.cloud.sap/#/missiondetail/3156/3192</A>). We are currently working on the same scenario – only using an SAP ECC system instead of an S/4HANA system.<BR /> <H2 id="toc-hId-343881607">Summary</H2><BR /> The Add-On <STRONG>SAP NetWeaver, add-on for event enablement</STRONG> (ASANWEE) is available for NetWeaver 7.31 and higher (<A href="https://help.sap.com/viewer/e966e6c0e61443ebaa0270a4bae4b363/1.0/en-US/3eba827c531344eb879d8e35022d90ba.html" target="_blank" rel="noopener noreferrer">Documentation</A>) and is based on an ABAP Add-On from the partner company ASAPIO (<A href="https://www.asapio.com/en/index.html" target="_blank" rel="nofollow noopener noreferrer">https://www.asapio.com/en/index.html</A>), but adapted for the usage with the SAP Cloud Platform.<BR /> <BR /> <STRONG>2020 - Dec 22nd - Update:</STRONG><BR /> <UL><BR /> <LI>Mission now available:<BR /> <UL><BR /> <LI><BR /> <P style="margin: 0mm;margin-bottom: .0001pt">Public Mission Board link: <A href="https://discovery-center.cloud.sap/missiondetail/3338/3384" target="_blank" rel="nofollow noopener noreferrer">https://discovery-center.cloud.sap/missiondetail/3338/3384</A></P><BR /> <P style="margin: 0mm;margin-bottom: .0001pt"></P><BR /> </LI><BR /> <LI><BR /> <P style="margin: 0mm;margin-bottom: .0001pt">Public Github link : <A href="https://github.com/SAP-samples/cloud-extension-ecc-business-process/tree/mission/mission" target="_blank" rel="nofollow noopener noreferrer">https://github.com/SAP-samples/cloud-extension-ecc-business-process/tree/mission/mission</A></P><BR /> </LI><BR /> </UL><BR /> </LI><BR /> <LI>Mentioned at TechEd Keynote 2020: <A href="https://youtu.be/wP0pL_Ps1dM?t=1195" target="_blank" rel="nofollow noopener noreferrer">https://youtu.be/wP0pL_Ps1dM?t=1195</A></LI><BR /> </UL><BR /> <STRONG>2021 - June 1st - Update</STRONG><BR /> <UL><BR /> <LI>AddOn is now also available for S/4HANA<BR /> <UL><BR /> <LI><A href="https://blogs.sap.com/2021/06/01/sap-netweaver-addon-for-event-enablement-now-also-released-for-s-4hana/" target="_blank" rel="noopener noreferrer">https://blogs.sap.com/2021/06/01/sap-netweaver-addon-for-event-enablement-now-also-released-for-s-4hana/</A></LI><BR /> <LI><A href="https://www.sap.com/dmc/exp/2021-06-sapphireinnovation-news/sap-enhances-support-for-event-based-integration/" target="_blank" rel="noopener noreferrer">https://www.sap.com/dmc/exp/2021-06-sapphireinnovation-news/sap-enhances-support-for-event-based-integration/&nbsp;</A></LI><BR /> </UL><BR /> </LI><BR /> </UL> 2020-07-01T10:30:19+02:00 https://community.sap.com/t5/technology-blogs-by-sap/writing-function-as-a-service-5-top-secret/ba-p/13469715 Writing Function-as-a-Service [5]: Top Secret 2020-07-07T13:37:43+02:00 CarlosRoggan https://community.sap.com/t5/user/viewprofilepage/user-id/5495 With other words:<BR /> <P style="text-align: center"><STRONG>Secrets</STRONG> and <STRONG>Config Maps</STRONG><BR /> in<BR /> <STRONG>SAP Cloud Platform Serverless Runtime</STRONG><BR /> What's that?</P><BR /> This blog is part of a&nbsp;<A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs/" target="_blank" rel="noopener noreferrer">series of tutorials</A>&nbsp;explaining how to write serverless functions using the Functions-as-a-Service offering in&nbsp;<STRONG>SAP Cloud Platform Serverless Runtime</STRONG><BR /> <P style="text-align: right">Quicklinks:<BR /> <SPAN style="font-size: smaller"><A href="#quickguide" target="_blank" rel="nofollow noopener noreferrer">Quick Guide</A><BR /> <A href="#projectfiles" target="_blank" rel="nofollow noopener noreferrer">Sample Code</A></SPAN></P><BR /> <SPAN style="color: #999999">So what about those Secrets and Config Maps?w</SPAN><BR /> Short answer:<BR /> To me, they look like simple property files<BR /> <BR /> Example:<BR /> We want to write a function which needs to call a different REST endpoint<BR /> We don’t want to hardcode the URL and credentials in the code<BR /> So we're looking for a way to store such info outside the code, somehow in some property files<BR /> In the <STRONG>server</STRONG><SPAN style="font-size: smaller">-and-file-system-</SPAN><STRONG>less Function</STRONG>&nbsp;world, there’s a special mechanism to do so.<BR /> <BR /> In this blog, we’re going through a simple example.<BR /> We're going to define a Secret and access it from within the code<BR /> <BR /> Note:<BR /> This blog shows the usage of Extension Center, but everything can be done in your <A href="https://blogs.sap.com/2020/06/24/writing-function-as-a-service-2-local-development" target="_blank" rel="noopener noreferrer">local</A> dev environment as well<BR /> <H2 id="toc-hId-934555078">Prerequisites</H2><BR /> You should already be familiar with the Function-as-a-Service part of <STRONG>SAP Cloud Platform Extension Center, serverless runtime</STRONG><BR /> Otherwise, see <A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs/" target="_blank" rel="noopener noreferrer">series of tutorials</A><BR /> <H2 id="toc-hId-738041573">Create Project</H2><BR /> We can <A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-1.1-first-function-using-extension-center/" target="_blank" rel="noopener noreferrer">create</A> a new Extension (Functions project) or reuse an existing project<BR /> <BR /> In my example, using "Template with function":<BR /> Extension name: <EM>topsecret</EM><BR /> Runtime: <EM>nodejs10</EM><BR /> Function name : <EM>secretfunction</EM><BR /> HTTP trigger name : <EM>showsecret</EM><BR /> <BR /> After project creation, we want to define <EM>secret</EM> and <EM>config map</EM>.<BR /> Currently, there’s no UI support, so we have to create files and folders and edit the <SPAN style="font-family: Courier New">faas.json</SPAN> manually<BR /> <H2 id="toc-hId-541528068">Define secret</H2><BR /> First of all, we have to understand that a secret is a logical artifact, handled by the FaaS runtime<BR /> A secret points to a folder, where a file is located (can be multiple)<BR /> That file contains the secret information<BR /> <BR /> To me, a <EM>secret</EM> is like a secret treasure map. It doesn’t contain the treasure itself, only the path<BR /> The hidden treasure can be found there and it contains …. tasty cat food<BR /> <BR /> So let's create files and folders first<BR /> <BR /> Note:<BR /> As usual, by choosing silly names, I’ve tried to make clear that you’re free to choose the file and folder names<BR /> There’s only one exception:<BR /> The “data” folder must have name as “data”<BR /> <BR /> <SPAN style="text-decoration: underline">Create Folder<BR /> </SPAN>To create a folder, select the <SPAN style="background-color: #ffe78f;font-family: Courier New">Code</SPAN> folder, then press the create-folder symbol<BR /> Enter folder name as <STRONG>data</STRONG><BR /> Then click on the new <SPAN style="background-color: #ffe78f;font-family: Courier New">data</SPAN> folder and create a subfolder called <STRONG>donotopenthisfolder<BR /> </STRONG>Click on folder <SPAN style="background-color: #ffe78f;font-family: Courier New">donotopenthisfolder</SPAN> and press the file symbol<BR /> Enter file name as <SPAN style="font-family: Courier New;background-color: #f5f5f5">hiddensecret.json</SPAN><BR /> <BR /> <SPAN style="text-decoration: underline">Enter secret values</SPAN><BR /> Now open the file&nbsp; <SPAN style="font-family: Courier New">hiddensecret.json<BR /> </SPAN>Paste the following content:<BR /> <PRE class="language-javascript"><CODE>{<BR /> "Max": {<BR /> "username": "max@maxmail.com",<BR /> "password": "max123"<BR /> },<BR /> "Joe": {<BR /> "username": "joe@joemail.com",<BR /> "password": "joe123"<BR /> }<BR /> }</CODE></PRE><BR /> That’s not it<BR /> I mean, we've only created a file with secret info.<BR /> That's not THE <EM>secret </EM>yet<BR /> Although the file is located in&nbsp;a folder which nobody would dare to open<BR /> <BR /> Note:<BR /> Make sure to press “Save And Deploy” from time to time<BR /> <H2 id="toc-hId-345014563">Define Config Map</H2><BR /> A <EM>config map</EM> is similar like a secret, but not so top secret.<BR /> We use <EM>config maps</EM> to store any configuration info which we don’t want to hardcode in the javascript module<BR /> Again, a config map is a file, located in a subfolder of the “data” folder<BR /> <BR /> <SPAN style="text-decoration: underline">Create folder</SPAN><BR /> Select the folder <SPAN style="background-color: #ffe78f;font-family: Courier New">data</SPAN> and create a subfolder with name <STRONG>cfg<BR /> </STRONG>Select the folder <SPAN style="background-color: #ffe78f;font-family: Courier New">cfg</SPAN> and create a file with name <SPAN style="font-family: Courier New;background-color: #f5f5f5">addressconfig.json</SPAN><BR /> <BR /> <SPAN style="text-decoration: underline">Enter configuration values</SPAN><BR /> Paste the following content which contains an example for possible REST endpoint URLs<BR /> <PRE class="language-javascript"><CODE>{<BR /> "dev": {<BR /> "user": "Max",<BR /> "url": "http://maxservice/endpoint"<BR /> },<BR /> "prod": {<BR /> "user": "Joe",<BR /> "url": "http://joeservice/endpoint"<BR /> }<BR /> }</CODE></PRE><BR /> <SPAN style="text-decoration: underline">Create another folder</SPAN><BR /> Select the folder <SPAN style="background-color: #ffe78f;font-family: Courier New">cfg</SPAN> and create a file with name <SPAN style="font-family: Courier New;background-color: #f5f5f5">addresstext.de</SPAN><BR /> <BR /> <SPAN style="text-decoration: underline">Enter configuration value</SPAN><BR /> This file contains an example for plain text in German language:<BR /> <PRE class="language-javascript"><CODE>Guten Morgen</CODE></PRE><BR /> Note:<BR /> Finally, our FaaS project should look like this:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/projectStructure.jpg" height="226" width="196" /></P><BR /> <BR /> <H2 id="toc-hId-148501058">Now really define Secret and Config Map</H2><BR /> Up to now we’ve only created files.<BR /> Now we need to declare them in the manifest file, such that the FaaS runtime can take care of them<BR /> So now we really DEFINE the secret and config maps<BR /> <BR /> We open <SPAN style="font-family: Courier New">faas.json<BR /> </SPAN>Since we’ve used the template to create the extension project, the file initially would look like this:<BR /> <BR /> &nbsp;<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/faasjson1.jpg" height="267" width="377" /></P><BR /> We can see the elements which sound promising: “secrets” and “configs”<BR /> And we can see that the secrets are defined and where they are referenced:<BR /> A function has to declare which secret and config it wants to use<BR /> <BR /> So now we can go ahead and enter the declarations and usages<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/faasjson2.jpg" height="209" width="392" /></P><BR /> As mentioned, a <EM>secret</EM> is a logical artifact which is defined as an entry in <SPAN style="font-family: Courier New">faas.json</SPAN><BR /> To make it less logical and more physical, it has the <SPAN style="font-family: Courier New">source</SPAN> property which points to a directory.<BR /> It is the directory which contains the files with configuration info<BR /> <PRE class="language-javascript"><CODE>"secrets": {<BR /> "mysecret": {<BR /> "source": "./data/donotopenthisfolder"<BR /> }<BR /> },<BR /> </CODE></PRE><BR /> And the secret has a name of our choice, such that it can be referenced.<BR /> <BR /> BTW, same procedure with <EM>config</EM><BR /> <BR /> Next step:<BR /> If we want to access secret info from the implementation of our function, we need to explicitly reference it.<BR /> To reference a secret, we just write the name in an array (can reference multiple secrets)<BR /> <PRE class="language-javascript"><CODE>"functions": {<BR /> "secretfunction": {<BR /> "secrets": ["mysecret"],<BR /> </CODE></PRE><BR /> Finally, the full <SPAN style="font-family: Courier New">faas.json</SPAN> looks as follows<BR /> <PRE class="language-javascript"><CODE>{<BR /> "project": "topsecret",<BR /> "version": "0.0.1",<BR /> "runtime": "nodejs10",<BR /> "library": "./lib",<BR /> "secrets": {<BR /> "mysecret": {<BR /> "source": "./data/donotopenthisfolder"<BR /> }<BR /> },<BR /> "configs": {<BR /> "myconfiguration": {<BR /> "source": "./data/cfg"<BR /> }<BR /> },<BR /> "functions": {<BR /> "secretfunction": {<BR /> "module": "index.js",<BR /> "handler": "handler",<BR /> "secrets": ["mysecret"],<BR /> "configs": ["myconfiguration"]<BR /> }<BR /> },<BR /> "triggers": {<BR /> "showsecret": {<BR /> "type": "HTTP",<BR /> "function": "secretfunction"<BR /> }<BR /> }<BR /> }<BR /> </CODE></PRE><BR /> Note: saveanddeploy<BR /> <H2 id="toc-hId--48012447">View the values</H2><BR /> Now let’s explore what we’ve just defined<BR /> In Extension Center, click on Form View, then “Secrets” tab and finally on the icon to view the details<BR /> The content of the secret is visualized in a nice way:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/secretDetails.jpg" height="141" width="303" /></P><BR /> The dialog is well aware that the value of a secret is secret<BR /> Anyways, I still believe it looks like cat food in a treasure chest...<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/catfood.jpg" height="145" width="258" /></P><BR /> Let’s also view the details of the config:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/configDetails.jpg" height="334" width="347" /></P><BR /> We can see that we have 2 config maps and that one has json content and the second one has string content (german string, BTW)<BR /> <BR /> Note the terminology:<BR /> The file name is the <EM>key</EM>, the file content is the <EM>value</EM><BR /> In fact, it is a (config) map with key and value<BR /> We will need that later<BR /> <H2 id="toc-hId--244525952">Access secret and config in function code</H2><BR /> We didn’t create the secret only for playing hide and seek<BR /> We want to access it in the impementation and extract the value<BR /> <BR /> <SPAN style="color: #999999">How to do this?</SPAN><BR /> <BR /> It is done with support of FaaS runtime, which offers a convenient API for it<BR /> When the runtime invokes our handler function, it passes an object to us which contains all required info: the <SPAN style="font-family: Courier New">context</SPAN> object<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/contextParam.jpg" height="49" width="238" /></P><BR /> <SPAN style="color: #999999">How to use it?</SPAN><BR /> <BR /> The convenience API offers access to the value of the <EM>secret</EM>, based on the name of the <EM>secret</EM><BR /> <PRE class="language-javascript"><CODE>context.getSecretValue...</CODE></PRE><BR /> Furthermore, we can choose if we want the response as plain String or as JSON<BR /> <PRE class="language-javascript"><CODE>context.getSecretValueJSON(…)</CODE></PRE><BR /> Next, we have to pass the information about which secret we want to use<BR /> Since there can be multiple secrets defined in <SPAN style="font-family: Courier New">faas.json</SPAN> and referenced by any function, we have to pass the <EM>secret</EM> name as first param<BR /> <BR /> Remember that the secret basically is a folder, such that we also have to pass the file name as second param:<BR /> <PRE class="language-javascript"><CODE>context.getSecretValueJSON('mysecret', 'hiddensecret.json')</CODE></PRE><BR /> As a response we get a JSON object (depending on the API-method we’ve chosen)<BR /> Example for node10 syntax:<BR /> <PRE class="language-javascript"><CODE>const credentials = await context.getSecretValueJSON('mysecret', 'hiddensecret.json');</CODE></PRE><BR /> The API overview can be found in the <A href="https://help.sap.com/viewer/bf7b2ff68518427c85b30ac3184ad215/Cloud/en-US/8382ef0e89044fb8b294660ae2f2bb14.html" target="_blank" rel="noopener noreferrer">SAP Help Portal</A><BR /> Example for the syntax<BR /> <PRE class="language-javascript"><CODE>getSecretValueJSON(name, key)</CODE></PRE><BR /> &nbsp;<BR /> <BR /> Explanation:<BR /> As already explained, the<BR /> “name” is the secret name (pointing to a folder)<BR /> “key” is the file name<BR /> <BR /> Now we can open our function in the Code editor and replace the generated content with the following snippet:<BR /> <PRE class="language-javascript"><CODE>module.exports = { <BR /> handler: async function (event, context) { <BR /> const credentials = await context.getSecretValueJSON('mysecret', 'hiddensecret.json');<BR /> const config = await context.getConfigValueJSON('myconfiguration', 'addressconfig.json')<BR /> const greeting = await context.getConfigValueString('myconfiguration', 'addresstext.de');<BR /> <BR /> const url = config.dev.url;<BR /> const user = credentials.Max.username<BR /> const pwd = credentials.Max.password<BR /> <BR /> console.log(`Calling URL: '${url}' with user '${user}' and password '${pwd}' and greeting '${greeting}'`)<BR /> return "Top Secret";<BR /> } <BR /> }</CODE></PRE><BR /> The example shows the usage of API for secret and configs and it shows how the different content is accessed: JSON and String<BR /> The example assumes that we would use the info for sending a request to a REST endpoint…<BR /> But we can skip that and just write the info to the log<BR /> <BR /> Note: saveanddeploy<BR /> <H2 id="toc-hId--441039457">Run</H2><BR /> After paste and save and deploy, we can invoke the function with the generated HTTP trigger URL<BR /> Our browser response contains just a silly text.<BR /> Now we regret that we’ve written the info text to the log instead to the HTTTP response<BR /> So we have to continue cligging digging for the treasure chest<BR /> Finally we find it:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/logoutput.jpg" height="148" width="370" /></P><BR /> What we see:<BR /> The secret information was extracted from the secret and config maps<BR /> Good job done nicely by the FaaS runtime<BR /> Maybe we think that it wasn’t a good idea to write the sensitive info to the log – but who cares what is done in a simple example?<BR /> We can also see that the runtime provides some info about the used environment, which is the nodejs runtime and the existing secrets and configs<BR /> This is another convenient service for us<BR /> <H2 id="toc-hId--637552962">Summary</H2><BR /> We've learned that secrets and config maps are elements in the <SPAN style="font-family: Courier New">faas.json</SPAN> which point to simple property files<BR /> And we've learned how to access the content of such files from the implementation code<BR /> <H2 id="quickguide" id="toc-hId--834066467">Quick Guide</H2><BR /> Create files and folders to store the desired config data<BR /> Concrete: create "data" folder and another subfolder and store the files there<BR /> <BR /> Define the secret in the <SPAN style="font-family: Courier New">faas.json<BR /> "secrets": {<BR /> <SPAN style="padding-left: 20px">"mysecret": {</SPAN><BR /> <SPAN style="padding-left: 40px">"source": "./data/donotopenthisfolder"</SPAN><BR /> <SPAN style="padding-left: 20px">}</SPAN><BR /> },<BR /> </SPAN><BR /> <BR /> Reference it from function in <SPAN style="font-family: Courier New">faas.json<BR /> <SPAN style="padding-left: 20px">"secrets": ["mysecret"],</SPAN><BR /> </SPAN><BR /> <BR /> In code:<BR /> Use helper methods to access the secret, and pass secret name and file name:<BR /> <SPAN style="font-family: Courier New">context.getSecretValueJSON('mysecret', 'filename.json')</SPAN><BR /> <H2 id="projectfiles" id="toc-hId--683325615">Appendix: All Sample Project Files</H2><BR /> For your convenience, see here the project structure:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/projectStructure-1.jpg" height="193" width="168" /></P><BR /> <SPAN style="text-decoration: underline">faas.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "project": "topsecret",<BR /> "version": "0.0.1",<BR /> "runtime": "nodejs10",<BR /> "library": "./lib",<BR /> "secrets": {<BR /> "mysecret": {<BR /> "source": "./data/donotopenthisfolder"<BR /> }<BR /> },<BR /> "configs": {<BR /> "myconfiguration": {<BR /> "source": "./data/cfg"<BR /> }<BR /> },<BR /> "functions": {<BR /> "secretfunction": {<BR /> "module": "index.js",<BR /> "handler": "handler",<BR /> "secrets": ["mysecret"],<BR /> "configs": ["myconfiguration"]<BR /> }<BR /> },<BR /> "triggers": {<BR /> "showsecret": {<BR /> "type": "HTTP",<BR /> "function": "secretfunction"<BR /> }<BR /> }<BR /> }</CODE></PRE><BR /> <SPAN style="text-decoration: underline">package.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{}</CODE></PRE><BR /> <SPAN style="text-decoration: underline">index.js</SPAN><BR /> <PRE class="language-javascript"><CODE>module.exports = { <BR /> handler: async function (event, context) { <BR /> const credentials = await context.getSecretValueJSON('mysecret', 'hiddensecret.json');<BR /> const config = await context.getConfigValueJSON('myconfiguration', 'addressconfig.json')<BR /> const greeting = await context.getConfigValueString('myconfiguration', 'addresstext.de');<BR /> <BR /> const url = config.dev.url;<BR /> const user = credentials.Max.username<BR /> const pwd = credentials.Max.password<BR /> <BR /> console.log(`Calling URL: '${url}' with user '${user}' and password '${pwd}' and greeting '${greeting}'`)<BR /> return "Top Secret";<BR /> } <BR /> }</CODE></PRE><BR /> &nbsp;<BR /> <H2 id="toc-hId--879839120">Appendix: The Hidden Secret</H2><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/cat_hidden.jpg" height="416" width="234" /></P> 2020-07-07T13:37:43+02:00 https://community.sap.com/t5/technology-blogs-by-sap/writing-function-as-a-service-6-values-yaml/ba-p/13474508 Writing Function-as-a-Service [6]: values.yaml 2020-07-10T10:36:45+02:00 CarlosRoggan https://community.sap.com/t5/user/viewprofilepage/user-id/5495 This blog is part of a&nbsp;<A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs/" target="_blank" rel="noopener noreferrer">series of tutorials</A>&nbsp;explaining how to write serverless functions using the Functions-as-a-Service offering in&nbsp;<STRONG>SAP Cloud Platform Serverless Runtime<BR /> </STRONG>Nice.<BR /> <P style="text-align: right">Quicklinks:<BR /> <SPAN style="font-size: smaller"><A href="#quickguide" target="_blank" rel="nofollow noopener noreferrer">Quick Guide</A><BR /> </SPAN></P><BR /> In the <A href="https://blogs.sap.com/2020/07/07/writing-function-as-a-service-5-top-secret/" target="_blank" rel="noopener noreferrer">previous tutorial</A> we learned how to extract secret sensitive values from the code into some property files<BR /> We defined <EM>secret</EM> and <EM>config maps</EM> to store the sensitive values<BR /> Nice.<BR /> But when the time has come to move from silly prototypes to productive projects, then we want… no: YOU want (because I continue with silly examples) to upload your project to GIT (or similar) and you don’t want to upload files with passwords etc<BR /> <BR /> FaaS has a solution for it: it supports values-files<BR /> Nice.<BR /> <BR /> <SPAN style="color: #999999">How does it work?<BR /> </SPAN>We extract the sensitive info from the “secret”-file into a values-file<BR /> That values-file is not uploaded to GIT<BR /> The “secret”-file (which is uploaded to GIT) contains placeholder instead of passwords<BR /> During deployment, the placeholders are replaced by the values from the values-file<BR /> That’s all.<BR /> Nice.<BR /> <BR /> Now we only need to go through a silly example to learn how to do it<BR /> Nice.<BR /> <BR /> Note that this tutorial is only possible for local development, such that it is a<BR /> <H2 id="toc-hId-935327694">Prerequisite</H2><BR /> to go through the 2 local tutorials, to learn about <A href="https://blogs.sap.com/2020/06/24/writing-function-as-a-service-2-local-development" target="_blank" rel="noopener noreferrer">xfsrt-cli</A> and <A href="https://blogs.sap.com/2020/06/29/writing-function-as-a-service-3-real-local-development/" target="_blank" rel="noopener noreferrer">faas-sdk</A><BR /> <BR /> Another prerequisite is the usual blabla that you have to be familiar, etc, so please don’t ask me “What is FaaS”, and instead go through some of my <A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs" target="_blank" rel="noopener noreferrer">tutorials</A><BR /> <BR /> Although this tutorial is based on the <A href="https://blogs.sap.com/2020/07/07/writing-function-as-a-service-5-top-secret/" target="_blank" rel="noopener noreferrer">previous</A> one, you can skip that silly top-secret-blog and use your own project, as long as it has any secrets and configs<BR /> <H2 id="toc-hId-738814189">Create Project</H2><BR /> Sorry for the wrong wording:<BR /> we don’t create a project, we download the project which we created in previous blog, from Extension Center to our local file system<BR /> We go to Extension Center, open our project and press the “download” button<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/download.jpg" height="86" width="427" /></P><BR /> And extract the project to a location of our boring choice.<BR /> The boring choice, like in my example: <SPAN style="background-color: #ffe78f;font-family: Courier New">C:\topsecret</SPAN><BR /> The project looks like this:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/projectStructure-2.jpg" height="192" width="174" /></P><BR /> <BR /> <H2 id="toc-hId-542300684">Create values file</H2><BR /> Correct wording.<BR /> But not good.<BR /> In fact, we don’t create that file:<BR /> We let the <STRONG>faas-sdk</STRONG> generate it<BR /> <BR /> On command line, we jump into the project folder, then execute the following command:<BR /> <PRE class="language-javascript"><CODE>faas-sdk init-values -y ./_tmp/mydeployvalues.yaml</CODE></PRE><BR /> The syntax:<BR /> <SPAN style="background-color: black;font-family: Courier New"><SPAN style="color: #ffffff">faas-sdk init-values</SPAN> <SPAN style="color: #ffcc00">--deploy-values</SPAN> <SPAN style="color: #00ff00">&lt;yourFilePath&gt;</SPAN></SPAN><BR /> <BR /> Note:<BR /> We can use abbreviation -y<BR /> <BR /> Note:<BR /> File extension (currently) must be <SPAN style="font-family: Courier New">.yaml</SPAN><BR /> <BR /> The command will generate the <SPAN style="font-family: Courier New">.yaml</SPAN> file with the given name in the specified folder.<BR /> If you don’t specify a folder, then a folder with name “deploy” will be generated.<BR /> <BR /> In our example, the result looks like this:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/command.jpg" /></P><BR /> In our case, we get a warning.<BR /> Reason: for a config map, we used a file extension which is unknown to the parser.<BR /> It was skipped, but that doesn’t matter for us. it doesn’t contain sensitive information, so we don’t need to extract the content into the values-file<BR /> <BR /> <SPAN style="color: #999999">What has happened?<BR /> </SPAN>The <SPAN style="font-family: Courier New">init-values</SPAN> command parses the <SPAN style="font-family: Courier New">faas.json</SPAN> file and collects all secrets and configs which are declared there (also registered services).<BR /> In addition, all files which are found are listed as keys with the file-content as values<BR /> All is pulled out of the project and pushed into one values-file<BR /> <BR /> In our example, it looks like this:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/values-content.jpg" /></P><BR /> We can see the silly names which we had chosen in the previous blog for secret names and file names.<BR /> We can see that the file content has been copied into this file, along with file names and secret names<BR /> <H2 id="toc-hId-345787179">Replace the values</H2><BR /> The title of this chapter could also be replaced by:<BR /> “Extract the values”<BR /> Or<BR /> “Move the sensitive content from all files of secrets and config maps to the new values-file”<BR /> <BR /> Anyways, the next step is to fill the generated values-file with the sensitive information<BR /> <BR /> In our case, it is already there.<BR /> But if you start a project from scratch - in future - you might create the secrets with placeholders instead of passwords<BR /> <BR /> OK, in our example, we now have to delete all passwords etc from our secrets and configs<BR /> <BR /> We open <SPAN style="font-family: Courier New">hiddensecret.json</SPAN>, delete the values for "username" and "password" and replace them with anything silly that comes into our mind<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/values-placeholder.jpg" height="187" width="363" /></P><BR /> Similar with file <SPAN style="font-family: Courier New">addressconfig.json</SPAN><BR /> <BR /> That’s it<BR /> <H2 id="toc-hId-149273674">Deploy</H2><BR /> This title is correct and good<BR /> But before we deploy….<BR /> Wrong: nothing needs to be done before deployment<BR /> But <EM>when</EM> we deploy, we have to tell the deployer where to find the values<BR /> Because the deployer will replace the silly placeholders with the serious values.<BR /> So we have to add&nbsp;the <SPAN style="font-family: Courier New">-y</SPAN> flag to the deploy command (or <SPAN style="font-family: Courier New">--deploy-values</SPAN>)<BR /> As such, in our example, the deploy command looks like this:<BR /> <PRE class="language-javascript"><CODE>xfsrt-cli faas project deploy -y ./_tmp/mydeployvalues.yaml</CODE></PRE><BR /> The syntax:<BR /> <SPAN style="background-color: black;font-family: Courier New"><SPAN style="color: #ffffff">xfsrt-cli faas project deploy</SPAN> <SPAN style="color: #ffcc00">--deploy-values</SPAN> <SPAN style="color: #00ff00">&lt;yourFilePath&gt;</SPAN></SPAN><BR /> <BR /> After deployment we call the function and check the log to verify if the values really have been replaced<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/log.jpg" /></P><BR /> Looks as expected.<BR /> Isn’t it a nice feature? Our placeholders are replaced with the real values during deployment<BR /> <BR /> If you don’t believe it and if you think it was just an illusion or a coincidence… try it with different values in the values-file<BR /> <H2 id="toc-hId--47239831">Summary</H2><BR /> We’ve learned how to move sensitive information out of the secrets into a values file<BR /> Reason: secrets are part of the dev project and need to be uploaded to GIT<BR /> The values-file mechanism helps to keep sensitive information out of GIT<BR /> The FaaS SDK supports with generation of the values-file<BR /> The XFRST-CLI allows to deploy with values-files parameter<BR /> The Extension Center does (currently) not offer any support here<BR /> Nice.<BR /> <H2 id="quickguide" id="toc-hId--243753336">Quick Guide</H2><BR /> Project: remove the passwords (etc) from secrets (etc), replace with placeholder<BR /> Laptop: generate values-file with command (run in project root):<BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">faas-sdk init-values -y ./mypath/mydeployvalues.yaml<BR /> </SPAN>FaaS: deploy the project with configured values-file<BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas project deploy -y ./mypath/mydeployvalues.yaml</SPAN> 2020-07-10T10:36:45+02:00 https://community.sap.com/t5/technology-blogs-by-sap/writing-function-as-a-service-7-how-to-use-platform-services/ba-p/13484087 Writing Function-as-a-Service [7]: How to use Platform Services 2020-07-16T16:12:31+02:00 CarlosRoggan https://community.sap.com/t5/user/viewprofilepage/user-id/5495 This blog is part of a&nbsp;<A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs/" target="_blank" rel="noopener noreferrer">series of tutorials</A>&nbsp;explaining how to write serverless functions using the Functions-as-a-Service offering in&nbsp;<STRONG>SAP Cloud Platform Serverless Runtime</STRONG><BR /> <P style="text-align: right">Quicklinks:<BR /> <SPAN style="font-size: smaller"><A href="#quickguide" target="_blank" rel="nofollow noopener noreferrer">Quick Guide</A><BR /> <A href="#sampleproject" target="_blank" rel="nofollow noopener noreferrer">Sample Project</A></SPAN></P><BR /> In a <A href="https://blogs.sap.com/2020/07/07/writing-function-as-a-service-5-top-secret" target="_blank" rel="noopener noreferrer">previous blog</A>, we had a little introduction into the usage of <EM>secrets</EM> and <EM>config maps<BR /> </EM>Today, I’d like to go through an example which shows how to connect a function to a service running in <EM>SAP Cloud Platform, Cloud Foundry Environment<BR /> </EM>As an example, we’re going to use the <EM>Destination service</EM> and call a configured destination<BR /> <SPAN style="color: #999999">Any useful purpose to do that?</SPAN><BR /> Surprisingly: yes<BR /> This example could be enhanced to a checktool which runs every night and checks if the destinations are still responding as expected<BR /> <SPAN style="color: #999999">Why useful?</SPAN><BR /> It would help with troubleshooting &nbsp;and early identify errors<BR /> <SPAN style="color: #999999">Indeed<BR /> </SPAN>Anyways, this sample can be modified to use any other platform service<BR /> <H2 id="toc-hId-936246657">Prerequisites</H2><BR /> To follow this tutorial, you should be familiar with FaaS (see <A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs/" target="_blank" rel="noopener noreferrer">series</A>)<BR /> The function code is based on <EM>Node.js</EM><BR /> As usual, this tutorial can be followed in <EM>Extension Center</EM> or with l<A href="https://blogs.sap.com/2020/06/29/writing-function-as-a-service-3-real-local-development/" target="_blank" rel="noopener noreferrer">ocal dev</A> environment<BR /> <H2 id="toc-hId-739733152">Overview</H2><BR /> <OL><BR /> <LI>Configure Destination in the cloud cockpit<BR /> Create Destination Service Instance and Service Key<BR /> Create Destination Configuration</LI><BR /> </OL><BR /> <OL start="2"><BR /> <LI>Create function which uses the destination<BR /> Create Function Project with <EM>secret</EM> and <EM>config map<BR /> </EM>Implement Function, calling destination service, using secret</LI><BR /> </OL><BR /> <OL start="3"><BR /> <LI>Run the sample</LI><BR /> </OL><BR /> <H2 id="toc-hId-543219647">1. Configure Destination in the cloud cockpit</H2><BR /> In our example, we want to use destinations from our function.<BR /> Destinations are usually used whenever a cloud app calls a URL of an external service.<BR /> With destinations, we can avoid hardcoding URLs, it solves CORS issues and maintenance<BR /> <H3 id="toc-hId-475788861">1.1. Create Destination Service Instance</H3><BR /> So the first step is to create a service instance of "Destination" service<BR /> In your Space, go to Service Marketplace and search for the tile "Destination"<BR /> If you don't find it, you might need to add "Entitlement" (see <A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-0.1-preparation/#entitlements" target="_blank" rel="noopener noreferrer">here</A> for general description)<BR /> When creating the service instance,<BR /> no params are required<BR /> and the name can be&nbsp;<EM>destinationinstance</EM><BR /> <H3 id="toc-hId-279275356">1.2. Create Service Key</H3><BR /> In the server-more world, we would deploy an app, with manifest, where we would define a binding to the service instance. Then, in the code, we access the service-credentials via env variables<BR /> <BR /> In the serverless world, we don't deploy an app, so we don't have binding and no env variables<BR /> That's why we need to create a service key for the service instance.<BR /> The service key provides us with same credentials which we would get with binding<BR /> <BR /> We click on the created instance name<BR /> Then choose “Service Keys” in the left pane<BR /> Finally we press “Create Service Key” and enter a name like <EM>skfordestination<BR /> </EM>Now we can view the content of the service key<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/dest_sk.jpg" height="153" width="366" /></P><BR /> I’ve marked the relevant properties<BR /> But for the moment, we leave it like it is<BR /> We don't close the browser window, as we'll need the service key content later<BR /> <BR /> <SPAN style="color: #999999">Why?</SPAN><BR /> We need these credentials to call the destination programmatically<BR /> <H3 id="toc-hId-82761851">1.3. Create Destination Configuration</H3><BR /> Until now we’ve only created a <EM>destination service</EM> instance<BR /> Yes, it was necessary, otherwise we couldn't read the <EM>destination configuration<BR /> </EM>Now we create a <EM>configuration</EM> for a destination that should point to any URL<BR /> <BR /> In the cockpit, we need to go to our subaccount, then expand “Connectivity”, click on “Destinations” and press “New Destination”<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/dest_config-1.jpg" height="115" width="337" /></P><BR /> We create a destination of our choice, it doesn’t matter which URL, we won’t really use it seriously.<BR /> The screenshot shows an example<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/dest_config_details_wiki-1.jpg" height="173" width="343" /></P><BR /> The above example points to an existing URL.<BR /> It specifies user credentials which aren't required. Just as example for later usage in the code<BR /> <BR /> To illustrate the advantage of a destination:<BR /> The admin can decide to change the host of the destination to a different country, for example, (or landscape upgrade), and our function code wouldn't need to be adapted<BR /> <H2 id="toc-hId--242834373">2. Create Function which uses the Destination</H2><BR /> Nest step is to create a function which programmatically uses the destination<BR /> <H3 id="toc-hId--310265159">2.1. Create Project Artifacts</H3><BR /> I assume that you're familiar with Function-as-a-Service in <EM>serverless runtime</EM>, so let's only roughly go through the required steps<BR /> Please refer to the <A href="#sampleproject" target="_blank" rel="nofollow noopener noreferrer">appendix</A> for the file content<BR /> <H4 id="toc-hId--377695945"><SPAN style="text-decoration: underline">2.1.1. Create Project</SPAN></H4><BR /> We create a project with name <EM>destichecker</EM> and runtime <EM>nodejs10</EM><BR /> Can be a project on local machine, or an "Extension" if you use the <EM>Extension Center</EM><BR /> <H4 id="toc-hId--574209450"><SPAN style="text-decoration: underline">2.1.2. Define Secret</SPAN></H4><BR /> We define a <EM>secret</EM> (with name "destinationsecret") in the <SPAN style="font-family: Courier New">faas.json<BR /> </SPAN>We create <SPAN style="background-color: #ffe78f;font-family: Courier New">data</SPAN> folder and <SPAN style="background-color: #ffe78f;font-family: Courier New">secrets</SPAN> subfolder and create a file (with name <SPAN style="font-family: Courier New;background-color: #f5f5f5">destsrv.json</SPAN>) to carry the secret content<BR /> In our example, the file content is the content of the service key<BR /> As such, we go to our cloud cockpit and view the service key which we created above<BR /> We can just copy the whole content (yes, everything, including all the stuff which we’re not interested in) and we paste it into the file for our secret<BR /> And we save<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/secret_content-1.jpg" height="205" width="313" /></P><BR /> <BR /> <H4 id="toc-hId--1268440050"><SPAN style="text-decoration: underline">2.1.3. Define Config Map</SPAN></H4><BR /> We define a <EM>config</EM> in the <SPAN style="font-family: Courier New">faas.json<BR /> </SPAN>In the <SPAN style="background-color: #ffe78f;font-family: Courier New">data</SPAN> folder, we create a file (<SPAN style="font-family: Courier New;background-color: #f5f5f5">destcfg.json</SPAN>) to carry some configuration values<BR /> In our example, we store the name(s) of the <EM>destination configuration(s)</EM> created above<BR /> Make sure to copy&amp;paste the destination name correctly<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/config_content.jpg" height="170" width="312" /></P><BR /> <BR /> <H4 id="toc-hId--1464953555"><SPAN style="text-decoration: underline">2.1.4. Create Function</SPAN></H4><BR /> In our <SPAN style="font-family: Courier New">faas.json</SPAN>, we define a function (e.g. "checkdestfun") with module and handler and reference to the secret and the config.<BR /> We create the <SPAN style="background-color: #ffe78f;font-family: Courier New">lib</SPAN> folder (because predefined in the generated faas manifest) and the javascript file (<SPAN style="font-family: Courier New;background-color: #f5f5f5">destichecker.js</SPAN>)<BR /> The javascript file contains the handler and some silly test code (for a first test)<BR /> <H4 id="toc-hId--1661467060"><SPAN style="text-decoration: underline">2.1.5. Define Trigger</SPAN></H4><BR /> To test our example, we create a HTTP trigger (with name "checkdest"), later we can create an additional timer trigger to let our function do its useful work on a regular basis<BR /> <SPAN style="color: #999999">Later?</SPAN><BR /> Yes, but not in this blog<BR /> <H4 id="toc-hId--1857980565"><SPAN style="text-decoration: underline">2.1.6. Try the Function</SPAN></H4><BR /> After verifying that our <SPAN style="font-family: Courier New">faas.json</SPAN> looks like the one in the <A href="#sampleproject" target="_blank" rel="nofollow noopener noreferrer">appendix</A>, we deploy our function project and invoke it with the HTTP trigger URL<BR /> e.g.<BR /> <SPAN style="color: #0000ff"><A href="https://123-faas-http.tenant.eu10.functions.xfs.cloud.sap/checkdest" target="test_blank" rel="nofollow noopener noreferrer">https://123-faas-http.tenant.eu10.functions.xfs.cloud.sap/checkdest</A></SPAN><BR /> It should work as expected<BR /> <H3 id="toc-hId--1761091063">2.2. Implement Function</H3><BR /> Now we come to the implementation part.<BR /> This is about how to use the destination service programmatically.<BR /> The code is not specific for serverless functions.<BR /> The only difference is that we get the credentials - which are required to call the destination service - from the FaaS runtime instead of accessing the environment variables<BR /> <BR /> What our function should do:<BR /> . Access the service credentials<BR /> . Call the destination service to get the configured destinations<BR /> . Afterwards, read the destination configuration to get the URL and authentication<BR /> . Finally, execute the URL<BR /> <H4 id="toc-hId-2043959721"><SPAN style="text-decoration: underline">2.2.1. Access the Service Credentials</SPAN></H4><BR /> Above, we created a Service Key<BR /> Then stored the content (JSON) in a <EM>secret</EM><BR /> Now we use the API of the FaaS runtime to obtain the properties which we need from the service key:<BR /> <PRE class="language-javascript"><CODE>const credentials = await context.getSecretValueJSON('destinationsecret', 'destsrv.json')<BR /> <BR /> const destSrvUrl = credentials.uri<BR /> const oauthUrl = credentials.url<BR /> const clientId = credentials.clientid<BR /> const clientSecret = credentials.clientsecret<BR /> </CODE></PRE><BR /> Similar approach to get the destination name from the <EM>config map</EM><BR /> <H4 id="toc-hId-1847446216"><SPAN style="text-decoration: underline">2.2.2. Call the Destination Service</SPAN></H4><BR /> The Destination Service itself is protected with OAuth.<BR /> As such, we need 2 steps for calling the Destination Service:<BR /> 1. Fetch JWT token<BR /> Here we need the info from the service key (clientid, clientsecret and oauthUrl)<BR /> <PRE class="language-javascript"><CODE>const _fetchJwtToken = async function(oauthUrl, oauthClient, oauthSecret) {<BR /> ... <BR /> const options = {<BR /> host: oauthUrl.replace('https://', ''),<BR /> path: '/oauth/token?grant_type=client_credentials&amp;response_type=token',<BR /> headers: {<BR /> Authorization: "Basic " + Buffer.from(oauthClient + ':' + oauthSecret).toString("base64")</CODE></PRE><BR /> 2. Call Destination Service<BR /> Once we have the JWT token, we can use the Url from service key to call the service, and we use the destinationName which we had stored in a <EM>config map</EM><BR /> <PRE class="language-javascript"><CODE>const _readDestinationConfig = async function(destinationName, destUri, jwtToken){<BR /> ... <BR /> const options = {<BR /> host: destUri.replace('https://', ''),<BR /> path: '/destination-configuration/v1/destinations/' + destinationName,<BR /> headers: {<BR /> Authorization: 'Bearer ' + jwtToken<BR /> </CODE></PRE><BR /> <H4 id="toc-hId-1650932711"><SPAN style="text-decoration: underline">2.2.3. Read Destination Configuration</SPAN></H4><BR /> The result of the call to the Destination Service is the destination configuration for the requested destination name<BR /> In our Example, the destination configuration is simple.<BR /> We need the URL and the authentication info<BR /> We don't need to encode the user and password, this is done conveniently by the destination service<BR /> <PRE class="language-javascript"><CODE>destiConfi.destinationConfiguration.URL <BR /> destiConfi.authTokens[0].http_header.value</CODE></PRE><BR /> <H4 id="toc-hId-1454419206"><SPAN style="text-decoration: underline">2.2.4. Call the target endpoint</SPAN></H4><BR /> Now we have all information we need to call the endpoint which is configured in the destination configuration<BR /> Usually, in a destination configuration, we would extract only the variable part of a URL.<BR /> Which usually would be the host.<BR /> That's what we've done in our example<BR /> As such, in the code, we have to append all stable segments to the URL which we obtained from the destination configuration.<BR /> In our example, we call the info about "Cat" (hardcoded)<BR /> <PRE class="language-javascript"><CODE>const _callEndpoint = async function(url, auth){<BR /> ...<BR /> const options = {<BR /> host: url.replace('https://', ''),<BR /> path: '/Cat',<BR /> headers: {<BR /> Authorization: auth.http_header.value</CODE></PRE><BR /> See <A href="#sampleproject" target="_blank" rel="nofollow noopener noreferrer">appendix</A> for full file content<BR /> <H4 id="toc-hId-1257905701"><SPAN style="text-decoration: underline">2.2.5. Small Recap</SPAN></H4><BR /> In our function implementation, we want to call a target URL<BR /> The full URL is not stored in our function project.<BR /> We want to use a Destination<BR /> To call the destination service, we use credentials which we had stored in a <EM>secret</EM><BR /> First get a token<BR /> Then use it to get the destination config<BR /> Then read the config info to call the target endpoint<BR /> <H2 id="toc-hId-1816381901">3. Run the sample</H2><BR /> So finally, we copy all the function code from the appendix.<BR /> After deploy, we open the URL of our function and we get the expected result<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/result.jpg" height="169" width="494" /></P><BR /> Of course, the next step would be to improve our checker:<BR /> Refactor the code, use config map for destination name, support multiple/all destinations check, support control URL parameters, send JSON response, support timer trigger,store check results in servless BackendService<BR /> etc<BR /> However, no next steps in this blog nor in future blogs<BR /> <H2 id="toc-hId-1619868396">Summary</H2><BR /> In this tutorial we've learned how to consume the destination service from a serverless function<BR /> The sample code can easily be adapted to any other service of SAP Cloud Platform<BR /> The essential learning was to copy a service key to the function project<BR /> <H2 id="quickguide" id="toc-hId-1423354891">Quick Guide</H2><BR /> To consume a service in a function we have to:<BR /> - create service key for the service we want to use<BR /> - copy the service key into a file in the FaaS project<BR /> - define a <EM>secret</EM> which points to that file<BR /> - in the function code, access the <EM>secret</EM><BR /> - use the credentials properties in order to call the cloud service<BR /> <H2 id="sampleproject" id="toc-hId-1226841386">All Sample Project Files</H2><BR /> Project Structure<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/projectStructure-3.jpg" height="182" width="126" /></P><BR /> <SPAN style="text-decoration: underline">faas.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "project": "destichecker",<BR /> "version": "0.0.1",<BR /> <BR /> "runtime": "nodejs10",<BR /> "library": "./lib",<BR /> <BR /> "secrets": {<BR /> "destinationsecret": {<BR /> "source": "./data/secrets"<BR /> }<BR /> },<BR /> "configs": {<BR /> "destinationconfig": {<BR /> "source" : "./data"<BR /> }<BR /> },<BR /> "functions": {<BR /> "checkdestfun": {<BR /> "module": "destichecker.js",<BR /> "handler": "doDestinationCheck",<BR /> "secrets": ["destinationsecret"],<BR /> "configs": ["destinationconfig"]<BR /> }<BR /> },<BR /> <BR /> "triggers": {<BR /> "checkdest": {<BR /> "type": "HTTP",<BR /> "function": "checkdestfun"<BR /> }<BR /> }<BR /> }</CODE></PRE><BR /> <SPAN style="text-decoration: underline">package.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{}</CODE></PRE><BR /> <SPAN style="text-decoration: underline">destsrv.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "uaadomain": "authentication.eu10.hana.ondemand.com",<BR /> "tenantmode": "dedicated",<BR /> "clientid": "sb-clone123!b22273|destination-xsappname!0000",<BR /> "instanceid": "34ea5555-bbbb-bbbb-9999-4444200bbbbb",<BR /> "verificationkey": "-----BEGIN PUBLIC KEY-----MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwThn6OO9kj0bchkOGkqYBnV1dQ3zU/xxxxxxx+5/yDZUc0IXXyIWPZD+XdL+0EogC3d4+fqyvg/BF/F0t2hKHWr/UTXE6zrGhBKaL0d8rKfYd6olGWigFd+3+24CKI14zWVxUBtC+P9Fhngc9DRzkXqhxOK/EKn0HzSgotf5duq6Tmk9DCNM4sLW4+ERc6xzrgbeEexakabvax/Az9WZ4qhwgw+fwIhKIC7WLwCEJaRsW4m7NKkv+++++LKYesuQ9SVAJ3EXV86RwdnH4uAv7lQHsKURPVAQBlranSqyQu0EXs2N9OlWTxe8SoKJnRcRF2KxWKs355FhpHpzqyZflO5l98+O8wOsFjGpL9d0ECAwEAAQ==-----END PUBLIC KEY-----",<BR /> "xsappname": "clone34ea5555b4ee4bbb9999db444444b888!b22222|destination-xsappname!b000",<BR /> "identityzone": "myzone",<BR /> "clientsecret": "c2D3E4F5G6A1B2C3D4E5=",<BR /> "tenantid": "628e9f87-e539-49fc-a54f-1a1a1a1a1a",<BR /> "uri": "https://destination-configuration.cfapps.eu10.hana.ondemand.com",<BR /> "url": "https://mysubaccount.authentication.eu10.hana.ondemand.com"<BR /> }</CODE></PRE><BR /> <SPAN style="text-decoration: underline">destcfg.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "name": "dest_wiki_en"<BR /> }</CODE></PRE><BR /> <SPAN style="text-decoration: underline">destichecker.js</SPAN><BR /> <PRE class="language-javascript"><CODE>const https = require('https');<BR /> <BR /> // main entry<BR /> const _doDestinationCheck = async function (event, context) {<BR /> <BR /> // we stored the desired destination name in a config map <BR /> const config = await context.getConfigValueJSON('destinationconfig', 'destcfg.json')<BR /> const destinationName = config.name<BR /> <BR /> // use faas API to get credentials stored in secret<BR /> const credentials = await context.getSecretValueJSON('destinationsecret', 'destsrv.json')<BR /> const destSrvUrl = credentials.uri<BR /> const oauthUrl = credentials.url<BR /> const clientId = credentials.clientid<BR /> const clientSecret = credentials.clientsecret<BR /> <BR /> // execute required steps to call the target URL<BR /> const jwtToken = await _fetchJwtToken(oauthUrl, clientId, clientSecret)<BR /> const destiConfi = await _readDestinationConfig(destinationName, destSrvUrl, jwtToken)<BR /> const result = await _callEndpoint(destiConfi.destinationConfiguration.URL, destiConfi.authTokens[0])<BR /> <BR /> return `Check Result:\n${result.message}`<BR /> } <BR /> <BR /> // JWT token is required to call the Destination Service<BR /> const _fetchJwtToken = async function(oauthUrl, oauthClient, oauthSecret) {<BR /> return new Promise ((resolve, reject) =&gt; {<BR /> const options = {<BR /> host: oauthUrl.replace('https://', ''),<BR /> path: '/oauth/token?grant_type=client_credentials&amp;response_type=token',<BR /> headers: {<BR /> Authorization: "Basic " + Buffer.from(oauthClient + ':' + oauthSecret).toString("base64")<BR /> }<BR /> }<BR /> https.get(options, res =&gt; {<BR /> res.setEncoding('utf8')<BR /> let response = ''<BR /> res.on('data', chunk =&gt; {<BR /> response += chunk<BR /> })<BR /> res.on('end', () =&gt; {<BR /> try {<BR /> const responseAsJson = JSON.parse(response)<BR /> const jwtToken = responseAsJson.access_token <BR /> if (!jwtToken) {<BR /> return reject(new Error('Error while fetching JWT token'))<BR /> }<BR /> resolve(jwtToken)<BR /> } catch (error) {<BR /> return reject(new Error('Error while fetching JWT token')) <BR /> }<BR /> })<BR /> })<BR /> .on("error", (error) =&gt; {<BR /> console.log("Error: " + error.message);<BR /> return reject({error: error})<BR /> });<BR /> }) <BR /> }<BR /> <BR /> // Call Destination Service. Result will be an object with Destination Configuration info<BR /> const _readDestinationConfig = async function(destinationName, destUri, jwtToken){<BR /> return new Promise((resolve, reject) =&gt; {<BR /> const options = {<BR /> host: destUri.replace('https://', ''),<BR /> path: '/destination-configuration/v1/destinations/' + destinationName,<BR /> headers: {<BR /> Authorization: 'Bearer ' + jwtToken<BR /> }<BR /> } <BR /> https.get(options, res =&gt; {<BR /> res.setEncoding('utf8')<BR /> let response = ''<BR /> res.on('data', chunk =&gt; {<BR /> response += chunk<BR /> }) <BR /> res.on('end', () =&gt; {<BR /> try {<BR /> const destInfo = JSON.parse(response); <BR /> resolve(destInfo)<BR /> } catch (error) {<BR /> return reject(new Error('Error while parsing destination configuration')) <BR /> }<BR /> })<BR /> })<BR /> .on("error", (error) =&gt; {<BR /> console.log("Error while calling Destination Service : " + error.message);<BR /> return reject({error: error})<BR /> }); <BR /> })<BR /> }<BR /> <BR /> // call the URL extracted from Destination Config. Manually append required URI segments<BR /> const _callEndpoint = async function(url, auth){<BR /> return new Promise((resolve, reject) =&gt; {<BR /> const options = {<BR /> host: url.replace('https://', ''),<BR /> path: '/wiki/Cat',<BR /> headers: {<BR /> Authorization: auth.http_header.value<BR /> }<BR /> } <BR /> https.get(options, res =&gt; {<BR /> const status = res.statusCode<BR /> if(status &lt; 400){<BR /> resolve({message: `Successfully called destination with target URL: ${url}${res.req.path}`})<BR /> }else{<BR /> reject({error: {message: `Invalid Url: ${url} with status ${status}`}})<BR /> }<BR /> })<BR /> .on("error", (error) =&gt; {<BR /> reject({error: error})<BR /> }); <BR /> })<BR /> }<BR /> <BR /> // export the handler function with readable alias<BR /> module.exports = {<BR /> doDestinationCheck : _doDestinationCheck<BR /> }</CODE></PRE><BR /> &nbsp; 2020-07-16T16:12:31+02:00 https://community.sap.com/t5/technology-blogs-by-sap/writing-function-as-a-service-8-how-to-easily-use-platform-services/ba-p/13463926 Writing Function-as-a-Service [8]: How to (easily) use Platform Services 2020-07-23T17:18:59+02:00 CarlosRoggan https://community.sap.com/t5/user/viewprofilepage/user-id/5495 With other words:<BR /> <P style="text-align: center">How to write serverless function<BR /> in<BR /> <STRONG>SAP Cloud Platform Serverless Runtime</STRONG><BR /> and using<BR /> <STRONG>Service Registration<BR /> </STRONG>aka<BR /> <STRONG>Credential Store</STRONG><BR /> to reference<BR /> Platform Services</P><BR /> This blog is part of a&nbsp;<A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs/" target="_blank" rel="noopener noreferrer">series of tutorials</A>&nbsp;explaining how to write serverless functions using the Functions-as-a-Service offering in&nbsp;<STRONG>SAP Cloud Platform Serverless Runtime</STRONG><BR /> <P style="text-align: right">Quicklinks:<BR /> <SPAN style="font-size: smaller"><A href="#localdev" target="_blank" rel="nofollow noopener noreferrer">Local development</A><BR /> <A href="#quickguide" target="_blank" rel="nofollow noopener noreferrer">Quick Guide</A><BR /> <A href="#sampleproject" target="_blank" rel="nofollow noopener noreferrer">Sample Project</A></SPAN></P><BR /> The <A href="https://blogs.sap.com/2020/07/16/writing-function-as-a-service-7-how-to-use-platform-services" target="_blank" rel="noopener noreferrer">previous blog</A> was not good<BR /> <SPAN style="color: #999999">Why?</SPAN><BR /> Too complicated<BR /> The same can be achieved more easily using the <EM>Service Registration</EM> feature offered by FaaS<BR /> <SPAN style="color: #999999">What's this?<BR /> </SPAN>Let's see:<BR /> <BR /> In the previous blog,<BR /> <SPAN style="padding-left: 15px">we created a service instance and service key</SPAN><BR /> <SPAN style="padding-left: 15px">we created a project</SPAN><BR /> <SPAN style="padding-left: 15px">we created a file to store the service key</SPAN><BR /> <SPAN style="padding-left: 15px">we created a secret pointing to the file</SPAN><BR /> <BR /> To facilitate using a service instance, FaaS offers a feature to register a service instance in FaaS<BR /> This means that we don't need to create a secret anymore<BR /> We just reference the registered service<BR /> Furthermore, the service is registered globally in FaaS, making it available for all projects<BR /> <BR /> In this tutorial, we're going to learn how to use the service registration feature in the <STRONG>Extension Center&nbsp;</STRONG>but also how to achieve the same with the <STRONG>command line client</STRONG> for local development<BR /> <BR /> As example, we're going to use the platform service <STRONG>Enterprise Messaging</STRONG>, and we'll connect our function to it.<BR /> Our scenario is:<BR /> <BR /> <SPAN style="padding-left: 20px">Whenever a message is sent to Messaging,</SPAN><BR /> <SPAN style="padding-left: 50px">then our function will be triggered</SPAN><BR /> <SPAN style="padding-left: 80px">and it will do nothing</SPAN><BR /> <H2 id="toc-hId-934378286">Prerequisites</H2><BR /> <UL><BR /> <LI>Access to <A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-0.1-preparation" target="_blank" rel="noopener noreferrer">FaaS</A> which is currently only available in productive landscape</LI><BR /> <LI>Basic knowledge about <A style="font-size: 1rem" href="https://nodejs.org/" target="_blank" rel="noopener noreferrer nofollow">Node.js</A></LI><BR /> <LI>Access to <A style="font-size: 1rem" href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-1.1-first-function-using-extension-center" target="_blank" rel="noopener noreferrer">Extension Center</A><SPAN style="font-size: 1rem"> is </SPAN><SPAN style="text-decoration: underline">not</SPAN><SPAN style="font-size: 1rem"> required, everything can be done with local dev environment</SPAN></LI><BR /> <LI>To follow this tutorial, it is necessary to be able to use <A style="font-size: 1rem" href="https://help.sap.com/viewer/bf82e6b26456494cbdd197057c09979f/Cloud/en-US/df532e8735eb4322b00bfc7e42f84e8d.html" target="_blank" rel="noopener noreferrer">SAP Cloud Platform Enterprise Messaging</A></LI><BR /> </UL><BR /> <H2 id="toc-hId-737864781">Preparation</H2><BR /> In this tutorial, we want to learn how to register a service instance.<BR /> As such, the first step is to create such instance<BR /> A nice side-effect: we learn how to connect FaaS to Enterprise Messaging<BR /> <BR /> If you aren't familiar at all with messaging, you can have a look in a rough description I wrote <A href="https://blogs.sap.com/2020/03/03/sap-cloud-application-programming-model-and-enterprise-messaging-1-intro" target="_blank" rel="noopener noreferrer">here</A><BR /> <BR /> Command line users see <A href="#localdev" target="_blank" rel="nofollow noopener noreferrer">here</A><BR /> <H3 id="toc-hId-670433995">1. Create instance of service Enterprise Messaging</H3><BR /> We create an instance of the Enterprise Messaging service.<BR /> We use the following JSON parameters:<BR /> <PRE class="language-javascript"><CODE>{<BR /> "options": {<BR /> "management": true,<BR /> "messagingrest": true,<BR /> "messaging": true<BR /> },<BR /> "rules": {<BR /> "topicRules": {<BR /> "publishFilter": [<BR /> "${namespace}/*"<BR /> ],<BR /> "subscribeFilter": [<BR /> "${namespace}/*"<BR /> ]<BR /> },<BR /> "queueRules": {<BR /> "publishFilter": [<BR /> "${namespace}/*"<BR /> ],<BR /> "subscribeFilter": [<BR /> "${namespace}/*"<BR /> ]<BR /> }<BR /> },<BR /> "version": "1.1.0",<BR /> "emname": "faas_msg_client",<BR /> "namespace": "comp/busi/crm"<BR /> }</CODE></PRE><BR /> And as name for the service instance, we enter <STRONG>faas_msg_instance</STRONG><BR /> <BR /> Note:<BR /> As you know, I like to use silly names in my tutorials, I believe it makes the learning easier, because the name always tells what it is about and it makes clear that an arbitrary name can be chosen<BR /> Of course, it is <SPAN style="text-decoration: underline">not</SPAN> recommended to use silly names in productive environment<BR /> <BR /> Note:<BR /> If you don't find the Messaging service, you might need to configure <A href="https://blogs.sap.com/2020/03/03/sap-cloud-application-programming-model-and-enterprise-messaging-1-intro/#entitlements" target="_blank" rel="noopener noreferrer">Entitlements</A><BR /> <H3 id="toc-hId-473920490">2. Create Service Key for the instance</H3><BR /> After creating the service instance, we need a Service Key.<BR /> It provides credentials which allow to access the service externally, without binding an application<BR /> <BR /> To create a service key, we have to click on the newly created service instance<BR /> In our example, we enter the name as <STRONG>faas_service_key<BR /> </STRONG>We don’t close the browser page, we will need to view the service key content later on<BR /> <H3 id="toc-hId-277406985">3. Configure Messaging</H3><BR /> After creating the messaging instance, we can <A href="https://blogs.sap.com/2020/03/03/sap-cloud-application-programming-model-and-enterprise-messaging-1-intro/#open_dashboard" target="_blank" rel="noopener noreferrer">open the messaging dashboard</A>.<BR /> <BR /> We go to messaging client created above (property "emname": <EM>faas_msg_client</EM>)<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/msgDashboard1.jpg" height="145" width="207" /></P><BR /> We create queue with name <STRONG>queue/for/faas<BR /> </STRONG>This will result in full queue name <STRONG>comp/busi/crm/queue/for/faas<BR /> </STRONG>The queue is used for receiving and queuing messages.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/msgDashboard2.jpg" height="160" width="147" /></P><BR /> That's it for the preparation.<BR /> <BR /> Small Recap:<BR /> We've created messaging instance and service key<BR /> We've created a queue in our client<BR /> <H2 id="toc-hId--48189239">Extension Center</H2><BR /> Now we want to use this service in a Function<BR /> I've promised that it is easy.<BR /> Yes<BR /> <H3 id="toc-hId--115620025">1. Register Service Instance</H3><BR /> FaaS provides built-in support for connecting to Platform services<BR /> Prerequisites are: service instance and service key<BR /> Fortunately, we just created both in the chapter above<BR /> So we’re lucky enough to use them<BR /> The FaaS feature to support the Platform services includes a kind of service registration<BR /> Independent of any specific function project, we can register a service instance in the FaaS runtime<BR /> It will safeguard the service instance credentials against any malicious phishing hooks<BR /> No secret required, no tedious copy&amp;pasting of clientids and so on<BR /> And whenever we need it, we can just use it<BR /> <BR /> <SPAN style="font-size: 60%">I like to compare it to a dog show in a circus:<BR /> If you say "hepp", the dog will jump, etc<BR /> If you say “enterprise-messaging”, the function will connect to the registered service instance<BR /> (BTW, why I’ve never seen a cat show in a circus…?)</SPAN><BR /> <BR /> <SPAN style="color: #999999">How to register a service instance?</SPAN><BR /> <BR /> In Extension Center, we <SPAN style="text-decoration: underline">don’t</SPAN> click on “Extensions”<BR /> We don’t create any project<BR /> Instead, on the left menu pane, we expand “Configurations” and click on “Extensions”<BR /> Yes, it's quite similar name<BR /> The "Extension Credentials" screen is shown<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/CredScreen.jpg" height="126" width="344" /></P><BR /> Here we can store credentials of a service instance. As mentioned above, the service key of a service instance is used for external access.<BR /> Since we cannot <EM>bind</EM> a function to a service, it will always be external.<BR /> <SPAN style="color: #999999">Problem?</SPAN><BR /> No.<BR /> <BR /> In the “Extension Credentials” screen we can register a service instance.<BR /> It is clear that changes in this screen are not related to individual projects.<BR /> Services are registered globally in FaaS and can be used by multiple projects (extensions)<BR /> With other words: using the Extension Center, we can store credentials.<BR /> <BR /> So let's go ahead and press “New Credentials”<BR /> In the creation dialog, we have to enter following values:<BR /> <BR /> Type:<BR /> This is the service offering of the SAP Cloud Platform, the service names as displayed in the service marketplace. Luckily, we have tool support, such that we don't need to look up the type names.<BR /> For our example, we choose "enterprise-messaging"<BR /> <BR /> Service Instance:<BR /> Here we enter the name of the instance of the Enterprise Messaging service which we created in the preparation section above.<BR /> In our example: <STRONG>faas_msg_instance</STRONG><BR /> <BR /> Service Key:<BR /> Similarly, we enter the name of the service key created above<BR /> In our example: <STRONG>faas_service_key</STRONG><BR /> <BR /> Credentials:<BR /> Here we enter the value of the service key.<BR /> Remember I told you to not close the browser window...?<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/credWizard.jpg" height="291" width="298" /></P><BR /> <BR /> <H3 id="toc-hId--312133530">2. Use Registered Service</H3><BR /> As we’ve seen, the service is registered in the FaaS runtime on global level<BR /> With other words, we've stored service credentials in the Extension Center<BR /> Now we can use it in a project<BR /> So let’s create a new Faas Project, or, with other words, a new Extension<BR /> <BR /> <SPAN style="text-decoration: underline">Create Extension</SPAN><BR /> <BR /> In our example, we create an extension that consumes messages from a message queue and writes any silly statement to the log<BR /> <BR /> In the creation wizard, we choose use the "Blank Template"<BR /> And we give a name like "tracker"<BR /> <BR /> <U>Create Function</U><BR /> <BR /> We create a new Function with the following sample values:<BR /> <TABLE style="height: 39px;width: 100%;border-collapse: collapse;border-style: hidden" border="0"><BR /> <TBODY><BR /> <TR style="height: 13px"><BR /> <TD style="width: 50%;height: 13px;border-style: hidden">Function Name</TD><BR /> <TD style="width: 50%;height: 13px;border-style: hidden"><EM>tracker-func</EM></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 50%;height: 13px;border-style: hidden">Module Name</TD><BR /> <TD style="width: 50%;height: 13px;border-style: hidden"><EM>trackerimpl.js</EM></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 50%;height: 13px;border-style: hidden">Handler Name</TD><BR /> <TD style="width: 50%;height: 13px;border-style: hidden"><EM>msgreceiver</EM></TD><BR /> </TR><BR /> </TBODY><BR /> </TABLE><BR /> The function implementation could be as follows:<BR /> <PRE class="language-javascript"><CODE>module.exports = { <BR /> msgreceiver: function (event, context) { <BR /> console.log(event.data)<BR /> } <BR /> }</CODE></PRE><BR /> As usual, to keep the code simple and readable, the function implementation does:<BR /> nothing.<BR /> Just log the payload of the received message<BR /> In a productive scenario, the function would do anything more interesting, process the message and call any other component of the extension scenario<BR /> <SPAN style="font-size: 60%">(Otherwise it would be a shame: wake up the function, just to do nothing...)</SPAN><BR /> <BR /> <U>Create</U><U> AMQP Trigger</U><BR /> <BR /> In Extension Center, press "Add Trigger" and choose type <EM>AMQP<BR /> </EM>Step through the wizard and enter the following values:<BR /> <BR /> Step 1:<BR /> Trigger Name: <EM>amqp-tracker-tricker</EM><BR /> <BR /> Step 2:<BR /> Incoming link name: <EM>msg-to-faas-link<BR /> </EM>Source Address: <EM>queue:comp/busi/crm/queue/for/faas<BR /> </EM>With other words: the full queue name as created in the preparation section, preceded by "queue:"<BR /> To add the link, we press the PLUS icon<BR /> <BR /> Step 3: Outgoing Link:<BR /> We don't enter anything here<BR /> Only press “step 4”<BR /> <BR /> Step 4: Rules<BR /> Filter: <EM>msg-to-faas-link<BR /> </EM>Invoke Function: <EM>tracker-func<BR /> </EM>On failure: <EM>reject message<BR /> </EM>And we press the +<BR /> <BR /> Step 5: Credentials<BR /> Press “use existing credentials store”<BR /> Type: <EM>enterprise-messaging</EM><BR /> Instance: <EM>faas_msg_instance</EM><BR /> Key: <EM>faas_service_key</EM><BR /> <BR /> Finally confirm and close the wizard<BR /> <BR /> We can check the extension project to verify what the wizard has done for us:<BR /> <BR /> In file system:<BR /> Created folders to carry config files<BR /> Created config files to carry the configs<BR /> Created configs for the messaging trigger, containing the config we entered in the wizard<BR /> <BR /> In <SPAN style="font-family: Courier New">faas.json:<BR /> </SPAN>Defined a reference to the service which we registered in FaaS (Credentials store)<BR /> Defined a config map (generated name)<BR /> Defined the trigger which uses the config and the service-reference<BR /> <BR /> <SPAN style="text-decoration: underline">Small recap<BR /> </SPAN>Connecting a function to messaging involves:<BR /> <SPAN style="padding-left: 15px">Create and use service registration (alternatively, use secret)</SPAN><BR /> <SPAN style="padding-left: 15px">Create AMQP triger and configure it with queue / topic names and rules</SPAN><BR /> <H3 id="toc-hId--508647035">3. Run the scenario</H3><BR /> Remember the scenario:<BR /> A message is sent to the Messaging service, which causes the AMQP-trigger to wake up the function<BR /> Fortunately, the dashboard offers a little tool which allows to send messages.<BR /> <BR /> So we go to the Enterprise Messaging dashboard<BR /> To send a message to the queue which we defined above:<BR /> <SPAN style="padding-left: 20px">We select the messaging client created above</SPAN><BR /> <SPAN style="padding-left: 20px">We select the Tab “Test”</SPAN><BR /> <SPAN style="padding-left: 20px">We choose Action as “Publish Message to a Queue”</SPAN><BR /> <SPAN style="padding-left: 20px">Then we select our queue</SPAN><BR /> <SPAN style="padding-left: 20px">Obviously, we enter a silly useless message</SPAN><BR /> <SPAN style="padding-left: 20px">Finally, we press “Publish”</SPAN><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/sendMsg.jpg" height="270" width="250" /></P><BR /> What's happening now?<BR /> <BR /> The Test tool has sent a message to the queue<BR /> We can see in the same tool ui, that the “Number of Messages” has been increased from 0 to 1<BR /> The message would wait there in the queue until it is picked up<BR /> (That’s why a queue is called queue)<BR /> Now, we have somebody who is watching that queue and picks the messages which are dropped there:<BR /> <SPAN style="color: #999999">Yes.</SPAN><BR /> <SPAN style="color: #999999">Us.</SPAN><BR /> OK, but also the AMQP trigger is watching the queue (we configured it)<BR /> And whenever an innocent message falls into the queue, the trigger will wake up the function<BR /> Before it wakes up the function, the trigger checks the rules, of course<BR /> The function then receives the message in the parameter “event”<BR /> Like that, the function implementation can access the message payload<BR /> <SPAN style="color: #999999">That's what we did</SPAN><BR /> And the function can do something with the payload<BR /> <SPAN style="color: #999999">That's what we didn’t do</SPAN><BR /> Since the trigger has picked the message from the queue, the “Number of Messages” in the dashboard is reduced from 1 to 0<BR /> We can check that in the dashboard (<EM>Queues</EM> tab)<BR /> <BR /> What we need to check as well: the FaaS log<BR /> Here we can see the log output which was written by our function.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/log-1.jpg" height="133" width="424" /></P><BR /> This makes our little scenario complete:<BR /> A message is sent to Enterprise Messaging and the connected FaaS reacts and reads the message<BR /> <H2 id="localdev" id="toc-hId--834243259">Local Development</H2><BR /> The following description is meant for those of you who are used to work locally and interact with FaaS using the command line<BR /> Since you're anyways the experts, I'm only giving some useful commands<BR /> No explanations<BR /> <H3 id="toc-hId--976905414">Preparation</H3><BR /> Create instance of Enterprise Messaging service:<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf cs enterprise-messaging default faas_msg_instance -c &lt;jsonFileWithParams&gt;</SPAN><BR /> <BR /> Create service key<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf csk messaginginstance serviceKeyForMessaging</SPAN><BR /> <BR /> View the created service key<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf service-key faas_msg_instance faas_service_key</SPAN><BR /> <BR /> It is not required to copy the content<BR /> However, we will need to view the ID of the messaging service instance<BR /> So don't close this command shell<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/cli_instanceid.jpg" height="127" width="364" /></P><BR /> <BR /> <H3 id="toc-hId--1173418919">1. Register Service Instance</H3><BR /> Very first step: login to cloud foundry using the CF CLI<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf login</SPAN><BR /> <BR /> First step: login to the FaaS runtime using the FaaS CLI<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli login</SPAN><BR /> <BR /> Or we might want to check to which subaccount our FaaS CLI is currently pointing at<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli target</SPAN><BR /> <BR /> Now, to register an existing instance of Enterprise Messaging in FaaS, we use the following command<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas service register -s faas_msg_instance -b faas_service_key</SPAN><BR /> <BR /> Syntax:<BR /> <BR /> <SPAN style="font-family: Courier New">xfsrt-cli faas service register</SPAN><BR /> <SPAN style="font-family: Courier New;padding-left: 40px">--service-name &lt;name of instance&gt;</SPAN><BR /> <SPAN style="font-family: Courier New;padding-left: 40px">--service-binding &lt;name of service key&gt;</SPAN><BR /> <BR /> Note:<BR /> You need to be logged in Cloud Foundry using the CF CLI<BR /> If not, you might get an error message and it might be required to execute the service-registration command twice<BR /> <BR /> Note:<BR /> The FaaS CLI is so convenient that it proposes the existing service instances, so we can skip the parameters of the command<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/cli_reg.jpg" /></P><BR /> I have to repeat that the FaaS CLI is a really great and convenient tool<BR /> <BR /> Few more commands:<BR /> <BR /> To view already registered service instances:<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas service list</SPAN><BR /> <BR /> To unregister a service instance<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas service delete</SPAN><BR /> <BR /> then wait and choose the desired service<BR /> <H3 id="toc-hId--1369932424">2. Use Registered Service</H3><BR /> <U>Create a project </U><BR /> <BR /> On our local file system, we create a project based on the sample code in the appendix<BR /> <BR /> To use the registered service, we define a reference to it<BR /> <PRE class="language-javascript"><CODE>"services": {<BR /> "cs1": {<BR /> "type": "enterprise-messaging",<BR /> "instance": "a1b2c3d4-a1b2-1234-a1b2-a1b2c3d4e5f6",<BR /> "key": "faas_service_key"</CODE></PRE><BR /> Then we can use the service reference in our trigger:<BR /> <PRE class="language-javascript"><CODE>"triggers": {<BR /> "amqp-tracker-tricker": {<BR /> "type": "AMQP",<BR /> "service": "cs1",</CODE></PRE><BR /> Note:<BR /> In the <SPAN style="font-family: Courier New">faas.json</SPAN>, which is the manifest of our project, we define a reference to a registered service. Like that, we can use it in our project. The reference points to the registered service instance. It has to point to the unique name of the service instance. Otherwise there might be name clashes. As such, we have to look for the instance ID. We find it in the service key content<BR /> <BR /> <U>Deploy</U><BR /> <BR /> Jump into the project root directory (same folder where faas.json is located) and execute<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas project deploy</SPAN><BR /> <BR /> Note:<BR /> Even for service references it is possible to provide deploy values, as described <A href="https://blogs.sap.com/2020/07/10/writing-function-as-a-service-6-values.yaml" target="_blank" rel="noopener noreferrer">here</A><BR /> <H3 id="toc-hId--1566445929">3. Run the scenario</H3><BR /> To run our scenario, send messages from Enterprise Messaging Dashboard and make sure that they are consumed (number of messages in the queue must decrease to 0)<BR /> <BR /> Check the logs to view if our function works as expected<BR /> Jump into the project folder (to make the command easier), then execute<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas project logs</SPAN><BR /> <BR /> In the logs we can see the payload of the messages that we send<BR /> <H2 id="toc-hId--1469556427">Summary</H2><BR /> In this tutorial we've learned how to register an existing service instance in FaaS<BR /> With other words, we've learned how to store credentials in Extension Center<BR /> And we've learned how to use them in a FaaS project<BR /> To showcase it, we've used an instance of Enterprise Messaging service.<BR /> More precisely, we've used the registered service from an AMQP trigger<BR /> <H2 id="toc-hId--1666069932">Troubleshooting</H2><BR /> If you have headache because messages aren't arriving in Messaging Queue, you should check <A href="https://blogs.sap.com/2020/08/27/enterprise-event-enablement-troubleshooting/" target="_blank" rel="noopener noreferrer">this guide</A><BR /> <H2 id="quickguide" id="toc-hId--1862583437">Quick Guide</H2><BR /> An existing service instance can be registered globally in FaaS (service key is required)<BR /> In Extension Center, it is done via top level menu "Configuration"<BR /> With command line: <SPAN style="font-family: Courier New">xfsrt-cli faas service register</SPAN><BR /> <BR /> To use the registered service (credentials) in a faas.json:<BR /> Define new service reference under element "services"<BR /> Use service reference from trigger definition<BR /> Also (not covered in this blog), the stored credentials can be accessed from function code<BR /> <H2 id="sampleproject" id="toc-hId--2059096942">Appendix: All Sample Project Files</H2><BR /> I’ve downloaded the project from Extension Center and for your convenience, pasting the file content here<BR /> Note that I would have chosen different names, where it is visible that names were generated by Extension Center<BR /> <BR /> Note:<BR /> In the faas.json we have to enter the ID of the instance of the Enterprise Messaging service.<BR /> When registering the service instance, we can choose the instance by name<BR /> However, when defining the reference in the faas.json, we have to enter the ID<BR /> To retrieve the ID, proceed as follows<BR /> Go to chapter "Preparation-&gt;Create Service Key" and copy the value of property instanceid<BR /> If you coincidentally closed the window, proceed as follows<BR /> Open the content of the service key with command csk<BR /> Then search for the property instanceid (usually at the end of the json )<BR /> The value of the instanceid usually looks like this:<BR /> <SPAN style="font-family: Courier New">a1b2c3d4- a1b2- a1b2- a1b2- a1b2c3d4e5f6</SPAN><BR /> This has to be entered as value for the property instance in the faas.json<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/07/projectStructure-4.jpg" height="139" width="153" /></P><BR /> <SPAN style="text-decoration: underline">package.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{}</CODE></PRE><BR /> <SPAN style="text-decoration: underline">faas.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "project": "tracker",<BR /> "version": "0.0.1",<BR /> "runtime": "nodejs10",<BR /> "library": "./lib",<BR /> "configs": {<BR /> "amqp-tracker-tricker-config": {<BR /> "source": "./data/amqp-tracker-tricker-config"<BR /> }<BR /> },<BR /> "functions": {<BR /> "tracker-func": {<BR /> "module": "trackerimpl.js",<BR /> "handler": "msgreceiver",<BR /> "timeout": 180<BR /> }<BR /> },<BR /> "triggers": {<BR /> "amqp-tracker-tricker": {<BR /> "type": "AMQP",<BR /> "service": "cs1",<BR /> "config": "amqp-tracker-tricker-config"<BR /> }<BR /> },<BR /> "services": {<BR /> "cs1": {<BR /> "type": "enterprise-messaging",<BR /> "instance": "c2ab778a-0c68-4523-b7df-ce188bc67337",<BR /> "key": "faas_service_key"<BR /> }<BR /> }<BR /> }</CODE></PRE><BR /> <SPAN style="text-decoration: underline">amqp.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "incoming": {<BR /> "msg-to-faas-link": {<BR /> "sourceAddress": "queue:comp/busi/crm/queue/for/faas"<BR /> }<BR /> }<BR /> }</CODE></PRE><BR /> <SPAN style="text-decoration: underline">bind.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "functions": {<BR /> "tracker-func": {}<BR /> },<BR /> "rules": [<BR /> {<BR /> "filter": {<BR /> "incoming": "msg-to-faas-link"<BR /> },<BR /> "action": {<BR /> "function": "tracker-func",<BR /> "failure": "reject",<BR /> "content": "application/json"<BR /> }<BR /> }<BR /> ]<BR /> }</CODE></PRE><BR /> <SPAN style="text-decoration: underline">trackerimpl.js</SPAN><BR /> <PRE class="language-javascript"><CODE>module.exports = { <BR /> msgreceiver: function (event, context) { <BR /> console.log(event.data)<BR /> } <BR /> }</CODE></PRE><BR /> Params for messaging instance:<BR /> <PRE class="language-javascript"><CODE>{<BR /> "options": {<BR /> "management": true,<BR /> "messagingrest": true,<BR /> "messaging": true<BR /> },<BR /> "rules": {<BR /> "topicRules": {<BR /> "publishFilter": [<BR /> "${namespace}/*"<BR /> ],<BR /> "subscribeFilter": [<BR /> "${namespace}/*"<BR /> ]<BR /> },<BR /> "queueRules": {<BR /> "publishFilter": [<BR /> "${namespace}/*"<BR /> ],<BR /> "subscribeFilter": [<BR /> "${namespace}/*"<BR /> ]<BR /> }<BR /> },<BR /> "version": "1.1.0",<BR /> "emname": "faas_msg_client",<BR /> "namespace": "comp/busi/crm"<BR /> }</CODE></PRE> 2020-07-23T17:18:59+02:00 https://community.sap.com/t5/technology-blogs-by-sap/writing-function-as-a-service-9-how-to-call-oauth-protected-endpoint/ba-p/13467981 Writing Function-as-a-Service [9]: How to call OAuth-protected endpoint 2020-08-19T10:32:42+02:00 CarlosRoggan https://community.sap.com/t5/user/viewprofilepage/user-id/5495 This blog is part of a&nbsp;<A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs/" target="_blank" rel="noopener noreferrer">series of tutorials</A>&nbsp;explaining how to write serverless functions using the Functions-as-a-Service offering in&nbsp;<STRONG>SAP Cloud Platform Serverless Runtime</STRONG><BR /> <P style="text-align: right">Quicklinks:<BR /> <SPAN style="font-size: smaller"><A href="#quickguide" target="_blank" rel="nofollow noopener noreferrer">Quick Guide</A><BR /> <A href="#samples" target="_blank" rel="nofollow noopener noreferrer">Sample Code</A></SPAN></P><BR /> <BR /> <H2 id="toc-hId-934497631">Introduction</H2><BR /> In function code we can do basically anything that is possible in <A href="https://nodejs.org/en/" target="_blank" rel="noopener noreferrer nofollow">Node.js</A><BR /> Sounds good, but sometimes there are hurdles<BR /> Like the (necessary but tedious) security aspects<BR /> If we do a demo and call a REST service endpoint from within a function:<BR /> then that endpoint will always be open, to make the demo small and nice<BR /> However, usually, REST endpoints are protected and users need to authenticate with user/password<BR /> -&gt; That’s easy, just provide basic auth<BR /> Other endpoints require an apiKey<BR /> -&gt; No prob, we set it as header<BR /> Prof endpoints are protected with OAuth authentication<BR /> -&gt; Here it gets more complicated, but I showed it in <A href="https://blogs.sap.com/2020/07/16/writing-function-as-a-service-7-how-to-use-platform-services/" target="_blank" rel="noopener noreferrer">previous blog</A><BR /> Really prof endpoints are OAuth protected and require authorization<BR /> -&gt; This is a bit more tricky and requires some config in addition to the OAuth flow<BR /> <BR /> Why tricky? Just assign a role to the user...?<BR /> Stop:<BR /> A function is not a user and we cannot assign the required role to the function like we would assign it to a user<BR /> Luckily, this tutorial shows how to achieve that:<BR /> Write a function that calls an endpoint that is protected with OAuth and requires scope<BR /> <BR /> With other words, about the tricky part:<BR /> How to assign a scope to a function in client-credentials flow<BR /> <BR /> <SPAN style="text-decoration: underline">Remember OAuth 2.0:</SPAN><BR /> <BR /> In order to access a resource (REST endpoint), we need a JWT token.<BR /> We get that token from “Authorization Server” (XSUAA)<BR /> To get it, there are several “grant types”:<BR /> - “Resource Owner Password Credentials”: human user authenticates with password (login screen) and token contains scopes based on his roles<BR /> - “client credentials”: from app to app, no login<BR /> (see <A href="https://blogs.sap.com/2019/05/06/sap-cloud-platform-backend-service-tutorial-14-about-oauth-mechanism/" target="_blank" rel="noopener noreferrer">here</A> for more info)<BR /> <BR /> In our scenario, the function runs without user context<BR /> As such we’re talking about “client credentials” flow<BR /> We don’t have user password.<BR /> We cannot get a user password by opening a login screen<BR /> <BR /> Of course, we know how to obtain a JWT token from XSUAA service instance<BR /> But that token doesn’t contain the scope which is required by the protected REST endpoint<BR /> <BR /> So what needs to be done in order to get a JWT token containing a certain scope?<BR /> The trick is in the configuration of the xsuaa instances<BR /> <BR /> Note:<BR /> Of course, it is possible to call a function from e.g. a UI5 application, which is user centric and has user login<BR /> In that scenario, the function would receive the JWT token from the UI5 application, so nothing needs to be done<BR /> So no tutorial required<BR /> This tutorial is for the non-user-centric scenario, no UI, no user, no fun<BR /> <H2 id="prerequisites" id="toc-hId-737984126">Prerequisites</H2><BR /> – Access to&nbsp;<EM>SAP Cloud Platform</EM>, productive account. Trial is (currently) not supported<BR /> – Instance of <EM>Serverless Runtime (s</EM>ee&nbsp;<A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-0.1-preparation/" target="_blank" rel="noopener noreferrer">Preparation blog</A>)<BR /> – Cloud Foundry CLI installed locally (see&nbsp;<A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-0.1-preparation/#local" target="_blank" rel="noopener noreferrer">same Preparation blog</A>)<BR /> – Functions CLI available locally (see&nbsp;<A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-0.1-preparation/#local" target="_blank" rel="noopener noreferrer">still same link</A>)<BR /> –&nbsp;<A href="https://nodejs.org/" target="_blank" rel="noopener noreferrer nofollow">Node.js</A>&nbsp;installed<BR /> – Basic knowledge of writing functions in SAP Cloud Platform serverless runtime<BR /> <H2 id="toc-hId-541470621">Overview</H2><BR /> In the first part of this tutorial, we’re creating a very basic app which exposes an endpoint which is protected with OAuth and which requires a certain scope<BR /> In the second part, we’re creating a Function which tries to call that endpoint<BR /> <BR /> <A href="#part1" target="_blank" rel="nofollow noopener noreferrer">Part 1</A>: The Protected App<BR /> <SPAN style="padding-left: 20px">Create xsuaa, define scope</SPAN><BR /> <SPAN style="padding-left: 20px">Create app, check scope</SPAN><BR /> <BR /> <A href="#part2" target="_blank" rel="nofollow noopener noreferrer">Part 2</A>: The Calling Function<BR /> <SPAN style="padding-left: 20px">Create xsuaa, define authority</SPAN><BR /> <SPAN style="padding-left: 20px">Create Function, do OAuth flow</SPAN><BR /> <H2 id="preparation" id="toc-hId-344957116">Preparation: Create Project Structure</H2><BR /> In our example, we're using one root Project <SPAN style="background-color: #ffe78f;font-family: Courier New">C:\tmp_faas_callsafe</SPAN><BR /> containing 2 working directories,<BR /> for the secure app <SPAN style="background-color: #ffe78f;font-family: Courier New">C:\tmp_faas_callsafe\safeapp</SPAN><BR /> and for the Function <SPAN style="background-color: #ffe78f;font-family: Courier New">C:\tmp_faas_callsafe\unsafefunction</SPAN><BR /> So let's create the folder structure:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/08/projectStructure.jpg" height="240" width="182" /></P><BR /> Beautiful.<BR /> <H2 id="part1" id="toc-hId-148443611">Part 1: <STRONG>The Protected App</STRONG></H2><BR /> We use <A href="https://nodejs.org/en/" target="_blank" rel="noopener noreferrer nofollow">Node.js</A> and <A href="https://www.npmjs.com/package/express" target="_blank" rel="noopener noreferrer nofollow">express</A> to create a simple app which provides a REST endpoint<BR /> We use <A href="https://www.npmjs.com/package/passport" target="_blank" rel="noopener noreferrer nofollow">passport</A> and <A href="https://www.npmjs.com/package/@sap/xssec" target="_blank" rel="noopener noreferrer nofollow">@sap/xssec</A> to protect it with OAuth 2.0<BR /> We use <A href="http://deadLink" target="_blank" rel="noopener noreferrer nofollow">manual code</A> to enforce the required authorization<BR /> We use an instance of <A href="https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/51ec15a8979e497fbcaadf80da9b63ba.html" target="_blank" rel="noopener noreferrer">XSUAA</A> service for managing the OAuth flow<BR /> <H3 id="toc-hId-81012825">1.1. Security Configuration</H3><BR /> Security for our app is carried out by an instance of XSUAA<BR /> We configure it with the following params<BR /> <PRE class="language-javascript"><CODE>{<BR /> "xsappname" : "xsappforsafeapp",<BR /> "tenant-mode" : "dedicated",<BR /> "scopes": [{<BR /> "name": "$XSAPPNAME.scopeformysafety",<BR /> "grant-as-authority-to-apps" : [ "$XSAPPNAME(application, xsappforfaas)"]<BR /> }]<BR /> }<BR /> </CODE></PRE><BR /> This content can be pasted during instance creation in cloud cockpit, but for better handling, we store above content in a file with a name of our choice.<BR /> In my example, the file has a silly name to avoid confusion: <SPAN style="font-family: Courier New;background-color: #f5f5f5">xs-security-safe.json</SPAN><BR /> <BR /> <SPAN style="text-decoration: underline">Short explanation</SPAN><BR /> <BR /> <SPAN style="font-family: Courier New">xsappname<BR /> </SPAN>The name of the security artifact (Internally, it is treated like an application).<BR /> Security artifacts to be distinguished by XSUAA and by us.<BR /> <BR /> <SPAN style="font-family: Courier New">scope</SPAN><BR /> We declare: we require that anybody who calls our endpoint should have special permission.<BR /> To distinguish “our” permission from others, we give a name to “our” special permission<BR /> This is the "scope".<BR /> Note: we aren’t defining a “role”. This means that no human user will be able to call our endpoint<BR /> In our tutorial we don’t need it<BR /> So let’s keep the file short and focus on the essential<BR /> <BR /> <SPAN style="font-family: Courier New">"grant-as-authority-to-apps"</SPAN><BR /> This is the essential line:<BR /> Instead of assigning a “role” to a human user, we assign it to an application.<BR /> Because that’s what we want to learn in this tutorial: how a function from FaaS can call a protected app that requires a special "scope"<BR /> Our app is protected with oauth and requires a scope.<BR /> And "grant" is the mechanism how an external application (FaaS in our case) can get the permission/authorization to call the protected endpoint<BR /> Note:<BR /> We have to understand that we’re not assigning the scope to the name of a deployed Node.js application (or whatever)<BR /> What we’re explicitly writing here: the name of the security artifact. The name of the security artifact is the value of the property <SPAN style="font-family: Courier New">xsappname<BR /> </SPAN>To avoid confusion, I always give stupid names to all artifacts, that makes it easier to distinguish.<BR /> As such, if a name starts with xsapp..., it is the name of a security artifact<BR /> In the current tutorial, we will create a second instance of xsuaa and we will name it <EM>xsappforfaas<BR /> </EM>So that's what we write here: We "grant" the new scope to the security artifact with name <EM>xsappforfaas</EM><BR /> Note:<BR /> The syntax for defining the permitted application:<BR /> <SPAN style="font-family: Courier New">"$XSAPPNAME(&lt;service_plan&gt;, &lt;name_of_xsapp_attribute&gt;)"<BR /> </SPAN>Note:<BR /> In the section below, there will be a note to type the value of <SPAN style="font-family: Courier New">xsappname</SPAN> exactly like here<BR /> Or we can say: here we have to type the value of <SPAN style="font-family: Courier New">xsappname</SPAN> exactly like we’ll type it in the section below<BR /> Doesn't matter: No typos allowed for <SPAN style="font-family: Courier New">xsappname</SPAN> attribute, otherwise we'll get errors<BR /> <BR /> Anyways, creating a file is not enough: this isn’t a deployment artifact, it is just a source file containing the security description<BR /> So now we have to go ahead and create an instance of XSUAA service<BR /> As everybody knows, it can be done in the cockpit (then point to this file) or on command line (and point to this file)<BR /> In my example, we jump into <SPAN style="background-color: #ffe78f;font-family: Courier New">C:\tmp_faas_callsafe\safeapp</SPAN> and run the following command<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf cs xsuaa application xsuaaforsafetyapp -c xs-security-safe.json</SPAN><BR /> <H3 id="toc-hId--115500680">1.2. Create Application</H3><BR /> Now it is time to protect our created application<BR /> Sorry, my fault<BR /> I wanted to say:<BR /> Now it is time to create our protected application<BR /> <BR /> We've already created the project structure, we only need to copy and paste the file contents from the <A href="#samples" target="_blank" rel="nofollow noopener noreferrer">appendix</A><BR /> <BR /> As usual, the app does nothing.<BR /> Only be there<BR /> And be protected<BR /> <BR /> So what we have to do:<BR /> In order to "be there" we have to do nothing<BR /> In order to “be protected” we have to do two things:<BR /> <BR /> We have to “wish” that the caller has that scope when he or it calls our endpoint<BR /> We have to enforce that the caller has the "wished" scope<BR /> <BR /> This is done manually by checking the scope and refusing the access to our endpoint<BR /> To manually check the scope, we have to do 2 things:<BR /> <BR /> We have call a helper to do the work (check scope)<BR /> We have to act upon the result:<BR /> If we don’t like the result of the scope check, we say it<BR /> <BR /> This has been confusing.<BR /> Let's try again:<BR /> <BR /> The "passport" node module does the authentication check before our code is reached<BR /> <PRE class="language-javascript"><CODE>const jwtStrategy = new JWTStrategy(xsuaaCredentials)<BR /> passport.use(jwtStrategy)<BR /> ...<BR /> app.use(passport.authenticate('JWT', { session: false }));<BR /> </CODE></PRE><BR /> The "passport" module itself is configured with specific xsuaa implementation for OAuth<BR /> As such, passport can check if a JWT token is present and if it is valid<BR /> Afterwards, our endpoint-implementation is invoked and we can check manually if the JWT token contains the scope that we wish.<BR /> We use a helper method for that check:<BR /> <PRE class="language-javascript"><CODE>const fullScopeName = xsuaaCredentials.xsappname + '.scopeformysafety'<BR /> if(! req.authInfo.checkScope(fullScopeName)){<BR /> return res.status(403).json({<BR /> message: "We don't like the scopes in the JWT token"<BR /> </CODE></PRE><BR /> Our error response is not polite, but clear<BR /> <H3 id="toc-hId--312014185">1.3. Deploy and Run the App</H3><BR /> We create a <SPAN style="font-family: Courier New">manifest.yml</SPAN> file according to the <A href="#samples" target="_blank" rel="nofollow noopener noreferrer">appendix</A> section.<BR /> It might be required to change the app name in the manifest, in case it is already taken<BR /> <BR /> In the manifest, we declare that we want to bind the app to the instance of XSUAA service which we created above<BR /> Like that, our app has access to the XSUAA server which is necessary to protect our app with OAuth 2.0<BR /> <BR /> We deploy the app to our SAP Cloud Platform space, located in the same subaccount like our serverless runtime instance<BR /> <BR /> After deployment, we can try to invoke our endpoint in the browser<BR /> <BR /> <SPAN style="color: #0000ff"><A href="https://safetyapp.cfapps.eu10.hana.ondemand.com/securedEntry" target="test_blank" rel="nofollow noopener noreferrer">https://safetyapp.cfapps.eu10.hana.ondemand.com/securedEntry</A></SPAN><BR /> <BR /> The response is <SPAN style="font-family: Courier New">Unauthorized</SPAN> with status code 401<BR /> This response sounds quite polite, so it is not ours... it is provided by the FWK which we’ve used<BR /> Since we’ve invoked the endpoint in the browser, there’s no JWT token present at all, so our call is immediately rejected<BR /> <H3 id="toc-hId--508527690">1.4. Small Recap</H3><BR /> We’ve created an instance of XSUAA service<BR /> We’ve configured it with our desired scope and grant<BR /> We’ve created an application with an endpoint, to be secured<BR /> We’ve bound it to the instance of XSUAA, such that XSUAA can help in protecting the app<BR /> In the code, we’ve added authentication and implemented authorization check<BR /> <H2 id="part2" id="toc-hId--834123914">Part 2: The Calling Function</H2><BR /> Now we’re coming to the main part of our tutorial:<BR /> How to access the secured app from within a function?<BR /> <BR /> Basically, our function has to perform the OAuth flow.<BR /> It has to go to XSUAA and ask for a JWT token.<BR /> Then go to the safeguarded app and handover the token<BR /> And it has to pray that the token contains the required scope<BR /> <BR /> Aha!<BR /> <BR /> How to ensure that the JWT token contains the required scope?<BR /> Praying is good – but in this tutorial we’re learning the security mechanism<BR /> <H3 id="toc-hId--976786069">2.1. Security Configuration</H3><BR /> We create an instance of XSUAA service, dedicated for our function.<BR /> Again, this xsuaa instance is configured with params stored in a json file.<BR /> <BR /> The file is located in the working directory for the function <SPAN style="background-color: #ffe78f;font-family: Courier New">unsafefunction</SPAN> and we name this file <SPAN style="font-family: Courier New;background-color: #f5f5f5">xs-security-faas.json</SPAN>, to make sure that we don’t mix it.<BR /> <BR /> The content is copied from the appendix and looks like this:<BR /> <PRE class="language-javascript"><CODE>{<BR /> "xsappname" : "xsappforfaas",<BR /> "tenant-mode" : "dedicated",<BR /> "authorities":["$XSAPPNAME(application,xsappforsafeapp).scopeformysafety"]<BR /> }</CODE></PRE><BR /> The security configuration contains the essential line, to acquire the “authority”.<BR /> Basically, once created, the security artifact already HAS the authority “…scopeformysafety”, because it is GRANTED by the other XSUAA security artifact, the other xsapp<BR /> Remember the attribute <SPAN style="font-family: Courier New">grant-as-authority-to-apps<BR /> </SPAN>Nevertheless, the faas-xsapp has to explicitly "accept" the granted scope<BR /> <BR /> The syntax:<BR /> <SPAN style="font-family: Courier New">$XSAPPNAME(&lt;service_plan&gt;,&lt;name_of_foreign_xsapp&gt;).&lt;name_of_foreign_scope&gt;</SPAN><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/08/diagramDetailed.jpg" /></P><BR /> To create the XSUAA-instance for FaaS, we jump into the function folder <SPAN style="background-color: #ffe78f;font-family: Courier New">unsafefunction</SPAN> and we execute the following command:<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf cs xsuaa application xsuaaforfaas -c xs-security-faas.json</SPAN><BR /> <BR /> As usual, in order to use it from FaaS, we need to create a service key:<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf csk xsuaaforfaas servicekeyforfaas</SPAN><BR /> <BR /> Of course, both can be done from the Cloud Cockpit, if you prefer:<BR /> <BR /> First create the service instance of XSUAA, along with above JSON parameters<BR /> Then click on the new instance and create a service key with above name<BR /> <H3 id="toc-hId--1173299574">2.2. Create Function</H3><BR /> At this point, we’ve already learned everything we’re supposed to learn in this tutorial:<BR /> We’ve done the security configuration which allows to call an app from a function<BR /> We could say good-bye now, or good night… &nbsp;but that wouldn’t be good<BR /> So let’s quickly finish<BR /> <H4 id="toc-hId--1663216086"><SPAN style="text-decoration: underline">2.2.1. Register Service Instance</SPAN></H4><BR /> We know how to use a service instance from FaaS: There are 2 ways, the <A href="https://blogs.sap.com/2020/07/23/writing-function-as-a-service-8-how-to-easily-use-platform-services/" target="_blank" rel="noopener noreferrer">second one</A> is more comfortable: The service registration allows to store service credentials in FaaS itself<BR /> It can be done in Extension Center, but currently not all service types are supported.<BR /> So let's use the command line<BR /> <BR /> First we do the login<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli login</SPAN><BR /> <BR /> Then we run the interactive service registration command<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas service register</SPAN><BR /> <BR /> When prompted, we choose the instance <EM>xsuaaforfaas</EM> from the list<BR /> <BR /> Note:<BR /> If you don’t see it, although it is created and shown by the CF CLI, then you’ve probably forgotten to create a service key<BR /> <BR /> After creation, we may view the content of the service key<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf service-key xsuaaforfaas servicekeyforfaas</SPAN><BR /> <H4 id="toc-hId--1859729591"><SPAN style="text-decoration: underline">2.2.2. Declare Usage</SPAN></H4><BR /> Once the service instance is registered in FaaS, we have to declare the usage of it in our faas project.<BR /> In <SPAN style="font-family: Courier New">faas.json</SPAN>, we need to reference the registered service by its guid<BR /> To get that guid, we ask the FaaS itself, with the command<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas service list -o yaml</SPAN><BR /> <BR /> The result of this command prints all registered services and additional information, like the guid:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/08/servicelist.jpg" height="195" width="243" /></P><BR /> Note:<BR /> The parameter <SPAN style="font-family: Courier New">-o yaml</SPAN> (alternatively, <SPAN style="font-family: Courier New">-o json</SPAN>) specifies the format of the output<BR /> <BR /> We copy the guid from the service-section<BR /> In <SPAN style="font-family: Courier New">faas.json</SPAN>, we can now add the following section<BR /> <PRE class="language-javascript"><CODE> "services": {<BR /> "regxsuaa":{<BR /> "type": "xsuaa",<BR /> "instance": "ab11ab11-ab11-ab11-ab11-ab11ab11ab11",<BR /> "key": "servicekeyforfaas" <BR /> }<BR /> }<BR /> </CODE></PRE><BR /> Here we declare that in the project we're using that concrete registered service, and we assign a name to it<BR /> <H4 id="toc-hId--2056243096"><SPAN style="text-decoration: underline">2.2.3. Declare Usage Usage</SPAN></H4><BR /> Next: declare the usage of the declared usage<BR /> In <SPAN style="font-family: Courier New">faas.json</SPAN>, we’ve declared the usage of the registered service and we’ve assigned an alias: “regxsuaa”<BR /> Now we can declare that our function declaration wants to make use of that registered service instance<BR /> <PRE class="language-javascript"><CODE> "functions": {<BR /> "oauthcallerfun": {<BR /> "module": "caller.js",<BR /> "handler": "doCallProtectedSrv",<BR /> "services": ["regxsuaa"]<BR /> }<BR /> }</CODE></PRE><BR /> <H3 id="toc-hId--1959353594">2.3. Implement Function</H3><BR /> Coming to the function code, in folder<BR /> <SPAN style="background-color: #ffe78f;font-family: Courier New">C:\tmp_faas_callsafe\unsafefunction\lib</SPAN><BR /> and file<BR /> <SPAN style="font-family: Courier New;background-color: #f5f5f5">caller.js</SPAN><BR /> <BR /> High-level overview of what the code is doing:<BR /> <SPAN style="padding-left: 20px">Nothing</SPAN><BR /> <BR /> I mean, there’s nothing new in this section, the interesting trick, to get the scenario running, was in the configuration of the xsuaa.<BR /> So we can go ahead and copy the code from the <A href="#samples" target="_blank" rel="nofollow noopener noreferrer">appendix</A><BR /> <BR /> Less-high-level overview:<BR /> <SPAN style="padding-left: 20px">It does nothing but executing the OAuth flow and calling our app (which does nothing)</SPAN><BR /> <SPAN style="padding-left: 20px">It does nothing with the result, only return it</SPAN><BR /> <BR /> Detailed overview:<BR /> <BR /> 1. Access the registered service instance to obtain the credentials for XSUAA Authorization server<BR /> <PRE class="language-javascript"><CODE>const xsuaaServiceKey = await context.getServiceCredentialsString('regxsuaa') <BR /> const xsuaaCredentials = JSON.parse(xsuaaServiceKey)<BR /> const oauthUrl = xsuaaCredentials.url<BR /> const clientId = xsuaaCredentials.clientid<BR /> const clientSecret = xsuaaCredentials.clientsecret </CODE></PRE><BR /> 2. Call the XSUAA server to get a JWT token<BR /> <PRE class="language-javascript"><CODE>const jwtToken = await _fetchJwtToken(oauthUrl, clientId, clientSecret)</CODE></PRE><BR /> 3. Use the JWT token to call the silly endpoint<BR /> <PRE class="language-javascript"><CODE>const result = await _callEndpoint(jwtToken)</CODE></PRE><BR /> The implementation of this function is a normal request (using native module to avoid dependency)<BR /> The URL of the protected endpoint is hard-coded here<BR /> <PRE class="language-javascript"><CODE>const options = {<BR /> host: 'safetyapp.cfapps.eu10.hana.ondemand.com',<BR /> path: '/secureEntry',<BR /> headers: {<BR /> Authorization: 'Bearer ' + jwtToken<BR /> }<BR /> } <BR /> https.get(options, res =&gt; {<BR /> let response = ''<BR /> res.on('data', chunk =&gt; {<BR /> response += chunk<BR /> })<BR /> res.on('end', () =&gt; {<BR /> resolve({message: `Function called protected service. Endpoint response: \n${response}`})<BR /> . . .</CODE></PRE><BR /> 4. Get the response and return it<BR /> <PRE class="language-javascript"><CODE>return result.message</CODE></PRE><BR /> <H3 id="toc-hId-2139100197">2.4. Deploy and Run the Function</H3><BR /> To deploy the function project we jump into folder <SPAN style="background-color: #ffe78f;font-family: Courier New">C:\tmp_faas_callsafe\unsafefunction</SPAN><BR /> and execute<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas project deploy</SPAN><BR /> <BR /> Afterwards, we can&nbsp; invoke the function via HTTP trigger.<BR /> The URL looks similar like this:<BR /> <BR /> <SPAN style="color: #3366ff"><A href="https://abc123-faas-http.tenant.eu10.functions.xfs.cloud.sap/callendpoint/" target="test_blank" rel="nofollow noopener noreferrer">https://abc123-faas-http.tenant.eu10.functions.xfs.cloud.sap/callendpoint/</A></SPAN><BR /> <BR /> As a result, we get the response of our function which contains the response of our protected app<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/08/result.jpg" height="230" width="617" /></P><BR /> <BR /> <H2 id="toc-hId--2058977597">Summary</H2><BR /> If an OAuth-and-scope-protected app should be called by a function (no user-login), two configuration steps are required:<BR /> <UL><BR /> <LI>The app has to explicitly “grant” the scope to the function</LI><BR /> <LI>The function has to accept it</LI><BR /> </UL><BR /> <SPAN style="font-size: 1rem">These configuration steps are done in the </SPAN><SPAN style="font-family: Courier New">xs-security.json</SPAN><SPAN style="font-size: 1rem"> files of the 2 XSUAA instances</SPAN><BR /> <BR /> In this tutorial, we’ve learned how to protect an app with OAuth 2 and how to enforce a scope. And we’ve learned how to call such protected app from a serverless function<BR /> <H2 id="quickguide" id="toc-hId-2039476194">Quick Guide</H2><BR /> An app that requires OAuth and scope, can allow to be accessed by other app (without user-login)<BR /> The xsappname of consuming app has to be entered<BR /> <PRE class="language-javascript"><CODE> "scopes": [{<BR /> "grant-as-authority-to-apps" : [ "$XSAPPNAME(application, xsappforfaas)"]</CODE></PRE><BR /> An app or function that wants to call the protected (and granting) app, has to declare the access<BR /> <PRE class="language-javascript"><CODE>"authorities":["$XSAPPNAME(application,xsappforsafeapp).scopeformysafety"]</CODE></PRE><BR /> <H2 id="links" id="toc-hId-1842962689">Links</H2><BR /> <UL><BR /> <LI>About OAuth: see this <A href="https://blogs.sap.com/2019/05/06/sap-cloud-platform-backend-service-tutorial-14-about-oauth-mechanism/" target="_blank" rel="noopener noreferrer">blog</A></LI><BR /> <LI>Manual OAuth flow for Password credentials: explained in detail&nbsp;<A href="https://blogs.sap.com/2019/04/29/sap-cloud-platform-backend-service-tutorial-13-api-called-from-external-tool/" target="_blank" rel="noopener noreferrer">here</A></LI><BR /> <LI>Programmatic OAuth flow: see this <A href="https://blogs.sap.com/2019/05/20/sap-cloud-platform-backend-service-tutorial-18-api-called-from-external-node.js/" target="_blank" rel="noopener noreferrer">example</A></LI><BR /> <LI>Today's&nbsp;tutorial is based on an <A href="https://blogs.sap.com/2020/06/02/how-to-call-protected-app-from-external-app-as-external-user-with-scope/" target="_blank" rel="noopener noreferrer">older blog</A> where I already explained same topic</LI><BR /> </UL><BR /> <H2 id="samples" id="toc-hId-1814632875">Appendix: All Project Files</H2><BR /> Here you can find all the code required for this tutorial, ready for copy&amp;paste<BR /> You only need to adapt:<BR /> - the app name in the manifest<BR /> - the instanceID in the faas.json<BR /> <BR /> For your convenience, see here the Project Structure again:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/08/projectStructure-1.jpg" height="268" width="203" /></P><BR /> <BR /> <H3 id="toc-hId-1324716363">Part 1: The Protected App</H3><BR /> These are the files of the app which provides an endpoint and which is called by the function<BR /> These files are located in the folder "safeapp"<BR /> <BR /> <SPAN style="text-decoration: underline">xs-security-safe.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "xsappname" : "xsappforsafeapp",<BR /> "tenant-mode" : "dedicated",<BR /> "scopes": [{<BR /> "name": "$XSAPPNAME.scopeformysafety",<BR /> "grant-as-authority-to-apps" : [ "$XSAPPNAME(application, xsappforfaas)"]<BR /> }]<BR /> }<BR /> </CODE></PRE><BR /> <SPAN style="text-decoration: underline">manifest.yml</SPAN><BR /> <PRE class="language-javascript"><CODE>---<BR /> applications:<BR /> - name: safetyapp<BR /> memory: 128M<BR /> buildpacks:<BR /> - nodejs_buildpack<BR /> services:<BR /> - xsuaaforsafetyapp</CODE></PRE><BR /> <SPAN style="text-decoration: underline">package.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "main": "server.js",<BR /> "dependencies": {<BR /> "@sap/xsenv": "latest",<BR /> "@sap/xssec": "latest",<BR /> "express": "^4.16.3",<BR /> "passport": "^0.4.1"<BR /> }<BR /> }<BR /> </CODE></PRE><BR /> <SPAN style="text-decoration: underline">server.js</SPAN><BR /> <PRE class="language-javascript"><CODE>const express = require('express');<BR /> const passport = require('passport');<BR /> const xsenv = require('@sap/xsenv');<BR /> const JWTStrategy = require('@sap/xssec').JWTStrategy;<BR /> <BR /> const xsuaaService = xsenv.getServices({ myXsuaa: { tag: 'xsuaa' }});<BR /> const xsuaaCredentials = xsuaaService.myXsuaa; <BR /> const jwtStrategy = new JWTStrategy(xsuaaCredentials)<BR /> passport.use(jwtStrategy);<BR /> <BR /> const app = express();<BR /> app.use(passport.initialize());<BR /> app.use(passport.authenticate('JWT', { session: false }));<BR /> <BR /> // our protected endpoint<BR /> app.get('/secureEntry', function(req, res){ <BR /> console.log('===&gt; Endpoint has been reached. Authentication ok. Now checking authorization')<BR /> <BR /> const fullScopeName = xsuaaCredentials.xsappname + '.scopeformysafety'<BR /> if(! req.authInfo.checkScope(fullScopeName)){<BR /> return res.status(403).json({<BR /> error: 'Unauthorized',<BR /> message: "We don't like the scopes in the JWT token"<BR /> }) <BR /> }<BR /> <BR /> res.send('Successfully passed security control' );<BR /> });<BR /> <BR /> // start server<BR /> app.listen(process.env.PORT, () =&gt; {})</CODE></PRE><BR /> <H3 id="toc-hId-1128202858">Part 2: The Calling Function</H3><BR /> These are the files required for the function<BR /> They are located in the folder "unsafefunction"<BR /> <BR /> <SPAN style="text-decoration: underline">xs-security-faas.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "xsappname" : "xsappforfaas",<BR /> "tenant-mode" : "dedicated",<BR /> "authorities":["$XSAPPNAME(application,xsappforsafeapp).scopeformysafety"]<BR /> }</CODE></PRE><BR /> <SPAN style="text-decoration: underline">faas.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "project": "calloauth",<BR /> "version": "0.0.1",<BR /> "runtime": "nodejs10",<BR /> "library": "./lib",<BR /> "functions": {<BR /> "oauthcallerfun": {<BR /> "module": "caller.js",<BR /> "handler": "doCallProtectedSrv",<BR /> "services": ["regxsuaa"]<BR /> }<BR /> },<BR /> <BR /> "triggers": {<BR /> "callendpoint": {<BR /> "type": "HTTP",<BR /> "function": "oauthcallerfun"<BR /> }<BR /> },<BR /> "services": {<BR /> "regxsuaa":{<BR /> "type": "xsuaa",<BR /> "instance": "ab12ab12-ab12-ab12-ab12-ab12ab12ab12",<BR /> "key": "servicekeyforfaas" <BR /> }<BR /> }<BR /> }</CODE></PRE><BR /> <SPAN style="text-decoration: underline">package.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{}</CODE></PRE><BR /> <SPAN style="text-decoration: underline">caller.js</SPAN><BR /> <PRE class="language-javascript"><CODE>const https = require('https');<BR /> <BR /> // main entry<BR /> const _faasHandler = async function (event, context) {<BR /> <BR /> const xsuaaServiceKey = await context.getServiceCredentialsString('regxsuaa') <BR /> const xsuaaCredentials = JSON.parse(xsuaaServiceKey)<BR /> const oauthUrl = xsuaaCredentials.url<BR /> const clientId = xsuaaCredentials.clientid<BR /> const clientSecret = xsuaaCredentials.clientsecret <BR /> <BR /> // call the target endpoint<BR /> const jwtToken = await _fetchJwtToken(oauthUrl, clientId, clientSecret)<BR /> const result = await _callEndpoint(jwtToken)<BR /> <BR /> return result.message<BR /> } <BR /> <BR /> const _fetchJwtToken = async function(oauthUrl, oauthClient, oauthSecret) {<BR /> return new Promise ((resolve, reject) =&gt; {<BR /> const options = {<BR /> host: oauthUrl.replace('https://', ''),<BR /> path: '/oauth/token?grant_type=client_credentials&amp;response_type=token',<BR /> headers: {<BR /> Authorization: "Basic " + Buffer.from(oauthClient + ':' + oauthSecret).toString("base64")<BR /> }<BR /> }<BR /> https.get(options, res =&gt; {<BR /> res.setEncoding('utf8')<BR /> let response = ''<BR /> res.on('data', chunk =&gt; {<BR /> response += chunk<BR /> })<BR /> res.on('end', () =&gt; {<BR /> try {<BR /> const responseAsJson = JSON.parse(response)<BR /> const jwtToken = responseAsJson.access_token <BR /> if (!jwtToken) {<BR /> return reject(new Error('Error while fetching JWT token'))<BR /> }<BR /> resolve(jwtToken)<BR /> } catch (error) {<BR /> return reject(new Error('Error while fetching JWT token')) <BR /> }<BR /> })<BR /> })<BR /> .on("error", (error) =&gt; {<BR /> console.log("Error: " + error.message);<BR /> return reject({error: error})<BR /> });<BR /> }) <BR /> }<BR /> <BR /> const _callEndpoint = async function(jwtToken){<BR /> return new Promise((resolve, reject) =&gt; {<BR /> const options = {<BR /> host: 'safetyapp.cfapps.eu10.hana.ondemand.com',<BR /> path: '/secureEntry',<BR /> headers: {<BR /> Authorization: 'Bearer ' + jwtToken<BR /> }<BR /> } <BR /> https.get(options, res =&gt; {<BR /> res.setEncoding('utf8')<BR /> let response = ''<BR /> res.on('data', chunk =&gt; {<BR /> response += chunk<BR /> })<BR /> res.on('end', () =&gt; {<BR /> const status = res.statusCode<BR /> if(status &lt; 400){<BR /> resolve({message: `Function called protected service. Endpoint response: \n${response}`})<BR /> }else{<BR /> reject({error: {message: `Error calling endpoint with status ${status}`}})<BR /> }<BR /> })<BR /> })<BR /> .on("error", (error) =&gt; {<BR /> reject({error: error})<BR /> }); <BR /> })<BR /> }<BR /> <BR /> // export the handler function with readable alias<BR /> module.exports = {<BR /> doCallProtectedSrv : _faasHandler<BR /> }</CODE></PRE> 2020-08-19T10:32:42+02:00 https://community.sap.com/t5/technology-blogs-by-sap/writing-function-as-a-service-10-call-protected-endpoint-across-subaccounts/ba-p/13479226 Writing Function-as-a-Service [10]: Call protected endpoint across Subaccounts 2020-08-27T10:02:41+02:00 CarlosRoggan https://community.sap.com/t5/user/viewprofilepage/user-id/5495 This blog is part of a&nbsp;<A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs/" target="_blank" rel="noopener noreferrer">series of tutorials</A>&nbsp;explaining how to write serverless functions using the Functions-as-a-Service offering in&nbsp;<STRONG>SAP Cloud Platform Serverless Runtime</STRONG><BR /> <P style="text-align: right">Quicklinks:<BR /> <SPAN style="font-size: smaller"><A href="#quickguide" target="_blank" rel="nofollow noopener noreferrer">Quick Guide</A><BR /> <A href="#samples" target="_blank" rel="nofollow noopener noreferrer">Sample Code</A></SPAN></P><BR /> <BR /> <H2 id="toc-hId-935473826">Introduction</H2><BR /> In the <A href="https://blogs.sap.com/2020/08/19/writing-function-as-a-service-9-how-to-call-oauth-protected-endpoint/" target="_blank" rel="noopener noreferrer">previous tutorial</A> we‘ve learned how to call a service which is protected with OAuth and requires a scope.<BR /> The challenge was:<BR /> -&gt; how can the function possess the scope?<BR /> The solution was:<BR /> -&gt; configuration of both xs-security.json files<BR /> <BR /> BUT:<BR /> There was a precondition:<BR /> -&gt; both app and function have to live in the same subaccount<BR /> <BR /> This is necessary because the central XSUAA of the subaccount creates one oauth-client for each instance and assigns the granted scope to the other client.<BR /> Thus, it has to resolve the names of the scope and involved clients<BR /> This is possible because the name of the scope is made unique and identifiable by adding the variable <SPAN style="font-family: Courier New">$XSAPPNAME</SPAN> to the scope name.<BR /> As we all know, the value of the variable is not just the same as the name of the property <SPAN style="font-family: Courier New">xsappname<BR /> </SPAN>The value of <SPAN style="font-family: Courier New">$XSAPPNAME</SPAN> is generated at runtime and contains some mystic suffix <SPAN style="font-family: Courier New">!t1234</SPAN><BR /> <BR /> OK.<BR /> Today we want to call from the function to a service which lives in a different subaccount.<BR /> So now, the XSUAA has to find not only the correct scope and oauth-client.<BR /> It has to find it in a different subaccount (identityzone)<BR /> <BR /> How to do it?<BR /> -&gt; communicate with the other central XSUAA<BR /> <BR /> How to find that one?<BR /> -&gt; By the unique identifier of the subaccount (identityzone)<BR /> <BR /> As such, we have to add the subaccountID to the <SPAN style="font-family: Courier New">GRANT</SPAN> statement<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/08/diagram1-1.jpg" /></P><BR /> And that’s already all for today<BR /> <BR /> Nevertheless, let’s make it real<BR /> <H2 id="toc-hId-738960321">Overview</H2><BR /> In this tutorial, we’re going to learn how to assign a scope to an application in a different subaccount<BR /> <BR /> The scenario is almost the same as in the <A href="https://blogs.sap.com/2020/08/19/writing-function-as-a-service-9-how-to-call-oauth-protected-endpoint/" target="_blank" rel="noopener noreferrer">previous tutorial</A>.<BR /> We can either re-use it and do the required little changes<BR /> Or create the project as described below, everything based on the previous blog<BR /> <BR /> In the first part of this tutorial, we’re creating a very basic app which exposes an endpoint which is protected with OAuth and which requires a certain scope<BR /> In the second part, we’re creating a Function which tries to call that endpoint<BR /> <BR /> <A href="#part1" target="_blank" rel="nofollow noopener noreferrer">Part 1</A>: The Protected App<BR /> <SPAN style="padding-left: 20px">Create xsuaa, define scope</SPAN><BR /> <SPAN style="padding-left: 20px">Create app, check scope</SPAN><BR /> <BR /> <A href="#part2" target="_blank" rel="nofollow noopener noreferrer">Part 2</A>: The Calling Function<BR /> <SPAN style="padding-left: 20px">Create xsuaa, define authority</SPAN><BR /> <SPAN style="padding-left: 20px">Create Function, do OAuth flow</SPAN><BR /> <H2 id="toc-hId-542446816">Prerequisites</H2><BR /> <UL><BR /> <LI>If you’re new to the topic, the previous tutorial is a prerequisite, along with all its <A href="https://blogs.sap.com/2020/08/19/writing-function-as-a-service-9-how-to-call-oauth-protected-endpoint/#prerequisites" target="_blank" rel="noopener noreferrer">prerequisites</A> and blablabla</LI><BR /> <LI>In addition:<BR /> To follow this tutorial, we need 2 different subaccounts.<BR /> In my example, I’m using my trial account, in addition to my FaaS-account (containing the instance of SAP Cloud Platform serverless runtime)<BR /> Limitation:<BR /> Both accounts have to live in the same data center (in my example, eu10)</LI><BR /> </UL><BR /> <H2 id="preparation" id="toc-hId-345933311">Preparation: Create Project Structure</H2><BR /> Same <A href="https://blogs.sap.com/2020/08/19/writing-function-as-a-service-9-how-to-call-oauth-protected-endpoint/#preparation" target="_blank" rel="noopener noreferrer">preparation</A> like in previous blog: create files and folders<BR /> <BR /> In addition:<BR /> We need to find the ID of the FaaS-subaccount.<BR /> It is easy to find: just open the subaccount in the cloud cockpit<BR /> Which subaccount?<BR /> We need the ID of the subaccount where the FaaS is located<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/08/subacc_ID.jpg" /></P><BR /> <BR /> <H2 id="part1" id="toc-hId-149419806">Part 1: <STRONG>The Protected App</STRONG></H2><BR /> Almost same as <A href="https://blogs.sap.com/2020/08/19/writing-function-as-a-service-9-how-to-call-oauth-protected-endpoint/#part1" target="_blank" rel="noopener noreferrer">part 1</A> of previous tutorial<BR /> Really only the <SPAN style="font-family: Courier New">xs-security.json</SPAN> file is little bit different<BR /> <H3 id="toc-hId-81989020">1.1. Security Configuration</H3><BR /> This is the essential section of this tutorial:<BR /> Here we’re using the subaccount ID:<BR /> -&gt; grant the scope to an oauth-client living in the specified subaccount<BR /> <PRE class="language-javascript"><CODE>{<BR /> "xsappname" : "xsappforsafeapp",<BR /> "scopes": [{<BR /> "name": "$XSAPPNAME.scopeformysafety",<BR /> "grant-as-authority-to-apps" : [ "$XSAPPNAME(application, 12ab12ab-34cd-56ef-34cd-12ab12ab12ab, xsappforfaas)"]<BR /> }]<BR /> }<BR /> </CODE></PRE><BR /> &nbsp;<BR /> <BR /> Syntax:<BR /> <PRE class="language-javascript"><CODE>"grant-as-authority-to-apps" : [ <BR /> "$XSAPPNAME(&lt;service_plan&gt;, &lt;subaccount_id&gt;, &lt; xsappname_of_caller &gt;)"<BR /> ]</CODE></PRE><BR /> After clarifying the new security descriptor, we create an instance of XSUAA in our Trial account (or whatever account you’ve chosen)<BR /> <BR /> We have to make sure that we're targeting the different account, e.g. trial<BR /> To change location:<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf t -o p123456trial</SPAN><BR /> <BR /> see:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/08/cftarget.jpg" height="129" width="303" /></P><BR /> OK, for your convenience, here's again the command to create a service instance:<BR /> In my example, we jump into <SPAN style="background-color: #ffe78f;font-family: Courier New">C:\tmp_faas_callsafe\safeapp</SPAN> and run the following command<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf cs xsuaa application xsuaaforsafetyapp -c xs-security-safe.json</SPAN><BR /> <H3 id="toc-hId--114524485">1.2. Create Application</H3><BR /> No change needed to the app.<BR /> Just take the sample code (Part 1) from <A href="https://blogs.sap.com/2020/08/19/writing-function-as-a-service-9-how-to-call-oauth-protected-endpoint/#samples" target="_blank" rel="noopener noreferrer">previous tutorial</A><BR /> <H3 id="toc-hId--311037990">1.3. Deploy and Run the App</H3><BR /> Yapp, just deploy it to the different account.<BR /> No need to run it, we did it in the previous torture<BR /> <H3 id="toc-hId--507551495">1.4. Small Recap</H3><BR /> We deploy a protected app to trial account.<BR /> We specify that the Function of FAC account is allowed to call us<BR /> So we add a statement to grant access to the calling xsapp<BR /> And here is the place to enter the ID of the FAC account<BR /> <H2 id="part2" id="toc-hId--833147719">Part 2: The Calling Function</H2><BR /> Again, all the same as in <A href="https://blogs.sap.com/2020/08/19/writing-function-as-a-service-9-how-to-call-oauth-protected-endpoint/#part2" target="_blank" rel="noopener noreferrer">one</A> of the other boring tutorials<BR /> <H3 id="toc-hId--975809874">2.1. Security Configuration</H3><BR /> In part 1 we learned how to grant a scope to an xsuaa instance in a different subaccount<BR /> Now we’re on the receiving side: we receive the granted scope and we need to accept it<BR /> In the previous tutorial, we used the following statement:<BR /> <BR /> <SPAN style="font-family: Courier New">"authorities":["$XSAPPNAME(application,xsappforsafeapp).scopeformysafety"]</SPAN><BR /> <BR /> Now we would need to make clear where to find that foreign xsapp<BR /> However, I haven’t found a way to add the subaccountID in this statement<BR /> OK, no prob.<BR /> -&gt; we have to fall back to the generic statement:<BR /> <BR /> <SPAN style="font-family: Courier New">"authorities":["$ACCEPT_GRANTED_AUTHORITIES"]</SPAN><BR /> <BR /> Using this statement, we accept all scopes granted by anybody to our app (xsapp, to be more precise)<BR /> <BR /> So now we open the file <SPAN style="font-family: Courier New;background-color: #f5f5f5">xs-security-faas.json</SPAN>, in folder <SPAN style="background-color: #ffe78f;font-family: Courier New">unsafefunction</SPAN> and enter the following content<BR /> <PRE class="language-javascript"><CODE>{<BR /> "xsappname" : "xsappforfaas",<BR /> "tenant-mode" : "dedicated",<BR /> "authorities":["$ACCEPT_GRANTED_AUTHORITIES"]<BR /> }</CODE></PRE><BR /> Note:<BR /> This statement doesn't apply only for different subaccounts, it can be used in any scenario<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/08/diagramDetailed-1.jpg" /></P><BR /> As usual. this JSON config can be used to create or update&nbsp; an instance of XSUAA.<BR /> <BR /> Before executing a command, don't forget that this time, we have to make sure that we target the subaccount where FaaS is living<BR /> <BR /> To create the XSUAA-instance for FaaS, we jump into the function folder <SPAN style="background-color: #ffe78f;font-family: Courier New">unsafefunction</SPAN> then:<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf cs xsuaa application xsuaaforfaas -c xs-security-faas.json</SPAN><BR /> <BR /> And create a service key:<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf csk xsuaaforfaas servicekeyforfaas</SPAN><BR /> <BR /> BUT:<BR /> If you have the scenario in place, It is enough to update the existing service:<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf update-service xsuaaforfaas -c xs-security-faas.json</SPAN><BR /> <BR /> &nbsp;<BR /> <BR /> &nbsp;<BR /> <H3 id="toc-hId--1172323379">2.2. Create Function</H3><BR /> All the same as in <A href="https://blogs.sap.com/2020/08/19/writing-function-as-a-service-9-how-to-call-oauth-protected-endpoint/#part2" target="_blank" rel="noopener noreferrer">previous</A> hands-on. It shouldn’t be necessary to do any change, register or deploy.<BR /> But it might be necessary to repeat steps, if the xsuaa instance was deleted, etc<BR /> Or to adapt the code, if the name of the protected app was changed in trial<BR /> <BR /> Otherwise, we can just use the deployed function, if remaining from the previous blog<BR /> <BR /> Or we create everything from scratch, based on the <A href="https://blogs.sap.com/2020/08/19/writing-function-as-a-service-9-how-to-call-oauth-protected-endpoint/#samples" target="_blank" rel="noopener noreferrer">sample code Part 2</A><BR /> <BR /> &nbsp;<BR /> <BR /> And then, finally, we invoke the function and are happy to see the same result which we had before<BR /> No shame to be happy:<BR /> We’ve learned the little trick which enables us to cross the borders of subaccounts<BR /> <H2 id="toc-hId--1075433877">Summary</H2><BR /> In this blog we’ve learned almost nothing<BR /> Just adding a cryptic guid in the middle of a cryptic statement<BR /> Luckily, the blog post hasn’t been too long…<BR /> <H2 id="quickguide" id="toc-hId--1271947382">Quick Guide</H2><BR /> The protected app which is called by the Function has to grant the scope to the Function<BR /> If both are not located in the same subaccount, then the subaccountID of the Function has to be added (ID can be found in the cockpit)<BR /> <BR /> <SPAN style="font-family: Courier New">xs-security.json</SPAN> of protected app:<BR /> <PRE class="language-javascript"><CODE>"scopes": [{<BR /> "name": "$XSAPPNAME.scopeformysafety",<BR /> "grant-as-authority-to-apps" : [ <BR /> "$XSAPPNAME(application, 12345678-abcd-..., xsappforfaas)"<BR /> <BR /> <BR /> </CODE></PRE><BR /> The calling Function has to accept the grant. In case of different subaccounts, the generic statement has to be used to accept all granted scopes<BR /> <BR /> <SPAN style="font-family: Courier New">xs-security.json</SPAN>: of Function<BR /> <BR /> &nbsp;<BR /> <PRE class="language-javascript"><CODE>"authorities":["$ACCEPT_GRANTED_AUTHORITIES"]</CODE></PRE><BR /> <H2 id="links" id="toc-hId--1468460887">Links</H2><BR /> Same as in <A href="https://blogs.sap.com/2020/08/19/writing-function-as-a-service-9-how-to-call-oauth-protected-endpoint/#links" target="_blank" rel="noopener noreferrer">previous tuutorial</A><BR /> <H2 id="samples" id="toc-hId--1664974392">Appendix: All Project Files</H2><BR /> The whole project can be found in <A href="https://blogs.sap.com/2020/08/19/writing-function-as-a-service-9-how-to-call-oauth-protected-endpoint/#samples" target="_blank" rel="noopener noreferrer">previous tutorial</A><BR /> <BR /> However, we have to adjust the following files in both projects<BR /> <H3 id="toc-hId-2140076392">Part 1: The Protected App</H3><BR /> These files are located in the folder "safeapp"<BR /> <BR /> <SPAN style="text-decoration: underline">xs-security-safe.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "xsappname" : "xsappforsafeapp",<BR /> "tenant-mode" : "dedicated",<BR /> "scopes": [{<BR /> "name": "$XSAPPNAME.scopeformysafety",<BR /> "grant-as-authority-to-apps" : [ <BR /> "$XSAPPNAME(application, 1a2b3c4d-0000-1111-aaaa-..., xsappforfaas)"<BR /> ]<BR /> }]<BR /> }<BR /> </CODE></PRE><BR /> <H3 id="toc-hId-1943562887">Part 2: The Calling Function</H3><BR /> They are located in the folder "unsafefunction"<BR /> <BR /> <SPAN style="text-decoration: underline">xs-security-faas.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "xsappname" : "xsappforfaas",<BR /> "tenant-mode" : "dedicated",<BR /> "authorities":["$ACCEPT_GRANTED_AUTHORITIES"]<BR /> }</CODE></PRE><BR /> &nbsp; 2020-08-27T10:02:41+02:00 https://community.sap.com/t5/technology-blogs-by-sap/sap-cloud-platform-extension-factory-serverless-runtime-function-as-a/ba-p/13482670 SAP Cloud Platform [Extension Factory], serverless runtime | Function-as-a-Service | FaaS 2020-09-21T14:49:15+02:00 CarlosRoggan https://community.sap.com/t5/user/viewprofilepage/user-id/5495 <P style="text-align: center"><STRONG>SAP Cloud Platform Extension Factory, serverless runtime</STRONG></P><BR /> <P style="text-align: center">|</P><BR /> <P style="text-align: center"><STRONG>SAP Cloud Platform Serverless Runtime</STRONG></P><BR /> <P style="text-align: center">|</P><BR /> <P style="text-align: center"><STRONG>SAP Cloud Platform Functions - Beta</STRONG></P><BR /> <P style="text-align: center">|</P><BR /> <P style="text-align: center"><STRONG>Function-as-a-Service</STRONG></P><BR /> <P style="text-align: center">|</P><BR /> <P style="text-align: center"><STRONG>FaaS</STRONG></P><BR /> <P style="text-align: center">|</P><BR /> &nbsp;<BR /> <BR /> <SPAN style="color: #999999">I'm confused...</SPAN><BR /> <SPAN style="color: #999999">What’s the difference?</SPAN><BR /> <BR /> To give a short answer: It’s all the same<BR /> (almost)<BR /> At the end, we can write some javascript code and it runs in a serverless environment<BR /> <BR /> <SPAN style="color: #999999">What is it, precisely?</SPAN><BR /> <BR /> It is a service offering in the SAP Cloud Platform, Cloud Foundry environment<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/09/tile-1.jpg" height="324" width="506" /></P><BR /> &nbsp;<BR /> <BR /> <SPAN style="color: #999999">But why that strange name?</SPAN><BR /> <BR /> While the name itself, <STRONG>Serverless Runtime</STRONG>, might still sound confusing, it makes more sense if we compare it to other “runtime” offerings in SAP Cloud Platform:<BR /> <BR /> <EM>Application Runtime</EM><BR /> The classic way of writing e.g. a java web application and deploying it to the cloud where it runs on classic java server with underlying JRE. The application developer takes care of operating and maintaining, etc.<BR /> Agree that these efforts are already less that in the even more classical onPrem world<BR /> <BR /> <EM>Serverless Runtime</EM><BR /> Here the developer doesn’t need to care about the runtime, meaning that he writes the code and the environment takes care about scaling etc just like it is expected of a serverless environment.<BR /> And, as expected, it causes cost only when running<BR /> This is the right choice for lightweight extensions, small portions of javascript code<BR /> It is nicely coupled with other offerings like Enterprise Messaging, Backend Service, OData Provisioning<BR /> <BR /> <EM>Kyma Runtime</EM><BR /> Bigger, more powerful, more flexible, more costly<BR /> <BR /> While the <EM>Serverless Runtime</EM> is a "service" offering, it also provides a subscription-based tool:<BR /> The <EM>Extension Center</EM><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/09/ExtensionCenter-1.jpg" /></P><BR /> <SPAN style="color: #999999">Nice, but where are the functions?</SPAN><BR /> <BR /> The screenshot helps us to understand:<BR /> <BR /> The name "Serverless Runtime" is an umbrella for several capabilities that can be used to build serverless extensions.<BR /> Writing functions is only one of the capabilities, provided by "Serverless Runtime"<BR /> We don't see any "Function" in the Extension Center UI. Instead, a&nbsp; function project is called "Extension".<BR /> <BR /> <SPAN style="color: #999999">Why?</SPAN><BR /> <BR /> We understand that this is not a general worldwide FaaS technology product. Functions are meant to be part of a serverless extension scenario.<BR /> An extension scenario which is meant to be simple and still provides the possibility to write code.<BR /> And in fact: it is REALLY easy<BR /> <BR /> <SPAN style="color: #999999">Blablabla...</SPAN><BR /> <BR /> Really.<BR /> Try to imagine this example scenario:<BR /> Get notified about Backend changes with Enterprise Messaging<BR /> On every change, a Function is triggered<BR /> It can call an API with OData provisioning to get more details<BR /> The function can store data with Backend Service<BR /> <BR /> Such a powerful extension scenario can be realized completely serverless, with few lines of code and little configuration<BR /> No local dev, no operation, no maintenance, no database trouble, no scaling headaches.<BR /> And at low cost, as everything has to be payed only when it does its work<BR /> <BR /> <SPAN style="color: #999999">OK, cool.</SPAN><BR /> <SPAN style="color: #999999">But what if the scenario grows and the offerings don't cover the required functionality?</SPAN><BR /> <BR /> In that case, the function can be easily and seamlessly migrated to <EM>Kyma</EM><BR /> <BR /> <SPAN style="color: #999999">Sounds like a marketing slogan...</SPAN><BR /> <BR /> It can be easily proven:<BR /> Both Kyma and Functions have the same underlying technology (Kybernetes)<BR /> <BR /> <SPAN style="color: #999999">Cool, thanks</SPAN><BR /> <BR /> Cool, time to close the blog....<BR /> <BR /> <SPAN style="color: #999999">Ehmmm - didn't you forget anything?</SPAN><BR /> <BR /> Thank you for reading<BR /> <BR /> <SPAN style="color: #999999">Welcome. Missing info: which is the <EM>CORRECT</EM> name?</SPAN><BR /> <BR /> <STRONG>SAP Cloud Platform Serverless Runtime</STRONG><BR /> <BR /> <SPAN style="color: #999999">Last question: how can we learn more?</SPAN><BR /> <BR /> This <A href="https://blogs.sap.com/2020/01/17/sap-cloud-platform-extension-factory-serverless-runtime-is-ga-now/" target="_blank" rel="noopener noreferrer">announcement blog</A> gives an introduction and overview about Serverless Runtime<BR /> SAP Help Portal: Serverless Runtime <A href="https://help.sap.com/viewer/bf7b2ff68518427c85b30ac3184ad215/Cloud/en-US/7b8cc2b0e8d141d6aa37c7dff4d70b82.html" target="_blank" rel="noopener noreferrer">official docu</A><BR /> Recommended <A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs" target="_blank" rel="noopener noreferrer">series of tutorials</A> which explain in detail how to implement functions<BR /> <BR /> <SPAN style="color: #999999">Thanks</SPAN> 2020-09-21T14:49:15+02:00 https://community.sap.com/t5/technology-blogs-by-sap/writing-function-as-a-service-11-how-and-why-access-http-api/ba-p/13481267 Writing Function-as-a-Service [11]: How and Why access HTTP API 2020-10-15T09:57:49+02:00 CarlosRoggan https://community.sap.com/t5/user/viewprofilepage/user-id/5495 This blog is part of a&nbsp;<A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs/" target="_blank" rel="noopener noreferrer">series of tutorials</A>&nbsp;explaining how to write serverless functions using the Functions-as-a-Service offering in&nbsp;<STRONG>SAP Cloud Platform Serverless Runtime</STRONG><BR /> <P style="text-align: right">Quicklinks:<BR /> <SPAN style="font-size: smaller"><A href="#quickguide" target="_blank" rel="nofollow noopener noreferrer">Quick Guide</A><BR /> <A href="#samples" target="_blank" rel="nofollow noopener noreferrer">Sample Code</A></SPAN></P><BR /> <BR /> <H2 id="toc-hId-936159144">Introduction</H2><BR /> In this blog we're talking about functions with HTTP trigger only. These are functions that are invoked with HTTP request.<BR /> In such scenarios, the function returns a value which ends up in the response body of the calling client (e.g. browser)<BR /> Sometimes, the function handler code needs more information that it gets from the FaaS runtime<BR /> Also, it may need to write custom info to the response<BR /> <BR /> For these cases, the FaaS runtime allows access to the underlying native node.js HTTP API<BR /> For those of you who require such functionality, I’m providing a silly example, to make your life easier<BR /> <BR /> First of all, 2 basic info that we have to understand:<BR /> <OL><BR /> <LI>In most cases, access to the underlying HTTP API is not needed.<BR /> As such, if we need it, we have to enable it<BR /> That’s done in <SPAN style="font-family: Courier New">faas.json</SPAN>, for each function definition</LI><BR /> <LI>The underlying HTTP API is the standard node.js <SPAN style="font-family: Courier New">http</SPAN> module<BR /> As such, no special tutorial needed here, please refer to the standard documentation<BR /> Here: <A style="font-size: 1rem" href="https://nodejs.org/api/http.html" target="_blank" rel="nofollow noopener noreferrer">https://nodejs.org/api/http.html</A></LI><BR /> <LI>And there’s one more basic info<BR /> We cannot mix both approaches<BR /> Either use the standard FaaS convenience methods OR use the HTTP API<BR /> With "FaaS convenience methods" I mean the following<BR /> Use return value to set the response:<BR /> <SPAN style="font-family: Courier New">return ‘Error, invalid function invocation’</SPAN><BR /> Use convenience method to set the status<BR /> <SPAN style="font-family: Courier New">event.setUnauthorized()</SPAN><BR /> With other words: if we want to add custom header to the response, we cannot use <SPAN style="font-family: Courier New">event.setUnauthorized()</SPAN> to set the status. We have to set the status with <SPAN style="font-family: Courier New">response.writeHead(400)</SPAN></LI><BR /> </OL><BR /> <H2 id="toc-hId-739645639">Overview</H2><BR /> <UL><BR /> <LI><A href="#howto" target="_blank" rel="nofollow noopener noreferrer">How to use HTTP API</A></LI><BR /> <LI><A href="#usecases" target="_blank" rel="nofollow noopener noreferrer">Example use cases</A></LI><BR /> <LI><A href="#create" target="_blank" rel="nofollow noopener noreferrer">Create sample function</A></LI><BR /> <LI><A href="#run" target="_blank" rel="nofollow noopener noreferrer">Run sample function</A></LI><BR /> </UL><BR /> <H2 id="toc-hId-543132134">Prerequisites</H2><BR /> If you're new to Serverless Runtime, Function-as-a-Service, you should check out the <A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs/" target="_blank" rel="noopener noreferrer">overview of blogs</A> and read all of them...<BR /> <H2 id="howto" id="toc-hId-346618629">How to use HTTP API</H2><BR /> First of all, the access to the API needs to be enabled.<BR /> This is done in a function definition in&nbsp; <SPAN style="font-family: Courier New">faas.json</SPAN><BR /> <PRE class="language-javascript"><CODE>"functions": {<BR /> "function-with-httpapi": {<BR /> "httpApi": true</CODE></PRE><BR /> Once enabled, the <SPAN style="font-family: Courier New">http</SPAN> property of <SPAN style="font-family: Courier New">event</SPAN> will be filled with <SPAN style="font-family: Courier New">request</SPAN> and <SPAN style="font-family: Courier New">response</SPAN> objects<BR /> <PRE class="language-javascript"><CODE>module.exports = async function (event, context) {<BR /> const request = event.http.request<BR /> const response = event.http.response</CODE></PRE><BR /> The <SPAN style="font-family: Courier New">request</SPAN> object represents the class <A href="https://nodejs.org/api/http.html#http_class_http_clientrequest" target="_blank" rel="noopener noreferrer nofollow">http.ClientRequest&nbsp;</A><BR /> and the <SPAN style="font-family: Courier New">response</SPAN> object the <A href="https://nodejs.org/api/http.html#http_class_http_serverresponse" target="_blank" rel="noopener noreferrer nofollow">http.ServerResponse</A><BR /> <BR /> And that's it about accessing the HTTP API<BR /> <H2 id="usecases" id="toc-hId-150105124">Example use cases</H2><BR /> Now let's see why and how we might need to use it<BR /> <BR /> A few example use cases where we need access to HTTP API:<BR /> <UL><BR /> <LI>HTTP method</LI><BR /> <LI>Query parameters</LI><BR /> <LI>Request Headers</LI><BR /> <LI>Trigger name</LI><BR /> <LI>Response Headers</LI><BR /> <LI>Response Body</LI><BR /> <LI>Response Status</LI><BR /> </UL><BR /> <SPAN style="text-decoration: underline">HTTP method</SPAN><BR /> <BR /> Sometimes we need to know with which HTTP verb we've been invoked. For instance, if our function should distinguish between GET or POST, etc<BR /> We can throw an error, or react accordingly<BR /> <PRE class="language-javascript"><CODE>const httpMethod = request.method<BR /> if (httpMethod != 'POST'){<BR /> response.writeHead(405, {<BR /> 'Content-Type': 'text/plain',<BR /> 'Allow': 'POST'<BR /> });<BR /> response.write('Request failed. Method not allowed. See response headers for hint');<BR /> response.end();<BR /> </CODE></PRE><BR /> <SPAN style="text-decoration: underline">Query parameters</SPAN><BR /> <BR /> Query parameters are the params that can be added to a URL after the <SPAN style="font-size: larger;font-family: Courier New">?</SPAN><BR /> Multiple parameters are appended with <SPAN style="font-size: larger;font-family: Courier New"><STRONG>&amp;</STRONG>&nbsp;</SPAN><BR /> E.g.<BR /> <SPAN style="color: #0000ff">xxxxx?customerName=otto&amp;userid=123</SPAN><BR /> <BR /> They’re accessed via the <SPAN style="font-family: Courier New">query</SPAN> property<BR /> <PRE class="language-javascript"><CODE>const name = event.http.request.query.customerName</CODE></PRE><BR /> It returns the value of the param<BR /> <BR /> <SPAN style="text-decoration: underline">Request Headers</SPAN><BR /> <BR /> We can find the headers sent with the request in the headers property<BR /> <PRE class="language-javascript"><CODE>request.headers</CODE></PRE><BR /> <SPAN style="text-decoration: underline">Trigger name</SPAN><BR /> <BR /> Two interesting headers are those provided by the <EM>FaaS</EM> runtime, giving info about the used trigger<BR /> <PRE class="language-javascript"><CODE>const triggerName = request.headers['sap-faas-http-trigger-name']<BR /> const triggerPath = request.headers['sap-faas-http-trigger-path'] </CODE></PRE><BR /> What is the difference?<BR /> /<BR /> Yes, the slash makes the path<BR /> In our example, the trigger name is <EM>customlogin</EM> and the path&nbsp; customlogin/<BR /> <BR /> <SPAN style="text-decoration: underline">Response Headers</SPAN><BR /> <BR /> In case of response object, we're interested in modifying it.<BR /> For instance, we might want to send some additional information in the response header, as it might be required by the caller of our function<BR /> To add own headers to the response:<BR /> <PRE class="language-javascript"><CODE>response.set('MyHeader', 'MyValue')<BR /> <BR /> response.append('CustomHeader', 'CustomValue')</CODE></PRE><BR /> Alternatively, set multiple headers and status at once<BR /> <PRE class="language-abap"><CODE>response.writeHead(403, {<BR /> 'Content-Type': 'text/plain',<BR /> 'FailureHint': 'Authorization required, see docu'<BR /> });</CODE></PRE><BR /> <SPAN style="text-decoration: underline">Response Body</SPAN><BR /> <BR /> In most cases, we set the response body using the standard way in FaaS: as return value<BR /> However, we might need to switch to http API, due to requirements of caller who might need some custom text in the response body in case of failure, etc<BR /> If this is the case we can use standard way of <SPAN style="font-family: Courier New">http.ServerResponse</SPAN> class<BR /> <PRE class="language-javascript"><CODE>response.write('Error. Don't ask admin. Don't see log for no info')</CODE></PRE><BR /> Note:<BR /> As mentioned, we cannot mix the used APIs.<BR /> If we set a header in the response, then we have to use this way of writing response<BR /> <BR /> <SPAN style="text-decoration: underline">Response Status </SPAN><BR /> <BR /> There can be several reasons why we might wish to set the response status code.<BR /> For instance, if we don’t support all the HTTP methods, then we have to set the response status to 405, which means Method not allowed<BR /> Also, we might have special requirements to the incoming call, so we would have to decide on our own to set the status code to 400, Bad Request<BR /> Please see Links section for reference<BR /> <BR /> Setting the response status code to a custom value can be again done in several ways<BR /> <BR /> E.g. directly setting the property value:<BR /> <PRE class="language-javascript"><CODE>response.statusCode = 405</CODE></PRE><BR /> Or again using the convenience method, where we can set a custom header at the same time:<BR /> <PRE class="language-javascript"><CODE>response.writeHead(405, {<BR /> 'Content-Type': 'text/plain',<BR /> </CODE></PRE><BR /> <H2 id="create" id="toc-hId--46408381">Create sample Function</H2><BR /> To make things less theoretical, you can find here a reusable sample project, which is meant to showcase how the HTTP API can be used.<BR /> As usual, it is a small silly sample, without real use case, but focusing on demonstrating some capabilities for your convenience, so you can easily copy&amp;paste and adapt for your own needs<BR /> <BR /> Please refer to the <A href="#samples" target="_blank" rel="nofollow noopener noreferrer">Appendix</A> for the full sample code<BR /> <BR /> Note:<BR /> In case that it doesn’t suit your needs, you can send me a personal message<BR /> <BR /> In this example, we’re simulating a kind of strange special login process for a customer app<BR /> The user of the function is required to pass a couple of pieces of login information:<BR /> <BR /> Customer Name as query param<BR /> Customer Password as request header<BR /> Authorization scope as body in a POST request<BR /> As such, only POST is supported<BR /> In addition, our function simulates usage of multiple endpoints and only one of them is meant for productive usage with login<BR /> As such, the function code has to access request header to determine the used trigger<BR /> <H3 id="toc-hId--113839167">Code walkthrough</H3><BR /> Our function does nothing than accessing the HTTP API and using it for some login-checks<BR /> In case of success, it does nothing, just return some silly text<BR /> <BR /> <SPAN style="text-decoration: underline">Preparation</SPAN><BR /> <BR /> First we declare the usage of the HTTP API in <SPAN style="font-family: Courier New">faas.json</SPAN><BR /> <PRE class="language-javascript"><CODE>"functions": {<BR /> "function-with-httpapi": {<BR /> "httpApi": true</CODE></PRE><BR /> Then we can access the HTTP API in function code.<BR /> Otherwise, the object would be empty<BR /> <PRE class="language-javascript"><CODE>const request = event.http.request<BR /> const response = event.http.response</CODE></PRE><BR /> Now, in our function, we can implement custom checks which wouldn't be possible without the HTTP API.<BR /> Our function contains 4 checks:<BR /> <BR /> <SPAN style="text-decoration: underline">1. Check for correct HTTP verb:</SPAN><BR /> <PRE class="language-javascript"><CODE>const httpMethod = request.method<BR /> if (httpMethod != 'POST'){<BR /> response.writeHead(405, {<BR /> 'Content-Type': 'text/plain',<BR /> 'Allow': 'POST'<BR /> });<BR /> response.write('Request failed. Method not allowed. See response headers for hint');<BR /> </CODE></PRE><BR /> <SPAN style="text-decoration: underline">2. Check for desired productive endpoint</SPAN><BR /> <BR /> In <SPAN style="font-family: Courier New">faas.json</SPAN> we’ve defined 2 triggers.<BR /> The only reason for the second one (<EM>nologin</EM>) is to be able to run this check<BR /> <PRE class="language-javascript"><CODE>const triggerName = request.headers['sap-faas-http-trigger-name']<BR /> if(triggerName != 'customlogin'){<BR /> response.statusCode = 400<BR /> </CODE></PRE><BR /> <SPAN style="text-decoration: underline">3. Check for authentication</SPAN><BR /> <BR /> Note that we don’t verify the customer name, only password – for the sake of simplicity<BR /> We need to access the request header<BR /> <PRE class="language-javascript"><CODE>const customerAuth = request.headers['customer-auth']<BR /> if(! customerAuth){<BR /> response.writeHead(401, {<BR /> 'Content-Type': 'text/plain',<BR /> 'FailureHint': 'Required: request header customer-auth containing customer password'<BR /> });<BR /> </CODE></PRE><BR /> <SPAN style="text-decoration: underline">4. Check scope</SPAN><BR /> <BR /> We use this to show how to access the request body<BR /> <PRE class="language-javascript"><CODE>if((! request.body) || (JSON.parse(request.body).customerAccess != 'true')){<BR /> response.writeHead(403, {<BR /> 'FailureHint': 'Authorization required...'<BR /> <BR /> </CODE></PRE><BR /> <SPAN style="text-decoration: underline">Success response</SPAN><BR /> <BR /> If the request has passed all checks, then we do nothing,<BR /> Just note that here we’re using the standard way of using a function:<BR /> The response body is sent as return value of the function<BR /> When I mentioned earlier that we cannot mix the HTTP API with standard API, I meant we cannot mix in one response definition. But here, in case of success, we don't do any custom header setting, etc, so we can just return a text<BR /> <PRE class="language-javascript"><CODE>const customerName = request.query.customerName <BR /> return `Function called successfully. Customer '${customerName}' is welcome.`<BR /> </CODE></PRE><BR /> <H2 id="run" id="toc-hId--439435391">Run the sample Function</H2><BR /> Let's deploy and run the sample action, to see our HTTP API implementation in action<BR /> After deploy, we want to see if your checks are executed properly and if we get the expected results in response body , status and header<BR /> <BR /> <SPAN style="text-decoration: underline"><span class="lia-unicode-emoji" title=":right_arrow:">➡️</span>1. Wrong HTTP verb</SPAN><BR /> <BR /> In our first example, we choose to use a wrong HTTP method, to see our first check working<BR /> <TABLE style="height: 117px;width: 100%;border-collapse: collapse;border-style: none" border="0"><BR /> <TBODY><BR /> <TR style="height: 13px"><BR /> <TD style="width: 100%;height: 13px" colspan="2"><STRONG>Request</STRONG></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">URL</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New"><A href="https://...faas.../customlogin/" target="test_blank" rel="nofollow noopener noreferrer">https://...faas.../customlogin/</A></SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Verb</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">GET</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Header</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">-</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Body</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">-</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 100%;height: 13px" colspan="2"><STRONG>Response</STRONG></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Body</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller">Error text</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Status</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">405</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Header</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">allow</SPAN></TD><BR /> </TR><BR /> </TBODY><BR /> </TABLE><BR /> See result:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/10/postman_405.jpg" height="210" width="401" /></P><BR /> &nbsp;<BR /> <BR /> <SPAN style="text-decoration: underline"><span class="lia-unicode-emoji" title=":right_arrow:">➡️</span>2. Wrong endpoint</SPAN><BR /> <BR /> Now we use the correct HTTP method, but the wrong trigger<BR /> <TABLE style="height: 117px;width: 100%;border-collapse: collapse;border-style: none" border="0"><BR /> <TBODY><BR /> <TR style="height: 13px"><BR /> <TD style="width: 100%;height: 13px" colspan="2"><STRONG>Request</STRONG></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">URL</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New"><A href="https://...faas.../nologin/" target="test_blank" rel="nofollow noopener noreferrer">https://...faas.../nologin/</A></SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Verb</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">POST</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Header</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">-</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Body</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-family: Courier New"><SPAN style="font-size: 11.6667px">-</SPAN></SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 100%;height: 13px" colspan="2"><STRONG>Response</STRONG></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Body</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller">Error text</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Status</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">400</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Header</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">-</SPAN></TD><BR /> </TR><BR /> </TBODY><BR /> </TABLE><BR /> See result:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/10/2postman_400.jpg" height="123" width="398" /></P><BR /> <SPAN style="text-decoration: underline"><span class="lia-unicode-emoji" title=":right_arrow:">➡️</span>3. Missing authentication</SPAN><BR /> <BR /> Next try: correct endpoint, but we don't send the required authentication data<BR /> <TABLE style="height: 117px;width: 100%;border-collapse: collapse;border-style: none" border="0"><BR /> <TBODY><BR /> <TR style="height: 13px"><BR /> <TD style="width: 100%;height: 13px" colspan="2"><STRONG>Request</STRONG></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">URL</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New"><A href="https://...faas.../customlogin/" target="test_blank" rel="nofollow noopener noreferrer">https://...faas.../customlogin/</A></SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Verb</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">POST</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Header</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">-</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Body</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-family: Courier New"><SPAN style="font-size: 11.6667px">-</SPAN></SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 100%;height: 13px" colspan="2"><STRONG>Response</STRONG></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Body</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller">Error text</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Status</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">401</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Header</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">failurehint</SPAN></TD><BR /> </TR><BR /> </TBODY><BR /> </TABLE><BR /> See result:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/10/postman_401.jpg" height="213" width="395" /></P><BR /> <SPAN style="text-decoration: underline"><span class="lia-unicode-emoji" title=":right_arrow:">➡️</span>4. Missing authorization</SPAN><BR /> <BR /> We send a request with mostly correct settings, only the scope, to be passed in the request body, is&nbsp; missing<BR /> <TABLE style="height: 117px;width: 100%;border-collapse: collapse;border-style: none" border="0"><BR /> <TBODY><BR /> <TR style="height: 13px"><BR /> <TD style="width: 100%;height: 13px" colspan="2"><STRONG>Request</STRONG></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">URL</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New"><A href="https://...faas.../customlogin/" target="test_blank" rel="nofollow noopener noreferrer">https://...faas.../customlogin/</A></SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Verb</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">POST</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Header</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">customer-auth : abc123</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Body</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-family: Courier New"><SPAN style="font-size: 11.6667px">-</SPAN></SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 100%;height: 13px" colspan="2"><STRONG>Response</STRONG></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Body</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller">Error text</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Status</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">403</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Header</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">failurehint</SPAN></TD><BR /> </TR><BR /> </TBODY><BR /> </TABLE><BR /> See result:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/10/4postman_403-1.jpg" height="77" width="401" /></P><BR /> <P style="overflow: hidden;margin-bottom: 0px"><SPAN style="text-decoration: underline"><span class="lia-unicode-emoji" title=":right_arrow:">➡️</span>5. Successful call</SPAN></P><BR /> Finally we send a request with correct settings, including the name parameter in the URL<BR /> <TABLE style="height: 117px;width: 100%;border-collapse: collapse;border-style: none" border="0"><BR /> <TBODY><BR /> <TR style="height: 13px"><BR /> <TD style="width: 100%;height: 13px" colspan="2"><STRONG>Request</STRONG></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">URL</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New"><A href="https://...faas.../customlogin/?customerName=otto" target="test_blank" rel="nofollow noopener noreferrer">https://...faas.../customlogin/?customerName=otto</A></SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Verb</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">POST</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Header</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">customer-auth : abc123</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Body</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">{ "customerAccess" : "true" }</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 100%;height: 13px" colspan="2"><STRONG>Response</STRONG></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Body</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller">Success text</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Status</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">200</SPAN></TD><BR /> </TR><BR /> <TR style="height: 13px"><BR /> <TD style="width: 9.68%;height: 13px"><SPAN style="font-size: smaller">Header</SPAN></TD><BR /> <TD style="width: 90.32%;height: 13px"><SPAN style="font-size: smaller;font-family: Courier New">-</SPAN></TD><BR /> </TR><BR /> </TBODY><BR /> </TABLE><BR /> See result:<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/10/5postman_200.jpg" height="191" width="375" /></P><BR /> <BR /> <H2 id="toc-hId--635948896">Summary</H2><BR /> In this tutorial, we’ve learned which steps are required to access the HTTP API<BR /> This is probably not necessary in most use cases of serverless function<BR /> But sometimes it required, so we need to know how it works<BR /> <BR /> We've learned that we need to enable it first i<SPAN style="font-size: 1rem">n </SPAN><SPAN style="font-family: Courier New">faas.json</SPAN><SPAN style="font-size: 1rem"><BR /> Then we can access it via the <SPAN style="font-family: Courier New">event.http </SPAN></SPAN><SPAN style="font-size: 1rem">property<BR /> And we've understood that we can use the standard node.js http methods&nbsp;</SPAN><BR /> <H2 id="quickguide" id="toc-hId--832462401">Quick Guide</H2><BR /> faas.json:<BR /> <PRE class="language-javascript"><CODE>"my-function": {<BR /> "httpApi": true<BR /> </CODE></PRE><BR /> Function code:<BR /> <PRE class="language-javascript"><CODE>const request = event.http.request<BR /> const response = event.http.response</CODE></PRE><BR /> <H2 id="links" id="toc-hId--681721549">Links</H2><BR /> <UL><BR /> <LI>Reference for underlying HTTP API: <A href="https://nodejs.org/api/http.html" target="_blank" rel="noopener noreferrer nofollow">https://nodejs.org/api/http.html</A></LI><BR /> <LI>API for request: <A href="https://nodejs.org/api/http.html#http_class_http_clientrequest" target="_blank" rel="noopener noreferrer nofollow">https://nodejs.org/api/http.html#http_class_http_clientrequest</A></LI><BR /> <LI>API for response: <A href="https://nodejs.org/api/http.html#http_class_http_serverresponse" target="_blank" rel="noopener noreferrer nofollow">https://nodejs.org/api/http.html#http_class_http_serverresponse</A></LI><BR /> <LI>Spec for status codes: <A href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html" target="_blank" rel="noopener noreferrer nofollow">https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html</A></LI><BR /> <LI>Spec for status code 401:&nbsp;<A href="https://tools.ietf.org/html/rfc7235#section-3.1" target="_blank" rel="noopener noreferrer nofollow">https://tools.ietf.org/html/rfc7235#section-3.1</A></LI><BR /> <LI>Spec for status code 403: <A href="https://tools.ietf.org/html/rfc7231#section-6.5.3" target="_blank" rel="noopener noreferrer nofollow">https://tools.ietf.org/html/rfc7231#section-6.5.3</A></LI><BR /> <LI>Spec for status code 405: <A href="https://tools.ietf.org/html/rfc7231#section-6.5.5" target="_blank" rel="noopener noreferrer nofollow">https://tools.ietf.org/html/rfc7231#section-6.5.5</A></LI><BR /> </UL><BR /> <H2 id="samples" id="toc-hId--878235054">Appendix: All Project Files</H2><BR /> Here you can find the project files used for the sample, ready for copy&amp;paste<BR /> <BR /> <SPAN style="text-decoration: underline">faas.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "project": "httpapiproject",<BR /> "version": "1",<BR /> "runtime": "nodejs10",<BR /> "library": "./src",<BR /> "functions": {<BR /> "function-with-httpapi": {<BR /> "module": "mymodule.js",<BR /> "httpApi": true<BR /> }<BR /> },<BR /> "triggers": {<BR /> "customlogin": {<BR /> "type": "HTTP",<BR /> "function": "function-with-httpapi"<BR /> },<BR /> "nologin": {<BR /> "type": "HTTP",<BR /> "function": "function-with-httpapi"<BR /> }<BR /> }<BR /> }</CODE></PRE><BR /> <SPAN style="text-decoration: underline">package.json</SPAN><BR /> <BR /> Adding dev dependency for local testing<BR /> <PRE class="language-javascript"><CODE>{<BR /> "devDependencies": {<BR /> "@sap/faas": "&gt;=0.7.0"<BR /> }<BR /> }<BR /> </CODE></PRE><BR /> <SPAN style="text-decoration: underline">mymodule.js</SPAN><BR /> <PRE class="language-javascript"><CODE>module.exports = async function (event, context) {<BR /> <BR /> // to access HTTP API, add param to function definition in faas.json: "httpApi": true<BR /> const request = event.http.request<BR /> const response = event.http.response<BR /> <BR /> // check request method<BR /> const httpMethod = request.method<BR /> if (httpMethod != 'POST'){<BR /> response.writeHead(405, {<BR /> 'Content-Type': 'text/plain',<BR /> 'Allow': 'POST'<BR /> });<BR /> response.write('Request failed. Method not allowed. See response headers for hint');<BR /> response.end();<BR /> return<BR /> }<BR /> <BR /> // check which HTTP trigger was used<BR /> const triggerName = request.headers['sap-faas-http-trigger-name']<BR /> if(triggerName != 'customlogin'){<BR /> response.statusCode = 400<BR /> response.write("Endpoint not supported. Use 'customlogin' endpoint")<BR /> response.end();<BR /> return<BR /> }<BR /> <BR /> // check authentication via request header<BR /> const customerAuth = request.headers['customer-auth']<BR /> if(! customerAuth){<BR /> response.writeHead(401, {<BR /> 'Content-Type': 'text/plain',<BR /> 'FailureHint': 'Required: request header customer-auth containing customer password'<BR /> });<BR /> response.write('Request failed. Unauthorized. Customer login data missing. See hint for more info');<BR /> response.end();<BR /> return<BR /> }<BR /> <BR /> // check authorization via request body <BR /> if((! request.body) || (JSON.parse(request.body).customerAccess != 'true')){<BR /> response.writeHead(403, {<BR /> 'Content-Type': 'text/plain',<BR /> 'FailureHint': 'Authorization for customer required: Request body with customer JSON access must be true'<BR /> });<BR /> response.write('Request failed. Forbidden. Customer authorization not sufficient. See hint for more info');<BR /> response.end();<BR /> return <BR /> }<BR /> <BR /> // use standard way of writing response<BR /> const customerName = request.query.customerName <BR /> return `Function called successfully. Customer '${customerName}' is welcome.`<BR /> };<BR /> </CODE></PRE> 2020-10-15T09:57:49+02:00 https://community.sap.com/t5/technology-blogs-by-sap/writing-function-as-a-service-13-secure-scenario-with-scope-and-consumer/ba-p/13466000 Writing Function-as-a-Service [13]: Secure scenario with scope and consumer 2020-10-29T15:06:37+01:00 CarlosRoggan https://community.sap.com/t5/user/viewprofilepage/user-id/5495 This blog is part of a&nbsp;<A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs/" target="_blank" rel="noopener noreferrer">series of tutorials</A>&nbsp;explaining how to write serverless functions using the Functions-as-a-Service offering in&nbsp;<STRONG>SAP Cloud Platform Serverless Runtime</STRONG><BR /> <P style="text-align: right">Quicklinks:<BR /> <SPAN style="font-size: smaller"><A href="#quickguide" target="_blank" rel="nofollow noopener noreferrer">Quick Guide</A><BR /> <A href="#samples" target="_blank" rel="nofollow noopener noreferrer">Sample Code</A></SPAN></P><BR /> <BR /> <H2 id="toc-hId-934458942">Introduction</H2><BR /> In the <A href="https://blogs.sap.com/2020/10/29/writing-function-as-a-service-12-adding-security/" target="_blank" rel="noopener noreferrer">previous blog</A> we’ve learned the basics about protecting a function with OAuth<BR /> What we didn’t learn: our function didn’t require a <STRONG>scope<BR /> </STRONG>In this blog, let’s learn how to enforce a scope in the JWT token and<BR /> - assign the scope to our user and call the function (REST client)<BR /> - call the function from a client application (node.js)<BR /> <H2 id="toc-hId-737945437">Overview</H2><BR /> Small recap:<BR /> In the previous tutorial, we’ve created a very silly small function and we’ve used framework functionality to protect it with small configuration snippet.<BR /> Furthermore, we created a very basic instance of XSUAA and connect the function to it<BR /> That was enough to protect our function with OAuth 2.0<BR /> The FaaS Runtime took care of rejecting HTTP requests which didn't send a valid JWT token<BR /> The FaaS Runtime used the connected XSUAA instance for validation<BR /> That was OK.<BR /> Fair enough for the beginning<BR /> Now, in today's tutorial we’re going to<BR /> - add a scope<BR /> - and in the function we check if the scope exists in the JWT<BR /> <BR /> These are the steps we're going to cover:<BR /> <OL><BR /> <LI>Create XSUAA</LI><BR /> <LI>Create Function<BR /> Implement scope check</LI><BR /> <LI>Call Function in user centric scenario</LI><BR /> <LI>Call Function with client app</LI><BR /> </OL><BR /> <H2 id="toc-hId-541431932">Prerequisites</H2><BR /> <UL><BR /> <LI>Basic understanding of OAuth, <A href="https://blogs.sap.com/2019/05/06/sap-cloud-platform-backend-service-tutorial-14-about-oauth-mechanism/" target="_blank" rel="noopener noreferrer">see easy intro</A>&nbsp;and <A href="https://blogs.sap.com/2019/04/29/sap-cloud-platform-backend-service-tutorial-13-api-called-from-external-tool/" target="_blank" rel="noopener noreferrer">description for using REST client</A></LI><BR /> <LI><A href="https://blogs.sap.com/2020/10/29/writing-function-as-a-service-12-adding-security/" target="_blank" rel="noopener noreferrer">Previous blog</A></LI><BR /> <LI>HTTP API <A href="https://blogs.sap.com/2020/10/15/writing-function-as-a-service-11-how-and-why-access-http-api/" target="_blank" rel="noopener noreferrer">blog post</A></LI><BR /> </UL><BR /> <H2 id="toc-hId-344918427">1. Create Instance of XSUAA</H2><BR /> We need an instance of XSUAA which is configured with a <STRONG>scope<BR /> </STRONG>We can <A href="#commands" target="_blank" rel="nofollow noopener noreferrer">update</A> the existing one, or create a new one<BR /> In my example, I create a new instance with the following security configuration in a file called&nbsp;<SPAN style="font-family: Courier New">xs-security_faas_scope.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "xsappname" : "xsappforfaaswithscope",<BR /> "tenant-mode" : "dedicated",<BR /> "scopes": [<BR /> {<BR /> "name": "$XSAPPNAME.scopeforfunction",<BR /> "description": "Scope required for accessing function"<BR /> }<BR /> ],<BR /> "role-templates": [ { <BR /> "name" : "FunctionRoleTemplate", <BR /> "description" : "Role for serverless function", <BR /> "default-role-name" : "RoleForFunction",<BR /> "scope-references" : ["$XSAPPNAME.scopeforfunction"]<BR /> }],<BR /> "authorities":["$XSAPPNAME.scopeforfunction"]<BR /> }<BR /> </CODE></PRE><BR /> <SPAN style="text-decoration: underline">Explanation:</SPAN><BR /> <BR /> <SPAN style="font-family: Courier New">scopes</SPAN><BR /> We define a scope.<BR /> For us, thie means: when calling the function, a JWT token is not enough. The caller must have the scope. If yes, the scope is contained in the token<BR /> However, in the security configuration we only define the scope, such that XSUAA knows about it<BR /> If things go well (see below) XSUAA will issue a JWT token that contains the scope<BR /> However, XSUAA doesn't enforce the scope.<BR /> <BR /> <SPAN style="font-family: Courier New">role-templates</SPAN><BR /> A scope is nothing that can be assigned to a human user. For that, we need to define a “role”, along with “role-template”. That can be found in the Cloud Cockpit and an admin can assign the role to users<BR /> <BR /> <SPAN style="font-family: Courier New">authorities</SPAN><BR /> This attribute is meant for non-human users, for client apps, in a client-credentials scenario<BR /> With this statement, we accept that scope. A client application bound to XSUAA will get the scope in the JWT token<BR /> <BR /> <STRONG><SPAN style="text-decoration: underline">Create XSUAA</SPAN></STRONG><BR /> <BR /> We create an instance of XSUAA and we use above JSON parameters<BR /> Can be done in the Cloud Cockpit, or on command line:<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf&nbsp;cs&nbsp;xsuaa&nbsp;application&nbsp;xsuaa_faas_scope&nbsp;-c&nbsp;xs-security_faas_scope.json</SPAN><BR /> <BR /> <STRONG><SPAN style="text-decoration: underline">Create Service Key</SPAN></STRONG><BR /> <BR /> As usual, we need a service key in order to reference it from FaaS and also from REST client (see below)<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf csk xsuaa_faas_scope xsuaa_faasscope_servicekey</SPAN><BR /> <BR /> See <A href="#commands" target="_blank" rel="nofollow noopener noreferrer">Appendix</A> for all commands<BR /> <H2 id="toc-hId-148404922">2. Create Function</H2><BR /> After creating an instance of XSUAA service, we need to register it in FaaS, and use it in the function definition.<BR /> We’ve learned that in a <A href="https://blogs.sap.com/2020/07/23/writing-function-as-a-service-8-how-to-easily-use-platform-services/" target="_blank" rel="noopener noreferrer">previous tutorial</A><BR /> <BR /> <STRONG><U>Register the service in faas</U></STRONG><BR /> <BR /> For your convenience, find here the necessary commands<BR /> <BR /> Always need to login first to Cloud Foundry and/or FaaS client<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli login</SPAN><BR /> <BR /> The convenient command to register a service interactively (the command line client will propose existing services, so we can choose. Precondition: only service instances with service key are proposed)<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas service register</SPAN><BR /> <BR /> After registration, in the console, we get the info which we need to specify in <SPAN style="font-family: Courier New">faas.json</SPAN>:<BR /> the service key and the GUID of the service instance<BR /> <BR /> <STRONG><SPAN style="text-decoration: underline">Use the service in FaaS project</SPAN></STRONG><BR /> <BR /> <SPAN style="font-family: Courier New">faas.json</SPAN><BR /> <PRE class="language-javascript"><CODE> "services": {<BR /> "xsuaa-with-scope": {<BR /> "type": "xsuaa",<BR /> "instance": "&lt;your GUID of service instance&gt;",<BR /> "key": "xsuaa_faasscope_servicekey" <BR /> }<BR /> }<BR /> </CODE></PRE><BR /> See <A href="#samples" target="_blank" rel="nofollow noopener noreferrer">Appendix</A> for full <SPAN style="font-family: Courier New">faas.json</SPAN><BR /> <BR /> <STRONG><SPAN style="text-decoration: underline">Configure Security</SPAN></STRONG><BR /> <BR /> As learned in previous blog post, with below setting, we tell the FaaS runtime that we want them to enforce a valid JWT token<BR /> <PRE class="language-javascript"><CODE>"triggers": {<BR /> "prot": {<BR /> "type": "HTTP",<BR /> "function": "prot-func-with-scope",<BR /> "auth": {<BR /> "type": "xsuaa",<BR /> "service": "xsuaa-with-scope"<BR /> </CODE></PRE><BR /> <STRONG><SPAN style="text-decoration: underline">Enforce scope</SPAN></STRONG><BR /> <BR /> As mentioned earlier: the above setting will have the following consequence:<BR /> <BR /> Whenever our function is called with HTTP trigger, the request must contain a valid JWT token<BR /> Otherwise the call is rejected by the FaaS runtime (with proper status code and error message) and our function code is not even invoked.<BR /> <BR /> As such, the FaaS runtime enforces the authentication.<BR /> But it cannot take care of authorization in a generic way<BR /> <BR /> For example, a function might have the following logic:<BR /> If EDIT scope is available, the function may accept POST requests, otherwise only GET<BR /> Such logic has to be implemented manually by us<BR /> <BR /> We have to look into the JWT token, read the available scopes and check if the one which we require, is there.<BR /> The framework offers a convenience method which decodes the JWT token (if available)<BR /> We can then access the JWT payload as JSON object<BR /> <PRE class="language-javascript"><CODE>const jwtToken = event.decodeJsonWebToken()<BR /> const jwtScopes = jwtToken.payload.scope<BR /> </CODE></PRE><BR /> In order to check the scope, we need to know the exact scope name.<BR /> Background:<BR /> <BR /> We defined the scope name as<BR /> <SPAN style="font-family: Courier New">$XSAPPNAME.scopeforfunction</SPAN><BR /> The value of the variable <SPAN style="font-family: Courier New">$XSAPPNAME</SPAN> is generated on the cloud platform, as such we have to ask at runtime for the exact value.<BR /> The exact value is contained in the service key<BR /> And the service key is registered in FaaS<BR /> As such, we can ask FaaS for the xsuaa service<BR /> Then access the <SPAN style="font-family: Courier New">xsappname</SPAN> property<BR /> <PRE class="language-javascript"><CODE>const xsuaaCredentials = await context.getServiceCredentialsJSON('xsuaa-with-scope')<BR /> const requiredScope = xsuaaCredentials.xsappname + '.scopeforfunction'<BR /> </CODE></PRE><BR /> Based on above 2 code snippets we have the needed information:<BR /> Which scope do we expect (for whatever business case)<BR /> Which scope is contained in the JWT token<BR /> <BR /> In our example, we require a scope for calling the function.<BR /> To check if the scope is sent, we just look into the array of available scopes<BR /> If not available, we reject the request.<BR /> In our case, the correct status code is 403, because use is authenticated, but lacking authorization<BR /> In order to set the status, we need to use the HTTP API (see previous <A href="https://blogs.sap.com/2020/10/15/writing-function-as-a-service-11-how-and-why-access-http-api/" target="_blank" rel="noopener noreferrer">blog post</A>)<BR /> <BR /> Below snippet puts the snippets together:<BR /> <PRE class="language-javascript"><CODE>const xsuaaCredentials = await context.getServiceCredentialsJSON('xsuaa-with-scope')<BR /> const requiredScope = xsuaaCredentials.xsappname + '.scopeforfunction'<BR /> <BR /> // read the JWT token and check required scope<BR /> const jwtToken = event.decodeJsonWebToken()<BR /> const jwtScopes = jwtToken.payload.scope<BR /> if(! jwtScopes.includes(requiredScope)){<BR /> // fail with 403 <BR /> const response = event.http.response // must be enabled in faas.json<BR /> response.writeHead(403, {<BR /> ...</CODE></PRE><BR /> See <A href="#samples" target="_blank" rel="nofollow noopener noreferrer">Appendix</A> for full sample code<BR /> <BR /> Note:<BR /> Before using the HTTP API, it must be enabled, which is done in the <SPAN style="font-family: Courier New">faas.json</SPAN><BR /> <BR /> Note:<BR /> SAP provides security libraries and there’s a little helper function, recommended to use for the scope check<BR /> The function I’m talking about:<BR /> <SPAN style="font-family: Courier New"><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1387241">@Sap</a>/xssec: checkScope(scope)</SPAN><BR /> Please refer to Links section<BR /> In my examples, I’d like to keep the code free from dependencies (as there might be changes, etc), so I’m not using it. Please forgive<BR /> But for your convenience, I've created little sample code based on the library. See <A href="#samplewithlib" target="_blank" rel="nofollow noopener noreferrer">Appendix</A><BR /> <BR /> <STRONG><SPAN style="text-decoration: underline">Deploy function</SPAN></STRONG><BR /> <BR /> To deploy the function, you might find this command useful (run from project directory)<BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas project deploy</SPAN><BR /> <H2 id="toc-hId--48108583">3.&nbsp;<STRONG>Call function in user centric scenario</STRONG></H2><BR /> After deploy, we want to test the security of our function<BR /> <UL><BR /> <LI>If we call the function in browser –&gt; error<BR /> Reason: no JWT token at all</LI><BR /> <LI>If we call the function in REST client with OAuth flow –&gt; error<BR /> Reason: JWT token available, but doesn't contain the required scope</LI><BR /> </UL><BR /> Solution: To call the function we need to assign the required role to our user<BR /> <BR /> <STRONG><SPAN style="text-decoration: underline">The Role</SPAN></STRONG><BR /> <BR /> In Cloud Foundry, authorization is controlled by means of Role Based Access Control (RBAC)<BR /> In the first step, we created an instance of XSUAA, based on a security configuration which defined a scope and a role-template.<BR /> After creating our instance of XSUAA, our role-definition has been added to the list of roles in the cockpit.<BR /> We can view it there<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/10/cockpit_roles-1.jpg" height="131" width="394" /></P><BR /> But viewing is not enough, we have to add the role to our user.<BR /> <BR /> <SPAN style="text-decoration: underline"><STRONG>Assign Role to User</STRONG></SPAN><BR /> <BR /> First, we create a new <EM>Role Collection</EM>, dedicated for your function<BR /> In the Cockpit, go to your subaccount-&gt;Security-&gt; Role Collections<BR /> <BR /> Second, we have to add the desired role to the new Role Collection<BR /> So we “edit” the new role collection and add our new role<BR /> Then press “save”<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/10/cockpit_rc.jpg" height="98" width="237" /></P><BR /> Third, we need to add our Role Collection to the Identity Provider (IDP)<BR /> Go to menu entry <EM>Trust Configuration</EM><BR /> Click on default IDP<BR /> Enter the E-Mail Address of your user<BR /> Click “show Assignments”<BR /> Then “Assign Role Collection”<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/10/cockpit_trust-1.jpg" height="189" width="403" /></P><BR /> <STRONG><U>Test the function with human user</U></STRONG><BR /> <BR /> Now that our user has the required role, we can call the function<BR /> We don’t have a user interface in our scenario, so we use a REST client<BR /> See <A href="https://blogs.sap.com/2019/04/29/sap-cloud-platform-backend-service-tutorial-13-api-called-from-external-tool/" target="_blank" rel="noopener noreferrer">here</A> for a detailed description about how to call an OAuth protected endpoint with REST client<BR /> <BR /> Short description:<BR /> 1. fetch a JWT token<BR /> 2. Use token when calling our function<BR /> <BR /> In my example, I’m using Postman which helps to do both steps in one request<BR /> To configure postman request, we need to view the service key of our xsuaa instance which we created above<BR /> To view the service key, we can use the following command (alternatively, use cockpit)<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf service-key xsuaa_faas_scope xsuaa_faasscope_servicekey</SPAN><BR /> <BR /> Back in Postman, we have to configure the request as follows<BR /> <BR /> <SPAN style="font-family: Courier New">Method</SPAN>: GET<BR /> <SPAN style="font-family: Courier New">URL</SPAN>: The endpoint of our function, we get the info during deploy, or with xfsrt-cli faas project get<BR /> e.g. <SPAN style="color: #0000ff"><A href="https://...-faas.....functions.xfs.cloud.sap/prot/" target="test_blank" rel="nofollow noopener noreferrer">https://...-faas.....functions.xfs.cloud.sap/prot/</A><BR /> </SPAN>Don’t forget the slash at the end<BR /> <BR /> <SPAN style="font-family: Courier New">Authorization</SPAN>: Oauth 2.0<BR /> <SPAN style="font-family: Courier New">Access Token</SPAN>:<BR /> press "Get new Access Token"<BR /> "Get Access Token" Dialog:<BR /> <SPAN style="font-family: Courier New">Grant Type</SPAN>: Password Credentials<BR /> <SPAN style="font-family: Courier New">Access Token URL</SPAN>: we copy the “url” property from the service key and append&nbsp;&nbsp; <SPAN style="color: #0000ff">/oauth/token<BR /> </SPAN>Example: <SPAN style="color: #0000ff"><A href="https://&lt;subaccount&gt;.authentication.....hana.ondemand.com/oauth/token" target="test_blank" rel="nofollow noopener noreferrer">https://&lt;subaccount&gt;.authentication.....hana.ondemand.com/oauth/token</A><BR /> </SPAN><SPAN style="font-family: Courier New">Username</SPAN>: &lt;your cloud user&gt;<BR /> <SPAN style="font-family: Courier New">Password</SPAN>: &lt;your cloud user password&gt;<BR /> <SPAN style="font-family: Courier New">Client ID</SPAN>: copy the value of property <SPAN style="font-family: Courier New">clientid</SPAN> from service key<BR /> <SPAN style="font-family: Courier New">Client Secret</SPAN>: copy the value of property <SPAN style="font-family: Courier New">clientsecret</SPAN> from service key<BR /> <SPAN style="font-family: Courier New">Client Authentication</SPAN>: choose "Send as Basic Auth header"<BR /> <BR /> Then press “Request Token” on the dialog<BR /> After getting the token response, press “Use Token”<BR /> Then, back in the main Postman window, press “Send” to send the request to the Function endpoint<BR /> <BR /> As a result, we get our success message, which proves that the scope was included in the JWT token<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/10/postmanSuccess.jpg" height="220" width="432" /></P><BR /> Now we want to do the negative test, to check our function implementation works correctly and if the correct status code is sent<BR /> To do so, we have to remove the role from our user.<BR /> We can simply unassign the role collection in the "Trust Configuration"<BR /> Afterwards, we need to fetch a new JWT token via “Get New Access Token”<BR /> Then send the request again to call the function without scope<BR /> <BR /> The result is <SPAN style="font-family: Courier New">403</SPAN>, as coded by us, and the response message shows that the scope is not contained in the JWT token<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/10/postman403.jpg" height="72" width="433" /></P><BR /> Note:<BR /> In this scenario, we’re using only one instance of XSUAA<BR /> The function uses an instance of XSUAA to protect its endpoint<BR /> The user who calls the function, uses the same ClientID to call the function<BR /> This is OK, because we as developers of the function trust our user<BR /> In case that 2 instances of XSUAA are required, please refer to <A href="https://blogs.sap.com/2020/06/02/how-to-call-protected-app-from-external-app-as-external-user-with-scope/" target="_blank" rel="noopener noreferrer">this blog for more information</A><BR /> <BR /> &nbsp;<BR /> <H2 id="toc-hId--244622088">4.&nbsp;<STRONG>Call function in client credentials scenario</STRONG></H2><BR /> Now we want to call the protected function from an application<BR /> Again, we’re using only one instance of XSUAA.<BR /> We bind our application to the same instance which is used to protect the function<BR /> (Sure, in a future blog, we’ll describe a scenario with different XSUAA instances)<BR /> <BR /> In client credentials scenario, we may wonder: how to assign the required scope to the calling app?<BR /> We cannot assign it in the cockpit like we did for our (human) user.<BR /> To answer this question, somebody wrote <A href="https://blogs.sap.com/2020/06/02/how-to-call-protected-app-from-external-app-as-external-user-with-scope/" target="_blank" rel="noopener noreferrer">this useful blog post</A><BR /> <BR /> Our example is a bit different from above blog post, because both the function and the client app are "bound" to the same instance of XSUAA<BR /> The solution in our example:<BR /> When creating an instance of XSUAA, we defined a parameter called <SPAN style="font-family: Courier New">authorities<BR /> </SPAN>This is interesting and might be a new learning for us:<BR /> The function is attached to the XSUAA instance and the client app is bound to the same instance<BR /> As such, we would expect that the xsuaa instance (== OAuth client) trusts itself, because it is the same instance<BR /> In fact, trust is there. But the scope is not automatically there<BR /> In a client-credentials scenario, the scope must be explicitly granted, just like we assign a role to a user<BR /> To assign the scope to the client itself, no “grant” statement is required, but the <SPAN style="font-family: Courier New">authorities</SPAN> statement is necessary<BR /> This statement declares: our application wants to take the scope<BR /> <PRE class="language-javascript"><CODE>"authorities":["$XSAPPNAME.scopeforfunction"]</CODE></PRE><BR /> <STRONG>&nbsp;</STRONG><SPAN style="text-decoration: underline"><STRONG>Create Client app</STRONG></SPAN><BR /> <BR /> The only intention of our app is to call the function – and to send a JWT token which contains the required scope<BR /> Well... we’ve just learned how to get the required scope into the JWT token<BR /> Nothing special needs to be done in the app code<BR /> Just fetch a JWT token and then call the function<BR /> So we can skip explanations.<BR /> We can go ahead and copy the code from the <A href="#samples" target="_blank" rel="nofollow noopener noreferrer">Appendix</A><BR /> <BR /> Note:<BR /> Don’t forget to replace the <SPAN style="font-family: Courier New">FUNC_HOST</SPAN> URL with the URL of your function<BR /> <BR /> Note:<BR /> You might need to change the app name in the <SPAN style="font-family: Courier New">manifest.yml</SPAN><BR /> <BR /> Then deploy the clientapp with <SPAN style="background-color: black;color: white;font-family: Courier New">cf push<BR /> </SPAN>Finally, invoke the endpoint of our function caller app and hope to get a success message<BR /> <SPAN style="color: #0000ff"><A href="http://functioncallerapp.cfapps.sap.hana.ondemand.com/call" target="test_blank" rel="nofollow noopener noreferrer">http://functioncallerapp.cfapps.sap.hana.ondemand.com/call</A></SPAN><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/10/clientappSuccess.jpg" height="55" width="435" /></P><BR /> To test the negative scenario, we have to remove the <SPAN style="font-family: Courier New">authorities</SPAN> statement from our <SPAN style="font-family: Courier New">xs-security_faas_scope.json</SPAN> file and then execute an update-service command in Cloud Foundry<BR /> <BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf&nbsp;update-service&nbsp;xsuaa_faas_scope&nbsp;-c&nbsp;xs-security_faas_scope.json</SPAN><BR /> <BR /> Note:<BR /> Instead of deleting the “authorities” statement, we can invalidate it by changing the name of the scope to anything non-existing<BR /> e.g.<BR /> <SPAN style="font-family: Courier New">"authorities":["$XSAPPNAME.scopeforfunctionXX"]</SPAN><BR /> <BR /> In fact, after running update-service, we can invoke our endpoint again and we get the expected error message:<BR /> It says that the function responded with 403, which was expected<BR /> <H2 id="toc-hId--441135593">Summary</H2><BR /> We’ve learned that the FaaS runtime uses XSUAA for token validation, there’s no automatic check of available scopes<BR /> To manually check the available scopes, we can access the decoded JW T token<BR /> We need to access the scope prefix from xsuaa service key<BR /> In client-credentials scenario, we need to use the <SPAN style="font-family: Courier New">authorities</SPAN> statement to assign the scope to ourselves<BR /> <H2 id="quickguide" id="toc-hId--637649098">Quick Guide</H2><BR /> Few Code snippets<BR /> <PRE class="language-javascript"><CODE>// access registered xsuaa service in order to get the value of variable $XSAPPNAME<BR /> const xsuaaCredentials = await context.getServiceCredentialsJSON('xsuaa-with-scope')<BR /> const requiredScope = xsuaaCredentials.xsappname + '.scopeforfunction'<BR /> <BR /> // the decoded JWT token<BR /> const jwtTokenDecoded = event.decodeJsonWebToken()<BR /> <BR /> // the raw JWT token<BR /> const jwtTokenRaw = event.auth.credentials<BR /> </CODE></PRE><BR /> <H2 id="toc-hId--834162603">Links</H2><BR /> <UL><BR /> <LI><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1387241">@Sap</a>/xssec <A href="https://www.npmjs.com/package/@sap/xssec" target="_blank" rel="noopener noreferrer nofollow">documentation</A></LI><BR /> <LI>Call OAuth protected service from <A href="https://blogs.sap.com/2019/04/29/sap-cloud-platform-backend-service-tutorial-13-api-called-from-external-tool/" target="_blank" rel="noopener noreferrer">REST client</A></LI><BR /> <LI>OAuth <A href="https://blogs.sap.com/2019/05/06/sap-cloud-platform-backend-service-tutorial-14-about-oauth-mechanism/" target="_blank" rel="noopener noreferrer">Intro</A></LI><BR /> <LI>JWT Token <A href="https://blogs.sap.com/2020/09/03/outdated-sap_jwt_trust_acl/" target="_blank" rel="noopener noreferrer">details</A></LI><BR /> <LI>Interesting article: <A href="https://blogs.sap.com/2020/10/21/how-to-add-custom-properties-to-jwt-token/" target="_blank" rel="noopener noreferrer">how to add property to JWT</A></LI><BR /> <LI>Overview of <A href="https://blogs.sap.com/2020/06/17/writing-functions-as-a-service-overview-of-blogs/" target="_blank" rel="noopener noreferrer">FaaS blog posts</A></LI><BR /> </UL><BR /> <H2 id="commands" id="toc-hId--683421751">Appendix 1: Console Commands</H2><BR /> <UL><BR /> <LI><SPAN style="background-color: black;color: white;font-family: Courier New">cf cs xsuaa application xsuaa_faas_scope -c xs-security_faas_scope.json</SPAN></LI><BR /> <LI><SPAN style="background-color: black;color: white;font-family: Courier New">cf&nbsp;csk&nbsp;xsuaa_faas_scope&nbsp;xsuaa_faasscope_servicekey</SPAN><BR /> <SPAN style="background-color: black;color: white;font-family: Courier New">cf service-key xsuaa_client xsuaa_client_servicekey&nbsp;</SPAN></LI><BR /> <LI><SPAN style="background-color: black;color: white;font-family: Courier New">cf update-service xsuaa_faas_scope -c xs-security_faas_scope.json</SPAN></LI><BR /> <LI><SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli login</SPAN></LI><BR /> <LI><SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas service register</SPAN></LI><BR /> <LI><SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas project deploy</SPAN></LI><BR /> <LI><SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas project get</SPAN></LI><BR /> <LI><SPAN style="background-color: black;color: white;font-family: Courier New">xfsrt-cli faas project logs</SPAN></LI><BR /> </UL><BR /> <H2 id="samples" id="toc-hId--879935256">Appendix 2: All sample project files</H2><BR /> For your convenience, I'm pasting the structure of my example project.<BR /> Folder names can be changed<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/10/projectStructure.jpg" height="191" width="185" /></P><BR /> <BR /> <H3 id="toc-hId--1369851768">Protected Function</H3><BR /> These are the files required for the function<BR /> They are located in the function project folder, with "lib" subfolder<BR /> <BR /> <SPAN style="text-decoration: underline;font-family: Courier New">xs-security_faas_scope.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "xsappname" : "xsappforfaaswithscope",<BR /> "tenant-mode" : "dedicated",<BR /> "scopes": [<BR /> {<BR /> "name": "$XSAPPNAME.scopeforfunction",<BR /> "description": "Scope required for accessing function"<BR /> }<BR /> ],<BR /> "role-templates": [ { <BR /> "name" : "FunctionRoleTemplate", <BR /> "description" : "Role for serverless function", <BR /> "default-role-name" : "RoleForFunction",<BR /> "scope-references" : ["$XSAPPNAME.scopeforfunction"]<BR /> }],<BR /> "authorities":["$XSAPPNAME.scopeforfunction"]<BR /> }<BR /> </CODE></PRE><BR /> <SPAN style="text-decoration: underline;font-family: Courier New">faas.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "project": "protectedwithscope",<BR /> "version": "0.0.1",<BR /> "runtime": "nodejs10",<BR /> "library": "./lib",<BR /> "functions": {<BR /> "prot-func-with-scope": {<BR /> "module": "functionImpl.js",<BR /> "httpApi": true,<BR /> "services": ["xsuaa-with-scope"]<BR /> }<BR /> },<BR /> "triggers": {<BR /> "prot": {<BR /> "type": "HTTP",<BR /> "function": "prot-func-with-scope",<BR /> "auth": {<BR /> "type": "xsuaa",<BR /> "service": "xsuaa-with-scope"<BR /> } <BR /> }<BR /> },<BR /> "services": {<BR /> "xsuaa-with-scope": {<BR /> "type": "xsuaa",<BR /> "instance": "d8819eda-41e6-40b0-9286-0afa1a59d12c",<BR /> "key": "xsuaa_faasscope_servicekey" <BR /> }<BR /> }<BR /> } </CODE></PRE><BR /> <SPAN style="text-decoration: underline;font-family: Courier New">package.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{}</CODE></PRE><BR /> <SPAN style="text-decoration: underline;font-family: Courier New">functionImpl.js</SPAN><BR /> <PRE class="language-javascript"><CODE>module.exports = async function (event, context) { <BR /> // access registered xsuaa service in order to get the value of variable $XSAPPNAME<BR /> const xsuaaCredentials = await context.getServiceCredentialsJSON('xsuaa-with-scope')<BR /> const requiredScope = xsuaaCredentials.xsappname + '.scopeforfunction'<BR /> <BR /> // read the JWT token and check required scope<BR /> const jwtToken = event.decodeJsonWebToken()<BR /> const jwtScopes = jwtToken.payload.scope<BR /> if(! jwtScopes.includes(requiredScope)){<BR /> // HTTP API required for configuring response<BR /> const response = event.http.response // must be enabled in faas.json<BR /> response.writeHead(403, {<BR /> 'Content-Type': 'text/plain'<BR /> });<BR /> response.write(`Unauthorized: required scope '${requiredScope}' not found in JWT. Availbale scopes: '${jwtScopes}' ;-(`);<BR /> response.end();<BR /> } else{<BR /> return `Reached protected function. Scope check successful: required scope '${requiredScope}' found in JWT. Availbale scopes: '${jwtScopes}'`<BR /> }<BR /> } <BR /> </CODE></PRE><BR /> &nbsp;<BR /> <H3 id="toc-hId--1566365273">Function Caller Client App</H3><BR /> These are the files required for a little node.js app which calls the protected function<BR /> Make sure to adapt the URL of the function in the application code<BR /> <BR /> <SPAN style="text-decoration: underline;font-family: Courier New">manifest.yml</SPAN><BR /> <BR /> The application is bound to the same instance of XSUAA which we registered in FaaS<BR /> <PRE class="language-javascript"><CODE>---<BR /> applications:<BR /> - name: functioncallerapp<BR /> memory: 128M<BR /> buildpacks:<BR /> - nodejs_buildpack<BR /> services:<BR /> - xsuaa_faas_scope</CODE></PRE><BR /> <SPAN style="text-decoration: underline;font-family: Courier New">server.js</SPAN><BR /> <PRE class="language-javascript"><CODE>const express = require('express')<BR /> const app = express()<BR /> const https = require('https');<BR /> <BR /> const VCAP_SERVICES = JSON.parse(process.env.VCAP_SERVICES)<BR /> const CREDENTIALS = VCAP_SERVICES.xsuaa[0].credentials<BR /> //oauth<BR /> const CLIENTID = CREDENTIALS.clientid; <BR /> const SECRET = CREDENTIALS.clientsecret;<BR /> const OAUTH_HOST = CREDENTIALS.url;<BR /> <BR /> const FUNC_HOST = 'https://abcd1234-...-...-faas-...-functions.xfs.cloud.sap' // adapt URL<BR /> const FUNC_TRIGGER = 'prot'<BR /> <BR /> <BR /> app.get('/call', function(req, res){ <BR /> // call function endpoint <BR /> doCallEndpoint(FUNC_HOST, FUNC_TRIGGER, OAUTH_HOST, CLIENTID, SECRET)<BR /> .then((response)=&gt;{<BR /> res.status(202).send('Successfully called remote endpoint. Function response: ' + response);<BR /> }).catch((error)=&gt;{<BR /> res.status(500).send(`Error while calling remote endpoint: ${error} `);<BR /> })<BR /> });<BR /> <BR /> <BR /> // helper method to call the endpoint<BR /> const doCallEndpoint = function(host, endpoint, token_uri, client_id, client_secret){<BR /> return new Promise((resolve, reject) =&gt; {<BR /> return fetchJwtToken(token_uri, client_id, client_secret)<BR /> .then((jwtToken) =&gt; {<BR /> const options = {<BR /> host: host.replace('https://', ''),<BR /> path: `/${endpoint}/`,<BR /> method: 'GET',<BR /> headers: {<BR /> Authorization: 'Bearer ' + jwtToken<BR /> }<BR /> }<BR /> <BR /> const req = https.request(options, (res) =&gt; {<BR /> res.setEncoding('utf8')<BR /> const status = res.statusCode <BR /> const statusMessage = res.statusMessage <BR /> let response = ''<BR /> res.on('data', chunk =&gt; {<BR /> response += chunk<BR /> })<BR /> <BR /> res.on('end', () =&gt; {<BR /> if (status !== 200 &amp;&amp; status !== 201) {<BR /> return reject(new Error(`Failed to call function. Message: ${status} - ${statusMessage} - ${response}`))<BR /> }<BR /> resolve(response)<BR /> })<BR /> <BR /> });<BR /> <BR /> req.on('error', (error) =&gt; {<BR /> return reject({error: error})<BR /> });<BR /> <BR /> req.write('done')<BR /> req.end() <BR /> })<BR /> .catch((error) =&gt; {<BR /> reject(error)<BR /> })<BR /> })<BR /> }<BR /> <BR /> // jwt token required for calling REST api<BR /> const fetchJwtToken = function(token_uri, client_id, client_secret) {<BR /> return new Promise ((resolve, reject) =&gt; {<BR /> const options = {<BR /> host: token_uri.replace('https://', ''),<BR /> path: '/oauth/token?grant_type=client_credentials&amp;response_type=token',<BR /> // path: '?grant_type=client_credentials&amp;response_type=token',<BR /> headers: {<BR /> Authorization: "Basic " + Buffer.from(client_id + ':' + client_secret).toString("base64")<BR /> }<BR /> }<BR /> <BR /> https.get(options, res =&gt; {<BR /> res.setEncoding('utf8')<BR /> let response = ''<BR /> res.on('data', chunk =&gt; {<BR /> response += chunk<BR /> })<BR /> <BR /> res.on('end', () =&gt; {<BR /> try {<BR /> const jwtToken = JSON.parse(response).access_token <BR /> resolve(jwtToken)<BR /> } catch (error) {<BR /> return reject(new Error('Error while fetching JWT token')) <BR /> }<BR /> })<BR /> })<BR /> .on("error", (error) =&gt; {<BR /> return reject({error: error})<BR /> });<BR /> }) <BR /> }<BR /> <BR /> // Start server<BR /> app.listen(process.env.PORT || 8080, ()=&gt;{})<BR /> <BR /> </CODE></PRE><BR /> <SPAN style="text-decoration: underline;font-family: Courier New">package.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "dependencies": {<BR /> "express": "^4.16.3"<BR /> }<BR /> }<BR /> </CODE></PRE><BR /> &nbsp;<BR /> <H2 id="samplewithlib" id="toc-hId--1469475771">Appendix 3: Sample code using <a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1387241">@Sap</a>/xssec</H2><BR /> <SPAN style="text-decoration: underline;font-family: Courier New">package.json</SPAN><BR /> <PRE class="language-javascript"><CODE>{<BR /> "dependencies": {<BR /> "@sap/xssec": "latest"<BR /> }<BR /> }<BR /> </CODE></PRE><BR /> <SPAN style="text-decoration: underline;font-family: Courier New">functionImpl.js</SPAN><BR /> <PRE class="language-javascript"><CODE>const xssec = require('@sap/xssec')<BR /> const util = require('util');<BR /> const createSecurityContext = util.promisify(xssec.createSecurityContext);<BR /> <BR /> module.exports = async function (event, context) { <BR /> // access registered xsuaa service in order to get the value of variable $XSAPPNAME<BR /> const xsuaaCredentials = await context.getServiceCredentialsJSON('xsuaa-with-scope')<BR /> const requiredScope = xsuaaCredentials.xsappname + '.scopeforfunction'<BR /> <BR /> const jwtTokenRaw = event.auth.credentials<BR /> const securityContext = await createSecurityContext(jwtTokenRaw, xsuaaCredentials)<BR /> const jwtScopes = securityContext.getTokenInfo().getPayload().scope<BR /> <BR /> if(! securityContext.checkScope(requiredScope)){<BR /> // HTTP API required for configuring response<BR /> const response = event.http.response // must be enabled in faas.json<BR /> response.writeHead(403, {<BR /> 'Content-Type': 'text/plain'<BR /> });<BR /> response.write(`Unauthorized: required scope '${requiredScope}' not found in JWT. Availbale scopes: '${jwtScopes}'`);<BR /> response.end();<BR /> } else{<BR /> return `Reached protected function. Scope check successful: required scope found in JWT. All availbale scopes: '${jwtScopes}'`<BR /> }<BR /> }</CODE></PRE><BR /> &nbsp; 2020-10-29T15:06:37+01:00 https://community.sap.com/t5/technology-blogs-by-members/making-your-sap-integration-serverless/ba-p/13505297 Making your SAP Integration Serverless 2021-03-05T20:29:57+01:00 former_member731571 https://community.sap.com/t5/user/viewprofilepage/user-id/731571 <SPAN style="font-weight: 400">Growing demands of rapid digital interactions and expanding the ecosystem means IT departments will need to spend more time eliminating silos, bridging between services. While handling the ever complex, immense amount of data in real-time. Also trying to bring the new service to the market at an expedited speed. But also with cost, and good resource management in mind.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">This drives a drastic change to how systems are interconnected. From JUST API calls to more scalable event-driven architecture. Development style has gone from heavy service oriented to small quick turn around snippets of code. With a Serverless platform, it can help lowering the operation cost and reduce packaging and deployment complexity.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">SAP being a world's leading enterprise business processing solution. There is always going to be a need to connect an organization's core to other SaaS offering, partners or even another SAP solution.&nbsp; Red Hat Integration offers </SPAN><I><SPAN style="font-weight: 400">flexibility, adaptability, and ability to move quickly</SPAN></I><SPAN style="font-weight: 400"> with framework and software to build the event-driven integration architecture. Not just connect, but also maintain data consistency across platforms.&nbsp;</SPAN><BR /> <BR /> &nbsp;<BR /> <H3 id="toc-hId-1086602103"><B>An Architectural Overview</B></H3><BR /> <SPAN style="font-weight: 400">This is how Red Hat Integration can help to achieve modernized integration with SAP.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">SAP exposes business functionality through the Netweaver Gateway. Camel K or Camel in RHI can be used by developers to integrate(bi-directional) these functionalities. Camel K/Camel not only connects the dot, but also provides a set of built-in patterns and data transformations components making customized integration easy. They can be deployed in the form of a serverless function, serverless source/sink, or a long running microservice.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">At the time of writing, I don’t see a complete support from SAP event enhancements, developers do still require to retrieve real data via other methods such as through OData and APIs. To implement a true event driven architecture, AMQ Streams (Kafka) can be used as the event stream store to handle streaming of events, for reducing decoupling and achieving near real-time latency.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">Since the system is based on events, we can also capture changes of data state in Databases using Debezium. Keeping all data consistent by passing the updated state back to SAP.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">When there is a need to expose any functions or services as API endpoints, we can easily implement it with Camel using the OpenAPI Standard Specification. And have the API managed and secured by the 3scale API management platform.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">Openshift as the platform that can run on major cloud vendors and on-prem, so it’s truly cloud agnostic. It provides a serverless platform to deploy and manage all functions. And with Interconnect we will be able to broadcast events, to the closest data center to optimize traffic control.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">As a result,&nbsp; it is now ready to connect to endless 3rd party and partner services, streaming large amounts of edge signals and providing real-time processing from edge devices. Legacy, mainframe systems can also be part of the ecosystem. Lastly, this is a good tool for SAP to SAP integration too.</SPAN><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/03/Screen-Shot-2021-03-03-at-10.58.27-AM-1.png" /></P><BR /> <BR /> <H3 id="toc-hId-890088598"><B>Technical Dive</B></H3><BR /> <H4 id="toc-hId-822657812"><I><SPAN style="font-weight: 400">Connect with Camel</SPAN></I></H4><BR /> <SPAN style="font-weight: 400">SAP offers interfaces such as OData v4, OData v2, RESTful API and SOAP as the HTTP based one, or you can also use the classic RFC(remote procedure call) and iDoc Messages. And there is a recent event enablement add-on that offers AMQP and MQTT protocol.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">The Camel feature in RHI allows you to seamlessly connect to any of your preferred protocols. Developers can simply configure to connect to the endpoints with it’s address, credential and/or SSL settings.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">Example:&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">Camel code connecting to OData v4&nbsp; (Olingo4 components)</SPAN><BR /> <PRE class="language-java"><CODE>.to("olingo4://read/SalesOrder")</CODE></PRE><BR /> <SPAN style="font-weight: 400">[NOTE]: </SPAN><A href="https://camel.apache.org/components/latest/olingo4-component.html" target="_blank" rel="nofollow noopener noreferrer"><SPAN style="font-weight: 400"></SPAN></A><A href="https://camel.apache.org/components/latest/olingo4-component.html" target="test_blank" rel="nofollow noopener noreferrer">https://camel.apache.org/components/latest/olingo4-component.html</A><BR /> <BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">Camel code connecting to Restful API</SPAN><BR /> <PRE class="language-java"><CODE>.to("http://demo.sap.io/SalesOrder")</CODE></PRE><BR /> <SPAN style="font-weight: 400">[NOTE]: </SPAN><A href="https://camel.apache.org/components/latest/http-component.html" target="_blank" rel="nofollow noopener noreferrer"><SPAN style="font-weight: 400"></SPAN></A><A href="https://camel.apache.org/components/latest/http-component.html" target="test_blank" rel="nofollow noopener noreferrer">https://camel.apache.org/components/latest/http-component.html</A><SPAN style="font-weight: 400">&nbsp;</SPAN><BR /> <BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">OData v4, RESTful API and Events protocols are better suited for Serverless. As OData v4 has significant performance over the older version, and support for analytical application. Whereas OData v2, RFC and iDoc are better used in traditional Camel Project.&nbsp;</SPAN><BR /> <BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">What Components to use for SAP endpoints.</SPAN><BR /> <TABLE style="width: 583px"><BR /> <TBODY><BR /> <TR><BR /> <TD style="width: 74px;text-align: center"></TD><BR /> <TD style="width: 252px;text-align: center"><BR /> <BR /> <STRONG>Serverless</STRONG><BR /> <BR /> <SPAN style="font-weight: 400">Camel K/Camel Quarkus</SPAN></TD><BR /> <TD style="width: 257px;text-align: center"><STRONG>Camel</STRONG></TD><BR /> </TR><BR /> <TR><BR /> <TD style="width: 74px;text-align: center"><SPAN style="font-weight: 400">OData V4</SPAN></TD><BR /> <TD style="width: 252px;text-align: center"><SPAN style="font-weight: 400">✓</SPAN></TD><BR /> <TD style="width: 257px;text-align: center"><SPAN style="font-weight: 400">✓</SPAN></TD><BR /> </TR><BR /> <TR><BR /> <TD style="width: 74px;text-align: center"><SPAN style="font-weight: 400">OData V2</SPAN></TD><BR /> <TD style="width: 252px;text-align: center"><SPAN style="font-weight: 400">✕</SPAN></TD><BR /> <TD style="width: 257px;text-align: center"><SPAN style="font-weight: 400">✓</SPAN></TD><BR /> </TR><BR /> <TR><BR /> <TD style="width: 74px;text-align: center"><SPAN style="font-weight: 400">Restful API</SPAN></TD><BR /> <TD style="width: 252px;text-align: center"><SPAN style="font-weight: 400">✓</SPAN></TD><BR /> <TD style="width: 257px;text-align: center"><SPAN style="font-weight: 400">✓</SPAN></TD><BR /> </TR><BR /> <TR><BR /> <TD style="width: 74px;text-align: center"><SPAN style="font-weight: 400">SOAP</SPAN></TD><BR /> <TD style="width: 252px;text-align: center"><SPAN style="font-weight: 400">O</SPAN></TD><BR /> <TD style="width: 257px;text-align: center"><SPAN style="font-weight: 400">✓</SPAN></TD><BR /> </TR><BR /> <TR><BR /> <TD style="width: 74px;text-align: center"><SPAN style="font-weight: 400">RFC/IDoc</SPAN></TD><BR /> <TD style="width: 252px;text-align: center"><SPAN style="font-weight: 400">✕</SPAN></TD><BR /> <TD style="width: 257px;text-align: center"><SPAN style="font-weight: 400">✓</SPAN></TD><BR /> </TR><BR /> <TR><BR /> <TD style="width: 74px;text-align: center"><SPAN style="font-weight: 400">Events</SPAN></TD><BR /> <TD style="width: 252px;text-align: center"><SPAN style="font-weight: 400">✓</SPAN></TD><BR /> <TD style="width: 257px;text-align: center"><SPAN style="font-weight: 400">✓</SPAN></TD><BR /> </TR><BR /> </TBODY><BR /> </TABLE><BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">After receiving the payload, Camel can then use the built-in data format components to transform it. In the serverless case, data is mostly in the form of JSON.&nbsp; By marshal and unmarshalling incoming payload, we can easily access the value and retrieve the content we need from the payload.&nbsp;</SPAN><BR /> <BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">Example:&nbsp;</SPAN><BR /> <PRE class="language-java"><CODE>.marshal().json().<BR /> .to("kafka:mytopic")</CODE></PRE><BR /> <SPAN style="font-weight: 400">[NOTE: <A href="https://camel.apache.org/manual/latest/json.html" target="_blank" rel="nofollow noopener noreferrer">https://camel.apache.org/manual/latest/json.html</A>]</SPAN><BR /> <BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">For syntax mapping between incoming and outgoing payload, there is visual tooling for you to design the mapping, and run that data mapping via Camel Engine.&nbsp;</SPAN><BR /> <P style="overflow: hidden;margin-bottom: 0px"><SPAN style="font-weight: 400"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/03/Screen-Shot-2021-03-03-at-12.58.54-PM.png" height="362" width="507" /></SPAN></P><BR /> <P style="overflow: hidden;margin-bottom: 0px"><SPAN style="font-weight: 400">[NOTE: <A href="https://www.atlasmap.io/" target="_blank" rel="nofollow noopener noreferrer">https://www.atlasmap.io/</A>]</SPAN></P><BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">Utilize the useful pattern in Camel, they are a straight&nbsp; implementation from </SPAN><A href="http://www.eaipatterns.com/toc.html" target="_blank" rel="nofollow noopener noreferrer"><SPAN style="font-weight: 400">Enterprise Integration Pattern</SPAN></A><SPAN style="font-weight: 400">, which organize and define the most used pattern and behaviour of integration applications.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">Example:</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;&nbsp;Split streams by and split with token “,”.</SPAN><BR /> <PRE class="language-java"><CODE>.split(body().tokenize(",")).streaming()<BR /> .to("knative:mychannel")</CODE></PRE><BR /> <SPAN style="font-weight: 400">[NOTE] (</SPAN><A href="https://camel.apache.org/components/latest/eips/split-eip.html" target="_blank" rel="nofollow noopener noreferrer"><SPAN style="font-weight: 400"></SPAN></A><A href="https://camel.apache.org/components/latest/eips/split-eip.html" target="test_blank" rel="nofollow noopener noreferrer">https://camel.apache.org/components/latest/eips/split-eip.html</A><SPAN style="font-weight: 400">)&nbsp;</SPAN><BR /> <BR /> &nbsp;<BR /> <H4 id="toc-hId-626144307"><I><SPAN style="font-weight: 400">Flexible workload with Red Hat Serverless</SPAN></I></H4><BR /> <SPAN style="font-weight: 400">There aren’t many things Camel K developers need to worry about when converting the long running application “Serverless”. First thing you have to do is make sure Red Hat Serverless is installed on the OpenShift platform (Should be done by the platform admin).&nbsp; And you are ready to go.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">Camel K will detect if Serverless is available, and create the services needed for serverless. But there are two aspects of Serverless that you might want to take a closer look at.&nbsp;</SPAN><BR /> <BR /> &nbsp;<BR /> <BR /> <STRONG>AutoScaling setup&nbsp;</STRONG><BR /> <BR /> <SPAN style="font-weight: 400">You can scale the replicas for an application/function to closely match incoming demand. When the administrator sets up the cluster for Serverless, they would already have configured the autoscaler to apply globally to the cluster. It watches the traffic flow and scale accordingly. You can however override the setting, by using the “knative-service” trait in Camel K.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">Example:</SPAN><BR /> <PRE class="language-abap"><CODE>kamel run --trait knative-service.autoscaling-class=hpa.autoscaling.knative.dev --trait knative-service.autoscaling-metric=concurrency integration.java</CODE></PRE><BR /> <STRONG>Eventing setup</STRONG><BR /> <BR /> <SPAN style="font-weight: 400">Eventing enables late-binding event sources and event consumers. The cluster admin should have already set up the underlying layer to store the events. Some options may not be persistent and may perish when nodes restarts.&nbsp;</SPAN><BR /> <BR /> &nbsp;<BR /> <BR /> This is quick demo showing how to Integrate SAP with 3rd party services Telegram. Using Camel K with Knative Eventing.<BR /> <BR /> <IFRAME width="560" height="315" src="https://www.youtube.com/embed/V419524sHBE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></IFRAME><BR /> <H3 id="toc-hId-300548083"><STRONG>Summary</STRONG></H3><BR /> <SPAN style="font-weight: 400">This is a quick overview of how we can use Camel K to build an true event-driven serverless integration for SAP to 3rd party services or other SAP modules. With Red Hat Integration Camel’s OData 4 connector to interact with the exposed SAP functions. And also by deploying on OpenShift serverless platform will automatically turn the integration application serverless.&nbsp;&nbsp;</SPAN> 2021-03-05T20:29:57+01:00 https://community.sap.com/t5/technology-blogs-by-members/make-sap-cloud-native-and-event-driven-in-4-days/ba-p/13507623 Make SAP Cloud Native and Event Driven in 4 days 2021-06-23T14:51:04+02:00 former_member731571 https://community.sap.com/t5/user/viewprofilepage/user-id/731571 <SPAN style="font-weight: 400">Recently I had an opportunity to work with Sanket Taur (IBM UK) and his team on a demo, showcasing how Red Hat products can help speed up innovation with SAP Landscapes. To be honest I was shocked at how little time we were given to create the entire demo from scratch. It’s less than a week. While still doing our day job, having a couple of hours per day to work on it. If this doesn't convince you..&nbsp; I don’t know any other stronger proof than this, to show how agile and fast a cloud solution can be from development to production.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">I STRONGLY encourage you to attend Sanket’s session for more details, this post is JUST my view on the demo, and things I did to make it running. The demo was a simple approval process of Sales Orders. The SOs are created in the Core SAP platform (In this case ES5), therefore we need to create an application that speaks to the Core SAP platform and retrieve all the data needed.&nbsp;</SPAN><BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/06/Screen-Shot-2021-06-21-at-11.14.05-PM.png" /><BR /> <SPAN style="font-weight: 400">First thing first, we need a Kubernetes(k8s) platform. And then I used Camel K -- an enhanced framework based on Camel (part of Red Hat Integration product) to create the application. There was some mixup during the setup, instead of the OData v4 endpoint from ES5 for SO, line items and customer details. I was given an OData v2 endpoint. (Needless to say, how more efficient the OData v4 is, compared to v2. Please do update it when you have a chance). Note that Camel K only supports OData v4. HOWEVER, we can still get the results using normal REST API calls (So you are still covered).&nbsp;&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">This is how Camel helps you retrieve all the information needed. As you can see I have made several requests to get all the data needed as well as doing some transformation to extract results to return.&nbsp;</SPAN><BR /> <BR /> &nbsp;<BR /> <PRE><CODE><SPAN style="font-weight: 400">from</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"direct:getSO"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">setHeader</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"Authorization"</SPAN><SPAN style="font-weight: 400">).</SPAN><SPAN style="font-weight: 400">constant</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"Basic XXXX"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">setHeader</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"Accept"</SPAN><SPAN style="font-weight: 400">).</SPAN><SPAN style="font-weight: 400">constant</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"application/json"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">toD</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"https://sapes5.sapdevcenter.com/sap/opu/odata/iwbep/GWSAMPLE_BASIC/SalesOrderSet('${header.SalesOrderID}')?bridgeEndpoint=true"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">unmarshal</SPAN><SPAN style="font-weight: 400">().</SPAN><SPAN style="font-weight: 400">json</SPAN><SPAN style="font-weight: 400">()</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">setHeader</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"CustomerID"</SPAN><SPAN style="font-weight: 400">).</SPAN><SPAN style="font-weight: 400">simple</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"${body[d][CustomerID]}"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">marshal</SPAN><SPAN style="font-weight: 400">().</SPAN><SPAN style="font-weight: 400">json</SPAN><SPAN style="font-weight: 400">()<BR /> </SPAN><SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">bean</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">this</SPAN><SPAN style="font-weight: 400">, </SPAN><SPAN style="font-weight: 400">"setSO(</SPAN><SPAN style="font-weight: 400">\"</SPAN><SPAN style="font-weight: 400">${body}</SPAN><SPAN style="font-weight: 400">\"</SPAN><SPAN style="font-weight: 400">,</SPAN><SPAN style="font-weight: 400">\"</SPAN><SPAN style="font-weight: 400">${headers.CustomerID}</SPAN><SPAN style="font-weight: 400">\"</SPAN><SPAN style="font-weight: 400">)"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">&nbsp;</SPAN><SPAN style="font-weight: 400">from</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"direct:getItems"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">setHeader</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"Authorization"</SPAN><SPAN style="font-weight: 400">).</SPAN><SPAN style="font-weight: 400">constant</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"Basic XXXX"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">setHeader</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"Accept"</SPAN><SPAN style="font-weight: 400">).</SPAN><SPAN style="font-weight: 400">constant</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"application/json"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <SPAN style="font-weight: 400"> .</SPAN><SPAN style="font-weight: 400">toD</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"https://sapes5.sapdevcenter.com/sap/opu/odata/iwbep/GWSAMPLE_BASIC/SalesOrderSet('${header.SalesOrderID}')/ToLineItems?bridgeEndpoint=true")</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">unmarshal</SPAN><SPAN style="font-weight: 400">().</SPAN><SPAN style="font-weight: 400">json</SPAN><SPAN style="font-weight: 400">()</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">marshal</SPAN><SPAN style="font-weight: 400">().</SPAN><SPAN style="font-weight: 400">json</SPAN><SPAN style="font-weight: 400">()</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">bean</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">this</SPAN><SPAN style="font-weight: 400">, </SPAN><SPAN style="font-weight: 400">"setPO(</SPAN><SPAN style="font-weight: 400">\"</SPAN><SPAN style="font-weight: 400">${body}</SPAN><SPAN style="font-weight: 400">\"</SPAN><SPAN style="font-weight: 400">)"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <SPAN style="font-weight: 400"> ;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">&nbsp;</SPAN><SPAN style="font-weight: 400">from</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"direct:getCustomer"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">setHeader</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"Authorization"</SPAN><SPAN style="font-weight: 400">).</SPAN><SPAN style="font-weight: 400">constant</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"Basic XXXX"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.</SPAN><SPAN style="font-weight: 400">setHeader</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"Accept"</SPAN><SPAN style="font-weight: 400">).</SPAN><SPAN style="font-weight: 400">constant</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"application/json"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <SPAN style="font-weight: 400"> .</SPAN><SPAN style="font-weight: 400">toD</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">"https://sapes5.sapdevcenter.com/sap/opu/odata/iwbep/GWSAMPLE_BASIC/BusinessPartnerSet('${header.CustomerID}')?bridgeEndpoint=true"</SPAN><SPAN style="font-weight: 400">)<BR /> .</SPAN><SPAN style="font-weight: 400">unmarshal</SPAN><SPAN style="font-weight: 400">().</SPAN><SPAN style="font-weight: 400">json</SPAN><SPAN style="font-weight: 400">()<BR /> </SPAN><SPAN style="font-weight: 400"> .</SPAN><SPAN style="font-weight: 400">marshal</SPAN><SPAN style="font-weight: 400">().</SPAN><SPAN style="font-weight: 400">json</SPAN><SPAN style="font-weight: 400">() &nbsp; &nbsp; <BR /> .</SPAN><SPAN style="font-weight: 400">bean</SPAN><SPAN style="font-weight: 400">(</SPAN><SPAN style="font-weight: 400">this</SPAN><SPAN style="font-weight: 400">, </SPAN><SPAN style="font-weight: 400">"setCust(</SPAN><SPAN style="font-weight: 400">\"</SPAN><SPAN style="font-weight: 400">${body}</SPAN><SPAN style="font-weight: 400">\"</SPAN><SPAN style="font-weight: 400">)"</SPAN><SPAN style="font-weight: 400">)</SPAN><BR /> <BR /> <SPAN style="font-weight: 400"> ;</SPAN></CODE></PRE><BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">The endpoints to trigger the call to SAP, is exposed as an API. Here I use Apicurio Studio to define the API contract. With two endpoints, fetch and fetchall. One returns SO, PO and Customer data, where the other one returns a collection of them.&nbsp;</SPAN><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/06/Screen-Shot-2021-06-21-at-11.38.13-PM.png" /></P><BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">We can now export the definition as a OpenAPI Specification contract in the form of YAML (<A href="https://github.com/weimeilin79/sap-odata4-camelk/blob/main/ibm/ibm-sap.yaml" target="_blank" rel="nofollow noopener noreferrer">Link</A> to see the yaml). Save the file into the folder of where your Camel application is. Add the API yaml file name to your Camel K application mode line, and Camel K will automatically map your code to this contract.</SPAN><BR /> <PRE><CODE><SPAN style="font-weight: 400"> // camel-k: language=java dependency=camel-openapi-java </SPAN><B>open-api=ibm-sap.yaml </B><SPAN style="font-weight: 400">dependency=camel-jackson</SPAN></CODE></PRE><BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">By using the Camel K CLI tool. Run the command to deploy the code to the OpenShift platform.&nbsp;</SPAN><BR /> <PRE><CODE><SPAN style="font-weight: 400"> kamel run SapOdata.java</SPAN></CODE></PRE><BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">And you should now see a microservice running. Did you notice how Camel K helps you, not only it detects and loads the libraries needed for you, but also containerised it as a running instance.&nbsp;</SPAN><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/06/Screen-Shot-2021-06-21-at-11.48.40-PM.png" /></P><BR /> <SPAN style="font-weight: 400">Go to my <A href="https://github.com/weimeilin79/sap-odata4-camelk/tree/main/ibm" target="_blank" rel="nofollow noopener noreferrer">git repo</A> to see the full code and running instructions.</SPAN><BR /> <BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">Kafka was used in the middle to set the event driven architecture. So the SO approval application can notify the shopping cart client when it’s been approved.&nbsp;</SPAN><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/06/Screen-Shot-2021-06-22-at-12.00.12-AM.png" /></P><BR /> <SPAN style="font-weight: 400">Since everything was put together in a week, with everyone in different timezones, miss communication will happen. What I did not realize was that all the client applications, SO approval and shopping carts were all written in JavaScript, and must communicate via HTTP. But</SPAN><B> Kafka only does Kafka protocols</B><SPAN style="font-weight: 400">!!! Therefore, I set up an Http Bridge in front of the Kafka clusters, so it will now translate the Kafka protocols.&nbsp;</SPAN><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/06/Screen-Shot-2021-06-22-at-12.07.00-AM.png" /><BR /> <SPAN style="font-weight: 400">And now clients can access the topic via HTTP endpoints.&nbsp; For more information on how to set, go to my <A href="https://github.com/weimeilin79/sap-odata4-camelk/blob/main/ibm/kafkaHttpBridge.adoc" target="_blank" rel="nofollow noopener noreferrer">Github repo</A> for more detailed instructions.</SPAN></P><BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">Last but not least, we need to migrate all UI5 SAP web applications to OpenShift. The UI5 is basically an NODEJS app. We first create the docker file to containerize it. And push it to a container registry.</SPAN><BR /> <PRE><CODE><SPAN style="font-weight: 400"> docker push quay.io/&lt;YOUR_REPO&gt;/socreate</SPAN></CODE></PRE><BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">And deploy the application to OpenShift.</SPAN><BR /> <PRE><CODE><SPAN style="font-weight: 400"> oc new-app quay.io/&lt;YOUR_REPO&gt;/socreate:latest --as-deployment-config</SPAN></CODE></PRE><BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400"><EM>BUT WAIT</EM>!! Since UI5 only does binds to *<STRONG>localhost</STRONG>* (weird..), we need to add a proxy that can tunnel traffic to it. Therefore, I added a sidecar proxy running right next to the NodeJS application. By adding the following configuration.&nbsp;</SPAN><BR /> <PRE><CODE><SPAN style="font-weight: 400">spec:</SPAN><BR /> <SPAN style="font-weight: 400"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;containers:</SPAN><BR /> <SPAN style="font-weight: 400"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- name: nginx</SPAN><BR /> <SPAN style="font-weight: 400"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;image: quay.io/weimei79/nginx-sidecar</SPAN><BR /> <SPAN style="font-weight: 400"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ports:</SPAN><BR /> <SPAN style="font-weight: 400"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- containerPort: 8081</SPAN><BR /> <SPAN style="font-weight: 400"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;protocol: TCP</SPAN><BR /> <SPAN style="font-weight: 400"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resources:</SPAN><BR /> <SPAN style="font-weight: 400"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;limits:</SPAN><BR /> <SPAN style="font-weight: 400"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cpu: 500m</SPAN><BR /> <SPAN style="font-weight: 400"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;memory: 1Gi</SPAN><BR /> <SPAN style="font-weight: 400"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;terminationMessagePath: /dev/termination-log</SPAN><BR /> <SPAN style="font-weight: 400"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;terminationMessagePolicy: File</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;imagePullPolicy: Always</SPAN><BR /> <BR /> <BR /> </CODE></PRE><BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">This will start the proxy, and since this NGINX proxy starts on port 8081, make sure you update all related settings on OpenShift.</SPAN><BR /> <PRE><CODE><SPAN style="font-weight: 400"> oc expose dc socreate --port=8181</SPAN><BR /> <SPAN style="font-weight: 400">&nbsp; oc expose svc socreate<BR /> <BR /> </SPAN></CODE></PRE><BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">And this is how you would migrate the UI5 application from a local SAP instance onto OpenShift. More detailed migration instructions, check out my <A href="https://github.com/weimeilin79/sap-odata4-camelk/blob/main/ibm/UI5-Migration/README.adoc" target="_blank" rel="nofollow noopener noreferrer">Github repo</A>.&nbsp;</SPAN><BR /> <BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">Once it’s done, you can see all the applications are running as a container on the cloud. And ready to approve the SOs.&nbsp;</SPAN><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/06/Screen-Shot-2021-06-22-at-12.21.31-AM-1.png" /></P><BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">This is actual developer view on top of our demo OpenShift platform</SPAN><BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/06/Screen-Shot-2021-06-22-at-12.22.35-AM.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> <SPAN style="font-weight: 400">Thank you Sanket for this fun ride, all the nail biting moments, but this is all the fun in IT right? We work through problems, tackle issues and ultimately get everything done! <span class="lia-unicode-emoji" title=":slightly_smiling_face:">🙂</span> If you are a SAPer, and want to explore the world of clouds and containers, what are you still waiting for? Join the ride! This is the story on how we made SAP Cloud Native and Event Driven in 4 days.&nbsp;</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">To see the full version, be sure to attend Sanket’s session (Virtual free events):</SPAN><BR /> <BR /> <B>SAP &amp; OpenShift:</B><SPAN style="font-weight: 400"> From classic ABAP development to cloud native applications: Use cases and reference architecture to implement with SAP Landscapes to unlock innovation enabled by Hybrid Cloud and Red Hat OpenShift.</SPAN><BR /> <BR /> <SPAN style="font-weight: 400">Register here:</SPAN><BR /> <BR /> <A href="https://events.redhat.com/profile/form/index.cfm?PKformID=0x373237abcd&amp;sc_cid=7013a000002w6W7AAI" target="_blank" rel="nofollow noopener noreferrer"><SPAN style="font-weight: 400"></SPAN></A><A href="https://events.redhat.com/profile/form/index.cfm?PKformID=0x373237abcd&amp;sc_cid=7013a000002w6W7AAI" target="test_blank" rel="nofollow noopener noreferrer">https://events.redhat.com/profile/form/index.cfm?PKformID=0x373237abcd&amp;sc_cid=7013a000002w6W7AAI</A><BR /> <BR /> Refer to Sanket's awesome post for more details on the concept and why we created this demo.<BR /> <BR /> <A href="https://blogs.sap.com/2021/06/28/safeguard-your-investments-with-red-hat-openshift/" target="_blank" rel="noopener noreferrer">https://blogs.sap.com/2021/06/28/safeguard-your-investments-with-red-hat-openshift/</A> 2021-06-23T14:51:04+02:00 https://community.sap.com/t5/enterprise-resource-planning-blogs-by-members/safeguard-your-investments-with-red-hat-openshift/ba-p/13512568 Safeguard your Investments with Red Hat OpenShift 2021-06-28T12:59:02+02:00 STaur https://community.sap.com/t5/user/viewprofilepage/user-id/7346 Traditional development with SAP has evolved from Classical ABAP to ABAP RAP, SAP CAP (Cloud Application Programming Model). Its analogous to moving on from doing Stop Motion animation to immersive CGI to virtual reality that we experience today. The need to evolve and innovate has never stalled for SAP rather has accelerated and multiplied in last decade. With an imperative to keep the core clean and lean in the new SAP world, the side-by-side concept has pushed the extensions towards the cloud to orchestrate the data and processes with cloud native capabilities in Hybrid cloud environments.<BR /> <H2 id="toc-hId-958356323"><STRONG>Evolving Hyperscalers</STRONG></H2><BR /> Hyperscalers tend to have higher churn rates on innovative platform services and are sharing the side-by-side extensibility space with SAP BTP. Whether you go with SAP BTP as your primary extension platform and create plethora of cloud native applications on the periphery utilising the native platform services on hyperscaler’s, or you go pure Hyperscaler based extension is based on the hyperscaler startegy your organisations are adopting for now or in future. It can influence the choice of application services, development services or data services that will have direct influence on how quickly you can turn around an innovative idea to a product or service. That&nbsp;is dependent on the vision the organisation or business has about its products and services in the near and long term.<BR /> <BR /> The extension and innovation platform choice is further influenced by SAP footprint on the landscape, where you are in the S/4 HANA adoption journey, how open is your organisation for open source innovation, the development culture, cloud native skillsets are a few other facets to consider. Its an architectural choice, again influenced by platform services that will help drive your innovation agenda.<BR /> <BR /> What if you want to abstract yourself from all these choices and changes that may overturn, the decisions your organisations take today in the future as hyperscalers evolve i.e. continue accelerating your digital transformation agenda. Red Hat OpenShift creates that abstraction layer and provides the scalability, interoperability and control that the organisation desires. Its like a hovercraft which glides across the changing turf. Its about getting from point A to B with the least number of changes in direction. The good part is all SAP BTP extensions work in harmony and unison along sides the Red Hat® OpenShift® cloud native applications / extensions i.e. you can opt to choose best of both worlds.<BR /> <BR /> The open innovation built behind Red Hat® Openshift® and its products is critical to achieving digital transformation objectives. This open innovation and embracing open source has driven tooling that enables us to do the following:<BR /> <UL><BR /> <LI>Innovate anywhere, at pace and agility on any cloud</LI><BR /> <LI>Consumption based models with wider ecosystem platform services</LI><BR /> <LI>Reduce the turn around times with streamlined deployment cycles.</LI><BR /> <LI>Improve team productivity Dev + Sec + Ops</LI><BR /> <LI>Optimise costs and efficiencies, share resources, accelerate with repositories of reusable assets and components.</LI><BR /> <LI>Moving away from VM’s with operating system footprint to application footprints, which enables hosting and running multiple applications and functions consuming the same amount of compute.</LI><BR /> <LI>The docker image or the golden image of the application can reproduce the same results with same behaviour and consistency in any environment. The immutability results in interoperability.</LI><BR /> </UL><BR /> As organisation comes to realise that "Cloud Adoption" is not just modernisation of applications with lift and shift to the cloud but the true value is realised by&nbsp;change in the organisation culture, the development and delivery practices, with Continuous Integration and Deployment practice, the Architectural and Integration Patterns.<BR /> <H2 id="toc-hId-761842818">Modular Architectures</H2><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/06/Picture-2-1.png" /><BR /> <P style="text-align: center"><EM>Modern Architectures and Agile Integrations (source : IBM + Red Hat)</EM></P><BR /> From enterprise architecture point of view, it’s how do we move away from traditional integration like enterprise service buses towards more of an agile integration which is decentralised. For e.g. we don’t want applications hogging system resources 24*7 to serve couple of requests. When we talk about event driven architectures, we can have a serverless function, being invoked by event or a web-hook call, spins up for the moment serves the request Just-In-Time and then spins down.<BR /> <BR /> With headless architectures, we are more microservices driven for any front end framework as far as it adheres to the API Contracts for the capabilities being delivered by underlying platform.&nbsp;When we look at Edge devices, we are using machine learning on edge , sifting out the most criticak data points for further insights from what is being sensed. Pub-Sub mechanisms allow application to listen for events / messages, to react only if they are relevant for any action. Istio Mesh based distributed Microservices deployment leads to failsafe, resilient, secure and observable mesh network.<BR /> <BR /> So we can almost end up distributed architectural components with a RACI matrix of all things, applications and digital entities that are part of that ecosystems which drive the enterprise systems and processes. This is where we start seeing the way we design applications become more modular, agile, scalable across different clouds to be iterated more quickly and more efficiently.<BR /> <H2 id="toc-hId-565329313">Delivering Outcomes at Speed, Scale and Control</H2><BR /> &nbsp;<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/06/Picture-1-6.png" /></P><BR /> <P class="image_caption" style="text-align: center;font-style: italic">Shift in the way we deliver outcomes (source : IBM + Red Hat)</P><BR /> &nbsp;<BR /> <BR /> Adopting&nbsp;dev-sec-ops takes a lot of different steps moving away from traditional methods and practices to hybrid cloud platform approach. Moving from siloed teams using waterfall delivery methods, to Agile practices with CI/CD adoption. Deploying in hybrid, public / private cloud with enterprise grade container platforms, is preferred over VMs with co-located workloads in data centers.&nbsp;From an application perspective we’re still building these monolithic applications and we need to start to move towards more modular , decentralised architectures and agile integrations, delivering microservices or serverless functions as explained above.<BR /> <BR /> When we talk about speed we're really talking about time to market. Agility, how quickly we can make new updates, new commits to specific processes or applications further how they can be simplified as far as the process more streamlined so that we have more freedom to experiment. The personas within these different roles especially when you're a developer you're usually, much more bleeding edge, want a quick turn around. Build once and run everywhere is emerging trend amongst the developers to cater the business demands.<BR /> <BR /> Though the questions like, how do we scale with resilience, without sacrificing the stability and control of enterprise landscape needs to be addressed. When you’re on the operations side or administration side you want a little more tried and trusted solutions, how do we enable both the personas and then control how do we mitigate any kind of security, risks or any kind of issues with bugs, how do we approach them more quickly when they do come up.<BR /> <H2 id="toc-hId-368815808">Red Hat Openshift - Smarter Kubernetes</H2><BR /> This is where the containers are becoming a defacto standard in the cloud native application development. It is underpinning the adoption of Devops culture. Containers promise things like application portability across any cloud. They allow developers to really just focus on building their apps instead of having to worry about the underlying infrastructure technologies.&nbsp;This is where you would require a policy based automated container orchestration mechanism and features to manage containers across your dev, test /staging and production environments.<BR /> <BR /> Kubernetes as open source, has really emerged as the standard for container orchestration, management and scalability. Though just installing Kubernetes to have a production grade platform is not enough: you’ll need to add authentication, networking, security, monitoring, logs management. That means you will also have to pick your tools among everything available (see&nbsp;<A href="https://landscape.cncf.io/" target="_blank" rel="nofollow noopener noreferrer">CNCF landscape</A>&nbsp;to get an idea of the complexity of the ecosystem), and maintain the cohesion of all of them as a whole; but also do updates and regression tests whenever there is a new version of one of these components.<BR /> <BR /> With OpenShift, Red Hat has decided to shield this complexity and deliver a&nbsp;<A href="https://www.openshift.com/blog/enterprise-kubernetes-with-openshift-part-one" target="_blank" rel="nofollow noopener noreferrer">comprehensive platform</A>, including not only Kubernetes at its core, but also all the essential open source tools that make it an enterprise-ready solution to confidently run your production. Of course, in case you already have your own stacks, then you can opt-out and plug into your existing solutions.<BR /> <BR /> &nbsp;<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/06/Picture-3-2.png" /></P><BR /> <P class="image_caption" style="text-align: center;font-style: italic">Red Hat Openshift run on "Anycloud" (source : IBM + Red Hat)</P><BR /> <BR /> <UL><BR /> <LI>OpenShift allows you to deploy traditional stateful applications, alongside cutting-edge cloud-native applications, by supporting modern architectures such as microservices or serverless.</LI><BR /> <LI>AT IBM we believe that containers, Kubernetes and Devops are the key ingredients that a modern application platform should be based on that allows you to transform your traditional apps, build cloud-native applications and also experiment with analytics, machine learning and serverless applications.</LI><BR /> <LI>This application platform needs to provide a consistent way for both developers and operations teams to collaborate across all deployment footprints, from Edge and air gapped environments to infrastructure you have in your own datacenter and all the way to the hybrid cloud.</LI><BR /> <LI>OpenShift allows you now to even migrate your legacy virtual machines to OpenShift itself by using Container Native Virtualisation</LI><BR /> </UL><BR /> I have been advocating the container based extensibility with modern architectures at several of IBM’s SAP Clients using Red Hat Openshift Platform and portfolio of products. I believe that the developers have the power and energy to create and architects help channelise that energy in right direction for business outcomes, while operational teams provide a level playing field for all participants of the Innovation Games. Liberating these personas of the traditional shackles is the need of the hour and this is what I am talking about in my upcoming talk on <A href="https://events.redhat.com/profile/form/index.cfm?PKformID=0x373237abcd" target="_blank" rel="nofollow noopener noreferrer">SAP and Openshift : From classic ABAP development to cloud-native applications.</A> Please join us to know more , where we also cover a demo to show you how we deploy cloud native extensions with OpenShift.<BR /> <BR /> The demo is based on modularising and separating the UI, Business Logic and the System functions using several Red Hat Openshift Products and Platform services to create a modern decentralised architectures. Its a Sales Order Approval Scenario which covers three Fiori apps : the Shopping Cart for Customer persona, the SO Approval App for the Approver persona and the Back-office persona using the approved Sales Order app to complete order fulfilment.<BR /> <BR /> &nbsp;<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/06/Picture-5.png" /></P><BR /> <P class="image_caption" style="text-align: center;font-style: italic;font-family: 'SAPRegular', 'Helvetica Neue', Arial, sans-serif">Demo Scenario : Sales Order Approval (source : IBM + Red Hat)</P><BR /> All Fiori apps / SAP Ui5 apps deployed on Openshift Containers , invoking microservices and serverless functions, streaming events and reacting to them using Red Hat Openshift streams for Apache Kafka broker. &nbsp;More details on technical deployment with sample code on GitHUb refer to the blog post here : <A href="https://blogs.sap.com/2021/06/23/make-sap-cloud-native-and-event-driven-in-4-days/" target="_blank" rel="noopener noreferrer">https://blogs.sap.com/2021/06/23/make-sap-cloud-native-and-event-driven-in-4-days/</A><BR /> <BR /> Hope you find this article useful. Feel free to comment and get in touch.<BR /> <BR /> &nbsp; 2021-06-28T12:59:02+02:00 https://community.sap.com/t5/technology-blogs-by-sap/how-to-extend-sap-s-4hana-business-address-services-or-any-other-sap-s/ba-p/13493350 How to Extend SAP S/4HANA Business Address Services (or any other SAP S/4HANA application) with Serverless Functions on Kyma Part-II 2021-09-23T09:07:03+02:00 former_member760197 https://community.sap.com/t5/user/viewprofilepage/user-id/760197 <P style="overflow: hidden;margin-bottom: 0px">In this blog post you will learn…</P><BR /> &nbsp;<BR /> <BR /> &nbsp;<BR /> <H3 id="toc-hId-1066225889"><STRONG>Overview:</STRONG></H3><BR /> <UL><BR /> <LI>We will learn about how we will create serverless function in Kyma Runtime and how they are deployed (i.e. how URLs are created to get which can be used to run serverless functions) .</LI><BR /> <LI>We will also learn about how to use Google APIs to get Geocodes of any inputted address .</LI><BR /> <LI>To Know what is serverless function and need of serverless function refer to<A href="https://blogs.sap.com/2021/09/21/how-to-extend-sap-s-4hana-business-address-services-or-any-other-sap-s-4hana-application-with-serverless-functions-on-kyma-part-i/" target="_blank" rel="noopener noreferrer"> previous blog post</A> of this blog series</LI><BR /> </UL><BR /> <H3 id="toc-hId-869712384"><STRONG>Prerequisites/Skills:</STRONG></H3><BR /> <OL><BR /> <LI>Access to SAP BTP Cockpit<BR /> You need access to a productive account of SAP BTP Cockpit<BR /> <EM>Note:</EM><BR /> <EM>This service is available in Trial version of SAP BTP Cockpit</EM></LI><BR /> <LI>Node.js/JavaScript<BR /> Functions are written in JavaScript for Node.js runtime.<BR /> As such, some knowledge is helpful, however, most of the tutorials can be followed without any knowledge.</LI><BR /> <LI>ABAP.</LI><BR /> <LI><BR /> <DIV>Kyma Environment Setup explained in <A title="https://blogs.sap.com/2021/09/17/how-to-extend-s-%e2%80%a6-on-kyma-part-i/" href="https://blogs.sap.com/2021/09/23/how-to-extend-sap-s-4hana-business-address-services-or-any-other-sap-s-4hana-application-with-serverless-functions-on-kyma-part-ii/" target="_blank" rel="noreferrer noopener">Part-I</A> of this Blog Series</DIV></LI><BR /> </OL><BR /> <H3 id="toc-hId-673198879"><STRONG>Implementation Steps:</STRONG></H3><BR /> After successfully setting up the Kyma Runtime Environment as described in the steps above, we now must define the Kyma Namespace. This is where all our functions will be defined. The steps below are to set up your Kyma Namespace and get started with the Kyma environment.<BR /> <BR /> 1. Open Your Subaccount in SAP BTP Cockpit.<BR /> <BR /> 2. Click on Instances and Subscription.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/p11.png" /></P><BR /> <P style="overflow: hidden;margin-bottom: 0px">3. Click on Environments.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/p12.png" /></P><BR /> &nbsp;<BR /> <P style="overflow: hidden;margin-bottom: 0px">4. Click on the Actions button in your Kyma environment and navigate to ‘Go to&nbsp; Dashboard’.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/p14.png" /></P><BR /> 5. On the Dashboard click on ‘Select Namespace’ and choose your namespace.<BR /> <BR /> &nbsp;<BR /> <H3 id="toc-hId-476685374"><STRONG>Creating Serverless Function</STRONG></H3><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/p15.png" /></P><BR /> <BR /> <UL><BR /> <LI>Click on ‘Functions’ on left hand pane and then click on ‘Create Function’ to create a new function.</LI><BR /> <LI>Enter name of your choice and press Enter. You will see the screen shown below.</LI><BR /> <LI>There are two sections under code tab:<BR /> <UL><BR /> <LI>First section will contain our source code to call the Google API.</LI><BR /> <LI>Second section will contain dependencies (if any).<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/p16.png" /></LI><BR /> </UL><BR /> </LI><BR /> </UL><BR /> <UL><BR /> <LI>We are now going to add code in Source section<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/p17.png" /></LI><BR /> </UL><BR /> &nbsp;<BR /> <PRE class="language-javascript"><CODE>const https = require('https');<BR /> <BR /> const key = {Enter Your Key Here}<BR /> <BR /> const generateGeoCode = (address) =&gt; {<BR /> <BR /> let data = '';<BR /> <BR /> return new Promise((resolve,reject) =&gt; {<BR /> <BR /> https.get(`https://maps.googleapis.com/maps/api/geocode/json?address=${address}&amp;key=${key}`,<BR /> <BR /> (response)=&gt;{<BR /> <BR /> response.on('data',(chunks)=&gt;{<BR /> <BR /> data+=chunks;<BR /> <BR /> })<BR /> <BR /> response.on('end',()=&gt;<BR /> <BR /> {<BR /> <BR /> // console.log('check123',data);<BR /> <BR /> // response = data;<BR /> <BR /> // return data;<BR /> <BR /> resolve(data);<BR /> <BR /> })<BR /> <BR /> response.on('error',(error)=&gt;{<BR /> <BR /> console.log(error)<BR /> <BR /> reject(error);})<BR /> <BR /> });<BR /> <BR /> // console.log("google",response);<BR /> <BR /> })<BR /> <BR /> }<BR /> <BR /> <BR /> <BR /> <BR /> module.exports = {<BR /> <BR /> main: async function (event, context) {<BR /> <BR /> console.log('event',event.extensions.request.query.address);<BR /> <BR /> address = event.extensions.request.query.address;<BR /> <BR /> const output = await generateGeoCode(address);<BR /> <BR /> let {results} = JSON.parse(output);<BR /> <BR /> console.log('add',results[0].geometry.location.lat);<BR /> <BR /> let final = '';<BR /> <BR /> final +=`${results[0].geometry.location.lat}, ${results[0].geometry.location.lng}`<BR /> <BR /> return final;<BR /> <BR /> //console.log('f',f);<BR /> <BR /> }}<BR /> <BR /> <BR /> </CODE></PRE><BR /> In the above Picture you can see that there is a variable key that has been purposely left blank. Here you need to get your credentials from Google Cloud Platform before entering them. you can get them from <A href="https://developers.google.com/maps/documentation/geocoding/overview" target="_blank" rel="nofollow noopener noreferrer">Here</A><BR /> <BR /> &nbsp;<BR /> <OL start="4"><BR /> <LI>Here we can see that our code is dependent on HTTP module to run so we are going to put that in our dependency:</LI><BR /> </OL><BR /> <PRE class="language-javascript"><CODE>{<BR /> <BR /> "name": "geocode-api",<BR /> <BR /> "version": "1.0.0",<BR /> <BR /> "description": "geocode api to get latitude and longitude",<BR /> <BR /> "dependencies": {<BR /> <BR /> "https": "^1.0.0"<BR /> <BR /> },<BR /> <BR /> "license": "ISC"<BR /> <BR /> }</CODE></PRE><BR /> &nbsp;<BR /> <BR /> This Completes first part of our function. Now it’s time to create the URL to send request to API.<BR /> <BR /> &nbsp;<BR /> <H3 id="toc-hId-280171869"><STRONG>Creation of URL for Function to place request to API:</STRONG></H3><BR /> &nbsp;<BR /> <UL><BR /> <LI>Click on Configuration tab next to Code tab and click on ‘Expose Function’.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/p18.png" /></LI><BR /> </UL><BR /> &nbsp;<BR /> <UL><BR /> <LI>Enter API Rule Name and Host Name and press Enter. This will generate the URL.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/p19.png" /></LI><BR /> </UL><BR /> &nbsp;<BR /> <UL><BR /> <LI>You can run the URL above in your browser and see the output.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/p20.png" /></LI><BR /> </UL><BR /> <H3 id="toc-hId-83658364"><STRONG>Summary:</STRONG></H3><BR /> In this blog post we saw about<BR /> <UL><BR /> <LI>How to create serverless function in Kyma.</LI><BR /> <LI>How to use Google APIs to find Geocodes using address send by user.</LI><BR /> <LI>And How to deploy the serverless function and generate API URL which can be used to send get request to server.</LI><BR /> </UL><BR /> <H3 id="toc-hId--112855141"><STRONG>Next Steps:&nbsp;</STRONG></H3><BR /> We will be seeing how to<A href="https://blogs.sap.com/2021/09/27/how-to-extend-sap-s-4hana-business-address-services-or-any-other-sap-s-4hana-application-with-serverless-functions-on-kyma-part-iii/" target="_blank" rel="noopener noreferrer"> Integrate the Kyma serverless function in any SAP S/4HANA applications</A><BR /> <BR /> For any queries and doubts please comment in comment section I will be more then happy to help you, also you can follow my profile to get updated of upcoming blog posts. 2021-09-23T09:07:03+02:00 https://community.sap.com/t5/technology-blogs-by-sap/how-to-extend-sap-s-4hana-business-address-services-or-any-other-sap-s/ba-p/13509781 How to Extend SAP S/4HANA Business Address Services (or any other SAP S/4HANA application) with Serverless Functions on Kyma Part-III 2021-09-27T10:31:38+02:00 shilpi_sen06 https://community.sap.com/t5/user/viewprofilepage/user-id/762068 <STRONG>Introduction</STRONG><BR /> <BR /> This blog post explains how a Serverless Function developed in Kyma Runtime can be consumed in the SAP S/4HANA system for the extensibility of SAP S/4HANA applications. Specifically, this blog post covers the extensibility of Business Address Services and is in continuation of Blog Series of the same title(mentioned under Prerequisites).<BR /> <BR /> <STRONG>Prerequisites/Skills</STRONG><BR /> <OL><BR /> <LI>ABAP</LI><BR /> <LI>Kyma Environment Setup explained in <A href="https://blogs.sap.com/2021/09/21/how-to-extend-sap-s-4hana-business-address-services-or-any-other-sap-s-4hana-application-with-serverless-functions-on-kyma-part-i/" target="_blank" rel="noopener noreferrer">Part-I</A> of this Blog Series</LI><BR /> <LI>Kyma Serverless Function Implementation explained in <A href="https://blogs.sap.com/2021/09/23/how-to-extend-sap-s-4hana-business-address-services-or-any-other-sap-s-4hana-application-with-serverless-functions-on-kyma-part-ii/" target="_blank" rel="noopener noreferrer">Part-II</A> of this Blog Series</LI><BR /> </OL><BR /> &nbsp;<BR /> <BR /> In our example, we will be implementing the geocode functionality with the Business Partner (BP) transaction. Once our serverless function returns the geocodes, we will be updating a Custom Z table.<BR /> <UL><BR /> <LI>Since it is a customer modification, we can make use of the BADIs available. ADDRESS_UPDATE is the BADI that is used to trigger updates on Address Changes. We have implemented this BADI in SE18 as ZADDRESS_UPD_SRV_POC. The implementing class name is given as ZCL_IM_ADDRESS_UPD_SRV_POC</LI><BR /> </UL><BR /> &nbsp;<BR /> <UL><BR /> <LI style="overflow: hidden"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/P1-20.png" height="268" width="555" /></LI><BR /> <LI>Based on the type of address created i.e. Organization, Person, or Workplace, add the logic in methods ADDRESS1_SAVED, ADDRESS2_SAVED, and ADDRESS3_SAVED&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; respectively. Next, we must compile the complete URL which will be a combination of:<BR /> <UL><BR /> <LI>URL generated by Kyma runtime</LI><BR /> <LI>Search parameters from BP transaction which will be components of address based on which Geocode will be found. For our PoC, we have considered House Number, Street, City, Region, Postal Code, and Country</LI><BR /> </UL><BR /> </LI><BR /> </UL><BR /> &nbsp;<BR /> <OL><BR /> <LI style="list-style-type: none"></LI><BR /> </OL><BR /> <PRE class="language-abap"><CODE> DATA(url) = 'https://get-geocodes.fdc4fe9.kyma.shoot.live.k8s-hana.ondemand.com/?address='.<BR /> LOOP AT im_t_xadrc ASSIGNING FIELD-SYMBOL(&lt;fs_xadrc&gt;).<BR /> <BR /> IF &lt;fs_xadrc&gt;-house_num1 IS NOT INITIAL.<BR /> full_address = &lt;fs_xadrc&gt;-house_num1.<BR /> ENDIF.<BR /> IF &lt;fs_xadrc&gt;-street IS NOT INITIAL.<BR /> CONCATENATE full_address &lt;fs_xadrc&gt;-street INTO full_address SEPARATED BY space.<BR /> ENDIF.<BR /> IF &lt;fs_xadrc&gt;-city1 IS NOT INITIAL.<BR /> CONCATENATE full_address &lt;fs_xadrc&gt;-city1 INTO full_address SEPARATED BY space.<BR /> ENDIF.<BR /> IF &lt;fs_xadrc&gt;-region IS NOT INITIAL.<BR /> CONCATENATE full_address &lt;fs_xadrc&gt;-region INTO full_address SEPARATED BY space.<BR /> ENDIF.<BR /> IF &lt;fs_xadrc&gt;-post_code1 IS NOT INITIAL.<BR /> CONCATENATE full_address &lt;fs_xadrc&gt;-post_code1 INTO full_address SEPARATED BY space.<BR /> ENDIF.<BR /> IF &lt;fs_xadrc&gt;-country IS NOT INITIAL.<BR /> CONCATENATE full_address &lt;fs_xadrc&gt;-country INTO full_address SEPARATED BY space.<BR /> ENDIF.<BR /> REPLACE '#' WITH space INTO full_address.<BR /> SHIFT full_address LEFT DELETING LEADING space.<BR /> CONDENSE full_address.<BR /> CONCATENATE url full_address INTO DATA(final_url).<BR /> ENDLOOP.</CODE></PRE><BR /> &nbsp;<BR /> <UL><BR /> <LI>Next, an HTTP client for the generated URL must be created. This is done by calling the method CREATE_FROM_URL of class CL_HTTP_CLIENT. This HTTP client is used to send the request to Kyma Serverless Runtime and receive in response the Geocodes of the provided search parameters.</LI><BR /> </UL><BR /> &nbsp;<BR /> <PRE class="language-abap"><CODE>"create HTTP client by url<BR /> CALL METHOD cl_http_client=&gt;create_by_url<BR /> EXPORTING<BR /> url = final_url<BR /> IMPORTING<BR /> client = lo_http_client<BR /> EXCEPTIONS<BR /> argument_not_found = 1<BR /> plugin_not_active = 2<BR /> internal_error = 3<BR /> OTHERS = 4.<BR /> <BR /> IF sy-subrc &lt;&gt; 0.<BR /> "error handling<BR /> ENDIF.<BR /> <BR /> *** Send the request<BR /> lo_http_client-&gt;send(<BR /> EXCEPTIONS<BR /> http_communication_failure = 1<BR /> http_invalid_state = 2 ).<BR /> <BR /> *** Receive the respose<BR /> lo_http_client-&gt;receive(<BR /> EXCEPTIONS<BR /> http_communication_failure = 1<BR /> http_invalid_state = 2<BR /> http_processing_failed = 3 ).</CODE></PRE><BR /> &nbsp;<BR /> <UL><BR /> <LI>Finally, we read the result from the HTTP response and update our custom table with the&nbsp; &nbsp; &nbsp; &nbsp; Geocode.</LI><BR /> </UL><BR /> <PRE class="language-abap"><CODE>*** Read the result<BR /> lv_result = lo_http_client-&gt;response-&gt;get_cdata( ).<BR /> IF lv_result NP 'Internal Server Error'.<BR /> ls_geocode-mandt = sy-mandt.<BR /> ls_geocode-addrnumber = im_address_number.<BR /> SPLIT lv_result AT ',' INTO ls_geocode-latitude ls_geocode-longitude.<BR /> <BR /> MODIFY zaddress_geocode FROM ls_geocode.<BR /> ENDIF.</CODE></PRE><BR /> &nbsp;<BR /> <UL><BR /> <LI>The Custom table structure is as below:</LI><BR /> </UL><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/P2-5.png" /></P><BR /> &nbsp;<BR /> <UL><BR /> <LI>Next, in BP transaction we must create an Organization BP with the values below for address fields which in turn will be considered for the final URL generation:</LI><BR /> </UL><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/P3-2.png" /></P><BR /> &nbsp;<BR /> <UL><BR /> <LI>The address number of the BP above is:</LI><BR /> </UL><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/P4-3.png" /></P><BR /> &nbsp;<BR /> <UL><BR /> <LI>On Save of the Business Partner, the custom table ZADDRESS_GEOCODE is updated:</LI><BR /> </UL><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/08/P5-4.png" /></P><BR /> &nbsp;<BR /> <BR /> <EM>Note: We did not enhance the BP transaction to display these coordinates, but such enhancements can be taken up if required for the project.</EM><BR /> <BR /> <STRONG>Summary</STRONG><BR /> <BR /> In this blog post, we have seen how a Serverless Function developed in Kyma Runtime can be consumed in the SAP S/4HANA system for the extensibility of Business Address Services or any other SAP S/4HANA applications. 2021-09-27T10:31:38+02:00 https://community.sap.com/t5/technology-blogs-by-sap/deploying-an-sap-customer-data-cloud-extension-to-sap-btp-kyma-runtime/ba-p/13492566 Deploying an SAP Customer Data Cloud Extension to SAP BTP, Kyma Runtime 2021-11-10T19:51:00+01:00 sissa https://community.sap.com/t5/user/viewprofilepage/user-id/560415 The objective of this example is to demonstrate how to setup a&nbsp;<STRONG>Kyma serverless function</STRONG>&nbsp;to be used as an&nbsp;<STRONG><A href="https://help.sap.com/viewer/product/SAP_CUSTOMER_DATA_CLOUD/GIGYA/en-US" rel="noopener noreferrer" target="_blank">SAP Customer Data Cloud</A> Extension endpoint</STRONG>.<BR /> <BR /> <STRONG>SAP Customer Data Cloud Extensions</STRONG> allow us to synchronously intercept specific SAP Customer Data Cloud REST API calls on the server-side and to execute custom business logic before the specific API call invokes SAP Customer Data Cloud. They also act like filters as they can be used to prevent an API call from invoking SAP Customer Data Cloud by returning a status with the value of "FAIL" and a custom error message to the user if specific business rules aren't met.<BR /> <BR /> Currently, the following SAP Customer Data Cloud REST API calls can be intercepted using an SAP Customer Data Cloud Extension:<BR /> <UL><BR /> <LI><STRONG>accounts.register</STRONG>&nbsp;- Registration (OnBeforeAccountsRegister extension)</LI><BR /> <LI><STRONG>accounts.login</STRONG>&nbsp;- Login (OnBeforeAccountsLogin extension)</LI><BR /> <LI><STRONG>socialize.login</STRONG>&nbsp;- Social Login (OnBeforeSocialLogin extension)</LI><BR /> <LI><STRONG>accounts.setAccountInfo</STRONG>&nbsp;- Profile Updates (OnBeforeSetAccountInfo extension)</LI><BR /> <LI><STRONG>accounts.resetPassword</STRONG>&nbsp;- Reset Password (OnBeforeResetPassword extension)</LI><BR /> <LI><STRONG>Send SMS</STRONG>&nbsp;- Communication (OnBeforeSendSMS extension)</LI><BR /> </UL><BR /> In this example, the&nbsp;<STRONG>accounts.setAccountInfo</STRONG> REST API endpoint has been intercepted to cleanse the user's address via an external SAP Data Quality Management microservice.<BR /> <BR /> If the address can be cleansed, the request is enriched with the cleansed address and the user's account is updated.<BR /> <BR /> On the other hand, if the address can't be cleansed, an error message is returned to the user and the user's account isn't updated.<BR /> <BR /> The code of this example can be easily re-used to build any other SAP Customer Data Cloud Extension and deploy it to SAP BTP, Kyma Runtime or to Kyma Open Source.<BR /> <BLOCKQUOTE><STRONG>Notes:</STRONG><BR /> <UL><BR /> <LI>All the functionality presented here are subject to change and may be changed by SAP at any time for any reason without notice.</LI><BR /> <LI>For demonstration, this example uses an API Key to authenticate requests to SAP Data Quality Management. In a real-world scenario, either <A href="https://help.sap.com/viewer/d95546360fea44988eb614718ff7e959/Cloud/en-US/1fa6310be4e14ebb86c0411491bcff97.html" rel="noopener noreferrer" target="_blank">OAuth 2.0</A>&nbsp;or&nbsp;<A href="https://help.sap.com/viewer/d95546360fea44988eb614718ff7e959/Cloud/en-US/7e983cce55604a8bb23a137483ea5dca.html" rel="noopener noreferrer" target="_blank">Client Certificate Authentication</A>&nbsp;are to be used.</LI><BR /> </UL><BR /> </BLOCKQUOTE><BR /> <H2 id="toc-hId-937115338"><A id="user-content-scenario" class="anchor" href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdc-extension#scenario" aria-hidden="true" target="_blank" rel="nofollow noopener noreferrer"></A>Scenario</H2><BR /> This example includes a&nbsp;<STRONG>Kyma serverless function</STRONG>,&nbsp;<EM>cdc-extension</EM>, that is exposed as an <STRONG>SAP Customer Data Cloud extension endpoint</STRONG>, and demonstrates how to:<BR /> <UL><BR /> <LI>Create an <A href="https://help.sap.com/viewer/8b8d6fffe113457094a17701f63e3d6a/GIGYA/en-US/4153ec2f70b21014bbc5a10ce4041860.html" rel="noopener noreferrer" target="_blank">SAP Customer Data Cloud extension</A>&nbsp;endpoint using a&nbsp;<A href="https://kyma-project.io/docs/kyma/latest/01-overview/main-areas/serverless/svls-01-overview/#documentation-content" rel="nofollow noopener noreferrer" target="_blank">Kyma serverless function</A></LI><BR /> <LI>Deploy a Kyma serverless function and an API Rule using the&nbsp;<A href="https://kubernetes.io/docs/reference/kubectl/overview/" rel="nofollow noopener noreferrer" target="_blank">Kubernetes command-line tool</A></LI><BR /> <LI>Alternately, deploy a Kyma serverless function and an API Rule using the Kyma Console User Interface</LI><BR /> <LI>Explore&nbsp;<A href="https://api.sap.com/" rel="noopener noreferrer" target="_blank">api.sap.com</A>&nbsp;and try out REST API calls using a sandbox environment</LI><BR /> <LI>Use the&nbsp;<A href="https://www.sap.com/canada/products/data-quality-management.html" rel="noopener noreferrer" target="_blank">SAP Data Quality Management microservice</A>&nbsp;for location data to cleanse addresses</LI><BR /> </UL><BR /> <H4 id="toc-hId-998767271"><A id="user-content-solution-architecture" class="anchor" href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdc-extension#solution-architecture" aria-hidden="true" target="_blank" rel="nofollow noopener noreferrer"></A>Solution Architecture</H4><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/cdc-extension-diagram-2.png" /></P><BR /> <BR /> <H4 id="toc-hId-802253766">Sequence Diagram</H4><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/cdc-extension-seq-1.jpg" /></P><BR /> <BR /> <H2 id="toc-hId-347574823">Suggested introductory readings</H2><BR /> <UL><BR /> <LI><A href="https://help.sap.com/viewer/8b8d6fffe113457094a17701f63e3d6a/GIGYA/en-US/4153ec2f70b21014bbc5a10ce4041860.html" rel="noopener noreferrer" target="_blank">What is an SAP Customer Data Cloud Extension?</A></LI><BR /> <LI><A href="https://microlearning.opensap.com/media/1_ucyxrfoj" rel="noopener noreferrer" target="_blank">An Introduction to SAP Customer Data Cloud Extensions (video)</A></LI><BR /> <LI><A href="https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/468c2f3c3ca24c2c8497ef9f83154c44.html" rel="noopener noreferrer" target="_blank">What is Kyma Environment?</A></LI><BR /> <LI><A href="https://kyma-project.io/docs/kyma/latest/01-overview/main-areas/serverless/svls-01-overview/#documentation-content" rel="nofollow noopener noreferrer" target="_blank">What is a Kyma Serverless Function?</A></LI><BR /> <LI><A href="https://kyma-project.io/docs/kyma/latest/05-technical-reference/00-custom-resources/apix-01-apirule/#documentation-content" rel="nofollow noopener noreferrer" target="_blank">What is a Kyma API Rule?</A></LI><BR /> <LI><A href="https://kyma-project.io/" rel="nofollow noopener noreferrer" target="_blank">Project Kyma Documentation</A></LI><BR /> <LI><A href="https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/b8e16869e64a4abe93cc194aa6fdacf5.html" rel="noopener noreferrer" target="_blank">Administration and Operations in the Kyma Environment</A></LI><BR /> </UL><BR /> <H2 id="toc-hId-151061318"><A id="user-content-pre-requisites" class="anchor" href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdc-extension#pre-requisites" aria-hidden="true" target="_blank" rel="nofollow noopener noreferrer"></A>Pre-requisites</H2><BR /> <UL><BR /> <LI>Provision&nbsp;<A href="https://www.sap.com/canada/products/crm/customer-data-management.html" rel="noopener noreferrer" target="_blank">SAP Customer Data Cloud from Gigya</A>&nbsp;and <A href="https://help.sap.com/viewer/8b8d6fffe113457094a17701f63e3d6a/GIGYA/en-US/41720d7370b21014bbc5a10ce4041860.html" rel="noopener noreferrer" target="_blank">setup an SAP Customer Data Cloud site</A>.</LI><BR /> <LI><A href="https://developers.sap.com/tutorials/hcp-create-trial-account.html" rel="noopener noreferrer" target="_blank">Get a Free Account on SAP BTP Trial</A>.</LI><BR /> <LI><A href="https://developers.sap.com/tutorials/cp-kyma-getting-started.html" rel="noopener noreferrer" target="_blank">Enable SAP BTP, Kyma Runtime</A>.</LI><BR /> <LI><A href="https://developers.sap.com/tutorials/cp-kyma-download-cli.html#d81e7789-ced4-4df6-b4a0-132d8c637077" rel="noopener noreferrer" target="_blank">Download and install the Kubernetes Command Line Tool</A>.</LI><BR /> <LI><A href="https://developers.sap.com/tutorials/cp-kyma-download-cli.html#4709f3b9-b9bc-45f1-89c1-cd6f097c55f5" rel="noopener noreferrer" target="_blank">Test the kubectl installation</A>.</LI><BR /> <LI><A href="https://developers.sap.com/tutorials/cp-kyma-download-cli.html#2ef10816-b759-4080-a8ec-eadbc3317ebd" rel="noopener noreferrer" target="_blank">Download the Kyma Runtime kubeconfig</A>.</LI><BR /> <LI>Create your&nbsp;<A href="https://api.sap.com/" rel="noopener noreferrer" target="_blank">api.sap.com</A>&nbsp;account.</LI><BR /> </UL><BR /> <H2 id="toc-hId--45452187"><A id="user-content-deployment-steps" class="anchor" href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdc-extension#deployment-steps" aria-hidden="true" target="_blank" rel="nofollow noopener noreferrer"></A>Deployment steps</H2><BR /> <OL><BR /> <LI>Go to the&nbsp;<A href="https://github.com/SAP-samples/kyma-runtime-extension-samples" target="_blank" rel="nofollow noopener noreferrer">kyma-runtime-extension-samples</A>&nbsp;repository and clone it. This repository contains a collection of Kyma sample applications including this example (in the&nbsp;<STRONG>cdc-extension</STRONG> subfolder). Download the code by choosing the green <STRONG>Code</STRONG> button, and then choosing one of the options to download the code locally. Alternately, you can also run the following command using your command-line interface within your desired folder location:<BR /> <UL><BR /> <LI><CODE>git clone <A href="https://github.com/SAP-samples/kyma-runtime-extension-samples" target="test_blank" rel="nofollow noopener noreferrer">https://github.com/SAP-samples/kyma-runtime-extension-samples</A></CODE><BR /> <BLOCKQUOTE><STRONG>Note:</STRONG> The source code of this example is in the <A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdc-extension" target="_blank" rel="nofollow noopener noreferrer"><STRONG>cdc-extension</STRONG></A> subfolder of&nbsp;<A href="https://github.com/SAP-samples/kyma-runtime-extension-samples" target="_blank" rel="nofollow noopener noreferrer">this repository</A>.</BLOCKQUOTE><BR /> </LI><BR /> </UL><BR /> </LI><BR /> <LI>Update the values of the following environment variables in the&nbsp;<STRONG>./kyma-runtime-extension-samples/cdc-extension/k8s/function.yaml</STRONG>&nbsp;file:<BR /> <CODE>CDC_API_KEY</CODE>,&nbsp;<CODE>SAP_API_HUB_API_KEY</CODE>,&nbsp;<CODE>PUBLIC_KEY_KID</CODE>,&nbsp;<CODE>PUBLIC_KEY_N</CODE>&nbsp;and&nbsp;<CODE>PUBLIC_KEY_E</CODE><BR /> <UL><BR /> <LI><CODE>CDC_API_KEY</CODE> - This is the API Key of the SAP Customer Data Cloud site and can be got from the <A href="https://console.gigya.com/" rel="nofollow noopener noreferrer" target="_blank">SAP Customer Data Cloud console</A>.</LI><BR /> <LI><CODE>SAP_API_HUB_API_KEY</CODE>&nbsp;- This is the API Key of SAP API Business Hub. Login to&nbsp;<A href="https://api.sap.com/" rel="noopener noreferrer" target="_blank">api.sap.com</A>. Then, go to your profile settings and click on&nbsp;<STRONG>Show API Key</STRONG>&nbsp;to get the value for this variable.</LI><BR /> </UL><BR /> Go to <A href="https://accounts" target="test_blank" rel="nofollow noopener noreferrer">https://accounts</A>.<STRONG>SAP_Customer_Data_Cloud_Data_Center</STRONG>/accounts.getJWTPublicKey?apiKey=<STRONG>Your_SAP_Customer_Data_Cloud_Site_API_Key</STRONG>.<BR /> <BR /> <A href="https://help.sap.com/viewer/8b8d6fffe113457094a17701f63e3d6a/GIGYA/en-US/41573b6370b21014bbc5a10ce4041860.html" rel="noopener noreferrer" target="_blank">Find your SAP Customer Data Cloud Data Center</A>. Replace <STRONG>SAP_Customer_Data_Cloud_Data_Center</STRONG>&nbsp;with your Data Center (For example, the US Data Center is&nbsp;<STRONG>us1.gigya.com</STRONG>). Replace <STRONG>Your_SAP_Customer_Data_Cloud_Site_API_Key</STRONG> with your SAP Customer Data Cloud site's API Key.<BR /> <BR /> The response JSON body of the above request will also include the following fields: <STRONG>kid</STRONG>,&nbsp;<STRONG>n</STRONG>&nbsp;and&nbsp;<STRONG>e</STRONG><BR /> <UL><BR /> <LI><CODE>PUBLIC_KEY_KID</CODE>&nbsp;- This is the value of the&nbsp;<STRONG>kid</STRONG>&nbsp;field in the response above.</LI><BR /> <LI><CODE>PUBLIC_KEY_N</CODE>&nbsp;- This is the value of the&nbsp;<STRONG>n</STRONG>&nbsp;field in the response above.</LI><BR /> <LI><CODE>PUBLIC_KEY_E</CODE>&nbsp;- This is the value of the&nbsp;<STRONG>e</STRONG>&nbsp;field in the response above.</LI><BR /> </UL><BR /> </LI><BR /> <LI>Create a Kubernetes namespace with the name&nbsp;<CODE>cdc</CODE>.<BR /> <UL><BR /> <LI><CODE>kubectl create namespace cdc</CODE></LI><BR /> </UL><BR /> <BLOCKQUOTE><STRONG>Note:</STRONG>&nbsp;As a prerequisite, please follow the steps listed in the following tutorial:&nbsp;<A href="https://developers.sap.com/tutorials/cp-kyma-download-cli.html#2ef10816-b759-4080-a8ec-eadbc3317ebd" rel="noopener noreferrer" target="_blank">Download the Kyma Runtime kubeconfig</A></BLOCKQUOTE><BR /> <STRONG>Alternately, use the Kyma Console User Interface to create a new namespace</STRONG><BR /> <UL><BR /> <LI>Open the Kyma console and click on&nbsp;<STRONG>Add new namespace</STRONG>. Enter its name as&nbsp;<STRONG>cdc</STRONG>&nbsp;and click the&nbsp;<STRONG>Create</STRONG>&nbsp;button.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/1-16.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/2-11.png" /></LI><BR /> </UL><BR /> </LI><BR /> <LI>Create/update Kubernetes resources of the&nbsp;<STRONG>cdc-extension serverless function</STRONG>.<BR /> <UL><BR /> <LI><CODE>kubectl apply -f ./kyma-runtime-extension-samples/cdc-extension/k8s/function.yaml</CODE></LI><BR /> <LI><CODE>kubectl apply -f ./kyma-runtime-extension-samples/cdc-extension/k8s/api-rule.yaml</CODE></LI><BR /> </UL><BR /> <BLOCKQUOTE><STRONG>Note:</STRONG>&nbsp;As a prerequisite, please follow the steps listed in the following tutorial:&nbsp;<A href="https://developers.sap.com/tutorials/cp-kyma-download-cli.html#2ef10816-b759-4080-a8ec-eadbc3317ebd" rel="noopener noreferrer" target="_blank">Download the Kyma Runtime kubeconfig</A></BLOCKQUOTE><BR /> <BLOCKQUOTE>The resources are represented as declarative YAML objects. Applying the resources will perform the following steps:<BR /> <UL><BR /> <LI>Deploy the Kyma serverless function</LI><BR /> <LI>Expose the serverless function using a Kyma API Rule that will serve as the SAP Customer Data Cloud Extension endpoint</LI><BR /> </UL><BR /> </BLOCKQUOTE><BR /> <STRONG>Alternately, deploy the Kyma serverless function and API Rule using the Kyma Console User Interface:</STRONG><BR /> <UL><BR /> <LI>Open the Kyma console and select the&nbsp;<STRONG>cdc</STRONG>&nbsp;namespace.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/3-8.png" /></LI><BR /> <LI>Click on&nbsp;<STRONG>Workloads</STRONG>. Then, click on&nbsp;<STRONG>Deploy new workload</STRONG>&nbsp;and select&nbsp;<STRONG>Upload YAML</STRONG>.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/4-7.png" /></LI><BR /> <LI>Then, click on&nbsp;<STRONG>Browse</STRONG>&nbsp;to select the following YAML file, and click on&nbsp;<STRONG>Deploy</STRONG>:&nbsp;<STRONG><STRONG>./kyma-runtime-extension-samples/cdc-extension/k8s/function.yaml</STRONG></STRONG><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/5-6.png" /></LI><BR /> <LI>Repeat the above steps and select the following YAML file. Then, click on&nbsp;<STRONG>Deploy</STRONG>:&nbsp;<STRONG><STRONG>./kyma-runtime-extension-samples/cdc-extension/k8s/api-rule.yaml</STRONG></STRONG><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/6-6.png" /></LI><BR /> </UL><BR /> </LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">Go to the Kyma Console -&gt;&nbsp;<STRONG>cdc</STRONG>&nbsp;namespace -&gt; <STRONG>Discovery &amp; Network</STRONG> -&gt; <STRONG>API Rules</STRONG>. Copy the&nbsp;<STRONG>host</STRONG>&nbsp;URL of the cdc-extension API Rule.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/7-4.png" /></LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">Then, go to the <A href="https://console.gigya.com/" rel="nofollow noopener noreferrer" target="_blank">SAP Customer Data Cloud Console</A>. Select your site and click on <STRONG>Extensions</STRONG> -&gt; <STRONG>Add</STRONG>.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/8-5.png" /></LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">Enter any <STRONG>Name</STRONG>, select the <STRONG>API</STRONG> as&nbsp;<STRONG>accounts.setAccountInfo (OnBeforeSetAccountInfo)</STRONG>, enter any <STRONG>Description</STRONG>, paste the <STRONG>host</STRONG> URL from step 5 above and click on <STRONG>Advanced</STRONG>. Then, enter the <STRONG>Timeout</STRONG> as 10000 ms, select the <STRONG>Fallback Policy</STRONG> as <STRONG>FailOnAnyError</STRONG> and click <STRONG>Save</STRONG>.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/9-6.png" /></LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">In the <A href="https://console.gigya.com/" rel="nofollow noopener noreferrer" target="_blank">SAP Customer Data Cloud Console</A>, select your site and click on <STRONG>Schema</STRONG> in the left menu under <STRONG>Registration-as-a-Service</STRONG>.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/10-4.png" /></LI><BR /> <LI>Set the following schema fields to be <STRONG>Required</STRONG> and select&nbsp;<STRONG>Write Access</STRONG>&nbsp;as&nbsp;<STRONG>clientModify </STRONG>for all of them. Then, click <STRONG>Save Changes</STRONG>.<BR /> <UL><BR /> <LI>profile.address</LI><BR /> <LI>profile.city</LI><BR /> <LI>profile.state</LI><BR /> <LI>profile.zip</LI><BR /> <LI>profile.country<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/16-2.png" /></LI><BR /> </UL><BR /> </LI><BR /> <LI>In the <A href="https://console.gigya.com/" rel="nofollow noopener noreferrer" target="_blank">SAP Customer Data Cloud Console</A>, select your site and click on <STRONG>Screen-Sets</STRONG> in the left menu under <STRONG>Registration-as-a-Service</STRONG>.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/17-1.png" /></LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">Click on the <STRONG>Default-RegistrationLogin</STRONG> screen-set.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/18-1.png" /></LI><BR /> <LI>Select the <STRONG>Registration Completion</STRONG> screen from the dropdown.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/19-1.png" /></LI><BR /> <LI>Add four Textbox components and one Dropdown component to the SAP Customer Data Cloud <STRONG>Registration Completion</STRONG> screen and map them to the following fields.<BR /> <UL><BR /> <LI>profile.address</LI><BR /> <LI>profile.city</LI><BR /> <LI>profile.state</LI><BR /> <LI>profile.zip</LI><BR /> <LI>profile.country</LI><BR /> </UL><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/20-5.png" /></P><BR /> </LI><BR /> </OL><BR /> <H2 id="toc-hId--241965692"><A id="user-content-steps-to-test-the-solution" class="anchor" href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdc-extension#steps-to-test-the-solution" aria-hidden="true" target="_blank" rel="nofollow noopener noreferrer"></A>Steps to test the solution</H2><BR /> <OL><BR /> <LI>Register an account in your SAP Customer Data Cloud site.<BR /> <BLOCKQUOTE><STRONG>Note:</STRONG>&nbsp;This can also be done by previewing the registration screen of the&nbsp;<STRONG>Default-RegistrationLogin</STRONG> screen-set in the&nbsp;<A href="https://console.gigya.com/" rel="nofollow noopener noreferrer" target="_blank">SAP Customer Data Cloud console</A>.</BLOCKQUOTE><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/21-6.png" /></P><BR /> </LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">The&nbsp;<STRONG>Registration Completion</STRONG> screen will be displayed. Enter an invalid address and click the&nbsp;<STRONG>Submit</STRONG>&nbsp;button. An error message will be returned.</P><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/22-5.png" /></P><BR /> </LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">Fix the address and click the&nbsp;<STRONG>Submit</STRONG>&nbsp;button. The form should be processed successfully.</P><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/23-3.png" /></P><BR /> </LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">View the details of the account that was registered in the&nbsp;<STRONG>Identity Access</STRONG> screen of the SAP Customer Data Cloud console. It can be observed that the address of the user has been cleansed.</P><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/24-6.png" /></P><BR /> </LI><BR /> </OL><BR /> <H2 id="toc-hId--438479197"><A id="user-content-troubleshooting-steps" class="anchor" href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdc-extension#troubleshooting-steps" aria-hidden="true" target="_blank" rel="nofollow noopener noreferrer"></A>Troubleshooting steps</H2><BR /> <H3 id="toc-hId--505909983"><A id="user-content-check-the-logs-of-your-kubernetes-pods" class="anchor" href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdc-extension#check-the-logs-of-your-kubernetes-pods" aria-hidden="true" target="_blank" rel="nofollow noopener noreferrer"></A>Check the logs of your Kubernetes pods</H3><BR /> To see the logs of a specific function, open the function in the Kyma console (Go&nbsp;to&nbsp;<STRONG>Workloads</STRONG> -&gt;&nbsp;<STRONG>Functions</STRONG> -&gt; <STRONG>cdc-extension</STRONG>) and you will see the logs in an expandable window at the bottom of the page.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/25-6.png" /></P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/26-3.png" /><BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/27-3.png" /><BR /> <P style="overflow: hidden;margin-bottom: 0px">Alternately, go to <STRONG>Workloads</STRONG> -&gt;&nbsp;<STRONG>Pods</STRONG> in the Kyma Console (within the <STRONG>cdc</STRONG> namespace) to see the list of all running pods. Then, click on the three dots to the right of the running pod of the cdc-extension function, and click on <STRONG>Show Logs</STRONG> to see the logs of the pod.</P><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/29-1.png" /></P><BR /> Or use the following kubectl command to get the list of pods running in the cdc namespace.<BR /> <BR /> <CODE>kubectl get pods -n cdc</CODE><BR /> <BR /> Then, to see the logs of any of the pods, use the following syntax:<BR /> <BR /> <CODE>kubectl logs &lt;pod-name&gt; -n &lt;namespace-name&gt;</CODE><BR /> <BR /> Example:<BR /> <BR /> <CODE>kubectl logs cdc-extension-65khj-58b6d69cd9-l7dgs -n cdc</CODE><BR /> <H2 id="toc-hId--831506207">Conclusion<A id="user-content-check-the-logs-of-your-kubernetes-pods" class="anchor" href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdc-extension#check-the-logs-of-your-kubernetes-pods" aria-hidden="true" target="_blank" rel="nofollow noopener noreferrer"></A></H2><BR /> This example demonstrates how to easily extend SAP Customer Data Cloud. Care should be taken, however, to ensure that the custom business logic executes as quickly as possible so as to provide a good user experience. In general, synchronous microservices should execute within about 50 ms to about 300 ms depending on the use case. It is to be noted that SAP Customer Data Cloud Extensions currently support a maximum latency of 10,000 ms. Here are a few things you could do that could help decrease the latency of your Kyma serverless function:<BR /> <UL><BR /> <LI>Minimize external API calls as much as possible.</LI><BR /> <LI>Parallelize external API calls that aren't dependent on each other.</LI><BR /> <LI>Use OData or GraphQL APIs if possible, to get only the required data when making external API calls. For example, use the <STRONG>$select</STRONG> parameter when using OData calls to select the fields to be returned.</LI><BR /> <LI>Simplify the logic of the Kyma serverless function and reduce the amount of custom code as much as possible.</LI><BR /> <LI>Cache the responses of repetitive external API calls having identical request payloads using fully managed caching solutions if possible. In this example, the SAP Customer Data Cloud Public Key, that hardly changes, has been saved using environment variables, and the API call to get the Public Key is only made if it has changed.</LI><BR /> <LI>Increase the replicas (horizontal scaling) or the memory and CPU resource usage of your Kyma serverless function (vertical scaling) for more resource intensive workloads or higher load, which can be easily done in the <STRONG>Resources</STRONG> tab of the Kyma serverless function (<STRONG>Workloads</STRONG> -&gt; <STRONG>Functions</STRONG> -&gt; <STRONG>cdc-extension</STRONG> -&gt; <STRONG>Resources</STRONG> -&gt; <STRONG>Runtime Profile</STRONG>). You can also utilize the auto-scaling feature of the Kyma Runtime by setting minimum and maximum replicas.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/30-1.png" /></LI><BR /> </UL><BR /> As a next step, you could try to extend this example to implement your own custom logic or use case.<BR /> <BR /> I'd like to request you to kindly provide your feedback or ask clarifying questions related to this post in the comment section below. Additionally, I'd like to invite you to submit any broader Kyma related questions in the <A href="https://answers.sap.com/tags/73554900100800003012" target="_blank" rel="noopener noreferrer">Q&amp;A area of the SAP BTP, Kyma runtime topic</A>.<BR /> <BR /> If the SAP BTP, Kyma runtime topic interests you, here are some other links that you may like:<BR /> <UL><BR /> <LI><A href="https://community.sap.com/topics/kyma" target="_blank">Kyma at SAP</A></LI><BR /> <LI><A href="https://developers.sap.com/tutorial-navigator.html?search=kyma" target="_blank" rel="noopener noreferrer">Kyma related SAP Tutorials for Developers</A></LI><BR /> </UL><BR /> Lastly, if you liked this post, I'd like to request you to kindly hit the like icon, leave a comment below or share this post. 2021-11-10T19:51:00+01:00 https://community.sap.com/t5/technology-blogs-by-sap/enrich-contact-data-on-sap-customer-data-platform-with-master-data-from-sap/ba-p/13510526 Enrich contact data on SAP Customer Data Platform with master data from SAP S/4HANA Cloud using a Kyma Serverless Function 2021-11-24T11:30:42+01:00 sissa https://community.sap.com/t5/user/viewprofilepage/user-id/560415 This example demonstrates how to set up a Kyma Serverless Function as an <STRONG>SAP Customer Data Platform</STRONG> pre-mapping <STRONG>Extension</STRONG> endpoint and to use it to enrich contact data extracted from an <STRONG>Amazon S3 Cloud Object Storage </STRONG>bucket with master data from <STRONG>SAP S/4HANA Cloud</STRONG> <A href="https://api.sap.com/api/API_BUSINESS_PARTNER/overview" target="_blank" rel="noopener noreferrer">Business Partner (A2X) microservice</A><STRONG>,</STRONG> before ingesting it to <STRONG>SAP Customer Data Platform (CDP)</STRONG>.<BR /> <BR /> An <STRONG>SAP Customer Data Platform </STRONG>pre-mapping <STRONG>Extension</STRONG> can be used to synchronously intercept the data ingestion flow of <STRONG>SAP Customer Data Platform events</STRONG> that ingest data from supported sources such as Commerce applications, Cloud Storage Solutions, CRM Solutions, Custom Web Applications and SAP Customer Data Cloud.<BR /> <BR /> In this example, email addresses and unique identifiers of contacts are ingested from an <STRONG>Amazon S3 Cloud Object Storage </STRONG>bucket into <STRONG>SAP Customer Data Platform</STRONG>. During the data ingestion process, the flow is intercepted by an <STRONG>Extension</STRONG> (the <STRONG>Kyma Serverless Function</STRONG>) that enriches the request with additional data from <STRONG>SAP S/4HANA Cloud </STRONG>such as the name, phone number and organizations of each of the contacts.<BR /> <BR /> The <STRONG>Kyma Serverless Function</STRONG> used in this example will work with other supported sources as well such as <STRONG>Google Cloud Storage</STRONG> or <STRONG>Microsoft Azure Blob</STRONG>. The only change required is to connect a different source to <STRONG>SAP Customer Data Platform</STRONG> instead of an <STRONG>Amazon S3 Cloud Object Storage </STRONG>bucket that is demonstrated here.<BR /> <BLOCKQUOTE>Note: All the functionality presented here are subject to change and may be changed by SAP at any time for any reason without notice.</BLOCKQUOTE><BR /> <H2 id="toc_1" id="toc-hId-958296615">Scenario</H2><BR /> This example includes a <STRONG>Kyma Serverless Function</STRONG>, <EM>cdp-extension</EM>, that is exposed as an <STRONG>SAP Customer Data Platform Extension</STRONG> endpoint, and demonstrates how to:<BR /> <UL><BR /> <LI>Create an <A href="https://help.sap.com/viewer/8438f051ded544d2ba1303e67fc5ff86/PROD/en-US/67ac7304cead44a9a6b762f583fe1fe1.html" target="_blank" rel="noopener noreferrer">SAP Customer Data Platform Extension</A> endpoint using a <A href="https://kyma-project.io/docs/kyma/latest/01-overview/main-areas/serverless/svls-01-overview/#documentation-content" target="_blank" rel="nofollow noopener noreferrer">Kyma Serverless Function</A></LI><BR /> <LI>Deploy a Kyma Serverless Function and an API Rule with <STRONG>JWT Access strategy</STRONG> using the <A href="https://kubernetes.io/docs/reference/kubectl/overview/" target="_blank" rel="nofollow noopener noreferrer">Kubernetes command-line tool</A></LI><BR /> <LI>Alternately, deploy a Kyma Serverless Function and an API Rule with <STRONG>JWT Access strategy</STRONG> using the Kyma Console User Interface</LI><BR /> <LI>Explore <A href="https://api.sap.com/" target="_blank" rel="noopener noreferrer">api.sap.com</A> and try out REST API calls using a sandbox environment</LI><BR /> <LI>Use the <A href="https://www.sap.com/canada/products/data-quality-management.html" target="_blank" rel="noopener noreferrer">SAP S/4HANA Cloud Business Partner (A2X) microservice</A> for Business Partner, Supplier, and Customer master data in SAP S/4HANA Cloud system</LI><BR /> </UL><BR /> <H4 id="toc_2" id="toc-hId-1019948548">Solution Architecture</H4><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/cdp-extension-diagram.png" /></P><BR /> <BR /> <H4 id="toc_3" id="toc-hId-823435043">Sequence Diagram</H4><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/cdp-extension-seq.jpg" /></P><BR /> <BR /> <H2 id="toc_4" id="toc-hId-368756100">Suggested introductory readings</H2><BR /> <UL><BR /> <LI><A href="https://help.sap.com/viewer/8438f051ded544d2ba1303e67fc5ff86/PROD/en-US/67ac7304cead44a9a6b762f583fe1fe1.html" target="_blank" rel="noopener noreferrer">What is an SAP Customer Data Platform Extension?</A></LI><BR /> <LI><A href="https://help.sap.com/viewer/8438f051ded544d2ba1303e67fc5ff86/PROD/en-US/f4b17b0302e248da805fd9e4530934e5.html" target="_blank" rel="noopener noreferrer">SAP Customer Data Platform Sources and Destinations</A></LI><BR /> <LI><A href="https://help.sap.com/viewer/8438f051ded544d2ba1303e67fc5ff86/PROD/en-US/8722f8e5157b4cf9be5f0177906a0351.html" target="_blank" rel="noopener noreferrer">Using Kyma with SAP Customer Data Platform Extensions</A></LI><BR /> <LI><A href="https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/468c2f3c3ca24c2c8497ef9f83154c44.html" target="_blank" rel="noopener noreferrer">What is Kyma Environment?</A></LI><BR /> <LI><A href="https://kyma-project.io/docs/kyma/latest/01-overview/main-areas/serverless/svls-01-overview/#documentation-content" target="_blank" rel="nofollow noopener noreferrer">What is a Kyma Serverless Function?</A></LI><BR /> <LI><A href="https://kyma-project.io/docs/kyma/latest/05-technical-reference/00-custom-resources/apix-01-apirule/#documentation-content" target="_blank" rel="nofollow noopener noreferrer">What is a Kyma API Rule?</A></LI><BR /> <LI><A href="https://kyma-project.io/" target="_blank" rel="nofollow noopener noreferrer">Project Kyma Documentation</A></LI><BR /> <LI><A href="https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/b8e16869e64a4abe93cc194aa6fdacf5.html" target="_blank" rel="noopener noreferrer">Administration and Operations in the Kyma Environment</A></LI><BR /> </UL><BR /> <H2 id="toc_5" id="toc-hId-172242595">Pre-requisites</H2><BR /> <UL><BR /> <LI>Provision <A href="https://www.sap.com/canada/products/customer-data-platform.html" target="_blank" rel="noopener noreferrer">SAP Customer Data Platform</A>.</LI><BR /> <LI><A href="https://developers.sap.com/tutorials/hcp-create-trial-account.html" target="_blank" rel="noopener noreferrer">Get a Free Account on SAP BTP Trial</A>.</LI><BR /> <LI><A href="https://developers.sap.com/tutorials/cp-kyma-getting-started.html" target="_blank" rel="noopener noreferrer">Enable SAP BTP, Kyma Runtime</A>.</LI><BR /> <LI><A href="https://developers.sap.com/tutorials/cp-kyma-download-cli.html#d81e7789-ced4-4df6-b4a0-132d8c637077" target="_blank" rel="noopener noreferrer">Download and install the Kubernetes Command Line Tool</A>.</LI><BR /> <LI><A href="https://developers.sap.com/tutorials/cp-kyma-download-cli.html#4709f3b9-b9bc-45f1-89c1-cd6f097c55f5" target="_blank" rel="noopener noreferrer">Test the kubectl installation</A>.</LI><BR /> <LI><A href="https://developers.sap.com/tutorials/cp-kyma-download-cli.html#2ef10816-b759-4080-a8ec-eadbc3317ebd" target="_blank" rel="noopener noreferrer">Download the Kyma Runtime kubeconfig</A>.</LI><BR /> <LI>Create your <A href="https://api.sap.com/" target="_blank" rel="noopener noreferrer">api.sap.com</A> account.</LI><BR /> <LI>Get an <A href="https://aws.amazon.com/free/" target="_blank" rel="nofollow noopener noreferrer">AWS Free Tier account</A> or use your existing AWS account to <A href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/create-bucket-overview.html" target="_blank" rel="nofollow noopener noreferrer">create an Amazon S3 bucket</A>.</LI><BR /> </UL><BR /> <H2 id="toc_6" id="toc-hId--24270910">Deployment steps</H2><BR /> <H3 id="toc_7" id="toc-hId--91701696">Deploy the Kyma Serverless Function</H3><BR /> <OL><BR /> <LI>Go to the <A href="https://github.com/SAP-samples/kyma-runtime-extension-samples" target="_blank" rel="nofollow noopener noreferrer">kyma-runtime-extension-samples</A> repository and clone it. This repository contains a collection of Kyma sample applications including this example (in the <STRONG>cdp-extension</STRONG> subfolder). Download the code by choosing the green Code button, and then choosing one of the options to download the code locally. Alternately, you can also run the following command using your command-line interface within your desired folder location:<BR /> <UL><BR /> <LI style="list-style-type: none"><BR /> <UL><BR /> <LI><CODE>git clone <A href="https://github.com/SAP-samples/kyma-runtime-extension-samples" target="test_blank" rel="nofollow noopener noreferrer">https://github.com/SAP-samples/kyma-runtime-extension-samples</A></CODE></LI><BR /> </UL><BR /> </LI><BR /> </UL><BR /> <BLOCKQUOTE><STRONG>Note:</STRONG> The source code of this example is in the <A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdp-extension" target="_blank" rel="nofollow noopener noreferrer">cdp-extension</A> subfolder of this repository.</BLOCKQUOTE><BR /> </LI><BR /> <LI>Update the values of the following environment variable in the <STRONG>./kyma-runtime-extension-samples/cdp-extension/k8s/function.yaml</STRONG> file: <CODE>SAP_API_HUB_API_KEY</CODE><BR /> <UL><BR /> <LI><CODE>SAP_API_HUB_API_KEY</CODE> - This is the API Key of SAP API Business Hub. Login to <A href="https://api.sap.com/" target="_blank" rel="noopener noreferrer">api.sap.com</A>. Then, go to your profile settings and click on <STRONG>Show API Key</STRONG> to get the value for this variable.</LI><BR /> </UL><BR /> </LI><BR /> <LI>Make the following changes in the <STRONG>./kyma-runtime-extension-samples/cdp-extension/k8s/api-rule.yaml</STRONG> file:<BR /> <UL><BR /> <LI>Replace <CODE>DATA_CENTER</CODE> with the Data Center that is closest to you. As an example, the US Data Center is <CODE>us1.gigya.com</CODE>. For other locations, check <A href="https://help.sap.com/viewer/8b8d6fffe113457094a17701f63e3d6a/GIGYA/en-US/41573b6370b21014bbc5a10ce4041860.html" target="_blank" rel="noopener noreferrer">Finding your Data Center</A>.</LI><BR /> <LI>Replace <CODE>BUSINESS_UNIT_ID</CODE> with your Business Unit ID, which can be located in your SAP Customer Data Platform console URL after <CODE>/business-unit/</CODE>.</LI><BR /> </UL><BR /> </LI><BR /> <LI>Create a Kubernetes namespace with the name <CODE>cdp</CODE>.<BR /> <UL><BR /> <LI><CODE>kubectl create namespace cdp</CODE></LI><BR /> </UL><BR /> <BLOCKQUOTE><STRONG>Note:</STRONG> As a prerequisite, please follow the steps listed in the following tutorial: <A href="https://developers.sap.com/tutorials/cp-kyma-download-cli.html#2ef10816-b759-4080-a8ec-eadbc3317ebd" target="_blank" rel="noopener noreferrer">Download the Kyma Runtime kubeconfig</A></BLOCKQUOTE><BR /> <STRONG>Alternately, use the Kyma Console User Interface to create a new namespace</STRONG><BR /> <UL><BR /> <LI>Open the Kyma console and click on <STRONG>Add new namespace</STRONG>. Enter its name as <STRONG>cdp</STRONG> and click the <STRONG>Create</STRONG> button.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/19-7.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/20-10.png" /></LI><BR /> </UL><BR /> </LI><BR /> <LI>Create/update Kubernetes resources of the <STRONG>cdp-extension serverless function</STRONG>.<BR /> <UL><BR /> <LI><CODE>kubectl apply -f ./kyma-runtime-extension-samples/cdp-extension/k8s/function.yaml</CODE></LI><BR /> <LI><CODE>kubectl apply -f ./kyma-runtime-extension-samples/cdp-extension/k8s/api-rule.yaml</CODE></LI><BR /> </UL><BR /> <BLOCKQUOTE><STRONG>Note:</STRONG> As a prerequisite, please follow the steps listed in the following tutorial: <A href="https://developers.sap.com/tutorials/cp-kyma-download-cli.html#2ef10816-b759-4080-a8ec-eadbc3317ebd" target="_blank" rel="noopener noreferrer">Download the Kyma Runtime kubeconfig</A><BR /> <BR /> The resources are represented as declarative YAML objects. Applying the resources will perform the following steps:<BR /> <UL><BR /> <LI>Deploy the Kyma serverless function</LI><BR /> <LI>Expose the serverless function using a Kyma API Rule with <STRONG>JWT Access strategy</STRONG> that will serve as the SAP Customer Data Platform Extension endpoint</LI><BR /> </UL><BR /> </BLOCKQUOTE><BR /> <STRONG>Alternately, deploy the Kyma serverless function and API Rule using the Kyma Console User Interface:</STRONG><BR /> <UL><BR /> <LI>Open the Kyma console and select the <STRONG>cdp</STRONG> namespace.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/21-8.png" /></LI><BR /> <LI>Click on <STRONG>Workloads</STRONG>. Then, click on <STRONG>Deploy new workload</STRONG> and select <STRONG>Upload YAML</STRONG>.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/22-9.png" /></LI><BR /> <LI>Then, click on <STRONG>Browse</STRONG> to select the following YAML file, and click on <STRONG>Deploy</STRONG>: <STRONG><STRONG>./kyma-runtime-extension-samples/cdp-extension/k8s/function.yaml</STRONG></STRONG><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/23-5.png" /></LI><BR /> <LI>Repeat the above steps and select the following YAML file. Then, click on <STRONG>Deploy</STRONG>: <STRONG><STRONG>./kyma-runtime-extension-samples/cdp-extension/k8s/api-rule.yaml</STRONG></STRONG><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/24-7.png" /></LI><BR /> </UL><BR /> </LI><BR /> <LI>Go to the Kyma Console --&gt; <STRONG>cdp</STRONG> namespace --&gt; <STRONG>Discovery &amp; Network</STRONG> --&gt; <STRONG>API Rules</STRONG>. Copy the <STRONG>host</STRONG> URL of the cdp-extension API Rule and use it in the last step below.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/25-7.png" /></LI><BR /> </OL><BR /> <H3 id="toc_8" id="toc-hId--288215201">Update the Schema in the SAP Customer Data Platform console</H3><BR /> <OL><BR /> <LI>Login to the <A href="https://universe.cdp.gigya.com/" target="_blank" rel="nofollow noopener noreferrer">SAP Customer Data Platform console</A>.</LI><BR /> <LI>Go to Dashboard --&gt; Connect --&gt; Customer Schema<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/26-4.png" /></LI><BR /> <LI>Select Profile Entity. Then, click on JSON and enter the following JSON:<BR /> <BLOCKQUOTE>Note: If you already have some custom fields, then only add the <STRONG>phoneNumberDetails</STRONG> and <STRONG>contactPersonBPID</STRONG> fields listed in the JSON below.</BLOCKQUOTE><BR /> <DIV><BR /> <PRE class="language-javascript"><CODE>{ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "firstName": { "type": "string" }, "lastName": { "type": "string" }, "primaryEmail": { "type": "string" }, "primaryPhone": { "type": "string" }, "masterDataId": { "type": "array", "items": { "type": "string" } }, "crmId": { "type": "array", "items": { "type": "string" } }, "ciamId": { "type": "array", "items": { "type": "string" } }, "emails": { "type": "array", "items": { "type": "string" } }, "phones": { "scopes": [], "type": "array", "items": { "type": "string" } }, "cookieId": { "type": "array", "additionalItems": true, "items": { "type": "string" } }, "searchHistory": { "type": "array", "additionalItems": true, "items": { "type": "string" } }, "phoneNumberDetails": { "type": "object", "additionalProperties": false, "properties": { "phoneNumber": { "type": "string" }, "phoneNumberExtension": { "type": "string" }, "phoneNumberType": { "type": "string" } } }, "contactPersonBPID": { "type": "string" } } }</CODE></PRE><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/27-5.png" /></P><BR /> <BR /> </DIV></LI><BR /> <LI>Select Activities --&gt; Click on the icon with three lines --&gt; Create New Activity. Enter its name as "groups" and click Save. Then, click on JSON and enter the following JSON:<BR /> <DIV><BR /> <PRE class="language-javascript"><CODE>{ "type": "array", "items": { "type": "object", "properties": { "customerBPID": { "type": "string" }, "relationshipNumber": { "type": "string" }, "relationshipCategory": { "type": "string" } } } }</CODE></PRE><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/28-5.png" /></P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/29-2.png" /><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/30-3.png" /></P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/31-1.png" /><BR /> <BR /> </DIV></LI><BR /> </OL><BR /> <H3 id="toc_9" id="toc-hId--484728706">Add a source and create a new event</H3><BR /> <OL><BR /> <LI>Login to the <A href="https://universe.cdp.gigya.com/" target="_blank" rel="nofollow noopener noreferrer">SAP Customer Data Platform console</A>.</LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">Go to Dashboard --&gt; Connect --&gt; Sources --&gt; Connect Application --&gt; Select connect within AWS S3 (under Cloud Storage).</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/01-1.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/02-1.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/03-1.png" /><BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/04-2.png" /></LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">Enter the connection details and your AWS credentials as depicted in the screenshots below.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/05-2.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/06-2.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/07-3.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/08.png" /></LI><BR /> <LI>Once the <STRONG>aws-cdp-integration</STRONG> source has been created, click to select it.</LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">Then, click on <STRONG>Create New Event</STRONG>.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/09.png" /></LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">In the <STRONG>Settings</STRONG> screen, enter the settings as per the screenshots below and click <STRONG>Next</STRONG>.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/10-14.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/11-15.png" /></LI><BR /> <LI>In the <STRONG>Model</STRONG> screen, click on JSON and enter the following JSON:<BR /> <DIV><BR /> <PRE class="language-javascript"><CODE>{ "type": "object", "additionalProperties": true, "properties": { "profile": { "type": "object", "additionalProperties": true, "properties": { "UID": { "type": "string" }, "email": { "type": "string" }, "firstName": { "type": "string" }, "lastName": { "type": "string" } } }, "data": { "type": "object", "additionalProperties": true, "properties": { "phoneNumber": { "type": "string" }, "phoneNumberExtension": { "type": "string" }, "internationalPhoneNumber": { "type": "string" }, "phoneNumberType": { "type": "string" }, "contactPersonBPID": { "type": "string" }, "groups": { "type": "array", "additionalItems": true, "items": { "type": "object", "additionalProperties": true, "properties": { "customerBPID": { "type": "string" }, "relationshipNumber": { "type": "string" }, "relationshipCategory": { "type": "string" } } } } } } } }</CODE></PRE><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/12-14.png" /></P><BR /> <BR /> </DIV></LI><BR /> <LI>Perform the following mappings in the <STRONG>Mapping</STRONG> screen:<BR /> <TABLE><BR /> <THEAD><BR /> <TR><BR /> <TH>EVENT SCHEMA</TH><BR /> <TH>CUSTOMER SCHEMA</TH><BR /> </TR><BR /> </THEAD><BR /> <TBODY><BR /> <TR><BR /> <TD>profile.UID</TD><BR /> <TD>PROFILE.ciamId</TD><BR /> </TR><BR /> <TR><BR /> <TD>profile.email</TD><BR /> <TD>PROFILE.primaryEmail</TD><BR /> </TR><BR /> <TR><BR /> <TD>profile.firstName</TD><BR /> <TD>PROFILE.firstName</TD><BR /> </TR><BR /> <TR><BR /> <TD>profile.lastName</TD><BR /> <TD>PROFILE.lastName</TD><BR /> </TR><BR /> <TR><BR /> <TD>data.phoneNumber</TD><BR /> <TD>PROFILE.phoneNumberDetails.phoneNumber</TD><BR /> </TR><BR /> <TR><BR /> <TD>data.phoneNumberExtension</TD><BR /> <TD>PROFILE.phoneNumberDetails.phoneNumberExtension</TD><BR /> </TR><BR /> <TR><BR /> <TD>data.internationalPhoneNumber</TD><BR /> <TD>PROFILE.primaryPhone</TD><BR /> </TR><BR /> <TR><BR /> <TD>data.phoneNumberType</TD><BR /> <TD>PROFILE.phoneNumberDetails.phoneNumberType</TD><BR /> </TR><BR /> <TR><BR /> <TD>data.contactPersonBPID</TD><BR /> <TD>PROFILE.contactPersonBPID</TD><BR /> </TR><BR /> <TR><BR /> <TD>data.groups</TD><BR /> <TD>GROUPS</TD><BR /> </TR><BR /> </TBODY><BR /> </TABLE><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/13-9.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/14-10.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/15-8.png" /></LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">Click Next. Finally, in the Scheduled Polling screen, click on <STRONG>Extensions</STRONG>.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/16-8.png" /></LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">Under Pre-mapping, enter the name and URL of the extension, which is the <STRONG>host</STRONG> URL of the cdp-extension API Rule. Set the <STRONG>Timeout</STRONG> to 10 seconds, select <STRONG>Stop</STRONG> as the <STRONG>Failure Policy</STRONG>.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/17-7.png" /></LI><BR /> <LI>Click <STRONG>Save</STRONG> and <STRONG>Done</STRONG>.</LI><BR /> </OL><BR /> <H2 id="toc_11" id="toc-hId--810324930">Test the solution</H2><BR /> <OL><BR /> <LI><STRONG>Upload the sample input file to your AWS S3 bucket:</STRONG> Upload the <CODE>input_20211119_001.json</CODE> file (located in the <A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdp-extension/input" target="_blank" rel="nofollow noopener noreferrer"><CODE>./kyma-runtime-extension-samples/cdp-extension/input</CODE></A> folder of the <A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdp-extension" target="_blank" rel="nofollow noopener noreferrer"><STRONG>cdp-extension</STRONG></A>&nbsp;repo) to the <STRONG>input </STRONG>folder of your AWS S3 bucket.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/32-1.png" /><BR /> <BLOCKQUOTE>Note: The <CODE>input_20211119_001.json</CODE> file contains mock email addresses that are currently used by the <A href="https://www.sap.com/canada/products/data-quality-management.html" target="_blank" rel="noopener noreferrer">SAP S/4HANA Cloud Business Partner (A2X) microservice</A> for Business Partner, Supplier, and Customer master data in SAP S/4HANA Cloud system.</BLOCKQUOTE><BR /> </LI><BR /> <LI>Login to the <A href="https://universe.cdp.gigya.com/" target="_blank" rel="nofollow noopener noreferrer">SAP Customer Data Platform console</A>.</LI><BR /> <LI><BR /> <P style="overflow: hidden;margin-bottom: 0px">Go to Dashboard --&gt; Connect --&gt; Sources --&gt; aws-cdp-integration --&gt; Load users from AWS S3 --&gt; Ingest Now.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/18-6.png" /></LI><BR /> <LI>If SAP API Hub is up and running and your Extension has been setup correctly, then the data is enriched during ingestion with additional data from SAP S/4HANA Cloud. To verify this, go to Customers --&gt; Search. Then, check to confirm that the contact data has been enriched and ingested as expected.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/36-1.png" /><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/33-1.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/34-1.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/35-2.png" /></LI><BR /> </OL><BR /> <H2 id="toc_12" id="toc-hId--659584078">Troubleshooting steps</H2><BR /> <H3 id="toc_13" id="toc-hId--1149500590">Check the logs of your Kubernetes pods</H3><BR /> <P style="overflow: hidden;margin-bottom: 0px">To see the logs of a specific function, open the function in the Kyma console (Go to <STRONG>Workloads</STRONG> &gt; <STRONG>Functions</STRONG> &gt; <STRONG>cdp-extension</STRONG>) and you will see the logs in an expandable window at the bottom of the page.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/37-2.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/38-1.png" /><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/39-1.png" /><BR /> <P style="overflow: hidden;margin-bottom: 0px">Alternately, go to <STRONG>Workloads</STRONG> &gt; <STRONG>Pods</STRONG> in the Kyma Console (within the cdp namespace) to see the list of all running pods. Then, click on the three dots to the right of the running pod of the cdp-extension function, and click on Show Logs to see the logs of the pod.</P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2021/11/40.png" /><BR /> <BR /> Or use the following kubectl command to get the list of pods running in the cdp namespace.<BR /> <BR /> <CODE>kubectl get pods -n cdp</CODE><BR /> <BR /> Then, to see the logs of any of the pods, use the following syntax:<BR /> <BR /> <CODE>kubectl logs &lt;pod-name&gt; -n &lt;namespace-name&gt;</CODE><BR /> <BR /> Example:<BR /> <BR /> <CODE>kubectl logs cdp-extension-jqckp-57787d8cd4-d54nw -n cdp</CODE><BR /> <H2 id="toc_14" id="toc-hId--1052611088">Conclusion</H2><BR /> As you’ve seen, this example demonstrates how to enrich data being ingested to SAP Customer Data Platform using a Kyma Serverless Function that makes external API calls to SAP S/4HANA Cloud Business Partner (A2X) microservice.<BR /> <BR /> The external API calls get specific additional information about the contacts, which is also ingested. You can also extend this example to validate specific information about the contacts from the SAP S/4HANA Cloud Business Partner (A2X) microservice. For example, you could verify the postal code and city of contacts.<BR /> <BR /> As a next step, you could extend this example to support your use case and you could also try using another source instead such as Google Cloud Storage.<BR /> <BR /> Since this is a synchronous flow, it is important to ensure that the extension executes as fast as possible. In this example, parallel API calls have been made to the SAP S/4HANA Cloud Business Partner (A2X) microservice to save processing time and to reduce the latency.<BR /> <BR /> I’d like to request you to kindly provide your feedback or ask clarifying questions related to this post in the comment section below. Additionally, I’d like to invite you to submit any broader Kyma related questions in the <A href="https://answers.sap.com/tags/73554900100800003012" target="_blank" rel="noopener noreferrer">Q&amp;A area of the SAP BTP, Kyma runtime topic</A>.<BR /> <BR /> If the SAP BTP, Kyma runtime topic interests you, here are some other links that you may like:<BR /> <UL><BR /> <LI><A href="https://community.sap.com/topics/kyma" target="_blank">Kyma at SAP</A></LI><BR /> <LI><A href="https://developers.sap.com/tutorial-navigator.html?search=kyma" target="_blank" rel="noopener noreferrer">Kyma related SAP Tutorials for Developers</A></LI><BR /> </UL><BR /> Lastly, if you liked this post, I’d like to request you to kindly hit the like icon, leave a comment below or share this post. 2021-11-24T11:30:42+01:00 https://community.sap.com/t5/technology-blogs-by-sap/going-jamstack-with-kyma-runtime-building-a-high-performance-web-app/ba-p/13532938 Going Jamstack with Kyma Runtime & building a high-performance web app 2022-02-24T03:28:46+01:00 sissa https://community.sap.com/t5/user/viewprofilepage/user-id/560415 Some common goals of web development projects are to build fast, secure, scalable and high-performance websites and apps. At the same time, there's also a need for delivering value faster.<BR /> <BR /> Thankfully, these days, there are many tools and technologies readily available to help us.<BR /> <BR /> This post demonstrates an example that uses the <STRONG>following state-of-the-art technologies</STRONG> to help achieve these goals and is inspired by the <A href="https://jamstack.org/what-is-jamstack/" target="_blank" rel="nofollow noopener noreferrer">Jamstack architecture</A> — <EM>“the modern way to build Websites and Apps that delivers better performance”<SUP>[1]</SUP></EM>.<BR /> <UL><BR /> <LI><STRONG>Next.js</STRONG> — <EM>"gives you the best developer experience with all the features you need for production"<SUP>[2]</SUP></EM></LI><BR /> <LI><STRONG>Chakra UI</STRONG> — <EM>"gives you the building blocks you need to build your React applications"<SUP>[3]</SUP></EM>with<EM> "less code"<SUP>[3]</SUP></EM>and<EM> "more speed"<SUP>[3]</SUP></EM></LI><BR /> <LI><STRONG>Go Programming Language</STRONG> — language of choice for backend microservices</LI><BR /> <LI><STRONG>Gin</STRONG> - an HTTP web framework written in Go that <EM>"features a Martini-like API with much better performance -- up to 40 times faster"<SUP>[4]</SUP></EM></LI><BR /> <LI><STRONG>SAP HANA Cloud</STRONG> — a <EM>"high-performance in-memory database"<SUP>[5]</SUP></EM></LI><BR /> <LI><STRONG>SAP Event Mesh</STRONG> — <EM>"a fully managed cloud service that allows applications to communicate through asynchronous events"<SUP>[6]</SUP></EM></LI><BR /> <LI><STRONG>Kyma</STRONG>&nbsp;— <EM>"a cloud-native application runtime that combines the power of Kubernetes with a set of best-in-class tools and open-source components"<SUP>[7]</SUP></EM></LI><BR /> <LI><STRONG>Kubernetes</STRONG> — <EM>"an open source container orchestration engine for automating deployment, scaling, and management of containerized applications"<SUP>[8]</SUP></EM></LI><BR /> <LI><STRONG>Cloudflare CDN</STRONG> — <EM>"stops malicious traffic"<SUP>[9]</SUP></EM>, <EM>"optimizes the delivery of website resources"<SUP>[9]</SUP></EM>, <EM>"routes visitor requests to the nearest Cloudflare data center"<SUP>[9]</SUP></EM>&nbsp;and <EM>"provides security"<SUP>[9]</SUP></EM></LI><BR /> </UL><BR /> <H2 id="toc-hId-960207116">Next.js app with Kyma Eventing &amp; Go backend connected to SAP HANA Cloud database</H2><BR /> This example illustrates the <STRONG>following major components of SAP BTP, Kyma Runtime</STRONG> and its goal is to help you to get familiarized with some of the <STRONG>key features of SAP BTP, Kyma Runtime</STRONG> and to help you use your learnings in your own experiments using a <A href="https://developers.sap.com/tutorials/hcp-create-trial-account.html" target="_blank" rel="noopener noreferrer">free SAP BTP Trial account</A>.<BR /> <UL><BR /> <LI><STRONG>Containerized Microservice exposed via an API Rule — a Next.js app used as a client-side web app</STRONG></LI><BR /> <LI><STRONG>Go Containerized Microservice — a Go app used as a backing service</STRONG></LI><BR /> <LI><STRONG>Node.js Serverless Function</STRONG></LI><BR /> <LI><STRONG>Kyma Eventing using either <A href="https://nats.io/" target="_blank" rel="nofollow noopener noreferrer">NATS</A> </STRONG>(available with a free SAP BTP Trial account) <STRONG>or <A href="https://help.sap.com/viewer/product/SAP_EM/Cloud/en-US" target="_blank" rel="noopener noreferrer">SAP Event Mesh</A></STRONG> (requires a paid account)</LI><BR /> </UL><BR /> It is modelled as a simple conference registration system that stores user information in an <STRONG>SAP HANA Cloud database</STRONG> and sends a confirmation email to the user.<BR /> <BR /> Its front-end is powered by a <STRONG>Next.js app</STRONG> that is deployed as a <STRONG>microservice</STRONG> on <STRONG>Kyma Runtime</STRONG> with a <STRONG>client-side user interface</STRONG> and an <STRONG>API endpoint</STRONG>.<BR /> <BR /> Form submissions are sent via the API endpoint to <STRONG>Kyma Eventing</STRONG>, which can be either <A href="https://nats.io/" target="_blank" rel="nofollow noopener noreferrer"><STRONG>NATS</STRONG></A> by default, or optionally <A href="https://help.sap.com/viewer/product/SAP_EM/Cloud/en-US" target="_blank" rel="noopener noreferrer"><STRONG>SAP Event Mesh</STRONG></A>.<BR /> <BR /> A <STRONG>Serverless Function </STRONG>consumes events from the queue and sends a confirmation email to the user after saving the user's details to an <STRONG>SAP HANA Cloud database </STRONG>via a <STRONG>Go Microservice</STRONG> — a REST API Server microservice that is implemented in <A href="https://go.dev/" target="_blank" rel="nofollow noopener noreferrer"><STRONG>Go</STRONG></A> using <A href="https://github.com/gin-gonic/gin" target="_blank" rel="nofollow noopener noreferrer"><STRONG>Gin Web Framework</STRONG></A>.<BR /> <BR /> The <STRONG>Go Microservice</STRONG> is exposed as a Kubernetes service with <A href="https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types" target="_blank" rel="nofollow noopener noreferrer">ClusterIP ServiceType</A> (by default) and can be accessed via a <STRONG>hostname</STRONG> that is only accessible within the Kubernetes cluster, with the following format: <EM>&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local</EM><BR /> <BLOCKQUOTE><STRONG>Note:</STRONG> Mutual TLS (mTLS) is <A href="https://istio.io/latest/docs/tasks/security/authentication/authn-policy/#globally-enabling-istio-mutual-tls-in-strict-mode" target="_blank" rel="nofollow noopener noreferrer">enabled mesh-wide</A> by default in Kyma Runtime. To be more specific, the mTLS mode is set to STRICT in a <A href="https://istio.io/latest/docs/reference/config/security/peer_authentication/" target="_blank" rel="nofollow noopener noreferrer">PeerAuthentication</A> policy within the root istio-system namespace. As a result, all internal service to service communication within the Kubernetes cluster is encrypted by default using <A href="https://datatracker.ietf.org/doc/html/rfc8446" target="_blank" rel="nofollow noopener noreferrer">Transport Layer Security</A> (TLS).</BLOCKQUOTE><BR /> <H3 id="toc-hId-892776330"><STRONG>Solution Architecture</STRONG></H3><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/conference-registration-app-architecture-1.png" /></P><BR /> <BR /> <H3 id="toc-hId-696262825"><STRONG>Sequence Diagram</STRONG></H3><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/conference-registration-app-sequence.png" /></P><BR /> <BR /> <H3 id="toc-hId-499749320"><STRONG>Build and deployment steps</STRONG></H3><BR /> This web app can be deployed using a <A href="https://developers.sap.com/tutorials/hcp-create-trial-account.html" target="_blank" rel="noopener noreferrer">free SAP BTP Trial account</A> via the <STRONG>following steps</STRONG> that will also help you learn how the major components of <STRONG>Kyma Runtime</STRONG> work together.<BR /> <BLOCKQUOTE><STRONG>Note:</STRONG> Click the links below for more details.</BLOCKQUOTE><BR /> <UL><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/setup/pre-requisites.md" target="_blank" rel="nofollow noopener noreferrer">Pre-requisites</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/setup/step-1.md" target="_blank" rel="nofollow noopener noreferrer">Step 1 - Pre-requisite SendGrid setup steps</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/setup/step-2.md" target="_blank" rel="nofollow noopener noreferrer">Step 2 - Build &amp; deploy the Conference Registration app (Next.js app)</A> — this step includes the link to the source-code repository</LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/setup/step-3.md" target="_blank" rel="nofollow noopener noreferrer">Step 3 - Deploy the Event Consumer function</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/setup/step-4.md" target="_blank" rel="nofollow noopener noreferrer">Step 4 - Apply the Event Registration Subscription</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/setup/step-5.md" target="_blank" rel="nofollow noopener noreferrer">Step 5 - Create an instance of SAP HANA Cloud</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/setup/step-6.md" target="_blank" rel="nofollow noopener noreferrer">Step 6 - Deploy the Registrations REST API Server</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/setup/step-7.md" target="_blank" rel="nofollow noopener noreferrer">Step 7 - Connect your web app running on Kyma Runtime to a domain via Cloudflare</A> — optional step</LI><BR /> </UL><BR /> <H3 id="toc-hId-303235815"><STRONG>Verification steps</STRONG></H3><BR /> <UL><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/verification/step-1.md" target="_blank" rel="nofollow noopener noreferrer">Step 1 - Verify that all the resources of the app are running</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/verification/step-2.md" target="_blank" rel="nofollow noopener noreferrer">Step 2 - View the environment variables in the config map</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/verification/step-3.md" target="_blank" rel="nofollow noopener noreferrer">Step 3 - View the environment variables in the secret</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/verification/step-4.md" target="_blank" rel="nofollow noopener noreferrer">Step 4 - View the API Rule</A></LI><BR /> <LI><span class="lia-unicode-emoji" title=":balloon:">🎈</span> <span class="lia-unicode-emoji" title=":party_popper:">🎉</span> <A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/verification/step-5.md" target="_blank" rel="nofollow noopener noreferrer">Step 5 - Launch the app and Register to attend the Tech Conference via the app</A></LI><BR /> </UL><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/tech-conference-2022-app-screenshot.png" /></P><BR /> <BR /> <H3 id="toc-hId-106722310"><STRONG><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/verification/troubleshooting-steps.md" target="_blank" rel="nofollow noopener noreferrer">Troubleshooting steps</A></STRONG></H3><BR /> <H3 id="toc-hId--89791195"><STRONG>Using Cloudflare CDN</STRONG></H3><BR /> The DNS provider of the namespace needs be set as Cloudflare to use Cloudflare CDN.<BR /> <BR /> For more Information, refer to: <A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/nextjs-app-with-kyma-eventing/docs/setup/step-7.md" target="_blank" rel="nofollow noopener noreferrer">Step 7 - Connect your web app running on Kyma Runtime to a domain via Cloudflare</A>.<BR /> <H3 id="toc-hId--286304700"><STRONG>Superb performance of the user facing app</STRONG></H3><BR /> <H4 id="toc-hId--353735486"><STRONG>Without Cloudflare CDN</STRONG></H4><BR /> Even without Cloudflare CDN, <A href="https://pagespeed.web.dev/" target="_blank" rel="nofollow noopener noreferrer">Google's PageSpeed Insights</A> tool gave a <STRONG>Performance score of 100</STRONG> to the Next.js web app for Desktop.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/pagespeed-insights.png" /></P><BR /> <BR /> <H4 id="toc-hId--550248991"><STRONG>With Cloudflare CDN</STRONG></H4><BR /> With Cloudflare CDN, the <STRONG>time to interactive</STRONG> and <STRONG>total blocking time</STRONG> were reduced even further.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/pagespeed-insights-with-cdn.png" /></P><BR /> <BR /> <H3 id="toc-hId--951076584"><STRONG>Blazing fast performance of the backing service</STRONG></H3><BR /> Although this web app only uses the HTTP POST API call of the Go Microservice to save customer records to the SAP HANA Cloud database, the Go Microservice has all the CRUD operations (Create, read, update and delete) implemented in it for the sake of demonstration.<BR /> <BR /> The HTTP POST and PUT API calls of the Go Microservice had an almost similar response time. In order to not create too many records in the database during load testing, the HTTP PUT API calls were load tested, which make update requests and they&nbsp;had an <STRONG>average response time of 27.33 ms with 143.4 Hits per second</STRONG> when tested with <A href="https://www.blazemeter.com/" target="_blank" rel="nofollow noopener noreferrer">BlazeMeter</A>.<BR /> <BR /> That is, on average each request was executed within about <STRONG>1/36th of a second</STRONG>, which is blazing fast for a backing service — and that too with using a <A href="https://developers.sap.com/tutorials/hcp-create-trial-account.html" target="_blank" rel="noopener noreferrer">free SAP BTP Trial account</A>!<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/blazemeter-test-results.png" /></P><BR /> <BR /> <H3 id="toc-hId--1147590089"><STRONG>Conclusion</STRONG></H3><BR /> Implementing the <A href="https://jamstack.org/" target="_blank" rel="noopener nofollow noreferrer">Jamstack architecture</A> using the <STRONG>Kyma Runtime</STRONG> and an <STRONG>SAP HANA Cloud database</STRONG> results in extremely fast performance, which can be fine-tuned even further by either using caching services such as Redis or by horizontal as well as vertical scaling — key features of Kubernetes that can be easily configured. The added benefit of using the <STRONG>Kyma Runtime</STRONG> is the seamless connectivity provided by it to various SAP solutions as well as to services of other hyperscalers such as Google Cloud Platform, AWS &amp; Azure using <A href="https://kyma-project.io/docs/kyma/latest/01-overview/main-areas/service-management/smgt-01-overview#documentation-content" target="_blank" rel="nofollow noopener noreferrer">service operators</A> — a great topic that is covered in the following blog post: <A href="https://blogs.sap.com/2022/02/25/using-kubernetes-operators-with-kyma-2.0/" target="_blank" rel="noopener noreferrer">Using Kubernetes operators with SAP BTP Kyma runtime</A>.<BR /> <BR /> As a next step, you could try to customize this example or put together the different components of <STRONG>Kyma Runtime</STRONG> that were covered in this example and build a web app for another use case using the <A href="https://jamstack.org/" target="_blank" rel="noopener nofollow noreferrer">Jamstack architecture</A>.<BR /> <BR /> If you'd like to dive deeper into some of the key topics that were covered in this blog post, you may find the resources listed in the <STRONG>Further Readings</STRONG> section below interesting.<BR /> <BR /> Also, if you'd like to access the <STRONG>Go Microservice</STRONG> via a public URL from outside the Kubernetes cluster for testing it using an API client such as <A href="https://www.postman.com/downloads/" target="_blank" rel="nofollow noopener noreferrer">Postman</A>, then you'll need to create an API Rule and expose the registrations-rest-api service. To do that, select the conference-registration namespace in the Kyma console. Next, under <STRONG>Discovery and Network</STRONG>, select <STRONG>API Rules</STRONG>. Then, click on <STRONG>Create API Rule.</STRONG> When creating the API Rule, you could also select to set its access strategy to OAuth2 to secure it. If you do that, you'll also need to create an OAuth client to be used to access it. You can do that by selecting <STRONG>Configuration</STRONG> and then <STRONG>OAuth Clients</STRONG> from the left menu in the Kyma console.<BR /> <BR /> Kindly provide your feedback or feel free to ask clarifying questions related to this post in the comment section below. Additionally, I’d like to invite you to submit any broader Kyma related questions in the <A href="https://answers.sap.com/tags/73554900100800003012" target="_blank" rel="noopener noreferrer">Q&amp;A area of the SAP BTP, Kyma runtime topic</A>.<BR /> <BR /> If the <A href="https://blogs.sap.com/tags/73554900100800003012/" target="_blank" rel="noopener noreferrer">SAP BTP, Kyma runtime topic</A> interests you, here are some other links that you may like:<BR /> <UL><BR /> <LI><A href="https://community.sap.com/topics/kyma" target="_blank">Kyma at SAP</A></LI><BR /> <LI><A href="https://developers.sap.com/tutorial-navigator.html?search=kyma" target="_blank" rel="noopener noreferrer">Kyma related SAP Tutorials for Developers</A></LI><BR /> </UL><BR /> Lastly, if you liked this post, kindly hit the like icon, leave a comment below or share this post. Thank you!<BR /> <H3 id="toc-hId--1344103594"><STRONG>References</STRONG></H3><BR /> <OL><BR /> <LI><A href="https://jamstack.org/" target="_blank" rel="nofollow noopener noreferrer">Jamstack: For fast and secure sites</A></LI><BR /> <LI><A href="https://nextjs.org/" target="_blank" rel="nofollow noopener noreferrer">Next.js by Vercel</A></LI><BR /> <LI><A href="https://chakra-ui.com/" target="_blank" rel="nofollow noopener noreferrer">Chakra UI</A></LI><BR /> <LI><A href="https://github.com/gin-gonic/gin" target="_blank" rel="nofollow noopener noreferrer">Gin Web Framework - GitHub</A></LI><BR /> <LI><A href="https://www.sap.com/canada/products/hana.html" target="_blank" rel="noopener noreferrer">SAP HANA</A></LI><BR /> <LI><A href="https://help.sap.com/viewer/bf82e6b26456494cbdd197057c09979f/Cloud/en-US" target="_blank" rel="noopener noreferrer">What Is SAP Event Mesh?</A></LI><BR /> <LI><A href="https://kyma-project.io/" target="_blank" rel="nofollow noopener noreferrer">Kyma - An easy way to extend enterprise applications on Kubernetes</A></LI><BR /> <LI><A href="https://kubernetes.io/docs/home/" target="_blank" rel="nofollow noopener noreferrer">Kubernetes Documentation</A></LI><BR /> <LI><A href="https://developers.cloudflare.com/fundamentals/get-started/how-cloudflare-works" target="_blank" rel="nofollow noopener noreferrer">How Cloudflare works</A></LI><BR /> </OL><BR /> <H3 class="LC20lb MBeuO DKV0Md" id="toc-hId--1540617099"><STRONG>Further Readings</STRONG></H3><BR /> <UL><BR /> <LI><A href="https://learning.sap.com/learning-journey/developing-applications-running-on-sap-btp-using-sap-hana-cloud" target="_blank" rel="noopener noreferrer">SAP Learning Journey: Developing Applications Running on SAP BTP Using SAP HANA Cloud</A></LI><BR /> <LI><A href="https://open.sap.com/courses/hana8" target="_blank" rel="noopener noreferrer">Ride the SAP HANA Wave: Fundamentals and Insights into Cloud Databases</A></LI><BR /> <LI><A href="https://www.sap-press.com/introducing-sap-hana-cloud_5276/" target="_blank" rel="nofollow noopener noreferrer">Introducing SAP HANA Cloud By Raja Gupta, Denys van Kempen</A></LI><BR /> <LI><A href="https://www.packtpub.com/product/jumpstart-jamstack-development/9781800203495" target="_blank" rel="nofollow noopener noreferrer">Jumpstart Jamstack Development By Christopher Pecoraro, Vincenzo Gambino</A></LI><BR /> <LI><A href="https://www.packtpub.com/product/nextjs-quick-start-guide/9781788993661" target="_blank" rel="nofollow noopener noreferrer">Next.js Quick Start Guide By Kirill Konshin</A></LI><BR /> <LI><A href="https://www.packtpub.com/product/real-world-next-js/9781801073493" target="_blank" rel="nofollow noopener noreferrer">Real-World Next.js By Michele Riva</A></LI><BR /> <LI><A href="https://www.packtpub.com/product/software-architecture-patterns-for-serverless-systems/9781800207035" target="_blank" rel="nofollow noopener noreferrer">Software Architecture Patterns for Serverless Systems By John Gilbert</A></LI><BR /> <LI><A href="https://www.packtpub.com/product/hands-on-microservices-with-kubernetes/9781789805468" target="_blank" rel="nofollow noopener noreferrer">Hands-On Microservices with Kubernetes By Gigi Sayfan</A></LI><BR /> <LI><A href="https://www.packtpub.com/product/hands-on-software-architecture-with-golang/9781788622592" target="_blank" rel="nofollow noopener noreferrer">Hands-On Software Architecture with Golang By Jyotiswarup Raiturkar</A></LI><BR /> <LI><A href="https://www.packtpub.com/product/hands-on-restful-web-services-with-go-second-edition/9781838643577" target="_blank" rel="nofollow noopener noreferrer">Hands-On RESTful Web Services with Go - Second Edition By Naren Yellavula</A></LI><BR /> <LI><A href="https://www.packtpub.com/product/building-distributed-applications-in-gin/9781801074858" target="_blank" rel="nofollow noopener noreferrer">Building Distributed Applications in Gin By Mohamed Labouardy</A></LI><BR /> </UL> 2022-02-24T03:28:46+01:00 https://community.sap.com/t5/technology-blogs-by-sap/send-personalized-emails-with-sap-customer-data-cloud-webhooks-and-kyma/ba-p/13527872 Send personalized emails with SAP Customer Data Cloud webhooks and Kyma serverless functions 2022-06-28T20:41:33+02:00 sissa https://community.sap.com/t5/user/viewprofilepage/user-id/560415 <STRONG>SAP Customer Data Cloud webhooks</STRONG> send out asynchronous event notifications to custom notification URLs when specific events occur in SAP Customer Data Cloud flows such as <STRONG>login</STRONG>, <STRONG>registration</STRONG>, and <STRONG>account update</STRONG>.<BR /> <BR /> This post demonstrates an example in which event notifications for the <STRONG>subscription updated</STRONG> event are sent to a <STRONG>Kyma serverless function, </STRONG>which has a code snippet that implements some business logic to eventually send a personalized welcome email to users.<BR /> <BR /> Two Kyma serverless functions are used in this example and they work together as explained below.<BR /> <BR /> The <STRONG>first serverless function</STRONG> receives notification messages and has a code snippet to extract all the events from each message and post them to an SAP Event Mesh Queue.<BR /> <BR /> The <STRONG>second serverless function</STRONG> has a code snippet to consume the events from the SAP Event Mesh Queue and process them as follows:<BR /> <UL><BR /> <LI>First, it makes an <A href="https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/413560f270b21014bbc5a10ce4041860.html" target="_blank" rel="noopener noreferrer">accounts.getLiteToken REST API</A> call to get a regToken that is required for the next step</LI><BR /> <LI>Next, it makes an <A href="https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/cab69a86edae49e2be93fd51b78fc35b.html" target="_blank" rel="noopener noreferrer">accounts.getAccountInfo REST API</A> call to get the user's preferences</LI><BR /> <LI>Finally, it sends a personalized email to the user based on the user's preferences using SendGrid Email Delivery Service</LI><BR /> </UL><BR /> <BLOCKQUOTE>Note: All the functionality presented here are subject to change and may be changed by SAP at any time for any reason without notice.</BLOCKQUOTE><BR /> <H3 id="toc-hId-1088514426"><STRONG>Solution Architecture</STRONG></H3><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/06/cdc-webhook-architecture.png" /></P><BR /> <BR /> <H3 id="toc-hId-892000921"><STRONG>Sequence Diagram</STRONG></H3><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/06/cdc-webhook-sequence.jpeg" /></P><BR /> <BR /> <H3 id="toc-hId-695487416"><STRONG>Source code repository</STRONG></H3><BR /> <A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/cdc-webhook" target="_blank" rel="nofollow noopener noreferrer">Here's a link</A> to the source code of this example.<BR /> <H3 id="toc-hId-498973911"><STRONG>Build and deployment steps</STRONG></H3><BR /> This example can be setup using SAP Customer Data Cloud and a free SAP BTP Trial account via the following steps:<BR /> <BLOCKQUOTE>Note: Click the links below for more details.</BLOCKQUOTE><BR /> <UL><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/cdc-webhook/docs/setup/pre-requisites.md" target="_blank" rel="nofollow noopener noreferrer">Pre-requisites</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/cdc-webhook/docs/setup/step-1.md" target="_blank" rel="nofollow noopener noreferrer">Step 1 - Pre-requisite SendGrid setup steps</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/cdc-webhook/docs/setup/step-2.md" target="_blank" rel="nofollow noopener noreferrer">Step 2 - Create a lite registration screen in SAP Customer Data Cloud</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/cdc-webhook/docs/setup/step-3.md" target="_blank" rel="nofollow noopener noreferrer">Step 3 - Deploy a Memcached memory-caching service</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/cdc-webhook/docs/setup/step-4.md" target="_blank" rel="nofollow noopener noreferrer">Step 4 - Deploy the Webhook endpoint</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/cdc-webhook/docs/setup/step-5.md" target="_blank" rel="nofollow noopener noreferrer">Step 5 - Deploy the Event Consumer Serverless Function</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/cdc-webhook/docs/setup/step-6.md" target="_blank" rel="nofollow noopener noreferrer">Step 6 - Apply the Webhook Event Subscription</A></LI><BR /> </UL><BR /> <H3 id="toc-hId-302460406"><STRONG>Verification steps</STRONG></H3><BR /> <UL><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/cdc-webhook/docs/verification/step-1.md" target="_blank" rel="nofollow noopener noreferrer">Step 1 - Verify that all the resources of the app are running</A></LI><BR /> <LI><A href="https://github.com/SAP-samples/kyma-runtime-extension-samples/blob/main/cdc-webhook/docs/verification/step-2.md" target="_blank" rel="nofollow noopener noreferrer">Step 2 - Subscribe for a newsletter and receive a customized confirmation email</A></LI><BR /> </UL><BR /> <H3 id="toc-hId-105946901"><STRONG>Conclusion</STRONG></H3><BR /> This example shows how easy it is to extend SAP Customer Data Cloud using webhooks and SAP BTP, Kyma runtime. It also shows how to effortlessly and quickly create Kyma serverless functions using the Kyma console to add some simple code snippets without having to worry about the infrastructure details, which results in faster time to market as well as lower infrastructure total cost of ownership (TCO).<BR /> <BR /> As a next step, you could <A href="https://blogs.sap.com/2022/03/02/accessing-the-built-in-observability-tools-of-kyma-runtime/" target="_blank" rel="noopener noreferrer">explore the built-in observability tools of Kyma runtime</A> for monitoring the serverless functions. With reference to this example, you could also try to create other Kyma serverless functions for your own use cases and implement SAP Customer Data Cloud webhook endpoints for some other events such as the account logged in or account updated events.<BR /> <BR /> Kindly provide your feedback or feel free to ask clarifying questions related to this post in the comment section below. Additionally, I’d like to invite you to submit any broader Kyma related questions in the <A href="https://answers.sap.com/tags/73554900100800003012" target="_blank" rel="noopener noreferrer">Q&amp;A area of the SAP BTP, Kyma runtime topic</A>.<BR /> <BR /> If the <A href="https://blogs.sap.com/tags/73554900100800003012/" target="_blank" rel="noopener noreferrer">SAP BTP, Kyma runtime topic</A> interests you, here are some other links that you may like:<BR /> <UL><BR /> <LI><A href="https://community.sap.com/topics/kyma" target="_blank">Kyma at SAP</A></LI><BR /> <LI><A href="https://developers.sap.com/tutorial-navigator.html?search=kyma" target="_blank" rel="noopener noreferrer">Kyma related SAP Tutorials for Developers</A></LI><BR /> </UL><BR /> Lastly, if you liked this post, kindly hit the like icon, leave a comment below or share this post. Thank you!<BR /> <H3 class="LC20lb MBeuO DKV0Md" id="toc-hId--90566604"><STRONG>Further Readings</STRONG></H3><BR /> <UL><BR /> <LI><A href="https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/417f918270b21014bbc5a10ce4041860.html" target="_blank" rel="noopener noreferrer">SAP Customer Data Cloud webhooks</A></LI><BR /> <LI><A href="https://kyma-project.io/docs/kyma/latest/01-overview/main-areas/serverless/svls-01-overview#documentation-content" target="_blank" rel="nofollow noopener noreferrer">Kyma serverless functions</A></LI><BR /> <LI><A href="https://discovery-center.cloud.sap/missiondetail/3252/3281/" target="_blank" rel="nofollow noopener noreferrer">SAP Discovery Center mission - Getting Started with the SAP BTP, Kyma runtime</A></LI><BR /> <LI><A href="https://discovery-center.cloud.sap/serviceCatalog/event-mesh?region=all" target="_blank" rel="nofollow noopener noreferrer">SAP Discovery Center mission - Learn more about SAP Event Mesh</A></LI><BR /> </UL> 2022-06-28T20:41:33+02:00 https://community.sap.com/t5/technology-blogs-by-sap/sap-business-technology-platform-serverless-functions-hands-on-tutorial/ba-p/13567231 SAP Business Technology Platform - Serverless Functions | Hands-On Tutorial Videos 2023-02-10T14:22:24+01:00 dvankempen https://community.sap.com/t5/user/viewprofilepage/user-id/67 <TABLE style="height: 59px" width="612" bgcolor="#e7f5ff"><BR /> <TBODY><BR /> <TR><BR /> <TD><BR /> <BR /> A new playlist about SAP Business Technology Platform (BTP) serverless functions has been made available on our YouTube channel.&nbsp;Video tutorials by&nbsp;<SPAN class="mention-scrubbed">philip.mugglestone</SPAN>&nbsp;for the SAP HANA Academy and Partner Ecosystem Success.<BR /> <BR /> In this article you will find the&nbsp;videos embedded with references and additional information.<BR /> <BR /> A prerequisite, as covered in the first video of this series, is some familiarity with BTP. For the SAP BTP Onboarding article, visit<BR /> <UL><BR /> <LI><A href="https://blogs.sap.com/2022/04/29/sap-btp-developer-onboarding-2022-hands-on-video-tutorials/" target="_blank" rel="noopener noreferrer">SAP BTP Developer Onboarding | Hands-on Video Tutorials</A></LI><BR /> </UL><BR /> Anything to add? Leave a comment below.<BR /> <BR /> Useful? Give us a like and share on social media.<BR /> <BR /> Questions? Please use the <A href="https://answers.sap.com/questions/ask.html" target="_blank" rel="noopener noreferrer">community Q&amp;A</A>.<BR /> <BR /> Thanks!</TD><BR /> </TR><BR /> </TBODY><BR /> </TABLE><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2023/02/280646_GettyImages-153605568_super_low-1.jpg" height="429" width="612" /></P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2016/02/sapnwabline_885687.png" width="610" /><BR /> <H2 id="toc-hId-963119900"><SPAN style="color: #d68c00">Hands-On Video Tutorials</SPAN></H2><BR /> <H3 id="toc-hId-895689114">Serverless Functions</H3><BR /> In this series of hands-on tutorial videos we cover serverless functions in SAP Business Technology Platform (SAP BTP).&nbsp;Serverless functions allow you to build API- and event-based extensions which can be triggered on demand whilst reducing the implementation and operation effort of an application to the absolute minimum.&nbsp;SAP BTP, Kyma runtime provides a platform to run lightweight functions in a cost-efficient and scalable way using Node.js or Python.<BR /> <BR /> Following along in the patented zero-to-hero format, you will be ready to start developing business applications on the SAP Business Technology Platform with minimal effort and no time wasted.<BR /> <BR /> Video tutorials by&nbsp;<SPAN class="mention-scrubbed">philip.mugglestone</SPAN>&nbsp;for the SAP HANA Academy and Partner Ecosystem Success.<BR /> <H3 id="toc-hId-699175609">What You Will Learn</H3><BR /> Watching the complete series of eight videos takes about an hour. What you will learn is<BR /> <UL><BR /> <LI>The concepts and what you need to know to get started with serverless functions</LI><BR /> <LI>How to install and use the jump-start generator for serverless functions</LI><BR /> <LI>How to use the generator to create serverless function projects<BR /> <UL><BR /> <LI>using Python to interact with SAP HANA Cloud</LI><BR /> <LI>using Node.js and the SAP Cloud SDK to interact with different APIs</LI><BR /> <LI>providing enterprise security for the endpoints (authentication and authorization)</LI><BR /> <LI>subscribing to events</LI><BR /> <LI>managing source code using Git and GitHub</LI><BR /> <LI>incorporating an Application Router to facilitate interactive browser-based authentication</LI><BR /> </UL><BR /> </LI><BR /> <LI>How access a serverless function created using the jump-start generator from SAP Build Apps including authentication and authorization.</LI><BR /> </UL><BR /> <H3 id="toc-hId-502662104">YouTube Playlist</H3><BR /> To bookmark the playlist on YouTube, go to<BR /> <UL><BR /> <LI><A href="https://www.youtube.com/playlist?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3" target="_blank" rel="noopener noreferrer nofollow">SAP Business Technology Platform - Serverless Functions</A></LI><BR /> </UL><BR /> <H3 id="toc-hId-306148599"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2023/02/playlist.png" height="520" width="608" /></H3><BR /> <H3 id="toc-hId-109635094"></H3><BR /> <H3 id="toc-hId--86878411">Documentation</H3><BR /> For the references, visit<BR /> <UL><BR /> <LI><A href="https://kyma-project.io/docs/kyma/latest/01-overview/main-areas/serverless/svls-01-overview" target="_blank" rel="nofollow noopener noreferrer">Serverless - Docs | Kyma</A></LI><BR /> </UL><BR /> For the SAP BTP Onboarding series, go to<BR /> <UL><BR /> <LI><A href="https://blogs.sap.com/2022/04/29/sap-btp-developer-onboarding-2022-hands-on-video-tutorials/" target="_blank" rel="noopener noreferrer">SAP BTP Developer Onboarding | Hands-on Video Tutorials</A></LI><BR /> </UL><BR /> For the tutorials about the SAP BTP Kyma environment, see<BR /> <UL><BR /> <LI><A href="https://blogs.sap.com/2022/04/29/sap-btp-developer-onboarding-2022-hands-on-video-tutorials/#kyma" target="_blank" rel="noopener noreferrer">Kyma Environment | Tutorial Video </A></LI><BR /> </UL><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2023/02/docs.png" /></P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2016/02/sapnwabline_885687.png" width="610" /><BR /> <H2 id="toc-hId--412474635"><SPAN style="color: #d68c00">Getting Started</SPAN></H2><BR /> <H3 id="toc-hId--479905421">Video Tutorial</H3><BR /> In this hands-on video tutorial, Philip Mugglestone gets started with serverless functions in SAP BTP. After providing an introduction Philip covers prerequisites before showing how to install and use the jump-start generator for serverless functions.<BR /> <BR /> <A href="https://youtu.be/GOznweBGewU?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3" target="test_blank" rel="nofollow noopener noreferrer">https://youtu.be/GOznweBGewU?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3</A><BR /> <H3 id="toc-hId--676418926">Markers</H3><BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=GOznweBGewU&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=1&amp;t=13s" target="_blank" rel="nofollow noopener noreferrer">0:13</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Introduction </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=GOznweBGewU&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=1&amp;t=295s" target="_blank" rel="nofollow noopener noreferrer">4:55</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Prerequisites </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=GOznweBGewU&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=1&amp;t=394s" target="_blank" rel="nofollow noopener noreferrer">6:34</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Application Wizard </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=GOznweBGewU&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=1&amp;t=420s" target="_blank" rel="nofollow noopener noreferrer">7:00</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Jump-start Generator </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=GOznweBGewU&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=1&amp;t=597s" target="_blank" rel="nofollow noopener noreferrer">9:57</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Recap</SPAN><BR /> <H3 id="toc-hId--948163800">References</H3><BR /> For the prerequisites, visit<BR /> <UL><BR /> <LI><A href="https://blogs.sap.com/2022/04/29/sap-btp-developer-onboarding-2022-hands-on-video-tutorials/#kyma" target="_blank" rel="noopener noreferrer">Kyma Environment | Tutorial Video </A></LI><BR /> <LI><A href="https://blogs.sap.com/2022/12/16/sap-btp-developer-onboarding-sap-hana-cloud/" target="_blank" rel="noopener noreferrer">SAP HANA Cloud | Tutorial Video</A></LI><BR /> <LI><A href="https://blogs.sap.com/2022/12/16/sap-btp-developer-onboarding-sap-build-apps/" target="_blank" rel="noopener noreferrer">SAP Build Apps | Tutorial Video</A></LI><BR /> </UL><BR /> For the downloads, see<BR /> <UL><BR /> <LI><A href="https://marketplace.visualstudio.com/items?itemName=SAPOS.yeoman-ui" target="_blank" rel="nofollow noopener noreferrer">Application Wizard - Visual Studio Marketplace</A></LI><BR /> </UL><BR /> <H3 id="toc-hId--1144677305">Illustration</H3><BR /> Run the SAP HANA Academy Function-as-a-service generator in Visual Studio Code with the Template Wizard.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2023/02/faas.png" height="343" width="612" /></P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2016/02/sapnwabline_885687.png" width="610" /><BR /> <H2 id="toc-hId--1047787803"><SPAN style="color: #d68c00">Python and SAP HANA Cloud</SPAN></H2><BR /> <H3 id="toc-hId--1537704315">Video Tutorial</H3><BR /> In this hands-on video tutorial, Philip Mugglestone shows how to use the jump-start generator to create a serverless function project that uses Python to interact with SAP HANA Cloud.<BR /> <BR /> <A href="https://youtu.be/j_rBuwarYqo?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3" target="test_blank" rel="nofollow noopener noreferrer">https://youtu.be/j_rBuwarYqo?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3</A><BR /> <H3 id="toc-hId--1734217820">Markers</H3><BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=j_rBuwarYqo&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=2&amp;t=22s" target="_blank" rel="nofollow noopener noreferrer">0:22</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Jump-start Generator </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=j_rBuwarYqo&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=2&amp;t=68s" target="_blank" rel="nofollow noopener noreferrer">1:08</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Code Review </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=j_rBuwarYqo&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=2&amp;t=354s" target="_blank" rel="nofollow noopener noreferrer">5:54</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Test </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=j_rBuwarYqo&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=2&amp;t=488s" target="_blank" rel="nofollow noopener noreferrer">8:08</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Tear Down </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=j_rBuwarYqo&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=2&amp;t=539s" target="_blank" rel="nofollow noopener noreferrer">8:59</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Recap</SPAN><BR /> <H3 id="toc-hId--1930731325">References</H3><BR /> For the prerequisites, visit<BR /> <UL><BR /> <LI><A href="https://blogs.sap.com/2022/12/16/sap-btp-developer-onboarding-sap-hana-cloud/" target="_blank" rel="noopener noreferrer">SAP HANA Cloud | Tutorial Video</A></LI><BR /> </UL><BR /> <H3 id="toc-hId--2127244830">Illustration</H3><BR /> View the function source code using the Kyma Dashboard.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2023/02/kyma.png" /></P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2016/02/sapnwabline_885687.png" width="610" /><BR /> <H2 id="toc-hId--2030355328"><SPAN style="color: #d68c00">Node.js and APIs</SPAN></H2><BR /> <H3 id="toc-hId-1774695456">Video Tutorial</H3><BR /> In this hands-on video tutorial, Philip Mugglestone shows how to use the jump-start generator to create a serverless function project that uses Node.js and the SAP Cloud SDK to interact with different APIs.<BR /> <BR /> <A href="https://youtu.be/SERjGBG55kQ?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3" target="test_blank" rel="nofollow noopener noreferrer">https://youtu.be/SERjGBG55kQ?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3</A><BR /> <H3 id="toc-hId-1578181951">Markers</H3><BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=SERjGBG55kQ&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=3&amp;t=17s" target="_blank" rel="nofollow noopener noreferrer">0:17</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Jump-start Generator </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=SERjGBG55kQ&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=3&amp;t=65s" target="_blank" rel="nofollow noopener noreferrer">1:05</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Code Review </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=SERjGBG55kQ&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=3&amp;t=100s" target="_blank" rel="nofollow noopener noreferrer">1:40</A><SPAN class="style-scope yt-formatted-string" dir="auto"> API Key </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=SERjGBG55kQ&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=3&amp;t=133s" target="_blank" rel="nofollow noopener noreferrer">2:13</A><SPAN class="style-scope yt-formatted-string" dir="auto"> SAP Cloud SDK </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=SERjGBG55kQ&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=3&amp;t=180s" target="_blank" rel="nofollow noopener noreferrer">3:00</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Deploy </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=SERjGBG55kQ&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=3&amp;t=235s" target="_blank" rel="nofollow noopener noreferrer">3:55</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Test </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=SERjGBG55kQ&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=3&amp;t=345s" target="_blank" rel="nofollow noopener noreferrer">5:45</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Review Services </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=SERjGBG55kQ&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=3&amp;t=417s" target="_blank" rel="nofollow noopener noreferrer">6:57</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Recap</SPAN><BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2016/02/sapnwabline_885687.png" width="610" /><BR /> <H2 id="toc-hId-1843255144"><SPAN style="color: #d68c00">Authentication and Authorization</SPAN></H2><BR /> <H3 id="toc-hId-1353338632">Video Tutorial</H3><BR /> In this hands-on video tutorial, Philip Mugglestone shows how to use the jump-start generator to create a serverless function project that provides enterprise security for the endpoints incorporating both authentication and authorization.<BR /> <BR /> <A href="https://youtu.be/2Qnkqs821Xc?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3" target="test_blank" rel="nofollow noopener noreferrer">https://youtu.be/2Qnkqs821Xc?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3</A><BR /> <H3 id="toc-hId-1156825127">Markers</H3><BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=2Qnkqs821Xc&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=4&amp;t=32s" target="_blank" rel="nofollow noopener noreferrer">0:32</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Jump-start Generator </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=2Qnkqs821Xc&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=4&amp;t=55s" target="_blank" rel="nofollow noopener noreferrer">0:55</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Code Review </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=2Qnkqs821Xc&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=4&amp;t=251s" target="_blank" rel="nofollow noopener noreferrer">4:11</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Deploy </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=2Qnkqs821Xc&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=4&amp;t=313s" target="_blank" rel="nofollow noopener noreferrer">5:13</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Test </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=2Qnkqs821Xc&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=4&amp;t=433s" target="_blank" rel="nofollow noopener noreferrer">7:13</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Assign Role Collections </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=2Qnkqs821Xc&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=4&amp;t=483s" target="_blank" rel="nofollow noopener noreferrer">8:03</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Recap</SPAN><BR /> <H3 id="toc-hId-960311622">Illustration</H3><BR /> Get an authentication token using Postman.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2023/02/postman-1.png" /></P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2016/02/sapnwabline_885687.png" width="610" /><BR /> <H2 id="toc-hId-1057201124"><SPAN style="color: #d68c00">Events</SPAN></H2><BR /> <H3 id="toc-hId-567284612">Video Tutorial</H3><BR /> In this hands-on video tutorial, Philip Mugglestone shows how to use the jump-start generator to create a serverless function project that subscribes to events.<BR /> <BR /> <A href="https://youtu.be/kVPgFiRn_tU?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3" target="test_blank" rel="nofollow noopener noreferrer">https://youtu.be/kVPgFiRn_tU?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3</A><BR /> <H3 id="toc-hId-370771107">Markers</H3><BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=kVPgFiRn_tU&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=5&amp;t=27s" target="_blank" rel="nofollow noopener noreferrer">0:27</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Jump-start Generator </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=kVPgFiRn_tU&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=5&amp;t=63s" target="_blank" rel="nofollow noopener noreferrer">1:03</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Code Review </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=kVPgFiRn_tU&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=5&amp;t=184s" target="_blank" rel="nofollow noopener noreferrer">3:04</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Deploy </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=kVPgFiRn_tU&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=5&amp;t=216s" target="_blank" rel="nofollow noopener noreferrer">3:36</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Test </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=kVPgFiRn_tU&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=5&amp;t=394s" target="_blank" rel="nofollow noopener noreferrer">6:34</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Recap</SPAN><BR /> <H3 id="toc-hId-174257602">References</H3><BR /> For the documentation, see<BR /> <UL><BR /> <LI><A href="https://kyma-project.io/docs/kyma/latest/02-get-started/04-trigger-workload-with-event/" target="_blank" rel="nofollow noopener noreferrer">Trigger a workload with an event</A></LI><BR /> </UL><BR /> <H3 id="toc-hId--22255903">Illustrations</H3><BR /> Triggering an event on the command line using curl.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2023/02/event1.png" /></P><BR /> View the result in the pod log file using the Kyma dashboard.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2023/02/event2.png" /></P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2016/02/sapnwabline_885687.png" width="610" /><BR /> <H2 id="toc-hId-74633599"><SPAN style="color: #d68c00">Git</SPAN></H2><BR /> <H3 id="toc-hId--247099222">Video Tutorial</H3><BR /> In this hands-on video tutorial, Philip Mugglestone shows how to use the jump-start generator to create a serverless function project where the source code is managed via Git instead of being inline.<BR /> <BR /> <A href="https://youtu.be/HBXazXXKnuY?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3" target="test_blank" rel="nofollow noopener noreferrer">https://youtu.be/HBXazXXKnuY?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3</A><BR /> <H3 id="toc-hId--443612727">Markers</H3><BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=HBXazXXKnuY&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=6&amp;t=46s" target="_blank" rel="nofollow noopener noreferrer">0:46</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Jump-start Generator </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=HBXazXXKnuY&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=6&amp;t=69s" target="_blank" rel="nofollow noopener noreferrer">1:09</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Create Git Repository </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=HBXazXXKnuY&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=6&amp;t=272s" target="_blank" rel="nofollow noopener noreferrer">4:32</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Code Review </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=HBXazXXKnuY&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=6&amp;t=344s" target="_blank" rel="nofollow noopener noreferrer">5:44</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Clone Git Repository </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=HBXazXXKnuY&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=6&amp;t=365s" target="_blank" rel="nofollow noopener noreferrer">6:05</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Commit and Push Source Files </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=HBXazXXKnuY&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=6&amp;t=384s" target="_blank" rel="nofollow noopener noreferrer">6:24</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Deploy </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=HBXazXXKnuY&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=6&amp;t=441s" target="_blank" rel="nofollow noopener noreferrer">7:21</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Test </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=HBXazXXKnuY&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=6&amp;t=581s" target="_blank" rel="nofollow noopener noreferrer">9:41</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Recap</SPAN><BR /> <H3 id="toc-hId--640126232">Illustrations</H3><BR /> Commit source code update using git and GitHub.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2023/02/git.png" /></P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2016/02/sapnwabline_885687.png" width="610" /><BR /> <H2 id="toc-hId--543236730"><SPAN style="color: #d68c00">SAP Build Apps</SPAN></H2><BR /> <H3 id="toc-hId--1033153242">Video Tutorial</H3><BR /> In this hands-on video tutorial, Philip Mugglestone shows how to access a serverless function created using the jump-start generator from SAP Build Apps including authentication and authorization.<BR /> <BR /> <A href="https://youtu.be/1lnXZT4whXM?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3" target="test_blank" rel="nofollow noopener noreferrer">https://youtu.be/1lnXZT4whXM?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3</A><BR /> <H3 id="toc-hId--1229666747">Markers</H3><BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=1lnXZT4whXM&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=7&amp;t=29s" target="_blank" rel="nofollow noopener noreferrer">0:29</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Jump-start Generator </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=1lnXZT4whXM&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=7&amp;t=80s" target="_blank" rel="nofollow noopener noreferrer">1:20</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Launch SAP Build Apps </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=1lnXZT4whXM&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=7&amp;t=96s" target="_blank" rel="nofollow noopener noreferrer">1:36</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Assign Role Collections </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=1lnXZT4whXM&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=7&amp;t=135s" target="_blank" rel="nofollow noopener noreferrer">2:15</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Create Destination </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=1lnXZT4whXM&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=7&amp;t=265s" target="_blank" rel="nofollow noopener noreferrer">4:25</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Create Application in SAP Build Apps </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=1lnXZT4whXM&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=7&amp;t=461s" target="_blank" rel="nofollow noopener noreferrer">7:41</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Preview App </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=1lnXZT4whXM&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=7&amp;t=491s" target="_blank" rel="nofollow noopener noreferrer">8:11</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Recap</SPAN><BR /> <H3 id="toc-hId--1426180252">References</H3><BR /> For the prerequisites, visit<BR /> <UL><BR /> <LI><A href="https://blogs.sap.com/2022/12/16/sap-btp-developer-onboarding-sap-build-apps/" target="_blank" rel="noopener noreferrer">SAP Build Apps | Tutorial Video</A></LI><BR /> </UL><BR /> <H3 id="toc-hId--1622693757">Illustrations</H3><BR /> Configure SAP BTP destination REST API integration in SAP Build Apps.<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2023/02/build.png" /></P><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2016/02/sapnwabline_885687.png" width="610" /><BR /> <H2 id="toc-hId--1525804255"><SPAN style="color: #d68c00">Application Router</SPAN></H2><BR /> <H3 id="toc-hId--2015720767">Video Tutorial</H3><BR /> In this hands-on video tutorial, Philip Mugglestone shows how to use the jump-start generator to create a serverless function project that also incorporates an Application Router in order to facilitate interactive browser-based authentication.<BR /> <BR /> <A href="https://youtu.be/bXnR9fUliVA?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3" target="test_blank" rel="nofollow noopener noreferrer">https://youtu.be/bXnR9fUliVA?list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3</A><BR /> <H3 id="toc-hId--2044050581">Markers</H3><BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=bXnR9fUliVA&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=8&amp;t=30s" target="_blank" rel="nofollow noopener noreferrer">0:30</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Jump-start Generator </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=bXnR9fUliVA&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=8&amp;t=62s" target="_blank" rel="nofollow noopener noreferrer">1:02</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Code Review </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=bXnR9fUliVA&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=8&amp;t=156s" target="_blank" rel="nofollow noopener noreferrer">2:36</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Deploy </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=bXnR9fUliVA&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=8&amp;t=214s" target="_blank" rel="nofollow noopener noreferrer">3:34</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Assign Role Collections </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=bXnR9fUliVA&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=8&amp;t=240s" target="_blank" rel="nofollow noopener noreferrer">4:00</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Test </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=bXnR9fUliVA&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=8&amp;t=338s" target="_blank" rel="nofollow noopener noreferrer">5:38</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Tear Down </SPAN><BR /> <BR /> <A class="yt-simple-endpoint style-scope yt-formatted-string" href="https://www.youtube.com/watch?v=bXnR9fUliVA&amp;list=PLkzo92owKnVyyemLABuRYmyc29crnvxn3&amp;index=8&amp;t=368s" target="_blank" rel="nofollow noopener noreferrer">6:08</A><SPAN class="style-scope yt-formatted-string" dir="auto"> Recap</SPAN><BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2016/02/sapnwabline_885687.png" width="610" /><BR /> <H2 id="toc-hId--1947161079"><SPAN style="color: #d68c00">Share and Connect</SPAN></H2><BR /> Anything to add? Leave a comment below.<BR /> <BR /> Useful? Give us a like and share on social media.<BR /> <BR /> Questions? Please use the <A href="https://answers.sap.com/questions/ask.html" target="_blank" rel="noopener noreferrer">community Q&amp;A</A>.<BR /> <BR /> Thanks!<BR /> <BR /> If you would like to receive updates, connect with me on<BR /> <UL><BR /> <LI>LinkedIn &gt; <A href="https://linkedin.com/in/dvankempen" target="_blank" rel="noopener noreferrer nofollow">linkedin.com/in/dvankempen</A></LI><BR /> <LI>Twitter &gt; <A href="https://twitter.com/dvankempen" target="_blank" rel="noopener noreferrer nofollow">@dvankempen</A></LI><BR /> </UL><BR /> For the author page of SAP PRESS, visit<BR /> <UL><BR /> <LI><A href="https://www.sap-press.com/sap-hana-20-certification-guide-technology-associate-exam_5078/author/" target="_blank" rel="nofollow noopener noreferrer">Denys van Kempen</A></LI><BR /> </UL><BR /> <TABLE style="height: 59px" width="612" bgcolor="#e7f5ff"><BR /> <TBODY><BR /> <TR><BR /> <TD>Over the years, for the SAP HANA Academy, SAP’s Partner Innovation Lab, and à titre personnel, I have written a little over 300 posts here for the SAP Community. Some articles only reached a few readers. Others attracted quite a few more. For your reading pleasure and convenience, here is a curated list of posts which somehow managed to pass the 10k-view milestone and, as sign of current interest, still tickle the counters each month.<BR /> <UL><BR /> <LI><A href="https://blogs.sap.com/2021/11/05/good-reads-my-two-cents/" target="_blank" rel="noopener noreferrer">Good Reads (my two cents)</A></LI><BR /> </UL><BR /> </TD><BR /> </TR><BR /> </TBODY><BR /> </TABLE><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2016/02/sapnwabline_885687.png" width="610" /> 2023-02-10T14:22:24+01:00