├── .cra ├── .cveignore └── .fileignore ├── .gitignore ├── .pipeline-config.yaml ├── .secrets.baseline ├── LICENSE ├── README.md ├── cd-scripts ├── acceptance-test.sh ├── deploy.sh ├── deploy_setup.sh ├── doi-publish-deploy.sh └── setup.sh ├── configuration ├── global.json └── tenants │ ├── tenant-a.json │ └── tenant-b.json ├── documentation ├── Options-Simple.drawio ├── Options-Simple.png ├── SaaS-Options.png ├── announcement-blog-template.md ├── announcement-blog.md ├── diagrams │ ├── multi-tenancy-app-architecture.drawio │ ├── multi-tenant-app-architecture.jpg │ └── multi-tenant-app-architecture.png ├── example-app.png └── images │ ├── IKS-Infrastructure-by-Terraform-1.png │ ├── Multi-tenancy-serverless.png │ ├── VPC-Infrastructure-by-Terraform-1.png │ ├── VPC-Infrastructure-by-Terraform-2.png │ ├── VPC-Infrastructure-by-Terraform-Subnets.png │ └── button-documentation.png ├── installapp ├── appid-configs │ ├── add-application-template.json │ ├── add-redirecturis-template.json │ ├── add-roles-template.json │ ├── add-scope.json │ ├── add-ui-color.json │ ├── add-ui-text-template.json │ ├── idps-clouddirectory.json │ ├── idps-custom.json │ ├── idps-facebook.json │ ├── idps-google.json │ ├── idps-saml.json │ ├── user-export.json │ └── user-import.json ├── appid-images │ └── logo.png ├── backup-bash-scripts │ ├── ce-build-images-quay-podman.sh │ ├── ce-install-application-ibmcr.sh │ ├── ce-install-application-quay.sh │ └── run-two-applications.sh ├── ce-build-images-ibm-buildah.sh ├── ce-check-prerequisites.sh ├── ce-clean-up-two-tenancies.sh ├── ce-clean-up.sh ├── ce-create-two-tenancies.sh ├── ce-install-application.sh ├── ops-create-two-appids.sh ├── ops-create-two-postgres.sh ├── ops-install-single-appid.sh ├── ops-install-single-postgres.sh ├── postgres-config │ ├── create-populate-tenant-a.sql │ ├── create-populate-tenant-b.sql │ └── insert-template.sh ├── tools-image │ └── Dockerfile ├── tz-build-images-code-engine.sh ├── tz-check-prerequisites.sh ├── tz-create-two-tenancies.sh ├── tz-install-application.sh └── tz-update-config.sh ├── local.env.template ├── operator ├── .dockerignore ├── .gitignore ├── Dockerfile ├── Makefile ├── PROJECT ├── api │ └── v1alpha1 │ │ ├── ecommerceapplication_types.go │ │ ├── groupversion_info.go │ │ └── zz_generated.deepcopy.go ├── appIdHelper │ └── appId.go ├── config │ ├── crd │ │ ├── bases │ │ │ └── cache.saas.ecommerce.sample.com_ecommerceapplications.yaml │ │ ├── kustomization.yaml │ │ ├── kustomizeconfig.yaml │ │ └── patches │ │ │ ├── cainjection_in_ecommerceapplications.yaml │ │ │ └── webhook_in_ecommerceapplications.yaml │ ├── default │ │ ├── kustomization.yaml │ │ ├── manager_auth_proxy_patch.yaml │ │ └── manager_config_patch.yaml │ ├── manager │ │ ├── controller_manager_config.yaml │ │ ├── kustomization.yaml │ │ └── manager.yaml │ ├── manifests │ │ └── kustomization.yaml │ ├── prometheus │ │ ├── kustomization.yaml │ │ └── monitor.yaml │ ├── rbac │ │ ├── auth_proxy_client_clusterrole.yaml │ │ ├── auth_proxy_role.yaml │ │ ├── auth_proxy_role_binding.yaml │ │ ├── auth_proxy_service.yaml │ │ ├── ecommerceapplication_editor_role.yaml │ │ ├── ecommerceapplication_viewer_role.yaml │ │ ├── kustomization.yaml │ │ ├── leader_election_role.yaml │ │ ├── leader_election_role_binding.yaml │ │ ├── role.yaml │ │ ├── role_binding.yaml │ │ └── service_account.yaml │ ├── samples │ │ ├── cache_v1alpha1_ecommerceapplication.yaml │ │ ├── job.yml │ │ └── kustomization.yaml │ └── scorecard │ │ ├── bases │ │ └── config.yaml │ │ ├── kustomization.yaml │ │ └── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml ├── controllers │ ├── ecommerceapplication_controller.go │ └── suite_test.go ├── ecommerceapplication │ └── tmp │ │ └── copy-secret.sh ├── go.mod ├── go.sum ├── hack │ └── boilerplate.go.txt ├── main.go └── postgres.yaml └── scripts ├── env-config-template.js ├── run-locally-backend.sh ├── run-locally-container-backend.sh ├── run-locally-container-frontend.sh └── run-locally-frontend.sh /.cra/.cveignore: -------------------------------------------------------------------------------- 1 | * 2 | */ -------------------------------------------------------------------------------- /.cra/.fileignore: -------------------------------------------------------------------------------- 1 | * 2 | */ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | javacore* 2 | jitdump* 3 | .mvn 4 | certificates 5 | code/service-catalog/run-locally.sh 6 | local.env 7 | test.env 8 | test.sh 9 | cert.temp 10 | postgres-key-temp.json 11 | appid-key-temp.json 12 | target/ 13 | pom.xml.tag 14 | pom.xml.releaseBackup 15 | pom.xml.versionsBackup 16 | pom.xml.next 17 | release.properties 18 | dependency-reduced-pom.xml 19 | buildNumber.properties 20 | .mvn/timing.properties 21 | code/frontend/package-lock.json 22 | #various files 23 | temp_information.txt 24 | 25 | #fontend configs 26 | env-config.js 27 | env-config 28 | -env-config 29 | 30 | #env configs 31 | local.env_tmp 32 | local.env-tmp 33 | 34 | #appid configs 35 | add-redirecturis.json 36 | add-roles.json 37 | add-application.json 38 | 39 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 40 | !/.mvn/wrapper/maven-wrapper.jar 41 | 42 | .gradle/ 43 | build/ 44 | .gradletasknamecache 45 | 46 | *.log 47 | graph-output.dot 48 | 49 | .settings 50 | .vscode 51 | .project 52 | .classpath 53 | *.iml 54 | .idea 55 | .factorypath 56 | 57 | settings.xml 58 | quarkus.log 59 | 60 | bin/ 61 | .DS_Store 62 | ObjectStore 63 | 64 | lsp/ 65 | 66 | *.swp 67 | *.swo 68 | -------------------------------------------------------------------------------- /.pipeline-config.yaml: -------------------------------------------------------------------------------- 1 | # Documentation on available configuration 2 | # https://pages.github.ibm.com/one-pipeline/docs/custom-scripts.html 3 | 4 | version: '1' 5 | 6 | setup: 7 | image: icr.io/continuous-delivery/pipeline/pipeline-base-image:2.12@sha256:ff4053b0bca784d6d105fee1d008cfb20db206011453071e86b69ca3fde706a4 8 | script: | 9 | #!/usr/bin/env bash 10 | 11 | source cd-scripts/setup.sh 12 | 13 | deploy: 14 | image: icr.io/continuous-delivery/pipeline/pipeline-base-image:2.12@sha256:ff4053b0bca784d6d105fee1d008cfb20db206011453071e86b69ca3fde706a4 15 | script: | 16 | #!/usr/bin/env bash 17 | 18 | if [[ "$PIPELINE_DEBUG" == 1 ]]; then 19 | trap env EXIT 20 | env 21 | set -x 22 | fi 23 | 24 | source cd-scripts/deploy_setup.sh 25 | source cd-scripts/deploy.sh 26 | export DEPLOY_EXIT=$? 27 | source cd-scripts/doi-publish-deploy.sh 28 | 29 | acceptance-test: 30 | image: icr.io/continuous-delivery/pipeline/pipeline-base-image:2.12@sha256:ff4053b0bca784d6d105fee1d008cfb20db206011453071e86b69ca3fde706a4 31 | script: | 32 | #!/usr/bin/env bash 33 | 34 | #source scripts/acceptance-test.sh 35 | exit 0 36 | -------------------------------------------------------------------------------- /.secrets.baseline: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": { 3 | "files": "^.secrets.baseline$|documentation/getting-started.md|documentation/local-development.md|ci-scripts/sign_image.sh|README.md|installapp/appid-configs/user-export.json|installapp/appid-configs/user-import.json|installapp/backup-bash-scripts/ce-install-application-ibmcr.sh|installapp/ce-install-application.sh|local.env.template", 4 | "lines": null 5 | }, 6 | "generated_at": "2021-06-16T07:00:42Z", 7 | "plugins_used": [ 8 | { 9 | "name": "AWSKeyDetector" 10 | }, 11 | { 12 | "name": "ArtifactoryDetector" 13 | }, 14 | { 15 | "base64_limit": 4.5, 16 | "name": "Base64HighEntropyString" 17 | }, 18 | { 19 | "name": "BasicAuthDetector" 20 | }, 21 | { 22 | "name": "BoxDetector" 23 | }, 24 | { 25 | "name": "CloudantDetector" 26 | }, 27 | { 28 | "ghe_instance": "github.ibm.com", 29 | "name": "GheDetector" 30 | }, 31 | { 32 | "hex_limit": 3, 33 | "name": "HexHighEntropyString" 34 | }, 35 | { 36 | "name": "IbmCloudIamDetector" 37 | }, 38 | { 39 | "name": "IbmCosHmacDetector" 40 | }, 41 | { 42 | "name": "JwtTokenDetector" 43 | }, 44 | { 45 | "keyword_exclude": null, 46 | "name": "KeywordDetector" 47 | }, 48 | { 49 | "name": "MailchimpDetector" 50 | }, 51 | { 52 | "name": "PrivateKeyDetector" 53 | }, 54 | { 55 | "name": "SlackDetector" 56 | }, 57 | { 58 | "name": "SoftlayerDetector" 59 | }, 60 | { 61 | "name": "StripeDetector" 62 | }, 63 | { 64 | "name": "TwilioKeyDetector" 65 | } 66 | ], 67 | "results": {}, 68 | "version": "0.13.1+ibm.38.dss", 69 | "word_list": { 70 | "file": null, 71 | "hash": null 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi-tenancy Assets for IBM Clients to build SaaS 2 | 3 | This repo contains multi-tenancy assets for IBM clients and partners to build SaaS (Software as a Service). 4 | 5 | ### Project Structure 6 | 7 | * [Introduction](#introduction) 8 | * [Documentation](#documentation) 9 | * [Getting Started](#getting-started) 10 | * [Repositories](#repositories) 11 | 12 | ## Introduction 13 | 14 | A key benefit of the cloud is the ability to deploy software for multiple consumers without having to install it redundantly on-premises. When software is provided as a managed service (SaaS), costs can be reduced for the deployments and the operations of applications. Additionally SaaS can be scaled and new consumers can be added easily. 15 | 16 | In order to leverage these advantages, applications need to be designed, so that they can support multiple tenants. Often tenants are not single users, but clients of SaaS providers with their own corporate authentication mechanisms. When running SaaS for multiple tenants, it's often required to keep the workloads isolated from each other for security reasons. For example, typically separate databases are used for tenants. 17 | 18 | At the same time common deployment and operation models are required, so that new SaaS versions can be deployed to different tenants in an unique and efficient way. 19 | 20 | This project aims to support IBM partners to build SaaS for different platforms including Kubernetes, OpenShift, Serverless, Satellite, AWS and Azure. The used sample application, which contains two containers, is the same one for all platforms. The CI/CD mechanisms slightly differentiate between the platforms. 21 | 22 | ### Platform Options 23 | 24 | The following diagram shows the different platform options. At this point the repo contains the IBM Cloud platforms. 25 | 26 | More options are planned to be added. For example with Satellite the SaaS application can be deployed on-premises to client data centers, but managed centrally. Additionally the same SaaS application can be deployed on other managed OpenShift services like AWS ROSA and Azure ARO. 27 | 28 | **Serverless on IBM Cloud** 29 | 30 | The easiest way to get started is to use serverless. The repo describes how to use IBM Code Engine to run the application logic, IBM App ID for authentication, IBM Postgres for persistence and IBM Toolchain for CI/CD. Scripts are provided to make the setup as easy as possible. 31 | 32 | **Managed Kubernetes and OpenShift on IBM Cloud** 33 | 34 | For more advanced cloud-native applications Kubernetes and OpenShift can be used. Compute isolation can be done either by sharing clusters and using Kubernetes namespaces/OpenShift projects or by having separate clusters for tenants. For authentication the managed services App ID and Postgres can be used, but they can also be replaced by other managed services or services running within the clusters. 35 | 36 | For CI/CD the IBM DevSecOps reference architecture based on IBM Toolchain is used which is also the internal IBM standard and which guarantees compliance for regulated industries. 37 | 38 | 39 | 40 | ### Sample Application 41 | 42 | The project comes with a simple e-commerce example application. A SaaS provider might have one client selling books, another one selling shoes. 43 | 44 | 45 | 46 | 47 | 48 | ## Documentation 49 | 50 | You can access the easier navigable and consumable version of the documentation by pressing this button [![](documentation/images/button-documentation.png)](https://ibm.github.io/multi-tenancy-documentation/) 51 | 52 | * [Introduction](#introduction) 53 | * Development of Microservices 54 | * [Quarkus Backend Service Code](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/development_of_microservices/backend-service-impl.md) 55 | * [Quarkus Backend Service Container](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/development_of_microservices/backend-service-container.md) 56 | * [Vue.js Frontend Service Code](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/development_of_microservices/frontend-service-code.md) 57 | * [Vue.js Frontend Service Container](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/development_of_microservices/frontend-service-container.md) 58 | * [Externalization of Variables in Backend Microservices](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/development_of_microservices/externalization-of-variables-in-backend-microservices.md) 59 | * [Externalization of Variables in Frontend Microservices](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/development_of_microservices/externalization-of-variables-in-frontend-microservices.md) 60 | * [Local Development of Services](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/development_of_microservices/local-development.md) 61 | * [Authentication Flow (AppID, backend, frontend)](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/development_of_microservices/authentication-flow-appip-backend-frontend.md) 62 | * Creation of managed IBM Cloud Services 63 | * Database 64 | * [Programmatic Creation of Postgres](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/creation-of-managed-ibm-cloud-services/create-postgres.md) 65 | * [Programmatic Configuration of Postgres including Schema](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/creation-of-managed-ibm-cloud-services/create-postgres-schema.md) 66 | * Authentication 67 | * [Programmatic Creation of AppID](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/creation-of-managed-ibm-cloud-services/create-appid.md) 68 | * [Programmatic Configuration of AppID](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/creation-of-managed-ibm-cloud-services/configure-appid.md) 69 | * Serverless via IBM Code Engine 70 | * [Architecture](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/serverless-via-ibm-code-engine/ce-arcitecture.md) 71 | * Initial Setup via Scripts 72 | 1. [Create the instances](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/serverless-via-ibm-code-engine/ce-setup-create-the-instances.md) 73 | 2. [Verify the created instances](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/serverless-via-ibm-code-engine/ce-verify-the-created-instances.md) 74 | * [CI/CD](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/serverless-via-ibm-code-engine/serverless-cicd.md) 75 | * [Onboarding](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/serverless-via-ibm-code-engine/code-engine-onboarding.md) 76 | * [Observability (logging, monitoring, vulnerabilities)](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/serverless-via-ibm-code-engine/observability.md) 77 | * [Billing](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/serverless-via-ibm-code-engine/code-engine-billing.md) 78 | * [Clean Up](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/serverless-via-ibm-code-engine/ce_clean_up.md) 79 | * Kubernetes via IBM Kubernetes Service and IBM OpenShift 80 | * Architecture 81 | * [Initial Setup via Scripts](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/automation/terraform/3-Provisionning-A-Kubernetes-Based-Infrastructure.md) 82 | * CI/CD DevSecOps 83 | * [Overview](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/kubernetes-via-ibm-kubernetes-service-and-ibm-openshift/devsecops-overview.md) 84 | * CI 85 | * [CI pull request](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/kubernetes-via-ibm-kubernetes-service-and-ibm-openshift/ci-pull-request.md) 86 | * [CI pipeline](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/kubernetes-via-ibm-kubernetes-service-and-ibm-openshift/ci-pipeline.md) 87 | * CD 88 | * [CD pull request](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/kubernetes-via-ibm-kubernetes-service-and-ibm-openshift/cd-pull-request.md) 89 | * [CD pipeline](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/kubernetes-via-ibm-kubernetes-service-and-ibm-openshift/cd-pipeline.md) 90 | * [Security and Compliance](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/kubernetes-via-ibm-kubernetes-service-and-ibm-openshift/security-and-compliance.md) 91 | * Setup of the Toolchains 92 | * [CI Toolchains](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/k8s/3-ci-cd/README_ci.md) 93 | * [CD Toolchains](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/k8s/3-ci-cd/README_cd.md) 94 | * [Onboarding](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/kubernetes-via-ibm-kubernetes-service-and-ibm-openshift/k8s-onboarding.md) 95 | * [Observability (logging, monitoring, vulnerabilities)](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/kubernetes-via-ibm-kubernetes-service-and-ibm-openshift/observability.md) 96 | * [Billing](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/kubernetes-via-ibm-kubernetes-service-and-ibm-openshift/k8s-billing.md) 97 | 98 | ## Repositories 99 | 100 | This repo is the 'parent repo' including documentation and global configuration. The other four repos contain the implementation of the microservices and the serverless pipelines. 101 | 102 | * [multi-tenancy](https://github.com/IBM/multi-tenancy) - this repo (parent repo) 103 | * Overview documentation 104 | * Global and tenant specific application configuration 105 | * CD pipeline 106 | * Scripts to deploy cloud services/infrastructure 107 | 108 | * [multi-tenancy-backend](https://github.com/IBM/multi-tenancy-backend) - backend microservice 109 | * Code 110 | * CI pipeline 111 | 112 | * [multi-tenancy-frontend](https://github.com/IBM/multi-tenancy-frontend) - frontend microservice 113 | * Code 114 | * CI pipeline 115 | 116 | * [multi-tenancy-serverless-ci-cd](https://github.com/IBM/multi-tenancy-serverless-ci-cd) - CI and CD pipelines for serverless 117 | 118 | 119 | ## Getting Started 120 | 121 | The easiest way to get started is to set up the sample application for two tenants on the IBM Cloud using serverless technology. The following diagram describes the serverless architecture of the simple e-commerce application which has two images (backend and frontend). 122 | 123 | Isolated Compute: 124 | * One frontend container per tenant 125 | * One backend container per tenant 126 | * One App ID instance per tenant 127 | * One Postgres instance (with one database) per tenant 128 | 129 | Shared CI/CD: 130 | * One code base for frontend and backend services 131 | * One image for frontend service 132 | * One image for backend service 133 | * One toolchain for all tenants (with four pipelines) 134 | 135 | 136 | 137 | Used IBM Services: 138 | * IBM Code Engine 139 | * IBM Container Registry 140 | * IBM App ID 141 | * IBM Postgres 142 | * IBM Toolchain 143 | 144 | Used Technologies: 145 | * Quarkus 146 | * Vue.js and nginx 147 | * Bash scripts 148 | 149 | **Initial Deployment Scripts** 150 | 151 | Scripts and provided to set up all services and the application automatically. Follow this [step by step guide](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/serverless-via-ibm-code-engine/ce-setup-create-the-instances.md) to set up everything using local bash scripts. 152 | 153 | **Deployments of Updates via CI/CD** 154 | 155 | Additionally pipelines are provided to re-deploy the backend and frontend services when their implementations have changed. Follow this [step by step guide](https://github.com/IBM/multi-tenancy-documentation/blob/main/documentation/serverless-via-ibm-code-engine/serverless-cicd.md) to set up the pipelines. 156 | 157 | -------------------------------------------------------------------------------- /cd-scripts/acceptance-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | export TARGET_ENVIRONMENT 4 | export HOME 5 | export DEPLOYMENT_DELTA 6 | 7 | TARGET_ENVIRONMENT="$(cat /config/environment)" 8 | INVENTORY_PATH="$(cat /config/inventory-path)" 9 | DEPLOYMENT_DELTA_PATH="$(cat /config/deployment-delta-path)" 10 | DEPLOYMENT_DELTA=$(cat "${DEPLOYMENT_DELTA_PATH}") 11 | 12 | echo "Target environment: ${TARGET_ENVIRONMENT}" 13 | echo "Deployment Delta (inventory entries with updated artifacts)" 14 | echo "" 15 | 16 | echo "$DEPLOYMENT_DELTA" | jq '.' 17 | 18 | echo "" 19 | echo "Inventory content" 20 | echo "" 21 | 22 | ls -la ${INVENTORY_PATH} 23 | 24 | test_count=0 25 | 26 | # 27 | # prepare acceptance tests 28 | # 29 | source /root/.nvm/nvm.sh 30 | npm ci 31 | 32 | # 33 | # iterate over inventory deployment delta 34 | # and run acceptance tests 35 | # 36 | exit_code=0 37 | for INVENTORY_ENTRY in $(echo "${DEPLOYMENT_DELTA}" | jq -r '.[] '); do 38 | APP_URL_PATH="$(echo ${INVENTORY_ENTRY} | sed 's/\//_/g')_app-url.json" 39 | if [[ -f "../$APP_URL_PATH" ]]; then 40 | export APP_URL=$(cat "../${APP_URL_PATH}") 41 | 42 | echo "Running acceptance test for: '$APP_URL_PATH'" 43 | if ! npm run acceptance-test; then 44 | exit_code=1 45 | fi 46 | ((test_count+=1)) 47 | fi 48 | done 49 | 50 | echo "Run $test_count tests for $(echo "${DEPLOYMENT_DELTA}" | jq '. | length') entries" 51 | exit $exit_code 52 | -------------------------------------------------------------------------------- /cd-scripts/deploy_setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export IBMCLOUD_API_KEY 4 | export IBMCLOUD_TOOLCHAIN_ID 5 | export IBMCLOUD_IKS_REGION 6 | export IBMCLOUD_IKS_CLUSTER_NAME 7 | export IBMCLOUD_IKS_CLUSTER_NAMESPACE 8 | export IMAGE_PULL_SECRET_NAME 9 | export TARGET_ENVIRONMENT 10 | export HOME 11 | export BREAK_GLASS 12 | export DEPLOYMENT_DELTA 13 | 14 | if [ -f /config/api-key ]; then 15 | IBMCLOUD_API_KEY="$(cat /config/api-key)" # pragma: allowlist secret 16 | else 17 | IBMCLOUD_API_KEY="$(cat /config/ibmcloud-api-key)" # pragma: allowlist secret 18 | fi 19 | 20 | HOME=/root 21 | 22 | TARGET_ENVIRONMENT="$(cat /config/environment)" 23 | INVENTORY_PATH="$(cat /config/inventory-path)" 24 | DEPLOYMENT_DELTA_PATH="$(cat /config/deployment-delta-path)" 25 | DEPLOYMENT_DELTA=$(cat "${DEPLOYMENT_DELTA_PATH}") 26 | 27 | echo "Target environment: ${TARGET_ENVIRONMENT}" 28 | echo "Deployment Delta (inventory entries with updated artifacts)" 29 | echo "" 30 | 31 | echo "$DEPLOYMENT_DELTA" | jq '.' 32 | 33 | echo "" 34 | echo "Inventory content" 35 | echo "" 36 | 37 | ls -la ${INVENTORY_PATH} 38 | 39 | BREAK_GLASS=$(cat /config/break_glass || echo "") 40 | IBMCLOUD_TOOLCHAIN_ID="$(jq -r .toolchain_guid /toolchain/toolchain.json)" 41 | IBMCLOUD_IKS_REGION="$(cat /config/dev-region | awk -F ":" '{print $NF}')" 42 | #IBMCLOUD_IKS_CLUSTER_NAMESPACE="$(cat /config/dev-cluster-namespace)" 43 | #IBMCLOUD_IKS_CLUSTER_NAME="$(cat /config/cluster-name)" 44 | 45 | #example: https://github.com/IBM/multi-tenancy/blob/main/configuration/global.json 46 | CONFIG_FILE="configuration/global.json" 47 | IBM_CLOUD_RESOURCE_GROUP=$(cat ./$CONFIG_FILE | jq '.IBM_CLOUD.RESOURCE_GROUP' | sed 's/"//g') 48 | IBM_CLOUD_REGION=$(cat ./$CONFIG_FILE | jq '.IBM_CLOUD.REGION' | sed 's/"//g') 49 | REGISTRY_NAMESPACE=$(cat ./$CONFIG_FILE | jq '.REGISTRY.NAMESPACE' | sed 's/"//g') 50 | REGISTRY_TAG=$(cat ./$CONFIG_FILE | jq '.REGISTRY.TAG' | sed 's/"//g') 51 | REGISTRY_URL=$(cat ./$CONFIG_FILE | jq '.REGISTRY.URL' | sed 's/"//g') 52 | REGISTRY_SECRET_NAME=$(cat ./$CONFIG_FILE | jq '.REGISTRY.SECRET_NAME' | sed 's/"//g') 53 | IMAGES_NAME_BACKEND=$(cat ./$CONFIG_FILE | jq '.IMAGES.NAME_BACKEND' | sed 's/"//g') 54 | IMAGES_NAME_FRONTEND=$(cat ./$CONFIG_FILE | jq '.IMAGES.NAME_FRONTEND' | sed 's/"//g') 55 | 56 | #example: https://github.com/IBM/multi-tenancy/blob/main/configuration/tenants/tenant-a.json 57 | TENANT=$(get_env tenant '') 58 | echo "niklas tenant" 59 | echo ${TENANT} 60 | CONFIG_FILE="configuration/tenants/${TENANT}.json" 61 | APPID_SERVICE_INSTANCE_NAME=$(cat ./$CONFIG_FILE | jq '.APP_ID.SERVICE_INSTANCE' | sed 's/"//g') 62 | APPID_SERVICE_KEY_NAME=$(cat ./$CONFIG_FILE | jq '.APP_ID.SERVICE_KEY_NAME' | sed 's/"//g') 63 | POSTGRES_SERVICE_INSTANCE=$(cat ./$CONFIG_FILE | jq '.POSTGRES.SERVICE_INSTANCE' | sed 's/"//g') 64 | POSTGRES_SERVICE_KEY_NAME=$(cat ./$CONFIG_FILE | jq '.POSTGRES.SERVICE_KEY_NAME' | sed 's/"//g') 65 | POSTGRES_SQL_FILE=$(cat ./$CONFIG_FILE | jq '.POSTGRES.SQL_FILE' | sed 's/"//g') 66 | APPLICATION_CONTAINER_NAME_BACKEND=$(cat ./$CONFIG_FILE | jq '.APPLICATION.CONTAINER_NAME_BACKEND' | sed 's/"//g') 67 | APPLICATION_CONTAINER_NAME_FRONTEND=$(cat ./$CONFIG_FILE | jq '.APPLICATION.CONTAINER_NAME_FRONTEND' | sed 's/"//g') 68 | APPLICATION_CATEGORY=$(cat ./$CONFIG_FILE | jq '.APPLICATION.CATEGORY' | sed 's/"//g') 69 | CODE_ENGINE_PROJECT_NAME=$(cat ./$CONFIG_FILE | jq '.CODE_ENGINE.PROJECT_NAME' | sed 's/"//g') 70 | IBM_KUBERNETES_SERVICE_NAME=$(cat ./$CONFIG_FILE | jq '.IBM_KUBERNETES_SERVICE.NAME' | sed 's/"//g') 71 | IBM_KUBERNETES_SERVICE_NAMESPACE=$(cat ./$CONFIG_FILE | jq '.IBM_KUBERNETES_SERVICE.NAMESPACE' | sed 's/"//g') 72 | IBM_OPENSHIFT_SERVICE_NAME=$(cat ./$CONFIG_FILE | jq '.IBM_OPENSHIFT_SERVICE.NAME' | sed 's/"//g') 73 | IBM_OPENSHIFT_SERVICE_NAMESPACE=$(cat ./$CONFIG_FILE | jq '.IBM_OPENSHIFT_SERVICE.NAMESPACE' | sed 's/"//g') 74 | PLATFORM_NAME=$(cat ./$CONFIG_FILE | jq '.PLATFORM.NAME' | sed 's/"//g') 75 | 76 | IBM_KUBERNETES_SERVICE_NAMESPACE=${IBM_KUBERNETES_SERVICE_NAMESPACE} 77 | 78 | if [ "$PLATFORM_NAME" = "IBM_KUBERNETES_SERVICE" ]; then 79 | IBMCLOUD_IKS_CLUSTER_NAMESPACE=${IBM_KUBERNETES_SERVICE_NAMESPACE} 80 | IBMCLOUD_IKS_CLUSTER_NAME=${IBM_KUBERNETES_SERVICE_NAME} 81 | else 82 | IBMCLOUD_IKS_CLUSTER_NAMESPACE=${IBM_OPENSHIFT_SERVICE_NAMESPACE} 83 | IBMCLOUD_IKS_CLUSTER_NAME=${IBM_OPENSHIFT_SERVICE_NAME} 84 | fi 85 | 86 | IBMCLOUD_IKS_REGION=${IBM_CLOUD_REGION} 87 | #IBMCLOUD_IKS_CLUSTER_NAMESPACE=${IBM_KUBERNETES_SERVICE_NAMESPACE} 88 | #IBMCLOUD_IKS_CLUSTER_NAME=${IBM_KUBERNETES_SERVICE_NAME} 89 | 90 | if [[ -n "$BREAK_GLASS" ]]; then 91 | export KUBECONFIG 92 | KUBECONFIG=/config/cluster-cert 93 | else 94 | IBMCLOUD_IKS_REGION=$(echo "${IBMCLOUD_IKS_REGION}" | awk -F ":" '{print $NF}') 95 | ibmcloud login -r "$IBMCLOUD_IKS_REGION" 96 | ibmcloud ks cluster config --cluster "$IBMCLOUD_IKS_CLUSTER_NAME" 97 | 98 | ibmcloud ks cluster get --cluster "${IBMCLOUD_IKS_CLUSTER_NAME}" --json > "${IBMCLOUD_IKS_CLUSTER_NAME}.json" 99 | # If the target cluster is openshift then make the appropriate additional login with oc tool 100 | if which oc > /dev/null && jq -e '.type=="openshift"' "${IBMCLOUD_IKS_CLUSTER_NAME}.json" > /dev/null; then 101 | echo "${IBMCLOUD_IKS_CLUSTER_NAME} is an openshift cluster. Doing the appropriate oc login to target it" 102 | oc login -u apikey -p "${IBMCLOUD_API_KEY}" 103 | fi 104 | # 105 | # check pull traffic & storage quota in container registry 106 | # 107 | if ibmcloud cr quota | grep 'Your account has exceeded its pull traffic quota'; then 108 | echo "Your account has exceeded its pull traffic quota for the current month. Review your pull traffic quota in the preceding table." 109 | exit 1 110 | fi 111 | 112 | if ibmcloud cr quota | grep 'Your account has exceeded its storage quota'; then 113 | echo "Your account has exceeded its storage quota. You can check your images at https://cloud.ibm.com/kubernetes/registry/main/images" 114 | exit 1 115 | fi 116 | fi 117 | 118 | IMAGE="$(cat /config/image)" 119 | IMAGE_PULL_SECRET_NAME="ibmcloud-toolchain-${IBMCLOUD_TOOLCHAIN_ID}-${REGISTRY_URL}" 120 | 121 | IBMCLOUD_IKS_REGION=$(echo "${IBMCLOUD_IKS_REGION}" | awk -F ":" '{print $NF}') 122 | ibmcloud login -r "${IBMCLOUD_IKS_REGION}" 123 | ibmcloud target -g ${IBM_CLOUD_RESOURCE_GROUP} 124 | ibmcloud ks cluster config --cluster "${IBMCLOUD_IKS_CLUSTER_NAME}" 125 | 126 | ibmcloud ks cluster get --cluster "${IBMCLOUD_IKS_CLUSTER_NAME}" --json > "${IBMCLOUD_IKS_CLUSTER_NAME}.json" 127 | # If the target cluster is openshift then make the appropriate additional login with oc tool 128 | if which oc > /dev/null && jq -e '.type=="openshift"' "${IBMCLOUD_IKS_CLUSTER_NAME}.json" > /dev/null; then 129 | echo "${IBMCLOUD_IKS_CLUSTER_NAME} is an openshift cluster. Doing the appropriate oc login to target it" 130 | oc login -u apikey -p "${IBMCLOUD_API_KEY}" 131 | fi 132 | 133 | set_env IBM_CLOUD_REGION "${IBM_CLOUD_REGION}" 134 | set_env IBM_CLOUD_RESOURCE_GROUP "${IBM_CLOUD_RESOURCE_GROUP}" 135 | set_env REGISTRY_NAMESPACE "${REGISTRY_NAMESPACE}" 136 | set_env REGISTRY_TAG "${REGISTRY_TAG}" 137 | set_env REGISTRY_URL "${REGISTRY_URL}" 138 | set_env REGISTRY_SECRET_NAME "${REGISTRY_SECRET_NAME}" 139 | set_env IMAGES_NAME_BACKEND "${IMAGES_NAME_BACKEND}" 140 | set_env IMAGES_NAME_FRONTEND "${IMAGES_NAME_FRONTEND}" 141 | set_env APPID_SERVICE_INSTANCE_NAME "${APPID_SERVICE_INSTANCE_NAME}" 142 | set_env APPID_SERVICE_KEY_NAME "${APPID_SERVICE_KEY_NAME}" 143 | set_env POSTGRES_SERVICE_INSTANCE "${POSTGRES_SERVICE_INSTANCE}" 144 | set_env POSTGRES_SERVICE_KEY_NAME "${POSTGRES_SERVICE_KEY_NAME}" 145 | set_env POSTGRES_SQL_FILE "${POSTGRES_SQL_FILE}" 146 | set_env APPLICATION_CONTAINER_NAME_BACKEND "${APPLICATION_CONTAINER_NAME_BACKEND}" 147 | set_env APPLICATION_CONTAINER_NAME_FRONTEND "${APPLICATION_CONTAINER_NAME_FRONTEND}" 148 | set_env APPLICATION_CATEGORY "${APPLICATION_CATEGORY}" 149 | set_env CODE_ENGINE_PROJECT_NAME "${CODE_ENGINE_PROJECT_NAME}" 150 | set_env IBM_KUBERNETES_SERVICE_NAME "${IBM_KUBERNETES_SERVICE_NAME}" 151 | set_env IBM_KUBERNETES_SERVICE_NAMESPACE "${IBM_KUBERNETES_SERVICE_NAMESPACE}" 152 | set_env IBM_OPENSHIFT_SERVICE_NAME "${IBM_OPENSHIFT_SERVICE_NAME}" 153 | set_env IBM_OPENSHIFT_SERVICE_NAMESPACE "${IBM_OPENSHIFT_SERVICE_NAMESPACE}" 154 | set_env PLATFORM_NAME "${PLATFORM_NAME}" -------------------------------------------------------------------------------- /cd-scripts/doi-publish-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | export IBM_CLOUD_API 4 | export IBMCLOUD_API_KEY 5 | export BREAK_GLASS 6 | export DEPLOYMENT_DELTA 7 | 8 | if [ -f /config/api-key ]; then 9 | IBMCLOUD_API_KEY="$(cat /config/api-key)" # pragma: allowlist secret 10 | else 11 | IBMCLOUD_API_KEY="$(cat /config/ibmcloud-api-key)" # pragma: allowlist secret 12 | fi 13 | 14 | BREAK_GLASS=$(cat /config/break_glass || echo false) 15 | 16 | if [[ "$BREAK_GLASS" != "false" ]]; then 17 | echo "Break-Glass mode is on, skipping the rest of the task..." 18 | exit 0 19 | fi 20 | 21 | IBM_CLOUD_API="$(cat /config/ibmcloud-api || echo "https://cloud.ibm.com")" 22 | ########################################################################## 23 | # Setting HOME explicitly to have ibmcloud plugins available 24 | # doing the export rather than env definition is a workaround 25 | # until https://github.com/tektoncd/pipeline/issues/1836 is fixed 26 | export HOME="/root" 27 | ########################################################################## 28 | if [[ "$IBM_CLOUD_API" == *test* ]]; then 29 | export IBM_CLOUD_DEVOPS_ENV=dev 30 | fi 31 | 32 | TOOLCHAIN_ID=$(cat /config/doi-toolchain-id) 33 | CURRENT_TOOLCHAIN_ID=$(jq -r '.toolchain_guid' /toolchain/toolchain.json) 34 | DOI_IN_TOOLCHAIN=$(jq -e '[.services[] | select(.service_id=="draservicebroker")] | length' /toolchain/toolchain.json) 35 | DOI_ENVIRONMENT=$(cat /config/doi-environment 2> /dev/null || echo "") 36 | ENVIRONMENT=$(cat /config/environment 2> /dev/null || echo "") 37 | DEPLOYMENT_DELTA_PATH="$(cat /config/deployment-delta-path)" 38 | DEPLOYMENT_DELTA=$(cat "${DEPLOYMENT_DELTA_PATH}") 39 | JOB_URL=$(cat /config/job-url) 40 | INVENTORY_PATH="$(cat /config/inventory-path)" 41 | 42 | if [ "$DOI_IN_TOOLCHAIN" == 0 ]; then 43 | if [ -z "$TOOLCHAIN_ID" ] || [ "$CURRENT_TOOLCHAIN_ID" == "$TOOLCHAIN_ID" ]; then 44 | echo "No Devops Insights integration found in toolchain. Skipping ..." 45 | exit 0 46 | fi 47 | fi 48 | 49 | if [ "$DEPLOY_EXIT" -eq 0 ]; then 50 | DEPLOY_STATUS="pass" 51 | else 52 | DEPLOY_STATUS="fail" 53 | fi 54 | 55 | # Default Toolchain ID if needed 56 | if [ -z "$TOOLCHAIN_ID" ]; then 57 | TOOLCHAIN_ID="$CURRENT_TOOLCHAIN_ID" 58 | fi 59 | 60 | # Default Job URL if needed 61 | if [ -z "$JOB_URL" ]; then 62 | JOB_URL="$PIPELINE_RUN_URL" 63 | fi 64 | 65 | export TOOLCHAIN_ID=${TOOLCHAIN_ID} # for doi plugin 66 | 67 | if [ "$DOI_ENVIRONMENT" ]; then 68 | ENVIRONMENT="$DOI_ENVIRONMENT" 69 | fi 70 | 71 | ibmcloud login --apikey "${IBMCLOUD_API_KEY}" -a "${IBM_CLOUD_API}" --no-region 72 | 73 | 74 | declare -A app_hash_map 75 | for INVENTORY_ENTRY in $(echo "${DEPLOYMENT_DELTA}" | jq -r '.[] '); do 76 | APP=$(cat "${INVENTORY_PATH}/${INVENTORY_ENTRY}") 77 | BUILD_NUMBER=$(echo "${APP}" | jq -r '.build_number') 78 | 79 | APP_REPO=$(echo "${APP}" | jq -r '.repository_url') 80 | APP_REPO=$(echo -n "${APP_REPO}" | sed 's:/*$::') 81 | APP_NAME=$(echo "${APP_REPO}" | cut -f5 -d/) 82 | 83 | if [ ! "${app_hash_map[$APP_NAME]}" ] ; then 84 | ibmcloud doi publishdeployrecord \ 85 | --env "${ENVIRONMENT}" \ 86 | --status="${DEPLOY_STATUS}" \ 87 | --joburl="${JOB_URL}" \ 88 | --buildnumber="${BUILD_NUMBER}" \ 89 | --logicalappname="${APP_NAME}" 90 | fi 91 | app_hash_map[$APP_NAME]=1 92 | done 93 | -------------------------------------------------------------------------------- /cd-scripts/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | echo "This is a setup stage. You can add your custom settings in this stage." 6 | 7 | echo "scripts/setup.sh" 8 | cd .. 9 | 10 | export BACKEND=$(get_env "multi-tenancy-backend") 11 | echo $BACKEND 12 | git clone $BACKEND 13 | save_repo multi-tenancy-backend "url=${BACKEND}" 14 | 15 | export FRONTEND=$(get_env "multi-tenancy-frontend") 16 | echo $FRONTEND 17 | git clone $FRONTEND 18 | save_repo multi-tenancy-frontend "url=${FRONTEND}" 19 | 20 | #export PARENT=$(get_env "multi-tenancy") 21 | #echo $PARENT 22 | #git clone $PARENT 23 | #save_repo multi-tenancy "url=${PARENT}" 24 | -------------------------------------------------------------------------------- /configuration/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "IBM_CLOUD": { 3 | "RESOURCE_GROUP": "default", 4 | "REGION": "eu-de" 5 | }, 6 | "REGISTRY": { 7 | "URL": "de.icr.io", 8 | "NAMESPACE": "multi-tenancy-example", 9 | "TAG": "v2", 10 | "SECRET_NAME": "multi.tenancy.cr.sec" 11 | }, 12 | "IMAGES": { 13 | "NAME_BACKEND": "multi-tenancy-service-backend", 14 | "NAME_FRONTEND": "multi-tenancy-service-frontend" 15 | } 16 | } -------------------------------------------------------------------------------- /configuration/tenants/tenant-a.json: -------------------------------------------------------------------------------- 1 | { 2 | "APP_ID": { 3 | "SERVICE_INSTANCE": "multi-tenancy-serverless-appid-a", 4 | "SERVICE_KEY_NAME": "multi-tenancy-serverless-appid-key-a" 5 | }, 6 | "POSTGRES": { 7 | "SERVICE_INSTANCE": "multi-tenancy-serverless-pg-ten-a", 8 | "SERVICE_KEY_NAME": "multi-tenancy-serverless-pg-ten-a-key", 9 | "SQL_FILE": "create-populate-tenant-a.sql" 10 | }, 11 | "APPLICATION": { 12 | "CONTAINER_NAME_BACKEND": "multi-tenancy-service-backend-movies", 13 | "CONTAINER_NAME_FRONTEND": "multi-tenancy-service-frontend-movies", 14 | "CATEGORY": "Movies" 15 | }, 16 | "CODE_ENGINE": { 17 | "PROJECT_NAME": "multi-tenancy-serverless-a" 18 | }, 19 | "IBM_KUBERNETES_SERVICE": { 20 | "NAME": "niklas-heidloff3-fra04-b3c.4x16", 21 | "NAMESPACE": "tenant-a" 22 | }, 23 | "IBM_OPENSHIFT_SERVICE": { 24 | "NAME": "roks-gen2-suedbro", 25 | "NAMESPACE": "tenant-a" 26 | }, 27 | "PLATFORM": { 28 | "NAME": "IBM_OPENSHIFT_SERVICE" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /configuration/tenants/tenant-b.json: -------------------------------------------------------------------------------- 1 | { 2 | "APP_ID": { 3 | "SERVICE_INSTANCE": "multi-tenancy-serverless-appid-b", 4 | "SERVICE_KEY_NAME": "multi-tenancy-serverless-appid-key-b" 5 | }, 6 | "POSTGRES": { 7 | "SERVICE_INSTANCE": "multi-tenancy-serverless-pg-ten-b", 8 | "SERVICE_KEY_NAME": "multi-tenancy-serverless-pg-ten-b-key", 9 | "SQL_FILE": "create-populate-tenant-b.sql" 10 | }, 11 | "APPLICATION": { 12 | "CONTAINER_NAME_BACKEND": "multi-tenancy-service-backend-fantasy", 13 | "CONTAINER_NAME_FRONTEND": "multi-tenancy-service-frontend-fantasy", 14 | "CATEGORY": "Fantasy" 15 | }, 16 | "CODE_ENGINE": { 17 | "PROJECT_NAME": "multi-tenancy-serverless-b" 18 | }, 19 | "IBM_KUBERNETES_SERVICE": { 20 | "NAME": "niklas-heidloff3-fra04-b3c.4x16", 21 | "NAMESPACE": "tenant-b" 22 | }, 23 | "IBM_OPENSHIFT_SERVICE": { 24 | "NAME": "roks-gen2-suedbro", 25 | "NAMESPACE": "tenant-b" 26 | }, 27 | "PLATFORM": { 28 | "NAME": "IBM_KUBERNETES_SERVICE" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /documentation/Options-Simple.drawio: -------------------------------------------------------------------------------- 1 | 7Z1rc9o4F8c/TWZ2X6Tj++Ull2zSTbPJhvbp9tWOggW4NRZri5Dk0z8ytsCWBCEEyyYo00mxbBxZ56cj6S/56MzsTZ8uEzCb3KAARmeGFjydmf0zwzA00yX/ZSnPeYquu36eMk7CIE/T1gmD8AUWF9LUeRjAtEjLkzBCEQ5n1cQhimM4xJU0kCRoUb1shKKgkjADY8glDIYg4lO/hwGe5KmW45VOXMFwPCn+tGlqRc6ngF5dJKQTEKBFKcm8ODN7CUI4/zR96sEoKz5aMN+u/x3Nbi60Jzu4fepfe3B41T3Pb/bHW76yymoCY7z3rdGvtNsfXd2ayTR+ueh0R1fG87mT3/oRRPOiwL7C4SRGERqHxGz5c+NnWprpIpxGICZH3RGK8aA4o5Hj4SSMgi/gGc2zLKYYDH/Ro+4EJeELuR5E5JROEsjpBBewGE7likH2zeKeCUzJNXf0uXUm6QY8VS78AlJMc4OiCMzS8GGZv+yLU5CMw7iLMEbT4qLlI+SZ0LNMjMIo6pFHT0hCjJZPuWPJFxZ6hAmGTyXwCktcQjSFOHkmlxRnHa+AqqhXjmPnx4s1pIZdXDMp8WloXlE5iooxXt17bXvyoTD/G1BwORTuYUASrgAmv29nMB5MwhHmmCBPjJcmTdAvyBSfoERBFI5jchjBUfa1rMhCUl07RTJGs+xmMzAM4/GX5TV9a51yX5REloTId0fRskZOwiCAcQYCwgCDhxWVMxTGeFlSdpf8IwXa0z7ZZzbJeI8c6+tj8i+7PME9FJNnAeHS4JAgtYAZVhwtPBpba9jraBQoGM6OJDjvB+Hu7vqqp9v+X1cXd1eDf56//1zgFcxrED53b0hCHz4O4PCWOO6ecdbRvhI3PpyQclJE1EyEbcgjQphjfwMRdyjF4yRrJ7Qzt1dJUEjUioTrNYwE7d9xTHRmM/L7c3/FxDV8HkYI/FJM1MyErllNQ7Gp6biH4zDN/sKy5ci5KPUotN69oqNuOkytaToMwWAjScAIJVNl/rrNb0vsVorNb3Lmv0EJ+dTJnMFDCpNH8BBGIc5yDeJg6SJUT6J2MDyJvUvh4JnnYgCn4XmYogjg5RC0h6azOYYcCwmaxwHM7p2N8BeTEMMBMVx2dpGAzLITPKWaw7bR/plhBjb0Aosji5zxjAfTEZvgMGKAbhW9Odrf9/iGXDcEJnG0mkxi8SYhpohI5azXCCMNmBoQGaHbdzWNijaldG35U6dxNN2qWMcydd461IKHts6X86j/cPnXy+Xdz4Xx7cfl6PzCFFSYzvcB06P67f520Pn95Ixl+N7rxqLjaSnG4qtS3iXuRWge1Gqfbqfn9U2RfZzlD2+f/KdG+1hO1TyGJ6pLvkTz2ILGJyEPFMGU9D2cKGuKHxLyaZx9GoTTWVSvC3Q04OuuyGpG33WaqVW+/slmqpXP282pqYUS2o2fuBhMyINnvYXNwuQH6S+s1J8m+gtCa/BzBzfzCIfnJ+nkdIvpMQi9nNTa4nH2uY3P7xLSzU4FYq0c+6zclnT7sD06oX1sXaJ9BMLZyp31onmKYfJRnRlbWZp3ZgKdihqD7Q+sp8ROoNmxLdZSZtOW4kdCq9qizZa/v8IYxPyk9QcxCbuMoAUmEUgH1JOdVG3xdNY0jfs1fqzDFT6Mg0626upsJaBuKWxSYsnzP+Qo0zSLwx/FtcuDPl2fkx8906OnEJe+Ro5+lM6sv5Qd0O+8zUwpmidDuKUsiqLAIBlDvOW6wmYwqKwy441eMqpIO6VpCYwADh/L9xIbuvgLd5meXKrujl9hyrUYVvLnLr61xoW7katXb+T4zI3yguFutORu9djvQJEfvp0Cipr79J82xunc/dudp/9C3eheF6v2jg1F27Y4TYC5ya4wOloVRsuTDCM/OjoFGLfNxbwKo90qGE3Gndn7+kXHYG6kSUaRX2l1CihuU/iODEXjtZZ1VxQNn0FRslek5CkU1/JMmcVtbXlbWKRrPmh30diTRUFj7zO5qZtGfqR/ujQaR0mj5VVp3NsztoBGXuQ4XRp37TJSkb0tOPqMc2S7ervi6GkM17rkhlrJOpz6eGwwuvaBfGPjMKoBDLtM61UW3Vah6FP9mfpF6uDfiqKuGdU7OR5zp5pZpHpUs7NDIxC4YCiagniwHFuzapyC0A2LHUM2PQdh8qPK0vTQLELPU7iESMasd5OmMd3WmUYNsTjX8arvNlvlu3XNZ5y3ua/zNplmQHZHwlRDLM5pHhuNJtMBcLWjpZEfY51WV8J3TKa5anqhibl5BXDzq7Sa7FlofutMxS8Pfqcv38Mn7+v/D+3LBdNa217Baosv121m8R/tv77Zl+vswiiqpsry5Qef7j9iGgXroI6CRqdKo7M3jZrH3EmTTONpCmbbIHtdvG0VjK7PErTvojzmRjbbINeMosULACoYnIxgcOz73w71buVX8i2XJ/ggweDELGx8bRUFkPx3EZPy41+EVMEZ4m2Oa1XD3hUMTkjCAYIziLPMj3xzEFT4N2kQiOK/yYWAH2uzAeAUA/UyIAr4JpcBftjEBnxTDNTLgDDAm1QIvM3dAtJdIz2CTAldBXtTQNQNhCimm1wg+O7BbfyAQBIQE2TWHybhDGfCK8rQ+PZZQVE7FKJIb3VBIVx3yq+7UIHe2gCGKNKbVG9BO7JKYZCsMLBBFoQKQ23h5sUs8GrT9fwBJjHEajzxVmewqljHE2V+U/xoFWW+KSRkhpkXZ3mT1qTizDcGhcxA8+Isb9KelO4giwGpgeWFefb5jqPSHRoEQmYseXGe+SVFKpa8PPvLDCYvdgiCiJNKY2gBGTKjyYvzzM9TKI1ByioG9m2jNogM/OoqtandIRyDtzMdDegN4haDn7oqo9AJHkE8rAZc1W4+KTDqBqNx1cHhpyvy7RSU6tAYFI2rDg4/4Myh6MzxhDwtsRcOUbxCQ21yJwuN5sUIh5+5yNlQYkQjQDQuRvi8bq3ECHn2b16M4EcZSoxoAxmNixEOP+hQYoQMMYJ9X9lx+JdgJWsRDt9KKC3iAH5hVcdaqUUI1z5smudWax+aQqJ5FWLTNHdp403lI+QB0bwCwc9w8UAoRUoWDy2QHV5/AUeJUbKpaFx7cPixp1oZ0yAQzYsRvJtQYkQbyGhcjHD5Jdedl3nCdCy13zr3t6e3cbjtGZv2MyrHzaLhWuTsqnuakb3fFRumXcGUdddjNxRiwgvtHKiI3T7Bov5EUnQYGjG9/Kbn2muwIfeKjZ+1OC897jzpkcxJyS71r89xMCeeQKSHfvCN1V3DZVyOIAgw7VHI2Sj64EGAjzc4GiX+yHbm0zVmCyrLYu6x78Z8pstgV7PL8XlN7BTav62dt2NrAFfMvBdG7k5mfRuibdtNacP7qq81gKfVsFl0UV0TDZvQegZnvdMK4KzbTF9DalhgoUn4rsZpbgXh2G2zjFq10NArFGwllbtsQciCWrXwXgFxaxVr5aIFYY75zrh6f6J5LGQuXBDmmJ+mpqKyen+iKShkLl4Q5pifg6JQqPcnmkVD6joGYZb5CWvKhpqybgIImUsYxOECeWVFvT4hzfwyFyyIza/CRbYSDJnrFcRg8JodZ/UPKP9v08peV/8LA7VF/ve86tYWNg37+Gb53zOqb3RY9e2OIqbxNLdH3SamHx2Nll2d0OQ2e9p9Mordq0dncnM4Gn92/ndvnF8sOv95f/yJYfTDfMQC7fxmHuHwnE47DQBYvq2eppCX5hi9VjQxxMngqzOCtpQlexyBNC3Io3/rS7ZdTA73hpZoVz43q6da1dcYdDP6so7uaoKAx47zdvWUHCYI4bJZEzCb3KAAZlf8Hw== -------------------------------------------------------------------------------- /documentation/Options-Simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/multi-tenancy/ef6dc9ebfaec0e8041964ec0407eb648ef35ac17/documentation/Options-Simple.png -------------------------------------------------------------------------------- /documentation/SaaS-Options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/multi-tenancy/ef6dc9ebfaec0e8041964ec0407eb648ef35ac17/documentation/SaaS-Options.png -------------------------------------------------------------------------------- /documentation/announcement-blog-template.md: -------------------------------------------------------------------------------- 1 | * One or two sentence(s) sub-title 2 | * Intro one or two paragraphs describing the 'why' with focus on multi-cloud 3 | * Status: IBM Cloud done, other platforms to be done, more documentation to be done 4 | * Team effort/authors 5 | * Introduction 6 | * [Input](https://github.com/IBM/multi-tenancy#introduction) 7 | * Plus personas: Dev, Ops, Sec 8 | * Key messages 9 | * Key values of this asset: 1. Starting point for SaaS, 2. best practices, 3. shared CI/CD 10 | * Code, images and CI/CD are shared 11 | * Isolated workloads at runtime for security reasons 12 | * Support for multiple target platforms/multi-cloud 13 | * Screenshot 1: [Diagram](https://github.com/IBM/multi-tenancy/raw/main/documentation/SaaS-Options.png) 14 | * IBM Cloud 15 | * Serverless 16 | * IKS 17 | * ROKS for FS-compliance 18 | * On-premises 19 | * Other public clouds: AWS, Azure 20 | * Sample e-commerce application 21 | * Screenshot 2: (remove the refresh symbol and use readable title) 22 | * Scenario: one tenant selling books, another one selling shoes 23 | * Full end-to-end application including authentication and persistence 24 | * Sample can be used as starting point 25 | * Value of managed services 26 | * ROKS, IKS and Code Engine for compute 27 | * Postgres, AppID, Container Registry, Toolchain 28 | * Replaceable 29 | * Automation first 30 | * First time experience via scripts 31 | * Terraform scripts to create clusters 32 | * Onboarding scripts and configuration 33 | * CI/CD pipeline for serverless 34 | * CI/CD pipelines for cloud-native 35 | * Regulated industries 36 | * Value proposition of IBM Cloud 37 | * Usage of approved services and OpenShift is required 38 | * Based on [IBM's DevSecOps reference implementation](https://www.ibm.com/cloud/blog/announcements/devsecops-reference-implementation-for-audit-ready-compliance-across-development-teams#:~:text=The%20reference%20implementation%20of%20DevSecOps,manual%20overrides%20for%20exceptional%20situations) 39 | * Examples: don't push to main; detect secrets; detect vulernabilities - shift left 40 | * Next 41 | * More documentation 42 | * Other clouds including cloud specific services 43 | * On-premises via Satellite 44 | * Call to action 45 | * Try serverless getting started 46 | * Provide feedback 47 | 48 | 49 | 50 | 51 | Other notes: 52 | 53 | * Pull Request (PR) Pipeline: This is typically triggered by a developer when they want to conribute source code from their own branch, by creating a merge or pull request (PR) in the application repository. The pipeline runs unit test and static scans on the source code and second developer must approve the PR. 54 | * Continuous Integration (CI) Pipeline: When a change is merged to the main branch of the application repository, the CI Pipeline runs tests on the source code and deployment manifests to detect secrets or security risks, as well as scanning the container images for vulnerabilities. Unit and code coverage tests can also be incorporated. The CI pipeline builds the binary artifacts (containers), uploads them to the IBM Container Registry and deploys them to the runtime environment as an integration test. The CI Pipeline also generates metadata about the build artifacts and stores this in another repository, for purposes of compliance and audit. 55 | * Promotion (CD) Pipeline: This is manually triggered to create a new merge / PR to push the latest code changes from the source (main) branch to the target branch of a particular tenant. 56 | * Continuous Deployment (CD) Pipeline: The CD pipeline is used to deploy the application to the production environments of specific tenant (i.e. a tenant specifc namespace in a Kubernetes cluster). For compliance reasons it needs to be triggered manually, after the merge is completed from the previous Promotion Pipeline. 57 | -------------------------------------------------------------------------------- /documentation/announcement-blog.md: -------------------------------------------------------------------------------- 1 | ## New Open-Source Multi-Cloud Asset to build SaaS 2 | Development and automated deployment of SaaS for multiple tenants, using Red Hat OpenShift/Kubernetes and DevSecOps 3 | 4 | ### Introduction 5 | 6 | When software is provided as a managed service (SaaS), using a multi-tenant approach helps minimise costs for the deployments and operations of each tenant. In order to leverage these advantages, applications need to be designed so that they can be deployed to support multiple tenants, while maintaining isolation for security reasons. At the same time, common deployment and operation models are required so that new SaaS versions can be deployed to existing tenants, or to onboard new tenants, in a reliable and efficient way. 7 | 8 | A new [open-source project](https://github.com/IBM/multi-tenancy) from IBM developers aims to support a DevSecOps approach to building multi-tenant SaaS for different platforms including Kubernetes, Red Hat OpenShift, IBM Code Engine (serverless), IBM Satellite, and public clouds including IBM Cloud, AWS and Azure. The asset includes DevSecOps toolchains to automate the process for deploying a sample cloud native application to a common platform and create per-tenant cloud services for persistence and authentication. 9 | 10 | The initial release uses Continuous Integration and Continuous Delivery (CI/CD) toolchains on IBM Cloud to deploy SaaS to IBM Code Engine, Red Hat OpenShift on IBM Cloud, or IBM Kubernetes Service on IBM Cloud. We have plans to extend the deployment to support multi-cloud using Red Hat OpenShift on AWS (ROSA) and Azure Red Hat OpenShift (ARO). 11 | 12 | This asset has been created by IBM's Build Labs: 13 | 14 | * [Adam de Leeuw](https://www.linkedin.com/in/deleeuwa/) 15 | * [Alain Airom]() 16 | * [Niklas Heidloff](https://twitter.com/nheidloff) 17 | * [Thomas Suedbroecker]() 18 | 19 | ### Challenges 20 | 21 | Organisations offering SaaS will need to deliver rapidly and often, while maintaining a strong security posture and continuous state of audit-readiness. Achieving this goal involves several teams including developers, IT operations and security. DevSecOps integrates a set of security and compliance controls and makes application and infrastructure security a shared responsibility for all these teams, and automatically bakes in security at every phase of the software development lifecycle, bringing speed and security. This is of particular importance for SaaS where the challenges and benefits are a factor of the number of tenants! 22 | 23 | The project provides a starting point for learning how to create an application which is ready for SaaS, using best practices for DevSecOps such as detecting code or container vulnerabilities, and using CI/CD pipelines to automate deployments. The key value of this asset is that it shows how to reuse the same application code, containers, and CI/CD, with the flexibility to deploy to a dedicated or shared Kubernetes cluster, while maintaining isolation and security. 24 | 25 | ### Support for multiple platforms 26 | 27 | The following diagram shows the different deployment platform options, currently including several alternatives for IBM Cloud, as indicated by the green boxes. The orange boxes represent planned future developments, including the addition of IBM Cloud Satellite which will allow the SaaS application to be deployed on-premises at client data centers, while still taking advantage of an OpenShift cluster managed by IBM Cloud. Additionally, the same SaaS application could be deployed to other managed OpenShift services like AWS ROSA and Azure ARO. 28 | 29 | The easiest way to get started is with serverless, using the fully managed IBM Code Engine platform to run the application. For more advanced cloud-native applications, a dedicated Kubernetes or OpenShift cluster can be used. Compute isolation can be achieved with a shared cluster using Kubernetes namespaces/OpenShift projects, or by having dedicated clusters for each SaaS tenant. 30 | 31 | 32 | 33 | Core technologies used: 34 | 35 | * Kubernetes using either IBM Kubernetes Service or OpenShift on IBM Cloud 36 | * IBM Code Engine (serverless) 37 | * IBM Continuous Delivery CI/CD pipelines using Tekton 38 | * IBM Cloud Databases for PostgreSQL 39 | * IBM App ID 40 | * IBM Container Registry 41 | * Terraform 42 | 43 | ### Sample e-commerce application 44 | 45 | A sample e-commerce application is provided, which is deployed as two containers. A frontend web application displays a catalogue of products. The data for the catalogue is provided by a backend microservice. Configuration properties are used extensively to customise both the frontend and backend at deployment time, including titles, connection details for the PostgreSQL database and authentication service etc. This means the same e-commerce sample application can easily be used for multiple tenants, perhaps one selling books, the other shoes. 46 | 47 | ### Automation first 48 | 49 | Everything in this project embraces automation and a series of approaches to deploy SaaS are provided, each with an increasing degree of capability. You are able to start with any of the following approaches: 50 | 51 | * Simple bash scripts to create and deploy the sample application container images, and the PostgreSQL and AppID cloud services. 52 | * A simple DevOps toolchain with CI/CD pipelines which deploys to IBM Code Engine. The pipelines orchestrate build, test, and deployment jobs (optionally across multiple environments) as changes progress from the developer to production. 53 | * A more comprehensive DevSecOps toolchain which deploys to a Kubernetes cluster. This brings a more robust process where the CI/CD pipelines ensure that code is scanned for security vulnerabilities (e.g. secrets or credentials), and repository branch protection prevents a developer from directly updating the main branch without first issuing a pull/merge request to be approved by a second developer. In addition, the container images are scanned for vulnerabilities, a dynamic application security testing tool looks for vulnerabilities in the deployed application, and application acceptance tests all contribute to a secure and quality assured release. 54 | 55 | Any of these approaches are ready to deploy the multiple tenancies of a SaaS application. Simply change the externalised properties and re-run the script or trigger the pipelines. 56 | 57 | For deployments to IBM Kubernetes Service or OpenShift, terraform templates are also provided to automate the cluster creation on IBM Cloud. 58 | 59 | ### Ready for regulated industries 60 | 61 | Regulated industries such as financial institutions, insurance, healthcare and more, all want the advantages of a hybrid cloud, but need assurance they can protect their assets and maintain compliance with industry and regulatory requirements. The key to hosting regulated workloads in the cloud is to eliminate and mitigate the risks that might be standing in the way of progress. In regulated industries, critical risks fall into the general categories of compliance, cybersecurity, governance, business continuity and resilience. 62 | 63 | The DevSecOps approach of our CI/CD pipelines are based an [IBM DevSecOps reference architecture](https://www.ibm.com/cloud/blog/announcements/devsecops-reference-implementation-for-audit-ready-compliance-across-development-teams), helping to address some of the risks faced by regulated industries. The CI/CD pipelines include steps to collect and upload deployment log files, artifacts, and evidence to a secure evidence locker. In addition, a toolchain integration to [IBM Security and Compliance Center](https://cloud.ibm.com/docs/devsecops?topic=ContinuousDelivery-scc) verifies the security and compliance posture of the toolchain by identifying the location of the evidence locker, and the presence of the evidence information. 64 | 65 | ### What's next? 66 | 67 | Our project is constantly evolving. You can expect more supported platforms including IBM Cloud Satellite and other public clouds including support for their native database and authentication services. We still have some work to do on the documentation, e.g. explaining how to observe multi-tenant runtime logs, and understand how much cloud resource each tenant is consuming, to help calculate the bills. 68 | 69 | In the meantime, we invite you to explore the [repo](https://github.com/IBM/multi-tenancy) and give it a try. Why not start by using our most simple script-based approach with IBM Code Engine, and see for yourself how easy it is is to be a SaaS provider! We would also be happy to work together with you on using this asset to build your SaaS. 70 | 71 | If you have feedback or comments, please don't hesitate to get in touch via our social media links above. 72 | -------------------------------------------------------------------------------- /documentation/diagrams/multi-tenant-app-architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/multi-tenancy/ef6dc9ebfaec0e8041964ec0407eb648ef35ac17/documentation/diagrams/multi-tenant-app-architecture.jpg -------------------------------------------------------------------------------- /documentation/diagrams/multi-tenant-app-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/multi-tenancy/ef6dc9ebfaec0e8041964ec0407eb648ef35ac17/documentation/diagrams/multi-tenant-app-architecture.png -------------------------------------------------------------------------------- /documentation/example-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/multi-tenancy/ef6dc9ebfaec0e8041964ec0407eb648ef35ac17/documentation/example-app.png -------------------------------------------------------------------------------- /documentation/images/IKS-Infrastructure-by-Terraform-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/multi-tenancy/ef6dc9ebfaec0e8041964ec0407eb648ef35ac17/documentation/images/IKS-Infrastructure-by-Terraform-1.png -------------------------------------------------------------------------------- /documentation/images/Multi-tenancy-serverless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/multi-tenancy/ef6dc9ebfaec0e8041964ec0407eb648ef35ac17/documentation/images/Multi-tenancy-serverless.png -------------------------------------------------------------------------------- /documentation/images/VPC-Infrastructure-by-Terraform-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/multi-tenancy/ef6dc9ebfaec0e8041964ec0407eb648ef35ac17/documentation/images/VPC-Infrastructure-by-Terraform-1.png -------------------------------------------------------------------------------- /documentation/images/VPC-Infrastructure-by-Terraform-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/multi-tenancy/ef6dc9ebfaec0e8041964ec0407eb648ef35ac17/documentation/images/VPC-Infrastructure-by-Terraform-2.png -------------------------------------------------------------------------------- /documentation/images/VPC-Infrastructure-by-Terraform-Subnets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/multi-tenancy/ef6dc9ebfaec0e8041964ec0407eb648ef35ac17/documentation/images/VPC-Infrastructure-by-Terraform-Subnets.png -------------------------------------------------------------------------------- /documentation/images/button-documentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/multi-tenancy/ef6dc9ebfaec0e8041964ec0407eb648ef35ac17/documentation/images/button-documentation.png -------------------------------------------------------------------------------- /installapp/appid-configs/add-application-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FRONTENDNAME", 3 | "type": "singlepageapp" 4 | } -------------------------------------------------------------------------------- /installapp/appid-configs/add-redirecturis-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "redirectUris": [ 3 | "http://localhost:8080/*", 4 | "APPLICATION_REDIRECT_URL/*" 5 | ], 6 | "additionalProp1": {} 7 | } -------------------------------------------------------------------------------- /installapp/appid-configs/add-roles-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tenant_user_access", 3 | "description": "This is an example role.", 4 | "access": [ 5 | { 6 | "application_id": "APPLICATIONID", 7 | "scopes": [ 8 | "tenant_scope" 9 | ] 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /installapp/appid-configs/add-scope.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopes": [ 3 | "tenant_scope" 4 | ] 5 | } -------------------------------------------------------------------------------- /installapp/appid-configs/add-ui-color.json: -------------------------------------------------------------------------------- 1 | { 2 | "headerColor": "#008b8b" 3 | } -------------------------------------------------------------------------------- /installapp/appid-configs/add-ui-text-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabTitle": "Login to FRONTENDNAME", 3 | "footnote": "Powered by the EMEA - Hybrid Cloud Build Team" 4 | } -------------------------------------------------------------------------------- /installapp/appid-configs/idps-clouddirectory.json: -------------------------------------------------------------------------------- 1 | { 2 | "isActive": true, 3 | "config": { 4 | "selfServiceEnabled": true, 5 | "signupEnabled": true, 6 | "interactions": { 7 | "identityConfirmation": { 8 | "accessMode": "FULL", 9 | "methods": [ 10 | "email" 11 | ] 12 | }, 13 | "welcomeEnabled": false, 14 | "resetPasswordEnabled": false, 15 | "resetPasswordNotificationEnable": true 16 | }, 17 | "identityField": "email" 18 | } 19 | } -------------------------------------------------------------------------------- /installapp/appid-configs/idps-custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "isActive": false 3 | } -------------------------------------------------------------------------------- /installapp/appid-configs/idps-facebook.json: -------------------------------------------------------------------------------- 1 | { 2 | "isActive": false 3 | } -------------------------------------------------------------------------------- /installapp/appid-configs/idps-google.json: -------------------------------------------------------------------------------- 1 | { 2 | "isActive": false 3 | } -------------------------------------------------------------------------------- /installapp/appid-configs/idps-saml.json: -------------------------------------------------------------------------------- 1 | { 2 | "isActive": false 3 | } -------------------------------------------------------------------------------- /installapp/appid-configs/user-export.json: -------------------------------------------------------------------------------- 1 | {"itemsPerPage":1,"totalResults":1,"users":[{"scimUser":{"originalId":"7cdf7ac3-371f-4b4c-8d0a-81e479ab449b","name":{"givenName":"Thomas","familyName":"Example","formatted":"Thomas Example"},"displayName":"Thomas Example","active":true,"emails":[{"value":"thomas@example.com","primary":true}],"passwordHistory":[{"passwordHash":"L6EEYnQANBPSBF0tDCPDZl4uVD07H3Ur8qIVynB1Ht4Bn4s/x0lA6kvyJxEPr/06m5hi5wdLM45JtYDlT8M0hjVIBI3YpXRR9J4oXZA/Yt/V13yjsUPsXKek6RWdOKWp+wuD5w3Bobh43QbRR3dXFoKUbcLVWQoKLWqvRATMQis=","hashAlgorithm":"PBKDF2WithHmacSHA512"}],"status":"CONFIRMED","passwordExpirationTimestamp":0,"passwordUpdatedTimestamp":0,"mfaContext":{}},"passwordHash":"L6EEYnQANBPSBF0tDCPDZl4uVD07H3Ur8qIVynB1Ht4Bn4s/x0lA6kvyJxEPr/06m5hi5wdLM45JtYDlT8M0hjVIBI3YpXRR9J4oXZA/Yt/V13yjsUPsXKek6RWdOKWp+wuD5w3Bobh43QbRR3dXFoKUbcLVWQoKLWqvRATMQis=","passwordHashAlg":"PBKDF2WithHmacSHA512","profile":{"attributes":{}},"roles":["ce_user_access"]}]} 2 | -------------------------------------------------------------------------------- /installapp/appid-configs/user-import.json: -------------------------------------------------------------------------------- 1 | {"itemsPerPage":1,"totalResults":1,"users":[{"scimUser":{"originalId":"7cdf7ac3-371f-4b4c-8d0a-81e479ab449b","name":{"givenName":"Thomas","familyName":"Example","formatted":"Thomas Example"},"displayName":"Thomas Example","active":true,"emails":[{"value":"thomas@example.com","primary":true}],"passwordHistory":[{"passwordHash":"L6EEYnQANBPSBF0tDCPDZl4uVD07H3Ur8qIVynB1Ht4Bn4s/x0lA6kvyJxEPr/06m5hi5wdLM45JtYDlT8M0hjVIBI3YpXRR9J4oXZA/Yt/V13yjsUPsXKek6RWdOKWp+wuD5w3Bobh43QbRR3dXFoKUbcLVWQoKLWqvRATMQis=","hashAlgorithm":"PBKDF2WithHmacSHA512"}],"status":"CONFIRMED","passwordExpirationTimestamp":0,"passwordUpdatedTimestamp":0,"mfaContext":{}},"passwordHash":"L6EEYnQANBPSBF0tDCPDZl4uVD07H3Ur8qIVynB1Ht4Bn4s/x0lA6kvyJxEPr/06m5hi5wdLM45JtYDlT8M0hjVIBI3YpXRR9J4oXZA/Yt/V13yjsUPsXKek6RWdOKWp+wuD5w3Bobh43QbRR3dXFoKUbcLVWQoKLWqvRATMQis=","passwordHashAlg":"PBKDF2WithHmacSHA512","profile":{"attributes":{}},"roles":["tenant_user_access"]}]} 2 | -------------------------------------------------------------------------------- /installapp/appid-images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/multi-tenancy/ef6dc9ebfaec0e8041964ec0407eb648ef35ac17/installapp/appid-images/logo.png -------------------------------------------------------------------------------- /installapp/backup-bash-scripts/ce-build-images-quay-podman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # **************** Global variables 4 | export REPOSITORY=tsuedbroecker 5 | export SERVICE_CATALOG="multi-tenancy-service-catalog:v1" 6 | export FRONTEND="multi-tenancy-frontend:v1" 7 | #quay.io/tsuedbroecker/multi-tenancy-service-catalog:v1 8 | # ********************************************************************************** 9 | # Execution 10 | # ********************************************************************************** 11 | 12 | cd .. 13 | export ROOT_PATH=$(PWD) 14 | echo "Path: $ROOT_PATH" 15 | 16 | echo "************************************" 17 | echo " Clean up container if needed" 18 | echo "************************************" 19 | podman image list 20 | podman container list 21 | #podman container stop -f "TBD" 22 | #podman container rm -f "TBD" 23 | podman image prune -a -f 24 | podman version 25 | podman image rm -f "$SERVICE_CATALOG" 26 | podman image rm -f "$FRONTEND" 27 | # rm -rf ~/var/home/core/.local/share/containers/storage/overlay/* f 28 | #podman image rm -f "docker.io/adoptopenjdk/maven-openjdk11" 29 | #podman image rm -f "docker.io/adoptopenjdk/openjdk11-openj9:ubi-minimal" 30 | #podman image rm -f "registry.access.redhat.com/ubi8/ubi-minimal" 31 | 32 | echo "************************************" 33 | echo " Service catalog $SERVICE_CATALOG" 34 | echo "************************************" 35 | cd $ROOT_PATH/code/service-catalog-tmp 36 | pwd 37 | podman login quay.io 38 | podman build -t "quay.io/$REPOSITORY/$SERVICE_CATALOG" -f Dockerfile.simple-v1 . 39 | # podman push "quay.io/$REPOSITORY/$SERVICE_CATALOG" 40 | 41 | echo "" 42 | 43 | echo "************************************" 44 | echo " Frontend $FRONTEND" 45 | echo "************************************" 46 | cd $ROOT_PATH/code/frontend 47 | #pwd 48 | # podman login quay.io 49 | # podman build -t "quay.io/$REPOSITORY/$FRONTEND" -f Dockerfile.os4-webapp . 50 | # podman push "quay.io/$REPOSITORY/$FRONTEND" 51 | -------------------------------------------------------------------------------- /installapp/backup-bash-scripts/run-two-applications.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #export REPOSITORY=$MY_REPOSITORY # Example to set the variable: export MY_REPOSITORY=tsuedbroecker 3 | export REPOSITORY=kdeif # Example to set the variable: export MY_REPOSITORY=tsuedbroecker 4 | 5 | echo "************************************" 6 | echo " frontend" 7 | echo "************************************" 8 | #docker login quay.io 9 | 10 | echo "************************************" 11 | echo " Build frontend" 12 | echo "************************************" 13 | #docker build -t "quay.io/$REPOSITORY/frontend:v0.0" -f Dockerfile.os4-webapp . 14 | docker build -t "frontend:v0.0" -f Dockerfile.os4-webapp . 15 | 16 | echo "************************************" 17 | echo " Test frontend container in interactive mode" 18 | echo " -d or -it" 19 | echo "************************************" 20 | 21 | docker run --name="frontend-a" \ 22 | -d \ 23 | -e VUE_APPID_CLIENT_ID='b3adeb3b-36fc-40cb-9bc3-dd6f15047195' \ 24 | -e VUE_APPID_DISCOVERYENDPOINT='https://us-south.appid.cloud.ibm.com/oauth/v4/a7ec8ce4-3602-42c7-8e88-6f8a9db31935/.well-known/openid-configuration' \ 25 | -e VUE_APP_API_URL_PRODUCTS='https://service-catalog.ceqctuyxg6m.us-south.codeengine.appdomain.cloud/base/category/' \ 26 | -e VUE_APP_API_URL_ORDERS='https://service-catalog.ceqctuyxg6m.us-south.codeengine.appdomain.cloud/base/Customer/Orders' \ 27 | -e VUE_APP_API_URL_CATEGORIES='https://service-catalog.ceqctuyxg6m.us-south.codeengine.appdomain.cloud/base/category' \ 28 | -e VUE_APP_CATEGORY_NAME='Movies' \ 29 | -e VUE_APP_HEADLINE='Frontend A' \ 30 | -p 8080:8081 \ 31 | frontend:v0.0 32 | #"quay.io/$REPOSITORY/frontend-a:v0.0" 33 | 34 | docker run --name="frontend-b" \ 35 | -d \ 36 | -e VUE_APPID_CLIENT_ID='b3adeb3b-36fc-40cb-9bc3-dd6f15047195' \ 37 | -e VUE_APPID_DISCOVERYENDPOINT='https://us-south.appid.cloud.ibm.com/oauth/v4/a7ec8ce4-3602-42c7-8e88-6f8a9db31935/.well-known/openid-configuration' \ 38 | -e VUE_APP_API_URL_PRODUCTS='https://service-catalog.ceqctuyxg6m.us-south.codeengine.appdomain.cloud/mycompany/category/' \ 39 | -e VUE_APP_API_URL_ORDERS='https://service-catalog.ceqctuyxg6m.us-south.codeengine.appdomain.cloud/mycompany/Customer/Orders' \ 40 | -e VUE_APP_API_URL_CATEGORIES='https://service-catalog.ceqctuyxg6m.us-south.codeengine.appdomain.cloud/mycompany/category' \ 41 | -e VUE_APP_CATEGORY_NAME='Fantasy' \ 42 | -e VUE_APP_HEADLINE='Frontend B' \ 43 | -p 8082:8081 \ 44 | frontend:v0.0 45 | #"quay.io/$REPOSITORY/frontend-b:v0.0" 46 | 47 | echo "************************************" 48 | echo "Push frontend" 49 | echo "************************************" 50 | #docker push "quay.io/$REPOSITORY/frontend:v0.0" 51 | -------------------------------------------------------------------------------- /installapp/ce-build-images-ibm-buildah.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "************************************" 4 | echo " Display parameter" 5 | echo "************************************" 6 | echo "" 7 | echo "Parameter count : $@" 8 | echo "Parameter zero 'name of the script': $0" 9 | echo "---------------------------------" 10 | echo "Service catalog image : $1" 11 | echo "Frontend image : $2" 12 | echo "Registry URL : $3" 13 | echo "---------------------------------" 14 | echo "" 15 | 16 | # **************** Global variables 17 | export SERVICE_CATALOG_IMAGE=$1 18 | export FRONTEND_IMAGE=$2 19 | export REGISTRY_URL=$3 20 | 21 | export ROOT_PROJECT=multi-tenancy 22 | export FRONTEND_SOURCEFOLDER=multi-tenancy-frontend 23 | export BACKEND_SOURCEFOLDER=multi-tenancy-backend 24 | 25 | 26 | # ********************************************************************************** 27 | # Functions 28 | # ********************************************************************************** 29 | 30 | cleanUpLocalImages() { 31 | 32 | echo "************************************" 33 | echo " Clean up local images, if needed" 34 | echo "************************************" 35 | 36 | buildah rmi -f "$SERVICE_CATALOG_IMAGE" 37 | buildah rmi -f "$FRONTEND_IMAGE" 38 | } 39 | 40 | setROOT_PATH() { 41 | echo "************************************" 42 | echo " Set ROOT_PATH" 43 | echo "************************************" 44 | cd ../../ 45 | export ROOT_PATH=$(pwd) 46 | echo "Path: $ROOT_PATH" 47 | } 48 | 49 | buildAndPushBackend() { 50 | echo "************************************" 51 | echo " Backend $SERVICE_CATALOG_IMAGE" 52 | echo "************************************" 53 | cd $ROOT_PATH/$BACKEND_SOURCEFOLDER 54 | 55 | ibmcloud iam oauth-tokens | sed -ne '/IAM token/s/.* //p' | buildah login -u iambearer --password-stdin $REGISTRY_URL 56 | 57 | buildah bud -t "$SERVICE_CATALOG_IMAGE" -f Dockerfile . 58 | buildah push "$SERVICE_CATALOG_IMAGE" 59 | echo "" 60 | } 61 | 62 | buildAndPushFrontend() { 63 | echo "************************************" 64 | echo " Frontend $FRONTEND_IMAGE" 65 | echo "************************************" 66 | cd $ROOT_PATH/$FRONTEND_SOURCEFOLDER 67 | 68 | ibmcloud iam oauth-tokens | sed -ne '/IAM token/s/.* //p' | buildah login -u iambearer --password-stdin $REGISTRY_URL 69 | 70 | buildah bud -t "$FRONTEND_IMAGE" -f Dockerfile . 71 | buildah push "$FRONTEND_IMAGE" 72 | echo "" 73 | } 74 | 75 | resetPath() { 76 | echo "************************************" 77 | echo " Reset path" 78 | echo "************************************" 79 | cd $ROOT_PATH/$ROOT_PROJECT 80 | echo "" 81 | } 82 | 83 | 84 | # ********************************************************************************** 85 | # Execution 86 | # ********************************************************************************** 87 | 88 | setROOT_PATH 89 | cleanUpLocalImages 90 | buildAndPushBackend 91 | buildAndPushFrontend 92 | resetPath 93 | -------------------------------------------------------------------------------- /installapp/ce-check-prerequisites.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # **************** Global variables 4 | 5 | export CHECK_IBMCLOUDCLI="ibmcloud" 6 | export CHECK_JQ="jq-" 7 | export CHECK_SED="sed" 8 | export CHECK_AWK="mawk:" 9 | export CHECK_CURL="curl" 10 | export CHECK_BUILDAH="buildah" 11 | export CHECK_KUBECTL="Client" 12 | export CHECK_PLUGIN_CLOUDDATABASES="cloud-databases" 13 | export CHECK_PLUGIN_CODEENGINE="code-engine" 14 | export CHECK_PLUGIN_CONTAINERREGISTERY="container-registry" 15 | export CHECK_GREP="grep" 16 | export CHECK_LIBPQ="psql" 17 | 18 | export VERICATION="" 19 | 20 | 21 | # ********************************************************************************** 22 | # Functions definition 23 | # ********************************************************************************** 24 | 25 | verifyLibpq() { 26 | VERICATION=$(psql --version) 27 | 28 | if [[ $VERICATION =~ $CHECK_LIBPQ ]]; then 29 | echo "- libpq (psql) is installed: $VERICATION !" 30 | else 31 | echo "*** libpq (psql) is NOT installed !" 32 | echo "*** The scripts ends here!" 33 | exit 1 34 | fi 35 | } 36 | 37 | verifyGrep() { 38 | VERICATION=$(grep --version) 39 | 40 | if [[ $VERICATION =~ $CHECK_GREP ]]; then 41 | echo "- Grep is installed: $VERICATION !" 42 | else 43 | echo "*** Grep is NOT installed !" 44 | echo "*** The scripts ends here!" 45 | exit 1 46 | fi 47 | } 48 | 49 | verifyCURL() { 50 | VERICATION=$(curl --version) 51 | 52 | if [[ $VERICATION =~ $CHECK_CURL ]]; then 53 | echo "- cURL is installed: $VERICATION !" 54 | else 55 | echo "*** cURL is NOT installed !" 56 | echo "*** The scripts ends here!" 57 | exit 1 58 | fi 59 | } 60 | 61 | verifyAWK() { 62 | VERICATION=$(mawk -W) 63 | 64 | if [[ $VERICATION =~ $CHECK_AWK ]]; then 65 | echo "- AWK is installed: $VERICATION !" 66 | else 67 | echo "*** AWK is NOT installed !" 68 | echo "*** The scripts ends here!" 69 | exit 1 70 | fi 71 | } 72 | 73 | verifySed() { 74 | VERICATION=$(sed --version) 75 | 76 | if [[ $VERICATION =~ $CHECK_SED ]]; then 77 | echo "- Sed is installed: $VERICATION !" 78 | else 79 | echo "*** Sed is NOT installed !" 80 | echo "*** The scripts ends here!" 81 | exit 1 82 | fi 83 | } 84 | 85 | verifyIBMCloudCLI() { 86 | VERICATION=$(ibmcloud --version) 87 | 88 | if [[ $VERICATION =~ $CHECK_IBMCLOUDCLI ]]; then 89 | echo "- IBM Cloud CLI is installed: $VERICATION !" 90 | else 91 | echo "*** IBM Cloud CLI is NOT installed !" 92 | echo "*** The scripts ends here!" 93 | exit 1 94 | fi 95 | } 96 | 97 | verifyJQ() { 98 | VERICATION=$(jq --version) 99 | 100 | if [[ $VERICATION =~ $CHECK_JQ ]]; then 101 | echo "- JQ is installed: $VERICATION !" 102 | else 103 | echo "*** JQ is NOT installed !" 104 | echo "*** The scripts ends here!" 105 | exit 1 106 | fi 107 | } 108 | 109 | verifyIBMCloudPluginCloudDatabases() { 110 | VERICATION=$(ibmcloud plugin show cloud-databases | grep 'Plugin Name' | awk '{print $3}' ) 111 | 112 | if [[ $VERICATION =~ $CHECK_PLUGIN_CLOUDDATABASES ]]; then 113 | echo "- IBM Cloud Plugin 'cloud-databases' is installed: $VERICATION !" 114 | else 115 | echo "IBM Cloud Plugin 'cloud-databases' is NOT installed !" 116 | echo "*** The scripts ends here!" 117 | exit 1 118 | fi 119 | } 120 | 121 | verifyIBMCloudPluginCodeEngine() { 122 | VERICATION=$(ibmcloud plugin show code-engine | grep 'Plugin Name' | awk '{print $3}' ) 123 | 124 | if [[ $VERICATION =~ $CHECK_PLUGIN_CODEENGINE ]]; then 125 | echo "- IBM Cloud Plugin 'code-engine' is installed: $VERICATION !" 126 | else 127 | echo "IBM Cloud Plugin 'code-engine' is NOT installed !" 128 | echo "*** The scripts ends here!" 129 | exit 1 130 | fi 131 | } 132 | 133 | verifyIBMCloudPluginContainerRegistry() { 134 | VERICATION=$(ibmcloud plugin show container-registry | grep 'Plugin Name' | awk '{print $3}' ) 135 | 136 | if [[ $VERICATION =~ $CHECK_PLUGIN_CONTAINERREGISTERY ]]; then 137 | echo "- IBM Cloud Plugin 'container-registry' is installed: $VERICATION !" 138 | else 139 | echo "IBM Cloud Plugin 'container-registry' is NOT installed !" 140 | echo "*** The scripts ends here!" 141 | exit 1 142 | fi 143 | } 144 | 145 | verifyBuildah() { 146 | VERICATION=$(buildah --version) 147 | 148 | if [[ $VERICATION =~ $CHECK_BUILDAH ]]; then 149 | echo "- buildah is installed: $VERICATION !" 150 | else 151 | echo "*** buildah is NOT installed !" 152 | echo "*** The scripts ends here!" 153 | exit 1 154 | fi 155 | } 156 | 157 | verifyKubectl() { 158 | VERICATION=$(kubectl version) 159 | 160 | if [[ $VERICATION =~ $CHECK_KUBECTL ]]; then 161 | echo "- kubectl is installed: $VERICATION !" 162 | else 163 | echo "*** kubectl is NOT installed !" 164 | echo "*** The scripts ends here!" 165 | exit 1 166 | fi 167 | } 168 | 169 | # ********************************************************************************** 170 | # Execution 171 | # ********************************************************************************** 172 | 173 | echo "Check prereqisites" 174 | echo "1. Verify grep" 175 | verifyGrep 176 | echo "2. Verify awk" 177 | # verifyAWK 178 | echo "3. Verify cURL" 179 | verifyCURL 180 | echo "4. Verify Sed" 181 | verifySed 182 | echo "5. Verify jq" 183 | verifyJQ 184 | echo "6. Verify libpq (psql)" 185 | verifyLibpq 186 | echo "7. Verify Buildah" 187 | verifyBuildah 188 | echo "8. Verify ibmcloud cli" 189 | verifyIBMCloudCLI 190 | echo "9. Verify ibmcloud plugin cloud-databases" 191 | verifyIBMCloudPluginCloudDatabases 192 | echo "10. Verify ibmcloud plugin code-engine" 193 | verifyIBMCloudPluginCodeEngine 194 | echo "11. Verify ibmcloud plugin container-registry" 195 | verifyIBMCloudPluginContainerRegistry 196 | echo "12. Verify kubectl" 197 | verifyKubectl 198 | 199 | echo "Success! All prerequisites verified!" 200 | -------------------------------------------------------------------------------- /installapp/ce-clean-up-two-tenancies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # CLI tools Documentation 4 | # ================ 5 | # IBM Cloud CLI 6 | # Code Engine: https://cloud.ibm.com/docs/codeengine?topic=codeengine-cli#cli-application-create 7 | # Cloud databases 8 | # IBM Cloud Container Registry 9 | 10 | # Needed IBM Cloud CLI plugins 11 | # ============= 12 | # - code engine 13 | # - cloud databases (ibmcloud plugin install cloud-databases) 14 | # - ibm cloud container registry 15 | 16 | # Needed tools 17 | # ============ 18 | # brew install libpq 19 | # brew link --force libpq 20 | 21 | # Install jq to extract json in bash on mac 22 | # =============== 23 | # brew install jq 24 | 25 | # **************** Global variables 26 | # Configurations 27 | export GLOBAL="../configuration/global.json" 28 | export TENANT_A="../configuration/tenants/tenant-a.json" 29 | export TENANT_B="../configuration/tenants/tenant-b.json" 30 | 31 | # ********************************************************************************** 32 | # Functions definition 33 | # ********************************************************************************** 34 | 35 | # TBD 36 | 37 | # ********************************************************************************** 38 | # Execution 39 | # ********************************************************************************** 40 | 41 | echo "************************************" 42 | echo " Clean Tenant A" 43 | echo "************************************" 44 | 45 | bash ./ce-clean-up.sh $GLOBAL $TENANT_A 46 | 47 | bash ./ce-clean-up.sh $GLOBAL $TENANT_B -------------------------------------------------------------------------------- /installapp/ce-clean-up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # CLI tools Documentation 4 | # ================ 5 | # Code Engine: https://cloud.ibm.com/docs/codeengine?topic=codeengine-cli#cli-application-create 6 | # Cloud databases 7 | # IBM Cloud Container Registry 8 | 9 | # Needed IBM Cloud CLI plugins 10 | # ============= 11 | # - code engine 12 | # - cloud databases (ibmcloud plugin install cloud-databases) 13 | # - container registry 14 | 15 | # Needed tools 16 | # ============ 17 | # For Postgres database 18 | # - brew install libpq 19 | # - brew link --force libpq 20 | # Install jq to extract json in bash on mac 21 | # - brew install jq 22 | 23 | echo "************************************" 24 | echo " Display parameter" 25 | echo "************************************" 26 | echo "" 27 | echo "Parameter count : $@" 28 | echo "Parameter zero 'name of the script': $0" 29 | echo "---------------------------------" 30 | echo "Global configuration : $1" 31 | echo "Tenant configuration : $2" 32 | echo "---------------------------------" 33 | 34 | # **************** Global variables set by parameters 35 | 36 | # Globale config 37 | # -------------- 38 | # IBM Cloud Container Registry 39 | export IBM_CR_SERVER=$(cat ./$1 | jq '.REGISTRY.URL' | sed 's/"//g') 40 | # CE for IBM Cloud Container Registry access 41 | export SECRET_NAME=$(cat ./$1 | jq '.REGISTRY.SECRET_NAME' | sed 's/"//g') 42 | export IBMCLOUDCLI_KEY_NAME="cliapikey_for_multi_tenant_$PROJECT_NAME" 43 | export REGISTRY_URL=$(cat ./$1 | jq '.REGISTRY.URL' | sed 's/"//g') 44 | export IBM_CR_SERVER=$REGISTRY_URL 45 | export IMAGE_TAG=$(cat ./$1 | jq '.REGISTRY.TAG' | sed 's/"//g') 46 | export CR_NAMESPACE=$(cat ./$1 | jq '.REGISTRY.NAMESPACE' | sed 's/"//g') 47 | # IBM Cloud target 48 | export RESOURCE_GROUP=$(cat ./$1 | jq '.IBM_CLOUD.RESOURCE_GROUP' | sed 's/"//g') 49 | export REGION=$(cat ./$1 | jq '.IBM_CLOUD.REGION' | sed 's/"//g') 50 | # ecommerce application container registry 51 | export FRONTEND_IMAGE_NAME=$(cat ./$1 | jq '.IMAGES.NAME_FRONTEND' | sed 's/"//g') 52 | export BACKEND_IMAGE_NAME=$(cat ./$1 | jq '.IMAGES.NAME_BACKEND' | sed 's/"//g') 53 | export FRONTEND_IMAGE="$REGISTRY_URL/$CR_NAMESPACE/$FRONTEND_IMAGE_NAME:$IMAGE_TAG" 54 | export SERVICE_CATALOG_IMAGE="$REGISTRY_URL/$CR_NAMESPACE/$BACKEND_IMAGE_NAME:$IMAGE_TAG" 55 | 56 | # Tenant config 57 | # -------------- 58 | # Code Engine 59 | export PROJECT_NAME=$(cat ./$2 | jq '.CODE_ENGINE.PROJECT_NAME' | sed 's/"//g') 60 | # postgres 61 | export POSTGRES_SERVICE_INSTANCE=$(cat ./$2 | jq '.POSTGRES.SERVICE_INSTANCE' | sed 's/"//g') 62 | export POSTGRES_SERVICE_KEY_NAME=$(cat ./$2 | jq '.POSTGRES.SERVICE_KEY_NAME' | sed 's/"//g') 63 | export POSTGRES_SQL_FILE=$(cat ./$2 | jq '.POSTGRES.SQL_FILE' | sed 's/"//g') 64 | # ecommerce application names 65 | export SERVICE_CATALOG_NAME=$(cat ./$2 | jq '.APPLICATION.CONTAINER_NAME_BACKEND' | sed 's/"//g') 66 | export FRONTEND_NAME=$(cat ./$2 | jq '.APPLICATION.CONTAINER_NAME_FRONTEND' | sed 's/"//g') 67 | export FRONTEND_CATEGORY=$(cat ./$2 | jq '.APPLICATION.CATEGORY' | sed 's/"//g') 68 | # App ID 69 | export APPID_SERVICE_INSTANCE_NAME=$(cat ./$2 | jq '.APP_ID.SERVICE_INSTANCE' | sed 's/"//g') 70 | export APPID_SERVICE_KEY_NAME=$(cat ./$2 | jq '.APP_ID.SERVICE_KEY_NAME' | sed 's/"//g') 71 | export IBMCLOUDCLI_KEY_NAME="cliapikey_for_multi_tenant_$PROJECT_NAME" 72 | 73 | echo "Code Engine project : $PROJECT_NAME" 74 | echo "---------------------------------" 75 | echo "App ID service instance name : $APPID_SERVICE_INSTANCE_NAME" 76 | echo "App ID service key name : $APPID_SERVICE_KEY_NAME" 77 | echo "---------------------------------" 78 | echo "Application Service Catalog name : $SERVICE_CATALOG_NAME" 79 | echo "Application Frontend name : $FRONTEND_NAME" 80 | echo "Application Frontend category : $FRONTEND_CATEGORY" 81 | echo "Application Service Catalog image: $SERVICE_CATALOG_IMAGE" 82 | echo "Application Frontend image : $FRONTEND_IMAGE" 83 | echo "---------------------------------" 84 | echo "Postgres instance name : $POSTGRES_SERVICE_INSTANCE" 85 | echo "Postgres service key name : $POSTGRES_SERVICE_KEY_NAME" 86 | echo "Postgres sample data sql : $POSTGRES_SQL_FILE" 87 | echo "---------------------------------" 88 | echo "IBM Cloud Container Registry URL : $IBM_CR_SERVER" 89 | echo "Registry Namespace : $CR_NAMESPACE" 90 | echo "---------------------------------" 91 | echo "IBM Cloud RESOURCE_GROUP : $RESOURCE_GROUP" 92 | echo "IBM Cloud REGION : $REGION" 93 | echo "---------------------------------" 94 | echo "" 95 | echo "Verify parameters and press return" 96 | read input 97 | 98 | # ********************************************************************************** 99 | # Functions definition 100 | # ********************************************************************************** 101 | 102 | setupCLIenvCE() { 103 | echo "**********************************" 104 | echo " Using following project: $PROJECT_NAME" 105 | echo "**********************************" 106 | 107 | ibmcloud target -g $RESOURCE_GROUP 108 | ibmcloud target -r $REGION 109 | 110 | ibmcloud ce project get --name $PROJECT_NAME 111 | ibmcloud ce project select -n $PROJECT_NAME 112 | 113 | #to use the kubectl commands 114 | ibmcloud ce project select -n $PROJECT_NAME --kubecfg 115 | 116 | NAMESPACE=$(ibmcloud ce project get --name $PROJECT_NAME --output json | grep "namespace" | awk '{print $2;}' | sed 's/"//g' | sed 's/,//g') 117 | echo "Namespace: $NAMESPACE" 118 | kubectl get pods -n $NAMESPACE 119 | } 120 | 121 | cleanIBMContainerImages() { 122 | 123 | echo "delete images" 124 | ibmcloud target -g $RESOURCE_GROUP 125 | ibmcloud target -r $REGION 126 | ibmcloud target 127 | # login with buildah 128 | ibmcloud iam oauth-tokens | sed -ne '/IAM token/s/.* //p' | buildah login -u iambearer --password-stdin $REGISTRY_URL 129 | 130 | ibmcloud cr image-rm $SERVICE_CATALOG_IMAGE 131 | ibmcloud cr image-rm $FRONTEND_IMAGE 132 | 133 | } 134 | 135 | cleanIBMContainerNamespace () { 136 | 137 | echo "delete namespace" 138 | ibmcloud target -g $RESOURCE_GROUP 139 | ibmcloud target -r $REGION 140 | ibmcloud target 141 | # login with buildah 142 | ibmcloud iam oauth-tokens | sed -ne '/IAM token/s/.* //p' | buildah login -u iambearer --password-stdin $REGISTRY_URL 143 | 144 | ibmcloud cr namespace-rm $CR_NAMESPACE 145 | } 146 | 147 | cleanCEsecrets () { 148 | 149 | echo "delete secrects postgres" 150 | ibmcloud ce secret delete --name postgres.certificate-data --force 151 | ibmcloud ce secret delete --name postgres.username --force 152 | ibmcloud ce secret delete --name postgres.password --force 153 | ibmcloud ce secret delete --name postgres.url --force 154 | 155 | echo "delete secrets appid" 156 | ibmcloud ce secret delete --name appid.discovery-endpoint --force 157 | ibmcloud ce secret delete --name appid.oauthserverurl --force 158 | ibmcloud ce secret delete --name appid.client-id-catalog-service --force 159 | ibmcloud ce secret delete --name appid.client-id-fronted --force 160 | 161 | } 162 | 163 | cleanCEapplications () { 164 | 165 | ibmcloud ce application delete --name $FRONTEND_NAME --force 166 | ibmcloud ce application delete --name $SERVICE_CATALOG_NAME --force 167 | } 168 | 169 | cleanCEregistry(){ 170 | 171 | ibmcloud ce registry delete --name $SECRET_NAME 172 | } 173 | 174 | cleanKEYS () { 175 | 176 | echo "IBM Cloud Key: $IBMCLOUDCLI_KEY_NAME" 177 | #List api-keys 178 | ibmcloud iam api-keys | grep $IBMCLOUDCLI_KEY_NAME 179 | #Delete api-key 180 | ibmcloud iam api-key-delete $IBMCLOUDCLI_KEY_NAME -f 181 | 182 | #AppID 183 | ibmcloud resource service-keys | grep $APPID_SERVICE_KEY_NAME 184 | ibmcloud resource service-keys --instance-name $APPID_SERVICE_INSTANCE_NAME 185 | ibmcloud resource service-key-delete $APPID_SERVICE_KEY_NAME -f 186 | 187 | #Postgres 188 | ibmcloud resource service-keys | grep $POSTGRES_SERVICE_NAME 189 | ibmcloud resource service-keys --instance-name $POSTGRES_SERVICE_NAME 190 | ibmcloud resource service-key-delete $POSTGRES_SERVICE_KEY_NAME -f 191 | } 192 | 193 | cleanAppIDservice (){ 194 | ibmcloud resource service-instance $APPID_SERVICE_INSTANCE_NAME 195 | ibmcloud resource service-instance-delete $APPID_SERVICE_INSTANCE_NAME -f 196 | } 197 | 198 | cleanPostgresService (){ 199 | ibmcloud resource service-instance $POSTGRES_SERVICE_INSTANCE 200 | ibmcloud resource service-instance-delete $POSTGRES_SERVICE_INSTANCE -f 201 | } 202 | 203 | cleanCodeEngineProject (){ 204 | ibmcloud ce project delete --name $PROJECT_NAME 205 | } 206 | 207 | # ********************************************************************************** 208 | # Execution 209 | # ********************************************************************************** 210 | 211 | echo "************************************" 212 | echo " CLI config" 213 | echo "************************************" 214 | 215 | setupCLIenvCE 216 | 217 | echo "************************************" 218 | echo " Clean secrets" 219 | echo "************************************" 220 | 221 | cleanCEsecrets 222 | 223 | echo "************************************" 224 | echo " Clean CE apps" 225 | echo "************************************" 226 | 227 | cleanCEapplications 228 | 229 | echo "************************************" 230 | echo " Clean CE registry" 231 | echo "************************************" 232 | 233 | cleanCEregistry 234 | 235 | echo "************************************" 236 | echo " Clean IBM ContainerImages registry" 237 | echo "************************************" 238 | 239 | cleanIBMContainerImages 240 | # To avoid deletion of the Container Registry Namespace 241 | # please comment out the `cleanIBMContainerNamespace` 242 | cleanIBMContainerNamespace 243 | 244 | echo "************************************" 245 | echo " Clean keys " 246 | echo " - $IBMCLOUDCLI_KEY_NAME" 247 | echo " - $APPID_SERVICE_KEY_NAME" 248 | echo " - $POSTGRES_SERVICE_KEY_NAME" 249 | echo "************************************" 250 | 251 | cleanKEYS 252 | 253 | echo "************************************" 254 | echo " Clean AppID service $APPID_INSTANCE_NAME" 255 | echo "************************************" 256 | 257 | cleanAppIDservice 258 | 259 | echo "************************************" 260 | echo " Clean Postgres service $POSTGRES_SERVICE_INSTANCE" 261 | echo "************************************" 262 | 263 | cleanPostgresService 264 | 265 | echo "************************************" 266 | echo " Clean Code Engine Project $PROJECT_NAME" 267 | echo "************************************" 268 | 269 | # To avoid the deletion of the Code Engine project 270 | # please comment out the `cleanCodeEngineProject` 271 | cleanCodeEngineProject -------------------------------------------------------------------------------- /installapp/ce-create-two-tenancies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # CLI Documentation 3 | # ================ 4 | # command documentation: https://cloud.ibm.com/docs/codeengine?topic=codeengine-cli#cli-application-create 5 | 6 | # Install jq to extract json in bash on mac 7 | # =============== 8 | # brew install jq 9 | 10 | # **************** Global variables 11 | 12 | # Configurations 13 | export GLOBAL="../configuration/global.json" 14 | export TENANT_A="../configuration/tenants/tenant-a.json" 15 | export TENANT_B="../configuration/tenants/tenant-b.json" 16 | 17 | # ecommerce application container image names 18 | export REGISTRY_URL=$(cat ./$GLOBAL | jq '.REGISTRY.URL' | sed 's/"//g') 19 | export IMAGE_TAG=$(cat ./$GLOBAL | jq '.REGISTRY.TAG' | sed 's/"//g') 20 | export NAMESPACE=$(cat ./$GLOBAL | jq '.REGISTRY.NAMESPACE' | sed 's/"//g') 21 | export FRONTEND_IMAGE_NAME=$(cat ./$GLOBAL | jq '.IMAGES.NAME_FRONTEND' | sed 's/"//g') 22 | export BACKEND_IMAGE_NAME=$(cat ./$GLOBAL | jq '.IMAGES.NAME_BACKEND' | sed 's/"//g') 23 | export FRONTEND_IMAGE="$REGISTRY_URL/$NAMESPACE/$FRONTEND_IMAGE_NAME:$IMAGE_TAG" 24 | export SERVICE_CATALOG_IMAGE="$REGISTRY_URL/$NAMESPACE/$BACKEND_IMAGE_NAME:$IMAGE_TAG" 25 | 26 | # Registry settings 27 | # ibm cloud container registry settings 28 | export IBMCLOUD_CR_NAMESPACE=$(cat ./$GLOBAL | jq '.REGISTRY.NAMESPACE' | sed 's/"//g') 29 | export IBMCLOUD_CR_REGION_URL=$(cat ./$GLOBAL | jq '.REGISTRY.URL' | sed 's/"//g') 30 | export IBMCLOUD_CR_TAG=$(cat ./$GLOBAL | jq '.REGISTRY.TAG' | sed 's/"//g') 31 | 32 | # IBM Cloud target 33 | export RESOURCE_GROUP=$(cat ./$GLOBAL | jq '.IBM_CLOUD.RESOURCE_GROUP' | sed 's/"//g') 34 | export REGION=$(cat ./$GLOBAL | jq '.IBM_CLOUD.REGION' | sed 's/"//g') 35 | 36 | # ********************************************************************************** 37 | # Functions definition 38 | # ********************************************************************************** 39 | # FYI: https://stackoverflow.com/questions/12468889/bash-script-error-function-not-found-why-would-this-appear 40 | 41 | createNamespaceBuildah() { 42 | 43 | echo "IBMCLOUD_CR_NAMESPACE: $IBMCLOUD_CR_NAMESPACE" 44 | echo "RESOURCE_GROUP : $RESOURCE_GROUP" 45 | echo "REGION : $REGION" 46 | echo "------------------------------" 47 | echo "Verify the given entries and press return" 48 | 49 | read input 50 | 51 | ibmcloud target -g $RESOURCE_GROUP 52 | ibmcloud target -r $REGION 53 | ibmcloud target 54 | # login with buildah 55 | ibmcloud iam oauth-tokens | sed -ne '/IAM token/s/.* //p' | buildah login -u iambearer --password-stdin $IBMCLOUD_CR_REGION_URL 56 | RESULT=$(ibmcloud cr namespace-add $IBMCLOUD_CR_NAMESPACE | grep "FAILED") 57 | 58 | if [[ $RESULT =~ "FAILED" ]]; then 59 | echo "*** Namespace $IBMCLOUD_CR_NAMESPACE in IBM Cloud Container Registry NOT created !" 60 | echo "*** The scripts ends here!" 61 | echo "*** Please define a different namespace name in the 'configuration/global.json' file." 62 | exit 1 63 | else 64 | echo "- Namespace $IBMCLOUD_CR_NAMESPACE in IBM Cloud Container Registry created !" 65 | fi 66 | 67 | } 68 | 69 | createAndPushIBMContainer () { 70 | 71 | echo "FRONTEND_IMAGE: $FRONTEND_IMAGE" 72 | echo "SERVICE_CATALOG_IMAGE: $SERVICE_CATALOG_IMAGE" 73 | echo "IBMCLOUD_CR_REGION_URL: $IBMCLOUD_CR_REGION_URL" 74 | 75 | createNamespaceBuildah 76 | 77 | bash ./ce-build-images-ibm-buildah.sh $SERVICE_CATALOG_IMAGE $FRONTEND_IMAGE 78 | 79 | if [ $? == "1" ]; then 80 | echo "*** Creation of the container images failed !" 81 | echo "*** The script 'ce-create-two-tenancies.sh' ends here!" 82 | exit 1 83 | fi 84 | } 85 | 86 | # ********************************************************************************** 87 | # Execution 88 | # ********************************************************************************** 89 | 90 | echo "************************************" 91 | echo " Create and push container image to IBM Container Registry" 92 | echo "************************************" 93 | 94 | createAndPushIBMContainer 95 | 96 | echo "************************************" 97 | echo " Tenant A" 98 | echo "************************************" 99 | 100 | bash ./ce-install-application.sh $GLOBAL $TENANT_A 101 | 102 | if [ $? == "1" ]; then 103 | echo "*** The installation for '$GLOBAL' '$TENANT_A' configuation failed !" 104 | echo "*** The script 'ce-create-two-tenancies.sh' ends here!" 105 | exit 1 106 | fi 107 | 108 | echo "************************************" 109 | echo " Tenant B" 110 | echo "************************************" 111 | 112 | bash ./ce-install-application.sh $GLOBAL $TENANT_B 113 | 114 | if [ $? == "1" ]; then 115 | echo "*** The installation for '$GLOBAL' '$TENANT_B' configuation failed !" 116 | echo "*** The script 'ce-create-two-tenancies.sh' ends here!" 117 | exit 1 118 | fi -------------------------------------------------------------------------------- /installapp/ops-create-two-appids.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # CLI Documentation 4 | # ================ 5 | # command documentation: https://cloud.ibm.com/docs/codeengine?topic=codeengine-cli#cli-application-create 6 | 7 | # Install jq to extract json in bash on mac 8 | # =============== 9 | # brew install jq 10 | 11 | # **************** Global variables 12 | 13 | # Configurations 14 | export GLOBAL="../configuration/global.json" 15 | export TENANT_A="../configuration/tenants/tenant-a.json" 16 | export TENANT_B="../configuration/tenants/tenant-b.json" 17 | 18 | # ********************************************************************************** 19 | # Functions definition 20 | # ********************************************************************************** 21 | 22 | # TBD 23 | 24 | # ********************************************************************************** 25 | # Execution 26 | # ********************************************************************************** 27 | 28 | echo "************************************" 29 | echo " App ID for Tenant A" 30 | echo "************************************" 31 | 32 | bash ./ops-install-single-appid.sh $GLOBAL $TENANT_A 33 | 34 | if [ $? == "1" ]; then 35 | echo "*** The installation for '$GLOBAL' '$TENANT_A' configuation failed !" 36 | echo "*** The script 'ops-create-two-appids.sh' ends here!" 37 | exit 1 38 | fi 39 | 40 | echo "************************************" 41 | echo " App ID for Tenant B" 42 | echo "************************************" 43 | 44 | bash ./ops-install-single-appid.sh $GLOBAL $TENANT_B 45 | 46 | if [ $? == "1" ]; then 47 | echo "*** The installation for '$GLOBAL' '$TENANT_B' configuation failed !" 48 | echo "*** The script 'ops-create-two-appids.sh' ends here!" 49 | exit 1 50 | fi -------------------------------------------------------------------------------- /installapp/ops-create-two-postgres.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # CLI Documentation 4 | # ================ 5 | # command documentation: https://cloud.ibm.com/docs/codeengine?topic=codeengine-cli#cli-application-create 6 | 7 | # Install jq to extract json in bash on mac 8 | # =============== 9 | # brew install jq 10 | 11 | # **************** Global variables 12 | 13 | # Configurations 14 | export GLOBAL="../configuration/global.json" 15 | export TENANT_A="../configuration/tenants/tenant-a.json" 16 | export TENANT_B="../configuration/tenants/tenant-b.json" 17 | 18 | # ********************************************************************************** 19 | # Functions definition 20 | # ********************************************************************************** 21 | 22 | # TBD 23 | 24 | # ********************************************************************************** 25 | # Execution 26 | # ********************************************************************************** 27 | 28 | echo "************************************" 29 | echo " Postgres for Tenant A" 30 | echo "************************************" 31 | 32 | bash ./ops-install-single-postgres.sh $GLOBAL $TENANT_A 33 | 34 | if [ $? == "1" ]; then 35 | echo "*** The installation for '$GLOBAL' '$TENANT_A' configuation failed !" 36 | echo "*** The script 'ops-create-two-postgres.sh' ends here!" 37 | exit 1 38 | fi 39 | 40 | echo "************************************" 41 | echo " App ID for Tenant B" 42 | echo "************************************" 43 | 44 | bash ./ops-install-single-postgres.sh $GLOBAL $TENANT_B 45 | 46 | if [ $? == "1" ]; then 47 | echo "*** The installation for '$GLOBAL' '$TENANT_B' configuation failed !" 48 | echo "*** The script 'ops-create-two-postgres.sh' ends here!" 49 | exit 1 50 | fi -------------------------------------------------------------------------------- /installapp/ops-install-single-appid.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Needed tools 4 | # ============ 5 | # Install jq to extract json in bash on mac 6 | # - brew install jq 7 | 8 | # ********************************************************************************** 9 | # Set global variables using parameters 10 | # ********************************************************************************** 11 | 12 | echo "************************************" 13 | echo " Display parameter" 14 | echo "************************************" 15 | echo "" 16 | echo "Parameter count : $@" 17 | echo "Parameter zero 'name of the script': $0" 18 | echo "---------------------------------" 19 | echo "Global configuration : $1" 20 | echo "Tenant configuration : $2" 21 | echo "---------------------------------" 22 | 23 | # **************** Global variables set by parameters 24 | 25 | # Globale config 26 | # -------------- 27 | # IBM Cloud target 28 | export RESOURCE_GROUP=$(cat ./$1 | jq '.IBM_CLOUD.RESOURCE_GROUP' | sed 's/"//g') 29 | export REGION=$(cat ./$1 | jq '.IBM_CLOUD.REGION' | sed 's/"//g') 30 | 31 | # Tenant config 32 | # -------------- 33 | # App ID 34 | export APPID_SERVICE_INSTANCE_NAME=$(cat ./$2 | jq '.APP_ID.SERVICE_INSTANCE' | sed 's/"//g') 35 | export APPID_SERVICE_KEY_NAME=$(cat ./$2 | jq '.APP_ID.SERVICE_KEY_NAME' | sed 's/"//g') 36 | # ecommerce application names 37 | export FRONTEND_NAME=$(cat ./$2 | jq '.APPLICATION.CONTAINER_NAME_FRONTEND' | sed 's/"//g') 38 | 39 | echo "Application Frontend name : $FRONTEND_NAME" 40 | echo "---------------------------------" 41 | echo "App ID service instance name : $APPID_SERVICE_INSTANCE_NAME" 42 | echo "App ID service key name : $APPID_SERVICE_KEY_NAME" 43 | echo "---------------------------------" 44 | echo "IBM Cloud RESOURCE_GROUP : $RESOURCE_GROUP" 45 | echo "IBM Cloud REGION : $REGION" 46 | echo "---------------------------------" 47 | echo "" 48 | echo "------------------------------" 49 | echo "Verify parameters and press return" 50 | read input 51 | 52 | # **************** Global variables set as default values 53 | 54 | # AppID Service 55 | export SERVICE_PLAN="graduated-tier" 56 | export APPID_SERVICE_NAME="appid" 57 | export APPID_SERVICE_KEY_ROLE="Manager" 58 | export TENANTID="" 59 | export MANAGEMENTURL="" 60 | export APPLICATION_DISCOVERYENDPOINT="" 61 | 62 | # AppID User 63 | export USER_IMPORT_FILE="appid-configs/user-import.json" 64 | export USER_EXPORT_FILE="appid-configs/user-export.json" 65 | export ENCRYPTION_SECRET="12345678" 66 | 67 | # AppID Application configuration 68 | export ADD_APPLICATION="appid-configs/add-application.json" 69 | export ADD_SCOPE="appid-configs/add-scope.json" 70 | export ADD_ROLE="appid-configs/add-roles.json" 71 | export ADD_REDIRECT_URIS="appid-configs/add-redirecturis.json" 72 | export ADD_UI_TEXT="appid-configs/add-ui-text.json" 73 | export ADD_IMAGE="appid-images/logo.png" 74 | export ADD_COLOR="appid-configs/add-ui-color.json" 75 | export APPLICATION_CLIENTID="" 76 | export APPLICATION_TENANTID="" 77 | export APPLICATION_OAUTHSERVERURL="" 78 | 79 | # ********************************************************************************** 80 | # Functions definition 81 | # ********************************************************************************** 82 | 83 | setupCLIenv() { 84 | echo "**********************************" 85 | echo " Using following: " 86 | echo " - Resource group: $RESOURCE_GROUP " 87 | echo " - Region: $REGION " 88 | echo "**********************************" 89 | 90 | ibmcloud target -g $RESOURCE_GROUP 91 | ibmcloud target -r $REGION 92 | } 93 | 94 | # **** AppID **** 95 | 96 | createAppIDService() { 97 | ibmcloud target -g $RESOURCE_GROUP 98 | ibmcloud target -r $REGION 99 | 100 | # Create AppID service 101 | RESULT=$(ibmcloud resource service-instance $APPID_SERVICE_INSTANCE_NAME --location $REGION -g $RESOURCE_GROUP | grep "OK") 102 | if [[ $RESULT == "OK" ]]; then 103 | echo "*** The AppID service '$APPID_SERVICE_INSTANCE_NAME' already exists!" 104 | echo "*** The script 'ops-install-single-appid.sh' ends here!" 105 | exit 1 106 | fi 107 | ibmcloud resource service-instance-create $APPID_SERVICE_INSTANCE_NAME $APPID_SERVICE_NAME $SERVICE_PLAN $REGION 108 | # Create a service key for the service 109 | ibmcloud resource service-key-create $APPID_SERVICE_KEY_NAME $APPID_SERVICE_KEY_ROLE --instance-name $APPID_SERVICE_INSTANCE_NAME 110 | # Get the tenantId of the AppID service key 111 | TENANTID=$(ibmcloud resource service-keys --instance-name $APPID_SERVICE_INSTANCE_NAME --output json | grep "tenantId" | awk '{print $2;}' | sed 's/"//g') 112 | echo "Tenant ID: $TENANTID" 113 | # Get the managementUrl of the AppID from service key 114 | MANAGEMENTURL=$(ibmcloud resource service-keys --instance-name $APPID_SERVICE_INSTANCE_NAME --output json | grep "managementUrl" | awk '{print $2;}' | sed 's/"//g' | sed 's/,//g') 115 | echo "Management URL: $MANAGEMENTURL" 116 | } 117 | 118 | configureAppIDInformation(){ 119 | 120 | #****** Set identity providers 121 | echo "" 122 | echo "-------------------------" 123 | echo " Set identity providers" 124 | echo "-------------------------" 125 | echo "" 126 | OAUTHTOKEN=$(ibmcloud iam oauth-tokens | awk '{print $4;}') 127 | result=$(curl -d @./appid-configs/idps-custom.json -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $OAUTHTOKEN" $MANAGEMENTURL/config/idps/custom) 128 | echo "" 129 | echo "-------------------------" 130 | echo "Result custom: $result" 131 | echo "-------------------------" 132 | echo "" 133 | OAUTHTOKEN=$(ibmcloud iam oauth-tokens | awk '{print $4;}') 134 | result=$(curl -d @./appid-configs/idps-facebook.json -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $OAUTHTOKEN" $MANAGEMENTURL/config/idps/facebook) 135 | echo "" 136 | echo "-------------------------" 137 | echo "Result facebook: $result" 138 | echo "-------------------------" 139 | echo "" 140 | OAUTHTOKEN=$(ibmcloud iam oauth-tokens | awk '{print $4;}') 141 | result=$(curl -d @./appid-configs/idps-google.json -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $OAUTHTOKEN" $MANAGEMENTURL/config/idps/google) 142 | echo "" 143 | echo "-------------------------" 144 | echo "Result google: $result" 145 | echo "-------------------------" 146 | echo "" 147 | OAUTHTOKEN=$(ibmcloud iam oauth-tokens | awk '{print $4;}') 148 | result=$(curl -d @./appid-configs/idps-clouddirectory.json -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $OAUTHTOKEN" $MANAGEMENTURL/config/idps/cloud_directory) 149 | echo "" 150 | echo "-------------------------" 151 | echo "Result cloud directory: $result" 152 | echo "-------------------------" 153 | echo "" 154 | 155 | #****** Add application ****** 156 | echo "" 157 | echo "-------------------------" 158 | echo " Create application" 159 | echo "-------------------------" 160 | echo "" 161 | sed "s+FRONTENDNAME+$FRONTEND_NAME+g" ./appid-configs/add-application-template.json > ./$ADD_APPLICATION 162 | result=$(curl -d @./$ADD_APPLICATION -H "Content-Type: application/json" -H "Authorization: Bearer $OAUTHTOKEN" $MANAGEMENTURL/applications) 163 | echo "-------------------------" 164 | echo "Result application: $result" 165 | echo "-------------------------" 166 | APPLICATION_CLIENTID=$(echo $result | sed -n 's|.*"clientId":"\([^"]*\)".*|\1|p') 167 | APPLICATION_TENANTID=$(echo $result | sed -n 's|.*"tenantId":"\([^"]*\)".*|\1|p') 168 | APPLICATION_OAUTHSERVERURL=$(echo $result | sed -n 's|.*"oAuthServerUrl":"\([^"]*\)".*|\1|p') 169 | APPLICATION_DISCOVERYENDPOINT=$(echo $result | sed -n 's|.*"discoveryEndpoint":"\([^"]*\)".*|\1|p') 170 | echo "ClientID: $APPLICATION_CLIENTID" 171 | echo "TenantID: $APPLICATION_TENANTID" 172 | echo "oAuthServerUrl: $APPLICATION_OAUTHSERVERURL" 173 | echo "discoveryEndpoint: $APPLICATION_DISCOVERYENDPOINT" 174 | echo "" 175 | 176 | #****** Add scope ****** 177 | echo "" 178 | echo "-------------------------" 179 | echo " Add scope" 180 | echo "-------------------------" 181 | OAUTHTOKEN=$(ibmcloud iam oauth-tokens | awk '{print $4;}') 182 | result=$(curl -d @./$ADD_SCOPE -H "Content-Type: application/json" -X PUT -H "Authorization: Bearer $OAUTHTOKEN" $MANAGEMENTURL/applications/$APPLICATION_CLIENTID/scopes) 183 | echo "-------------------------" 184 | echo "Result scope: $result" 185 | echo "-------------------------" 186 | echo "" 187 | 188 | #****** Add role ****** 189 | echo "-------------------------" 190 | echo " Add role" 191 | echo "-------------------------" 192 | #Create file from template 193 | sed "s+APPLICATIONID+$APPLICATION_CLIENTID+g" ./appid-configs/add-roles-template.json > ./$ADD_ROLE 194 | OAUTHTOKEN=$(ibmcloud iam oauth-tokens | awk '{print $4;}') 195 | #echo $OAUTHTOKEN 196 | result=$(curl -d @./$ADD_ROLE -H "Content-Type: application/json" -X POST -H "Authorization: Bearer $OAUTHTOKEN" $MANAGEMENTURL/roles) 197 | rm -f ./$ADD_ROLE 198 | echo "-------------------------" 199 | echo "Result role: $result" 200 | echo "-------------------------" 201 | echo "" 202 | 203 | #****** Import cloud directory users ****** 204 | echo "" 205 | echo "-------------------------" 206 | echo " Cloud directory import users" 207 | echo "-------------------------" 208 | echo "" 209 | OAUTHTOKEN=$(ibmcloud iam oauth-tokens | awk '{print $4;}') 210 | result=$(curl -d @./$USER_IMPORT_FILE -H "Content-Type: application/json" -X POST -H "Authorization: Bearer $OAUTHTOKEN" $MANAGEMENTURL/cloud_directory/import?encryption_secret=$ENCRYPTION_SECRET) 211 | echo "-------------------------" 212 | echo "Result import: $result" 213 | echo "-------------------------" 214 | echo "" 215 | 216 | #******* Configure ui text ****** 217 | echo "" 218 | echo "-------------------------" 219 | echo " Configure ui text" 220 | echo "-------------------------" 221 | echo "" 222 | sed "s+FRONTENDNAME+$FRONTEND_NAME+g" ./appid-configs/add-ui-text-template.json > ./$ADD_UI_TEXT 223 | OAUTHTOKEN=$(ibmcloud iam oauth-tokens | awk '{print $4;}') 224 | echo "PUT url: $MANAGEMENTURL/config/ui/theme_txt" 225 | result=$(curl -d @./$ADD_UI_TEXT -H "Content-Type: application/json" -X PUT -H "Authorization: Bearer $OAUTHTOKEN" $MANAGEMENTURL/config/ui/theme_text) 226 | rm -f $ADD_UI_TEXT 227 | echo "-------------------------" 228 | echo "Result import: $result" 229 | echo "-------------------------" 230 | echo "" 231 | 232 | #******* Configure ui color ****** 233 | echo "" 234 | echo "-------------------------" 235 | echo " Configure ui color" 236 | echo "-------------------------" 237 | echo "" 238 | OAUTHTOKEN=$(ibmcloud iam oauth-tokens | awk '{print $4;}') 239 | echo "PUT url: $MANAGEMENTURL/config/ui/theme_color" 240 | result=$(curl -d @./$ADD_COLOR -H "Content-Type: application/json" -X PUT -H "Authorization: Bearer $OAUTHTOKEN" $MANAGEMENTURL/config/ui/theme_color) 241 | echo "-------------------------" 242 | echo "Result import: $result" 243 | echo "-------------------------" 244 | echo "" 245 | 246 | #******* Configure ui image ****** 247 | echo "" 248 | echo "-------------------------" 249 | echo " Configure ui image" 250 | echo "-------------------------" 251 | echo "" 252 | OAUTHTOKEN=$(ibmcloud iam oauth-tokens | awk '{print $4;}') 253 | echo "POST url: $MANAGEMENTURL/config/ui/media?mediaType=logo" 254 | result=$(curl -F "file=@./$ADD_IMAGE" -H "Content-Type: multipart/form-data" -X POST -v -H "Authorization: Bearer $OAUTHTOKEN" "$MANAGEMENTURL/config/ui/media?mediaType=logo") 255 | echo "-------------------------" 256 | echo "Result import: $result" 257 | echo "-------------------------" 258 | echo "" 259 | } 260 | 261 | # ********************************************************************************** 262 | # Execution 263 | # ********************************************************************************** 264 | 265 | echo "************************************" 266 | echo " CLI config" 267 | echo "************************************" 268 | 269 | setupCLIenv 270 | 271 | echo "************************************" 272 | echo " AppID creation" 273 | echo "************************************" 274 | 275 | createAppIDService 276 | 277 | echo "************************************" 278 | echo " AppID configuration" 279 | echo "************************************" 280 | 281 | configureAppIDInformation 282 | 283 | echo "************************************" 284 | echo " AppID URLs and IDs" 285 | echo "************************************" 286 | echo "- ClientID: $APPLICATION_CLIENTID" 287 | echo "- TenantID: $APPLICATION_TENANTID" 288 | echo "- oAuthServerUrl: $APPLICATION_OAUTHSERVERURL" 289 | echo "- discoveryEndpoint: $APPLICATION_DISCOVERYENDPOINT" 290 | echo "------------------------------" 291 | -------------------------------------------------------------------------------- /installapp/postgres-config/create-populate-tenant-a.sql: -------------------------------------------------------------------------------- 1 | CREATE SEQUENCE product_id_seq START 1; 2 | 3 | CREATE TABLE product 4 | ( 5 | id SERIAL PRIMARY KEY, 6 | price DECIMAL(14,2) NOT NULL, 7 | name TEXT NOT NULL, 8 | description TEXT NOT NULL, 9 | image TEXT NOT NULL 10 | ); 11 | 12 | INSERT INTO product VALUES (nextval('product_id_seq'), 29.99, 'Return of the Jedi', 'Episode 6, Luke has the final confrontation with his father!', 'images/Return.jpg'); 13 | INSERT INTO product VALUES (nextval('product_id_seq'), 39.99, 'Empire Strikes Back', 'Episode 5, Luke finds out a secret that will change his destiny', 'images/Empire.jpg'); 14 | INSERT INTO product VALUES (nextval('product_id_seq'), 49.99, 'New Hope', 'Episode 4, after years of oppression, a band of rebels fight for freedom', 'images/NewHope.jpg'); 15 | INSERT INTO product VALUES (nextval('product_id_seq'), 100.00, 'DVD Player', 'This Sony Player has crystal clear picture', 'images/Player.jpg'); 16 | INSERT INTO product VALUES (nextval('product_id_seq'), 200.00, 'BlackBerry Curve', 'This BlackBerry offers rich PDA functions and works with Notes.', 'images/BlackBerry.jpg'); 17 | INSERT INTO product VALUES (nextval('product_id_seq'), 150.00, 'Sony Ericsson', 'This Sony phone takes pictures and plays Music.', 'images/SonyPhone.jpg'); 18 | INSERT INTO product VALUES (nextval('product_id_seq'), 1800.00, 'Sony Bravia', 'This is a 40 inch 1080p LCD HDTV', 'images/SonyTV.jpg'); 19 | INSERT INTO product VALUES (nextval('product_id_seq'), 1150.00, 'Sharp Aquos', 'This is 32 inch 1080p LCD HDTV', 'images/SamTV.jpg'); 20 | INSERT INTO product VALUES (nextval('product_id_seq'), 20.00, 'Go Fish: Superstar', 'Go Fish release their great new hit, Superstar', 'images/Superstar.jpg'); 21 | INSERT INTO product VALUES (nextval('product_id_seq'), 20.00, 'Ludwig van Beethoven', 'This is a classic, the 9 Symphonies Box Set', 'images/Bet.jpg'); 22 | INSERT INTO product VALUES (nextval('product_id_seq'), 399.99, 'PlayStation 3', 'Brace yourself for the marvels of the PlayStation 3 complete with 80GB hard disk storage', 'images/PS3.jpg'); 23 | INSERT INTO product VALUES (nextval('product_id_seq'), 169.99, 'Nintendo Wii', 'Next-generation gaming with a motion-sensitive controller', 'images/wii.jpg'); 24 | INSERT INTO product VALUES (nextval('product_id_seq'), 299.99, 'XBOX 360', 'Expand your horizons with the gaming and multimedia capabilities of this incredible system', 'images/xbox360.jpg'); 25 | 26 | 27 | CREATE SEQUENCE category_id_seq START 1; 28 | CREATE TABLE category 29 | ( 30 | id SERIAL PRIMARY KEY , 31 | name TEXT NOT NULL, 32 | parent INT 33 | ); 34 | 35 | INSERT INTO category VALUES (nextval('category_id_seq'), 'Entertainment', null); 36 | INSERT INTO category VALUES (nextval('category_id_seq'), 'Movies', 1); 37 | INSERT INTO category VALUES (nextval('category_id_seq'), 'Music', 1); 38 | INSERT INTO category VALUES (nextval('category_id_seq'), 'Games', 1); 39 | INSERT INTO category VALUES (nextval('category_id_seq'), 'Electronics', null); 40 | INSERT INTO category VALUES (nextval('category_id_seq'), 'TV', 5); 41 | INSERT INTO category VALUES (nextval('category_id_seq'), 'CellPhones', 5); 42 | INSERT INTO category VALUES (nextval('category_id_seq'), 'DVD Players', 5); 43 | 44 | CREATE SEQUENCE productcategory_id_seq START 1; 45 | CREATE TABLE productcategory 46 | ( 47 | id SERIAL PRIMARY KEY , 48 | productid INT, 49 | categoryid INT 50 | ); 51 | 52 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 1, 2); 53 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 2, 2); 54 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 3, 2); 55 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 4, 8); 56 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 5, 7); 57 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 6, 7); 58 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 7, 6); 59 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 8, 6); 60 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 9, 4); 61 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 10, 3); 62 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 11, 4); 63 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 12, 4); 64 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 13, 4); 65 | 66 | -------------------------------------------------------------------------------- /installapp/postgres-config/create-populate-tenant-b.sql: -------------------------------------------------------------------------------- 1 | CREATE SEQUENCE product_id_seq START 1; 2 | 3 | CREATE TABLE product 4 | ( 5 | id SERIAL PRIMARY KEY, 6 | price DECIMAL(14,2) NOT NULL, 7 | name TEXT NOT NULL, 8 | description TEXT NOT NULL, 9 | image TEXT NOT NULL 10 | ); 11 | 12 | INSERT INTO product VALUES (nextval('product_id_seq'), 19.99, 'Beautiful Disaster', 'The new Abby Abernathy is a good girl.', 'images/beautifuldisaster.jpg'); 13 | INSERT INTO product VALUES (nextval('product_id_seq'), 9.99, 'Dune', 'Dune is a 1965 science fiction novel by American author Frank Herbert', 'images/dune.jpg'); 14 | INSERT INTO product VALUES (nextval('product_id_seq'), 15.99, 'Jade City', 'The Kaul family is one of two crime syndicates that control the island of Kekon.', 'images/greenbone.jpg'); 15 | INSERT INTO product VALUES (nextval('product_id_seq'), 12.00, 'The Heart Principle', 'When violinist Anna Sun accidentally achieves career success with a viral YouTube video', 'images/heartprinciple.jpg'); 16 | INSERT INTO product VALUES (nextval('product_id_seq'), 20.00, 'Handmaids Tale', 'It is set in a near-future New England, in a strongly patriarchal, totalitarian theonomic state, known as Republic of Gilead.', 'images/handmaidstale.jpg'); 17 | INSERT INTO product VALUES (nextval('product_id_seq'), 30.00, 'Ancillary Justice', ' It is Leckies debut novel and the first in her Imperial Radch space opera trilogy', 'images/imperial.jpg'); 18 | INSERT INTO product VALUES (nextval('product_id_seq'), 18.00, 'A Lesson in Vengence', 'Perched in the Catskill mountains, the centuries-old, ivy-covered campus was home until the tragic death of her girlfriend.', 'images/lessonvengence.jpg'); 19 | INSERT INTO product VALUES (nextval('product_id_seq'), 11.00, 'The Lord of the Rings', 'The Lord of the Rings is a series of three epic fantasy adventure films directed by Peter Jackson, based on the novel written by J. R. R. Tolkien', 'images/lordofrings.jpg'); 20 | INSERT INTO product VALUES (nextval('product_id_seq'), 22.00, 'A Slow Fire Burning', 'When a young man is found gruesomely murdered in a London houseboat, it triggers questions about three women who knew him.', 'images/slowfire.jpg'); 21 | INSERT INTO product VALUES (nextval('product_id_seq'), 27.00, 'The Guide', 'Kingfisher Lodge, nestled in a canyon on a mile and a half of the most pristine river water on the planet', 'images/theguide.jpg'); 22 | 23 | 24 | CREATE SEQUENCE category_id_seq START 1; 25 | CREATE TABLE category 26 | ( 27 | id SERIAL PRIMARY KEY , 28 | name TEXT NOT NULL, 29 | parent INT 30 | ); 31 | 32 | INSERT INTO category VALUES (nextval('category_id_seq'), 'Fantasy', null); 33 | INSERT INTO category VALUES (nextval('category_id_seq'), 'Thriller', 1); 34 | INSERT INTO category VALUES (nextval('category_id_seq'), 'Mystery', 1); 35 | INSERT INTO category VALUES (nextval('category_id_seq'), 'Fiction', 1); 36 | INSERT INTO category VALUES (nextval('category_id_seq'), 'Non-Fiction', null); 37 | 38 | CREATE SEQUENCE productcategory_id_seq START 1; 39 | CREATE TABLE productcategory 40 | ( 41 | id SERIAL PRIMARY KEY , 42 | productid INT, 43 | categoryid INT 44 | ); 45 | 46 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 1, 2); 47 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 2, 2); 48 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 3, 2); 49 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 4, 5); 50 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 5, 5); 51 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 6, 3); 52 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 7, 4); 53 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 8, 4); 54 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 9, 2); 55 | INSERT INTO productcategory VALUES (nextval('productcategory_id_seq'), 10, 1); 56 | 57 | 58 | -------------------------------------------------------------------------------- /installapp/postgres-config/insert-template.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | COMMAND_INSERT -------------------------------------------------------------------------------- /installapp/tools-image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/ubuntu:22.04 2 | 3 | RUN apt-get update -qq \ 4 | && apt-get --assume-yes install buildah \ 5 | && apt-get --assume-yes install iptables 6 | 7 | # Java 8 | # *********** default-jdk -> openjdk-11-jdk *************** 9 | RUN apt update \ 10 | && apt-get --assume-yes install default-jdk 11 | 12 | # *********** Basic tools *************** 13 | RUN apt-get update \ 14 | && apt-get --assume-yes install curl \ 15 | && apt-get --assume-yes install git-core \ 16 | && apt-get --assume-yes install wget \ 17 | && apt-get --assume-yes install gnupg2 \ 18 | && apt-get --assume-yes install nano \ 19 | && apt-get --assume-yes install apt-utils \ 20 | && apt-get --assume-yes install unzip \ 21 | && apt-get --assume-yes install zip \ 22 | && apt-get --assume-yes install sed \ 23 | && apt-get --assume-yes install jq 24 | ARG DEBIAN_FRONTEND="noninteractive" 25 | RUN apt-get --assume-yes install postgresql \ 26 | && apt-get --assume-yes install postgresql-contrib \ 27 | && apt-get --assume-yes install original-awk 28 | 29 | # Kubernetes 30 | # *********** Kubernetes *************** 31 | RUN apt-get update && apt-get install -y apt-transport-https \ 32 | && curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - \ 33 | && echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | tee -a /etc/apt/sources.list.d/kubernetes.list \ 34 | && apt-get update \ 35 | && apt-get --assume-yes install kubectl 36 | 37 | # *********** IBM Cloud CLI *********** 38 | RUN curl -fsSL https://clis.cloud.ibm.com/install/linux | sh \ 39 | && ibmcloud plugin install container-service \ 40 | && ibmcloud plugin install container-registry \ 41 | && ibmcloud plugin install code-engine \ 42 | && ibmcloud plugin install cloud-databases 43 | -------------------------------------------------------------------------------- /installapp/tz-build-images-code-engine.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "************************************" 4 | echo " Display parameter" 5 | echo "************************************" 6 | echo "" 7 | echo "Parameter count : $@" 8 | echo "Parameter zero 'name of the script': $0" 9 | echo "---------------------------------" 10 | echo "Service catalog image : $1" 11 | echo "Frontend image : $2" 12 | echo "Registry URL : $3" 13 | echo "Build CE Project : $4" 14 | echo "Registry Secret Name : $5" 15 | echo "---------------------------------" 16 | echo "" 17 | 18 | # **************** Global variables 19 | export SERVICE_CATALOG_IMAGE=$1 20 | export FRONTEND_IMAGE=$2 21 | export REGISTRY_URL=$3 22 | export PROJECT_NAME=$4 23 | export SECRET_NAME=$5 24 | 25 | export ROOT_PROJECT=multi-tenancy 26 | export FRONTEND_SOURCEFOLDER=multi-tenancy-frontend 27 | export BACKEND_SOURCEFOLDER=multi-tenancy-backend 28 | 29 | 30 | # ********************************************************************************** 31 | # Functions 32 | # ********************************************************************************** 33 | 34 | setROOT_PATH() { 35 | echo "************************************" 36 | echo " Set ROOT_PATH" 37 | echo "************************************" 38 | cd ../../ 39 | export ROOT_PATH=$(pwd) 40 | echo "Path: $ROOT_PATH" 41 | } 42 | 43 | setupCRenvCE() { 44 | 45 | IBMCLOUDCLI_KEY_NAME="cliapikey_for_multi_tenant_$PROJECT_NAME" 46 | IBMCLOUDCLI_KEY_DESCRIPTION="CLI APIkey $IBMCLOUDCLI_KEY_NAME" 47 | CLIKEY_FILE="cli_key.json" 48 | USERNAME="iamapikey" 49 | 50 | RESULT=$(ibmcloud iam api-keys | grep '$IBMCLOUDCLI_KEY_NAME' | awk '{print $1;}' | head -n 1) 51 | echo "API key: $RESULT" 52 | if [[ $RESULT == $IBMCLOUDCLI_KEY_NAME ]]; then 53 | echo "*** The Cloud API key '$IBMCLOUDCLI_KEY_NAME' already exists !" 54 | echo "*** The script 'ce-install-application.sh' ends here!" 55 | echo "*** Review your existing API keys 'https://cloud.ibm.com/iam/apikeys'." 56 | exit 1 57 | fi 58 | 59 | ibmcloud iam api-key-create $IBMCLOUDCLI_KEY_NAME -d "My IBM CLoud CLI API key for project $PROJECT_NAME" --file $CLIKEY_FILE 60 | CLIAPIKEY=$(cat $CLIKEY_FILE | grep '"apikey":' | awk '{print $2;}' | sed 's/"//g' | sed 's/,//g' ) 61 | #echo $CLIKEY 62 | rm -f $CLIKEY_FILE 63 | 64 | ibmcloud ce project select --name $PROJECT_NAME 65 | 66 | ibmcloud ce registry create --name $SECRET_NAME \ 67 | --server $REGISTRY_URL \ 68 | --username $USERNAME \ 69 | --password $CLIAPIKEY 70 | } 71 | 72 | buildBackend() { 73 | echo "************************************" 74 | echo " Backend $SERVICE_CATALOG_IMAGE" 75 | echo "************************************" 76 | cd $ROOT_PATH/$BACKEND_SOURCEFOLDER 77 | 78 | # ibmcloud iam oauth-tokens | sed -ne '/IAM token/s/.* //p' | buildah login -u iambearer --password-stdin $REGISTRY_URL 79 | 80 | # buildah bud -t "$SERVICE_CATALOG_IMAGE" -f Dockerfile . 81 | # buildah push "$SERVICE_CATALOG_IMAGE" 82 | 83 | ibmcloud ce buildrun submit --name service-catalog --image $SERVICE_CATALOG_IMAGE \ 84 | --registry-secret $SECRET_NAME --source . 85 | 86 | ibmcloud ce buildrun logs -f -n service-catalog 87 | echo "" 88 | } 89 | 90 | buildFrontend() { 91 | echo "************************************" 92 | echo " Frontend $FRONTEND_IMAGE" 93 | echo "************************************" 94 | cd $ROOT_PATH/$FRONTEND_SOURCEFOLDER 95 | 96 | # ibmcloud iam oauth-tokens | sed -ne '/IAM token/s/.* //p' | buildah login -u iambearer --password-stdin $REGISTRY_URL 97 | 98 | # buildah bud -t "$FRONTEND_IMAGE" -f Dockerfile . 99 | # buildah push "$FRONTEND_IMAGE" 100 | 101 | ibmcloud ce buildrun submit --name frontend-image --image $FRONTEND_IMAGE \ 102 | --registry-secret $SECRET_NAME --source . 103 | 104 | ibmcloud ce buildrun logs -f -n frontend-image 105 | echo "" 106 | } 107 | 108 | resetPath() { 109 | echo "************************************" 110 | echo " Reset path" 111 | echo "************************************" 112 | cd $ROOT_PATH/$ROOT_PROJECT 113 | echo "" 114 | } 115 | 116 | 117 | # ********************************************************************************** 118 | # Execution 119 | # ********************************************************************************** 120 | 121 | setROOT_PATH 122 | setupCRenvCE 123 | buildBackend 124 | buildFrontend 125 | resetPath 126 | -------------------------------------------------------------------------------- /installapp/tz-check-prerequisites.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # **************** Global variables 4 | 5 | export CHECK_IBMCLOUDCLI="ibmcloud" 6 | export CHECK_JQ="jq-" 7 | export CHECK_SED="sed" 8 | export CHECK_AWK="mawk:" 9 | export CHECK_CURL="curl" 10 | export CHECK_KUBECTL="Client" 11 | export CHECK_PLUGIN_CLOUDDATABASES="cloud-databases" 12 | export CHECK_PLUGIN_CODEENGINE="code-engine" 13 | export CHECK_PLUGIN_CONTAINERREGISTERY="container-registry" 14 | export CHECK_GREP="grep" 15 | export CHECK_LIBPQ="psql" 16 | 17 | export VERICATION="" 18 | 19 | 20 | # ********************************************************************************** 21 | # Functions definition 22 | # ********************************************************************************** 23 | 24 | verifyLibpq() { 25 | VERICATION=$(psql --version) 26 | 27 | if [[ $VERICATION =~ $CHECK_LIBPQ ]]; then 28 | echo "- libpq (psql) is installed: $VERICATION !" 29 | else 30 | echo "*** libpq (psql) is NOT installed !" 31 | echo "*** The scripts ends here!" 32 | exit 1 33 | fi 34 | } 35 | 36 | verifyGrep() { 37 | VERICATION=$(grep --version) 38 | 39 | if [[ $VERICATION =~ $CHECK_GREP ]]; then 40 | echo "- Grep is installed: $VERICATION !" 41 | else 42 | echo "*** Grep is NOT installed !" 43 | echo "*** The scripts ends here!" 44 | exit 1 45 | fi 46 | } 47 | 48 | verifyCURL() { 49 | VERICATION=$(curl --version) 50 | 51 | if [[ $VERICATION =~ $CHECK_CURL ]]; then 52 | echo "- cURL is installed: $VERICATION !" 53 | else 54 | echo "*** cURL is NOT installed !" 55 | echo "*** The scripts ends here!" 56 | exit 1 57 | fi 58 | } 59 | 60 | verifyAWK() { 61 | VERICATION=$(mawk -W) 62 | 63 | if [[ $VERICATION =~ $CHECK_AWK ]]; then 64 | echo "- AWK is installed: $VERICATION !" 65 | else 66 | echo "*** AWK is NOT installed !" 67 | echo "*** The scripts ends here!" 68 | exit 1 69 | fi 70 | } 71 | 72 | verifySed() { 73 | VERICATION=$(sed --version) 74 | 75 | if [[ $VERICATION =~ $CHECK_SED ]]; then 76 | echo "- Sed is installed: $VERICATION !" 77 | else 78 | echo "*** Sed is NOT installed !" 79 | echo "*** The scripts ends here!" 80 | exit 1 81 | fi 82 | } 83 | 84 | verifyIBMCloudCLI() { 85 | VERICATION=$(ibmcloud --version) 86 | 87 | if [[ $VERICATION =~ $CHECK_IBMCLOUDCLI ]]; then 88 | echo "- IBM Cloud CLI is installed: $VERICATION !" 89 | else 90 | echo "*** IBM Cloud CLI is NOT installed !" 91 | echo "*** The scripts ends here!" 92 | exit 1 93 | fi 94 | } 95 | 96 | verifyJQ() { 97 | VERICATION=$(jq --version) 98 | 99 | if [[ $VERICATION =~ $CHECK_JQ ]]; then 100 | echo "- JQ is installed: $VERICATION !" 101 | else 102 | echo "*** JQ is NOT installed !" 103 | echo "*** The scripts ends here!" 104 | exit 1 105 | fi 106 | } 107 | 108 | verifyIBMCloudPluginCloudDatabases() { 109 | VERICATION=$(ibmcloud plugin show cloud-databases | grep 'Plugin Name' | awk '{print $3}' ) 110 | 111 | if [[ $VERICATION =~ $CHECK_PLUGIN_CLOUDDATABASES ]]; then 112 | echo "- IBM Cloud Plugin 'cloud-databases' is installed: $VERICATION !" 113 | else 114 | echo "IBM Cloud Plugin 'cloud-databases' is NOT installed !" 115 | echo "*** The scripts ends here!" 116 | exit 1 117 | fi 118 | } 119 | 120 | verifyIBMCloudPluginCodeEngine() { 121 | VERICATION=$(ibmcloud plugin show code-engine | grep 'Plugin Name' | awk '{print $3}' ) 122 | 123 | if [[ $VERICATION =~ $CHECK_PLUGIN_CODEENGINE ]]; then 124 | echo "- IBM Cloud Plugin 'code-engine' is installed: $VERICATION !" 125 | else 126 | echo "IBM Cloud Plugin 'code-engine' is NOT installed !" 127 | echo "*** The scripts ends here!" 128 | exit 1 129 | fi 130 | } 131 | 132 | verifyIBMCloudPluginContainerRegistry() { 133 | VERICATION=$(ibmcloud plugin show container-registry | grep 'Plugin Name' | awk '{print $3}' ) 134 | 135 | if [[ $VERICATION =~ $CHECK_PLUGIN_CONTAINERREGISTERY ]]; then 136 | echo "- IBM Cloud Plugin 'container-registry' is installed: $VERICATION !" 137 | else 138 | echo "IBM Cloud Plugin 'container-registry' is NOT installed !" 139 | echo "*** The scripts ends here!" 140 | exit 1 141 | fi 142 | } 143 | 144 | verifyKubectl() { 145 | VERICATION=$(kubectl version) 146 | 147 | if [[ $VERICATION =~ $CHECK_KUBECTL ]]; then 148 | echo "- kubectl is installed: $VERICATION !" 149 | else 150 | echo "*** kubectl is NOT installed !" 151 | echo "*** The scripts ends here!" 152 | exit 1 153 | fi 154 | } 155 | 156 | # ********************************************************************************** 157 | # Execution 158 | # ********************************************************************************** 159 | 160 | echo "Check prereqisites" 161 | echo "1. Verify grep" 162 | verifyGrep 163 | echo "2. Verify awk" 164 | # verifyAWK 165 | echo "3. Verify cURL" 166 | verifyCURL 167 | echo "4. Verify Sed" 168 | verifySed 169 | echo "5. Verify jq" 170 | verifyJQ 171 | echo "6. Verify libpq (psql)" 172 | verifyLibpq 173 | echo "7. Verify ibmcloud cli" 174 | verifyIBMCloudCLI 175 | echo "8. Verify ibmcloud plugin cloud-databases" 176 | verifyIBMCloudPluginCloudDatabases 177 | echo "9. Verify ibmcloud plugin code-engine" 178 | verifyIBMCloudPluginCodeEngine 179 | echo "10. Verify ibmcloud plugin container-registry" 180 | verifyIBMCloudPluginContainerRegistry 181 | echo "11. Verify kubectl" 182 | verifyKubectl 183 | 184 | echo "Success! All prerequisites verified!" 185 | -------------------------------------------------------------------------------- /installapp/tz-create-two-tenancies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # CLI Documentation 3 | # ================ 4 | # command documentation: https://cloud.ibm.com/docs/codeengine?topic=codeengine-cli#cli-application-create 5 | 6 | # Install jq to extract json in bash on mac 7 | # =============== 8 | # brew install jq 9 | 10 | # **************** Global variables 11 | 12 | # Configurations 13 | export GLOBAL="../configuration/global.json" 14 | export TENANT_A="../configuration/tenants/tenant-a.json" 15 | export TENANT_B="../configuration/tenants/tenant-b.json" 16 | 17 | # ecommerce application container image names 18 | export REGISTRY_URL=$(cat ./$GLOBAL | jq '.REGISTRY.URL' | sed 's/"//g') 19 | export IMAGE_TAG=$(cat ./$GLOBAL | jq '.REGISTRY.TAG' | sed 's/"//g') 20 | export NAMESPACE=$(cat ./$GLOBAL | jq '.REGISTRY.NAMESPACE' | sed 's/"//g') 21 | export FRONTEND_IMAGE_NAME=$(cat ./$GLOBAL | jq '.IMAGES.NAME_FRONTEND' | sed 's/"//g') 22 | export BACKEND_IMAGE_NAME=$(cat ./$GLOBAL | jq '.IMAGES.NAME_BACKEND' | sed 's/"//g') 23 | export FRONTEND_IMAGE="$REGISTRY_URL/$NAMESPACE/$FRONTEND_IMAGE_NAME:$IMAGE_TAG" 24 | export SERVICE_CATALOG_IMAGE="$REGISTRY_URL/$NAMESPACE/$BACKEND_IMAGE_NAME:$IMAGE_TAG" 25 | 26 | # Registry settings 27 | # ibm cloud container registry settings 28 | export IBMCLOUD_CR_NAMESPACE=$(cat ./$GLOBAL | jq '.REGISTRY.NAMESPACE' | sed 's/"//g') 29 | export IBMCLOUD_CR_REGION_URL=$(cat ./$GLOBAL | jq '.REGISTRY.URL' | sed 's/"//g') 30 | export IBMCLOUD_CR_TAG=$(cat ./$GLOBAL | jq '.REGISTRY.TAG' | sed 's/"//g') 31 | export IBMCLOUD_CR_SECRET_NAME=$(cat ./$GLOBAL | jq -r '.REGISTRY.SECRET_NAME' | sed 's/"//g') 32 | 33 | # IBM Cloud target 34 | export RESOURCE_GROUP=$(cat ./$GLOBAL | jq -r '.IBM_CLOUD.RESOURCE_GROUP') 35 | export REGION=$(cat ./$GLOBAL | jq -r '.IBM_CLOUD.REGION') 36 | # Code Engine 37 | export IBMCLOUD_CE_BUILD_PROJECT=$(cat ./$TENANT_A | jq -r '.CODE_ENGINE.PROJECT_NAME') 38 | 39 | # ********************************************************************************** 40 | # Functions definition 41 | # ********************************************************************************** 42 | # FYI: https://stackoverflow.com/questions/12468889/bash-script-error-function-not-found-why-would-this-appear 43 | 44 | configLogin() { 45 | 46 | echo "IBMCLOUD_CR_NAMESPACE: $IBMCLOUD_CR_NAMESPACE" 47 | echo "RESOURCE_GROUP : $RESOURCE_GROUP" 48 | echo "REGION : $REGION" 49 | echo "------------------------------" 50 | echo "Verify the given entries and press return" 51 | 52 | read input 53 | 54 | ibmcloud target -g $RESOURCE_GROUP 55 | ibmcloud target -r $REGION 56 | ibmcloud target 57 | 58 | } 59 | 60 | createIBMContainer () { 61 | 62 | echo "FRONTEND_IMAGE: $FRONTEND_IMAGE" 63 | echo "SERVICE_CATALOG_IMAGE: $SERVICE_CATALOG_IMAGE" 64 | echo "IBMCLOUD_CR_REGION_URL: $IBMCLOUD_CR_REGION_URL" 65 | echo "IBMCLOUD_CE_BUILD_PROJECT: $IBMCLOUD_CE_BUILD_PROJECT" 66 | 67 | # createNamespaceBuildah 68 | 69 | bash ./tz-build-images-code-engine.sh $SERVICE_CATALOG_IMAGE $FRONTEND_IMAGE $IBMCLOUD_CR_REGION_URL \ 70 | $IBMCLOUD_CE_BUILD_PROJECT $IBMCLOUD_CR_SECRET_NAME 71 | 72 | if [ $? == "1" ]; then 73 | echo "*** Creation of the container images failed !" 74 | echo "*** The script 'tz-create-two-tenancies.sh' ends here!" 75 | exit 1 76 | fi 77 | } 78 | 79 | # ********************************************************************************** 80 | # Execution 81 | # ********************************************************************************** 82 | 83 | echo "************************************" 84 | echo " Create container image in IBM Container Registry" 85 | echo "************************************" 86 | 87 | configLogin 88 | createIBMContainer 89 | 90 | echo "************************************" 91 | echo " Tenant A" 92 | echo "************************************" 93 | 94 | bash ./tz-install-application.sh $GLOBAL $TENANT_A skip-cr-secret 95 | 96 | if [ $? == "1" ]; then 97 | echo "*** The installation for '$GLOBAL' '$TENANT_A' configuation failed !" 98 | echo "*** The script 'tz-create-two-tenancies.sh' ends here!" 99 | exit 1 100 | fi 101 | 102 | echo "************************************" 103 | echo " Tenant B" 104 | echo "************************************" 105 | 106 | bash ./tz-install-application.sh $GLOBAL $TENANT_B 107 | 108 | if [ $? == "1" ]; then 109 | echo "*** The installation for '$GLOBAL' '$TENANT_B' configuation failed !" 110 | echo "*** The script 'tz-create-two-tenancies.sh' ends here!" 111 | exit 1 112 | fi -------------------------------------------------------------------------------- /installapp/tz-update-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ********************************************************************************** 4 | # Set global variables using parameters 5 | # ********************************************************************************** 6 | 7 | echo "************************************" 8 | echo " Display parameter" 9 | echo "************************************" 10 | echo "" 11 | echo "Parameter count : $@" 12 | echo "Parameter zero 'name of the script': $0" 13 | echo "---------------------------------" 14 | echo "Code Engine Region : $1" 15 | echo "Container Registry Namespace : $2" 16 | echo "Resource Group Name : $3" 17 | echo "---------------------------------" 18 | 19 | export REGION=${REGION:-${1}} 20 | export NAMESPACE=${NAMESPACE:-${2}} 21 | export RESOURCE_GROUP=${RESOURCE_GROUP:-${3}} 22 | export RANDOM8=$(echo ${NAMESPACE} | cut -f3 -d"-") 23 | 24 | # Configurations 25 | export GLOBAL="../configuration/global.json" 26 | export TENANT_A="../configuration/tenants/tenant-a.json" 27 | export TENANT_B="../configuration/tenants/tenant-b.json" 28 | 29 | # ********************************************************************************** 30 | # Determine host for Container Registry based on region 31 | # ********************************************************************************** 32 | 33 | case $REGION in 34 | us-south) 35 | export REGISTRY_URL=us.icr.io 36 | ;; 37 | ca-tor) 38 | export REGISTRY_URL=ca.icr.io 39 | ;; 40 | eu-gb) 41 | export REGISTRY_URL=uk.icr.io 42 | ;; 43 | eu-de) 44 | export REGISTRY_URL=de.icr.io 45 | ;; 46 | jp-tok) 47 | export REGISTRY_URL=jp.icr.io 48 | ;; 49 | au-syd) 50 | export REGISTRY_URL=au.icr.io 51 | ;; 52 | esac 53 | 54 | # ********************************************************************************** 55 | # Update global configuration properties 56 | # ********************************************************************************** 57 | 58 | sed -i.bak "s/default/${RESOURCE_GROUP}/" ${GLOBAL} && rm ${GLOBAL}.bak 59 | sed -i.bak "s/eu-de/${REGION}/" ${GLOBAL} && rm ${GLOBAL}.bak 60 | sed -i.bak "s/de\.icr\.io/${REGISTRY_URL}/" ${GLOBAL} && rm ${GLOBAL}.bak 61 | sed -i.bak "s/multi-tenancy-example/${NAMESPACE}/" ${GLOBAL} && rm ${GLOBAL}.bak 62 | 63 | # ********************************************************************************** 64 | # Update Tenant A configuration properties 65 | # ********************************************************************************** 66 | 67 | sed -i.bak "s/multi-tenancy-serverless-appid-a/${NAMESPACE}-00/" ${TENANT_A}&& rm ${TENANT_A}.bak 68 | sed -i.bak "s/itzce/appid/" ${TENANT_A}&& rm ${TENANT_A}.bak 69 | sed -i.bak "s/multi-tenancy-serverless-appid-key-a/appid-service-key-${RANDOM8}-00/" ${TENANT_A}&& rm ${TENANT_A}.bak 70 | sed -i.bak "s/multi-tenancy-serverless-pg-ten-a-key/pgsql-service-key-${RANDOM8}-00/" ${TENANT_A}&& rm ${TENANT_A}.bak 71 | sed -i.bak "s/multi-tenancy-serverless-pg-ten-a/${NAMESPACE}-00/" ${TENANT_A}&& rm ${TENANT_A}.bak 72 | sed -i.bak "s/itzce/pgsql/" ${TENANT_A}&& rm ${TENANT_A}.bak 73 | sed -i.bak "s/multi-tenancy-serverless-a/${NAMESPACE}-00/" ${TENANT_A}&& rm ${TENANT_A}.bak 74 | 75 | # ********************************************************************************** 76 | # Update Tenant B configuration properties 77 | # ********************************************************************************** 78 | 79 | sed -i.bak "s/multi-tenancy-serverless-appid-b/${NAMESPACE}-01/" ${TENANT_B}&& rm ${TENANT_B}.bak 80 | sed -i.bak "s/itzce/appid/" ${TENANT_B}&& rm ${TENANT_B}.bak 81 | sed -i.bak "s/multi-tenancy-serverless-appid-key-b/appid-service-key-${RANDOM8}-01/" ${TENANT_B}&& rm ${TENANT_B}.bak 82 | sed -i.bak "s/multi-tenancy-serverless-pg-ten-b-key/pgsql-service-key-${RANDOM8}-01/" ${TENANT_B}&& rm ${TENANT_B}.bak 83 | sed -i.bak "s/multi-tenancy-serverless-pg-ten-b/${NAMESPACE}-01/" ${TENANT_B}&& rm ${TENANT_B}.bak 84 | sed -i.bak "s/itzce/pgsql/" ${TENANT_B}&& rm ${TENANT_B}.bak 85 | sed -i.bak "s/multi-tenancy-serverless-b/${NAMESPACE}-01/" ${TENANT_B}&& rm ${TENANT_B}.bak -------------------------------------------------------------------------------- /local.env.template: -------------------------------------------------------------------------------- 1 | POSTGRES_USERNAME="aaa" 2 | POSTGRES_PASSWORD="bbb" 3 | POSTGRES_URL="jdbc:postgresql://NO_USERNAME_NO_PASSWORDccc.ddd.databases.appdomain.cloud:30143/ibmclouddbNOTHING_ELSE" 4 | POSTGRES_CERTIFICATE_FILE_NAME="5df929c2-b76a-11e9-b3dd-4acf6c229d45" 5 | APPID_CLIENT_ID='XXXX-XXXX-XXX-XXXX-XXXXX' 6 | APPID_DISCOVERYENDPOINT=https://us-south.appid.cloud.ibm.com/oauth/v4/YOUR_TENDANT/.well-known/openid-configuration 7 | APPID_AUTH_SERVER_URL=https://us-south.appid.cloud.ibm.com/oauth/v4/YOUR_TENDANT -------------------------------------------------------------------------------- /operator/.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /operator/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | testbin/* 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Kubernetes Generated files - skip generated files, except for vendored files 18 | 19 | !vendor/**/zz_generated.* 20 | 21 | # editor and IDE paraphernalia 22 | .idea 23 | *.swp 24 | *.swo 25 | *~ 26 | -------------------------------------------------------------------------------- /operator/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.16 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # cache deps before building and copying source so that we don't need to re-download as much 9 | # and so that source changes don't invalidate our downloaded layer 10 | RUN go mod download 11 | 12 | # Copy the go source 13 | COPY main.go main.go 14 | COPY api/ api/ 15 | COPY controllers/ controllers/ 16 | 17 | # Build 18 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go 19 | 20 | # Use distroless as minimal base image to package the manager binary 21 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 22 | FROM gcr.io/distroless/static:nonroot 23 | WORKDIR / 24 | COPY --from=builder /workspace/manager . 25 | USER 65532:65532 26 | 27 | ENTRYPOINT ["/manager"] 28 | -------------------------------------------------------------------------------- /operator/Makefile: -------------------------------------------------------------------------------- 1 | # VERSION defines the project version for the bundle. 2 | # Update this value when you upgrade the version of your project. 3 | # To re-generate a bundle for another specific version without changing the standard setup, you can: 4 | # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) 5 | # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) 6 | VERSION ?= 0.0.1 7 | 8 | # CHANNELS define the bundle channels used in the bundle. 9 | # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") 10 | # To re-generate a bundle for other specific channels without changing the standard setup, you can: 11 | # - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) 12 | # - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") 13 | ifneq ($(origin CHANNELS), undefined) 14 | BUNDLE_CHANNELS := --channels=$(CHANNELS) 15 | endif 16 | 17 | # DEFAULT_CHANNEL defines the default channel used in the bundle. 18 | # Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") 19 | # To re-generate a bundle for any other default channel without changing the default setup, you can: 20 | # - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) 21 | # - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") 22 | ifneq ($(origin DEFAULT_CHANNEL), undefined) 23 | BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) 24 | endif 25 | BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) 26 | 27 | # IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. 28 | # This variable is used to construct full image tags for bundle and catalog images. 29 | # 30 | # For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both 31 | # saas.ecommerce.sample.com/temp-bundle:$VERSION and saas.ecommerce.sample.com/temp-catalog:$VERSION. 32 | IMAGE_TAG_BASE ?= saas.ecommerce.sample.com/temp 33 | 34 | # BUNDLE_IMG defines the image:tag used for the bundle. 35 | # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) 36 | BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) 37 | 38 | # Image URL to use all building/pushing image targets 39 | IMG ?= docker.io/nheidloff/controller:latest 40 | # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. 41 | ENVTEST_K8S_VERSION = 1.22 42 | 43 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 44 | ifeq (,$(shell go env GOBIN)) 45 | GOBIN=$(shell go env GOPATH)/bin 46 | else 47 | GOBIN=$(shell go env GOBIN) 48 | endif 49 | 50 | # Setting SHELL to bash allows bash commands to be executed by recipes. 51 | # This is a requirement for 'setup-envtest.sh' in the test target. 52 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 53 | SHELL = /usr/bin/env bash -o pipefail 54 | .SHELLFLAGS = -ec 55 | 56 | .PHONY: all 57 | all: build 58 | 59 | ##@ General 60 | 61 | # The help target prints out all targets with their descriptions organized 62 | # beneath their categories. The categories are represented by '##@' and the 63 | # target descriptions by '##'. The awk commands is responsible for reading the 64 | # entire set of makefiles included in this invocation, looking for lines of the 65 | # file as xyz: ## something, and then pretty-format the target and help. Then, 66 | # if there's a line with ##@ something, that gets pretty-printed as a category. 67 | # More info on the usage of ANSI control characters for terminal formatting: 68 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 69 | # More info on the awk command: 70 | # http://linuxcommand.org/lc3_adv_awk.php 71 | 72 | .PHONY: help 73 | help: ## Display this help. 74 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 75 | 76 | ##@ Development 77 | 78 | .PHONY: manifests 79 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 80 | $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases 81 | 82 | .PHONY: generate 83 | generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 84 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 85 | 86 | .PHONY: fmt 87 | fmt: ## Run go fmt against code. 88 | go fmt ./... 89 | 90 | .PHONY: vet 91 | vet: ## Run go vet against code. 92 | go vet ./... 93 | 94 | .PHONY: test 95 | test: manifests generate fmt vet envtest ## Run tests. 96 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out 97 | 98 | ##@ Build 99 | 100 | .PHONY: build 101 | build: generate fmt vet ## Build manager binary. 102 | go build -o bin/manager main.go 103 | 104 | .PHONY: run 105 | run: manifests generate fmt vet ## Run a controller from your host. 106 | go run ./main.go 107 | 108 | .PHONY: docker-build 109 | docker-build: test ## Build docker image with the manager. 110 | podman build -t ${IMG} . 111 | 112 | .PHONY: docker-push 113 | docker-push: ## Push docker image with the manager. 114 | podman push ${IMG} 115 | 116 | ##@ Deployment 117 | 118 | ifndef ignore-not-found 119 | ignore-not-found = false 120 | endif 121 | 122 | .PHONY: install 123 | install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. 124 | $(KUSTOMIZE) build config/crd | kubectl apply -f - 125 | 126 | .PHONY: uninstall 127 | uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 128 | $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - 129 | 130 | .PHONY: deploy 131 | deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. 132 | cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} 133 | $(KUSTOMIZE) build config/default | kubectl apply -f - 134 | 135 | .PHONY: undeploy 136 | undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 137 | $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - 138 | 139 | CONTROLLER_GEN = $(shell pwd)/bin/controller-gen 140 | .PHONY: controller-gen 141 | controller-gen: ## Download controller-gen locally if necessary. 142 | $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.7.0) 143 | 144 | KUSTOMIZE = $(shell pwd)/bin/kustomize 145 | .PHONY: kustomize 146 | kustomize: ## Download kustomize locally if necessary. 147 | $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) 148 | 149 | ENVTEST = $(shell pwd)/bin/setup-envtest 150 | .PHONY: envtest 151 | envtest: ## Download envtest-setup locally if necessary. 152 | $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) 153 | 154 | # go-get-tool will 'go get' any package $2 and install it to $1. 155 | PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) 156 | define go-get-tool 157 | @[ -f $(1) ] || { \ 158 | set -e ;\ 159 | TMP_DIR=$$(mktemp -d) ;\ 160 | cd $$TMP_DIR ;\ 161 | go mod init tmp ;\ 162 | echo "Downloading $(2)" ;\ 163 | GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ 164 | rm -rf $$TMP_DIR ;\ 165 | } 166 | endef 167 | 168 | .PHONY: bundle 169 | bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. 170 | operator-sdk generate kustomize manifests -q 171 | cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) 172 | $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) 173 | operator-sdk bundle validate ./bundle 174 | 175 | .PHONY: bundle-build 176 | bundle-build: ## Build the bundle image. 177 | docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . 178 | 179 | .PHONY: bundle-push 180 | bundle-push: ## Push the bundle image. 181 | $(MAKE) docker-push IMG=$(BUNDLE_IMG) 182 | 183 | .PHONY: opm 184 | OPM = ./bin/opm 185 | opm: ## Download opm locally if necessary. 186 | ifeq (,$(wildcard $(OPM))) 187 | ifeq (,$(shell which opm 2>/dev/null)) 188 | @{ \ 189 | set -e ;\ 190 | mkdir -p $(dir $(OPM)) ;\ 191 | OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ 192 | curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.19.1/$${OS}-$${ARCH}-opm ;\ 193 | chmod +x $(OPM) ;\ 194 | } 195 | else 196 | OPM = $(shell which opm) 197 | endif 198 | endif 199 | 200 | # A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). 201 | # These images MUST exist in a registry and be pull-able. 202 | BUNDLE_IMGS ?= $(BUNDLE_IMG) 203 | 204 | # The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). 205 | CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION) 206 | 207 | # Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. 208 | ifneq ($(origin CATALOG_BASE_IMG), undefined) 209 | FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) 210 | endif 211 | 212 | # Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. 213 | # This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: 214 | # https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator 215 | .PHONY: catalog-build 216 | catalog-build: opm ## Build a catalog image. 217 | $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) 218 | 219 | # Push the catalog image. 220 | .PHONY: catalog-push 221 | catalog-push: ## Push a catalog image. 222 | $(MAKE) docker-push IMG=$(CATALOG_IMG) 223 | -------------------------------------------------------------------------------- /operator/PROJECT: -------------------------------------------------------------------------------- 1 | domain: saas.ecommerce.sample.com 2 | layout: 3 | - go.kubebuilder.io/v3 4 | plugins: 5 | manifests.sdk.operatorframework.io/v2: {} 6 | scorecard.sdk.operatorframework.io/v2: {} 7 | projectName: temp 8 | repo: github.com/multi-tenancy/operator 9 | resources: 10 | - api: 11 | crdVersion: v1 12 | namespaced: true 13 | controller: true 14 | domain: saas.ecommerce.sample.com 15 | group: cache 16 | kind: ECommerceApplication 17 | path: github.com/multi-tenancy/operator/api/v1alpha1 18 | version: v1alpha1 19 | version: "3" 20 | -------------------------------------------------------------------------------- /operator/api/v1alpha1/ecommerceapplication_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // ECommerceApplicationSpec defines the desired state of ECommerceApplication 27 | type ECommerceApplicationSpec struct { 28 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 29 | // Important: Run "make" to regenerate code after modifying this file 30 | 31 | // Foo is an example field of ECommerceApplication. Edit ecommerceapplication_types.go to remove/update 32 | Foo string `json:"foo,omitempty"` 33 | 34 | //+kubebuilder:validation:Minimum=0 35 | // Size is the size of the memcached deployment 36 | Size int32 `json:"size"` 37 | 38 | PostgresSecretName string `json:"postgresSecretName,omitempty"` 39 | AppIdSecretName string `json:"appIdSecretName,omitempty"` 40 | 41 | TenantName string `json:"tenantName,omitempty"` 42 | 43 | IbmCloudOperatorSecretName string `json:"ibmCloudOperatorSecretName,omitempty"` 44 | IbmCloudOperatorSecretNamespace string `json:"ibmCloudOperatorSecretNamespace,omitempty"` 45 | } 46 | 47 | // ECommerceApplicationStatus defines the observed state of ECommerceApplication 48 | type ECommerceApplicationStatus struct { 49 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 50 | // Important: Run "make" to regenerate code after modifying this file 51 | } 52 | 53 | //+kubebuilder:object:root=true 54 | //+kubebuilder:subresource:status 55 | 56 | // ECommerceApplication is the Schema for the ecommerceapplications API 57 | type ECommerceApplication struct { 58 | metav1.TypeMeta `json:",inline"` 59 | metav1.ObjectMeta `json:"metadata,omitempty"` 60 | 61 | Spec ECommerceApplicationSpec `json:"spec,omitempty"` 62 | Status ECommerceApplicationStatus `json:"status,omitempty"` 63 | } 64 | 65 | //+kubebuilder:object:root=true 66 | 67 | // ECommerceApplicationList contains a list of ECommerceApplication 68 | type ECommerceApplicationList struct { 69 | metav1.TypeMeta `json:",inline"` 70 | metav1.ListMeta `json:"metadata,omitempty"` 71 | Items []ECommerceApplication `json:"items"` 72 | } 73 | 74 | func init() { 75 | SchemeBuilder.Register(&ECommerceApplication{}, &ECommerceApplicationList{}) 76 | } 77 | -------------------------------------------------------------------------------- /operator/api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the cache v1alpha1 API group 18 | //+kubebuilder:object:generate=true 19 | //+groupName=cache.saas.ecommerce.sample.com 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "cache.saas.ecommerce.sample.com", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /operator/api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright 2022. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by controller-gen. DO NOT EDIT. 21 | 22 | package v1alpha1 23 | 24 | import ( 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *ECommerceApplication) DeepCopyInto(out *ECommerceApplication) { 30 | *out = *in 31 | out.TypeMeta = in.TypeMeta 32 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 33 | out.Spec = in.Spec 34 | out.Status = in.Status 35 | } 36 | 37 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ECommerceApplication. 38 | func (in *ECommerceApplication) DeepCopy() *ECommerceApplication { 39 | if in == nil { 40 | return nil 41 | } 42 | out := new(ECommerceApplication) 43 | in.DeepCopyInto(out) 44 | return out 45 | } 46 | 47 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 48 | func (in *ECommerceApplication) DeepCopyObject() runtime.Object { 49 | if c := in.DeepCopy(); c != nil { 50 | return c 51 | } 52 | return nil 53 | } 54 | 55 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 56 | func (in *ECommerceApplicationList) DeepCopyInto(out *ECommerceApplicationList) { 57 | *out = *in 58 | out.TypeMeta = in.TypeMeta 59 | in.ListMeta.DeepCopyInto(&out.ListMeta) 60 | if in.Items != nil { 61 | in, out := &in.Items, &out.Items 62 | *out = make([]ECommerceApplication, len(*in)) 63 | for i := range *in { 64 | (*in)[i].DeepCopyInto(&(*out)[i]) 65 | } 66 | } 67 | } 68 | 69 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ECommerceApplicationList. 70 | func (in *ECommerceApplicationList) DeepCopy() *ECommerceApplicationList { 71 | if in == nil { 72 | return nil 73 | } 74 | out := new(ECommerceApplicationList) 75 | in.DeepCopyInto(out) 76 | return out 77 | } 78 | 79 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 80 | func (in *ECommerceApplicationList) DeepCopyObject() runtime.Object { 81 | if c := in.DeepCopy(); c != nil { 82 | return c 83 | } 84 | return nil 85 | } 86 | 87 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 88 | func (in *ECommerceApplicationSpec) DeepCopyInto(out *ECommerceApplicationSpec) { 89 | *out = *in 90 | } 91 | 92 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ECommerceApplicationSpec. 93 | func (in *ECommerceApplicationSpec) DeepCopy() *ECommerceApplicationSpec { 94 | if in == nil { 95 | return nil 96 | } 97 | out := new(ECommerceApplicationSpec) 98 | in.DeepCopyInto(out) 99 | return out 100 | } 101 | 102 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 103 | func (in *ECommerceApplicationStatus) DeepCopyInto(out *ECommerceApplicationStatus) { 104 | *out = *in 105 | } 106 | 107 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ECommerceApplicationStatus. 108 | func (in *ECommerceApplicationStatus) DeepCopy() *ECommerceApplicationStatus { 109 | if in == nil { 110 | return nil 111 | } 112 | out := new(ECommerceApplicationStatus) 113 | in.DeepCopyInto(out) 114 | return out 115 | } 116 | -------------------------------------------------------------------------------- /operator/appIdHelper/appId.go: -------------------------------------------------------------------------------- 1 | package appIdHelper 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "strconv" 12 | "strings" 13 | 14 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 15 | ) 16 | 17 | func GetClientId(managementUrl string, ibmCloudApiKey string, tenantId string, ctx context.Context) (string, error) { 18 | 19 | //$ curl -X POST "https://iam.cloud.ibm.com/identity/token" -H "content-type: application/x-www-form-urlencoded" -H "accept: application/json" -d 'grant_type=urn%3Aibm%3Aparams%3Aoauth%3Agrant-type%3Aapikey&apikey=' > token.json 20 | 21 | /*APPID_MANAGEMENT_URL_ALL_APPLICATIONS=${APPID_MANAGEMENT_URL}/applications 22 | echo $APPID_MANAGEMENT_URL_ALL_APPLICATIONS 23 | result=$(curl -H "Content-Type: application/json" -H "Authorization: Bearer $OAUTHTOKEN" $APPID_MANAGEMENT_URL_ALL_APPLICATIONS) 24 | echo $result 25 | APPID_CLIENT_ID=$(echo $result | sed -n 's|.*"clientId":"\([^"]*\)".*|\1|p') 26 | echo $APPID_CLIENT_ID*/ 27 | 28 | log := ctrllog.FromContext(ctx) 29 | 30 | //clientId := "" 31 | oauth, err := getIbmCloudOauthToken(ibmCloudApiKey, ctx) 32 | if err != nil { 33 | log.Error(err, "Error retrieving IBM Cloud Oauth token") 34 | return "", err 35 | } 36 | 37 | client := &http.Client{} 38 | 39 | //appIdUrl := fmt.Sprintf("%s%s", managementUrl, "applications") 40 | //log.Info(fmt.Sprintf("%s%s", "appIdUrl=", appIdUrl)) 41 | 42 | jsonPayload := []byte(fmt.Sprintf("%s%s%s%s", "{\"tenantId\":", "\"", tenantId, "\"}")) 43 | //jsonPayload := []byte(`{"tenantId":"e38a8715-b8a4-4f1f-82b2-5e434e198768"}`) 44 | 45 | log.Info(fmt.Sprintf("%s%s", "managementUrl=", managementUrl)) 46 | //log.Info(fmt.Sprintf("%s%s", "jsonPayload=", jsonPayload)) 47 | 48 | bearer := fmt.Sprintf("%s%s", "Bearer ", oauth) 49 | log.Info(fmt.Sprintf("%s%s", "bearer=", bearer)) 50 | 51 | //jsonStr = []byte(jsonPayload) 52 | req, err := http.NewRequest("GET", managementUrl, bytes.NewBuffer(jsonPayload)) 53 | req.Header.Set("Content-Type", "application/json") 54 | req.Header.Set("Authorization", bearer) 55 | 56 | resp, err := client.Do(req) 57 | if err != nil { 58 | log.Error(err, "Create GET request failed") 59 | return "", err 60 | } 61 | defer resp.Body.Close() 62 | 63 | log.Info(fmt.Sprintf("%s%s", "response Status:", resp.Status)) 64 | log.Info(fmt.Sprintf("%s%s", "response Headers:", resp.Header)) 65 | //fmt.Println("response Status:", resp.Status) 66 | //fmt.Println("response Headers:", resp.Header) 67 | body, _ := ioutil.ReadAll(resp.Body) 68 | //fmt.Println("response Body:", string(body)) 69 | 70 | log.Info(fmt.Sprintf("%s%s", "returning clientId=", string(body))) 71 | return string(body), nil 72 | 73 | } 74 | 75 | func getIbmCloudOauthToken(apiKey string, ctx context.Context) (string, error) { 76 | 77 | type oauthBindingJSON struct { 78 | Access_token string `json:"access_token"` 79 | Refresh_token string `json:"refresh_token"` 80 | Ims_user_id int `json:"ims_user_id"` 81 | Token_type string `json:"token_type"` 82 | Expires_in int `json:"expires_in"` 83 | Expiration int `json:"expiration"` 84 | Scope string `json:"scope"` 85 | } 86 | 87 | log := ctrllog.FromContext(ctx) 88 | 89 | endpoint := "https://iam.cloud.ibm.com/identity/token" 90 | data := url.Values{} 91 | data.Set("grant_type", "urn:ibm:params:oauth:grant-type:apikey") 92 | 93 | data.Set("apikey", apiKey) 94 | 95 | client := &http.Client{} 96 | 97 | r, err := http.NewRequest("POST", endpoint, strings.NewReader(data.Encode())) // URL-encoded payload 98 | if err != nil { 99 | log.Error(err, "Create request failed") 100 | return "", err 101 | } 102 | r.Header.Add("Content-Type", "application/x-www-form-urlencoded") 103 | r.Header.Add("Content-Length", strconv.Itoa(len(data.Encode()))) 104 | 105 | res, err := client.Do(r) 106 | if err != nil { 107 | log.Error(err, "Post failed") 108 | return "", err 109 | } 110 | log.Info(res.Status) 111 | defer res.Body.Close() 112 | body, err := ioutil.ReadAll(res.Body) 113 | if err != nil { 114 | log.Error(err, "Reading response failed") 115 | return "", err 116 | } 117 | 118 | log.Info(string(body)) 119 | 120 | var jsonData oauthBindingJSON 121 | 122 | err = json.Unmarshal(body, &jsonData) 123 | if err != nil { 124 | log.Error(err, "unmarshall error") 125 | return "", err 126 | } 127 | 128 | return jsonData.Access_token, nil 129 | } 130 | -------------------------------------------------------------------------------- /operator/config/crd/bases/cache.saas.ecommerce.sample.com_ecommerceapplications.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.7.0 8 | creationTimestamp: null 9 | name: ecommerceapplications.cache.saas.ecommerce.sample.com 10 | spec: 11 | group: cache.saas.ecommerce.sample.com 12 | names: 13 | kind: ECommerceApplication 14 | listKind: ECommerceApplicationList 15 | plural: ecommerceapplications 16 | singular: ecommerceapplication 17 | scope: Namespaced 18 | versions: 19 | - name: v1alpha1 20 | schema: 21 | openAPIV3Schema: 22 | description: ECommerceApplication is the Schema for the ecommerceapplications 23 | API 24 | properties: 25 | apiVersion: 26 | description: 'APIVersion defines the versioned schema of this representation 27 | of an object. Servers should convert recognized schemas to the latest 28 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 29 | type: string 30 | kind: 31 | description: 'Kind is a string value representing the REST resource this 32 | object represents. Servers may infer this from the endpoint the client 33 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 34 | type: string 35 | metadata: 36 | type: object 37 | spec: 38 | description: ECommerceApplicationSpec defines the desired state of ECommerceApplication 39 | properties: 40 | appIdSecretName: 41 | type: string 42 | foo: 43 | description: Foo is an example field of ECommerceApplication. Edit 44 | ecommerceapplication_types.go to remove/update 45 | type: string 46 | ibmCloudOperatorSecretName: 47 | type: string 48 | ibmCloudOperatorSecretNamespace: 49 | type: string 50 | postgresSecretName: 51 | type: string 52 | size: 53 | description: Size is the size of the memcached deployment 54 | format: int32 55 | minimum: 0 56 | type: integer 57 | tenantName: 58 | type: string 59 | required: 60 | - size 61 | type: object 62 | status: 63 | description: ECommerceApplicationStatus defines the observed state of 64 | ECommerceApplication 65 | type: object 66 | type: object 67 | served: true 68 | storage: true 69 | subresources: 70 | status: {} 71 | status: 72 | acceptedNames: 73 | kind: "" 74 | plural: "" 75 | conditions: [] 76 | storedVersions: [] 77 | -------------------------------------------------------------------------------- /operator/config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/cache.saas.ecommerce.sample.com_ecommerceapplications.yaml 6 | #+kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | #- patches/webhook_in_ecommerceapplications.yaml 12 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | #- patches/cainjection_in_ecommerceapplications.yaml 17 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 18 | 19 | # the following config is for teaching kustomize how to do kustomization for CRDs. 20 | configurations: 21 | - kustomizeconfig.yaml 22 | -------------------------------------------------------------------------------- /operator/config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /operator/config/crd/patches/cainjection_in_ecommerceapplications.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: ecommerceapplications.cache.saas.ecommerce.sample.com 8 | -------------------------------------------------------------------------------- /operator/config/crd/patches/webhook_in_ecommerceapplications.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: ecommerceapplications.cache.saas.ecommerce.sample.com 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /operator/config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: temp-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: temp- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | #- ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | #- ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | #- manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | #- webhookcainjection_patch.yaml 45 | 46 | # the following config is for teaching kustomize how to do var substitution 47 | vars: 48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 49 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 50 | # objref: 51 | # kind: Certificate 52 | # group: cert-manager.io 53 | # version: v1 54 | # name: serving-cert # this name should match the one in certificate.yaml 55 | # fieldref: 56 | # fieldpath: metadata.namespace 57 | #- name: CERTIFICATE_NAME 58 | # objref: 59 | # kind: Certificate 60 | # group: cert-manager.io 61 | # version: v1 62 | # name: serving-cert # this name should match the one in certificate.yaml 63 | #- name: SERVICE_NAMESPACE # namespace of the service 64 | # objref: 65 | # kind: Service 66 | # version: v1 67 | # name: webhook-service 68 | # fieldref: 69 | # fieldpath: metadata.namespace 70 | #- name: SERVICE_NAME 71 | # objref: 72 | # kind: Service 73 | # version: v1 74 | # name: webhook-service 75 | -------------------------------------------------------------------------------- /operator/config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | protocol: TCP 22 | name: https 23 | - name: manager 24 | args: 25 | - "--health-probe-bind-address=:8081" 26 | - "--metrics-bind-address=127.0.0.1:8080" 27 | - "--leader-elect" 28 | -------------------------------------------------------------------------------- /operator/config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /operator/config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: 27344913.saas.ecommerce.sample.com 12 | -------------------------------------------------------------------------------- /operator/config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | 4 | generatorOptions: 5 | disableNameSuffixHash: true 6 | 7 | configMapGenerator: 8 | - files: 9 | - controller_manager_config.yaml 10 | name: manager-config 11 | apiVersion: kustomize.config.k8s.io/v1beta1 12 | kind: Kustomization 13 | images: 14 | - name: controller 15 | newName: docker.io/nheidloff/controller 16 | newTag: latest 17 | -------------------------------------------------------------------------------- /operator/config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | annotations: 23 | kubectl.kubernetes.io/default-container: manager 24 | labels: 25 | control-plane: controller-manager 26 | spec: 27 | securityContext: 28 | runAsNonRoot: true 29 | containers: 30 | - command: 31 | - /manager 32 | args: 33 | - --leader-elect 34 | image: controller:latest 35 | name: manager 36 | securityContext: 37 | allowPrivilegeEscalation: false 38 | livenessProbe: 39 | httpGet: 40 | path: /healthz 41 | port: 8081 42 | initialDelaySeconds: 15 43 | periodSeconds: 20 44 | readinessProbe: 45 | httpGet: 46 | path: /readyz 47 | port: 8081 48 | initialDelaySeconds: 5 49 | periodSeconds: 10 50 | # TODO(user): Configure the resources accordingly based on the project requirements. 51 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 52 | resources: 53 | limits: 54 | cpu: 500m 55 | memory: 128Mi 56 | requests: 57 | cpu: 10m 58 | memory: 64Mi 59 | serviceAccountName: controller-manager 60 | terminationGracePeriodSeconds: 10 61 | -------------------------------------------------------------------------------- /operator/config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # These resources constitute the fully configured set of manifests 2 | # used to generate the 'manifests/' directory in a bundle. 3 | resources: 4 | - bases/temp.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | - ../scorecard 8 | 9 | # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. 10 | # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. 11 | # These patches remove the unnecessary "cert" volume and its manager container volumeMount. 12 | #patchesJson6902: 13 | #- target: 14 | # group: apps 15 | # version: v1 16 | # kind: Deployment 17 | # name: controller-manager 18 | # namespace: system 19 | # patch: |- 20 | # # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. 21 | # # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. 22 | # - op: remove 23 | # path: /spec/template/spec/containers/1/volumeMounts/0 24 | # # Remove the "cert" volume, since OLM will create and mount a set of certs. 25 | # # Update the indices in this path if adding or removing volumes in the manager's Deployment. 26 | # - op: remove 27 | # path: /spec/template/spec/volumes/0 28 | -------------------------------------------------------------------------------- /operator/config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /operator/config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | scheme: https 15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 16 | tlsConfig: 17 | insecureSkipVerify: true 18 | selector: 19 | matchLabels: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /operator/config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: 7 | - "/metrics" 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /operator/config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: 7 | - authentication.k8s.io 8 | resources: 9 | - tokenreviews 10 | verbs: 11 | - create 12 | - apiGroups: 13 | - authorization.k8s.io 14 | resources: 15 | - subjectaccessreviews 16 | verbs: 17 | - create 18 | -------------------------------------------------------------------------------- /operator/config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /operator/config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | control-plane: controller-manager 16 | -------------------------------------------------------------------------------- /operator/config/rbac/ecommerceapplication_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit ecommerceapplications. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ecommerceapplication-editor-role 6 | rules: 7 | - apiGroups: 8 | - cache.saas.ecommerce.sample.com 9 | resources: 10 | - ecommerceapplications 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - cache.saas.ecommerce.sample.com 21 | resources: 22 | - ecommerceapplications/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /operator/config/rbac/ecommerceapplication_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view ecommerceapplications. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ecommerceapplication-viewer-role 6 | rules: 7 | - apiGroups: 8 | - cache.saas.ecommerce.sample.com 9 | resources: 10 | - ecommerceapplications 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - cache.saas.ecommerce.sample.com 17 | resources: 18 | - ecommerceapplications/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /operator/config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /operator/config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /operator/config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /operator/config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: manager-role 8 | rules: 9 | - apiGroups: 10 | - apps 11 | resources: 12 | - deployments 13 | verbs: 14 | - create 15 | - delete 16 | - get 17 | - list 18 | - patch 19 | - update 20 | - watch 21 | - apiGroups: 22 | - cache.saas.ecommerce.sample.com 23 | resources: 24 | - ecommerceapplications 25 | verbs: 26 | - create 27 | - delete 28 | - get 29 | - list 30 | - patch 31 | - update 32 | - watch 33 | - apiGroups: 34 | - cache.saas.ecommerce.sample.com 35 | resources: 36 | - ecommerceapplications/finalizers 37 | verbs: 38 | - update 39 | - apiGroups: 40 | - cache.saas.ecommerce.sample.com 41 | resources: 42 | - ecommerceapplications/status 43 | verbs: 44 | - get 45 | - patch 46 | - update 47 | - apiGroups: 48 | - "" 49 | resources: 50 | - pods 51 | verbs: 52 | - get 53 | - list 54 | - watch 55 | -------------------------------------------------------------------------------- /operator/config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /operator/config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /operator/config/samples/cache_v1alpha1_ecommerceapplication.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cache.saas.ecommerce.sample.com/v1alpha1 2 | kind: ECommerceApplication 3 | metadata: 4 | name: ecommerceapplication-ten-c 5 | spec: 6 | size: 1 7 | appIdSecretName: multi-tenancy-appid-ten-c-secret 8 | postgresSecretName: multi-tenancy-pg-ten-c-secret 9 | tenantName: ten-c 10 | ibmCloudOperatorSecretName: ibmcloud-operator-secret 11 | ibmCloudOperatorSecretNamespace: default 12 | --- 13 | apiVersion: ibmcloud.ibm.com/v1 14 | kind: Service 15 | metadata: 16 | name: multi-tenancy-pg-ten-d 17 | spec: 18 | plan: standard 19 | serviceClass: databases-for-postgresql 20 | --- 21 | apiVersion: ibmcloud.ibm.com/v1 22 | kind: Service 23 | metadata: 24 | name: multi-tenancy-appid-ten-c 25 | spec: 26 | plan: graduated-tier 27 | serviceClass: appid 28 | --- 29 | apiVersion: ibmcloud.ibm.com/v1 30 | kind: Binding 31 | metadata: 32 | name: binding-multi-tenancy-pg-ten-c 33 | spec: 34 | serviceName: multi-tenancy-pg-ten-d 35 | secretName: multi-tenancy-pg-ten-c-secret 36 | --- 37 | apiVersion: ibmcloud.ibm.com/v1 38 | kind: Binding 39 | metadata: 40 | name: binding-multi-tenancy-appid-ten-c 41 | spec: 42 | serviceName: multi-tenancy-appid-ten-c 43 | secretName: multi-tenancy-appid-ten-c-secret 44 | -------------------------------------------------------------------------------- /operator/config/samples/job.yml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: pg 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: pg 10 | image: bash 11 | args: 12 | - /bin/sh 13 | - -c 14 | - date; echo Hello from the Kubernetes cluster 15 | restartPolicy: Never 16 | backoffLimit: 4 -------------------------------------------------------------------------------- /operator/config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - cache_v1alpha1_ecommerceapplication.yaml 4 | #+kubebuilder:scaffold:manifestskustomizesamples 5 | -------------------------------------------------------------------------------- /operator/config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /operator/config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | #+kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /operator/config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.16.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /operator/config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.16.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.16.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.16.0 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.16.0 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.16.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /operator/controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | "k8s.io/client-go/kubernetes/scheme" 26 | "k8s.io/client-go/rest" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/envtest" 29 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 30 | logf "sigs.k8s.io/controller-runtime/pkg/log" 31 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 32 | 33 | cachev1alpha1 "github.com/multi-tenancy/operator/api/v1alpha1" 34 | //+kubebuilder:scaffold:imports 35 | ) 36 | 37 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 38 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 39 | 40 | var cfg *rest.Config 41 | var k8sClient client.Client 42 | var testEnv *envtest.Environment 43 | 44 | func TestAPIs(t *testing.T) { 45 | RegisterFailHandler(Fail) 46 | 47 | RunSpecsWithDefaultAndCustomReporters(t, 48 | "Controller Suite", 49 | []Reporter{printer.NewlineReporter{}}) 50 | } 51 | 52 | var _ = BeforeSuite(func() { 53 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 54 | 55 | By("bootstrapping test environment") 56 | testEnv = &envtest.Environment{ 57 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 58 | ErrorIfCRDPathMissing: true, 59 | } 60 | 61 | cfg, err := testEnv.Start() 62 | Expect(err).NotTo(HaveOccurred()) 63 | Expect(cfg).NotTo(BeNil()) 64 | 65 | err = cachev1alpha1.AddToScheme(scheme.Scheme) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | //+kubebuilder:scaffold:scheme 69 | 70 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 71 | Expect(err).NotTo(HaveOccurred()) 72 | Expect(k8sClient).NotTo(BeNil()) 73 | 74 | }, 60) 75 | 76 | var _ = AfterSuite(func() { 77 | By("tearing down the test environment") 78 | err := testEnv.Stop() 79 | Expect(err).NotTo(HaveOccurred()) 80 | }) 81 | -------------------------------------------------------------------------------- /operator/ecommerceapplication/tmp/copy-secret.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export DEVELOPER_1_PROJECT=deleeuw 4 | export DEVELOPER_2_PROJECT=saas-operator-development-thomas 5 | 6 | export APPID_SECRET_NAME=multi-tenancy-appid-ten-f-secret 7 | export POSTGRES_SECRET_NAME=multi-tenancy-pg-ten-f-secret 8 | 9 | echo "Set Project $DEVELOPER_1_PROJECT" 10 | oc project $DEVELOPER_1_PROJECT 11 | oc get secret $APPID_SECRET_NAME -o yaml 12 | 13 | echo "Extract secrect for AppID" 14 | mkdir appid_secrets 15 | cd appid_secrets 16 | oc extract secret/$APPID_SECRET_NAME --to=. 17 | cd .. 18 | 19 | echo "Extract secrect for Postgres" 20 | mkdir postgres_secrets 21 | cd postgres_secrets 22 | oc extract secret/$POSTGRES_SECRET_NAME --to=. 23 | cd .. 24 | 25 | echo "Set Project $DEVELOPER_2_PROJECT" 26 | oc project $DEVELOPER_2_PROJECT 27 | 28 | echo "Create postgres secret: $APPID_SECRET_NAME" 29 | cd appid_secrets 30 | kubectl create secret generic $APPID_SECRET_NAME \ 31 | --from-file=apikey=../appid_secrets/apikey \ 32 | --from-file=appidServiceEndpoint=../appid_secrets/appidServiceEndpoint \ 33 | --from-file=clientId=../appid_secrets/clientId \ 34 | --from-file=discoveryEndpoint=../appid_secrets/discoveryEndpoint \ 35 | --from-file=iam_apikey_description=../appid_secrets/iam_apikey_description \ 36 | --from-file=iam_apikey_name=../appid_secrets/iam_apikey_name \ 37 | --from-file=iam_role_crn=../appid_secrets/iam_role_crn \ 38 | --from-file=iam_serviceid_crn=../appid_secrets/iam_serviceid_crn \ 39 | --from-file=managementUrl=../appid_secrets/managementUrl \ 40 | --from-file=oauthServerUrl=../appid_secrets/oauthServerUrl \ 41 | --from-file=secret=../appid_secrets/secret \ 42 | --from-file=tenantId=../appid_secrets/tenantId 43 | cd .. 44 | rm -r appid_secrets 45 | 46 | echo "Create postgres secret: $POSTGRES_SECRET_NAME" 47 | cd postgres_secrets 48 | kubectl create secret generic $POSTGRES_SECRET_NAME \ 49 | --from-file=connection=../postgres_secrets/connection \ 50 | --from-file=instance_administration_api=../postgres_secrets/instance_administration_api 51 | cd .. 52 | rm -r postgres_secrets 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /operator/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/multi-tenancy/operator 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/jackc/pgx/v4 v4.15.0 7 | github.com/onsi/ginkgo v1.16.4 8 | github.com/onsi/gomega v1.15.0 9 | k8s.io/api v0.22.1 10 | k8s.io/apimachinery v0.22.1 11 | k8s.io/client-go v0.22.1 12 | sigs.k8s.io/controller-runtime v0.10.0 13 | ) 14 | -------------------------------------------------------------------------------- /operator/hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /operator/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | 23 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 24 | // to ensure that exec-entrypoint and run can make use of them. 25 | _ "k8s.io/client-go/plugin/pkg/client/auth" 26 | 27 | "k8s.io/apimachinery/pkg/runtime" 28 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 29 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 30 | ctrl "sigs.k8s.io/controller-runtime" 31 | "sigs.k8s.io/controller-runtime/pkg/healthz" 32 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 33 | 34 | cachev1alpha1 "github.com/multi-tenancy/operator/api/v1alpha1" 35 | "github.com/multi-tenancy/operator/controllers" 36 | //+kubebuilder:scaffold:imports 37 | ) 38 | 39 | var ( 40 | scheme = runtime.NewScheme() 41 | setupLog = ctrl.Log.WithName("setup") 42 | ) 43 | 44 | func init() { 45 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 46 | 47 | utilruntime.Must(cachev1alpha1.AddToScheme(scheme)) 48 | //+kubebuilder:scaffold:scheme 49 | } 50 | 51 | func main() { 52 | var metricsAddr string 53 | var enableLeaderElection bool 54 | var probeAddr string 55 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 56 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 57 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 58 | "Enable leader election for controller manager. "+ 59 | "Enabling this will ensure there is only one active controller manager.") 60 | opts := zap.Options{ 61 | Development: true, 62 | } 63 | opts.BindFlags(flag.CommandLine) 64 | flag.Parse() 65 | 66 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 67 | 68 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 69 | Scheme: scheme, 70 | MetricsBindAddress: metricsAddr, 71 | Port: 9443, 72 | HealthProbeBindAddress: probeAddr, 73 | LeaderElection: enableLeaderElection, 74 | LeaderElectionID: "27344913.saas.ecommerce.sample.com", 75 | }) 76 | if err != nil { 77 | setupLog.Error(err, "unable to start manager") 78 | os.Exit(1) 79 | } 80 | 81 | if err = (&controllers.ECommerceApplicationReconciler{ 82 | Client: mgr.GetClient(), 83 | Scheme: mgr.GetScheme(), 84 | }).SetupWithManager(mgr); err != nil { 85 | setupLog.Error(err, "unable to create controller", "controller", "ECommerceApplication") 86 | os.Exit(1) 87 | } 88 | //+kubebuilder:scaffold:builder 89 | 90 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 91 | setupLog.Error(err, "unable to set up health check") 92 | os.Exit(1) 93 | } 94 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 95 | setupLog.Error(err, "unable to set up ready check") 96 | os.Exit(1) 97 | } 98 | 99 | setupLog.Info("starting manager") 100 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 101 | setupLog.Error(err, "problem running manager") 102 | os.Exit(1) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /operator/postgres.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ibmcloud.ibm.com/v1 2 | kind: Service 3 | metadata: 4 | name: multi-tenancy-pg-ten-d 5 | spec: 6 | plan: standard 7 | serviceClass: databases-for-postgresql 8 | --- 9 | apiVersion: ibmcloud.ibm.com/v1 10 | kind: Binding 11 | metadata: 12 | name: binding-multi-tenancy-pg-ten-d 13 | spec: 14 | serviceName: multi-tenancy-pg-ten-d 15 | secretName: multi-tenancy-pg-ten-d-secret -------------------------------------------------------------------------------- /scripts/env-config-template.js: -------------------------------------------------------------------------------- 1 | // used as 'environment' variables, 2 | // Single WebPage 3 | // App ID 4 | window.VUE_APPID_CLIENT_ID='APPID_CLIENT_ID_TEMPLATE' 5 | window.VUE_APPID_DISCOVERYENDPOINT='APPID_DISCOVERYENDPOINT_TEMPLATE' 6 | // Application URLs 7 | window.VUE_APP_API_URL_PRODUCTS="http://localhost:8081/category" 8 | window.VUE_APP_API_URL_CATEGORIES="http://localhost:8081/category" 9 | // window.VUE_APP_API_URL_ORDERS="http://localhost:8081/customer/orders" 10 | // Configurations 11 | window.VUE_APP_CATEGORY_NAME="Movies" 12 | window.VUE_APP_HEADLINE="Electronic and Movie Depot" -------------------------------------------------------------------------------- /scripts/run-locally-backend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROOT_PROJECT=multi-tenancy 4 | FRONTEND_SOURCEFOLDER=multi-tenancy-frontend 5 | BACKEND_SOURCEFOLDER=multi-tenancy-backend 6 | 7 | # change the standard output 8 | exec 3>&1 9 | 10 | # ********************************************************************************** 11 | # Functions definition 12 | # ********************************************************************************** 13 | 14 | function setROOT_PATH() { 15 | echo "************************************" 16 | echo " Set ROOT_PATH" 17 | echo " REMEMBER: You must start the script from the project root folder as written in the documentation!" 18 | echo "************************************" 19 | cd ../ 20 | export ROOT_PATH=$(PWD) 21 | echo "Path: $ROOT_PATH" 22 | } 23 | 24 | function resetPath() { 25 | echo "************************************" 26 | echo " Reset path" 27 | echo "************************************" 28 | cd $ROOT_PATH/$ROOT_PROJECT 29 | echo "" 30 | } 31 | 32 | function _out() { 33 | echo "$(date +'%F %H:%M:%S') $@" 34 | } 35 | 36 | function triggerScript() { 37 | 38 | echo "1. Have you created an App ID instance?" 39 | echo "Copy the credentials in local.env: APPID_CLIENT_ID, APPID_AUTH_SERVER_URL" 40 | echo "" 41 | echo "2. Have you created a Postgres instance?" 42 | echo "Copy the credentials in local.env: POSTGRES_USERNAME, POSTGRES_PASSWORD, POSTGRES_URL, POSTGRES_CERTIFICATE_FILE_NAME" 43 | echo "Copy the Postgres certificate in multi-tenancy-backend/src/main/resources/certificates" 44 | echo "Starting backend service locally ..." 45 | echo curl \"http://localhost:8081/category\" 46 | echo curl \"http://localhost:8081/category/2/products\" 47 | echo "/category will return a response code '401' not authorized!" 48 | echo "/category/2/products will return data from Postgres" 49 | 50 | cd $ROOT_PATH/$BACKEND_SOURCEFOLDER 51 | CFG_FILE=$ROOT_PATH/$ROOT_PROJECT/local.env 52 | if [ ! -f $CFG_FILE ]; then 53 | _out Config file local.env is missing! 54 | exit 1 55 | fi 56 | 57 | set -o allexport 58 | source $CFG_FILE 59 | 60 | APPID_AUTH_SERVER_URL=${APPID_AUTH_SERVER_URL} 61 | APPID_CLIENT_ID=${APPID_CLIENT_ID} 62 | 63 | POSTGRES_URL=$(echo $POSTGRES_URL| cut -d'?' -f 1) 64 | CERTIFICATE_PATH=$ROOT_PATH/$BACKEND_SOURCEFOLDER/src/main/resources/certificates/cloud-postgres-cert 65 | cp $ROOT_PATH/$BACKEND_SOURCEFOLDER/src/main/resources/certificates/$POSTGRES_CERTIFICATE_FILE_NAME $CERTIFICATE_PATH 66 | POSTGRES_URL="$POSTGRES_URL?sslmode=verify-full&sslrootcert=$CERTIFICATE_PATH" 67 | 68 | cd $ROOT_PATH/$BACKEND_SOURCEFOLDER 69 | mvn clean package 70 | mvn quarkus:dev 71 | } 72 | 73 | # ********************************************************************************** 74 | # Execution 75 | # ********************************************************************************** 76 | 77 | setROOT_PATH 78 | triggerScript 79 | resetPath -------------------------------------------------------------------------------- /scripts/run-locally-container-backend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROOT_PROJECT=multi-tenancy 4 | FRONTEND_SOURCEFOLDER=multi-tenancy-frontend 5 | BACKEND_SOURCEFOLDER=multi-tenancy-backend 6 | 7 | # change the standard output 8 | exec 3>&1 9 | 10 | # ********************************************************************************** 11 | # Functions definition 12 | # ********************************************************************************** 13 | 14 | function setROOT_PATH() { 15 | echo "************************************" 16 | echo " Set ROOT_PATH" 17 | echo " REMEMBER: You must start the script from the project root folder as written in the documentation!" 18 | echo "************************************" 19 | cd ../ 20 | export ROOT_PATH=$(PWD) 21 | echo "Path: $ROOT_PATH" 22 | } 23 | 24 | function resetPath() { 25 | echo "************************************" 26 | echo " Reset path" 27 | echo "************************************" 28 | cd $ROOT_PATH/$ROOT_PROJECT 29 | echo "" 30 | } 31 | 32 | function _out() { 33 | echo "$(date +'%F %H:%M:%S') $@" 34 | } 35 | 36 | function triggerScript() { 37 | 38 | echo "1. Have you created an App ID instance?" 39 | echo "Copy the credentials in local.env: APPID_CLIENT_ID, APPID_AUTH_SERVER_URL" 40 | echo "" 41 | echo "2. Have you created a Postgres instance?" 42 | echo "Copy the credentials in local.env: POSTGRES_USERNAME, POSTGRES_PASSWORD, POSTGRES_URL, POSTGRES_CERTIFICATE_FILE_NAME" 43 | echo "Copy the Postgres certificate in multi-tenancy-backend/src/main/resources/certificates" 44 | echo "Starting backend service locally in a container ..." 45 | echo curl \"http://localhost:8081/category\" 46 | echo curl \"http://localhost:8081/category/2/products\" 47 | echo "/category will return a response code '401' not authorized!" 48 | echo "/category/2/products will return data from Postgres" 49 | 50 | CFG_FILE=${ROOT_PATH}/$ROOT_PROJECT/local.env 51 | if [ ! -f $CFG_FILE ]; then 52 | _out Config file local.env is missing! 53 | exit 1 54 | fi 55 | 56 | cd ${ROOT_PATH}/$BACKEND_SOURCEFOLDER 57 | set -o allexport 58 | source $CFG_FILE 59 | 60 | POSTGRES_URL=$(echo $POSTGRES_URL| cut -d'?' -f 1) 61 | CERTIFICATE_PATH=/cloud-postgres-cert 62 | POSTGRES_URL="$POSTGRES_URL?sslmode=verify-full&sslrootcert=$CERTIFICATE_PATH" 63 | APPID_AUTH_SERVER_URL=${APPID_AUTH_SERVER_URL} 64 | APPID_CLIENT_ID=${APPID_CLIENT_ID} 65 | 66 | POSTGRES_CERTIFICATE_DATA=$(<$ROOT_PATH/$BACKEND_SOURCEFOLDER/src/main/resources/certificates/${POSTGRES_CERTIFICATE_FILE_NAME}) 67 | 68 | cd ${ROOT_PATH}/$BACKEND_SOURCEFOLDER 69 | podman container stop service-catalog --ignore 70 | podman container rm -f service-catalog --ignore 71 | podman build --file Dockerfile --tag service-catalog 72 | 73 | podman run --name=service-catalog \ 74 | -it \ 75 | -e POSTGRES_CERTIFICATE_DATA="${POSTGRES_CERTIFICATE_DATA}" \ 76 | -e POSTGRES_USERNAME="${POSTGRES_USERNAME}" \ 77 | -e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \ 78 | -e POSTGRES_URL="${POSTGRES_URL}" \ 79 | -e APPID_AUTH_SERVER_URL="${APPID_AUTH_SERVER_URL}" \ 80 | -e APPID_CLIENT_ID="${APPID_CLIENT_ID}" \ 81 | -p 8081:8081/tcp \ 82 | localhost/service-catalog:latest 83 | } 84 | 85 | # ********************************************************************************** 86 | # Execution 87 | # ********************************************************************************** 88 | 89 | setROOT_PATH 90 | triggerScript 91 | resetPath -------------------------------------------------------------------------------- /scripts/run-locally-container-frontend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # **************** Global variables set by parameters 4 | ROOT_PROJECT=multi-tenancy 5 | FRONTEND_SOURCEFOLDER=multi-tenancy-frontend 6 | BACKEND_SOURCEFOLDER=multi-tenancy-backend 7 | 8 | vue_env_config=./public/env-config.js 9 | vue_env_config_template=./scripts/env-config-template.js 10 | service_catalog_categories_endpoint="http://localhost:8081/category" 11 | service_catalog_product_endpoint="http://localhost:8081/category" 12 | 13 | # change the standard output 14 | exec 3>&1 15 | 16 | # ********************************************************************************** 17 | # Functions definition 18 | # ********************************************************************************** 19 | 20 | function setROOT_PATH() { 21 | echo "************************************" 22 | echo " Set ROOT_PATH" 23 | echo " REMEMBER: You must start the script from the project root folder as written in the documentation!" 24 | echo "************************************" 25 | cd ../ 26 | export ROOT_PATH=$(PWD) 27 | echo "Path: $ROOT_PATH" 28 | } 29 | 30 | function resetPath() { 31 | echo "************************************" 32 | echo " Reset path" 33 | echo "************************************" 34 | cd $ROOT_PATH/$ROOT_PROJECT 35 | echo "" 36 | } 37 | 38 | function _out() { 39 | echo "$(date +'%F %H:%M:%S') $@" 40 | } 41 | 42 | function triggerScript() { 43 | 44 | echo "********************" 45 | echo "Have you created an App ID instance?" 46 | echo "Copy the credentials in local.env: APPID_CLIENT_ID, APPID_DISCOVERYENDPOINT" 47 | 48 | CFG_FILE=${ROOT_PATH}/$ROOT_PROJECT/local.env 49 | if [ ! -f $CFG_FILE ]; then 50 | _out Config file local.env is missing! 51 | exit 1 52 | fi 53 | source $CFG_FILE 54 | 55 | cd ${ROOT_PATH}/$FRONTEND_SOURCEFOLDER 56 | echo "********************" 57 | echo "Clean-up container and image" 58 | podman container stop frontend-container --ignore 59 | podman container rm -f frontend-container --ignore 60 | #podman image rm -f 'frontend:v1' 61 | 62 | echo "********************" 63 | echo "Build container" 64 | podman build --file Dockerfile --tag 'frontend:v1' 65 | 66 | echo "********************" 67 | echo "Starting container with App ID configuration" 68 | echo " - $APPID_CLIENT_ID" 69 | echo " - $APPID_DISCOVERYENDPOINT" 70 | 71 | podman run --name=frontend-container \ 72 | -it \ 73 | -e VUE_APPID_CLIENT_ID=$APPID_CLIENT_ID \ 74 | -e VUE_APPID_DISCOVERYENDPOINT=$APPID_DISCOVERYENDPOINT \ 75 | -e VUE_APP_API_URL_PRODUCTS="${service_catalog_product_endpoint}" \ 76 | -e VUE_APP_API_URL_CATEGORIES="${service_catalog_product_endpoint}" \ 77 | -e VUE_APP_CATEGORY_NAME='Movies' \ 78 | -e VUE_APP_HEADLINE='Frontend Docker' \ 79 | -p 8080:8080/tcp \ 80 | "localhost/frontend:v1" 81 | } 82 | 83 | # ********************************************************************************** 84 | # Execution 85 | # ********************************************************************************** 86 | 87 | setROOT_PATH 88 | triggerScript 89 | resetPath -------------------------------------------------------------------------------- /scripts/run-locally-frontend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # **************** Global variables set by parameters 4 | ROOT_PROJECT=multi-tenancy 5 | FRONTEND_SOURCEFOLDER=multi-tenancy-frontend 6 | BACKEND_SOURCEFOLDER=multi-tenancy-backend 7 | 8 | # change the standard output 9 | exec 3>&1 10 | 11 | # ********************************************************************************** 12 | # Functions definition 13 | # ********************************************************************************** 14 | 15 | function setROOT_PATH() { 16 | echo "************************************" 17 | echo " Set ROOT_PATH" 18 | echo " REMEMBER: You must start the script from the project root folder as written in the documentation!" 19 | echo "************************************" 20 | cd ../ 21 | export ROOT_PATH=$(PWD) 22 | echo "Path: $ROOT_PATH" 23 | } 24 | 25 | function resetPath() { 26 | echo "************************************" 27 | echo " Reset path" 28 | echo "************************************" 29 | cd $ROOT_PATH/$ROOT_PROJECT/ 30 | echo "" 31 | } 32 | 33 | function _out() { 34 | echo "$(date +'%F %H:%M:%S') $@" 35 | } 36 | 37 | function triggerScript() { 38 | 39 | vue_env_config=$ROOT_PATH/$FRONTEND_SOURCEFOLDER/public/env-config.js 40 | vue_env_config_template=$ROOT_PATH/$ROOT_PROJECT/scripts/env-config-template.js 41 | 42 | echo "Have you created an App ID instance?" 43 | echo "Copy the credentials in local.env: APPID_CLIENT_ID, APPID_DISCOVERYENDPOINT" 44 | 45 | CFG_FILE=$ROOT_PATH/$ROOT_PROJECT/local.env 46 | if [ ! -f $CFG_FILE ]; then 47 | _out Config file local.env is missing! 48 | exit 1 49 | fi 50 | 51 | source $CFG_FILE 52 | 53 | echo "Creating App ID configuration $vue-env-config" 54 | echo " - $APPID_CLIENT_ID" 55 | echo " - $APPID_DISCOVERYENDPOINT" 56 | 57 | cd $ROOT_PATH/$FRONTEND_SOURCEFOLDER 58 | sed -e "s+APPID_CLIENT_ID_TEMPLATE+${APPID_CLIENT_ID}+g" \ 59 | -e "s+APPID_DISCOVERYENDPOINT_TEMPLATE+${APPID_DISCOVERYENDPOINT}+g" \ 60 | "${vue_env_config_template}" > "${vue_env_config}" 61 | 62 | echo "Starting frontend locally ..." 63 | cd $ROOT_PATH/$FRONTEND_SOURCEFOLDER 64 | npm install 65 | npm run serve 66 | } 67 | 68 | # ********************************************************************************** 69 | # Execution 70 | # ********************************************************************************** 71 | 72 | setROOT_PATH 73 | triggerScript 74 | resetPath --------------------------------------------------------------------------------