Skip to content

Instantly share code, notes, and snippets.

@ptesny
Last active May 20, 2024 07:51
Show Gist options
  • Save ptesny/aa8bc30ce043e1e11c145fe15278db62 to your computer and use it in GitHub Desktop.
Save ptesny/aa8bc30ce043e1e11c145fe15278db62 to your computer and use it in GitHub Desktop.
Jumpstart kymaruntime environment with SAP BTP trial

Jumpstart kymaruntime environment from SAP BTP trial.

This brief is about how to jumpstart a kyma environment from a default SAP BTP trial subaccount | SAP Help.
And then what to do in the aftermath of it.

Good to know:

  • with a default SAP BTP trial subaccount all the required services and subscriptions are already entitled
image image
btp target

Current target:
 ec7cc50etrial (global account, subdomain: ec7cc50etrial-ga)

Choose subaccount or directory:
  [..] Switch Global Accounts
   [.] ec7cc50etrial (global account)
   [1]  ├─ quovadis-ap (subaccount)
   [2]  ├─ quovadis-us (subaccount)
   [3]  └─ trial (subaccount)
Choose, or hit ENTER to stay in 'ec7cc50etrial' [.]> 3

Now targeting:
 ec7cc50etrial (global account, subdomain: ec7cc50etrial-ga)

How to use kyma environment once it's been created ?

Configure Custom SAP IAS tenant with SAP BTP Kyma runtime environment | SAP Blogs nicely details all the kymaruntime provisioning steps.

However, this time let's try to focus on the "aftermath".

  • How do I get access to a kyma dashboard ?
  • How to generate a sa-based (service account based) kubeconfig for pipeline automation?
  • How do I deploy my applications to a cluster with CI/CD tools, for instance, with ArgoCD?
  • How to dispose of it when no longer required ?

SAP Cloud Identity service (identity)

image Let's start with configuring a SAP Custom Identity provider for use with a kyma cluster.

An OIDC provider is required to grant kyma named users access to a kyma cluster api server.

It is an optional step, as otherwise the kyma environment provisioner will assign all nominated kyma cluster-admin users to a SAP-managed OIDC provider | SAP Help.

However, customers may prefer using their own custom identity provider | SAP Help.
This option offers customers and partners full control over the kyma cluster OIDC users.
Moreover, it supports federation with corporate IDPs | SAP Help.

Table of Contents
  1. establish BTP subaccount trust with a custom SAP IAS tenant.
    1. subscribe to a Cloud Identity service (optional).
    2. find available Cloud Identity service tenant from the list of available idps.
    3. establish idp trust on a subaccount level.
  2. deploy custom-idp oidc service provider application (automation).
    1. create Cloud Identity service instance and binding.

1. establish BTP subaccount trust with a custom SAP IAS tenant.

Custom SAP Cloud Identity provider trust is established on a BTP subaccount level.

1.1 subscribe to a Cloud Identity service (optional)

Each custom SAP Cloud Identity tenant subscription has a global account scope. Thus, a given Custom SAP Cloud Identity tenant can be used across multiple subaccounts.

Good to know:

  • This step is optional if the list of available idps is not empty
  • SAP Cloud Identity tenant onboarding email has been sent out the email address of the SAP BTP trial global account
btp subscribe accounts/subaccount --to-app sap-identity-services-onboarding --plan default 
{
  "jobId": "<jobId>"
}

The idp tenant subscriprion is asynchronous.

1.2 find available Cloud Identity service tenant from the list of available idps

Next step is to retrieve the list of the custom SAP Cloud Identity tenants available for a given customer id in the context of a global account.

The btp CLI offers security/available-idp set of commands to deal with the account idps.

The below idp list should have at least one entry, for instance:

btp list security/available-idp
[
  {
    "tenantType": "enterprise",
    "costCenterId": null,
    "displayName": null,
    "commonHost": "<tenantId>.trial-accounts.cloud.sap",
    "dataCenterId": "TRIAL1",
    "host": "<tenantId>.trial-accounts.ondemand.com",
    "customerId": null,
    "tenantId": "<tenantId>",
    "description": "Cloud Foundry Trial subscription",
    "customHost": null,
    "customerName": null,
    "status": "ACTIVE"
  }
]

That's the equivalent of using the Establish Trust button from the subaccount UI, for instance:

image

1.3 establish idp trust on a subaccount level

Let's list the existing idp trust just to double check the idp has not been trusted yet.
The security/trust list will never be empty as each subaccount always has at minima a default SAP ID OIDC trust, for instance:

image

btp list security/trust
[
  {
    "name": "sap.default",
    "originKey": "sap.default",
    "typeOfTrust": "Application",
    "status": "active",
    "description": null,
    "identityProvider": null,
    "domain": null,
    "linkTextForUserLogon": "Default Identity Provider",
    "availableForUserLogon": "true",
    "createShadowUsersDuringLogon": "false",
    "sapBtpCli": null,
    "protocol": "OpenID Connect",
    "readOnly": false
  }
]

After this verification (which may be optional with a trial subaccount), let's establish a new trust, using the [0].host field as follows:

btp create security/trust --idp <tenantId>.trial-accounts.ondemand.com --name <custom name>

2. deploy custom-idp oidc service provider application (automation).

The good news is that both creation and configuration of custom oidc service provider applications can be fully automated.
Let's see how.

2.1 create Cloud Identity service instance and binding.

The SAP Cloud Identity subscription has a sibling BTP service plan to help automate the creation and configuration of OIDC service providers applications.

Let's create ias-local service instance and ias-local-binding service binding, as follows:

btp create services/instance  --offering-name identity --plan-name application --name ias-local --parameters quovadis-ias-master.json
{
  "id": "<id>",
  "command": "btp get services/instance <instance> --subaccount <subaccount>",
  "description": "Use the command above with the provided values (instance ID and subaccount ID respectively) to check the status of the create instance operation you initiated."
}
btp create services/binding --name ias-local-binding --instance-name ias-local  --parameters quovadis-ias-binding.json
{
  "id": "<id>",
  "command": "btp get services/binding <binding> --subaccount <subaccount>",
  "description": "Use the command above with the provided values (binding ID and subaccount ID respectively) to check the status of the create binding operation you initiated."
}
btp get services/binding --name ias-local-binding
{
  "credentials": {
    "app_tid": "<app_tid>",
    "authorization_endpoint": "https://<tenantId>.trial-accounts.ondemand.com/oauth2/authorize",
    "btp-tenant-api": "https://api.authentication.us10.hana.ondemand.com",
    "clientid": "<clientid>",
    "credential-type": "NONE",
    "domain": "accounts.ondemand.com",
    "domains": [
      "accounts.ondemand.com",
      "accounts.cloud.sap",
      "trial-accounts.ondemand.com"
    ],
    "end_session_endpoint": "https://<tenantId>.trial-accounts.ondemand.com/oauth2/logout",
    "url": "https://<tenantId>.trial-accounts.ondemand.com",
    "zone_uuid": "<zone_uuid>"
  },
}

from where we need to extract two parameters, namely the clientID and the issuerURL

  • credentials.clientid as clientID
  • credentials.url as issuerURL

These two values will be used in the kyma runtime environment OIDC parameters.

SAP BTP Kyma runtime (kymaruntime)

Table of Contents
  1. Kyma Environment.
    1. provision kymaruntime environment with btp CLI.
    2. provision kymaruntime environment with cis-local.
    3. get an OIDC user-based kubeconfig for the provisioned kyma cluster.
  2. deploying with ArgoCD.
    1. prepare your kyma cluster for ArgoCD operations..
    2. enable cluster-wide technical user access (serviceaccount)..
    3. enable namespaced technical user access (btp-easy)..

1. Kyma Environment.

image Provision kymaruntime service (environment) with either SAP BTP CLI or SAP Provisioning service (cis-local) REST APIs.

BTP CLI offers a number of command dedicated to runtime environments, namely:

  • btp list accounts/environment-instance
  • btp create accounts/environment-instance --display-name quovadis --environment kyma --service kymaruntime --plan trial --parameters config.json
  • btp get accounts/environment-instance
  • btp update accounts/environment-instance
  • btp delete accounts/environment-instance

1.1 provision kymaruntime environment with btp CLI

btp create accounts/environment-instance --display-name quovadis-trial --environment kyma --service kymaruntime --plan trial --parameters kyma-config.json


{
  "id": "<kyma environment id>",
  "name": "quovadis-trial",
  "brokerId": "<brokerId>",
  "globalAccountGUID": "<globalAccountGUID>",
  "subaccountGUID": "<subaccountGUID>",
  "tenantId": "<tenantId>",
  "serviceId": "<kymaruntime serviceId>",
  "planId": "<kymaruntime planId>",
  "dashboardUrl": "https:\/\/dashboard.kyma.cloud.sap\/?kubeconfigID=69B01810-0425-4F8A-BF42-A4BEA69ED1A7",
  "operation": "<operation>",
  "parameters": "{\"modules\":{\"list\":[{\"name\":\"api-gateway\",\"channel\":\"regular\"},{\"name\":\"istio\",\"channel\":\"regular\"},{\"name\":\"btp-operator\",\"channel\":\"regular\"},{\"name\":\"serverless\",\"channel\":\"regular\"},{\"name\":\"connectivity-proxy\",\"channel\":\"regular\"}]},\"administrators\":[\"cluster-admin1@acme.com\",\"cluster-admin2@acme.com\",\"cluster-adminN@acme.com\"],\"oidc\":{\"clientID\":\"<clientID>\",\"groupsClaim\":\"groups\",\"issuerURL\":\"https:\/\/<idp-tenantId>.trial-accounts.ondemand.com\",\"signingAlgs\":[\"RS256\"],\"usernameClaim\":\"sub\",\"usernamePrefix\":\"-\"},\"name\":\"quovadis-trial\"}",
  "labels": "{\"KubeconfigURL\":\"https:\/\/kyma-env-broker.cp.kyma.cloud.sap\/kubeconfig\/69B01810-0425-4F8A-BF42-A4BEA69ED1A7\",\"Name\":\"quovadis-trial\"}",
  "customLabels": {},
  "type": "Provision",
  "status": "Processing",
  "environmentType": "kyma",
  "platformId": "<platformId>",
  "createdDate": "<createdDate>",
  "modifiedDate": "<modifiedDate>",
  "state": "CREATING",
  "stateMessage": "Creating environment instance.",
  "serviceName": "kymaruntime",
  "planName": "trial",
  "jobId": "<jobId>"
}

Environment provisioning (creation) is asynchronous. Kymaruntime creation may take up to 40 minutes or will timeout. Polling with btp get accounts/environment-instance F18A9B9E-99CE-4F28-B1CD-8DA3974637DA

1.1 provision kymaruntime environment with cis-local

POST /provisioning/v1/environments
{
  "description": "Trial",
  "environmentType": "kyma",

  "name": "quovadis",

  "parameters": {
    "name": "quovadis",
    "administrators": [
        "cluster-admin1@acme.com",
        "cluster-admin2@acme.com",
        "cluster-adminN@acme.com"
    ],
    "oidc": {
        "clientID": "<SAP IAS clientID>",
        "groupsClaim": "groups",
        "issuerURL": "https://<SAP IAS tenant>.trial-accounts.ondemand.com"",
        "signingAlgs": [
            "RS256"
        ],
        "usernameClaim": "sub",
        "usernamePrefix": "-"
    }
  },
  "planName": "trial",
  "serviceName": "kymaruntime",
  "user": "btptrial-admin@acme.com"

}

with the response payload as follows:

{
  "id": "F234B0AB-CDBF-4BF6-B62B-30272D8A997B",
  "name": "quovadis",
  "description": "Trial",
  "brokerId": "DBE346C8-77F3-40AB-9207-0788F94ADD5F",
  "globalAccountGUID": "50750a24-0f15-48ca-b9c8-9c1900279e6b",
  "subaccountGUID": "19c6a493-a6f5-46e0-9849-bf126ef6a0ff",
  "tenantId": "19c6a493-a6f5-46e0-9849-bf126ef6a0ff",
  "serviceId": "47c9dcbf-ff30-448e-ab36-d3bad66ba281",
  "planId": "7d55d31d-35ae-4438-bf13-6ffdfa107d9f",
  "dashboardUrl": "https://dashboard.kyma.cloud.sap/?kubeconfigID=F234B0AB-CDBF-4BF6-B62B-30272D8A997B",
  "operation": "c5fcd032-fee4-48d9-a373-291bdfeb7c4d",
  "parameters": "{\"name\":\"quovadis\",\"administrators\":[\"cluster-admin1@acme.com\",\"cluster-admin1@acme.com\",\"cluster-admin1@acme.com\"],\"oidc\":{\"clientID\":\"<clientID>\",\"groupsClaim\":\"groups\",\"issuerURL\":\"https://<tenant>.trial-accounts.ondemand.com\",\"signingAlgs\":[\"RS256\"],\"usernameClaim\":\"sub\",\"usernamePrefix\":\"-\"}}",
  "labels": "{\"KubeconfigURL\":\"https://kyma-env-broker.cp.kyma.cloud.sap/kubeconfig/F234B0AB-CDBF-4BF6-B62B-30272D8A997B\",\"Name\":\"quovadis\"}",
  "customLabels": {},
  "type": "Provision",
  "status": "Processing",
  "environmentType": "kyma",
  "platformId": "d40bcc14-6c3c-4ea8-8ff3-88498cd7e37b",
  "createdDate": <createdDate>,
  "modifiedDate": <modifiedDate>,
  "state": "CREATING",
  "stateMessage": "Creating environment instance.",
  "serviceName": "kymaruntime",
  "planName": "trial"
}
image image

1.3 get an user OIDC-based kubeconfig for the provisioned kyma cluster

After the successfull provisioning of kyma runtime environment:

  • one can retrieve dashboardUrlfor kyma dashboard interactive access
  • one can download the OIDC-user based kubeconfig from labels.KubeconfigURL

Good to know:

  • as of today there is no other way to gain access to a newly created kyma cluster but with a user OIDC-based kubeconfig
  • as soon as a cluster is accessed it can be prepared for both manual and CI/CD operations with service accounts

2. deploying with ArgoCD

image

2.1 prepare your kyma cluster for ArgoCD operations

2.2 enable cluster-wide technical user access (serviceaccount)

2.3 enable namespaced technical user access (btp-easy)

Documentation

kyma-trial-config.json

{
    "modules": {
        "list": [
            {
                "name": "api-gateway",
                "channel": "regular"
            },
            {
                "name": "istio",
                "channel": "regular"
            },
            {
                "name": "btp-operator",
                "channel": "regular"
            },
            {
                "name": "serverless",
                "channel": "regular"
            },
            {
                "name": "connectivity-proxy",
                "channel": "regular"
            }
        ]
    },    
    "administrators": [
        "cluster-admin1@acme.com",
        "cluster-admin2@acme.com",
        "cluster-adminN@acme.com"
    ],
    "oidc": {
        "clientID": "<clientID>",
        "groupsClaim": "groups",
        "issuerURL": "<issuerURL>",
        "signingAlgs": [
            "RS256"
        ],
        "usernameClaim": "sub",
        "usernamePrefix": "-"
    },
    "name": "quovadis-trial"
}

btp list accounts/environment-instance

btp list accounts/environment-instance
{
  "environmentInstances": [
    {
      "id": "F18A9B9E-99CE-4F28-B1CD-8DA3974637DA",
      "name": "quovadis-trial",
      "brokerId": "DBE346C8-77F3-40AB-9207-0788F94ADD5F",
      "globalAccountGUID": "f0266601-8e54-4941-b83d-e6c668bf258b",
      "subaccountGUID": "37303384-b53a-4e96-bae0-ae5547816a5f",
      "tenantId": "37303384-b53a-4e96-bae0-ae5547816a5f",
      "serviceId": "47c9dcbf-ff30-448e-ab36-d3bad66ba281",
      "planId": "7d55d31d-35ae-4438-bf13-6ffdfa107d9f",
      "dashboardUrl": "https://dashboard.kyma.cloud.sap/?kubeconfigID\u003dF18A9B9E-99CE-4F28-B1CD-8DA3974637DA",
      "operation": "cf763b7c-cf79-4525-aaed-8904d33a66f5",
      "parameters": "{\"modules\":{\"list\":[{\"name\":\"api-gateway\",\"channel\":\"regular\"},{\"name\":\"istio\",\"channel\":\"regular\"},{\"name\":\"btp-operator\",\"channel\":\"regular\"},{\"name\":\"serverless\",\"channel\":\"regular\"},{\"name\":\"connectivity-proxy\",\"channel\":\"regular\"}]},\"administrators\":[\"cluster-admin1@acme.com\",\"cluster-admin2@acme.com\",\"cluster-adminN@acme.com\"],\"oidc\":{\"clientID\":\"<clientID>\",\"groupsClaim\":\"groups\",\"issuerURL\":\"https://<tenantId>.trial-accounts.ondemand.com\",\"signingAlgs\":[\"RS256\"],\"usernameClaim\":\"sub\",\"usernamePrefix\":\"-\"},\"name\":\"quovadis\"}",
      "labels": "{\"KubeconfigURL\":\"https://kyma-env-broker.cp.kyma.cloud.sap/kubeconfig/F18A9B9E-99CE-4F28-B1CD-8DA3974637DA\",\"Name\":\"quovadis\"}",
      "customLabels": {},
      "type": "Provision",
      "status": "Processing",
      "environmentType": "kyma",
      "platformId": "<platformId>",
      "createdDate": "<createdDate>",
      "modifiedDate": "<modifiedDate>",
      "state": "CREATING",
      "stateMessage": "Operation created",
      "serviceName": "kymaruntime",
      "planName": "trial"
    },
    {
      "id": "564ED9A1-42ED-467F-9F98-6BA216D968B8",
      "name": "ec7cc50etrial",
      "brokerId": "985B006B-820E-40BF-AC53-AB3D2B86C294",
      "globalAccountGUID": "f0266601-8e54-4941-b83d-e6c668bf258b",
      "subaccountGUID": "37303384-b53a-4e96-bae0-ae5547816a5f",
      "tenantId": "37303384-b53a-4e96-bae0-ae5547816a5f",
      "serviceId": "fa31b750-375f-4268-bee1-604811a89fd9",
      "planId": "267b5620-3011-4c48-8e56-8d103876275b",
      "operation": "provision",
      "parameters": "{\"instance_name\":\"ec7cc50etrial\",\"archetype\":\"trial\",\"status\":\"ACTIVE\"}",
      "labels": "{\"API Endpoint\":\"https://api.cf.us10-001.hana.ondemand.com\",\"Org Name\":\"ec7cc50etrial\",\"Org ID\":\"ee69025b-3134-4663-ba09-81efc893a54a\",\"Org Memory Limit\":\"4,096MB\"}",
      "customLabels": {},
      "type": "Provision",
      "status": "Processed",
      "environmentType": "cloudfoundry",
      "landscapeLabel": "cf-us10-001",
      "platformId": "ee69025b-3134-4663-ba09-81efc893a54a",
      "createdDate": "<createdDate>",
      "modifiedDate": "<modifiedDate>",
      "state": "OK",
      "stateMessage": "Environment instance created.",
      "serviceName": "cloudfoundry",
      "planName": "trial"
    }
  ]
}

quovadis-ias-binding.json

 {
    "credential-type": "NONE"
 }

quovadis-ias-master.json

{
        "name": "quovadis-trial",
        "display-name": "quovadis-trial",
        "user-access": "public",
        "oauth2-configuration": {
            "grant-types": [
                "authorization_code",
                "authorization_code_pkce_s256"
            ],
            "token-policy": {
                "token-validity": 3600,
                "refresh-parallel": 3,
                "access-token-format": "default"
            },
            "public-client": true,
            "redirect-uris": [
                "https://dashboard.kyma.cloud.sap",
                "https://dashboard.stage.kyma.cloud.sap",
                "http://localhost:8000"
            ]
        },
        "subject-name-identifier": {
            "attribute": "mail",
            "fallback-attribute": "none"
        },
        "default-attributes": null,
        "assertion-attributes": {
            "email": "mail",
            "groups": "companyGroups",
            "first_name": "firstName",
            "last_name": "lastName",
            "login_name": "loginName",
            "mail": "mail",
            "scope": "companyGroups",
            "user_uuid": "userUuid",
            "locale": "language"
        }
}

Configuration as code (CaC). quovadis-master

Destinations are very handy and powerful mechanism to facilitate access to target systems and devices.

This is a companion gist to Configuration as code (CaC) with destinations. | SAP Blogs.

Table of Contents
  1. Configuration as code with SAP BTP destination service.
    1. create shared destination service instance and binding.
  2. Provision bootstrap destinations.
    1. retrieve destination service credentials from binding.
    2. describe bootstrap destination definitions.
    3. create bootstrap destinations on subaccount.
  3. Configure destination resources.
    1. dynamic_dest route with managed approuter.
    2. SAP Cloud SDK built-in destinations.
  4. Documentation.

1. Configuration as code with SAP BTP destination service

Configuration as code (CaC) defines maintaining configuration resources in a source repository as versioned assets and then managing them from code.

CaC refers to the practice of handling changes, deployments and updates to software and systems via code rather than manual processes.

When it comes to SAP BTP, the idea is to manage both subaccount and instance level destinations (and/or certificates) as shared configuration resources on a provider subaccount level.

That way, the destinations configurations need to be maintained only once per provider without application runtime tie-in.
Moreover, BTP destination service can be used as a self-configuration tool.

1.1 create shared destination service instance and binding

btp create services/instance --offering-name destination --plan-name lite --name quovadis-master 

share service instance

btp share services/instance --name quovadis-master                                 

create service binding

btp create services/binding --name quovadis-master-binding --instance-name quovadis-master

2. Provision bootstrap destination definition(s)

As aforementioned, SAP BTP destination service can be used as a tool to manage destinations configurations from code.
The first step is to add a single bootstrap destination to gain access to the destination service REST APIs.

2.1 retrieve destination service credentials from binding

A service binding contains the service credentials (secret).

btp get services/binding --name quovadis-master-binding
{
  "credentials": {
    "clientid": "sb-clone12847c4c89544b4f9234b26ede429f62!b282590|destination-xsappname!b62",
    "clientsecret": "<clientSecret>",
    "credential-type": "binding-secret",
    "identityzone": "<identityzone>",
    "instanceid": "<instanceid>",
    "tenantid": "<tenantid>",
    "tenantmode": "dedicated",
    "uaadomain": "authentication.us10.hana.ondemand.com",
    "uri": "https://destination-configuration.cfapps.us10.hana.ondemand.com",
    "url": "https://<subdomain>.authentication.us10.hana.ondemand.com",
    "verificationkey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqZi9fsiJ1HZDwkrEG8Io\n8NMgfsqlV8G54LgUXEEAyiIZpZrRVX3cBRt+8bVNUqQpiDMUoy2orRls1688p3yH\nQcNkeuv3rU7ymxSjffo8FHDcEq6agx64YlDrEybzUSY2KqQXwEhV+O3JXHhw6mfF\nPBw93BWokOj4BrcB7wHCaoKlYTSKEoU3CvzzEy/mL5Ac+9nCUKae73C/PBayIDp8\n6+z+iHMj9jr3cL4X22QXSz/T9CFr0o+VKzomPstaNStJXNpG+OezAuLybgFsZjF5\n8EHGDRp9TaIW7lK/8WvE0fJ+jyO9Pfo9JAk5lfYqiD+y41QE6/N7SsYHbmSr30g+\nGQIDAQAB\n-----END PUBLIC KEY-----",
    "xsappname": "clone12847c4c89544b4f9234b26ede429f62!b282590|destination-xsappname!b62"
  },
}

2.2 describe bootstrap destination definitions

Let's create a dest-bootstrap.json file (update payload) with two destination definitions, namely:

{
    "init_data": {
        "subaccount": {
            "destinations": [
                  {
                    "Description": "dest-httpbin",
                    "Type": "HTTP",
                    "clientId": "sb-clone12847c4c89544b4f9234b26ede429f62!b282590|destination-xsappname!b62",
                    "HTML5.DynamicDestination": "true",
                    "HTML5.Timeout": "60000",
                    "Authentication": "OAuth2ClientCredentials",
                    "Name": "dest-httpbin",
                    "tokenServiceURL": "https://<subdomain>.authentication.us10.hana.ondemand.com/oauth/token",
                    "ProxyType": "Internet",
                    "URL": "https://httpbin.org",
                    "tokenServiceURLType": "Dedicated",
                    "clientSecret": "<clientSecret>"
                  },
                  {
                    "Description": "SAP Destination Service APIs",
                    "Type": "HTTP",
                    "clientId": "sb-clone12847c4c89544b4f9234b26ede429f62!b282590|destination-xsappname!b62",
                    "HTML5.DynamicDestination": "true",
                    "HTML5.Timeout": "60000",
                    "Authentication": "OAuth2ClientCredentials",
                    "Name": "destination-service",
                    "tokenServiceURL": "https://<subdomain>.authentication.us10.hana.ondemand.com/oauth/token",
                    "ProxyType": "Internet",
                    "URL": "https://destination-configuration.cfapps.us10.hana.ondemand.com/destination-configuration/v1",
                    "tokenServiceURLType": "Dedicated",
                    "clientSecret": "<clientSecret>"
                  }
            ],
           "certificates": [
           ],

            "existing_certificates_policy": "update",
            "existing_destinations_policy": "update"           
       }
   }
}
description/purpose destination name
boostrap destination destination-service
OAuth2 token test destination dest-httpbin

2.3 create bootstrap destinations on subaccount

This payload can be then applied through the destination service instance update command.

btp update services/instance --name quovadis-master --parameters dest-bootstrap.json

This will result in provisioning of two subaccount level destinations.

image

3. Configure destination resources

3.1 dynamic_dest route with managed approuter

In order to bring managed approuter into a subaccount one needs to subscribe to SAPLaunchpad/SAP Workzone service beforehand.

btp subscribe accounts/subaccount --to-app SAPLaunchpad --plan standard 
{
  "jobId": "<jobId>"
}

Subsequently, a managed approuter dynamic_dest route can be used to call into these two subaccount level destinations:

description/purpose destination name dynamic_dest route
grab the destination service OAuth2 access token dest-httpbin https://<subdomain>.launchpad.cfapps.us10.hana.ondemand.com/dynamic_dest/dest-httpbin/headers
run destination service REST API endpoints destination-service https://<subdomain>.launchpad.cfapps.us10.hana.ondemand.com/dynamic_dest/destination-service/

3.2 SAP Cloud SDK built-in destinations

There is no generic BTP CLI comamnd to help create a destination definition from a service binding.
However, to some extent, this can be worked-around with SAP Cloud SDK built-in destinations.

The SAP Cloud SDK provides a default implementation for the transformation of service bindings of the following types:

  • business-logging
  • s4-hana-cloud
  • destination
  • saas-registry
  • workflow
  • service-manager
  • xsuaa
  • aicore

3.2.1 The below nodejs code snippet demonstrates how to leverage SAP Cloud SDK with its service binding destinations with the likes of service manager and destination services.

apiVersion: serverless.kyma-project.io/v1alpha2
kind: Function
metadata:
  name: {{ .Values.services.srv.name }}
  labels:
    {{- include "app.labels" . | nindent 4 }}
    app: {{ .Values.services.srv.name }}
spec:
  runtime: {{ .Values.services.srv.runtime }}
#  runtimeImageOverride: {{ .Values.services.srv.runtimeImageOverride }}
  source:
    inline:
      dependencies: |
        {
          "name": "{{ .Values.services.srv.name }}",
          "version": "0.0.1",
          "dependencies": {
            "axios":"latest"
            ,"debug": "latest"
            ,"@sap/xsenv": "latest"
            ,"@sap-cloud-sdk/http-client": "latest"
            ,"@sap-cloud-sdk/connectivity": "latest"
            ,"@sap-cloud-sdk/resilience": "latest"
            ,"async-retry": "latest"
          }
        }
      source: |
        const debug = require('debug')('{{ .Values.services.srv.name }}:function');
        const NOT_FOUND = 'Not Found';
        const xsenv = require('@sap/xsenv');

        const services = xsenv.getServices({
          sm: { label: 'service-manager', name: 'saas-sm' }
          ,
          dest: { label: 'destination' }

        });
        console.log('saas-sm: ', services.sm);

        const readServices = xsenv.readServices();
        console.log('readServices: ', readServices);

        const httpClient = require('@sap-cloud-sdk/http-client');

        const cloudSdkConnectivity = require('@sap-cloud-sdk/connectivity');
        const { retrieveJwt, decodeJwt, Destination } = require('@sap-cloud-sdk/connectivity');
        const { setGlobalLogLevel, createLogger } = require('@sap-cloud-sdk/util');
        const { retry } = require ('@sap-cloud-sdk/resilience');
        const { resilience } = require ('@sap-cloud-sdk/resilience');
        const ResilienceOptions = {
          retry: 10,
          circuitBreaker: false,
          timeout: 300*1000 // 5 minutes in milliseconds
        };          

        const retryme = require('async-retry');

        setGlobalLogLevel('debug');
        const logger = createLogger('http-logs');

        module.exports = {
          main: async function (event, context) {
            const req = event.extensions.request;

            const message = `Hello World`
              + ` from the Kyma Function ${context['function-name']}`
              + ` running on ${context.runtime}!`
              + ` with the request headers ${JSON.stringify(req.headers,0,2)}`;
            console.log(message);
            
            if (typeof req.path !== undefined) {
              console.log('path: ', JSON.stringify(req.path,0,2))
            }
            if (typeof req.params !== undefined) {
              console.log('params: ', JSON.stringify(req.params,0,2))
            }
            if (typeof req.url !== undefined) {
              console.log('url: ', JSON.stringify(req.url,0,2))
            }
            if (typeof req.authInfo !== undefined) {
              console.log('authInfo: ', JSON.stringify(req.authInfo,0,2))
            }

            const { pathname } = new URL(req.url || '', `https://${req.headers.host}`)
            console.log('pathname: ', pathname)

            const url = require("url");
            var url_parts = url.parse(req.url);
            console.log(url_parts);
            console.log(url_parts.pathname);

            // returns an array with paths
            let path_array = req.url.match('^[^?]*')[0].split('/').slice(1);
            console.log(path_array)

            console.log(req.url.match('^[^?]*')[0])

            if (!path_array?.length) return 'Please use an API verb';  
            const actions = [ 
               { name: 'offerings', verb: 'service_offerings', dest:  'saas-sm', url: '/v1/' },
               { name: 'plans', verb: 'service_plans', dest:  'saas-sm', url: '/v1/'  },
               { name: 'instances', verb: 'service_instances', dest:  'saas-sm', url: '/v1/'  },
               { name: 'bindings', verb: 'service_bindings', dest:  'saas-sm', url: '/v1/'  },
               { name: 'instanceDestinations', verb: 'instanceDestinations', dest:  'faas-dest-x509', url: '/destination-configuration/v1/'  },
               { name: 'subaccountDestinations', verb: 'subaccountDestinations', dest:  'faas-dest-x509' , url: '/destination-configuration/v1/' }
            ];
            
            const action = actions.find( ({ name }) => name === path_array[1] )
            console.log('action found: ', action)

            if (path_array[0] == 'srv' &&  action !== undefined) {

              path_array = req.url.match('^[^?]*')[0].split('/').slice(2);

              console.log('path_array: ', path_array)

              const queryString = req.query;
              console.log('queryString: ', queryString)
              const urlParams = new URLSearchParams(queryString);

              const params = req.params;
              console.log('params: ', params) 

              try {
                  // https://sap.github.io/cloud-sdk/docs/js/features/connectivity/destinations#service-binding-environment-variables
                  const endpoint =  path_array[1] !== undefined ? '/' + path_array[1] : '';
                  console.log(endpoint)
                  let res = await httpClient.executeHttpRequest({ destinationName: action.dest }, {
                      method: 'GET',
                      url: action.url + action.verb + endpoint
                  });
                  return res.data;
              } catch (err) {
                  console.log(err.stack);
                  return err.message;
              }
              
            }            
          }
        }
   scaleConfig:
    maxReplicas: 5
    minReplicas: 3
  resourceConfiguration:
    function:
      profile: S
  env: ## https://kyma-project.io/docs/kyma/latest/05-technical-reference/00-configuration-parameters/svls-02-environment-variables/#node-js-runtime-specific-environment-variables
    - name: FUNC_TIMEOUT ## Specifies the number of seconds in which a runtime must execute the code.
      value: '1800'
    - name: REQ_MB_LIMIT ## payload body size limit in megabytes.
      value: "10"

    - name: DEBUG
      value: '{{ .Values.services.srv.name }}:*'
    - name: SERVICE_BINDING_ROOT
      value: /bindings
    

  secretMounts: 
    - secretName: {{ .Values.services.sm.bindingSecretName }}
      mountPath: "/bindings/saas-sm"
    - secretName: {{ .Values.services.dest.bindingSecretNamex509 }}
      mountPath: "/bindings/faas-dest-x509"

In a nutshell, it is a one-liner httpClient.executeHttpRequest call, namely:

 const res = await httpClient.executeHttpRequest({ destinationName: action.dest }, {
     method: 'GET',
     url: action.url + action.verb + endpoint
 });
 return res.data;

Documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment