diff --git a/README.md b/README.md index cf12710c..f202886c 100644 --- a/README.md +++ b/README.md @@ -241,47 +241,62 @@ Before you can access the UI using the (tenant-specific) URL to the bookshop(-mt - Container Registry (e.g. [Docker Hub](https://hub.docker.com/)) - Command Line Tools: [`kubectl`](https://kubernetes.io/de/docs/tasks/tools/install-kubectl/), [`kubectl-oidc_login`](https://github.com/int128/kubelogin#setup), [`pack`](https://buildpacks.io/docs/tools/pack/), [`docker`](https://docs.docker.com/get-docker/), [`helm`](https://helm.sh/docs/intro/install/), [`cf`](https://docs.cloudfoundry.org/cf-cli/install-go-cli.html) - Logged into Kyma Runtime (with `kubectl` CLI), Cloud Foundry space (with `cf` CLI) and Container Registry (with `docker login`) -- `@sap/cds-dk` >= 6.0.1 +- `@sap/cds-dk` >= 6.6.0 ### Add Deployment Files -CAP tooling provides your a Helm chart for deployment to Kyma. +CAP tooling provides you a Helm chart for deployment to Kyma. -Add the CAP Helm chart with the required features to this project: +For single tenant deployment, replace the `requires` section in _`.cdsrc.json`_ with: -```bash -cds add helm -cds add hana -cds add xsuaa -cds add html5-repo +``` + "requires": { + "auth": { + "kind": "xsuaa" + }, + "approuter": { + "kind": "cloudfoundry" + }, + "db": { + "kind": "hana-cloud" + } + }, ``` -#### Helm chart configuration +For multi tenant deployment, replace the `requires` section in _`.cdsrc.json`_ with: -This project contains a pre-configured configuration file `values.yaml`, you just need to do the following changes in this file: +``` + "requires": { + "multitenancy": true, + "extensibility": true, + "toggles": true, + "auth": { + "kind": "xsuaa" + }, + "approuter": { + "kind": "cloudfoundry" + } + }, +``` -- `` - full-qualified hostname of your container registry -- `domain`- full-qualified domain name used to access applications in your Kyma cluster +Add the CAP Helm chart with the required features to this project: + +```bash +cds add helm +``` -#### Use API_BUSSINESS_PARTNER Remote Service (optional) +#### Use API_BUSSINESS_PARTNER Remote Service (optional, single tenant only) You can try the `API_BUSINESS_PARTNER` service with a real S/4HANA system with the following configuration: 1. Create either an on-premise or cloud destination in your subaccount. -2. Add the binding to the destination service for the service (`srv`) to the `values.yaml` file: +2. Add configuration required for the destination service by executing the following command. - ```yaml - srv: - ... - bindings: - ... - destinations: - serviceInstanceName: destinations + ```bash + cds add destination ``` - (The destination service instance is already configured) - 3. Set the profiles `cloud` and `destination` active in your `values.yaml` file: ```yaml @@ -289,9 +304,6 @@ You can try the `API_BUSINESS_PARTNER` service with a real S/4HANA system with t ... env: SPRING_PROFILES_ACTIVE: cloud,destination - # TODO: To be removed after @sap/cds-dk patch - CDS_ENVIRONMENT_K8S_SERVICEBINDINGS_CONNECTIVITY_SECRETSPATH: '/bindings/connectivity' - CDS_ENVIRONMENT_K8S_SERVICEBINDINGS_CONNECTIVITY_SERVICE: 'connectivity' ``` 4. For on-premise only: Add the connectivity service to your Helm chart: @@ -300,6 +312,8 @@ You can try the `API_BUSINESS_PARTNER` service with a real S/4HANA system with t cds add connectivity ``` + Note: `cds add helm` will not add configuration required to create a Connectivity Service Instance. This Service Instance should be created by the Kyma Cluster Administrator. For more information regarding configuration of Connectivity Instance, please check the [documentation](https://cap.cloud.sap/docs/guides/deployment/deploy-to-kyma#connectivity-service). + *See also: [API_BUSINESS_PARTNER Remote Service and Spring Profiles](#api_business_partner-remote-service-and-spring-profiles)* ### Prepare Kubernetes Namespace @@ -314,78 +328,198 @@ bash ./scripts/create-container-registry-secret.sh The *Docker Server* is the full qualified hostname of your container registry. -#### Create a HDI container and a secret +#### Create a HDI container / Service Manager Instance and a Secret + +This step is only required if you're using a BTP Trial account. If you're using a production or a free tier account then you can create HDI Container from Kyma directly by adding a [mapping to your Kyma namespace in your HANA Cloud Instance](https://blogs.sap.com/2022/12/15/consuming-sap-hana-cloud-from-the-kyma-environment/) and skip this step. + +##### Single Tenant ``` bash ./scripts/create-db-secret.sh bookshop-db ``` -It will create a HDI container `bookshop-db` on your currently targeted Cloud Foundry space and creates a secret `bookshop-db` with the HDI container's credentials in your current Kubernetes namespace. +It will create a HDI container `bookshop-db` instance on your currently targeted Cloud Foundry space and a secret `bookshop-db` with the credentials in your current Kubernetes namespace. -### Build +Make the following changes to your _`chart/values.yaml`_. + +```diff +srv: + bindings: + db: +- serviceInstanceName: hana ++ fromSecret: bookshop-db +... -**Build data base deployer image:** +hana-deployer: + bindings: + hana: +- serviceInstanceName: hana ++ fromSecret: bookshop-db +... +- hana: +- serviceOfferingName: hana +- servicePlanName: hdi-shared ``` -cds build --production -pack build $YOUR_CONTAINER_REGISTRY/bookshop-hana-deployer \ - --path db \ - --buildpack gcr.io/paketo-buildpacks/nodejs \ - --builder paketobuildpacks/builder:base +##### Multi Tenant + +``` +bash ./scripts/create-sm-secret.sh bookshop-sm ``` -(Replace `$YOUR_CONTAINER_REGISTRY` with the full-qualified hostname of your container registry) +It will create a Service Manager `bookshop-sm` instance on your currently targeted Cloud Foundry space and a secret `bookshop-sm` with the credentials in your current Kubernetes namespace. +Make the following changes to your _`chart/values.yaml`_. -**Build image for CAP service:** +```diff +srv: + bindings: + service-manager: +- serviceInstanceName: service-manager ++ fromSecret: bookshop-sm +... +sidecar: + bindings: + service-manager: +- serviceInstanceName: service-manager ++ fromSecret: bookshop-sm + +... +- service-manager: +- serviceOfferingName: service-manager +- servicePlanName: container ``` -mvn package + +### Build + +```bash +cds build --production ``` +**Build image for CAP service:** + +```bash +mvn clean package -DskipTests=true ``` + +```bash pack build $YOUR_CONTAINER_REGISTRY/bookshop-srv \ --path srv/target/*-exec.jar \ --buildpack gcr.io/paketo-buildpacks/sap-machine \ --buildpack gcr.io/paketo-buildpacks/java \ --builder paketobuildpacks/builder:base \ - --env SPRING_PROFILES_ACTIVE=cloud + --env SPRING_PROFILES_ACTIVE=cloud \ + --env BP_JVM_VERSION=17 ``` -**Build HTML5 application deployer image:** +(Replace `$YOUR_CONTAINER_REGISTRY` with the full-qualified hostname of your container registry) +**Build Approuter Image:** + +```bash +pack build $YOUR_CONTAINER_REGISTRY/bookshop-approuter \ + --path app \ + --buildpack gcr.io/paketo-buildpacks/nodejs \ + --builder paketobuildpacks/builder:base \ + --env BP_NODE_RUN_SCRIPTS="" +``` + +**Build database deployer image (single tenant only):** + +```bash +pack build $YOUR_CONTAINER_REGISTRY/bookshop-hana-deployer \ + --path db \ + --buildpack gcr.io/paketo-buildpacks/nodejs \ + --builder paketobuildpacks/builder:base \ + --env BP_NODE_RUN_SCRIPTS="" ``` -bash ./scripts/build-ui-image.sh + +**Build sidecar image (multi tenant only):** + +```bash +pack build $YOUR_CONTAINER_REGISTRY/bookshop-sidecar \ + --path mtx/sidecar/gen \ + --buildpack gcr.io/paketo-buildpacks/nodejs \ + --builder paketobuildpacks/builder:base \ + --env BP_NODE_RUN_SCRIPTS="" ``` ### Push container images You can push all the container images to your container registry, using: +```bash +docker push $YOUR_CONTAINER_REGISTRY/bookshop-srv + +docker push $YOUR_CONTAINER_REGISTRY/bookshop-approuter ``` -docker push $YOUR_CONTAINER_REGISTRY/bookshop-hana-deployer -docker push $YOUR_CONTAINER_REGISTRY/bookshop-srv +#### Single Tenant -docker push $YOUR_CONTAINER_REGISTRY/bookshop-html5-deployer +```bash +docker push $YOUR_CONTAINER_REGISTRY/bookshop-hana-deployer ``` -### Deployment +#### Multi Tenant +```bash +docker push $YOUR_CONTAINER_REGISTRY/bookshop-sidecar +``` + +### Configuration + +Make the following changes in the _`chart/values.yaml`_ file. + +1. Change value of `global.domain` key to your cluster domain. + +2. Replace `` in `xsuaa.parameters.oauth2-configuration.redirect-uris` with your cluster domain. + +3. Replace `` with your container registry. + +4. Make the following change to add backend destinations required by Approuter. + +```diff +- backendDestinations: {} ++ backendDestinations: ++ backend: ++ service: srv ++ mtx-api: ++ service: srv ``` -helm upgrade bookshop ./chart --install -f values.yaml + +5. Add your image registry secret created in [Create container registry secret](#create-container-registry-secret) step. + +```diff +global: + domain: null +- imagePullSecret: {} ++ imagePullSecret: ++ name: container-registry ``` -### Access the UI +### Deployment + +Deploy the helm chart using the following command: + +#### Single Tenant + +```bash +helm install bookshop ./chart --set-file xsuaa.jsonParameters=xs-security.json +``` Before you can access the UI you should make sure to [Setup Authorizations in SAP Business Technology Platform](#setup-authorizations-in-sap-business-technology-platform). -1. Create a Launchpad Service subscription in the BTP Cockpit -2. Go to **HTML5 Applications** -3. Start any of the HTML5 applications +Click on the approuter url logged by the `helm install` to access the UI. + +#### Multi Tenant + +```bash +helm install bookshop ./chart --set-file xsuaa.jsonParameters=xs-security-mt.json +``` -Additionally, you can add the UIs to a Launchpad Service site like it is described in in the last two steps of [this tutorial](https://developers.sap.com/tutorials/btp-app-kyma-launchpad-service.html#9aab2dd0-18ea-4ccd-bc44-24e87c845740). +In case of multi tenant, you'll have to subscribe to the application from a different subaccount. You can follow the steps mentioned [here](https://cap.cloud.sap/docs/guides/deployment/as-saas#subscribe) to access the application. ## Setup Authorizations in SAP Business Technology Platform diff --git a/scripts/build-ui-image.sh b/scripts/build-ui-image.sh deleted file mode 100755 index bef48493..00000000 --- a/scripts/build-ui-image.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -set -e -cd "$(dirname "$(dirname "$0")")" - -npm install --no-save yaml - -function value() { - node ./scripts/value.js "$1" -} - -function image() { - local REPOSITORY="$(value "$1.image.repository")" - local TAG="$(value "$1.image.tag")" - if [ "$TAG" != "" ]; then - echo "$REPOSITORY:$TAG" - else - echo "$REPOSITORY" - fi -} - -rm -rf gen/ui -mkdir -p gen/ui/resources - -CLOUD_SERVICE="$(value html5_apps_deployer.cloudService)" -DESTINATIONS="$(value html5_apps_deployer.backendDestinations)" - -IMAGE="$(image html5_apps_deployer)" - -for APP in app/*; do - if [ -f "$APP/webapp/manifest.json" ]; then - echo "Build $APP..." - echo - - rm -rf "gen/$APP" - mkdir -p "gen/app" - cp -r "$APP" gen/app - pushd >/dev/null "gen/$APP" - - node ../../../scripts/prepareUiFiles.js $CLOUD_SERVICE $DESTINATIONS - npm install - npx ui5 build preload --clean-dest --config ui5-deploy.yaml --include-task=generateManifestBundle generateCachebusterInfo - cd dist - rm manifest-bundle.zip - mv *.zip ../../../ui/resources - - popd >/dev/null - fi -done - -cd gen/ui - -echo -echo "HTML5 Apps:" -ls -l resources -echo - -cat >package.json <&2 "[ERROR] Please either specify the name for the service manager secret or maintain it in the Helm chart" + exit 1 + fi + NAME="$(value .srv.bindings.db.fromSecret)" + if [ "$NAME" == "" -o "$NAME" == "" ]; then + echo >&2 "[ERROR] Please either specify the name for the service manager secret or maintain it in the Helm chart" + exit 1 + fi +fi + +SECRET_HEADER="$(cat </dev/null >/dev/null service $NAME || cf create-service service-manager container $NAME +while true; do + STATUS="$(cf 2>/dev/null service $NAME | grep status: | head -n 1)" + echo $STATUS + if [[ "$STATUS" = *succeeded* ]]; then + break + fi + sleep 1 +done + +cf create-service-key $NAME $NAME-key + +node "$(dirname "$0")/format-kyma-secret.js" -- "$(echo "$SECRET_HEADER")" "$(cf service-key $NAME $NAME-key)" | kubectl apply -f - +echo +echo "Service Manager container secret '$NAME' created." +echo +echo "You can view it using:" +echo +echo "kubectl get secret $NAME -o yaml" +exit 0 \ No newline at end of file diff --git a/scripts/prepareUiFiles.js b/scripts/prepareUiFiles.js deleted file mode 100644 index 2fd750e1..00000000 --- a/scripts/prepareUiFiles.js +++ /dev/null @@ -1,166 +0,0 @@ - -const Path = require('path'); -const posixJoin = Path.posix.join; - -function prepareUiFiles(path, options) { - const { readFileSync, writeFileSync, existsSync } = require('fs'); - const Path = require('path'); - - const srvDestination = getSrvDestination(options.destinations); - - const packageJsonInclude = getPackageJsonInclude(); - const ui5DeployTemplate = getUI5DeployTemplateYaml(); - const xsAppJsonTemplate = getXsAppTemplateJson(); - - const packageJsonPath = Path.join(path, 'package.json'); - const manifestJsonPath = Path.join(path, 'webapp/manifest.json'); - const packageJson = JSON.parse(readFileSync(packageJsonPath)); - const manifestJson = JSON.parse(readFileSync(manifestJsonPath)); - - // Adjust package.json - packageJson.devDependencies = packageJsonInclude.devDependencies; - packageJson.ui5 = packageJsonInclude.ui5; - writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 4)); - - // Adjust manifest.json - console.log('Adjust webapp/manifest.json'); - manifestJson["sap.cloud"] = { - "public": true, - "service": options.cloudService || throwError(`Missing cloudService name`) - }; - - if (!existsSync(Path.join(path, 'xs-app.json'))) { - const dataSources = manifestJson['sap.app'].dataSources || {}; - for (const dataSource of Object.values(dataSources)) { - dataSource.uri = posixJoin('service', dataSource.uri); - } - } - - writeFileSync(manifestJsonPath, JSON.stringify(manifestJson, null, 4)); - - // Add xs-app.json - if (!existsSync(Path.join(path, 'xs-app.json'))) { - console.log('Add xs-app.json'); - const xsAppJson = JSON.parse(JSON.stringify(xsAppJsonTemplate)); - xsAppJson.routes[0].destination = srvDestination || throwError('Expect srv destination'); - writeFileSync(Path.join(path, 'xs-app.json'), JSON.stringify(xsAppJson, null, 4)); - } - - // Add ui5-deploy.yaml - if (!existsSync(Path.join(path, 'ui5-deploy.yaml'))) { - console.log('Add ui5-deploy.yaml'); - const appId = manifestJson["sap.app"].id; - const replacements = { - "ID": appId, - "ARCHIVENAME": appId.replace(/\./g, "") - }; - - const ui5Deploy = ui5DeployTemplate.replace(/\$([A-Z]+|)/g, (_, v) => replacements[v]); - writeFileSync(Path.join(path, "ui5-deploy.yaml"), ui5Deploy); - } -} - -function throwError(msg) { - throw new Error(msg); -} - -function getPackageJsonInclude() { - return { - "name": "ui5-builde-root", - "devDependencies": { - "@ui5/cli": "^2.11.1", - "@ui5/fs": "^2.0.6", - "@ui5/logger": "^2.0.1", - "@sap/ux-ui5-tooling": "1", - "rimraf": "3.0.2", - "@sap/ui5-builder-webide-extension": "1.0.x", - "ui5-task-zipper": "^0.3.1", - "mbt": "^1.0.15" - }, - "ui5": { - "dependencies": [ - "@sap/ui5-builder-webide-extension", - "ui5-task-zipper", - "mbt" - ] - } - } -} - -function getUI5DeployTemplateYaml() { - return `specVersion: '2.4' -metadata: - name: $ID -type: application -resources: - configuration: - propertiesFileSourceEncoding: UTF-8 -builder: - resources: - excludes: - - "/test/**" - - "/localService/**" - customTasks: - - name: webide-extension-task-updateManifestJson - beforeTask: generateManifestBundle - configuration: - appFolder: webapp - destDir: dist - - name: ui5-task-zipper - afterTask: generateCachebusterInfo - configuration: - archiveName: $ARCHIVENAME - additionalFiles: - - xs-app.json` -} - -function getXsAppTemplateJson() { - return { - "welcomeFile": "/index.html", - "authenticationMethod": "route", - "routes": [ - { - "source": "^/service/(.*)$", - "target": "$1", - "destination": "overwrite-me", - "authenticationType": "xsuaa", - "csrfProtection": false - }, - { - "source": "^/resources/(.*)$", - "target": "/resources/$1", - "authenticationType": "none", - "destination": "ui5" - }, - { - "source": "^/test-resources/(.*)$", - "target": "/test-resources/$1", - "authenticationType": "none", - "destination": "ui5" - }, - { - "source": "^(.*)$", - "target": "$1", - "service": "html5-apps-repo-rt", - "authenticationType": "xsuaa" - } - ] - } - -} - -function getSrvDestination(destinations) { - let destinationsJSON = JSON.parse(destinations); - for (let key in destinationsJSON) { - if(destinationsJSON[key].service === "srv") { - return key; - } - } -} - -if (process.argv[1].endsWith('prepareUiFiles.js')) { - // Run in standalone mode - prepareUiFiles('.', { cloudService: process.argv[2], destinations: process.argv[3] }); -} else { - module.exports = prepareUiFiles; -} \ No newline at end of file diff --git a/values.yaml b/values.yaml deleted file mode 100644 index 6439db80..00000000 --- a/values.yaml +++ /dev/null @@ -1,43 +0,0 @@ -global: - # >>> Execute the following command to receive the domain: - # $ kubectl get gateway -n kyma-system kyma-gateway -o jsonpath='{.spec.servers[0].hosts[0]}' - # Remove the leading "*." - domain: null - imagePullSecret: - name: container-registry -hana_deployer: - image: - # >>> Replace with your container registry - repository: /bookshop-hana-deployer - tag: latest - bindings: - hana: - fromSecret: bookshop-db -html5_apps_deployer: - cloudService: java.bookshop - backendDestinations: - bookshop-srv: - service: srv - image: - # >>> Replace with your container registry - repository: /bookshop-html5-deployer - tag: latest -srv: - bindings: - db: - fromSecret: bookshop-db - image: - # >>> Replace with your container registry - repository: /bookshop-srv - tag: latest - env: - SPRING_PROFILES_ACTIVE: cloud - resources: - limits: - cpu: 2000m - ephemeral-storage: 1G - memory: 2G - requests: - cpu: 1000m - ephemeral-storage: 1G - memory: 2G