├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── question.md ├── dependabot.yml └── workflows │ ├── build.yml │ └── codeql-analysis.yml ├── .gitignore ├── .prettierrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── LICENSES ├── Apache-2.0.txt └── LicenseRef-API-Definition-File-License.txt ├── README.md ├── REUSE.toml ├── SECURITY.md ├── logo.png ├── package-lock.json ├── package.json ├── samples ├── cds-sample-application │ ├── .cdsrc.json │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── db │ │ ├── data-model.cds │ │ └── data │ │ │ └── bupa-CapBusinessPartner.csv │ ├── manifest.yml │ ├── nest-cli.json │ ├── package-lock.json │ ├── package.json │ ├── resources │ │ └── service-specs │ │ │ ├── API_BUSINESS_PARTNER.edmx │ │ │ └── options-per-service.json │ ├── src │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── business-partner │ │ │ └── business-partner-service-handler.ts │ │ └── main.ts │ ├── srv │ │ └── business-partner-service.cds │ ├── test │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── cf-multi-tenant-application │ ├── .prettierrc │ ├── README.md │ ├── approuter │ │ ├── manifest.yml │ │ ├── package.json │ │ └── xs-app.json │ ├── multi-tenant-app │ │ ├── manifest.yml │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ │ ├── application.ts │ │ │ ├── dependencies-endpoint.ts │ │ │ ├── index.html │ │ │ ├── server.ts │ │ │ ├── service-endpoint.ts │ │ │ ├── subscription-endpoint.ts │ │ │ └── subscription-util.ts │ │ └── tsconfig.json │ ├── package-lock.json │ ├── package.json │ └── service-config │ │ ├── saas-registry-config.json │ │ └── xs-security.json ├── cf-sample-application │ ├── README.md │ ├── approuter │ │ ├── manifest.yml │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── package.sh │ │ ├── static-resources │ │ │ └── index.html │ │ ├── xs-app.json │ │ └── xs-security.json │ ├── e2e-tests │ │ ├── cypress.json │ │ ├── cypress │ │ │ ├── fixtures │ │ │ │ └── example.json │ │ │ ├── integration │ │ │ │ └── e2e.spec.ts │ │ │ ├── plugins │ │ │ │ └── index.js │ │ │ └── support │ │ │ │ ├── commands.js │ │ │ │ └── index.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── tsconfig.json │ ├── manifest.yml │ ├── nest-cli.json │ ├── package-lock.json │ ├── package.json │ ├── resources │ │ └── service-specs │ │ │ ├── API_BUSINESS_PARTNER.edmx │ │ │ ├── OP_API_BUSINESS_PARTNER_SRV.edmx │ │ │ └── options-per-service.json │ ├── src │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── cloud-business-partner │ │ │ ├── cloud-business-partner.service.spec.ts │ │ │ └── cloud-business-partner.service.ts │ │ ├── loadtest │ │ │ ├── loadtest.service.spec.ts │ │ │ └── loadtest.service.ts │ │ ├── main.ts │ │ ├── onpremise-business-partner │ │ │ ├── onpremise-business-partner.service.spec.ts │ │ │ └── onpremise-business-partner.service.ts │ │ └── principal-business-partner │ │ │ ├── principal-business-partner.service.spec.ts │ │ │ └── principal-business-partner.service.ts │ ├── systems.json │ ├── test │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── helm-sample-application │ ├── README.md │ ├── application │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── approuter │ │ │ ├── Dockerfile │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ └── static-resources │ │ │ │ └── index.html │ │ ├── e2e-tests │ │ │ ├── cypress.json │ │ │ ├── cypress │ │ │ │ ├── fixtures │ │ │ │ │ └── example.json │ │ │ │ ├── integration │ │ │ │ │ └── e2e.spec.ts │ │ │ │ ├── plugins │ │ │ │ │ └── index.js │ │ │ │ └── support │ │ │ │ │ ├── commands.js │ │ │ │ │ └── index.js │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ └── tsconfig.json │ │ ├── images │ │ │ └── cluster_arch.png │ │ ├── nest-cli.json │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── pipeline │ │ │ └── Dockerfile │ │ ├── resources │ │ │ └── service-specs │ │ │ │ ├── API_BUSINESS_PARTNER.edmx │ │ │ │ ├── OP_API_BUSINESS_PARTNER_SRV.edmx │ │ │ │ └── options-per-service.json │ │ ├── src │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── cloud-business-partner │ │ │ │ ├── cloud-business-partner.service.spec.ts │ │ │ │ └── cloud-business-partner.service.ts │ │ │ ├── loadtest │ │ │ │ ├── loadtest.service.spec.ts │ │ │ │ └── loadtest.service.ts │ │ │ ├── main.ts │ │ │ ├── onpremise-business-partner │ │ │ │ ├── onpremise-business-partner.service.spec.ts │ │ │ │ └── onpremise-business-partner.service.ts │ │ │ └── principal-business-partner │ │ │ │ ├── principal-business-partner.service.spec.ts │ │ │ │ └── principal-business-partner.service.ts │ │ ├── systems.json │ │ ├── test │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── helm-chart │ │ ├── README.md │ │ ├── k8s-e2e-app-helm-0.1.6.tgz │ │ ├── k8s-e2e-app-helm │ │ │ ├── .helmignore │ │ │ ├── Chart.yaml │ │ │ ├── charts │ │ │ │ ├── app-chart │ │ │ │ │ ├── .helmignore │ │ │ │ │ ├── Chart.yaml │ │ │ │ │ ├── templates │ │ │ │ │ │ ├── NOTES.txt │ │ │ │ │ │ ├── _helpers.tpl │ │ │ │ │ │ ├── configmap.yaml │ │ │ │ │ │ ├── deployment.yaml │ │ │ │ │ │ ├── service.yaml │ │ │ │ │ │ └── tests │ │ │ │ │ │ │ └── test-connection.yaml │ │ │ │ │ └── values.yaml │ │ │ │ └── approuter-chart │ │ │ │ │ ├── .helmignore │ │ │ │ │ ├── Chart.yaml │ │ │ │ │ ├── templates │ │ │ │ │ ├── NOTES.txt │ │ │ │ │ ├── _helpers.tpl │ │ │ │ │ ├── configmap.yaml │ │ │ │ │ ├── deployment.yaml │ │ │ │ │ ├── service.yaml │ │ │ │ │ └── tests │ │ │ │ │ │ └── test-connection.yaml │ │ │ │ │ └── values.yaml │ │ │ ├── templates │ │ │ │ ├── NOTES.txt │ │ │ │ ├── _helpers.tpl │ │ │ │ ├── api-gateway.yaml │ │ │ │ ├── ingress.yaml │ │ │ │ └── issuer.yaml │ │ │ └── values.yaml │ │ └── values.yaml │ └── sap-btp-operator │ │ ├── certificates │ │ └── self-signed-issuer.yml │ │ ├── sap-btp-operator-v0.1.9.tgz │ │ ├── services │ │ ├── connectivity │ │ │ ├── operator-connectivity-binding.yml │ │ │ └── operator-connectivity-service.yml │ │ ├── destination │ │ │ ├── operator-destination-binding.yml │ │ │ └── operator-destination-service.yml │ │ └── xsuaa │ │ │ ├── operator-xsuaa-binding.yml │ │ │ └── operator-xsuaa-service.yml │ │ └── values.yaml ├── http-client-examples │ ├── .prettierignore │ ├── README.md │ ├── http-client-httpbin.spec.ts │ ├── http-client.spec.ts │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ ├── server.ts │ └── tsconfig.json ├── k8s-sample-application │ ├── Dockerfile │ ├── README.md │ ├── approuter │ │ ├── Dockerfile │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── static-resources │ │ │ └── index.html │ │ └── xs-app.json │ ├── e2e-tests │ │ ├── cypress.json │ │ ├── cypress │ │ │ ├── fixtures │ │ │ │ └── example.json │ │ │ ├── integration │ │ │ │ └── e2e.spec.ts │ │ │ ├── plugins │ │ │ │ └── index.js │ │ │ └── support │ │ │ │ ├── commands.js │ │ │ │ └── index.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── tsconfig.json │ ├── k8s_files │ │ ├── app │ │ │ ├── deployment.yml │ │ │ └── k8s-e2e-service.yml │ │ ├── approuter │ │ │ ├── approuter-service.yml │ │ │ └── deployment.yml │ │ ├── ingress.yml │ │ └── operator │ │ │ ├── certificates │ │ │ └── self-signed-issuer.yml │ │ │ └── services │ │ │ ├── connectivity │ │ │ ├── operator-connectivity-binding.yml │ │ │ └── operator-connectivity-service.yml │ │ │ ├── destination │ │ │ ├── operator-destination-binding.yml │ │ │ └── operator-destination-service.yml │ │ │ └── xsuaa │ │ │ ├── operator-xsuaa-binding.yml │ │ │ └── operator-xsuaa-service.yml │ ├── nest-cli.json │ ├── package-lock.json │ ├── package.json │ ├── pipeline │ │ └── Dockerfile │ ├── resources │ │ └── service-specs │ │ │ ├── API_BUSINESS_PARTNER.edmx │ │ │ ├── OP_API_BUSINESS_PARTNER_SRV.edmx │ │ │ └── options-per-service.json │ ├── src │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── cloud-business-partner │ │ │ ├── cloud-business-partner.service.spec.ts │ │ │ └── cloud-business-partner.service.ts │ │ ├── main.ts │ │ ├── onpremise-business-partner │ │ │ ├── onpremise-business-partner.service.spec.ts │ │ │ └── onpremise-business-partner.service.ts │ │ └── principal-business-partner │ │ │ ├── principal-business-partner.service.spec.ts │ │ │ └── principal-business-partner.service.ts │ ├── systems.json │ ├── test │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json └── resilience-examples │ ├── .eslintrc.js │ ├── .prettierrc │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── resources │ └── service-specs │ │ ├── API_BUSINESS_PARTNER.edmx │ │ └── options-per-service.json │ ├── src │ ├── resilience.spec.ts │ └── resilience.ts │ └── tsconfig.json └── scripts ├── clean-install-all-samples.ts ├── fetch-service-spec-cli.ts ├── fetch-service-spec.ts ├── samples-folders.ts └── set-sdk-version.ts /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: You found an bug in one of the samples, please use this issue. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | 10 | 11 | **Describe the bug** 12 | A clear and concise description of what the bug is. 13 | 14 | **To Reproduce** 15 | Steps to reproduce the behavior: 16 | 17 | 1. Set up ... 18 | 2. Execute ... 19 | 3. Confirm ... 20 | 4. See error 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Screenshots** 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | **Used Versions:** 29 | 30 | - node version via `node -v` 31 | - npm version via `npm -v` 32 | - SAP Cloud SDK version you used as dependency 33 | 34 | **Code Examples** 35 | If applicable, add code snippets as examples to help explain your problem. Please remove sensitive information. 36 | 37 | **Log file** 38 | If applicable, add your log file or related error message. Again, please remove your sensitive information. 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about the SAP Cloud SDK Samples. 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | --- 8 | 9 | 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: '/samples/cds-sample-application' 5 | schedule: 6 | interval: daily 7 | time: '01:00' 8 | timezone: 'Europe/Berlin' 9 | open-pull-requests-limit: 5 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: daily 14 | time: '01:00' 15 | timezone: 'Europe/Berlin' 16 | open-pull-requests-limit: 5 17 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: ~ 5 | push: 6 | branches: [main] 7 | paths: 8 | - 'samples/cds-sample-application/**' 9 | permissions: 10 | pull-requests: write 11 | contents: write 12 | jobs: 13 | checks: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: REUSE Compliance Check 18 | uses: fsfe/reuse-action@v5 19 | tests: 20 | needs: [checks] 21 | runs-on: ${{ matrix.os }} 22 | timeout-minutes: 15 23 | strategy: 24 | matrix: 25 | os: [ubuntu-latest] 26 | node-version: [22] 27 | steps: 28 | - uses: actions/checkout@v4 29 | - run: git fetch --depth=1 30 | - uses: actions/setup-node@v4 31 | with: 32 | node-version: ${{ matrix.node-version }} 33 | - name: CDS Samples - Install Dependencies and Build Package 34 | run: | 35 | cd samples/cds-sample-application 36 | npm install 37 | npm run ci-build 38 | - name: CDS Samples - Tests 39 | run: | 40 | cd samples/cds-sample-application 41 | npm run test:e2e 42 | - name: Resilience Examples - Install Dependencies 43 | run: | 44 | cd samples/resilience-examples 45 | npm install 46 | - name: Resilience Examples - Tests 47 | run: | 48 | cd samples/resilience-examples 49 | npm run test 50 | - name: HTTP Client Examples - Install Dependencies 51 | run: | 52 | cd samples/http-client-examples 53 | npm ci 54 | - name: HTTP Client Examples - Tests 55 | run: | 56 | cd samples/http-client-examples 57 | npm run test 58 | dependabot: 59 | needs: tests 60 | runs-on: ubuntu-latest 61 | if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'pull_request' }} 62 | steps: 63 | - name: Dependabot metadata 64 | id: metadata 65 | uses: dependabot/fetch-metadata@v2.4.0 66 | with: 67 | github-token: "${{ secrets.GITHUB_TOKEN }}" 68 | - name: Approve a PR 69 | run: gh pr review --approve "$PR_URL" 70 | env: 71 | PR_URL: ${{github.event.pull_request.html_url}} 72 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 73 | - name: Enable auto-merge for Dependabot PRs 74 | run: gh pr merge --auto --squash "$PR_URL" 75 | env: 76 | PR_URL: ${{github.event.pull_request.html_url}} 77 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '25 12 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'typescript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v3 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v3 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | dist 3 | node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json 35 | credentials.json 36 | /s4hana_pipeline 37 | deployment 38 | 39 | # Ignore .env files 40 | .env 41 | cypress.env.json 42 | 43 | # Ignore cypress screenshots and videos 44 | screenshots 45 | videos 46 | .env 47 | generated 48 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/prettierrc", 3 | "singleQuote": true, 4 | "filepath": "*.ts", 5 | "trailingComma": "none", 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Thank you for taking your time to contribute to our samples repository. 3 | 4 | ## How to contribute 5 | When contributing to this repository, please first discuss the changes you wish to make through an issue, email, or any other method with the owners of this repository. 6 | 7 | Please note, that we have a [code of conduct](./CODE_OF_CONDUCT.md), please follow it in all your interactions with the project. 8 | 9 | Once you are ready to make a change, please test it appropriately, create a pull request and describe your change in the pull request. The owners of the repository will review your changes as soon as possible. 10 | 11 | ## Developer Certificate of Origin (DCO) 12 | Due to legal reasons, contributors will be asked to accept a DCO before they submit the first pull request to this project. This happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/). 13 | -------------------------------------------------------------------------------- /LICENSES/LicenseRef-API-Definition-File-License.txt: -------------------------------------------------------------------------------- 1 | API INFORMATION LICENSE 2 | 3 | 1. API INFORMATION: The file(s) accompanying this API Information License (“License”) may contain information on APIs of SAP products ("API Information"). You may only use the API Information as follows: 4 | 5 | (a) Licensed Products. If you have a valid license agreement with SAP to use or distribute a SAP product, then your use of any API Information related to that licensed product is subject to the terms of such license agreement. 6 | 7 | (b) Unlicensed Products. If you do not have a valid license agreement with SAP to use or distribute a particular SAP product, then you may only use API Information related to that product for your internal, non-productive and non-commercial test and evaluation purposes. 8 | SAP or its licensors retain all ownership and intellectual property rights in the API Information 9 | 10 | 2. WARRANTY: 11 | 12 | a) If You are located outside the US or Canada: AS THE API INFORMATION IS PROVIDED TO YOU FREE OF CHARGE, SAP DOES NOT GUARANTEE OR WARRANT ANY FEATURES OR QUALITIES OF API INFORMATION OR GIVE ANY UNDERTAKING WITH REGARD TO ANY OTHER QUALITY. NO SUCH WARRANTY OR UNDERTAKING SHALL BE IMPLIED BY YOU FROM ANY DESCRIPTION IN THE API INFORMATION. IN PARTICULAR, SAP DOES NOT WARRANT THAT THE API INFORMATION IS ACCURATE OR ERROR FREE. 13 | b) If You are located in the US or Canada: THE API INFORMATION IS PROVIDED TO YOU "AS IS", WITHOUT ANY WARRANTY, ESCROW, TRAINING, MAINTENANCE, OR SERVICE OBLIGATIONS WHATSOEVER ON THE PART OF SAP. SAP MAKES NO EXPRESS OR IMPLIED WARRANTIES OR CONDITIONS OF SALE OF ANY TYPE WHATSOEVER, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY AND OF FITNESS FOR A PARTICULAR PURPOSE. IN PARTICULAR, SAP DOES NOT WARRANT THAT THE API INFORMATION IS ACCURATE OR ERROR FREE. YOU ASSUME ALL RISKS ASSOCIATED WITH THE USE OF THE API INFORMATION, INCLUDING WITHOUT LIMITATION RISKS RELATING TO QUALITY, AVAILABILITY, PERFORMANCE, DATA LOSS, AND UTILITY IN A PRODUCTION ENVIRONMENT. 14 | 15 | 3. LIMITATION OF LIABILITY: 16 | 17 | a) If You are located outside the US or Canada: IRRESPECTIVE OF THE LEGAL REASONS, SAP SHALL ONLY BE LIABLE FOR DAMAGES UNDER THIS LICENSE IF SUCH DAMAGE (I) CAN BE CLAIMED UNDER THE GERMAN PRODUCT LIABILITY ACT OR (II) IS CAUSED BY INTENTIONAL MISCONDUCT OF SAP OR (III) CONSISTS OF PERSONAL INJURY. IN ALL OTHER CASES, NEITHER SAP NOR ITS AFFILIATES, SUBSIDIARIES, OFFICERS, EMPLOYEES, AGENTS, AND LICENSORS EMPLOYEES SHALL BE LIABLE FOR ANY KIND OF DAMAGE OR CLAIMS HEREUNDER. 18 | 19 | b) If You are located in the US or Canada: TO THE EXTENT ALLOWABLE BY APPLICABLE LAW, NEITHER SAP NOR ITS AFFILIATES, SUBSIDIARIES, OFFICERS, EMPLOYEES, AGENTS, AND LICENSORS EMPLOYEES SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, PUNITIVE, CONSEQUENTIAL, OR EXEMPLARY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DAMAGES FOR LOSS OF PROFITS, REVENUE, GOODWILL, USE, DATA, OR OTHER INTANGIBLE LOSSES (EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), HOWEVER CAUSED, WHETHER IN CONTRACT, TORT, OR OTHERWISE, ARISING OUT OF OR RESULTING FROM THE USE OF THE API INFORMATION. THESE LIMITATIONS SHALL NOT APPLY IN CASE OF INTENTIONAL OR GROSS NEGLIGENCE OR PERSONAL INJURY. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![REUSE status](https://api.reuse.software/badge/github.com/SAP-samples/cloud-sdk-js)](https://api.reuse.software/info/github.com/SAP-samples/cloud-sdk-js) 4 | 5 | # SAP Cloud SDK Samples 6 | 7 | ## Description 8 | This repository contains code [samples](./samples) showcasing the [SAP Cloud SDK for JavaScript / TypeScript](https://sap.github.io/cloud-sdk/docs/js/overview). 9 | We plan to increase the number of samples over time. 10 | The samples will serve different purposes: 11 | - There will be simple "Hello World" applications. They will contain a complete project with all configurations needed to execute it. 12 | - There will be code samples to be used in an existing project. These samples do not work on their own but illustrate how to use the SAP Cloud SDK. 13 | - There will be solutions to tutorials or training courses. These applications have larger scopes and showcase the business value you can create using the SAP Cloud SDK. 14 | 15 | ## Requirements 16 | The minimal requirements are: 17 | - A terminal to execute commands 18 | - A recent version of node and npm installed e.g. node 14 and npm 6 19 | - An IDE or a text editor of your choice 20 | 21 | If you want to explore the possibilities beyond local tests you need: 22 | - Access to a [SAP Business Technology Platform](https://www.sap.com/products/business-technology-platform.html) account 23 | - Entitlement to use resources like service instance creation and application processing units 24 | - Permission to deploy applications and create service instances 25 | 26 | ## Support 27 | If you find a bug or have questions about the concrete content of this repository [create an issue here](https://github.com/SAP-samples//issues). 28 | If you have a general question about a feature of the SAP Cloud SDK [create an issue in the SAP Cloud SDK repository](https://github.com/SAP/cloud-sdk-js/issues) 29 | 30 | ## Contributing 31 | If you wish to contribute code, offer fixes or improvements, please send a pull request. 32 | Due to legal reasons, contributors will be asked to accept a DCO when they create the first pull request to this project. 33 | This happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/). 34 | 35 | ## Code of Conduct 36 | For this repository the [code of conduct](https://github.com/SAP/cloud-sdk-js/blob/2.0/CODE_OF_CONDUCT.md) of the SDK applies. 37 | 38 | ## License 39 | Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSES/Apache-2.0.txt) file. 40 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | SPDX-PackageName = "cloud-sdk-js" 3 | SPDX-PackageSupplier = "The SAP Cloud SDK team (cloudsdk@sap.com)" 4 | SPDX-PackageDownloadLocation = "https://github.com/sap-samples/cloud-sdk-js" 5 | SPDX-PackageComment = "The code in this project may include calls to APIs (“API Calls”) of\n SAP or third-party products or services developed outside of this project\n (“External Products”).\n “APIs” means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project’s code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls." 6 | 7 | [[annotations]] 8 | path = "**" 9 | precedence = "aggregate" 10 | SPDX-FileCopyrightText = "2021 SAP SE or an SAP affiliate company and SAP-samples/cloud-sdk-js contributors" 11 | SPDX-License-Identifier = "Apache-2.0" 12 | 13 | [[annotations]] 14 | path = "**/resources/service-specs/**.edmx" 15 | precedence = "aggregate" 16 | SPDX-FileCopyrightText = "2021 SAP SE or an SAP affiliate company and SAP-samples/cloud-sdk-js contributors" 17 | SPDX-License-Identifier = "LicenseRef-API-Definition-File-License" 18 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # SAP Open Source Security Policy 4 | 5 | SAP takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, including our primary [SAP](https://github.com/SAP), [SAP-docs](https://github.com/SAP-docs) organizations as well as [our other GitHub organizations and projects](https://opensource.sap.com). 6 | 7 | If you believe you have found a security vulnerability in any SAP-owned repository, please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them via the SAP Trust Center at [https://www.sap.com/about/trust-center/security/incident-management.html](https://www.sap.com/about/trust-center/security/incident-management.html). 14 | 15 | If you prefer to submit via email, please send an email to [secure@sap.com](mailto:secure@sap.com). If possible, encrypt your message with our PGP key; please download it from the [SAP Trust Center](https://www.sap.com/dmc/policies/pgp/keyblock.txt). 16 | 17 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 18 | 19 | - The repository name or URL 20 | - Type of issue (buffer overflow, SQL injection, cross-site scripting, etc.) 21 | - Full paths of the source file(s) related to the manifestation of the issue 22 | - The location of the affected source code (tag/branch/commit or direct URL) 23 | - Any particular configuration required to reproduce the issue 24 | - Step-by-step instructions to reproduce the issue 25 | - Proof-of-concept or exploit code (if possible) 26 | - Impact of the issue, including how an attacker might exploit the issue 27 | 28 | This information will help us triage your report more quickly. 29 | 30 | ## Preferred Languages 31 | 32 | We prefer all communications to be in English. 33 | 34 | ## Disclosure Guidelines 35 | 36 | We like to ask you to follow the [Disclosure Guidelines for SAP Security Advisories](https://wiki.scn.sap.com/wiki/display/PSR/Disclosure+Guidelines+for+SAP+Security+Advisories). 37 | 38 | ## SAP Internal Response Process 39 | 40 | As an SAP employee, please check our internal open source security response process ([go/oss-security-response](https://go.sap.corp/oss-security-response)) for further details on how to handle security incidents. 41 | 42 | 43 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/cloud-sdk-js/1bfa08bcdc2694d96bef8798d849afc4c10ec4a8/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sap-cloud-sdk/samples", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "fetch-service-definitions": "ts-node scripts/fetch-service-spec-cli.ts", 7 | "prettier": "prettier scripts -w", 8 | "clean-install": "ts-node scripts/clean-install-all-samples.ts", 9 | "set-sdk-version": "ts-node scripts/set-sdk-version.ts", 10 | "test": "npm run test --workspaces" 11 | }, 12 | "license": "Apache-2.0", 13 | "dependencies": {}, 14 | "devDependencies": { 15 | "dotenv": "^16.0.1", 16 | "execa": "5.0.0", 17 | "typescript": "~5.7.2", 18 | "ts-node": "^10.0.0", 19 | "axios": "^0.27.2", 20 | "prettier": "^2.7.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/cds-sample-application/.cdsrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "target": ".", 4 | "tasks": [ 5 | { 6 | "for": "node-cf", 7 | "src": "srv", 8 | "options": { 9 | "model": [ 10 | "srv" 11 | ] 12 | } 13 | } 14 | ] 15 | }, 16 | "odata": { 17 | "version": "v4" 18 | }, 19 | "requires": { 20 | "db": { 21 | "kind": "sqlite", 22 | "model": [ 23 | "srv", 24 | "db" 25 | ], 26 | "credentials": { 27 | "database": "cap-test.db" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/cds-sample-application/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:prettier/recommended', 11 | ], 12 | root: true, 13 | env: { 14 | node: true, 15 | jest: true, 16 | }, 17 | ignorePatterns: ['.eslintrc.js'], 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-function-return-type': 'off', 21 | '@typescript-eslint/explicit-module-boundary-types': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /samples/cds-sample-application/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json 36 | credentials.json 37 | /s4hana_pipeline 38 | /deployment 39 | .cds_gen.log 40 | *.db 41 | connection.properties 42 | default-*.json 43 | gen/ 44 | target/ 45 | 46 | # Ignore .env files 47 | .env 48 | cypress.env.json 49 | 50 | # Ignore cypress screenshots and videos 51 | screenshots 52 | videos 53 | -------------------------------------------------------------------------------- /samples/cds-sample-application/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /samples/cds-sample-application/README.md: -------------------------------------------------------------------------------- 1 | # CAP + SAP Cloud SDK for JS Cloud Foundry End-2-End Application 2 | 3 | ## Description 4 | This repository contains a CAP-based application which we use to test the SAP Cloud SDK functionality in a SAP BTP Cloud Foundry environment. 5 | 6 | The repositories' structure is as following: 7 | 8 | - `/src` - Contains the application's source code with a CAP service handler implementation 9 | - `/srv` - Contains the CDS rervice definitions and implementations 10 | - `/db` - Contains CDS Domain Models and database-related stuff 11 | - `manifest.yml` - Manifest to deploy to SAP BTP Cloud Foundry 12 | 13 | ## Requirements 14 | The minimal requirements are: 15 | - A terminal to execute commands 16 | - A recent version of node and npm installed e.g. node 14 and npm 6 17 | - An IDE or a text editor of your choice 18 | 19 | If you want to explore the possibilities beyond local tests you need: 20 | - Access to a [SAP Business Technology Platform](https://www.sap.com/products/business-technology-platform.html) account 21 | - Entitlement to use resources like service instance creation and application processing units 22 | - Permission to deploy applications and create service instances 23 | 24 | ## Download and Installation 25 | To download the application run: 26 | 27 | ``` 28 | git clone \ 29 | --depth 1 \ 30 | --filter=blob:none \ 31 | --sparse \ 32 | https://github.com/SAP-samples/cloud-sdk-js.git \ 33 | ; 34 | cd cloud-sdk-js 35 | git sparse-checkout set samples/cds-sample-application 36 | ``` 37 | ### Generate oData Client 38 | 39 | The [Business Partner service cloud](https://api.sap.com/api/API_BUSINESS_PARTNER/overview) service definitions in `EDMX` format is already downloaded in the folder `resources/service-specs`. The client is generated using the `npm run generate-client` command. This command is executed automatically in the `postinstall` step after you execute `npm i `. 40 | 41 | **Note** These service is licensed under the terms of [SAP API Information License](../../LICENSES/LicenseRef-API-Definition-File-License.txt). This limits its use to development purposes only. 42 | 43 | ### Create Services on SAP BTP Cloud Foundry 44 | Before you can deploy the application, you have to create a `destination`and `xsuaa` service instance. 45 | Their name should match the one that is used in the `manifest.yml`, in this case: 46 | - `destination-service` 47 | - `xsuaa-service` 48 | 49 | ### Deploy the Application to SAP BTP Cloud Foundry 50 | Run `npm run deploy` in the root to deploy the application to SAP BTP Cloud Foundry. This command will transpile the application from TypeScript to JavaScript using the `ci-build` script, package our deployment using the `ci-package` script and deploy the application using `cf push`. 51 | 52 | To run the application locally, run `npm run cds-deploy` and then `npm run start:dev`. 53 | 54 | -------------------------------------------------------------------------------- /samples/cds-sample-application/db/data-model.cds: -------------------------------------------------------------------------------- 1 | namespace bupa; 2 | 3 | entity CapBusinessPartner { 4 | key ID : String; 5 | firstName : String; 6 | lastName : String; 7 | businessPartnerName: String; 8 | businessPartnerFullName: String; 9 | businessPartnerUuid: String; 10 | } -------------------------------------------------------------------------------- /samples/cds-sample-application/db/data/bupa-CapBusinessPartner.csv: -------------------------------------------------------------------------------- 1 | ID;firstName;lastName;businessPartnerName;businessPartnerFullName;businessPartnerUuid 2 | 1006192;Arthur;Heights;John Doe;John Doe;00145f30-2a5e-1ed8-8483-a08c52249f04 3 | 1009324;Jane;Eyre;Jane Roe;Jane Roe107;00189w30-2a5e-1ed8-8483-a08c52249f04 -------------------------------------------------------------------------------- /samples/cds-sample-application/manifest.yml: -------------------------------------------------------------------------------- 1 | applications: 2 | - name: cap-e2e-app 3 | path: deployment/ 4 | buildpacks: 5 | - nodejs_buildpack 6 | memory: 256M 7 | command: npm run start:prod 8 | random-route: true 9 | services: 10 | - destination-service 11 | - xsuaa-service 12 | -------------------------------------------------------------------------------- /samples/cds-sample-application/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /samples/cds-sample-application/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sap-cloud-sdk/samples", 3 | "version": "0.0.1", 4 | "description": "SAP Cloud SDK for JS - Test application for CAP", 5 | "author": "", 6 | "private": true, 7 | "license": "Apache-2.0", 8 | "scripts": { 9 | "postinstall": "npm run generate-client", 10 | "prebuild": "rimraf dist", 11 | "build": "nest build", 12 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 13 | "start": "nest start", 14 | "start:dev": "nest start --watch", 15 | "start:debug": "nest start --debug --watch", 16 | "start:prod": "node dist/main", 17 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 18 | "test": "jest", 19 | "test:watch": "jest --watch", 20 | "test:cov": "jest --coverage", 21 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 22 | "test:e2e": "jest --config ./test/jest-e2e.json", 23 | "deploy": "npm run ci-build && npm run ci-package && cf push", 24 | "ci-build": "npm run cds-deploy && npm run cds-build && npm run build", 25 | "ci-package": "rimraf deployment && mkdir deployment && cp -r dist package.json package-lock.json db srv .cdsrc.json deployment/", 26 | "cds-build": "cds build/all", 27 | "cds-deploy": "cds deploy", 28 | "generate-client": "npx generate-odata-client --input resources/service-specs --outputDir src/generated --clearOutputDir --transpile=false --optionsPerService=resources/service-specs/options-per-service.json" 29 | }, 30 | "dependencies": { 31 | "@nestjs/common": "^8.4.7", 32 | "@nestjs/core": "^8.4.7", 33 | "@nestjs/platform-express": "^8.4.7", 34 | "@sap/cds": "^6.1.3", 35 | "@sap/cds-dk": "^7.9.4", 36 | "passport": "^0.7.0", 37 | "reflect-metadata": "^0.1.14", 38 | "rimraf": "^6.0.1", 39 | "rxjs": "^7.8.2", 40 | "@sap-cloud-sdk/odata-v2": "^4.0.1" 41 | }, 42 | "devDependencies": { 43 | "@sap-cloud-sdk/generator": "^4.0.1", 44 | "@nestjs/cli": "^11.0.5", 45 | "@nestjs/schematics": "^11.0.2", 46 | "@nestjs/testing": "^8.4.7", 47 | "@sap-cloud-sdk/test-util": "^4.0.1", 48 | "@types/express": "^5.0.0", 49 | "@types/jest": "29.5.14", 50 | "@types/node": "^22.13.9", 51 | "@types/supertest": "^6.0.2", 52 | "@typescript-eslint/eslint-plugin": "^5.62.0", 53 | "@typescript-eslint/parser": "^5.62.0", 54 | "eslint": "^8.57.0", 55 | "eslint-config-prettier": "^10.1.1", 56 | "eslint-plugin-prettier": "^5.2.3", 57 | "jest": "^29.7.0", 58 | "nock": "^14.0.1", 59 | "prettier": "^3.4.2", 60 | "source-map-support": "^0.5.20", 61 | "sqlite3": "^5.1.7", 62 | "supertest": "^7.0.0", 63 | "ts-jest": "^29.2.6", 64 | "ts-loader": "^9.5.2", 65 | "ts-node": "^10.9.2", 66 | "tsconfig-paths": "^4.2.0", 67 | "typescript": "^5.8.2" 68 | }, 69 | "jest": { 70 | "moduleFileExtensions": [ 71 | "js", 72 | "json", 73 | "ts" 74 | ], 75 | "rootDir": "src", 76 | "testRegex": ".*\\.spec\\.ts$", 77 | "transform": { 78 | "^.+\\.(t|j)s$": "ts-jest" 79 | }, 80 | "collectCoverageFrom": [ 81 | "**/*.(t|j)s" 82 | ], 83 | "coverageDirectory": "../s4hana_pipeline/reports/coverage-reports/backend-unit", 84 | "testEnvironment": "node", 85 | "reporters": [ 86 | "default", 87 | [ 88 | "jest-junit", 89 | { 90 | "suiteName": "backend unit tests", 91 | "outputDirectory": "./s4hana_pipeline/reports/backend-unit" 92 | } 93 | ] 94 | ], 95 | "collectCoverage": true, 96 | "coverageReporters": [ 97 | "text", 98 | "cobertura" 99 | ] 100 | } 101 | } -------------------------------------------------------------------------------- /samples/cds-sample-application/resources/service-specs/options-per-service.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources/service-specs/API_BUSINESS_PARTNER.edmx": { 3 | "packageName": "cloud-business-partner-service", 4 | "directoryName": "cloud-business-partner-service", 5 | "basePath": "/sap/opu/odata/sap/API_BUSINESS_PARTNER" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/cds-sample-application/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | 4 | describe('AppController', () => { 5 | let appController: AppController; 6 | 7 | beforeEach(async () => { 8 | const app: TestingModule = await Test.createTestingModule({ 9 | controllers: [AppController], 10 | }).compile(); 11 | 12 | appController = app.get(AppController); 13 | }); 14 | 15 | describe('root', () => { 16 | it('should return "Hello World!"', () => { 17 | expect(appController.getHello()).toBe('Hello World!'); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /samples/cds-sample-application/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | @Controller() 4 | export class AppController { 5 | constructor() {} 6 | 7 | @Get() 8 | getHello(): string { 9 | return 'Hello World!'; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/cds-sample-application/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | 4 | @Module({ 5 | controllers: [AppController] 6 | }) 7 | export class AppModule {} 8 | -------------------------------------------------------------------------------- /samples/cds-sample-application/src/business-partner/business-partner-service-handler.ts: -------------------------------------------------------------------------------- 1 | import { cloudBusinessPartnerService as businessPartnerService } from '../generated/cloud-business-partner-service'; 2 | const { businessPartnerApi } = businessPartnerService(); 3 | 4 | /** 5 | * Service implementation for the cds service defined in /srv/business-partner-service.cds 6 | * Annotation @impl is used in service definition file (.cds) to specify alternative paths (relative to dist/) to load implementations from 7 | * Note: The name of service handler should match the cds service name 8 | */ 9 | export const BupaService = (srv) => { 10 | srv.on('getByKey', async (oRequest) => { 11 | const param = oRequest.data.param; 12 | const partner = await businessPartnerApi 13 | .requestBuilder() 14 | .getByKey(param) 15 | .execute({ destinationName: 'myDestinationName' }); 16 | oRequest.reply(partner); 17 | }); 18 | 19 | srv.on('getAll', async (oRequest) => { 20 | const partners = await businessPartnerApi 21 | .requestBuilder() 22 | .getAll() 23 | .top(5) 24 | .execute({ destinationName: 'myDestinationName' }); 25 | oRequest.reply(partners); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /samples/cds-sample-application/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import cds = require('@sap/cds'); 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | await cds.connect.to('db'); 8 | await cds 9 | .serve('all') 10 | .in(app); 11 | await app.listen(process.env.PORT || 3000); 12 | } 13 | bootstrap(); 14 | -------------------------------------------------------------------------------- /samples/cds-sample-application/srv/business-partner-service.cds: -------------------------------------------------------------------------------- 1 | using { bupa as my } from '../db/data-model'; 2 | 3 | @impl:'dist/business-partner/business-partner-service-handler' 4 | service BupaService { 5 | entity CapBusinessPartner @readonly as projection on my.CapBusinessPartner; 6 | 7 | function getByKey(param : String) returns CapBusinessPartner; 8 | function getAll() returns array of CapBusinessPartner; 9 | } -------------------------------------------------------------------------------- /samples/cds-sample-application/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | import { setTestDestination } from '@sap-cloud-sdk/test-util'; 6 | import nock = require('nock'); 7 | import cds = require('@sap/cds'); 8 | 9 | describe('AppController (e2e)', () => { 10 | let app: INestApplication; 11 | 12 | beforeAll(async () => { 13 | const moduleFixture: TestingModule = await Test.createTestingModule({ 14 | imports: [AppModule], 15 | }).compile(); 16 | 17 | app = moduleFixture.createNestApplication(); 18 | await cds.connect.to('db'); 19 | await cds 20 | .serve('all') 21 | .in(app); 22 | await app.init(); 23 | 24 | setTestDestination({ 25 | name: 'myDestinationName', 26 | url: 'https://my-destination-url.com', 27 | isTestDestination: true 28 | }); 29 | }); 30 | 31 | it('tests service handler implementation', () => { 32 | nock('https://my-destination-url.com') 33 | .get(/.*/) 34 | .reply(200); 35 | 36 | return request(app.getHttpServer()) 37 | .get('/bupa/getAll()') 38 | .expect(200) 39 | .expect({"@odata.context":"$metadata#CapBusinessPartner","value":[]}); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /samples/cds-sample-application/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/cds-sample-application/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "test", 6 | "dist", 7 | "**/*spec.ts" 8 | ] 9 | } -------------------------------------------------------------------------------- /samples/cds-sample-application/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true 14 | } 15 | } -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/prettierrc", 3 | "singleQuote": true, 4 | "filepath": "*.ts", 5 | "trailingComma": "none", 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/README.md: -------------------------------------------------------------------------------- 1 | # SAP Cloud SDK for JavaScript Multi-Tenant Sample Application 2 | 3 | ## Description 4 | 5 | The code sample in this directory is a reference for the [multi-tenant application tutorial](https://sap.github.io/cloud-sdk/docs/js/tutorials/multi-tenant-application). 6 | You need to make adjustments to the code before deployment. 7 | The terms you need to replace are given in all caps and start with _`YOUR_`_ e.g. _`YOUR_REGION`_. 8 | 9 | Also, note that the samples here are intended as a didactic example and are not necessarily a best practice. 10 | The repositories' structure is as follows: 11 | 12 | - [./multi-tenant-app](./multi-tenant-app) - Contains the code of the multi-tenant application. 13 | It contains a simple service endpoint and logic to be executed on subscription and unsubscription. 14 | - [./approuter](./approuter) - Contains the approuter necessary to attach JSON web tokens to the application. 15 | - [./service-config](./service-config) - Directory configurations for service instances. 16 | 17 | ## Requirements 18 | 19 | The minimal requirements are: 20 | 21 | - A terminal to execute commands 22 | - A recent version of node and npm installed e.g. node 14 and npm 6 23 | - A recent installation of the [Cloud Foundry command line interface](https://developers.sap.com/tutorials/cp-cf-download-cli.html) 24 | - An IDE or a text editor of your choice 25 | - Access to a [SAP Business Technology Platform](https://www.sap.com/products/business-technology-platform.html) account 26 | - Entitlement to use resources like service instance creation and application processing units 27 | - Permission to deploy applications and create service instances 28 | 29 | ## Download and Deployment 30 | 31 | To download the application run: 32 | 33 | ``` 34 | git clone \ 35 | --depth 1 \ 36 | --filter=blob:none \ 37 | --sparse \ 38 | https://github.com/SAP-samples/cloud-sdk-js.git \ 39 | ; 40 | cd cloud-sdk-js 41 | git sparse-checkout set samples/cf-multi-tenant-application 42 | ``` 43 | 44 | ### Create Services on SAP BTP Cloud Foundry 45 | 46 | Before you can deploy the application, you have to create a `destination` and `xsuaa` service instance. 47 | Their name should match the one that is used in the application `manifest.yml`, in this case: 48 | 49 | - `destination` 50 | - `xsuaa` 51 | 52 | ### Deploy the Application to SAP BTP Cloud Foundry 53 | 54 | Run `cf push` in the application directory to deploy the application. 55 | Please follow the steps described in the [multi-tenant application tutorial](https://sap.github.io/cloud-sdk/docs/js/tutorials/multi-tenant-application) for the deployment steps of all components. 56 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/approuter/manifest.yml: -------------------------------------------------------------------------------- 1 | applications: 2 | - name: approuter 3 | routes: 4 | - route: 'route-prefix-YOUR_SUBDOMAIN.cfapps.YOUR_REGION.hana.ondemand.com' 5 | path: . 6 | memory: 128M 7 | buildpacks: 8 | - nodejs_buildpack 9 | env: 10 | TENANT_HOST_PATTERN: 'route-prefix-(.*).cfapps.YOUR_REGION.hana.ondemand.com' 11 | destinations: > 12 | [ 13 | {"name":"multi-tenant-app","url":"https://multi-tenant-app.cfapps.YOUR_REGION.hana.ondemand.com","forwardAuthToken":true} 14 | ] 15 | services: 16 | - xsuaa 17 | - destination 18 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/approuter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "approuter", 3 | "dependencies": { 4 | "@sap/approuter": "latest" 5 | }, 6 | "scripts": { 7 | "start": "node node_modules/@sap/approuter/approuter.js" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/approuter/xs-app.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcomeFile": "index.html", 3 | "routes": [ 4 | { 5 | "source": "(.*)", 6 | "target": "/$1", 7 | "destination": "multi-tenant-app" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/multi-tenant-app/manifest.yml: -------------------------------------------------------------------------------- 1 | applications: 2 | - name: multi-tenant-app 3 | path: . 4 | memory: 256M 5 | buildpacks: 6 | - nodejs_buildpack 7 | services: 8 | - destination 9 | - xsuaa 10 | routes: 11 | - route: 'multi-tenant-app.cfapps.YOUR_REGION.hana.ondemand.com' 12 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/multi-tenant-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multi-tenant-app", 3 | "version": "1.0.0", 4 | "description": "Example application using the SAP Cloud SDK for JavaScript.", 5 | "scripts": { 6 | "start": "node -r ts-node/register --inspect src/server.ts", 7 | "debug": "node -r ts-node/register --inspect-brk src/server.ts" 8 | }, 9 | "dependencies": { 10 | "express": "^4", 11 | "@sap/xsenv": "^3.3.2", 12 | "cfenv": "^1.2.4", 13 | "@sap-cloud-sdk/util": "^3.0.0", 14 | "@sap-cloud-sdk/connectivity": "^3.0.0", 15 | "@sap-cloud-sdk/http-client": "^3.0.0", 16 | "@types/node": "^22.0.0", 17 | "ts-node": "^10", 18 | "typescript": "~5.7.2" 19 | }, 20 | "devDependencies": {}, 21 | "engines": { 22 | "node": "^20.0.0", 23 | "npm": "^10.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/multi-tenant-app/src/application.ts: -------------------------------------------------------------------------------- 1 | import * as bodyParser from 'body-parser'; 2 | import express from 'express'; 3 | import { serviceRoute } from './service-endpoint'; 4 | import { dependencyRoute } from './dependencies-endpoint'; 5 | import { subscribeRoute, unsubscribeRoute } from './subscription-endpoint'; 6 | import { join } from 'path'; 7 | import { readFileSync } from 'fs'; 8 | 9 | class App { 10 | public app: express.Application; 11 | public indexPage = readFileSync(join(__dirname, 'index.html'), { 12 | encoding: 'utf-8' 13 | }); 14 | 15 | constructor() { 16 | this.app = express(); 17 | this.config(); 18 | this.routes(); 19 | } 20 | 21 | private config(): void { 22 | this.app.use(bodyParser.json()); 23 | this.app.use(bodyParser.urlencoded({ extended: false })); 24 | } 25 | 26 | private routes(): void { 27 | const router = express.Router(); 28 | 29 | router.get('/service', serviceRoute); 30 | router.get('/dependencies', dependencyRoute); 31 | router.put('/subscription/:subscriberTenantId', subscribeRoute); 32 | router.delete('/subscription/:subscriberTenantId', unsubscribeRoute); 33 | router.get('/index.html', (req, res) => { 34 | res.send(this.indexPage); 35 | }); 36 | this.app.use('/', router); 37 | } 38 | } 39 | 40 | export default new App().app; 41 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/multi-tenant-app/src/dependencies-endpoint.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import * as xsenv from '@sap/xsenv'; 3 | 4 | const relevantServices = ['destination']; 5 | 6 | export function dependencyRoute(req: Request, res: Response): void { 7 | res.status(200).send( 8 | relevantServices 9 | .map(s => { 10 | const services = xsenv.filterCFServices({ label: s }); 11 | 12 | return services && services.length 13 | ? { 14 | appId: 15 | services[0].credentials.xsappname || 16 | services[0].credentials.uaa.xsappname, 17 | appName: s 18 | } 19 | : null; 20 | }) 21 | .filter(elem => elem) 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/multi-tenant-app/src/index.html: -------------------------------------------------------------------------------- 1 |

Multi-tenant sample application

2 | 3 |

/service to trigger the sample logic

4 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/multi-tenant-app/src/server.ts: -------------------------------------------------------------------------------- 1 | import app from './application'; 2 | 3 | const port = 8080; 4 | 5 | app.listen(port, () => { 6 | // eslint-disable-next-line no-console 7 | console.log('Express server listening on port ' + port); 8 | }); 9 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/multi-tenant-app/src/service-endpoint.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { 3 | decodeJwt, 4 | getDestination, 5 | retrieveJwt, 6 | subscriberFirst 7 | } from '@sap-cloud-sdk/connectivity'; 8 | import { createLogger } from '@sap-cloud-sdk/util'; 9 | 10 | const logger = createLogger('destination'); 11 | 12 | export async function serviceRoute(req: Request, res: Response): Promise { 13 | try { 14 | const jwt = retrieveJwt(req); 15 | const tenantText = jwt 16 | ? `You are on tenant: ${decodeJwt(jwt).zid}.` 17 | : `No jwt given in request. Provider tenant used.`; 18 | const destination = await getDestination({ 19 | destinationName: 'myDestination', 20 | selectionStrategy: subscriberFirst, 21 | jwt 22 | }); 23 | if (destination) { 24 | res.status(200).send( 25 | `${tenantText}. 26 | The destination description is: ${destination.originalProperties.Description}.` 27 | ); 28 | } else { 29 | res.status(404).send(`Destination with name 'myDestination' not found.`); 30 | } 31 | } catch (e) { 32 | logger.error(e); 33 | res.status(500).send('Error in retrieving destination - look at the logs.'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/multi-tenant-app/src/subscription-endpoint.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { createLogger } from '@sap-cloud-sdk/util'; 3 | import { 4 | bindRoute, 5 | createRoute, 6 | deleteRoute, 7 | getCfGuids, 8 | getLandscape 9 | } from './subscription-util'; 10 | 11 | const logger = createLogger('subscription'); 12 | const appRouterName = 'approuter'; 13 | 14 | export async function subscribeRoute(req: Request, res: Response) { 15 | try { 16 | const subscribedSubdomain = req.body.subscribedSubdomain; 17 | const subscriberRoute = `https://route-prefix-${subscribedSubdomain}.${getLandscape()}`; 18 | logger.info(`subscribe: ${subscriberRoute}`); 19 | 20 | const guids = await getCfGuids(appRouterName); 21 | const routeGuid = await createRoute(subscribedSubdomain, guids); 22 | await bindRoute(routeGuid, guids); 23 | 24 | res.status(200).send(subscriberRoute); 25 | } catch (e) { 26 | res.status(500).send(e.message); 27 | } 28 | } 29 | 30 | export async function unsubscribeRoute(req: Request, res: Response) { 31 | const subscribedSubdomain = req.body.subscribedSubdomain; 32 | logger.info(`un-subscribe: ${subscribedSubdomain}`); 33 | await deleteRoute(subscribedSubdomain); 34 | res.status(200).send('Unsubscribed.'); 35 | } 36 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/multi-tenant-app/src/subscription-util.ts: -------------------------------------------------------------------------------- 1 | import { executeHttpRequest } from '@sap-cloud-sdk/http-client'; 2 | import * as cfEnv from 'cfenv'; 3 | const appRouterName = 'approuter'; 4 | const routePrefix = 'route-prefix'; 5 | const cfApiDestination = { destinationName: 'cf-api', useCache: true }; 6 | 7 | type Guids = { 8 | appGuid: string; 9 | domainGuid: string; 10 | spaceGuid: string; 11 | }; 12 | 13 | export async function getCfGuids(appName: string): Promise { 14 | const spaceGuid = cfEnv.getAppEnv().app.space_id; 15 | const orgGuid = cfEnv.getAppEnv().app.organization_id; 16 | 17 | const { 18 | data: { 19 | resources: [{ guid: appGuid }] 20 | } 21 | } = await executeHttpRequest(cfApiDestination, { 22 | method: 'get', 23 | url: `/v3/apps?organization_guids=${orgGuid}&space_guids=${spaceGuid}&names=${appName}` 24 | }); 25 | 26 | try { 27 | const { 28 | data: { 29 | resources: [{ guid: domainGuid }] 30 | } 31 | } = await executeHttpRequest(cfApiDestination, { 32 | method: 'get', 33 | url: `/v3/domains?names=${encodeURI(getLandscape())}` 34 | }); 35 | return { appGuid, domainGuid, spaceGuid }; 36 | } catch (e) { 37 | console.log(e); 38 | } 39 | } 40 | 41 | export function getLandscape(): string { 42 | const result = cfEnv 43 | .getAppEnv() 44 | .app.application_uris[0].split('.') 45 | .slice(1) 46 | .join('.'); 47 | return result; 48 | } 49 | 50 | function getRoutePath(subscribedSubdomain: string): string { 51 | return `${routePrefix}-${subscribedSubdomain}`; 52 | } 53 | 54 | export async function bindRoute(routeGuid: string, guids: Guids) { 55 | const bindRouteBody = { 56 | destinations: [ 57 | { 58 | app: { 59 | guid: guids.appGuid 60 | } 61 | } 62 | ] 63 | }; 64 | return executeHttpRequest(cfApiDestination, { 65 | url: `/v3/routes/${routeGuid}/destinations`, 66 | method: 'post', 67 | data: bindRouteBody 68 | }); 69 | } 70 | 71 | export async function createRoute( 72 | subscribedSubdomain: string, 73 | guids: Guids 74 | ): Promise { 75 | const createRouteBody = { 76 | host: getRoutePath(subscribedSubdomain), 77 | relationships: { 78 | space: { 79 | data: { 80 | guid: guids.spaceGuid 81 | } 82 | }, 83 | domain: { 84 | data: { 85 | guid: guids.domainGuid 86 | } 87 | } 88 | } 89 | }; 90 | const createdRoute = ( 91 | await executeHttpRequest(cfApiDestination, { 92 | url: '/v3/routes', 93 | method: 'post', 94 | data: createRouteBody 95 | }) 96 | ).data; 97 | return createdRoute.guid; 98 | } 99 | 100 | export async function deleteRoute(subscribedSubdomain: string) { 101 | try { 102 | const { appGuid } = await getCfGuids(appRouterName); 103 | const routes = ( 104 | await executeHttpRequest(cfApiDestination, { 105 | method: 'get', 106 | url: `/v3/apps/${appGuid}/routes?hosts=${getRoutePath( 107 | subscribedSubdomain 108 | )}` 109 | }) 110 | ).data; 111 | await Promise.all( 112 | routes.resources.map(ele => 113 | executeHttpRequest(cfApiDestination, { 114 | method: 'delete', 115 | url: `/v3/routes/${ele.guid}` 116 | }) 117 | ) 118 | ); 119 | } catch (e) { 120 | console.log(e.msg); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/multi-tenant-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es2015", "dom"], 5 | "esModuleInterop": true 6 | }, 7 | "typeAcquisition": { 8 | "enable": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cf-multi-tenant-application", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "devDependencies": { 8 | "prettier": "^3.4.2" 9 | }, 10 | "engines": { 11 | "node": "^22.0.0", 12 | "npm": "^10.0.0" 13 | } 14 | }, 15 | "node_modules/prettier": { 16 | "version": "3.4.2", 17 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", 18 | "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", 19 | "dev": true, 20 | "bin": { 21 | "prettier": "bin/prettier.cjs" 22 | }, 23 | "engines": { 24 | "node": ">=14" 25 | }, 26 | "funding": { 27 | "url": "https://github.com/prettier/prettier?sponsor=1" 28 | } 29 | } 30 | }, 31 | "dependencies": { 32 | "prettier": { 33 | "version": "3.4.2", 34 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", 35 | "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", 36 | "dev": true 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "format": "prettier --write . " 4 | }, 5 | "dependencies": {}, 6 | "devDependencies": { 7 | "prettier": "^3.4.2" 8 | }, 9 | "engines": { 10 | "node": "^22.0.0", 11 | "npm": "^10.0.0" 12 | } 13 | } -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/service-config/saas-registry-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "xsappname": "xs-multi-tenant-sample-app", 3 | "appName": "multi-tenant-app", 4 | "providerTenantId": "YOUR_TENANT_GUID", 5 | "displayName": "multi tenant example application", 6 | "appUrls": { 7 | "getDependencies": "https://multi-tenant-app.cfapps.YOUR_REGION.hana.ondemand.com/dependencies", 8 | "onSubscription": "https://multi-tenant-app.cfapps.YOUR_REGION.hana.ondemand.com/subscription/{tenantId}" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/cf-multi-tenant-application/service-config/xs-security.json: -------------------------------------------------------------------------------- 1 | { 2 | "xsappname": "xs-multi-tenant-sample-app", 3 | "tenant-mode": "shared", 4 | "oauth2-configuration": { 5 | "credential-types": ["instance-secret"] 6 | }, 7 | "scopes": [ 8 | { 9 | "name": "$XSAPPNAME.Display", 10 | "description": "display" 11 | }, 12 | { 13 | "name": "$XSAPPNAME.Callback", 14 | "description": "With this scope set, the callbacks for tenant onboarding, offboarding and getDependencies can be called (according to the docs at least, we don't actually check for any scopes in our e2e test app).", 15 | "grant-as-authority-to-apps": [ 16 | "$XSAPPNAME(application,sap-provisioning,tenant-onboarding)" 17 | ] 18 | } 19 | ], 20 | "role-templates": [ 21 | { 22 | "name": "Viewer", 23 | "description": "Required to view things in our solution", 24 | "scope-references": ["$XSAPPNAME.Display", "uaa.user"] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /samples/cf-sample-application/README.md: -------------------------------------------------------------------------------- 1 | # SAP Cloud SDK for JS Cloud Foundry Sample Application 2 | 3 | ## Description 4 | This repository contains our Cloud Foundry sample application. 5 | 6 | The repositories structure is as following: 7 | 8 | - `/approuter` - Contains the approuter, a packaging script and a manifest which is used in the deployment 9 | - `/src` - Contains the application's source code with its 4 endpoints for cloud, onpremise and principal propagation 10 | - `/e2e-tests` - Contains the cypress tests that test all endpoints after deployment 11 | - `manifest.yml` - Manifest to deploy to SAP BTP Cloud Foundry 12 | 13 | If you want to locally trigger the e2e-tests, create a `cypress.env.json` file in the `/e2e-tests` directory containing the credentials for the IdP in the format: 14 | 15 | ``` 16 | { 17 | "username": "username", 18 | "password": "password" 19 | "url": "url" 20 | } 21 | ``` 22 | 23 | ## Requirements 24 | The minimal requirements are: 25 | - A terminal to execute commands 26 | - A recent version of node and npm installed e.g. node 14 and npm 6 27 | - An IDE or a text editor of your choice 28 | 29 | If you want to explore the possibilities beyond local tests you need: 30 | - Access to a [SAP Business Technology Platform](https://www.sap.com/products/business-technology-platform.html) account 31 | - Entitlement to use resources like service instance creation and application processing units 32 | - Permission to deploy applications and create service instances 33 | 34 | ## Download and Installation 35 | To download the application run: 36 | 37 | ``` 38 | git clone \ 39 | --depth 1 \ 40 | --filter=blob:none \ 41 | --sparse \ 42 | https://github.com/SAP-samples/cloud-sdk-js.git \ 43 | ; 44 | cd cloud-sdk-js 45 | git sparse-checkout set samples/cf-sample-application 46 | ``` 47 | 48 | ### Generate oData Client 49 | 50 | The following service definitions in `EDMX` format are already downloaded in the folder `resources/service-specs`: 51 | - [Business Partner service onPremise](https://api.sap.com/api/OP_API_BUSINESS_PARTNER_SRV/overview) 52 | - [Business Partner service cloud](https://api.sap.com/api/API_BUSINESS_PARTNER/overview) 53 | 54 | The clients are generated using the `npm run generate-client` command. This command is executed automatically in the `postinstall` step after you execute `npm i `. 55 | 56 | **Note** These services are licensed under the terms of [SAP API Information License](../../LICENSES/LicenseRef-API-Definition-File-License.txt). This limits their use to development purposes only. 57 | 58 | ### Create Services on SAP BTP Cloud Foundry 59 | Before you can deploy the application and approuter, you have to create a `destination`, `xsuaa`, and `connectivity` service instance. 60 | Their name should match the one that is used in the `manifest.yml`, in this case: 61 | 62 | ``` 63 | - sample-destination-service 64 | - sample-xsuaa-service 65 | - sample-connectivity-service 66 | ``` 67 | 68 | ### Deploy the Approuter and Application to SAP BTP Cloud Foundry 69 | 1. Change all occurrences of `` to your respective values, this includes destinations, your Subdomain, etc. 70 | 2. Change the `xs-app.json` in the approuter directory to use either an IdP associated with your subaccount, or no IdP at all. 71 | 3. Run `npm run deploy` in the root, as well as in the `approuter` directory to deploy the application to SAP BTP Cloud Foundry. 72 | 4. After both the `approuter` and application are deployed, open the `approuter's` url to access the deployed application. 73 | 74 | ### Code Placeholders 75 | If anything isn't working as intended, search for ``, as all parts that have to be adapted contain this placeholder. 76 | -------------------------------------------------------------------------------- /samples/cf-sample-application/approuter/manifest.yml: -------------------------------------------------------------------------------- 1 | applications: 2 | - name: approuter 3 | routes: 4 | # Replace the subpath with your subaccount, to match the TENANT_HOST_PATTERN 5 | # Example: approuter-.cfapps.sap.hana.ondemand.com 6 | - route: approuter-s4sdk.cfapps.sap.hana.ondemand.com 7 | memory: 512M 8 | buildpack: nodejs_buildpack 9 | command: npm run start 10 | path: ./approuter.zip 11 | services: 12 | # Use the same services across approuter and application 13 | - sample-xsuaa-service 14 | env: 15 | TENANT_HOST_PATTERN: 'approuter-(.*).cfapps.sap.hana.ondemand.com' 16 | # Replace the url with your application's url 17 | # Example: sdk-sample-application. 18 | destinations: '[{"name":"backend", "url":"https://sdk-sample-application.cfapps.sap.hana.ondemand.com", "forwardAuthToken": true}]' 19 | -------------------------------------------------------------------------------- /samples/cf-sample-application/approuter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "approuter", 3 | "dependencies": { 4 | "@sap/approuter": "20.2.0", 5 | "package.json": "2.0.1" 6 | }, 7 | "scripts": { 8 | "start": "node node_modules/@sap/approuter/approuter.js", 9 | "package": "./package.sh", 10 | "deploy": "npm run package && cf push", 11 | "deploy:docker": "docker build -t docker-cloudsdk.common.repositories.cloud.sap/k8s-approuter:latest . && docker push docker-cloudsdk.common.repositories.cloud.sap/k8s-approuter:latest" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/cf-sample-application/approuter/package.sh: -------------------------------------------------------------------------------- 1 | { 2 | for file in xs-app.json .npmrc package.json ; do 3 | if [[ ! -e $file ]] ; then echo -e "\e[33m[WARNING] $file does not exist\e[0m"; fi ; 4 | done 5 | zip -r approuter.zip . 6 | } -------------------------------------------------------------------------------- /samples/cf-sample-application/approuter/static-resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World 5 | 6 | 7 |

Hello world!

8 | 9 | -------------------------------------------------------------------------------- /samples/cf-sample-application/approuter/xs-app.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcomeFile": "/web-pages/index.html", 3 | "routes": [ 4 | { 5 | "source": "/backend-app/(.*)", 6 | "target": "$1", 7 | "destination": "backend", 8 | "identityProvider": "" 9 | }, 10 | { 11 | "source": "/web-pages/(.*)", 12 | "target": "$1", 13 | "localDir": "static-resources", 14 | "identityProvider": "" 15 | }] 16 | } -------------------------------------------------------------------------------- /samples/cf-sample-application/approuter/xs-security.json: -------------------------------------------------------------------------------- 1 | { 2 | "xsappname": "sample-xsuaa-service", 3 | "tenant-mode": "shared" 4 | } -------------------------------------------------------------------------------- /samples/cf-sample-application/e2e-tests/cypress.json: -------------------------------------------------------------------------------- 1 | {"chromeWebSecurity": false} 2 | -------------------------------------------------------------------------------- /samples/cf-sample-application/e2e-tests/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /samples/cf-sample-application/e2e-tests/cypress/integration/e2e.spec.ts: -------------------------------------------------------------------------------- 1 | describe('e2e tests for k8s test app', () => { 2 | function checkEndpoint(endpoint: string) { 3 | cy.visit('https://' + Cypress.env('url')); 4 | 5 | cy.get('#j_username') 6 | .type(Cypress.env('username')) 7 | .get('#j_password') 8 | .type(Cypress.env('password')) 9 | .get('#logOnFormSubmit') 10 | .click(); 11 | 12 | cy.request(`backend-app/${endpoint}`).then((resp) => { 13 | expect(resp.status).to.eq(200); 14 | }); 15 | } 16 | it('tests cloud endpoint', () => { 17 | checkEndpoint('cloud-business-partner'); 18 | }); 19 | 20 | it('tests onpremise endpoint', () => { 21 | checkEndpoint('onpremise-business-partner'); 22 | }); 23 | 24 | it('tests principal propagation endpoint', () => { 25 | checkEndpoint('principal-business-partner'); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /samples/cf-sample-application/e2e-tests/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | // eslint-disable-next-line no-unused-vars 19 | module.exports = (on, config) => { 20 | // `on` is used to hook into various events Cypress emits 21 | // `config` is the resolved Cypress config 22 | } 23 | -------------------------------------------------------------------------------- /samples/cf-sample-application/e2e-tests/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /samples/cf-sample-application/e2e-tests/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /samples/cf-sample-application/e2e-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sap-cloud-sdk/samples", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "npx cypress run" 7 | }, 8 | "author": "", 9 | "license": "Apache-2.0", 10 | "devDependencies": { 11 | "cypress": "^12.0.0", 12 | "typescript": "~5.7.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/cf-sample-application/e2e-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es5", "dom"], 5 | "types": ["cypress"] 6 | }, 7 | "include": ["**/*.ts"] 8 | } -------------------------------------------------------------------------------- /samples/cf-sample-application/manifest.yml: -------------------------------------------------------------------------------- 1 | applications: 2 | - name: sdk-sample-application 3 | path: ./ 4 | buildpacks: 5 | - nodejs_buildpack 6 | memory: 512M 7 | instances: 2 8 | command: npm run start:prod 9 | services: 10 | - sample-destination-service 11 | - sample-xsuaa-service 12 | - sample-connectivity-service 13 | -------------------------------------------------------------------------------- /samples/cf-sample-application/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /samples/cf-sample-application/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sap-cloud-sdk/samples", 3 | "version": "0.0.1", 4 | "description": "SAP Cloud SDK for JS - Sample application", 5 | "private": true, 6 | "license": "Apache-2.0", 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json", 21 | "ci-build": "echo \"Use this to compile or minify your application\"", 22 | "deploy": "npm run build && cf push", 23 | "deploy:docker": "npm run build && docker build -t docker-cloudsdk.common.repositories.cloud.sap/k8s-e2e-app:latest . && docker push docker-cloudsdk.common.repositories.cloud.sap/k8s-e2e-app:latest", 24 | "deploy:pipeline": "docker build -t docker-cloudsdk.common.repositories.cloud.sap/sdkjs-k8s-pipeline:latest ./pipeline && docker push docker-cloudsdk.common.repositories.cloud.sap/sdkjs-k8s-pipeline:latest", 25 | "generate-client": "npx generate-odata-client --input resources/service-specs --outputDir src/generated --optionsPerService=resources/service-specs/options-per-service.json --clearOutputDir --transpile=false" 26 | }, 27 | "dependencies": { 28 | "@nestjs/common": "^9.4.3", 29 | "@nestjs/core": "^9.4.3", 30 | "@nestjs/platform-express": "^9.4.3", 31 | "@sap-cloud-sdk/connectivity": "^4.0.0", 32 | "@sap-cloud-sdk/odata-v2": "^3.0.0", 33 | "@sap/xsenv": "^3.4.0", 34 | "@sap/xssec": "^3.2.17", 35 | "passport": "^0.7.0", 36 | "reflect-metadata": "^0.1.13", 37 | "rimraf": "^3.0.2", 38 | "rxjs": "^7.8.1", 39 | "webpack": "^5.80.0" 40 | }, 41 | "devDependencies": { 42 | "@nestjs/cli": "^9.5.0", 43 | "@nestjs/schematics": "^9.2.0", 44 | "@nestjs/testing": "^9.4.3", 45 | "@sap-cloud-sdk/test-util": "^3.0.0", 46 | "@sap-cloud-sdk/generator": "^3.0.0", 47 | "@types/express": "^4.17.17", 48 | "@types/jest": "^29.5.4", 49 | "@types/node": "^22.10.5", 50 | "@types/supertest": "^2.0.12", 51 | "@typescript-eslint/eslint-plugin": "^5.59.0", 52 | "@typescript-eslint/parser": "^5.59.0", 53 | "eslint": "^8.38.0", 54 | "eslint-config-prettier": "^8.8.0", 55 | "eslint-plugin-prettier": "^4.2.1", 56 | "jest": "29.6.4", 57 | "prettier": "^3.4.2", 58 | "supertest": "^6.3.3", 59 | "ts-jest": "^29.1.1", 60 | "ts-loader": "^9.4.4", 61 | "ts-node": "^10.9.1", 62 | "tsconfig-paths": "^4.2.0", 63 | "typescript": "~5.7.2" 64 | }, 65 | "jest": { 66 | "moduleFileExtensions": [ 67 | "js", 68 | "json", 69 | "ts" 70 | ], 71 | "rootDir": "src", 72 | "testRegex": ".*\\.spec\\.ts$", 73 | "transform": { 74 | "^.+\\.(t|j)s$": "ts-jest" 75 | }, 76 | "collectCoverageFrom": [ 77 | "**/*.(t|j)s" 78 | ], 79 | "coverageDirectory": "../coverage", 80 | "testEnvironment": "node" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /samples/cf-sample-application/resources/service-specs/options-per-service.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources/service-specs/OP_API_BUSINESS_PARTNER_SRV.edmx": { 3 | "packageName": "op-business-partner-service", 4 | "directoryName": "op-business-partner-service", 5 | "basePath": "/sap/opu/odata/sap/API_BUSINESS_PARTNER" 6 | }, 7 | "resources/service-specs/API_BUSINESS_PARTNER.edmx": { 8 | "packageName": "cloud-business-partner-service", 9 | "directoryName": "cloud-business-partner-service", 10 | "basePath": "/sap/opu/odata/sap/API_BUSINESS_PARTNER" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Req } from '@nestjs/common'; 2 | import { BusinessPartner as BusinessPartnerCloud } from './generated/cloud-business-partner-service'; 3 | import { BusinessPartner as BusinessPartnerOp } from './generated/op-business-partner-service'; 4 | import { OnpremiseBusinessPartnerService } from './onpremise-business-partner/onpremise-business-partner.service'; 5 | import { AppService } from './app.service'; 6 | import { CloudBusinessPartnerService } from './cloud-business-partner/cloud-business-partner.service'; 7 | import { PrincipalBusinessPartnerService } from './principal-business-partner/principal-business-partner.service'; 8 | import { Request } from 'express'; 9 | import { LoadtestService } from './loadtest/loadtest.service'; 10 | 11 | @Controller() 12 | export class AppController { 13 | constructor( 14 | private readonly appService: AppService, 15 | private readonly cloudBusinessPartnerService: CloudBusinessPartnerService, 16 | private readonly onpremiseBusinessPartnerService: OnpremiseBusinessPartnerService, 17 | private readonly principalBusinessPartnerService: PrincipalBusinessPartnerService, 18 | private readonly loadtestService: LoadtestService, 19 | ) {} 20 | 21 | @Get() 22 | getHello(): string { 23 | return this.appService.getHello(); 24 | } 25 | 26 | @Get('cloud-business-partner') 27 | getCloudBusinessPartner(): Promise { 28 | return this.cloudBusinessPartnerService.getFiveBusinessPartners(); 29 | } 30 | 31 | @Get('onpremise-business-partner') 32 | getOnpremiseBusinessPartner(): Promise { 33 | return this.onpremiseBusinessPartnerService.getFiveBusinessPartners(); 34 | } 35 | 36 | @Get('principal-business-partner') 37 | getPrincipalBusinessPartner( 38 | @Req() request: Request, 39 | ): Promise { 40 | return this.principalBusinessPartnerService.getFiveBusinessPartners( 41 | request, 42 | ); 43 | } 44 | 45 | @Get('loadtest') 46 | calculateExpensiveNumber(): number { 47 | return this.loadtestService.calculateExpensiveNumber(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { OnpremiseBusinessPartnerService } from './onpremise-business-partner/onpremise-business-partner.service'; 5 | import { CloudBusinessPartnerService } from './cloud-business-partner/cloud-business-partner.service'; 6 | import { PrincipalBusinessPartnerService } from './principal-business-partner/principal-business-partner.service'; 7 | import { LoadtestService } from './loadtest/loadtest.service'; 8 | 9 | @Module({ 10 | imports: [], 11 | controllers: [AppController], 12 | providers: [ 13 | AppService, 14 | OnpremiseBusinessPartnerService, 15 | CloudBusinessPartnerService, 16 | PrincipalBusinessPartnerService, 17 | LoadtestService, 18 | ], 19 | }) 20 | export class AppModule {} 21 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/cloud-business-partner/cloud-business-partner.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CloudBusinessPartnerService } from './cloud-business-partner.service'; 3 | 4 | describe('CloudBusinessPartnerService', () => { 5 | let service: CloudBusinessPartnerService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [CloudBusinessPartnerService], 10 | }).compile(); 11 | 12 | service = module.get( 13 | CloudBusinessPartnerService, 14 | ); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(service).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/cloud-business-partner/cloud-business-partner.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | BusinessPartner, 4 | cloudBusinessPartnerService as businessPartnerService, 5 | } from '../generated/cloud-business-partner-service'; 6 | const { businessPartnerApi } = businessPartnerService(); 7 | 8 | @Injectable() 9 | export class CloudBusinessPartnerService { 10 | async getFiveBusinessPartners(): Promise { 11 | return businessPartnerApi 12 | .requestBuilder() 13 | .getAll() 14 | .top(5) 15 | // the destination should point at a cloud basic auth destination 16 | // Example: 17 | .execute({ destinationName: 'myCloudDestination' }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/loadtest/loadtest.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { LoadtestService } from './loadtest.service'; 3 | 4 | describe('LoadtestService', () => { 5 | let service: LoadtestService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [LoadtestService], 10 | }).compile(); 11 | 12 | service = module.get(LoadtestService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/loadtest/loadtest.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class LoadtestService { 5 | calculateExpensiveNumber(): number { 6 | var expensiveNumber = 0; 7 | for(var i = 0; i<1000; i++){ 8 | for(var j = 0; j<1000; j++){ 9 | expensiveNumber += i*j*Date.now() 10 | } 11 | } 12 | return expensiveNumber; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { JWTStrategy } from '@sap/xssec'; 4 | import { getServices } from '@sap/xsenv'; 5 | import * as passport from 'passport'; 6 | 7 | // Use the same xsuaa across the entire application 8 | const xsuaa = getServices({ xsuaa: { name: 'sample-xsuaa-service' } }).xsuaa; 9 | passport.use(new JWTStrategy(xsuaa)); 10 | 11 | async function bootstrap() { 12 | const app = await NestFactory.create(AppModule); 13 | app.use(passport.initialize()); 14 | app.use(passport.authenticate('JWT', { session: false })); 15 | await app.listen(process.env.PORT || 3000); 16 | } 17 | bootstrap(); 18 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/onpremise-business-partner/onpremise-business-partner.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { OnpremiseBusinessPartnerService } from './onpremise-business-partner.service'; 3 | 4 | describe('OnpremiseBusinessPartnerService', () => { 5 | let service: OnpremiseBusinessPartnerService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [OnpremiseBusinessPartnerService], 10 | }).compile(); 11 | 12 | service = module.get( 13 | OnpremiseBusinessPartnerService, 14 | ); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(service).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/onpremise-business-partner/onpremise-business-partner.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | BusinessPartner, 4 | opBusinessPartnerService as businessPartnerService, 5 | } from '../generated/op-business-partner-service'; 6 | const { businessPartnerApi } = businessPartnerService(); 7 | 8 | @Injectable() 9 | export class OnpremiseBusinessPartnerService { 10 | async getFiveBusinessPartners(): Promise { 11 | return businessPartnerApi 12 | .requestBuilder() 13 | .getAll() 14 | .top(5) 15 | // the destination should point at a onpremise basic authentcation destination 16 | // Example: 17 | .execute({ destinationName: 'myOnpremiseDestination' }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/principal-business-partner/principal-business-partner.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PrincipalBusinessPartnerService } from './principal-business-partner.service'; 3 | 4 | describe('PrincipalBusinessPartnerService', () => { 5 | let service: PrincipalBusinessPartnerService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [PrincipalBusinessPartnerService], 10 | }).compile(); 11 | 12 | service = module.get(PrincipalBusinessPartnerService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /samples/cf-sample-application/src/principal-business-partner/principal-business-partner.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | BusinessPartner, 4 | opBusinessPartnerService as businessPartnerService, 5 | } from '../generated/op-business-partner-service'; 6 | import { retrieveJwt } from '@sap-cloud-sdk/connectivity'; 7 | import { Request } from 'express'; 8 | const { businessPartnerApi } = businessPartnerService(); 9 | 10 | @Injectable() 11 | export class PrincipalBusinessPartnerService { 12 | async getFiveBusinessPartners(request: Request): Promise { 13 | return businessPartnerApi 14 | .requestBuilder() 15 | .getAll() 16 | .top(5) 17 | .execute({ 18 | // the destination should point at a principal propagation destination 19 | // Example: 20 | destinationName: 'myPrincipalPropagationDestination', 21 | jwt: retrieveJwt(request), 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/cf-sample-application/systems.json: -------------------------------------------------------------------------------- 1 | { 2 | "systems": [{ 3 | "alias": "EXAMPLE", 4 | "uri": "https://example.com" 5 | }] 6 | } 7 | -------------------------------------------------------------------------------- /samples/cf-sample-application/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /samples/cf-sample-application/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/cf-sample-application/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /samples/cf-sample-application/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/helm-sample-application/README.md: -------------------------------------------------------------------------------- 1 | # SAP Cloud SDK for JS Kubernetes End-2-End Application 2 | This directory is structured the following way: 3 | 4 | - `./application` contains our Heln sample application 5 | - `./helm-chart` contains the Helm chart, which we use to deploy the application 6 | - `./sap-btp-operator` contains the Helm chart of the SAP BTP Operator, together with a self-signed issuer, all services which are related to our application, as well as a simple `values.yaml` for the helm chart 7 | 8 | ## Download and Installation 9 | To download the application run: 10 | 11 | ``` 12 | git clone \ 13 | --depth 1 \ 14 | --filter=blob:none \ 15 | --sparse \ 16 | https://github.com/SAP-samples/cloud-sdk-js.git \ 17 | ; 18 | cd cloud-sdk-js 19 | git sparse-checkout set samples/helm-sample-application 20 | ``` 21 | 22 | To deploy the application, follow the guidelines in the [application directory](./application/README.md). 23 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package.json . 6 | 7 | RUN npm install -g npm@latest 8 | 9 | RUN npm install --unsafe-perm --production 10 | 11 | COPY . ./ 12 | 13 | EXPOSE 3000 14 | CMD ["npm", "run", "start:prod"] 15 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/README.md: -------------------------------------------------------------------------------- 1 | # SAP Cloud SDK for JS Kubernetes End-2-End Application 2 | 3 | This repository contains our end-2-end application which we use to test the sdk's functionality in a Kubernetes environment. 4 | The application is deployed together with our Helm chart located [here](https://github.tools.sap/cloudsdk/k8s-sdkjs-chart/blob/main/README.md). 5 | 6 | The repositories structure is as following: 7 | 8 | - `/approuter` - Contains the approuter, a packaging script and a Dockerfile which is used in the deployment 9 | - `/pipeline` - Contains the pipeline's Dockerfile which contains all tools to run the pipeline 10 | - `/src` - Contains the application's source code with its 4 endpoints for cloud, onpremise and principal propagation 11 | - `/e2e-tests` - Contains the cypress tests that test all endpoints after deployment 12 | - `./github/workflows` - Contains the pipeline that builds, packages, deploys and tests the application 13 | - `Dockerfile` - Packages the application as Dockerimage to be used in Kubernetes 14 | 15 | If you want to locally trigger the e2e-tests, create a `cypress.env.json` file in the `/e2e-tests` directory containing the credentials for the IdP in the format: 16 | 17 | ``` 18 | { 19 | "username": "username", 20 | "password": "password" 21 | "url": "url" 22 | } 23 | ``` 24 | 25 | ## Requirements 26 | The minimal requirements are: 27 | - A terminal to execute commands 28 | - A recent version of node and npm installed e.g. node 14 and npm 6 29 | - An IDE or a text editor of your choice 30 | 31 | If you want to explore the possibilities beyond local tests you need: 32 | - Access to a [SAP Business Technology Platform](https://www.sap.com/products/business-technology-platform.html) account 33 | - Entitlement to use resources like service instance creation and application processing units 34 | - Permission to create service instances 35 | - Access to a `Docker` repository 36 | - A Kubernetes Cluster which runs: 37 | - The SAP BTP Operator 38 | - The SAP Connectivity Proxy 39 | 40 | ## Download and Installation 41 | To download the application run: 42 | 43 | ``` 44 | git clone \ 45 | --depth 1 \ 46 | --filter=blob:none \ 47 | --sparse \ 48 | https://github.com/SAP-samples/cloud-sdk-js.git \ 49 | ; 50 | cd cloud-sdk-js 51 | git sparse-checkout set samples/helm-sample-application 52 | ``` 53 | 54 | ### Generate oData Client 55 | 56 | The following service definitions in `EDMX` format are already downloaded in the folder `resources/service-specs`: 57 | - [Business Partner service onPremise](https://api.sap.com/api/OP_API_BUSINESS_PARTNER_SRV/overview) 58 | - [Business Partner service cloud](https://api.sap.com/api/API_BUSINESS_PARTNER/overview) 59 | 60 | The clients are generated using the `npm run generate-client` command. This command is executed automatically in the `postinstall` step after you execute `npm i `. 61 | 62 | **Note** These services are licensed under the terms of [SAP API Information License](../../../LICENSES/LicenseRef-API-Definition-File-License.txt). This limits their use to development purposes only. 63 | 64 | ### Deploy to Docker 65 | 1. In the `package.json`, change the `deploy:docker` and `deploy:pipeline` scripts to point at your docker repository. 66 | 2. Change the the `deploy:docker` script in the approuter's `package.json` to point at your docker repository. 67 | 3. Deploy the Docker images to your repository with `npm run deploy:docker` and `npm run deploy:pipeline` in case you want to use the pipeline. 68 | 69 | ### Deploy to Kubernetes 70 | 71 | To deploy this application to Kubernetes, first you have to deploy all the services this application has to use, which includes: 72 | - the `connectivity` service 73 | - the `xsuaa` service 74 | - the `destination` service 75 | 76 | To deploy them simply `kubectl apply -f` the yaml files under `sap-btp-operator/services`. 77 | 78 | After deploying the services, all you have to do is editing the `values.yaml` file under `helm-chart`. 79 | The helm chart's [README](../helm-chart/README.md) should explain to you what values you can use and which you have to change. 80 | 81 | Once you have changed the `values.yaml`, run `helm install e2e-app k8s-e2e-app-helm-0.1.5.tgz --values values.yaml` and you should be good to go. 82 | 83 | For more detailed information on Kubernetes deployment, check out our [Kubernetes migration guide](https://sap.github.io/cloud-sdk/docs/js/guides/migrate-sdk-application-from-btp-cf-to-kubernetes). 84 | 85 | The architecture of this application, together with its dependencies, the connectivity proxy, and the services created by the sap-btp-operator, is depicted in the following architecture diagram: 86 | 87 | 88 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/approuter/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm config set @sap:registry https://registry.npmjs.org 12 | RUN npm install 13 | 14 | # Bundle app source 15 | COPY . . 16 | 17 | EXPOSE 5000 18 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /samples/helm-sample-application/application/approuter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "approuter", 3 | "dependencies": { 4 | "@sap/approuter": "20.2.0", 5 | "package.json": "2.0.1" 6 | }, 7 | "scripts": { 8 | "start": "node node_modules/@sap/approuter/approuter.js", 9 | "package": "./package.sh", 10 | "deploy": "npm run package && cf push", 11 | "deploy:docker": "docker build -t docker-cloudsdk.common.repositories.cloud.sap/k8s-approuter:latest . && docker push docker-cloudsdk.common.repositories.cloud.sap/k8s-approuter:latest" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/approuter/static-resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World 5 | 6 | 7 |

Hello world!

8 | 9 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/e2e-tests/cypress.json: -------------------------------------------------------------------------------- 1 | {"chromeWebSecurity": false} 2 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/e2e-tests/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/e2e-tests/cypress/integration/e2e.spec.ts: -------------------------------------------------------------------------------- 1 | describe('e2e tests for k8s test app', () => { 2 | function checkEndpoint(endpoint: string) { 3 | cy.visit('https://' + Cypress.env('url')); 4 | 5 | cy.get('#j_username') 6 | .type(Cypress.env('username')) 7 | .get('#j_password') 8 | .type(Cypress.env('password')) 9 | .get('#logOnFormSubmit') 10 | .click(); 11 | 12 | cy.request(`backend-app/${endpoint}`).then((resp) => { 13 | expect(resp.status).to.eq(200); 14 | }); 15 | } 16 | it('tests cloud endpoint', () => { 17 | checkEndpoint('cloud-business-partner'); 18 | }); 19 | 20 | it('tests onpremise endpoint', () => { 21 | checkEndpoint('onpremise-business-partner'); 22 | }); 23 | 24 | it('tests principal propagation endpoint', () => { 25 | checkEndpoint('principal-business-partner'); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/e2e-tests/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | // eslint-disable-next-line no-unused-vars 19 | module.exports = (on, config) => { 20 | // `on` is used to hook into various events Cypress emits 21 | // `config` is the resolved Cypress config 22 | } 23 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/e2e-tests/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/e2e-tests/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/e2e-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sap-cloud-sdk/samples", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "npx cypress run" 7 | }, 8 | "author": "", 9 | "license": "Apache-2.0", 10 | "devDependencies": { 11 | "cypress": "^12.0.0", 12 | "typescript": "~5.7.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/e2e-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es5", "dom"], 5 | "types": ["cypress"] 6 | }, 7 | "include": ["**/*.ts"] 8 | } -------------------------------------------------------------------------------- /samples/helm-sample-application/application/images/cluster_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/cloud-sdk-js/1bfa08bcdc2694d96bef8798d849afc4c10ec4a8/samples/helm-sample-application/application/images/cluster_arch.png -------------------------------------------------------------------------------- /samples/helm-sample-application/application/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sap-cloud-sdk/samples", 3 | "version": "0.0.1", 4 | "description": "SAP Cloud SDK for JS - Sample application", 5 | "private": true, 6 | "license": "Apache-2.0", 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json", 21 | "ci-build": "echo \"Use this to compile or minify your application\"", 22 | "deploy": "npm run build && cf push", 23 | "deploy:docker": "npm run build && docker build -t docker-cloudsdk.common.repositories.cloud.sap/k8s-e2e-app:latest . && docker push docker-cloudsdk.common.repositories.cloud.sap/k8s-e2e-app:latest", 24 | "deploy:pipeline": "docker build -t docker-cloudsdk.common.repositories.cloud.sap/sdkjs-k8s-pipeline:latest ./pipeline && docker push docker-cloudsdk.common.repositories.cloud.sap/sdkjs-k8s-pipeline:latest", 25 | "generate-client": "npx generate-odata-client --input resources/service-specs --outputDir src/generated --optionsPerService=resources/service-specs/options-per-service.json --clearOutputDir --transpile=false" 26 | }, 27 | "dependencies": { 28 | "@nestjs/common": "^9.4.3", 29 | "@nestjs/core": "^9.4.3", 30 | "@nestjs/platform-express": "^9.4.3", 31 | "@sap-cloud-sdk/connectivity": "^4.0.0", 32 | "@sap-cloud-sdk/odata-v2": "^3.0.0", 33 | "@sap/xsenv": "^3.4.0", 34 | "@sap/xssec": "^3.2.17", 35 | "passport": "^0.7.0", 36 | "reflect-metadata": "^0.1.13", 37 | "rimraf": "^3.0.2", 38 | "rxjs": "^7.8.1", 39 | "webpack": "^5.80.0" 40 | }, 41 | "devDependencies": { 42 | "@nestjs/cli": "^9.5.0", 43 | "@nestjs/schematics": "^9.2.0", 44 | "@nestjs/testing": "^9.4.3", 45 | "@sap-cloud-sdk/test-util": "^3.0.0", 46 | "@sap-cloud-sdk/generator": "^3.0.0", 47 | "@types/express": "^4.17.17", 48 | "@types/jest": "^29.5.4", 49 | "@types/node": "^22.10.5", 50 | "@types/supertest": "^2.0.12", 51 | "@typescript-eslint/eslint-plugin": "^5.59.0", 52 | "@typescript-eslint/parser": "^5.59.0", 53 | "eslint": "^8.38.0", 54 | "eslint-config-prettier": "^8.8.0", 55 | "eslint-plugin-prettier": "^4.2.1", 56 | "jest": "29.6.4", 57 | "prettier": "^3.4.2", 58 | "supertest": "^6.3.3", 59 | "ts-jest": "^29.1.1", 60 | "ts-loader": "^9.4.4", 61 | "ts-node": "^10.9.1", 62 | "tsconfig-paths": "^4.2.0", 63 | "typescript": "~5.7.2" 64 | }, 65 | "jest": { 66 | "moduleFileExtensions": [ 67 | "js", 68 | "json", 69 | "ts" 70 | ], 71 | "rootDir": "src", 72 | "testRegex": ".*\\.spec\\.ts$", 73 | "transform": { 74 | "^.+\\.(t|j)s$": "ts-jest" 75 | }, 76 | "collectCoverageFrom": [ 77 | "**/*.(t|j)s" 78 | ], 79 | "coverageDirectory": "../coverage", 80 | "testEnvironment": "node" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/pipeline/Dockerfile: -------------------------------------------------------------------------------- 1 | # Start from the docker image which contains mvn 2 | FROM ubuntu:20.04 3 | 4 | # Install docker 5 | ## Update the apt package index and install packages to allow apt to use a repository over HTTPS: 6 | RUN apt-get update 7 | RUN apt-get install -y \ 8 | apt-transport-https \ 9 | ca-certificates \ 10 | curl \ 11 | gnupg \ 12 | lsb-release 13 | 14 | ## Add Docker’s official GPG key: 15 | RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 16 | 17 | ## Use the following command to set up the stable repository. 18 | RUN echo \ 19 | "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ 20 | $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null 21 | 22 | ## Install Docker Engine 23 | RUN apt-get update 24 | RUN DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce docker-ce-cli containerd.io 25 | 26 | # Install git 27 | RUN apt install -y git-all 28 | 29 | # Install kubectl 30 | RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" 31 | RUN install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl 32 | 33 | # Add core dependencies for cypress 34 | RUN apt-get install -y libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb 35 | 36 | # Install node 14 37 | RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash - 38 | RUN apt-get install -y nodejs 39 | 40 | # Update npm version to use lockFileV2 41 | RUN npm install -g npm@latest 42 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/resources/service-specs/options-per-service.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources/service-specs/API_BUSINESS_PARTNER.edmx": { 3 | "packageName": "cloud-business-partner-service", 4 | "directoryName": "cloud-business-partner-service", 5 | "basePath": "/sap/opu/odata/sap/API_BUSINESS_PARTNER" 6 | }, 7 | "resources/service-specs/OP_API_BUSINESS_PARTNER_SRV.edmx": { 8 | "packageName": "op-business-partner-service", 9 | "directoryName": "op-business-partner-service", 10 | "basePath": "/sap/opu/odata/sap/API_BUSINESS_PARTNER" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Req } from '@nestjs/common'; 2 | import { BusinessPartner as BusinessPartnerCloud } from './generated/cloud-business-partner-service'; 3 | import { BusinessPartner as BusinessPartnerOp } from './generated/op-business-partner-service'; 4 | import { OnpremiseBusinessPartnerService } from './onpremise-business-partner/onpremise-business-partner.service'; 5 | import { AppService } from './app.service'; 6 | import { CloudBusinessPartnerService } from './cloud-business-partner/cloud-business-partner.service'; 7 | import { PrincipalBusinessPartnerService } from './principal-business-partner/principal-business-partner.service'; 8 | import { Request } from 'express'; 9 | import { LoadtestService } from './loadtest/loadtest.service'; 10 | 11 | @Controller() 12 | export class AppController { 13 | constructor( 14 | private readonly appService: AppService, 15 | private readonly cloudBusinessPartnerService: CloudBusinessPartnerService, 16 | private readonly onpremiseBusinessPartnerService: OnpremiseBusinessPartnerService, 17 | private readonly principalBusinessPartnerService: PrincipalBusinessPartnerService, 18 | private readonly loadtestService: LoadtestService, 19 | ) {} 20 | 21 | @Get() 22 | getHello(): string { 23 | return this.appService.getHello(); 24 | } 25 | 26 | @Get('cloud-business-partner') 27 | getCloudBusinessPartner(): Promise { 28 | return this.cloudBusinessPartnerService.getFiveBusinessPartners(); 29 | } 30 | 31 | @Get('onpremise-business-partner') 32 | getOnpremiseBusinessPartner(): Promise { 33 | return this.onpremiseBusinessPartnerService.getFiveBusinessPartners(); 34 | } 35 | 36 | @Get('principal-business-partner') 37 | getPrincipalBusinessPartner( 38 | @Req() request: Request, 39 | ): Promise { 40 | return this.principalBusinessPartnerService.getFiveBusinessPartners( 41 | request, 42 | ); 43 | } 44 | 45 | @Get('loadtest') 46 | calculateExpensiveNumber(): number { 47 | return this.loadtestService.calculateExpensiveNumber(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { OnpremiseBusinessPartnerService } from './onpremise-business-partner/onpremise-business-partner.service'; 5 | import { CloudBusinessPartnerService } from './cloud-business-partner/cloud-business-partner.service'; 6 | import { PrincipalBusinessPartnerService } from './principal-business-partner/principal-business-partner.service'; 7 | import { LoadtestService } from './loadtest/loadtest.service'; 8 | 9 | @Module({ 10 | imports: [], 11 | controllers: [AppController], 12 | providers: [ 13 | AppService, 14 | OnpremiseBusinessPartnerService, 15 | CloudBusinessPartnerService, 16 | PrincipalBusinessPartnerService, 17 | LoadtestService, 18 | ], 19 | }) 20 | export class AppModule {} 21 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/cloud-business-partner/cloud-business-partner.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CloudBusinessPartnerService } from './cloud-business-partner.service'; 3 | 4 | describe('CloudBusinessPartnerService', () => { 5 | let service: CloudBusinessPartnerService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [CloudBusinessPartnerService], 10 | }).compile(); 11 | 12 | service = module.get( 13 | CloudBusinessPartnerService, 14 | ); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(service).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/cloud-business-partner/cloud-business-partner.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | BusinessPartner, 4 | cloudBusinessPartnerService as businessPartnerService, 5 | } from '../generated/cloud-business-partner-service'; 6 | const { businessPartnerApi } = businessPartnerService(); 7 | 8 | const destinationName: string = process.env.CLOUD_DESTINATION; 9 | @Injectable() 10 | export class CloudBusinessPartnerService { 11 | async getFiveBusinessPartners(): Promise { 12 | return businessPartnerApi 13 | .requestBuilder() 14 | .getAll() 15 | .top(5) 16 | .execute({ destinationName: destinationName }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/loadtest/loadtest.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { LoadtestService } from './loadtest.service'; 3 | 4 | describe('LoadtestService', () => { 5 | let service: LoadtestService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [LoadtestService], 10 | }).compile(); 11 | 12 | service = module.get(LoadtestService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/loadtest/loadtest.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class LoadtestService { 5 | calculateExpensiveNumber(): number { 6 | let expensiveNumber = 0; 7 | for (let i = 0; i < 1000; i++) { 8 | for (let j = 0; j < 1000; j++) { 9 | expensiveNumber += i * j * Date.now(); 10 | } 11 | } 12 | return expensiveNumber; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { JWTStrategy } from '@sap/xssec'; 4 | import { getServices } from '@sap/xsenv'; 5 | import * as passport from 'passport'; 6 | 7 | const xsuaa = getServices({ xsuaa: { name: 'operator-xsuaa-service' } }).xsuaa; 8 | passport.use(new JWTStrategy(xsuaa)); 9 | 10 | async function bootstrap() { 11 | const app = await NestFactory.create(AppModule); 12 | app.use(passport.initialize()); 13 | app.use(passport.authenticate('JWT', { session: false })); 14 | await app.listen(process.env.PORT || 3000); 15 | } 16 | bootstrap(); 17 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/onpremise-business-partner/onpremise-business-partner.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { OnpremiseBusinessPartnerService } from './onpremise-business-partner.service'; 3 | 4 | describe('OnpremiseBusinessPartnerService', () => { 5 | let service: OnpremiseBusinessPartnerService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [OnpremiseBusinessPartnerService], 10 | }).compile(); 11 | 12 | service = module.get( 13 | OnpremiseBusinessPartnerService, 14 | ); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(service).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/onpremise-business-partner/onpremise-business-partner.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | BusinessPartner, 4 | opBusinessPartnerService as businessPartnerService, 5 | } from '../generated/op-business-partner-service'; 6 | const { businessPartnerApi } = businessPartnerService(); 7 | 8 | const destinationName: string = process.env.ONPREMISE_DESTINATION; 9 | @Injectable() 10 | export class OnpremiseBusinessPartnerService { 11 | async getFiveBusinessPartners(): Promise { 12 | return businessPartnerApi 13 | .requestBuilder() 14 | .getAll() 15 | .top(5) 16 | .execute({ destinationName: destinationName }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/principal-business-partner/principal-business-partner.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PrincipalBusinessPartnerService } from './principal-business-partner.service'; 3 | 4 | describe('PrincipalBusinessPartnerService', () => { 5 | let service: PrincipalBusinessPartnerService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [PrincipalBusinessPartnerService], 10 | }).compile(); 11 | 12 | service = module.get(PrincipalBusinessPartnerService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/src/principal-business-partner/principal-business-partner.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | BusinessPartner, 4 | opBusinessPartnerService as businessPartnerService, 5 | } from '../generated/op-business-partner-service'; 6 | import { retrieveJwt } from '@sap-cloud-sdk/connectivity'; 7 | import { Request } from 'express'; 8 | const { businessPartnerApi } = businessPartnerService(); 9 | 10 | const destinationName: string = process.env.PRINCIPAL_PROPAGATION_DESTINATION; 11 | @Injectable() 12 | export class PrincipalBusinessPartnerService { 13 | async getFiveBusinessPartners(request: Request): Promise { 14 | return businessPartnerApi 15 | .requestBuilder() 16 | .getAll() 17 | .top(5) 18 | .execute({ 19 | destinationName: destinationName, 20 | jwt: retrieveJwt(request), 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/systems.json: -------------------------------------------------------------------------------- 1 | { 2 | "systems": [{ 3 | "alias": "EXAMPLE", 4 | "uri": "https://example.com" 5 | }] 6 | } 7 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /samples/helm-sample-application/application/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm-0.1.6.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/cloud-sdk-js/1bfa08bcdc2694d96bef8798d849afc4c10ec4a8/samples/helm-sample-application/helm-chart/k8s-e2e-app-helm-0.1.6.tgz -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | appVersion: 1.0.0 3 | dependencies: 4 | - name: app-chart 5 | repository: file://charts/app-chart 6 | version: 0.1.0 7 | - name: approuter-chart 8 | repository: file://charts/approuter-chart 9 | version: 0.1.0 10 | description: A Helm chart for Kubernetes 11 | name: k8s-e2e-app-helm 12 | type: application 13 | version: 0.1.5 14 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/app-chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/app-chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | appVersion: 1.0.0 3 | description: A Helm chart for Kubernetes 4 | name: app-chart 5 | type: application 6 | version: 0.1.0 7 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/app-chart/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "app-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 2 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 3 | echo "Visit http://127.0.0.1:8080 to use your application" 4 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 5 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/app-chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "app-chart.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "app-chart.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "app-chart.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "app-chart.labels" -}} 37 | helm.sh/chart: {{ include "app-chart.chart" . }} 38 | {{ include "app-chart.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "app-chart.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "app-chart.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "app-chart.serviceAccountName" -}} 57 | {{- dig "global" "serviceAccountname" "default" (.Values | merge (dict)) }} 58 | {{- end }} 59 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/app-chart/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "app-chart.fullname" . }}-config 5 | data: 6 | cloud_destination: {{ .Values.cloudDestination | quote }} 7 | onpremise_destination: {{ .Values.onPremiseDestination | quote }} 8 | principal_propagation_destination: {{ .Values.principalPropagationDestination | quote }} -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/app-chart/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "app-chart.fullname" . }} 5 | labels: 6 | {{- include "app-chart.labels" . | nindent 4 }} 7 | spec: 8 | replicas: 2 9 | selector: 10 | matchLabels: 11 | {{- include "app-chart.selectorLabels" . | nindent 6 }} 12 | template: 13 | metadata: 14 | labels: 15 | {{- include "app-chart.selectorLabels" . | nindent 8 }} 16 | spec: 17 | {{- with .Values.imagePullSecrets }} 18 | imagePullSecrets: 19 | {{- toYaml . | nindent 8 }} 20 | {{- end }} 21 | serviceAccountName: {{ include "app-chart.serviceAccountName" . }} 22 | containers: 23 | - name: {{ .Chart.Name }} 24 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 25 | ports: 26 | - containerPort: 3000 27 | resources: 28 | {{- toYaml .Values.resources | nindent 12 }} 29 | env: 30 | - name: CLOUD_DESTINATION 31 | valueFrom: 32 | configMapKeyRef: 33 | name: {{ include "app-chart.fullname" . }}-config 34 | key: cloud_destination 35 | - name: ONPREMISE_DESTINATION 36 | valueFrom: 37 | configMapKeyRef: 38 | name: {{ include "app-chart.fullname" . }}-config 39 | key: onpremise_destination 40 | - name: PRINCIPAL_PROPAGATION_DESTINATION 41 | valueFrom: 42 | configMapKeyRef: 43 | name: {{ include "app-chart.fullname" . }}-config 44 | key: principal_propagation_destination 45 | volumeMounts: 46 | - name: destination-volume 47 | mountPath: {{ printf "/etc/secrets/sapcp/destination/%s" .Values.destinationBinding | quote }} 48 | readOnly: true 49 | - name: xsuaa-volume 50 | mountPath: {{ printf "/etc/secrets/sapcp/xsuaa/%s" .Values.xsuaaBinding | quote }} 51 | readOnly: true 52 | - name: connectivity-volume 53 | mountPath: {{ printf "/etc/secrets/sapcp/connectivity/%s" .Values.connectivityBinding | quote }} 54 | readOnly: true 55 | volumes: 56 | - name: destination-volume 57 | secret: 58 | secretName: {{ .Values.destinationBinding | quote }} 59 | - name: xsuaa-volume 60 | secret: 61 | secretName: {{ .Values.xsuaaBinding | quote }} 62 | - name: connectivity-volume 63 | secret: 64 | secretName: {{ .Values.connectivityBinding | quote }} 65 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/app-chart/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "app-chart.fullname" . }}-svc 5 | labels: 6 | {{- include "app-chart.labels" . | nindent 4 }} 7 | spec: 8 | ports: 9 | - port: 8080 10 | targetPort: 3000 11 | selector: 12 | {{- include "app-chart.selectorLabels" . | nindent 4 }} 13 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/app-chart/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "app-chart.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "app-chart.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "app-chart.fullname" . }}:8080'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/app-chart/values.yaml: -------------------------------------------------------------------------------- 1 | image: 2 | repository: docker-cloudsdk.common.repositories.cloud.sap/k8s-e2e-app 3 | tag: latest 4 | resources: 5 | requests: 6 | memory: "256Mi" 7 | cpu: "500m" 8 | limits: 9 | memory: "512Mi" 10 | cpu: "1000m" -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/approuter-chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/approuter-chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | appVersion: 1.0.0 3 | description: A Helm chart for Kubernetes 4 | name: approuter-chart 5 | type: application 6 | version: 0.1.0 7 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/approuter-chart/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "approuter-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 2 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 3 | echo "Visit http://127.0.0.1:8080 to use your application" 4 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 5 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/approuter-chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "approuter-chart.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "approuter-chart.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "approuter-chart.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "approuter-chart.labels" -}} 37 | helm.sh/chart: {{ include "approuter-chart.chart" . }} 38 | {{ include "approuter-chart.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "approuter-chart.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "approuter-chart.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "approuter-chart.serviceAccountName" -}} 57 | {{- dig "global" "serviceAccountname" "default" (.Values | merge (dict)) }} 58 | {{- end }} 59 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/approuter-chart/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "approuter-chart.fullname" . }}-config 5 | data: 6 | {{- if .Values.config.idp }} 7 | xs-app.json: {{ printf "{\"welcomeFile\":\"/web-pages/index.html\",\"routes\":[{\"source\":\"/backend-app/(.*)\",\"target\":\"$1\",\"destination\":\"backend\",\"identityProvider\":\"%s\"},{\"source\":\"/web-pages/(.*)\",\"target\":\"$1\",\"localDir\":\"static-resources\",\"identityProvider\":\"%s\"}]}" .Values.config.idp .Values.config.idp | toPrettyJson }} 8 | {{- else }} 9 | xs-app.json: {{ .Values.config.json | toPrettyJson | quote }} 10 | {{ end -}} 11 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/approuter-chart/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "approuter-chart.fullname" . }} 5 | labels: 6 | {{- include "approuter-chart.labels" . | nindent 4 }} 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | {{- include "approuter-chart.selectorLabels" . | nindent 6 }} 12 | template: 13 | metadata: 14 | labels: 15 | {{- include "approuter-chart.selectorLabels" . | nindent 8 }} 16 | spec: 17 | {{- with .Values.imagePullSecrets }} 18 | imagePullSecrets: 19 | {{- toYaml . | nindent 8 }} 20 | {{- end }} 21 | serviceAccountName: {{ include "approuter-chart.serviceAccountName" . }} 22 | containers: 23 | - name: {{ .Chart.Name }} 24 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 25 | ports: 26 | - containerPort: 5000 27 | resources: 28 | {{- toYaml .Values.resources | nindent 12 }} 29 | env: 30 | - name: PORT 31 | value: "5000" 32 | - name: destinations 33 | value: '[{"name":"backend", "url":"http://{{ printf "%s-%s" .Release.Name "app-chart" | trunc 63 | trimSuffix "-" }}-svc:8080/", "forwardAuthToken": true}]' 34 | - name: TENANT_HOST_PATTERN 35 | value: {{ .Values.config.pattern | quote }} 36 | volumeMounts: 37 | - name: xsuaa-volume 38 | mountPath: {{ printf "/etc/secrets/sapcp/xsuaa/%s" .Values.xsuaaBinding | quote}} 39 | readOnly: true 40 | - name: approuter-volume 41 | mountPath: "/usr/src/app/xs-app.json" 42 | subPath: "xs-app.json" 43 | readOnly: true 44 | volumes: 45 | - name: xsuaa-volume 46 | secret: 47 | secretName: {{ .Values.xsuaaBinding | quote}} 48 | - name: approuter-volume 49 | configMap: 50 | name: {{ include "approuter-chart.fullname" . }}-config 51 | items: 52 | - key: xs-app.json 53 | path: xs-app.json 54 | 55 | 56 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/approuter-chart/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "approuter-chart.fullname" . }}-svc 5 | labels: 6 | {{- include "approuter-chart.labels" . | nindent 4 }} 7 | spec: 8 | ports: 9 | - port: 8080 10 | targetPort: 5000 11 | selector: 12 | {{- include "approuter-chart.selectorLabels" . | nindent 4 }} 13 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/approuter-chart/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "approuter-chart.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "approuter-chart.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "approuter-chart.fullname" . }}:8080'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/charts/approuter-chart/values.yaml: -------------------------------------------------------------------------------- 1 | xsuaaBinding: 2 | config: 3 | idp: 4 | pattern: 5 | image: 6 | repository: docker-cloudsdk.common.repositories.cloud.sap/k8s-approuter 7 | tag: latest 8 | resources: 9 | requests: 10 | memory: "256Mi" 11 | cpu: "500m" 12 | limits: 13 | memory: "512Mi" 14 | cpu: "1000m" -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. These routes were created with this chart: 2 | {{- if .Values.expose.enabled }} 3 | {{- if .Values.expose.ingress.shortRoute }} 4 | - https://{{ .Values.expose.ingress.shortRoute }} 5 | {{- end }} 6 | {{- if .Values.expose.ingress.exposedRoute }} 7 | - https://{{ .Values.expose.ingress.exposedRoute }} 8 | {{- end }} 9 | {{- if .Values.expose.ingress.connectivityProxyRoute }} 10 | - https://{{ .Values.expose.ingress.connectivityProxyRoute }} 11 | {{- end }} 12 | {{- end }} 13 | 14 | Use the following commands to connect to your application from within the cluster: 15 | 16 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name=approuter-chart,app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 17 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 18 | echo "Visit http://127.0.0.1:8080 to use your application" 19 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 20 | 21 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "k8s-e2e-app-helm.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "k8s-e2e-app-helm.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "k8s-e2e-app-helm.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "k8s-e2e-app-helm.labels" -}} 37 | helm.sh/chart: {{ include "k8s-e2e-app-helm.chart" . }} 38 | {{ include "k8s-e2e-app-helm.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "k8s-e2e-app-helm.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "k8s-e2e-app-helm.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "k8s-e2e-app-helm.serviceAccountName" -}} 57 | {{- dig "global" "serviceAccountname" "default" (.Values | merge (dict)) }} 58 | {{- end }} 59 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/templates/api-gateway.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Values.expose.enabled) (eq .Values.expose.environment "kyma") -}} 2 | {{- $name := default "approuter-chart" .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 3 | {{- $fullName := include "k8s-e2e-app-helm.fullname" . -}} 4 | {{- $svcName := index .Values "approuter-chart" "name" | default (printf "%s-%s-svc" .Release.Name $name) | trunc 63 | trimSuffix "-" | quote -}} 5 | {{- $svcPort := dig "approuter-chart" "service" "port" 8080 (.Values | merge (dict)) -}} 6 | 7 | apiVersion: gateway.kyma-project.io/v1beta1 8 | kind: APIRule 9 | metadata: 10 | name: {{ $fullName }}-api-rule 11 | labels: 12 | {{- include "k8s-e2e-app-helm.labels" . | nindent 4 }} 13 | spec: 14 | gateway: kyma-gateway.kyma-system.svc.cluster.local 15 | host: {{ index .Values.expose "api-rule" "host" }} 16 | service: 17 | name: {{ $svcName }} 18 | port: {{ $svcPort }} 19 | rules: 20 | - path: /.* 21 | methods: 22 | - GET 23 | - POST 24 | - DELETE 25 | mutators: [] 26 | accessStrategies: 27 | - handler: noop 28 | config: {} 29 | 30 | {{- end }} 31 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Values.expose.enabled) (eq .Values.expose.environment "gardener") -}} 2 | {{- $name := default "approuter-chart" .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 3 | {{- $fullName := include "k8s-e2e-app-helm.fullname" . -}} 4 | {{- $svcName := index .Values "approuter-chart" "name" | default (printf "%s-%s-svc" .Release.Name $name) | trunc 63 | trimSuffix "-" | quote -}} 5 | {{- $svcPort := dig "approuter-chart" "service" "port" 8080 (.Values | merge (dict)) -}} 6 | apiVersion: networking.k8s.io/v1 7 | kind: Ingress 8 | metadata: 9 | name: {{ $fullName }}-ingress 10 | annotations: 11 | nginx.ingress.kubernetes.io/affinity: "cookie" 12 | nginx.ingress.kubernetes.io/proxy-read-timeout: "600" 13 | nginx.ingress.kubernetes.io/session-cookie-name: "JSESSIONID" 14 | {{- if eq .Values.expose.environment "gardener" }} 15 | cert.gardener.cloud/purpose: "managed" 16 | {{- else }} 17 | kubernetes.io/ingress.class: "nginx" 18 | cert-manager.io/cluster-issuer: {{ .Values.expose.ingress.issuer.name | default "letsencrypt-production" | quote }} 19 | {{ end }} 20 | spec: 21 | tls: 22 | - hosts: 23 | {{- if .Values.expose.ingress.shortRoute }} 24 | - {{.Values.expose.ingress.shortRoute | quote }} 25 | {{ end -}} 26 | - {{ .Values.expose.ingress.exposedRoute | quote }} 27 | {{- if .Values.expose.ingress.connectivityProxyRoute }} 28 | - {{ .Values.expose.ingress.connectivityProxyRoute | quote }} 29 | {{ end -}} 30 | secretName: {{ .Values.expose.ingress.secretName | default "tls-secret" | quote }} 31 | rules: 32 | - host: {{ .Values.expose.ingress.exposedRoute | quote }} 33 | http: 34 | paths: 35 | - path: / 36 | pathType: Prefix 37 | backend: 38 | service: 39 | name: {{ $svcName }} 40 | port: 41 | number: {{ $svcPort }} 42 | 43 | {{- end }} 44 | -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/templates/issuer.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.expose.enabled -}} 2 | {{- if and (eq .Values.expose.environment "generic") (not .Values.expose.name) -}} 3 | apiVersion: cert-manager.io/v1 4 | kind: ClusterIssuer 5 | metadata: 6 | name: letsencrypt-production 7 | spec: 8 | acme: 9 | # You must replace this email address with your own. 10 | # Let's Encrypt will use this to contact you about expiring 11 | # certificates, and issues related to your account. 12 | email: {{ .Values.expose.ingress.issuer.email | quote }} 13 | server: https://acme-v02.api.letsencrypt.org/directory 14 | privateKeySecretRef: 15 | # Secret resource that will be used to store the account's private key. 16 | name: {{ .Values.expose.ingress.issuer.privateKeySecretRef | default "tls-private-key" | quote }} 17 | # Add a single challenge solver, HTTP01 using nginx 18 | solvers: 19 | - http01: 20 | ingress: 21 | class: nginx 22 | {{- end -}} 23 | {{- end -}} -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/k8s-e2e-app-helm/values.yaml: -------------------------------------------------------------------------------- 1 | app-chart: 2 | cloudDestination: 3 | onPremiseDestination: 4 | principalPropagationDestination: 5 | destinationBinding: 6 | connectivityBinding: 7 | xsuaaBinding: 8 | imagePullSecrets: 9 | - name: 10 | approuter-chart: 11 | config: 12 | idp: 13 | pattern: 14 | xsuaaBinding: 15 | imagePullSecrets: 16 | - name: 17 | expose: 18 | enabled: 19 | environment: 20 | ingress: 21 | shortRoute: 22 | exposedRoute: 23 | connectivityProxyRoute: 24 | api-rule: 25 | host: -------------------------------------------------------------------------------- /samples/helm-sample-application/helm-chart/values.yaml: -------------------------------------------------------------------------------- 1 | app-chart: 2 | # A cloud destination with basic authentication 3 | cloudDestination: myCloudDestination 4 | # A onpremise destination with basic authentication 5 | onPremiseDestination: myOnpremiseDestination 6 | # A onpremise destination with principal propagation 7 | principalPropagationDestination: myPrincipalPropagationDestination 8 | destinationBinding: operator-destination-service 9 | connectivityBinding: operator-connectivity-service 10 | xsuaaBinding: operator-xsuaa-service 11 | imagePullSecrets: 12 | # A secret containing the credentials to access your docker registry 13 | - name: docker-registry-secret 14 | approuter-chart: 15 | config: 16 | # An Identity Provider which is in your subaccount 17 | idp: default 18 | # The TENANT_HOST_PATTERN your approuter will use to point at the right subaccount 19 | pattern: (.*).e2e.ingress.cloud-sdk-js.sdktests.shoot.canary.k8s-hana.ondemand.com 20 | xsuaaBinding: operator-xsuaa-service 21 | # A secret containing the credentials to access your docker registry 22 | imagePullSecrets: 23 | - name: docker-registry-secret 24 | expose: 25 | enabled: true 26 | # Your cluster enviroment (see README table for more information) 27 | environment: gardener 28 | ingress: 29 | # Your domain 30 | shortRoute: cloud-sdk-js.sdktests.shoot.canary.k8s-hana.ondemand.com 31 | # The route your approuter will use, with a wildcard to enable multi-tenancy 32 | exposedRoute: "*.e2e.ingress.cloud-sdk-js.sdktests.shoot.canary.k8s-hana.ondemand.com" 33 | # The route your connectivity proxy will use to create a tunnel to your cloud connector 34 | connectivityProxyRoute: connectivitytunnel.ingress.cloud-sdk-js.sdktests.shoot.canary.k8s-hana.ondemand.com 35 | api-rule: 36 | # The hostname for the exposed api-gateway (see README table for more information) 37 | host: e2e-app- -------------------------------------------------------------------------------- /samples/helm-sample-application/sap-btp-operator/certificates/self-signed-issuer.yml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Issuer 3 | metadata: 4 | name: selfsigned-issuer 5 | spec: 6 | selfSigned: {} 7 | --- 8 | apiVersion: cert-manager.io/v1 9 | kind: ClusterIssuer 10 | metadata: 11 | name: selfsigned-cluster-issuer 12 | spec: 13 | selfSigned: {} -------------------------------------------------------------------------------- /samples/helm-sample-application/sap-btp-operator/sap-btp-operator-v0.1.9.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/cloud-sdk-js/1bfa08bcdc2694d96bef8798d849afc4c10ec4a8/samples/helm-sample-application/sap-btp-operator/sap-btp-operator-v0.1.9.tgz -------------------------------------------------------------------------------- /samples/helm-sample-application/sap-btp-operator/services/connectivity/operator-connectivity-binding.yml: -------------------------------------------------------------------------------- 1 | apiVersion: services.cloud.sap.com/v1alpha1 2 | kind: ServiceBinding 3 | metadata: 4 | name: operator-connectivity-service 5 | spec: 6 | serviceInstanceName: operator-connectivity-service -------------------------------------------------------------------------------- /samples/helm-sample-application/sap-btp-operator/services/connectivity/operator-connectivity-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: services.cloud.sap.com/v1alpha1 2 | kind: ServiceInstance 3 | metadata: 4 | name: operator-connectivity-service 5 | spec: 6 | serviceOfferingName: connectivity 7 | servicePlanName: connectivity_proxy 8 | -------------------------------------------------------------------------------- /samples/helm-sample-application/sap-btp-operator/services/destination/operator-destination-binding.yml: -------------------------------------------------------------------------------- 1 | apiVersion: services.cloud.sap.com/v1alpha1 2 | kind: ServiceBinding 3 | metadata: 4 | name: operator-destination-service 5 | spec: 6 | serviceInstanceName: operator-destination-service -------------------------------------------------------------------------------- /samples/helm-sample-application/sap-btp-operator/services/destination/operator-destination-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: services.cloud.sap.com/v1alpha1 2 | kind: ServiceInstance 3 | metadata: 4 | name: operator-destination-service 5 | spec: 6 | serviceOfferingName: destination 7 | servicePlanName: lite 8 | -------------------------------------------------------------------------------- /samples/helm-sample-application/sap-btp-operator/services/xsuaa/operator-xsuaa-binding.yml: -------------------------------------------------------------------------------- 1 | apiVersion: services.cloud.sap.com/v1alpha1 2 | kind: ServiceBinding 3 | metadata: 4 | name: operator-xsuaa-service 5 | spec: 6 | serviceInstanceName: operator-xsuaa-service -------------------------------------------------------------------------------- /samples/helm-sample-application/sap-btp-operator/services/xsuaa/operator-xsuaa-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: services.cloud.sap.com/v1alpha1 2 | kind: ServiceInstance 3 | metadata: 4 | name: operator-xsuaa-service 5 | spec: 6 | serviceOfferingName: xsuaa 7 | servicePlanName: application 8 | parameters: 9 | xsappname: kubernetes-xsuaa 10 | tenant-mode: shared 11 | scopes: 12 | - name: "$XSAPPNAME.Callback" 13 | description: "With this scope set, the callbacks for tenant onboarding, offboarding and getDependencies can be called." 14 | grant-as-authority-to-apps : 15 | - $XSAPPNAME(application,sap-provisioning,tenant-onboarding) 16 | role-templates: 17 | - name: TOKEN_EXCHANGE 18 | description: Token exchange 19 | scope-references: 20 | - uaa.user 21 | - name: "MultitenancyCallbackRoleTemplate" 22 | description: "Call callback-services of applications" 23 | scope-references: 24 | - "$XSAPPNAME.Callback" 25 | oauth2-configuration: 26 | grant-types: 27 | - authorization_code 28 | - client_credentials 29 | - password 30 | - refresh_token 31 | - urn:ietf:params:oauth:grant-type:saml2-bearer 32 | - user_token 33 | - client_x509 34 | - urn:ietf:params:oauth:grant-type:jwt-bearer 35 | redirect-uris: 36 | - https://*/** -------------------------------------------------------------------------------- /samples/helm-sample-application/sap-btp-operator/values.yaml: -------------------------------------------------------------------------------- 1 | manager: 2 | secret: 3 | clientid: 4 | clientsecret: 5 | url: https://service-manager.cfapps.sap.hana.ondemand.com 6 | tokenurl: https://s4sdk.authentication.sap.hana.ondemand.com 7 | -------------------------------------------------------------------------------- /samples/http-client-examples/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /samples/http-client-examples/README.md: -------------------------------------------------------------------------------- 1 | # HTTP Client Examples 2 | 3 | Examples for using the HTTP Client in SAP Cloud SDK for JavaScript. 4 | 5 | Refer to [the documentation](https://sap.github.io/cloud-sdk/docs/js/features/connectivity/generic-http-client) for a description of HTTP Client. 6 | 7 | The examples are implemented in [http-client.spec.ts](./http-client.spec.ts) and in [http-client-httpbin.spec.ts](./http-client-httpbin.spec.ts). 8 | The purpose of those examples is to provide usable sample code for the examples mentioned the documentation. 9 | 10 | ## Instructions to run locally 11 | 12 | ### Install dependencies 13 | 14 | ``` 15 | npm ci 16 | ``` 17 | 18 | ### Run tests 19 | 20 | They are built as tests which demonstrate how to use the HTTP Client. 21 | 22 | To run the tests, execute the following command: 23 | 24 | ``` 25 | npm run test 26 | ``` 27 | 28 | This will start a local [express](https://expressjs.com/)-based HTTP server and perform a few example requests using the HTTP Client. 29 | -------------------------------------------------------------------------------- /samples/http-client-examples/http-client-httpbin.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | executeHttpRequest, 3 | executeHttpRequestWithOrigin, 4 | ParameterEncoder 5 | } from '@sap-cloud-sdk/http-client'; 6 | 7 | /* 8 | 9 | This spec file includes usage examples for the HTTP client that make use of the httpbin.org service. 10 | The examples in this file should be exactly the same as on https://sap.github.io/cloud-sdk/docs/js/features/connectivity/generic-http-client 11 | 12 | */ 13 | 14 | describe('HTTP Client Usage Examples using httpbin.org', () => { 15 | it('Show simple HTTP Post without fetchCsrfToken', async () => { 16 | const response = await executeHttpRequest( 17 | { 18 | url: `https://httpbin.org/post` 19 | }, 20 | { 21 | method: 'post' 22 | }, 23 | { 24 | fetchCsrfToken: false 25 | } 26 | ); 27 | expect(response).toBeDefined(); 28 | expect(response.status).toBe(200); 29 | }); 30 | 31 | it('Customized Parameter Encoding', async () => { 32 | const myCustomParameterEncodingFunction: ParameterEncoder = function ( 33 | params: Record 34 | ): Record { 35 | const encodedParams: Record = {}; 36 | 37 | for (const k in params) { 38 | // Customize your required encoding logic here 39 | encodedParams[k] = params[k].toString().replace('x', 'y'); 40 | } 41 | 42 | return encodedParams; 43 | }; 44 | 45 | const response = await executeHttpRequest( 46 | { 47 | url: 'https://httpbin.org/anything' 48 | }, 49 | { 50 | method: 'get', 51 | params: { 52 | param1: 'a/bx', 53 | param2: 'x1' 54 | }, 55 | // Pass your custom encoding function 56 | parameterEncoder: myCustomParameterEncodingFunction 57 | } 58 | ); 59 | 60 | expect(response.data.args).toEqual({ 61 | param1: 'a/by', 62 | param2: 'y1' 63 | }); 64 | }); 65 | 66 | it('executeHttpRequestWithOrigin', async () => { 67 | const response = await executeHttpRequestWithOrigin( 68 | { 69 | url: 'https://httpbin.org/anything' 70 | }, 71 | { 72 | method: 'get', 73 | headers: { 74 | custom: { apiKey: 'custom-header' }, 75 | requestConfig: { apiKey: 'default-header' } 76 | }, 77 | params: { 78 | custom: { myParam: 'custom-param' }, 79 | requestConfig: { myParam: 'default-param' } 80 | } 81 | } 82 | ); 83 | 84 | expect(response.request.path).toEqual('/anything?myParam=custom-param'); 85 | expect(response.data.args).toEqual({ 86 | myParam: 'custom-param' 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /samples/http-client-examples/jest.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | clearMocks: true, 3 | preset: 'ts-jest' 4 | }; 5 | -------------------------------------------------------------------------------- /samples/http-client-examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sap-cloud-sdk/samples", 3 | "version": "1.0.0", 4 | "description": "SAP Cloud SDK for JS - Examples on HTTP Client", 5 | "scripts": { 6 | "test": "jest" 7 | }, 8 | "license": "Apache-2.0", 9 | "keywords": [], 10 | "author": "", 11 | "dependencies": { 12 | "@sap-cloud-sdk/http-client": "^3.0.0", 13 | "@sap-cloud-sdk/util": "^3.0.0", 14 | "concurrently": "^7.3.0", 15 | "express": "^4.18.1", 16 | "ts-node": "^10.9.1", 17 | "typescript": "~5.7.2" 18 | }, 19 | "devDependencies": { 20 | "@types/express": "^4.17.13", 21 | "@types/jest": "^28.1.7", 22 | "@types/node": "^22.0.0", 23 | "jest": "^28.1.3", 24 | "prettier": "^3.4.2", 25 | "ts-jest": "^28.0.8" 26 | } 27 | } -------------------------------------------------------------------------------- /samples/http-client-examples/server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const app = express(); 3 | 4 | app.get('', async (req: express.Request, res: express.Response) => { 5 | res.status(200).send(); 6 | }); 7 | 8 | app.head('/csrf-token', async (req: express.Request, res: express.Response) => { 9 | res 10 | .status(200) 11 | .header('x-csrf-token', 'abc') 12 | .header('set-cookie', 'foo=bar') 13 | .send(); 14 | }); 15 | 16 | app.post('/csrf-token', async (req: express.Request, res: express.Response) => { 17 | if (req.headers['x-csrf-token'] === 'abc') { 18 | res.send('Request with token'); 19 | } else { 20 | res.send('Request without token'); 21 | } 22 | }); 23 | 24 | app.post('/post-without-csrf-token', async (req: express.Request, res: express.Response) => { 25 | res.send(); 26 | }); 27 | 28 | app.get('/encoding', async (req: express.Request, res: express.Response) => { 29 | res.send(req.url); 30 | }); 31 | 32 | app.get('/origin', async (req: express.Request, res: express.Response) => { 33 | const result = req.headers; 34 | result['requestUrl'] = req.url; 35 | res.send(JSON.stringify(result)); 36 | }); 37 | 38 | app.get('/ping', async (req: express.Request, res: express.Response) => { 39 | res.send('pong'); 40 | }); 41 | 42 | export default app; 43 | -------------------------------------------------------------------------------- /samples/http-client-examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | "target": "es2017", 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "skipLibCheck": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package.json . 6 | 7 | RUN npm install -g npm@latest 8 | 9 | RUN npm install --unsafe-perm --production 10 | 11 | COPY . ./ 12 | 13 | EXPOSE 3000 14 | CMD ["npm", "run", "start:prod"] 15 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/README.md: -------------------------------------------------------------------------------- 1 | # SAP Cloud SDK for JS Kubernetes Sample Application 2 | 3 | ## Description 4 | This repository contains our Kubernetes Sample Application. 5 | 6 | The repositories structure is as following: 7 | 8 | - `/approuter` - Contains the approuter, a packaging script and a Dockerfile which is used in the deployment 9 | - `/pipeline` - Contains the pipeline's Dockerfile which contains all tools to run the pipeline 10 | - `/src` - Contains the application's source code with its 4 endpoints for cloud, onpremise and principal propagation 11 | - `/e2e-tests` - Contains the cypress tests that test all endpoints after deployment 12 | - `./github/workflows` - Contains the pipeline that builds, packages, deploys, and tests the application 13 | - `Dockerfile` - Packages the application as Dockerimage to be used in Kubernetes 14 | 15 | If you want to locally trigger the e2e-tests, create a `cypress.env.json` file in the `/e2e-tests` directory containing the credentials for the IdP in the format: 16 | 17 | ``` 18 | { 19 | "username": "username", 20 | "password": "password" 21 | "url": "url" 22 | } 23 | ``` 24 | 25 | ## Requirements 26 | The minimal requirements are: 27 | - A terminal to execute commands 28 | - A recent version of node and npm installed e.g. node 14 and npm 6 29 | - An IDE or a text editor of your choice 30 | 31 | If you want to explore the possibilities beyond local tests you need: 32 | - Access to a [SAP Business Technology Platform](https://www.sap.com/products/business-technology-platform.html) account 33 | - Entitlement to use resources like service instance creation and application processing units 34 | - Permission to create service instances 35 | - Access to a `Docker` repository 36 | - A Kubernetes Cluster which runs: 37 | - The SAP BTP Operator 38 | - The SAP Connectivity Proxy 39 | ## Download and Installation 40 | To download the application run: 41 | 42 | ``` 43 | git clone \ 44 | --depth 1 \ 45 | --filter=blob:none \ 46 | --sparse \ 47 | https://github.com/SAP-samples/cloud-sdk-js.git \ 48 | ; 49 | cd cloud-sdk-js 50 | git sparse-checkout set samples/k8s-sample-application 51 | ``` 52 | 53 | ### Generate oData Client 54 | 55 | The following service definitions in `EDMX` format are already downloaded in the folder `resources/service-specs`: 56 | - [Business Partner service onPremise](https://api.sap.com/api/OP_API_BUSINESS_PARTNER_SRV/overview) 57 | - [Business Partner service cloud](https://api.sap.com/api/API_BUSINESS_PARTNER/overview) 58 | 59 | The clients are generated using the `npm run generate-client` command. This command is executed automatically in the `postinstall` step after you execute `npm i `. 60 | 61 | **Note** These services are licensed under the terms of [SAP API Information License](../../LICENSES/LicenseRef-API-Definition-File-License.txt). This limits their use to development purposes only. 62 | 63 | 64 | ### Deploy to Docker 65 | 1. In the `package.json`, change the `deploy:docker` and `deploy:pipeline` scripts to point at your docker repository. 66 | 2. Change the the `deploy:docker` script in the approuter's `package.json` to point at your docker repository. 67 | 3. Change the `xs-app.json` in the approuter directory to use either an IdP of your choice, or no IdP at all. 68 | 4. Deploy the Docker images to your repository with `npm run deploy:docker` and `npm run deploy:pipeline` in case you want to use the pipeline. 69 | 70 | ### Deploy to Kubernetes 71 | 1. Update the `deployment.yml` to use your Docker images and the `ingress.yml` to use the domain associated with your cluster. 72 | 2. Use the same domain in the `TENANT_HOST_PATTERN` in the approuter's `deployment.yml` which you specified in the `ingress.yml`. 73 | 3. Deploy the services in `k8s_files/operator/services`. 74 | 4. Now you can deploy the application and approuter with the Kubernetes files in `k8s_files/app`, `k8s_files/approuter` and the `ingress.yml` with `kubectl apply -f`. 75 | 76 | ### Code Placeholders 77 | If anything isn't working as intended, search for ``, as all parts that have to be adapted contain this placeholder. 78 | 79 | For more detailed information on Kubernetes deployment, check out our [Kubernetes migration guide](https://sap.github.io/cloud-sdk/docs/js/guides/migrate-sdk-application-from-btp-cf-to-kubernetes). 80 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/approuter/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm config set @sap:registry https://registry.npmjs.org 12 | RUN npm install 13 | 14 | # Bundle app source 15 | COPY . . 16 | 17 | EXPOSE 5000 18 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /samples/k8s-sample-application/approuter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "approuter", 3 | "dependencies": { 4 | "@sap/approuter": "20.2.0", 5 | "package.json": "2.0.1" 6 | }, 7 | "scripts": { 8 | "start": "node node_modules/@sap/approuter/approuter.js", 9 | "package": "./package.sh", 10 | "deploy": "npm run package && cf push", 11 | "deploy:docker": "docker build -t docker-cloudsdk.common.repositories.cloud.sap/k8s-approuter:latest . && docker push docker-cloudsdk.common.repositories.cloud.sap/k8s-approuter:latest" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/approuter/static-resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World 5 | 6 | 7 |

Hello world!

8 | 9 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/approuter/xs-app.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcomeFile": "/web-pages/index.html", 3 | "routes": [ 4 | { 5 | "source": "/backend-app/(.*)", 6 | "target": "$1", 7 | "destination": "backend", 8 | "identityProvider": "" 9 | }, 10 | { 11 | "source": "/web-pages/(.*)", 12 | "target": "$1", 13 | "localDir": "static-resources", 14 | "identityProvider": "" 15 | }] 16 | } -------------------------------------------------------------------------------- /samples/k8s-sample-application/e2e-tests/cypress.json: -------------------------------------------------------------------------------- 1 | {"chromeWebSecurity": false} 2 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/e2e-tests/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/e2e-tests/cypress/integration/e2e.spec.ts: -------------------------------------------------------------------------------- 1 | describe('e2e tests for k8s test app', () => { 2 | function checkEndpoint(endpoint: string) { 3 | cy.visit('https://' + Cypress.env('url')); 4 | 5 | cy.get('#j_username') 6 | .type(Cypress.env('username')) 7 | .get('#j_password') 8 | .type(Cypress.env('password')) 9 | .get('#logOnFormSubmit') 10 | .click(); 11 | 12 | cy.request(`backend-app/${endpoint}`).then((resp) => { 13 | expect(resp.status).to.eq(200); 14 | }); 15 | } 16 | it('tests cloud endpoint', () => { 17 | checkEndpoint('cloud-business-partner'); 18 | }); 19 | 20 | it('tests onpremise endpoint', () => { 21 | checkEndpoint('onpremise-business-partner'); 22 | }); 23 | 24 | it('tests principal propagation endpoint', () => { 25 | checkEndpoint('principal-business-partner'); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/e2e-tests/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | // eslint-disable-next-line no-unused-vars 19 | module.exports = (on, config) => { 20 | // `on` is used to hook into various events Cypress emits 21 | // `config` is the resolved Cypress config 22 | } 23 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/e2e-tests/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/e2e-tests/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/e2e-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sap-cloud-sdk/samples", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "npx cypress run" 7 | }, 8 | "author": "", 9 | "license": "Apache-2.0", 10 | "devDependencies": { 11 | "cypress": "^12.0.0", 12 | "typescript": "~5.7.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/e2e-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es5", "dom"], 5 | "types": ["cypress"] 6 | }, 7 | "include": ["**/*.ts"] 8 | } -------------------------------------------------------------------------------- /samples/k8s-sample-application/k8s_files/app/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: sdkjs-e2e-deployment 5 | spec: 6 | replicas: 2 7 | selector: 8 | matchLabels: 9 | app: sdkjs-e2e 10 | template: 11 | metadata: 12 | labels: 13 | app: sdkjs-e2e 14 | spec: 15 | containers: 16 | - name: sdkjs-e2e 17 | # Replace this with the application's docker image, follwing this syntax: Repository/Image:Tag 18 | # Example: /: 19 | image: docker-cloudsdk.docker.repositories.sap.ondemand.com/k8s-e2e-app:latest 20 | resources: 21 | requests: 22 | memory: "256Mi" 23 | cpu: "500m" 24 | limits: 25 | memory: "512Mi" 26 | cpu: "1000m" 27 | ports: 28 | - containerPort: 3000 29 | volumeMounts: 30 | - name: destination-volume 31 | mountPath: "/etc/secrets/sapcp/destination/operator-destination-service" 32 | readOnly: true 33 | - name: xsuaa-volume 34 | mountPath: "/etc/secrets/sapcp/xsuaa/operator-xsuaa-service" 35 | readOnly: true 36 | - name: connectivity-volume 37 | mountPath: "/etc/secrets/sapcp/connectivity/operator-connectivity-service" 38 | readOnly: true 39 | imagePullSecrets: 40 | # Replace this with your docker registry secret 41 | # Example: 42 | - name: docker-registry-secret 43 | volumes: 44 | - name: destination-volume 45 | secret: 46 | secretName: operator-destination-service 47 | - name: xsuaa-volume 48 | secret: 49 | secretName: operator-xsuaa-service 50 | - name: connectivity-volume 51 | secret: 52 | secretName: operator-connectivity-service -------------------------------------------------------------------------------- /samples/k8s-sample-application/k8s_files/app/k8s-e2e-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: sdkjs-e2e-svc 5 | spec: 6 | selector: 7 | app: sdkjs-e2e 8 | ports: 9 | - port: 8080 10 | targetPort: 3000 11 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/k8s_files/approuter/approuter-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: approuter 6 | name: approuter-svc 7 | spec: 8 | ports: 9 | - port: 8080 10 | targetPort: 5000 11 | selector: 12 | app: approuter -------------------------------------------------------------------------------- /samples/k8s-sample-application/k8s_files/approuter/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: approuter 5 | labels: 6 | app: approuter 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: approuter 12 | template: 13 | metadata: 14 | labels: 15 | app: approuter 16 | spec: 17 | containers: 18 | - # Replace this with the approuter's docker image, follwing this syntax: Repository/Image:Tag 19 | # Example: /: 20 | image: docker-cloudsdk.docker.repositories.sap.ondemand.com/k8s-approuter:latest 21 | resources: 22 | requests: 23 | memory: "256Mi" 24 | cpu: "250m" 25 | limits: 26 | memory: "512Mi" 27 | cpu: "500m" 28 | name: approuter 29 | volumeMounts: 30 | - name: xsuaa-volume 31 | mountPath: "/etc/secrets/sapcp/xsuaa/operator-xsuaa-service" 32 | readOnly: true 33 | env: 34 | - name: PORT 35 | value: "5000" 36 | - name: destinations 37 | value: '[{"name":"backend", "url":"http://sdkjs-e2e-svc:8080/", "forwardAuthToken": true}]' 38 | - name: TENANT_HOST_PATTERN 39 | # Replace this with your wildcard subdoamin, has to match the ingress, with the pattern: (.*).Subdomain.Domain 40 | # Example: (.*).. 41 | value: (.*).e2e.ingress.cloud-sdk-js.sdktests.shoot.canary.k8s-hana.ondemand.com 42 | imagePullSecrets: 43 | # Replace this with your docker registry secret 44 | # Example: 45 | - name: docker-registry-secret 46 | volumes: 47 | - name: xsuaa-volume 48 | secret: 49 | secretName: operator-xsuaa-service 50 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/k8s_files/ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: sdkjs-e2e-ingress 5 | annotations: 6 | nginx.ingress.kubernetes.io/affinity: "cookie" 7 | nginx.ingress.kubernetes.io/proxy-read-timeout: "600" 8 | nginx.ingress.kubernetes.io/session-cookie-name: "JSESSIONID" 9 | cert.gardener.cloud/purpose: managed 10 | spec: 11 | tls: 12 | - hosts: 13 | # Replace with root domain 14 | # Example: 15 | - cloud-sdk-js.sdktests.shoot.canary.k8s-hana.ondemand.com 16 | # Replace with subdomain which includes a wildcard for multi-tenancy, with the pattern: "*.Subdomain.Domain" 17 | # Example: "*.." 18 | - "*.e2e.ingress.cloud-sdk-js.sdktests.shoot.canary.k8s-hana.ondemand.com" 19 | # Replace with domain for the connectivity-proxy, with the pattern: Subdomain.Domain 20 | # Example: . 21 | - connectivitytunnel.ingress.cloud-sdk-js.sdktests.shoot.canary.k8s-hana.ondemand.com 22 | secretName: secret-tls 23 | rules: 24 | # Replace with subdomain which includes a wildcard for multi-tenancy, with the pattern: "*.Subdomain.Domain" 25 | # Example: "*.." 26 | - host: "*.e2e.ingress.cloud-sdk-js.sdktests.shoot.canary.k8s-hana.ondemand.com" 27 | http: 28 | paths: 29 | - path: / 30 | pathType: Prefix 31 | backend: 32 | service: 33 | name: approuter-svc 34 | port: 35 | number: 8080 36 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/k8s_files/operator/certificates/self-signed-issuer.yml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Issuer 3 | metadata: 4 | name: selfsigned-issuer 5 | spec: 6 | selfSigned: {} 7 | --- 8 | apiVersion: cert-manager.io/v1 9 | kind: ClusterIssuer 10 | metadata: 11 | name: selfsigned-cluster-issuer 12 | spec: 13 | selfSigned: {} -------------------------------------------------------------------------------- /samples/k8s-sample-application/k8s_files/operator/services/connectivity/operator-connectivity-binding.yml: -------------------------------------------------------------------------------- 1 | apiVersion: services.cloud.sap.com/v1alpha1 2 | kind: ServiceBinding 3 | metadata: 4 | name: operator-connectivity-service 5 | spec: 6 | serviceInstanceName: operator-connectivity-service -------------------------------------------------------------------------------- /samples/k8s-sample-application/k8s_files/operator/services/connectivity/operator-connectivity-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: services.cloud.sap.com/v1alpha1 2 | kind: ServiceInstance 3 | metadata: 4 | name: operator-connectivity-service 5 | spec: 6 | serviceOfferingName: connectivity 7 | servicePlanName: connectivity_proxy 8 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/k8s_files/operator/services/destination/operator-destination-binding.yml: -------------------------------------------------------------------------------- 1 | apiVersion: services.cloud.sap.com/v1alpha1 2 | kind: ServiceBinding 3 | metadata: 4 | name: operator-destination-service 5 | spec: 6 | serviceInstanceName: operator-destination-service -------------------------------------------------------------------------------- /samples/k8s-sample-application/k8s_files/operator/services/destination/operator-destination-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: services.cloud.sap.com/v1alpha1 2 | kind: ServiceInstance 3 | metadata: 4 | name: operator-destination-service 5 | spec: 6 | serviceOfferingName: destination 7 | servicePlanName: lite 8 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/k8s_files/operator/services/xsuaa/operator-xsuaa-binding.yml: -------------------------------------------------------------------------------- 1 | apiVersion: services.cloud.sap.com/v1alpha1 2 | kind: ServiceBinding 3 | metadata: 4 | name: operator-xsuaa-service 5 | spec: 6 | serviceInstanceName: operator-xsuaa-service -------------------------------------------------------------------------------- /samples/k8s-sample-application/k8s_files/operator/services/xsuaa/operator-xsuaa-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: services.cloud.sap.com/v1alpha1 2 | kind: ServiceInstance 3 | metadata: 4 | name: operator-xsuaa-service 5 | spec: 6 | serviceOfferingName: xsuaa 7 | servicePlanName: application 8 | parameters: 9 | xsappname: kubernetes-xsuaa 10 | tenant-mode: shared 11 | scopes: 12 | - name: "$XSAPPNAME.Callback" 13 | description: "With this scope set, the callbacks for tenant onboarding, offboarding and getDependencies can be called." 14 | grant-as-authority-to-apps : 15 | - $XSAPPNAME(application,sap-provisioning,tenant-onboarding) 16 | role-templates: 17 | - name: TOKEN_EXCHANGE 18 | description: Token exchange 19 | scope-references: 20 | - uaa.user 21 | - name: "MultitenancyCallbackRoleTemplate" 22 | description: "Call callback-services of applications" 23 | scope-references: 24 | - "$XSAPPNAME.Callback" 25 | oauth2-configuration: 26 | grant-types: 27 | - authorization_code 28 | - client_credentials 29 | - password 30 | - refresh_token 31 | - urn:ietf:params:oauth:grant-type:saml2-bearer 32 | - user_token 33 | - client_x509 34 | - urn:ietf:params:oauth:grant-type:jwt-bearer 35 | redirect-uris: 36 | - https://*/** -------------------------------------------------------------------------------- /samples/k8s-sample-application/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sap-cloud-sdk/samples", 3 | "version": "0.0.1", 4 | "description": "SAP Cloud SDK for JS - Sample application", 5 | "private": true, 6 | "license": "Apache-2.0", 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json", 21 | "ci-build": "echo \"Use this to compile or minify your application\"", 22 | "deploy": "npm run build && cf push", 23 | "deploy:docker": "npm run build && docker build -t docker-cloudsdk.common.repositories.cloud.sap/k8s-e2e-app:latest . && docker push docker-cloudsdk.common.repositories.cloud.sap/k8s-e2e-app:latest", 24 | "deploy:pipeline": "docker build -t docker-cloudsdk.common.repositories.cloud.sap/sdkjs-k8s-pipeline:latest ./pipeline && docker push docker-cloudsdk.common.repositories.cloud.sap/sdkjs-k8s-pipeline:latest", 25 | "generate-client": "npx generate-odata-client --input resources/service-specs --outputDir src/generated --optionsPerService=resources/service-specs/options-per-service.json --clearOutputDir --transpile=false" 26 | }, 27 | "dependencies": { 28 | "@nestjs/common": "^9.4.3", 29 | "@nestjs/core": "^9.4.3", 30 | "@nestjs/platform-express": "^9.4.3", 31 | "@sap-cloud-sdk/connectivity": "^4.0.0", 32 | "@sap-cloud-sdk/odata-v2": "^3.0.0", 33 | "@sap/xsenv": "^3.4.0", 34 | "@sap/xssec": "^3.2.17", 35 | "passport": "^0.7.0", 36 | "reflect-metadata": "^0.1.13", 37 | "rimraf": "^3.0.2", 38 | "rxjs": "^7.8.1", 39 | "webpack": "^5.80.0" 40 | }, 41 | "devDependencies": { 42 | "@nestjs/cli": "^9.5.0", 43 | "@nestjs/schematics": "^9.2.0", 44 | "@nestjs/testing": "^9.4.3", 45 | "@sap-cloud-sdk/test-util": "^3.0.0", 46 | "@sap-cloud-sdk/generator": "^3.0.0", 47 | "@types/express": "^4.17.17", 48 | "@types/jest": "^29.5.4", 49 | "@types/node": "^22.10.5", 50 | "@types/supertest": "^2.0.12", 51 | "@typescript-eslint/eslint-plugin": "^5.59.0", 52 | "@typescript-eslint/parser": "^5.59.0", 53 | "eslint": "^8.38.0", 54 | "eslint-config-prettier": "^8.8.0", 55 | "eslint-plugin-prettier": "^4.2.1", 56 | "jest": "29.6.4", 57 | "prettier": "^3.4.2", 58 | "supertest": "^6.3.3", 59 | "ts-jest": "^29.1.1", 60 | "ts-loader": "^9.4.4", 61 | "ts-node": "^10.9.1", 62 | "tsconfig-paths": "^4.2.0", 63 | "typescript": "~5.7.2" 64 | }, 65 | "jest": { 66 | "moduleFileExtensions": [ 67 | "js", 68 | "json", 69 | "ts" 70 | ], 71 | "rootDir": "src", 72 | "testRegex": ".*\\.spec\\.ts$", 73 | "transform": { 74 | "^.+\\.(t|j)s$": "ts-jest" 75 | }, 76 | "collectCoverageFrom": [ 77 | "**/*.(t|j)s" 78 | ], 79 | "coverageDirectory": "../coverage", 80 | "testEnvironment": "node" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/pipeline/Dockerfile: -------------------------------------------------------------------------------- 1 | # Start from the docker image which contains mvn 2 | FROM ubuntu:20.04 3 | 4 | # Install docker 5 | ## Update the apt package index and install packages to allow apt to use a repository over HTTPS: 6 | RUN apt-get update 7 | RUN apt-get install -y \ 8 | apt-transport-https \ 9 | ca-certificates \ 10 | curl \ 11 | gnupg \ 12 | lsb-release 13 | 14 | ## Add Docker’s official GPG key: 15 | RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 16 | 17 | ## Use the following command to set up the stable repository. 18 | RUN echo \ 19 | "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ 20 | $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null 21 | 22 | ## Install Docker Engine 23 | RUN apt-get update 24 | RUN DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce docker-ce-cli containerd.io 25 | 26 | # Install git 27 | RUN apt install -y git-all 28 | 29 | # Install kubectl 30 | RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" 31 | RUN install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl 32 | 33 | # Add core dependencies for cypress 34 | RUN apt-get install -y libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb 35 | 36 | # Install node 14 37 | RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash - 38 | RUN apt-get install -y nodejs 39 | 40 | # Update npm version to use lockFileV2 41 | RUN npm install -g npm@latest 42 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/resources/service-specs/options-per-service.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources/service-specs/API_BUSINESS_PARTNER.edmx": { 3 | "packageName": "cloud-business-partner-service", 4 | "directoryName": "cloud-business-partner-service", 5 | "basePath": "/sap/opu/odata/sap/API_BUSINESS_PARTNER" 6 | }, 7 | "resources/service-specs/OP_API_BUSINESS_PARTNER_SRV.edmx": { 8 | "packageName": "op-business-partner-service", 9 | "directoryName": "op-business-partner-service", 10 | "basePath": "/sap/opu/odata/sap/API_BUSINESS_PARTNER" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Req } from '@nestjs/common'; 2 | import { BusinessPartner as BusinessPartnerCloud } from './generated/cloud-business-partner-service'; 3 | import { BusinessPartner as BusinessPartnerOp } from './generated/op-business-partner-service'; 4 | import { OnpremiseBusinessPartnerService } from './onpremise-business-partner/onpremise-business-partner.service'; 5 | import { AppService } from './app.service'; 6 | import { CloudBusinessPartnerService } from './cloud-business-partner/cloud-business-partner.service'; 7 | import { PrincipalBusinessPartnerService } from './principal-business-partner/principal-business-partner.service'; 8 | import { Request } from 'express'; 9 | 10 | @Controller() 11 | export class AppController { 12 | constructor( 13 | private readonly appService: AppService, 14 | private readonly cloudBusinessPartnerService: CloudBusinessPartnerService, 15 | private readonly onpremiseBusinessPartnerService: OnpremiseBusinessPartnerService, 16 | private readonly principalBusinessPartnerService: PrincipalBusinessPartnerService 17 | ) {} 18 | 19 | @Get() 20 | getHello(): string { 21 | return this.appService.getHello(); 22 | } 23 | 24 | @Get('cloud-business-partner') 25 | getCloudBusinessPartner(): Promise { 26 | return this.cloudBusinessPartnerService.getFiveBusinessPartners(); 27 | } 28 | 29 | @Get('onpremise-business-partner') 30 | getOnpremiseBusinessPartner(): Promise { 31 | return this.onpremiseBusinessPartnerService.getFiveBusinessPartners(); 32 | } 33 | 34 | @Get('principal-business-partner') 35 | getPrincipalBusinessPartner( 36 | @Req() request: Request, 37 | ): Promise { 38 | return this.principalBusinessPartnerService.getFiveBusinessPartners( 39 | request, 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { OnpremiseBusinessPartnerService } from './onpremise-business-partner/onpremise-business-partner.service'; 5 | import { CloudBusinessPartnerService } from './cloud-business-partner/cloud-business-partner.service'; 6 | import { PrincipalBusinessPartnerService } from './principal-business-partner/principal-business-partner.service'; 7 | 8 | @Module({ 9 | imports: [], 10 | controllers: [AppController], 11 | providers: [ 12 | AppService, 13 | OnpremiseBusinessPartnerService, 14 | CloudBusinessPartnerService, 15 | PrincipalBusinessPartnerService 16 | ], 17 | }) 18 | export class AppModule {} 19 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/src/cloud-business-partner/cloud-business-partner.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CloudBusinessPartnerService } from './cloud-business-partner.service'; 3 | 4 | describe('CloudBusinessPartnerService', () => { 5 | let service: CloudBusinessPartnerService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [CloudBusinessPartnerService], 10 | }).compile(); 11 | 12 | service = module.get( 13 | CloudBusinessPartnerService, 14 | ); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(service).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/src/cloud-business-partner/cloud-business-partner.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | BusinessPartner, 4 | cloudBusinessPartnerService as businessPartnerService, 5 | } from '../generated/cloud-business-partner-service'; 6 | const { businessPartnerApi } = businessPartnerService(); 7 | 8 | @Injectable() 9 | export class CloudBusinessPartnerService { 10 | async getFiveBusinessPartners(): Promise { 11 | return businessPartnerApi 12 | .requestBuilder() 13 | .getAll() 14 | .top(5) 15 | // the destination should point at a cloud basic auth destination 16 | // Example: 17 | .execute({ destinationName: 'myCloudDestination' }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { JWTStrategy } from '@sap/xssec'; 4 | import { getServices } from '@sap/xsenv'; 5 | import * as passport from 'passport'; 6 | 7 | const xsuaa = getServices({ xsuaa: { name: 'operator-xsuaa-service' } }).xsuaa; 8 | passport.use(new JWTStrategy(xsuaa)); 9 | 10 | async function bootstrap() { 11 | const app = await NestFactory.create(AppModule); 12 | app.use(passport.initialize()); 13 | app.use(passport.authenticate('JWT', { session: false })); 14 | await app.listen(process.env.PORT || 3000); 15 | } 16 | bootstrap(); 17 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/src/onpremise-business-partner/onpremise-business-partner.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { OnpremiseBusinessPartnerService } from './onpremise-business-partner.service'; 3 | 4 | describe('OnpremiseBusinessPartnerService', () => { 5 | let service: OnpremiseBusinessPartnerService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [OnpremiseBusinessPartnerService], 10 | }).compile(); 11 | 12 | service = module.get( 13 | OnpremiseBusinessPartnerService, 14 | ); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(service).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/src/onpremise-business-partner/onpremise-business-partner.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | BusinessPartner, 4 | opBusinessPartnerService as businessPartnerService, 5 | } from '../generated/op-business-partner-service'; 6 | const { businessPartnerApi } = businessPartnerService(); 7 | 8 | @Injectable() 9 | export class OnpremiseBusinessPartnerService { 10 | async getFiveBusinessPartners(): Promise { 11 | return businessPartnerApi 12 | .requestBuilder() 13 | .getAll() 14 | .top(5) 15 | // the destination should point at a onpremise basic authentcation destination 16 | // Example: 17 | .execute({ destinationName: 'myOnpremiseDestination' }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/src/principal-business-partner/principal-business-partner.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PrincipalBusinessPartnerService } from './principal-business-partner.service'; 3 | 4 | describe('PrincipalBusinessPartnerService', () => { 5 | let service: PrincipalBusinessPartnerService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [PrincipalBusinessPartnerService], 10 | }).compile(); 11 | 12 | service = module.get(PrincipalBusinessPartnerService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/src/principal-business-partner/principal-business-partner.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | BusinessPartner, 4 | opBusinessPartnerService as businessPartnerService, 5 | } from '../generated/op-business-partner-service'; 6 | import { retrieveJwt } from '@sap-cloud-sdk/connectivity'; 7 | import { Request } from 'express'; 8 | const { businessPartnerApi } = businessPartnerService(); 9 | 10 | @Injectable() 11 | export class PrincipalBusinessPartnerService { 12 | async getFiveBusinessPartners(request: Request): Promise { 13 | return businessPartnerApi 14 | .requestBuilder() 15 | .getAll() 16 | .top(5) 17 | .execute({ 18 | // the destination should point at a principal propagation destination 19 | // Example: 20 | destinationName: 'myPrincipalPropagationDestination', 21 | jwt: retrieveJwt(request), 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/systems.json: -------------------------------------------------------------------------------- 1 | { 2 | "systems": [{ 3 | "alias": "EXAMPLE", 4 | "uri": "https://example.com" 5 | }] 6 | } 7 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /samples/k8s-sample-application/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/resilience-examples/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { node: true, jest: true }, 3 | extends: ['@sap-cloud-sdk'], 4 | parser: '@typescript-eslint/parser', 5 | parserOptions: { 6 | project: { 7 | extends: 'tsconfig.json', 8 | include: ['**/*.ts'], 9 | exclude: ['**/generated/**'] 10 | }, 11 | sourceType: 'module' 12 | }, 13 | ignorePatterns: ['generated'], 14 | plugins: ['@typescript-eslint', 'header', 'import', 'prettier'], 15 | rules: {}, 16 | overrides: [] 17 | }; 18 | -------------------------------------------------------------------------------- /samples/resilience-examples/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/prettierrc", 3 | "singleQuote": true, 4 | "filepath": "*.ts", 5 | "trailingComma": "none", 6 | "arrowParens": "avoid", 7 | "endOfLine": "lf" 8 | } 9 | -------------------------------------------------------------------------------- /samples/resilience-examples/README.md: -------------------------------------------------------------------------------- 1 | # SAP Cloud SDK for JS Resilience examples 2 | 3 | ## Description 4 | 5 | This folder contains a few simple examples about resilience and the SDK. 6 | It shows the usage of the default resilience middlewares and also how to build and use a custom one. 7 | 8 | There are custom implementation examples of resilience middlewares in the `resilience.ts`. 9 | You find the usage in the `resilience.spec.ts` file. 10 | 11 | ### Generate oData Client 12 | 13 | The [Business Partner service cloud](https://api.sap.com/api/API_BUSINESS_PARTNER/overview) service definitions in `EDMX` format is already downloaded in the folder `resources/service-specs`. The client is generated using the `npm run generate-client` command. This command is executed automatically in the `postinstall` step after you execute `npm i `. 14 | 15 | **Note** These service is licensed under the terms of [SAP API Information License](../../LICENSES/LicenseRef-API-Definition-File-License.txt). This limits its use to development purposes only. 16 | -------------------------------------------------------------------------------- /samples/resilience-examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sap-cloud-sdk/samples", 3 | "version": "0.0.1", 4 | "description": "SAP Cloud SDK for JS - Examples on Resilience", 5 | "author": "Frank Essenberger", 6 | "private": true, 7 | "license": "Apache-2.0", 8 | "scripts": { 9 | "postinstall": "npm run generate-client", 10 | "lint": "eslint \"src/**/*.ts\" && yarn prettier .", 11 | "lint:fix": "eslint \"src/**/*.ts\" --fix && yarn prettier . -w", 12 | "test": "jest", 13 | "generate-client": "npx generate-odata-client --input resources/service-specs --outputDir src/generated --clearOutputDir --transpile=false --optionsPerService=resources/service-specs/options-per-service.json" 14 | }, 15 | "dependencies": { 16 | "@sap-cloud-sdk/odata-v2": "^3.0.0", 17 | "@sap-cloud-sdk/http-client": "^3.0.0", 18 | "@sap-cloud-sdk/connectivity": "^3.0.0", 19 | "@sap-cloud-sdk/util": "^3.0.0", 20 | "@sap-cloud-sdk/resilience": "^3.0.0", 21 | "@sap-cloud-sdk/generator": "^3.0.0" 22 | }, 23 | "devDependencies": { 24 | "@types/jest": "^27.4.0", 25 | "@typescript-eslint/eslint-plugin": "^5.21.0", 26 | "@typescript-eslint/parser": "^5.33.1", 27 | "@sap-cloud-sdk/eslint-config": "^3.0.0", 28 | "eslint": "^8.30.0", 29 | "eslint-config-prettier": "^8.0.0", 30 | "eslint-plugin-header": "^3.0.0", 31 | "eslint-plugin-import": "^2.20.2", 32 | "eslint-plugin-jest": "^27.1.7", 33 | "eslint-plugin-jsdoc": "^39.6.4", 34 | "eslint-plugin-prettier": "^4.2.1", 35 | "eslint-plugin-regex": "^1.10.0", 36 | "eslint-plugin-unused-imports": "^2.0.0", 37 | "jest": "29.3.1", 38 | "nock": "^13.2.4", 39 | "prettier": "^3.4.2", 40 | "ts-jest": "^29.0.3", 41 | "ts-loader": "^9.2.6", 42 | "ts-node": "^10.4.0", 43 | "typescript": "~5.7.2" 44 | }, 45 | "jest": { 46 | "moduleFileExtensions": [ 47 | "js", 48 | "json", 49 | "ts" 50 | ], 51 | "rootDir": "src", 52 | "testRegex": ".*\\.spec\\.ts$", 53 | "transform": { 54 | "^.+\\.(t|j)s$": "ts-jest" 55 | }, 56 | "collectCoverageFrom": [ 57 | "**/*.(t|j)s" 58 | ], 59 | "coverageDirectory": "../coverage", 60 | "testEnvironment": "node" 61 | } 62 | } -------------------------------------------------------------------------------- /samples/resilience-examples/resources/service-specs/options-per-service.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources/service-specs/API_BUSINESS_PARTNER.edmx": { 3 | "packageName": "cloud-business-partner-service", 4 | "directoryName": "cloud-business-partner-service", 5 | "basePath": "/sap/opu/odata/sap/API_BUSINESS_PARTNER" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/resilience-examples/src/resilience.ts: -------------------------------------------------------------------------------- 1 | import { executeHttpRequest, HttpMiddleware } from '@sap-cloud-sdk/http-client'; 2 | import { HttpDestination } from '@sap-cloud-sdk/connectivity'; 3 | import { createLogger } from '@sap-cloud-sdk/util'; 4 | 5 | /** 6 | * Middleware calling a fallback system if the initial request fails. 7 | * This is just a sample and in a real implementation you should consider what part of the request config should be forwarded to the new system. 8 | * @param fallbackSystem - System which is called if the initial request fails. 9 | * @returns The middleware adding a fallback. 10 | */ 11 | export function fallbackMiddleware( 12 | fallbackSystem: HttpDestination 13 | ): HttpMiddleware { 14 | return options => async arg => { 15 | try { 16 | return await options.fn(arg); 17 | } catch (e) { 18 | return executeHttpRequest(fallbackSystem); 19 | } 20 | }; 21 | } 22 | 23 | const logger = createLogger('http-logs'); 24 | 25 | /** 26 | * Middleware logging the username if the request fails with 403. 27 | * For this example basic authentication is assumed, but you could also decode a JWT and take the userId from there. 28 | * @returns The logging middleware. 29 | */ 30 | export function loggingMiddleware(): HttpMiddleware { 31 | return options => async arg => { 32 | try { 33 | return await options.fn(arg); 34 | } catch (err) { 35 | const [authType, authorizationHeader] = arg.headers['authorization'] 36 | .toString() 37 | .split(' '); 38 | if (authType.toLowerCase() === 'basic' && err.response.status === 403) { 39 | const decoded = Buffer.from(authorizationHeader, 'base64').toString( 40 | 'utf8' 41 | ); 42 | logger.error( 43 | `The user ${ 44 | decoded.split(':')[0] 45 | } is not authorized to do the request.` 46 | ); 47 | } 48 | throw err; 49 | } 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /samples/resilience-examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "moduleResolution": "node", 10 | "lib": ["ES2017"], 11 | "esModuleInterop": true, 12 | "target": "es2017", 13 | "sourceMap": true, 14 | "outDir": "./dist", 15 | "baseUrl": "./" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /scripts/clean-install-all-samples.ts: -------------------------------------------------------------------------------- 1 | import execa from 'execa'; 2 | import { resolve } from 'path'; 3 | import { samples } from './samples-folders'; 4 | 5 | /** 6 | * We can not use workspaces because we want single package-lock.json files in each samples. 7 | * Also the e2e test and update repo https://github.tools.sap/cloudsdk/k8s-e2e-app replaces the local pacakge.json files with one with the same name. 8 | */ 9 | async function forAll() { 10 | for (let i = 0; i < samples.length; i++) { 11 | const folder = samples[i]; 12 | const cwd = resolve(__dirname, '../', folder); 13 | let process = execa('rm', ['-rf', 'node_modules', 'package-lock.json'], { 14 | cwd 15 | }); 16 | process.stdout?.on('data', (data: any) => console.log(data.toString())); 17 | await process; 18 | console.log(`Sample ${folder} cleaned`); 19 | process = execa('npm', ['install'], { cwd }); 20 | process.stdout?.on('data', (data: any) => console.log(data.toString())); 21 | await process; 22 | console.log(`Sample ${folder} installed`); 23 | } 24 | } 25 | 26 | forAll(); 27 | -------------------------------------------------------------------------------- /scripts/fetch-service-spec-cli.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { writeFilesToServiceSpecFolder } from './fetch-service-spec'; 3 | 4 | /** 5 | * Helper script to fetch the service specs to all samples which need it. 6 | */ 7 | const cfSampleApplication = resolve( 8 | __dirname, 9 | '../samples/cf-sample-application/resources/service-specs' 10 | ); 11 | writeFilesToServiceSpecFolder(cfSampleApplication); 12 | 13 | const helmSampleApplication = resolve( 14 | __dirname, 15 | '../samples/helm-sample-application/application/resources/service-specs' 16 | ); 17 | writeFilesToServiceSpecFolder(helmSampleApplication); 18 | 19 | const k8sSampleApplication = resolve( 20 | __dirname, 21 | '../samples/k8s-sample-application/resources/service-specs' 22 | ); 23 | writeFilesToServiceSpecFolder(k8sSampleApplication); 24 | -------------------------------------------------------------------------------- /scripts/fetch-service-spec.ts: -------------------------------------------------------------------------------- 1 | import Axios, { AxiosResponse } from 'axios'; 2 | import * as dotenv from 'dotenv'; 3 | import { promises } from 'fs'; 4 | import { resolve, join } from 'path'; 5 | 6 | dotenv.config(); 7 | const { writeFile, rm, mkdir } = promises; 8 | 9 | export const productiveApiHubUrl = 10 | 'https://cloudintegration.hana.ondemand.com/'; 11 | 12 | async function fetchApiContent(artifactName: string): Promise { 13 | const requestURL = `odata/1.0/catalog.svc/APIContent.APIs(Name='${artifactName}')/$value?type=EDMX`; 14 | const response = await Axios.request({ 15 | method: 'GET', 16 | url: `${productiveApiHubUrl}${requestURL}`, 17 | headers: { authorization: getBasicHeaderFromEnv() } 18 | }); 19 | console.info(`Service definition fetched: ${artifactName}.`); 20 | return response.data; 21 | } 22 | 23 | function getBasicHeaderFromEnv(): string { 24 | if (!process.env.apiHubUser) { 25 | console.error('User not given in process.env.apiHubUser'); 26 | throw new Error('User not given in process.env.apiHubUser'); 27 | } 28 | if (!process.env.apiHubUserPassword) { 29 | console.error('Password not given in process.env.apiHubUserPassword'); 30 | throw new Error('Password not given in process.env.apiHubUserPassword'); 31 | } 32 | return basicHeader(process.env.apiHubUser, process.env.apiHubUserPassword); 33 | } 34 | 35 | function basicHeader(username: string, password: string): string { 36 | const encoded = Buffer.from(`${username}:${password}`).toString('base64'); 37 | return 'Basic ' + encoded; 38 | } 39 | async function writeServiceDefintion( 40 | specRoot: string, 41 | content: string, 42 | fileName: string 43 | ): Promise { 44 | const path = join(specRoot, fileName); 45 | await writeFile(path, content, { encoding: 'utf-8' }); 46 | console.info(`Service definition written: ${path}.`); 47 | } 48 | 49 | export async function writeFilesToServiceSpecFolder(specRoot: string) { 50 | await cleanUp(specRoot); 51 | const contentCloud = await fetchApiContent('API_BUSINESS_PARTNER'); 52 | await writeServiceDefintion( 53 | specRoot, 54 | contentCloud, 55 | 'API_BUSINESS_PARTNER.edmx' 56 | ); 57 | const contentOp = await fetchApiContent('OP_API_BUSINESS_PARTNER_SRV'); 58 | await writeServiceDefintion( 59 | specRoot, 60 | contentOp, 61 | 'OP_API_BUSINESS_PARTNER_SRV.edmx' 62 | ); 63 | await createServiceMapping(specRoot); 64 | } 65 | 66 | async function cleanUp(specRoot: string) { 67 | await rm(specRoot, { recursive: true }); 68 | await mkdir(specRoot); 69 | } 70 | 71 | async function createServiceMapping(specRoot: string) { 72 | const content = { 73 | 'resources/service-specs/API_BUSINESS_PARTNER.edmx': { 74 | directoryName: 'cloud-business-partner-service', 75 | basePath: '/sap/opu/odata/sap/API_BUSINESS_PARTNER', 76 | packageName: 'cloud-business-partner-service' 77 | }, 78 | 'resources/service-specs/OP_API_BUSINESS_PARTNER_SRV.edmx': { 79 | directoryName: 'op-business-partner-service', 80 | basePath: '/sap/opu/odata/sap/API_BUSINESS_PARTNER', 81 | packageName: 'op-business-partner-service' 82 | } 83 | }; 84 | await writeFile( 85 | join(specRoot, 'options-per-service.json'), 86 | JSON.stringify(content, null, 2), 87 | { encoding: 'utf-8' } 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /scripts/samples-folders.ts: -------------------------------------------------------------------------------- 1 | export const samples = [ 2 | 'samples/cds-sample-application', 3 | 'samples/cf-multi-tenant-application', 4 | 'samples/cf-sample-application', 5 | 'samples/helm-sample-application/application', 6 | 'samples/http-client-examples', 7 | 'samples/k8s-sample-application', 8 | 'samples/resilience-examples' 9 | ]; 10 | -------------------------------------------------------------------------------- /scripts/set-sdk-version.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import execa from 'execa'; 3 | import { readFileSync } from 'fs'; 4 | import { promises } from 'fs'; 5 | import { samples } from './samples-folders'; 6 | 7 | const sdkVersionToSet = 'next'; 8 | const { writeFile } = promises; 9 | 10 | /** 11 | * We can not use workspaces because we want single package-lock.json files in each samples. 12 | * Also the e2e test and update repo https://github.tools.sap/cloudsdk/k8s-e2e-app replaces the local pacakge.json files with one with the same name. 13 | */ 14 | async function forAll() { 15 | for (let i = 0; i < samples.length; i++) { 16 | const packageJsonPath = resolve( 17 | __dirname, 18 | '../', 19 | samples[i], 20 | 'package.json' 21 | ); 22 | const jsonContent = JSON.parse( 23 | readFileSync(packageJsonPath, { encoding: 'utf-8' }) 24 | ); 25 | setSdkVersion(jsonContent.dependencies); 26 | setSdkVersion(jsonContent.devDependencies); 27 | await writeFile(packageJsonPath, JSON.stringify(jsonContent, null, 2), { 28 | encoding: 'utf-8' 29 | }); 30 | console.log(`Package.json updated in ${samples[i]}`); 31 | } 32 | } 33 | 34 | function setSdkVersion(dependencies: Record) { 35 | Object.keys(dependencies).forEach(key => { 36 | if (key.startsWith('@sap-cloud-sdk')) { 37 | dependencies[key] = 'next'; 38 | } 39 | }); 40 | } 41 | 42 | forAll(); 43 | --------------------------------------------------------------------------------