├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── gitops ├── application-plane │ └── production │ │ ├── pooled-envs │ │ ├── kustomization.yaml │ │ └── pool-1.yaml │ │ ├── tenants │ │ ├── advanced │ │ │ ├── dummy-configmap.yaml │ │ │ └── kustomization.yaml │ │ ├── basic │ │ │ ├── dummy-configmap.yaml │ │ │ └── kustomization.yaml │ │ ├── kustomization.yaml │ │ └── premium │ │ │ ├── dummy-configmap.yaml │ │ │ └── kustomization.yaml │ │ └── tier-templates │ │ ├── advanced_tenant_template.yaml │ │ ├── basic_env_template.yaml │ │ ├── basic_tenant_template.yaml │ │ └── premium_tenant_template.yaml ├── clusters │ └── production │ │ ├── control-plane.yaml │ │ ├── dependencies.yaml │ │ ├── infrastructure.yaml │ │ ├── pooled-envs.yaml │ │ ├── sources.yaml │ │ └── tenants.yaml ├── control-plane │ └── production │ │ ├── applications │ │ ├── 00-onboarding-service.yaml │ │ └── kustomization.yaml │ │ ├── kustomization.yaml │ │ └── workflows │ │ ├── event-bus.yaml │ │ ├── kustomization.yaml │ │ ├── tenant-deployment-sensor.yaml.template │ │ ├── tenant-deployment-workflow-template.yaml.template │ │ ├── tenant-offboarding-sensor.yaml.template │ │ ├── tenant-offboarding-workflow-template.yaml.template │ │ ├── tenant-onboarding-sensor.yaml.template │ │ └── tenant-onboarding-workflow-template.yaml.template └── infrastructure │ ├── base │ └── sources │ │ ├── application-chart-helm.yaml.template │ │ ├── argo-workflows-helm.yaml │ │ ├── capacitor-web-ui.yaml │ │ ├── consumer-image-automation.yaml.template │ │ ├── cronjob-ecr-updater.yaml.template │ │ ├── eks-charts-helm.yaml │ │ ├── karpenter-helm.yaml │ │ ├── kubecost-helm.yaml │ │ ├── kustomization.yaml │ │ ├── metric-server-helm.yaml │ │ ├── payments-image-automation.yaml.template │ │ ├── producer-image-automation.yaml.template │ │ ├── tenant-chart-helm.yaml.template │ │ ├── terraform-git-v1.yaml.template │ │ └── tf-controller-helm.yaml │ └── production │ ├── 01-metric-server.yaml │ ├── 02-karpenter.yaml.template │ ├── 03-argo-workflows.yaml.template │ ├── 04-lb-controller.yaml.template │ ├── 05-kubecost.yaml │ ├── 06-argo-events.yaml.template │ ├── 07-tf-controller.yaml.template │ ├── dependencies │ ├── 01-default-karpenter-config.yaml.template │ ├── 02-application-karpenter-config.yaml │ └── kustomization.yaml │ └── kustomization.yaml ├── helm-charts ├── application-chart │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── deployment.yaml │ │ ├── hpa.yaml │ │ ├── ingress.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── tests │ │ │ └── test-connection.yaml │ └── values.yaml └── helm-tenant-chart │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ ├── deployment.yaml │ ├── hpa.yaml │ ├── ingress.yaml │ ├── service.yaml │ ├── serviceaccount.yaml │ └── terraform.yaml │ └── values.yaml.template ├── helpers └── saas-gitops-cloudformation.yaml ├── scripts ├── cleanup.sh ├── resize-cloud9-ebs-vol.sh └── tenant-control.sh ├── static └── github-repo-template.png ├── tenant-microservices ├── consumer │ ├── Dockerfile │ ├── buildspec.yml │ ├── consumer.py │ └── requirements.txt ├── payments │ ├── Dockerfile │ ├── buildspec.yml │ ├── payments.py │ └── requirements.txt └── producer │ ├── Dockerfile │ ├── buildspec.yml │ ├── producer.py │ └── requirements.txt ├── terraform ├── README.md ├── finish_cf_stack.sh ├── install.sh ├── modules │ ├── codebuild │ │ ├── README.md │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── variables.tf │ │ └── versions.tf │ ├── codepipeline │ │ ├── README.md │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── variables.tf │ │ └── versions.tf │ ├── flux_cd │ │ ├── README.md │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── variables.tf │ │ └── versions.tf │ ├── gitops-saas-infra │ │ ├── apps_needs.tf │ │ ├── data.tf │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ └── tenant-apps │ │ ├── README.md │ │ ├── data.tf │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── variables.tf │ │ └── versions.tf ├── quick_fix_flux.sh └── workshop │ ├── main.tf │ ├── outputs.tf │ ├── saas_gitops.tf │ ├── templating.sh │ └── variables.tf └── workflow-scripts ├── 00-validate-tenant.sh ├── 01-tenant-clone-repo.sh ├── 02-tenant-onboarding.sh ├── 03-tenant-deployment.sh ├── 04-tenant-offboarding.sh ├── Dockerfile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Chart ignore 2 | *chart*.tgz 3 | flux-secrets.yaml 4 | # Created by https://www.toptal.com/developers/gitignore/api/terraform 5 | # Edit at https://www.toptal.com/developers/gitignore?templates=terraform 6 | *.terraform.lock.hcl 7 | ### Terraform ### 8 | # Local .terraform directories 9 | **/.terraform/* 10 | tenant-infra-main.tf 11 | # .tfstate files 12 | *.tfstate 13 | *.tfstate.* 14 | output.json 15 | # Crash log files 16 | crash.log 17 | crash.*.log 18 | secrets.tf 19 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 20 | # password, private keys, and other secrets. These should not be part of version 21 | # control as they are data points which are potentially sensitive and subject 22 | # to change depending on the environment. 23 | *.tfvars 24 | *.tfvars.json 25 | 26 | # Ignore override files as they are usually used to override resources locally and so 27 | # are not checked in 28 | override.tf 29 | override.tf.json 30 | *_override.tf 31 | *_override.tf.json 32 | 33 | # Include override files you do wish to add to version control using negated pattern 34 | # !example_override.tf 35 | 36 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 37 | # example: *tfplan* 38 | 39 | # Ignore CLI configuration files 40 | .terraformrc 41 | terraform.rc 42 | 43 | .kube/ 44 | # End of https://www.toptal.com/developers/gitignore/api/terraform 45 | 46 | .DS_Store 47 | .idea 48 | .build 49 | *.vscode 50 | .config -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EKS SaaS GitOps Workshop Source Repository 2 | 3 | Welcome to the source repository for the **EKS SaaS GitOps Workshop**. This repository serves as a template to generate multiple other repositories, enabling a hands-on GitOps experience using Amazon EKS. It provides you with all the necessary patterns, configurations, and scripts to deploy a scalable SaaS application. 4 | 5 | ## Repository Overview 6 | 7 | This template repository is the foundation for creating individual repositories for the components of your SaaS architecture, as shown in the diagram below. These repositories include configurations for various microservices like Producer, Consumer, and Payments, each tailored to demonstrate best practices in a GitOps workflow. 8 | 9 | ![Repository Structure Diagram](./static/github-repo-template.png) 10 | 11 | This repository is organized to facilitate a hands-on learning experience, structured as follows: 12 | 13 | - **`/gitops`**: Contains GitOps configurations and templates for setting up the application plane, clusters, control plane, and infrastructure necessary for the SaaS architecture. 14 | - **`/helpers`**: Includes CloudFormation templates to assist in setting up the required AWS resources. (Only used if want to deploy Cloud9 and run this architecture all in AWS) 15 | - **`/helm-charts`**: Houses Helm chart definitions for deploying tenant-specific resources within the Kubernetes cluster and shared services resources. 16 | - **`/tenant-microservices`**: Contains the source code and Dockerfiles for the sample microservices used in the workshop (consumer, payments, producer). 17 | - **`/terraform`**: Features Terraform modules and scripts for provisioning the AWS infrastructure and Kubernetes resources. Detailed setup instructions are provided within this folder's README.md. 18 | - **`/workflow-scripts`**: Provides scripts to automate the workflow for tenant onboarding and application deployment within the GitOps framework. 19 | 20 | ## Getting Started 21 | 22 | Begin your journey to deploying a SaaS architecture on Amazon EKS by closely following the detailed instructions provided in the [/terraform folder's README.](terraform/README.md) This guide is your starting point for setting up the AWS environment, configuring your Kubernetes cluster, and applying GitOps principles for efficient resource management. 23 | 24 | ## Contributing 25 | 26 | Your contributions are welcome! If you'd like to improve the workshop or suggest changes, please feel free to submit issues or pull requests. 27 | 28 | ## Code of Conduct & Contributing 29 | 30 | We value your input and contributions! Please review our [Code of Conduct](CODE_OF_CONDUCT.md) and [Contributing Guidelines](CONTRIBUTING.md) for how to participate in making this project better. 31 | 32 | ## License 33 | 34 | This project is licensed under the terms of the [MIT license](LICENSE). -------------------------------------------------------------------------------- /gitops/application-plane/production/pooled-envs/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - pool-1.yaml -------------------------------------------------------------------------------- /gitops/application-plane/production/pooled-envs/pool-1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: pool-1 5 | --- 6 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 7 | kind: HelmRelease 8 | metadata: 9 | name: pool-1 10 | namespace: flux-system 11 | spec: 12 | releaseName: pool-1 13 | targetNamespace: pool-1 14 | interval: 1m0s 15 | chart: 16 | spec: 17 | chart: helm-tenant-chart 18 | version: "0.0.x" 19 | sourceRef: 20 | kind: HelmRepository 21 | name: helm-tenant-chart 22 | values: 23 | tenantId: pool-1 24 | apps: 25 | producer: 26 | enabled: true 27 | ingress: 28 | enabled: false 29 | image: 30 | tag: "0.1" # {"$imagepolicy": "flux-system:producer-image-policy:tag"} 31 | consumer: 32 | enabled: true 33 | ingress: 34 | enabled: false 35 | image: 36 | tag: "0.1" # {"$imagepolicy": "flux-system:consumer-image-policy:tag"} -------------------------------------------------------------------------------- /gitops/application-plane/production/tenants/advanced/dummy-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: dummy-configmap-advanced 5 | namespace: default 6 | data: 7 | note: "This is a dummy ConfigMap for Flux to avoid empty kustomization error." 8 | -------------------------------------------------------------------------------- /gitops/application-plane/production/tenants/advanced/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - dummy-configmap.yaml -------------------------------------------------------------------------------- /gitops/application-plane/production/tenants/basic/dummy-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: dummy-configmap-basic 5 | namespace: default 6 | data: 7 | note: "This is a dummy ConfigMap for Flux to avoid empty kustomization error." 8 | -------------------------------------------------------------------------------- /gitops/application-plane/production/tenants/basic/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - dummy-configmap.yaml -------------------------------------------------------------------------------- /gitops/application-plane/production/tenants/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - basic 5 | - advanced 6 | - premium -------------------------------------------------------------------------------- /gitops/application-plane/production/tenants/premium/dummy-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: dummy-configmap-premium 5 | namespace: default 6 | data: 7 | note: "This is a dummy ConfigMap for Flux to avoid empty kustomization error." 8 | -------------------------------------------------------------------------------- /gitops/application-plane/production/tenants/premium/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - dummy-configmap.yaml -------------------------------------------------------------------------------- /gitops/application-plane/production/tier-templates/advanced_tenant_template.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: {TENANT_ID} 5 | --- 6 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 7 | kind: HelmRelease 8 | metadata: 9 | name: {TENANT_ID}-advanced 10 | namespace: flux-system 11 | spec: 12 | releaseName: {TENANT_ID}-advanced 13 | targetNamespace: {TENANT_ID} # Deploying into the tenant-specific namespace 14 | interval: 1m0s 15 | chart: 16 | spec: 17 | chart: helm-tenant-chart 18 | version: "{RELEASE_VERSION}.x" 19 | sourceRef: 20 | kind: HelmRepository 21 | name: helm-tenant-chart 22 | values: 23 | tenantId: {TENANT_ID} 24 | apps: 25 | producer: 26 | envId: pool-1 27 | enabled: false # Pool deployment -- advanced tier shares resources with other tenants 28 | ingress: 29 | enabled: true 30 | consumer: 31 | enabled: true # Silo deployment -- advanced tier has a dedicated deployment for each tenant 32 | ingress: 33 | enabled: true 34 | image: 35 | tag: "0.1" # {"$imagepolicy": "flux-system:consumer-image-policy:tag"} 36 | -------------------------------------------------------------------------------- /gitops/application-plane/production/tier-templates/basic_env_template.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: {ENVIRONMENT_ID} 5 | --- 6 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 7 | kind: HelmRelease 8 | metadata: 9 | name: {ENVIRONMENT_ID} 10 | namespace: flux-system 11 | spec: 12 | releaseName: {ENVIRONMENT_ID} 13 | targetNamespace: {ENVIRONMENT_ID} 14 | storageNamespace: {ENVIRONMENT_ID} 15 | interval: 1m0s 16 | chart: 17 | spec: 18 | chart: helm-tenant-chart 19 | version: "{RELEASE_VERSION}.x" 20 | sourceRef: 21 | kind: HelmRepository 22 | name: helm-tenant-chart 23 | values: 24 | tenantId: {ENVIRONMENT_ID} 25 | apps: 26 | producer: 27 | enabled: true 28 | ingress: 29 | enabled: false 30 | image: 31 | tag: "0.1" # {"$imagepolicy": "flux-system:producer-image-policy:tag"} 32 | consumer: 33 | enabled: true 34 | ingress: 35 | enabled: false 36 | image: 37 | tag: "0.1" # {"$imagepolicy": "flux-system:consumer-image-policy:tag"} 38 | -------------------------------------------------------------------------------- /gitops/application-plane/production/tier-templates/basic_tenant_template.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 2 | kind: HelmRelease 3 | metadata: 4 | name: {TENANT_ID}-basic 5 | namespace: flux-system 6 | spec: 7 | releaseName: {TENANT_ID}-basic 8 | targetNamespace: pool-1 # Deploying into the tenant-specific namespace 9 | storageNamespace: pool-1 10 | interval: 1m0s 11 | chart: 12 | spec: 13 | chart: helm-tenant-chart 14 | version: "{RELEASE_VERSION}.x" 15 | sourceRef: 16 | kind: HelmRepository 17 | name: helm-tenant-chart 18 | values: 19 | tenantId: {TENANT_ID} 20 | apps: 21 | producer: 22 | envId: pool-1 23 | enabled: false # Pool deployment -- basic tier shares resources with other tenants 24 | ingress: 25 | enabled: true 26 | consumer: 27 | envId: pool-1 28 | enabled: false # Pool deployment -- basic tier shares resources with other tenants 29 | ingress: 30 | enabled: true 31 | -------------------------------------------------------------------------------- /gitops/application-plane/production/tier-templates/premium_tenant_template.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: {TENANT_ID} 5 | --- 6 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 7 | kind: HelmRelease 8 | metadata: 9 | name: {TENANT_ID}-premium 10 | namespace: flux-system 11 | spec: 12 | releaseName: {TENANT_ID}-premium 13 | targetNamespace: {TENANT_ID} 14 | storageNamespace: {TENANT_ID} 15 | interval: 1m0s 16 | chart: 17 | spec: 18 | chart: helm-tenant-chart 19 | version: "{RELEASE_VERSION}.x" 20 | sourceRef: 21 | kind: HelmRepository 22 | name: helm-tenant-chart 23 | values: 24 | tenantId: {TENANT_ID} 25 | apps: 26 | producer: 27 | enabled: true # Silo deployment -- premium tier has a dedicated deployment for each tenant 28 | ingress: 29 | enabled: true 30 | image: 31 | tag: "0.1" # {"$imagepolicy": "flux-system:producer-image-policy:tag"} 32 | consumer: 33 | enabled: true # Silo deployment -- premium tier has a dedicated deployment for each tenant 34 | ingress: 35 | enabled: true 36 | image: 37 | tag: "0.1" # {"$imagepolicy": "flux-system:consumer-image-policy:tag"} 38 | -------------------------------------------------------------------------------- /gitops/clusters/production/control-plane.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 2 | kind: Kustomization 3 | metadata: 4 | name: controlplane 5 | namespace: flux-system 6 | spec: 7 | interval: 1m0s 8 | dependsOn: 9 | - name: infrastructure 10 | sourceRef: 11 | kind: GitRepository 12 | name: flux-system 13 | path: ./gitops/control-plane/production 14 | prune: true 15 | validation: client -------------------------------------------------------------------------------- /gitops/clusters/production/dependencies.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 2 | kind: Kustomization 3 | metadata: 4 | name: dependencies 5 | namespace: flux-system 6 | spec: 7 | dependsOn: 8 | - name: infrastructure 9 | interval: 1m0s 10 | sourceRef: 11 | kind: GitRepository 12 | name: flux-system 13 | path: ./gitops/infrastructure/production/dependencies 14 | prune: true 15 | validation: client -------------------------------------------------------------------------------- /gitops/clusters/production/infrastructure.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 2 | kind: Kustomization 3 | metadata: 4 | name: infrastructure 5 | namespace: flux-system 6 | spec: 7 | dependsOn: 8 | - name: sources 9 | interval: 1m0s 10 | sourceRef: 11 | kind: GitRepository 12 | name: flux-system 13 | path: ./gitops/infrastructure/production 14 | prune: true 15 | validation: client -------------------------------------------------------------------------------- /gitops/clusters/production/pooled-envs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 2 | kind: Kustomization 3 | metadata: 4 | name: dataplane-pooled-envs 5 | namespace: flux-system 6 | spec: 7 | interval: 1m0s 8 | sourceRef: 9 | kind: GitRepository 10 | name: flux-system 11 | path: ./gitops/application-plane/production/pooled-envs 12 | prune: true 13 | validation: client -------------------------------------------------------------------------------- /gitops/clusters/production/sources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 2 | kind: Kustomization 3 | metadata: 4 | name: sources 5 | namespace: flux-system 6 | spec: 7 | interval: 1m0s 8 | sourceRef: 9 | kind: GitRepository 10 | name: flux-system 11 | path: ./gitops/infrastructure/base/sources 12 | prune: true 13 | validation: client -------------------------------------------------------------------------------- /gitops/clusters/production/tenants.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 2 | kind: Kustomization 3 | metadata: 4 | name: dataplane-tenants 5 | namespace: flux-system 6 | spec: 7 | interval: 1m0s 8 | sourceRef: 9 | kind: GitRepository 10 | name: flux-system 11 | path: ./gitops/application-plane/production/tenants 12 | prune: true 13 | validation: client -------------------------------------------------------------------------------- /gitops/control-plane/production/applications/00-onboarding-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: onboarding-service 5 | --- 6 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 7 | kind: HelmRelease 8 | metadata: 9 | name: onboarding-service 10 | namespace: flux-system 11 | spec: 12 | releaseName: onboarding-service 13 | targetNamespace: onboarding-service 14 | interval: 1m0s 15 | chart: 16 | spec: 17 | chart: application-chart 18 | version: "0.0.x" 19 | sourceRef: 20 | kind: HelmRepository 21 | name: helm-application-chart 22 | values: 23 | image: 24 | repository: nginx # TBD: Use On-boarding service ECR URL 25 | tag: "latest" # TBD: Can use same deployment style with .x here as well -------------------------------------------------------------------------------- /gitops/control-plane/production/applications/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - 00-onboarding-service.yaml -------------------------------------------------------------------------------- /gitops/control-plane/production/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - workflows 5 | - applications -------------------------------------------------------------------------------- /gitops/control-plane/production/workflows/event-bus.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: argoproj.io/v1alpha1 3 | kind: EventBus 4 | metadata: 5 | name: default 6 | namespace: argo-events 7 | spec: 8 | nats: 9 | native: 10 | replicas: 3 -------------------------------------------------------------------------------- /gitops/control-plane/production/workflows/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - event-bus.yaml 5 | - tenant-onboarding-workflow-template.yaml 6 | - tenant-onboarding-sensor.yaml 7 | - tenant-deployment-sensor.yaml 8 | - tenant-deployment-workflow-template.yaml 9 | - tenant-offboarding-workflow-template.yaml 10 | - tenant-offboarding-sensor.yaml -------------------------------------------------------------------------------- /gitops/control-plane/production/workflows/tenant-deployment-sensor.yaml.template: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: argoproj.io/v1alpha1 3 | kind: EventSource 4 | metadata: 5 | name: aws-sqs-deployment 6 | namespace: argo-events 7 | spec: 8 | template: 9 | serviceAccountName: argo-events-sa 10 | sqs: 11 | tenant-deployment: 12 | jsonBody: true 13 | region: "{aws_region}" 14 | queue: "argoworkflows-deployment-queue" # Static value defined in TF module 15 | waitTimeSeconds: 20 16 | --- 17 | apiVersion: argoproj.io/v1alpha1 18 | kind: Sensor 19 | metadata: 20 | name: aws-sqs-deployment 21 | namespace: argo-events 22 | spec: 23 | template: 24 | serviceAccountName: argo-events-sa 25 | dependencies: 26 | - name: tenant-deployment-dep 27 | eventSourceName: aws-sqs-deployment 28 | eventName: tenant-deployment 29 | triggers: 30 | - template: 31 | name: tenant-deployment-template 32 | k8s: 33 | operation: create 34 | source: 35 | resource: 36 | apiVersion: argoproj.io/v1alpha1 37 | kind: Workflow 38 | metadata: 39 | generateName: tenant-deployment- 40 | namespace: argo-workflows 41 | spec: 42 | serviceAccountName: argoworkflows-sa 43 | entrypoint: tenant-deployment 44 | synchronization: 45 | mutex: 46 | name: workflow 47 | arguments: 48 | parameters: 49 | - name: TENANT_TIER 50 | value: "" # Valid values are: silo, pool, hybrid 51 | - name: RELEASE_VERSION 52 | value: "" # Valid values are: silo, pool, hybrid 53 | - name: REPO_URL 54 | value: "{aws_codecommit_flux_clone_url_ssh}" 55 | - name: GIT_USER_EMAIL 56 | value: "{ssh_public_key_id}" 57 | - name: GIT_USERNAME 58 | value: "{ssh_public_key_id}" 59 | - name: GIT_BRANCH 60 | value: "main" # Can change based on your configs 61 | templates: 62 | - name: tenant-deployment 63 | steps: 64 | - - name: clone-repository 65 | templateRef: 66 | name: tenant-deployment-template 67 | template: clone-repository 68 | - - name: update-tenant-helm-release 69 | templateRef: 70 | name: tenant-deployment-template 71 | template: update-tenant-helm-release 72 | volumeClaimTemplates: 73 | - metadata: 74 | name: workdir 75 | spec: 76 | storageClassName: gp2 77 | accessModes: [ "ReadWriteOnce" ] 78 | resources: 79 | requests: 80 | storage: 1Gi 81 | volumes: 82 | - name: ssh-key 83 | secret: 84 | secretName: github-ssh-key # Secret created earlier 85 | items: 86 | - key: ssh-privatekey 87 | path: id_rsa 88 | parameters: 89 | - src: 90 | dependencyName: tenant-deployment-dep 91 | dataKey: body.tenant_tier 92 | dest: spec.arguments.parameters.0.value 93 | - src: 94 | dependencyName: tenant-deployment-dep 95 | dataKey: body.release_version 96 | dest: spec.arguments.parameters.1.value -------------------------------------------------------------------------------- /gitops/control-plane/production/workflows/tenant-deployment-workflow-template.yaml.template: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: WorkflowTemplate 3 | metadata: 4 | name: tenant-deployment-template 5 | namespace: argo-workflows 6 | spec: 7 | serviceAccountName: argoworkflows-sa # SA with IRSA permissions 8 | templates: 9 | - name: clone-repository 10 | container: 11 | image: "{ecr_argoworkflow_container}:0.1" 12 | command: ["/bin/sh","-c"] 13 | args: ['cp /tmp/id_rsa /root/.ssh/ && chmod 600 /root/.ssh/id_rsa && ./01-tenant-clone-repo.sh {{workflow.parameters.REPO_URL}} {{workflow.parameters.GIT_BRANCH}} && cp -r eks-saas-gitops /mnt/vol/eks-saas-gitops'] 14 | volumeMounts: 15 | - name: workdir 16 | mountPath: /mnt/vol 17 | - name: ssh-key 18 | mountPath: /tmp/ 19 | env: 20 | - name: GIT_SSH_COMMAND 21 | value: "ssh -i /root/.ssh/id_rsa" 22 | - name: update-tenant-helm-release 23 | container: 24 | image: "{ecr_argoworkflow_container}:0.1" 25 | command: ["/bin/sh","-c"] 26 | args: ['cp /tmp/id_rsa /root/.ssh/ && chmod 600 /root/.ssh/id_rsa && ./03-tenant-deployment.sh {{workflow.parameters.RELEASE_VERSION}} {{workflow.parameters.TENANT_TIER}} {{workflow.parameters.GIT_USER_EMAIL}} {{workflow.parameters.GIT_USERNAME}} {{workflow.parameters.GIT_BRANCH}}'] 27 | volumeMounts: 28 | - name: workdir 29 | mountPath: /mnt/vol 30 | - name: ssh-key 31 | mountPath: /tmp/ 32 | env: 33 | - name: GIT_SSH_COMMAND 34 | value: "ssh -i /root/.ssh/id_rsa" 35 | volumeClaimTemplates: 36 | - metadata: 37 | name: workdir 38 | spec: 39 | storageClassName: gp2 40 | accessModes: [ "ReadWriteOnce" ] 41 | resources: 42 | requests: 43 | storage: 1Gi 44 | volumes: 45 | - name: ssh-key 46 | secret: 47 | secretName: github-ssh-key # Secret need to be created to run this workflow 48 | items: 49 | - key: ssh-privatekey 50 | path: id_rsa 51 | -------------------------------------------------------------------------------- /gitops/control-plane/production/workflows/tenant-offboarding-sensor.yaml.template: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: argoproj.io/v1alpha1 3 | kind: EventSource 4 | metadata: 5 | name: aws-sqs-offboarding 6 | namespace: argo-events 7 | spec: 8 | template: 9 | serviceAccountName: argo-events-sa 10 | sqs: 11 | tenant-provisioning: 12 | jsonBody: true 13 | region: "{aws_region}" 14 | queue: "argoworkflows-offboarding-queue" 15 | waitTimeSeconds: 20 16 | --- 17 | apiVersion: argoproj.io/v1alpha1 18 | kind: Sensor 19 | metadata: 20 | name: aws-sqs-offboarding 21 | namespace: argo-events 22 | spec: 23 | template: 24 | serviceAccountName: argo-events-sa 25 | dependencies: 26 | - name: tenant-provisioning-dep 27 | eventSourceName: aws-sqs-offboarding 28 | eventName: tenant-provisioning 29 | triggers: 30 | - template: 31 | name: tenant-offboarding-template 32 | k8s: 33 | operation: create 34 | source: 35 | resource: 36 | apiVersion: argoproj.io/v1alpha1 37 | kind: Workflow 38 | metadata: 39 | generateName: tenant-offboarding- 40 | namespace: argo-workflows 41 | spec: 42 | serviceAccountName: argoworkflows-sa 43 | entrypoint: tenant-provisioning 44 | synchronization: 45 | mutex: 46 | name: workflow 47 | arguments: 48 | parameters: 49 | - name: TENANT_ID 50 | value: "" # ID of your tenant, use this patter eg. tenant-xx (tenant-10, tenant-11) 51 | - name: TENANT_TIER 52 | value: "" # Valid values are: premium, advanced, basic 53 | - name: REPO_URL 54 | value: "{aws_codecommit_flux_clone_url_ssh}" 55 | - name: GIT_USER_EMAIL 56 | value: "{ssh_public_key_id}" 57 | - name: GIT_USERNAME 58 | value: "{ssh_public_key_id}" 59 | - name: GIT_BRANCH 60 | value: "main" # Can change based on your configs 61 | templates: 62 | - name: tenant-provisioning 63 | steps: 64 | - - name: clone-repository 65 | templateRef: 66 | name: tenant-offboarding-template 67 | template: clone-repository 68 | - - name: validate-if-tenant-exists 69 | templateRef: 70 | name: tenant-offboarding-template 71 | template: validate-if-tenant-exists 72 | - - name: remove-tenant-helm-release 73 | templateRef: 74 | name: tenant-offboarding-template 75 | template: remove-tenant-helm-release 76 | volumeClaimTemplates: 77 | - metadata: 78 | name: workdir 79 | spec: 80 | storageClassName: gp2 81 | accessModes: [ "ReadWriteOnce" ] 82 | resources: 83 | requests: 84 | storage: 1Gi 85 | volumes: 86 | - name: ssh-key 87 | secret: 88 | secretName: github-ssh-key # Secret created earlier 89 | items: 90 | - key: ssh-privatekey 91 | path: id_rsa 92 | parameters: 93 | - src: 94 | dependencyName: tenant-provisioning-dep 95 | dataKey: body.tenant_id 96 | dest: spec.arguments.parameters.0.value 97 | - src: 98 | dependencyName: tenant-provisioning-dep 99 | dataKey: body.tenant_tier 100 | dest: spec.arguments.parameters.1.value 101 | -------------------------------------------------------------------------------- /gitops/control-plane/production/workflows/tenant-offboarding-workflow-template.yaml.template: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: WorkflowTemplate 3 | metadata: 4 | name: tenant-offboarding-template 5 | namespace: argo-workflows 6 | spec: 7 | serviceAccountName: argoworkflows-sa # SA with IRSA permissions 8 | templates: 9 | - name: validate-if-tenant-exists 10 | container: 11 | image: "{ecr_argoworkflow_container}:0.1" 12 | command: ["/bin/sh","-c"] 13 | args: ['cp /mnt/ssh/id_rsa /root/.ssh/ && chmod 600 /root/.ssh/id_rsa && ./00-validate-tenant.sh {{workflow.parameters.TENANT_ID}}'] 14 | volumeMounts: 15 | - name: workdir 16 | mountPath: /mnt/vol 17 | - name: ssh-key 18 | mountPath: /mnt/ssh 19 | env: 20 | - name: GIT_SSH_COMMAND 21 | value: "ssh -i /root/.ssh/id_rsa" 22 | - name: clone-repository 23 | container: 24 | image: "{ecr_argoworkflow_container}:0.1" 25 | command: ["/bin/sh","-c"] 26 | args: ['cp /tmp/id_rsa /root/.ssh/ && chmod 600 /root/.ssh/id_rsa && ./01-tenant-clone-repo.sh {{workflow.parameters.REPO_URL}} {{workflow.parameters.GIT_BRANCH}} && cp -r eks-saas-gitops /mnt/vol/eks-saas-gitops'] 27 | volumeMounts: 28 | - name: workdir 29 | mountPath: /mnt/vol 30 | - name: ssh-key 31 | mountPath: /tmp/ 32 | env: 33 | - name: GIT_SSH_COMMAND 34 | value: "ssh -i /root/.ssh/id_rsa" 35 | - name: remove-tenant-helm-release 36 | container: 37 | image: "{ecr_argoworkflow_container}:0.1" 38 | command: ["/bin/sh","-c"] 39 | args: ['cp /tmp/id_rsa /root/.ssh/ && chmod 600 /root/.ssh/id_rsa && ./04-tenant-offboarding.sh {{workflow.parameters.TENANT_ID}} {{workflow.parameters.TENANT_TIER}} {{workflow.parameters.GIT_USER_EMAIL}} {{workflow.parameters.GIT_USERNAME}} {{workflow.parameters.GIT_BRANCH}}'] 40 | volumeMounts: 41 | - name: workdir 42 | mountPath: /mnt/vol 43 | - name: ssh-key 44 | mountPath: /tmp/ 45 | env: 46 | - name: GIT_SSH_COMMAND 47 | value: "ssh -i /root/.ssh/id_rsa" 48 | volumeClaimTemplates: 49 | - metadata: 50 | name: workdir 51 | spec: 52 | storageClassName: gp2 53 | accessModes: [ "ReadWriteOnce" ] 54 | resources: 55 | requests: 56 | storage: 1Gi 57 | volumes: 58 | - name: ssh-key 59 | secret: 60 | secretName: github-ssh-key # Secret need to be created to run this workflow 61 | items: 62 | - key: ssh-privatekey 63 | path: id_rsa 64 | -------------------------------------------------------------------------------- /gitops/control-plane/production/workflows/tenant-onboarding-sensor.yaml.template: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: argoproj.io/v1alpha1 3 | kind: EventSource 4 | metadata: 5 | name: aws-sqs-onboarding 6 | namespace: argo-events 7 | spec: 8 | template: 9 | serviceAccountName: argo-events-sa 10 | sqs: 11 | tenant-provisioning: 12 | jsonBody: true 13 | region: "{aws_region}" 14 | queue: "argoworkflows-onboarding-queue" 15 | waitTimeSeconds: 20 16 | --- 17 | apiVersion: argoproj.io/v1alpha1 18 | kind: Sensor 19 | metadata: 20 | name: aws-sqs-onboarding 21 | namespace: argo-events 22 | spec: 23 | template: 24 | serviceAccountName: argo-events-sa 25 | dependencies: 26 | - name: tenant-provisioning-dep 27 | eventSourceName: aws-sqs-onboarding 28 | eventName: tenant-provisioning 29 | triggers: 30 | - template: 31 | name: tenant-onboarding-template 32 | k8s: 33 | operation: create 34 | source: 35 | resource: 36 | apiVersion: argoproj.io/v1alpha1 37 | kind: Workflow 38 | metadata: 39 | generateName: tenant-onboarding- 40 | namespace: argo-workflows 41 | spec: 42 | serviceAccountName: argoworkflows-sa 43 | entrypoint: tenant-provisioning 44 | synchronization: 45 | mutex: 46 | name: workflow 47 | arguments: 48 | parameters: 49 | - name: TENANT_ID 50 | value: "" # ID of your tenant, use this patter eg. tenant-xx (tenant-10, tenant-11) 51 | - name: TENANT_TIER 52 | value: "" # Valid values are: silo, pool, hybrid 53 | - name: RELEASE_VERSION 54 | value: "" # I.E. 0.0 or 1.0 55 | - name: REPO_URL 56 | value: "{aws_codecommit_flux_clone_url_ssh}" 57 | - name: GIT_USER_EMAIL 58 | value: "{ssh_public_key_id}" 59 | - name: GIT_USERNAME 60 | value: "{ssh_public_key_id}" 61 | - name: GIT_BRANCH 62 | value: "main" # Can change based on your configs 63 | templates: 64 | - name: tenant-provisioning 65 | steps: 66 | - - name: clone-repository 67 | templateRef: 68 | name: tenant-onboarding-template 69 | template: clone-repository 70 | - - name: validate-if-tenant-exists 71 | templateRef: 72 | name: tenant-onboarding-template 73 | template: validate-if-tenant-exists 74 | - - name: create-tenant-helm-release 75 | templateRef: 76 | name: tenant-onboarding-template 77 | template: create-tenant-helm-release 78 | volumeClaimTemplates: 79 | - metadata: 80 | name: workdir 81 | spec: 82 | storageClassName: gp2 83 | accessModes: [ "ReadWriteOnce" ] 84 | resources: 85 | requests: 86 | storage: 1Gi 87 | volumes: 88 | - name: ssh-key 89 | secret: 90 | secretName: github-ssh-key # Secret created earlier 91 | items: 92 | - key: ssh-privatekey 93 | path: id_rsa 94 | parameters: 95 | - src: 96 | dependencyName: tenant-provisioning-dep 97 | dataKey: body.tenant_id 98 | dest: spec.arguments.parameters.0.value 99 | - src: 100 | dependencyName: tenant-provisioning-dep 101 | dataKey: body.tenant_tier 102 | dest: spec.arguments.parameters.1.value 103 | - src: 104 | dependencyName: tenant-provisioning-dep 105 | dataKey: body.release_version 106 | dest: spec.arguments.parameters.2.value -------------------------------------------------------------------------------- /gitops/control-plane/production/workflows/tenant-onboarding-workflow-template.yaml.template: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: WorkflowTemplate 3 | metadata: 4 | name: tenant-onboarding-template 5 | namespace: argo-workflows 6 | spec: 7 | serviceAccountName: argoworkflows-sa # SA with IRSA permissions 8 | templates: 9 | - name: validate-if-tenant-exists 10 | container: 11 | image: "{ecr_argoworkflow_container}:0.1" 12 | command: ["/bin/sh","-c"] 13 | args: ['cp /mnt/ssh/id_rsa /root/.ssh/ && chmod 600 /root/.ssh/id_rsa && ./00-validate-tenant.sh {{workflow.parameters.TENANT_ID}}'] 14 | volumeMounts: 15 | - name: workdir 16 | mountPath: /mnt/vol 17 | - name: ssh-key 18 | mountPath: /mnt/ssh 19 | env: 20 | - name: GIT_SSH_COMMAND 21 | value: "ssh -i /root/.ssh/id_rsa" 22 | - name: clone-repository 23 | container: 24 | image: "{ecr_argoworkflow_container}:0.1" 25 | command: ["/bin/sh","-c"] 26 | args: ['cp /tmp/id_rsa /root/.ssh/ && chmod 600 /root/.ssh/id_rsa && ./01-tenant-clone-repo.sh {{workflow.parameters.REPO_URL}} {{workflow.parameters.GIT_BRANCH}} && cp -r eks-saas-gitops /mnt/vol/eks-saas-gitops'] 27 | volumeMounts: 28 | - name: workdir 29 | mountPath: /mnt/vol 30 | - name: ssh-key 31 | mountPath: /tmp/ 32 | env: 33 | - name: GIT_SSH_COMMAND 34 | value: "ssh -i /root/.ssh/id_rsa" 35 | - name: create-tenant-helm-release 36 | container: 37 | image: "{ecr_argoworkflow_container}:0.1" 38 | command: ["/bin/sh","-c"] 39 | args: ['cp /tmp/id_rsa /root/.ssh/ && chmod 600 /root/.ssh/id_rsa && ./02-tenant-onboarding.sh {{workflow.parameters.TENANT_ID}} {{workflow.parameters.RELEASE_VERSION}} {{workflow.parameters.TENANT_TIER}} {{workflow.parameters.GIT_USER_EMAIL}} {{workflow.parameters.GIT_USERNAME}} {{workflow.parameters.GIT_BRANCH}}'] 40 | volumeMounts: 41 | - name: workdir 42 | mountPath: /mnt/vol 43 | - name: ssh-key 44 | mountPath: /tmp/ 45 | env: 46 | - name: GIT_SSH_COMMAND 47 | value: "ssh -i /root/.ssh/id_rsa" 48 | volumeClaimTemplates: 49 | - metadata: 50 | name: workdir 51 | spec: 52 | storageClassName: gp2 53 | accessModes: [ "ReadWriteOnce" ] 54 | resources: 55 | requests: 56 | storage: 1Gi 57 | volumes: 58 | - name: ssh-key 59 | secret: 60 | secretName: github-ssh-key # Secret need to be created to run this workflow 61 | items: 62 | - key: ssh-privatekey 63 | path: id_rsa 64 | -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/application-chart-helm.yaml.template: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: source.toolkit.fluxcd.io/v1beta2 3 | kind: HelmRepository 4 | metadata: 5 | name: helm-application-chart 6 | namespace: flux-system 7 | spec: 8 | type: "oci" 9 | interval: 1m0s 10 | provider: aws 11 | url: oci://{ecr_helm_chart_url_base}/ 12 | -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/argo-workflows-helm.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: source.toolkit.fluxcd.io/v1beta2 3 | kind: HelmRepository 4 | metadata: 5 | name: argo 6 | namespace: flux-system 7 | spec: 8 | interval: 1m0s 9 | url: https://argoproj.github.io/argo-helm -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/capacitor-web-ui.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: source.toolkit.fluxcd.io/v1beta2 3 | kind: OCIRepository 4 | metadata: 5 | name: capacitor 6 | namespace: flux-system 7 | spec: 8 | interval: 12h 9 | url: oci://ghcr.io/gimlet-io/capacitor-manifests 10 | ref: 11 | semver: ">=0.1.0" 12 | --- 13 | apiVersion: kustomize.toolkit.fluxcd.io/v1 14 | kind: Kustomization 15 | metadata: 16 | name: capacitor 17 | namespace: flux-system 18 | spec: 19 | targetNamespace: flux-system 20 | interval: 1h 21 | retryInterval: 2m 22 | timeout: 5m 23 | wait: true 24 | prune: true 25 | path: "./" 26 | sourceRef: 27 | kind: OCIRepository 28 | name: capacitor 29 | --- 30 | apiVersion: v1 31 | kind: Service 32 | metadata: 33 | name: capacitor-lb 34 | namespace: flux-system 35 | annotations: 36 | service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing" 37 | spec: 38 | selector: 39 | app.kubernetes.io/instance: capacitor 40 | ports: 41 | - protocol: TCP 42 | port: 80 43 | targetPort: 9000 44 | type: LoadBalancer 45 | -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/consumer-image-automation.yaml.template: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: image.toolkit.fluxcd.io/v1beta2 3 | kind: ImagePolicy 4 | metadata: 5 | name: consumer-image-policy 6 | namespace: flux-system 7 | spec: 8 | imageRepositoryRef: 9 | name: consumer-image-repository 10 | filterTags: 11 | pattern: '^prd\-(?P.*)$' 12 | extract: '$timestamp' 13 | policy: 14 | alphabetical: 15 | order: asc 16 | --- 17 | apiVersion: image.toolkit.fluxcd.io/v1beta2 18 | kind: ImageRepository 19 | metadata: 20 | name: consumer-image-repository 21 | namespace: flux-system 22 | spec: 23 | image: {ecr_repository_urls_consumer} 24 | interval: 1m0s 25 | secretRef: 26 | name: ecr-credentials 27 | --- 28 | apiVersion: image.toolkit.fluxcd.io/v1beta2 29 | kind: ImageUpdateAutomation 30 | metadata: 31 | name: consumer-update-automation-tenants 32 | namespace: flux-system 33 | spec: 34 | interval: 5m0s 35 | sourceRef: 36 | kind: GitRepository 37 | name: flux-system 38 | git: 39 | commit: 40 | author: 41 | email: fluxcdbot@users.noreply.github.com 42 | name: fluxcdbot 43 | push: 44 | branch: main 45 | update: 46 | path: ./gitops/application-plane/production/tenants/ 47 | --- 48 | apiVersion: image.toolkit.fluxcd.io/v1beta2 49 | kind: ImageUpdateAutomation 50 | metadata: 51 | name: consumer-update-automation-pooled-envs 52 | namespace: flux-system 53 | spec: 54 | interval: 5m0s 55 | sourceRef: 56 | kind: GitRepository 57 | name: flux-system 58 | git: 59 | commit: 60 | author: 61 | email: fluxcdbot@users.noreply.github.com 62 | name: fluxcdbot 63 | push: 64 | branch: main 65 | update: 66 | path: ./gitops/application-plane/production/pooled-envs/ -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/cronjob-ecr-updater.yaml.template: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: ecr-credentials-sync 5 | namespace: flux-system 6 | rules: 7 | - apiGroups: [""] 8 | resources: 9 | - secrets 10 | verbs: 11 | - get 12 | - create 13 | - patch 14 | --- 15 | apiVersion: rbac.authorization.k8s.io/v1 16 | kind: RoleBinding 17 | metadata: 18 | name: ecr-credentials-sync 19 | namespace: flux-system 20 | subjects: 21 | - kind: ServiceAccount 22 | name: ecr-credentials-sync 23 | namespace: flux-system 24 | roleRef: 25 | kind: Role 26 | name: ecr-credentials-sync 27 | apiGroup: rbac.authorization.k8s.io 28 | --- 29 | apiVersion: v1 30 | kind: ServiceAccount 31 | metadata: 32 | name: ecr-credentials-sync 33 | namespace: flux-system 34 | # Uncomment and edit if using IRSA 35 | # annotations: 36 | # eks.amazonaws.com/role-arn: 37 | --- 38 | apiVersion: batch/v1 39 | kind: CronJob 40 | metadata: 41 | name: ecr-credentials-sync 42 | namespace: flux-system 43 | spec: 44 | suspend: false 45 | schedule: "*/5 * * * *" # Runs every 5 minutes 46 | failedJobsHistoryLimit: 1 47 | successfulJobsHistoryLimit: 1 48 | jobTemplate: 49 | spec: 50 | template: 51 | spec: 52 | serviceAccountName: ecr-credentials-sync 53 | restartPolicy: Never 54 | volumes: 55 | - name: token 56 | emptyDir: 57 | medium: Memory 58 | initContainers: 59 | - image: amazon/aws-cli 60 | name: get-token 61 | imagePullPolicy: IfNotPresent 62 | # You will need to set the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables if not using 63 | # IRSA. It is recommended to store the values in a Secret and load them in the container using envFrom. 64 | # envFrom: 65 | # - secretRef: 66 | # name: aws-credentials 67 | env: 68 | - name: REGION 69 | value: {aws_region} # change this if ECR repo is in a different region 70 | volumeMounts: 71 | - mountPath: /token 72 | name: token 73 | command: 74 | - /bin/sh 75 | - -ce 76 | - aws ecr get-login-password --region ${REGION} > /token/ecr-token 77 | containers: 78 | - image: ghcr.io/fluxcd/flux-cli:v0.25.2 79 | name: create-secret 80 | imagePullPolicy: IfNotPresent 81 | env: 82 | - name: SECRET_NAME 83 | value: ecr-credentials 84 | - name: ECR_REGISTRY 85 | value: {account_id}.dkr.ecr.{aws_region}.amazonaws.com # fill in the account id and region 86 | volumeMounts: 87 | - mountPath: /token 88 | name: token 89 | command: 90 | - /bin/sh 91 | - -ce 92 | - |- 93 | kubectl create secret docker-registry $SECRET_NAME \ 94 | --dry-run=client \ 95 | --docker-server="$ECR_REGISTRY" \ 96 | --docker-username=AWS \ 97 | --docker-password="$(cat /token/ecr-token)" \ 98 | -o yaml | kubectl apply -f - -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/eks-charts-helm.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: source.toolkit.fluxcd.io/v1beta2 3 | kind: HelmRepository 4 | metadata: 5 | name: eks-charts 6 | namespace: flux-system 7 | spec: 8 | interval: 1m0s 9 | url: https://aws.github.io/eks-charts -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/karpenter-helm.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: source.toolkit.fluxcd.io/v1beta2 3 | kind: HelmRepository 4 | metadata: 5 | name: karpenter 6 | namespace: flux-system 7 | spec: 8 | type: "oci" 9 | interval: 1m0s 10 | url: oci://public.ecr.aws/karpenter/ -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/kubecost-helm.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: source.toolkit.fluxcd.io/v1beta2 3 | kind: HelmRepository 4 | metadata: 5 | name: kubecost 6 | namespace: flux-system 7 | spec: 8 | type: "oci" 9 | interval: 1m0s 10 | url: oci://public.ecr.aws/kubecost/ -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - metric-server-helm.yaml 5 | - argo-workflows-helm.yaml 6 | - tenant-chart-helm.yaml 7 | - application-chart-helm.yaml 8 | - eks-charts-helm.yaml 9 | - kubecost-helm.yaml 10 | - karpenter-helm.yaml 11 | - tf-controller-helm.yaml 12 | - terraform-git-v1.yaml 13 | - capacitor-web-ui.yaml 14 | - cronjob-ecr-updater.yaml 15 | - producer-image-automation.yaml 16 | - consumer-image-automation.yaml 17 | - payments-image-automation.yaml 18 | -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/metric-server-helm.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: source.toolkit.fluxcd.io/v1beta2 3 | kind: HelmRepository 4 | metadata: 5 | name: metrics-server 6 | namespace: flux-system 7 | spec: 8 | interval: 10m0s 9 | url: https://kubernetes-sigs.github.io/metrics-server -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/payments-image-automation.yaml.template: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: image.toolkit.fluxcd.io/v1beta2 3 | kind: ImagePolicy 4 | metadata: 5 | name: payments-image-policy 6 | namespace: flux-system 7 | spec: 8 | imageRepositoryRef: 9 | name: payments-image-repository 10 | filterTags: 11 | pattern: '^prd\-(?P.*)$' 12 | extract: '$timestamp' 13 | policy: 14 | alphabetical: 15 | order: asc 16 | --- 17 | apiVersion: image.toolkit.fluxcd.io/v1beta2 18 | kind: ImageRepository 19 | metadata: 20 | name: payments-image-repository 21 | namespace: flux-system 22 | spec: 23 | image: {ecr_repository_urls_payments} 24 | interval: 1m0s 25 | secretRef: 26 | name: ecr-credentials 27 | --- 28 | apiVersion: image.toolkit.fluxcd.io/v1beta2 29 | kind: ImageUpdateAutomation 30 | metadata: 31 | name: payments-update-automation-tenants 32 | namespace: flux-system 33 | spec: 34 | interval: 5m0s 35 | sourceRef: 36 | kind: GitRepository 37 | name: flux-system 38 | git: 39 | commit: 40 | author: 41 | email: fluxcdbot@users.noreply.github.com 42 | name: fluxcdbot 43 | push: 44 | branch: main 45 | update: 46 | path: ./gitops/application-plane/production/tenants/ 47 | --- 48 | apiVersion: image.toolkit.fluxcd.io/v1beta2 49 | kind: ImageUpdateAutomation 50 | metadata: 51 | name: payments-update-automation-pooled-envs 52 | namespace: flux-system 53 | spec: 54 | interval: 5m0s 55 | sourceRef: 56 | kind: GitRepository 57 | name: flux-system 58 | git: 59 | commit: 60 | author: 61 | email: fluxcdbot@users.noreply.github.com 62 | name: fluxcdbot 63 | push: 64 | branch: main 65 | update: 66 | path: ./gitops/application-plane/production/pooled-envs/ -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/producer-image-automation.yaml.template: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: image.toolkit.fluxcd.io/v1beta2 3 | kind: ImagePolicy 4 | metadata: 5 | name: producer-image-policy 6 | namespace: flux-system 7 | spec: 8 | imageRepositoryRef: 9 | name: producer-image-repository 10 | filterTags: 11 | pattern: '^prd\-(?P.*)$' 12 | extract: '$timestamp' 13 | policy: 14 | alphabetical: 15 | order: asc 16 | --- 17 | apiVersion: image.toolkit.fluxcd.io/v1beta2 18 | kind: ImageRepository 19 | metadata: 20 | name: producer-image-repository 21 | namespace: flux-system 22 | spec: 23 | image: {ecr_repository_urls_producer} 24 | interval: 1m0s 25 | secretRef: 26 | name: ecr-credentials 27 | --- 28 | apiVersion: image.toolkit.fluxcd.io/v1beta2 29 | kind: ImageUpdateAutomation 30 | metadata: 31 | name: producer-update-automation-tenants 32 | namespace: flux-system 33 | spec: 34 | interval: 5m0s 35 | sourceRef: 36 | kind: GitRepository 37 | name: flux-system 38 | git: 39 | commit: 40 | author: 41 | email: fluxcdbot@users.noreply.github.com 42 | name: fluxcdbot 43 | push: 44 | branch: main 45 | update: 46 | path: ./gitops/application-plane/production/tenants/ 47 | --- 48 | apiVersion: image.toolkit.fluxcd.io/v1beta2 49 | kind: ImageUpdateAutomation 50 | metadata: 51 | name: producer-update-automation-pooled-envs 52 | namespace: flux-system 53 | spec: 54 | interval: 5m0s 55 | sourceRef: 56 | kind: GitRepository 57 | name: flux-system 58 | git: 59 | commit: 60 | author: 61 | email: fluxcdbot@users.noreply.github.com 62 | name: fluxcdbot 63 | push: 64 | branch: main 65 | update: 66 | path: ./gitops/application-plane/production/pooled-envs/ -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/tenant-chart-helm.yaml.template: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: source.toolkit.fluxcd.io/v1beta2 3 | kind: HelmRepository 4 | metadata: 5 | name: helm-tenant-chart 6 | namespace: flux-system 7 | spec: 8 | type: "oci" 9 | interval: 1m0s 10 | provider: aws 11 | url: oci://{ecr_helm_chart_url_base}/ 12 | -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/terraform-git-v1.yaml.template: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: source.toolkit.fluxcd.io/v1 3 | kind: GitRepository 4 | metadata: 5 | name: terraform-v0-0-1 6 | namespace: flux-system 7 | spec: 8 | interval: 300s 9 | url: "{aws_codecommit_flux_clone_url_ssh}" # Same repository for gitops components, could be sliptted 10 | ref: 11 | tag: "v0.0.1" 12 | secretRef: 13 | name: flux-system 14 | -------------------------------------------------------------------------------- /gitops/infrastructure/base/sources/tf-controller-helm.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: source.toolkit.fluxcd.io/v1beta2 3 | kind: HelmRepository 4 | metadata: 5 | name: tf-controller 6 | namespace: flux-system 7 | spec: 8 | interval: 1m0s 9 | url: https://flux-iac.github.io/tofu-controller/ -------------------------------------------------------------------------------- /gitops/infrastructure/production/01-metric-server.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: metrics-server 6 | namespace: flux-system 7 | labels: 8 | add-on-version: "3.11.0" 9 | spec: 10 | releaseName: metrics-server 11 | targetNamespace: kube-system 12 | storageNamespace: kube-system 13 | interval: 10m0s 14 | chart: 15 | spec: 16 | chart: metrics-server 17 | version: 3.11.0 18 | sourceRef: 19 | kind: HelmRepository 20 | name: metrics-server 21 | values: 22 | apiService: 23 | create: true 24 | install: {} -------------------------------------------------------------------------------- /gitops/infrastructure/production/02-karpenter.yaml.template: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: karpenter 5 | --- 6 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 7 | kind: HelmRelease 8 | metadata: 9 | name: karpenter 10 | namespace: flux-system 11 | spec: 12 | releaseName: karpenter 13 | targetNamespace: karpenter 14 | storageNamespace: karpenter 15 | interval: 1m0s 16 | chart: 17 | spec: 18 | chart: karpenter 19 | version: v0.34.1 20 | sourceRef: 21 | kind: HelmRepository 22 | name: karpenter 23 | values: 24 | serviceAccount: 25 | create: true 26 | name: karpenter # SA created via eksctl or 27 | annotations: 28 | eks.amazonaws.com/role-arn: {karpenter_irsa} 29 | settings: 30 | clusterName: eks-saas-gitops 31 | clusterEndpoint: {cluster_endpoint} 32 | interruptionQueueName: eks-saas-gitops 33 | install: {} 34 | -------------------------------------------------------------------------------- /gitops/infrastructure/production/03-argo-workflows.yaml.template: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: argo-workflows 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: ClusterRole 8 | metadata: 9 | name: full-permissions-cluster-role 10 | rules: 11 | - apiGroups: ["*"] 12 | resources: ["*"] 13 | verbs: ["*"] 14 | --- 15 | apiVersion: v1 16 | kind: ServiceAccount 17 | metadata: 18 | name: argoworkflows-sa 19 | namespace: argo-workflows 20 | annotations: 21 | eks.amazonaws.com/role-arn: {argo_workflows_irsa} 22 | --- 23 | apiVersion: rbac.authorization.k8s.io/v1 24 | kind: ClusterRoleBinding 25 | metadata: 26 | name: full-permissions-cluster-role-binding 27 | roleRef: 28 | apiGroup: rbac.authorization.k8s.io 29 | kind: ClusterRole 30 | name: full-permissions-cluster-role 31 | subjects: 32 | - kind: ServiceAccount 33 | name: argoworkflows-sa 34 | namespace: argo-workflows 35 | --- 36 | # TODO: Configure s3 artifact output 37 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 38 | kind: HelmRelease 39 | metadata: 40 | name: argo-workflows 41 | namespace: flux-system 42 | spec: 43 | dependsOn: 44 | - name: aws-load-balancer-controller 45 | releaseName: argo-workflows 46 | targetNamespace: argo-workflows 47 | storageNamespace: argo-workflows 48 | interval: 1m0s 49 | chart: 50 | spec: 51 | chart: argo-workflows 52 | version: 0.40.11 53 | sourceRef: 54 | kind: HelmRepository 55 | name: argo 56 | values: 57 | useStaticCredentials: false 58 | artifactRepository: 59 | s3: 60 | bucket: {argo_workflows_bucket_name} 61 | region: {aws_region} 62 | endpoint: s3.amazonaws.com 63 | workflow: 64 | serviceAccount: 65 | create: true 66 | name: "argo-workflow" 67 | annotations: 68 | eks.amazonaws.com/role-arn: {argo_workflows_irsa} 69 | rbac: 70 | create: true 71 | controller: 72 | workflowNamespaces: # Give permission to other namespaces, to use Karpenter 73 | - argo-workflows 74 | server: 75 | serviceType: LoadBalancer 76 | serviceAnnotations: 77 | service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing" 78 | serviceAccount: 79 | annotations: 80 | eks.amazonaws.com/role-arn: {argo_workflows_irsa} 81 | extraArgs: 82 | - --auth-mode=server # This is for demonstration purposes only 83 | install: {} 84 | 85 | # https://github.com/argoproj/argo-helm/blob/main/charts/argo-workflows/values.yaml -------------------------------------------------------------------------------- /gitops/infrastructure/production/04-lb-controller.yaml.template: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: aws-system 5 | --- 6 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 7 | kind: HelmRelease 8 | metadata: 9 | name: aws-load-balancer-controller 10 | namespace: flux-system 11 | spec: 12 | releaseName: aws-load-balancer-controller 13 | targetNamespace: aws-system 14 | storageNamespace: aws-system 15 | interval: 10m0s 16 | chart: 17 | spec: 18 | chart: aws-load-balancer-controller 19 | version: 1.6.2 20 | sourceRef: 21 | kind: HelmRepository 22 | name: eks-charts 23 | values: 24 | clusterName: eks-saas-gitops 25 | serviceAccount: 26 | create: true 27 | name: aws-load-balancer-controller 28 | annotations: 29 | eks.amazonaws.com/role-arn: {lb_controller_irsa} 30 | install: {} -------------------------------------------------------------------------------- /gitops/infrastructure/production/05-kubecost.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: kubecost 5 | --- 6 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 7 | kind: HelmRelease 8 | metadata: 9 | name: kubecost 10 | namespace: flux-system 11 | spec: 12 | releaseName: kubecost 13 | targetNamespace: kubecost 14 | storageNamespace: kubecost 15 | interval: 1m0s 16 | chart: 17 | spec: 18 | chart: cost-analyzer 19 | version: 2.1.0 20 | sourceRef: 21 | kind: HelmRepository 22 | name: kubecost 23 | values: 24 | global: 25 | grafana: 26 | enabled: false 27 | proxy: false 28 | 29 | networkCosts: # Test 30 | enabled: true 31 | amazon-web-services: true 32 | 33 | pricingCsv: 34 | enabled: false # ADD CUR Dashboard 35 | location: 36 | provider: "AWS" 37 | region: "us-east-1" 38 | URI: s3://kc-csv-test/pricing_schema.csv # a valid file URI 39 | csvAccessCredentials: pricing-schema-access-secret 40 | 41 | nodeSelector: {} 42 | 43 | tolerations: [] 44 | # - key: "key" 45 | # operator: "Equal|Exists" 46 | # value: "value" 47 | # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" 48 | 49 | affinity: {} 50 | 51 | # If true, creates a PriorityClass to be used by the cost-analyzer pod 52 | priority: 53 | enabled: false 54 | # value: 1000000 55 | 56 | # If true, enable creation of NetworkPolicy resources. 57 | networkPolicy: 58 | enabled: false 59 | 60 | podSecurityPolicy: 61 | enabled: false 62 | 63 | # Enable this flag if you need to install with specfic image tags 64 | # imageVersion: prod-1.97.0 65 | 66 | kubecostFrontend: 67 | image: public.ecr.aws/kubecost/frontend 68 | imagePullPolicy: Always 69 | resources: 70 | requests: 71 | cpu: "10m" 72 | memory: "55Mi" 73 | #limits: 74 | # cpu: "100m" 75 | # memory: "256Mi" 76 | 77 | kubecostModel: 78 | image: public.ecr.aws/kubecost/cost-model 79 | imagePullPolicy: Always 80 | warmCache: true 81 | warmSavingsCache: true 82 | etl: true 83 | # The total number of days the ETL storage will build 84 | etlStoreDurationDays: 120 85 | maxQueryConcurrency: 5 86 | # utcOffset represents a timezone in hours and minutes east (+) or west (-) 87 | # of UTC, itself, which is defined as +00:00. 88 | # See the tz database of timezones to look up your local UTC offset: 89 | # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones 90 | utcOffset: "+00:00" 91 | resources: 92 | requests: 93 | cpu: "200m" 94 | memory: "55Mi" 95 | #limits: 96 | # cpu: "800m" 97 | # memory: "256Mi" 98 | 99 | serviceAccount: 100 | create: true # Set this to false if you're bringing your own service account. 101 | annotations: {} 102 | # name: kc-test 103 | 104 | # Define persistence volume for cost-analyzer 105 | persistentVolume: 106 | size: 32Gi 107 | dbSize: 32.0Gi 108 | enabled: true # Note that setting this to false means configurations will be wiped out on pod restart. 109 | storageClass: "gp2" # 110 | # existingClaim: kubecost-cost-analyzer # a claim in the same namespace as kubecost 111 | 112 | ingress: 113 | enabled: false 114 | # className: nginx 115 | annotations: 116 | kubernetes.io/ingress.class: nginx 117 | # kubernetes.io/tls-acme: "true" 118 | paths: ["/"] # There's no need to route specifically to the pods-- we have an nginx deployed that handles routing 119 | hosts: 120 | - cost-analyzer.local 121 | tls: [] 122 | # - secretName: cost-analyzer-tls 123 | # hosts: 124 | # - cost-analyzer.local 125 | 126 | service: 127 | type: LoadBalancer 128 | port: 9090 129 | targetPort: 9090 130 | # nodePort: 131 | labels: {} 132 | annotations: 133 | service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing" 134 | 135 | prometheus: 136 | server: 137 | # If clusterIDConfigmap is defined, instead use user-generated configmap with key CLUSTER_ID 138 | # to use as unique cluster ID in kubecost cost-analyzer deployment. 139 | # This overrides the cluster_id set in prometheus.server.global.external_labels. 140 | # NOTE: This does not affect the external_labels set in prometheus config. 141 | # clusterIDConfigmap: cluster-id-configmap 142 | image: 143 | repository: public.ecr.aws/kubecost/prometheus 144 | tag: v2.35.0 145 | resources: {} 146 | # limits: 147 | # cpu: 500m 148 | # memory: 512Mi 149 | # requests: 150 | # cpu: 500m 151 | # memory: 512Mi 152 | global: 153 | scrape_interval: 1m 154 | scrape_timeout: 10s 155 | evaluation_interval: 1m 156 | external_labels: 157 | cluster_id: cluster-one # Each cluster should have a unique ID 158 | persistentVolume: 159 | size: 32Gi 160 | enabled: true 161 | storageClass: "gp2" 162 | extraArgs: 163 | query.max-concurrency: 1 164 | query.max-samples: 100000000 165 | tolerations: [] 166 | # - key: "key" 167 | # operator: "Equal|Exists" 168 | # value: "value" 169 | # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" 170 | 171 | configmapReload: 172 | prometheus: 173 | ## If false, the configmap-reload container will not be deployed 174 | ## 175 | enabled: false 176 | 177 | ## configmap-reload container name 178 | ## 179 | name: configmap-reload 180 | ## configmap-reload container image 181 | ## 182 | image: 183 | repository: public.ecr.aws/bitnami/configmap-reload 184 | tag: 0.7.1 185 | pullPolicy: IfNotPresent 186 | ## Additional configmap-reload container arguments 187 | ## 188 | extraArgs: {} 189 | ## Additional configmap-reload volume directories 190 | ## 191 | extraVolumeDirs: [] 192 | ## Additional configmap-reload mounts 193 | ## 194 | extraConfigmapMounts: [] 195 | # - name: prometheus-alerts 196 | # mountPath: /etc/alerts.d 197 | # subPath: "" 198 | # configMap: prometheus-alerts 199 | # readOnly: true 200 | ## configmap-reload resource requests and limits 201 | ## Ref: http://kubernetes.io/docs/user-guide/compute-resources/ 202 | ## 203 | resources: {} 204 | 205 | kube-state-metrics: 206 | disabled: true 207 | nodeExporter: 208 | enabled: true 209 | 210 | 211 | reporting: 212 | productAnalytics: false 213 | -------------------------------------------------------------------------------- /gitops/infrastructure/production/06-argo-events.yaml.template: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: argo-events 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: ClusterRole 8 | metadata: 9 | name: argo-events-cluster-role 10 | rules: 11 | - apiGroups: ["*"] 12 | resources: ["*"] 13 | verbs: ["*"] 14 | --- 15 | apiVersion: v1 16 | kind: ServiceAccount 17 | metadata: 18 | name: argo-events-sa 19 | namespace: argo-events 20 | annotations: 21 | eks.amazonaws.com/role-arn: {argo_events_irsa} 22 | --- 23 | apiVersion: rbac.authorization.k8s.io/v1 24 | kind: ClusterRoleBinding 25 | metadata: 26 | name: argo-events-role-binding 27 | roleRef: 28 | apiGroup: rbac.authorization.k8s.io 29 | kind: ClusterRole 30 | name: argo-events-cluster-role 31 | subjects: 32 | - kind: ServiceAccount 33 | name: argo-events-sa 34 | namespace: argo-events 35 | --- 36 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 37 | kind: HelmRelease 38 | metadata: 39 | name: argo-events 40 | namespace: flux-system 41 | spec: 42 | releaseName: argo-events 43 | targetNamespace: argo-events 44 | storageNamespace: argo-events 45 | interval: 1m0s 46 | chart: 47 | spec: 48 | chart: argo-events 49 | version: 2.4.3 50 | sourceRef: 51 | kind: HelmRepository 52 | name: argo -------------------------------------------------------------------------------- /gitops/infrastructure/production/07-tf-controller.yaml.template: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 2 | kind: HelmRelease 3 | metadata: 4 | name: tf-controller 5 | namespace: flux-system 6 | spec: 7 | releaseName: tf-controller 8 | targetNamespace: flux-system 9 | storageNamespace: flux-system 10 | interval: 1m0s 11 | chart: 12 | spec: 13 | chart: tf-controller 14 | version: 0.16.0-rc.4 15 | sourceRef: 16 | kind: HelmRepository 17 | name: tf-controller 18 | values: 19 | awsPackage: # Disabling this since using own modules 20 | install: false 21 | allowCrossNamespaceRefs: true 22 | serviceAccount: 23 | create: true 24 | name: tf-controller 25 | annotations: 26 | eks.amazonaws.com/role-arn: {tf_controller_irsa} 27 | runner: 28 | serviceAccount: 29 | create: true 30 | annotations: 31 | eks.amazonaws.com/role-arn: {tf_controller_irsa} 32 | name: tf-runner -------------------------------------------------------------------------------- /gitops/infrastructure/production/dependencies/01-default-karpenter-config.yaml.template: -------------------------------------------------------------------------------- 1 | # NodePool Configuration 2 | apiVersion: karpenter.sh/v1beta1 3 | kind: NodePool 4 | metadata: 5 | name: default 6 | spec: 7 | template: 8 | spec: 9 | requirements: 10 | - key: kubernetes.io/arch 11 | operator: In 12 | values: ["amd64"] 13 | - key: "karpenter.k8s.aws/instance-category" 14 | operator: In 15 | values: ["c", "m", "r"] 16 | - key: "karpenter.k8s.aws/instance-cpu" 17 | operator: In 18 | values: ["4", "8", "16", "32"] 19 | - key: "karpenter.sh/capacity-type" 20 | operator: In 21 | values: ["on-demand", "spot"] 22 | nodeClassRef: 23 | name: default 24 | limits: 25 | cpu: 1000 26 | memory: 2000Gi 27 | disruption: 28 | consolidationPolicy: WhenUnderutilized 29 | expireAfter: 720h # 30 * 24h = 720h 30 | --- 31 | # EC2NodeClass Configuration 32 | apiVersion: karpenter.k8s.aws/v1beta1 33 | kind: EC2NodeClass 34 | metadata: 35 | name: default 36 | spec: 37 | amiFamily: AL2 # Amazon Linux 2 38 | role: "KarpenterNodeRole-{cluster_name}" # Replace with your cluster name 39 | subnetSelectorTerms: 40 | - tags: 41 | karpenter.sh/discovery: "{cluster_name}" # Replace with your cluster name 42 | securityGroupSelectorTerms: 43 | - tags: 44 | karpenter.sh/discovery: "{cluster_name}" # Replace with your cluster name 45 | tags: 46 | Name: "karpenter-node" 47 | -------------------------------------------------------------------------------- /gitops/infrastructure/production/dependencies/02-application-karpenter-config.yaml: -------------------------------------------------------------------------------- 1 | # NodePool Configuration 2 | apiVersion: karpenter.sh/v1beta1 3 | kind: NodePool 4 | metadata: 5 | name: application 6 | spec: 7 | template: 8 | metadata: 9 | # Labels are arbitrary key-values that are applied to all nodes 10 | labels: 11 | node-type: applications 12 | spec: 13 | taints: 14 | - key: applications 15 | effect: NoSchedule 16 | requirements: 17 | - key: kubernetes.io/arch 18 | operator: In 19 | values: ["amd64"] 20 | - key: "karpenter.k8s.aws/instance-category" 21 | operator: In 22 | values: ["c", "m", "r"] 23 | - key: "karpenter.k8s.aws/instance-cpu" 24 | operator: In 25 | values: ["4", "8", "16", "32"] 26 | - key: "karpenter.sh/capacity-type" 27 | operator: In 28 | values: ["on-demand", "spot"] 29 | nodeClassRef: 30 | name: default 31 | limits: 32 | cpu: 1000 33 | memory: 2000Gi 34 | disruption: 35 | consolidationPolicy: WhenUnderutilized 36 | expireAfter: 720h # 30 * 24h = 720h -------------------------------------------------------------------------------- /gitops/infrastructure/production/dependencies/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - 01-default-karpenter-config.yaml 5 | - 02-application-karpenter-config.yaml 6 | -------------------------------------------------------------------------------- /gitops/infrastructure/production/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - 01-metric-server.yaml 5 | - 02-karpenter.yaml 6 | - 03-argo-workflows.yaml 7 | - 04-lb-controller.yaml 8 | - 05-kubecost.yaml 9 | - 06-argo-events.yaml 10 | - 07-tf-controller.yaml -------------------------------------------------------------------------------- /helm-charts/application-chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /helm-charts/application-chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: application-chart 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.0.1 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "0.0.1" 25 | -------------------------------------------------------------------------------- /helm-charts/application-chart/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "application-charts.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "application-charts.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "application-charts.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "application-charts.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /helm-charts/application-chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "application-charts.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "application-charts.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "application-charts.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "application-charts.labels" -}} 37 | helm.sh/chart: {{ include "application-charts.chart" . }} 38 | {{ include "application-charts.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "application-charts.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "application-charts.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "application-charts.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "application-charts.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /helm-charts/application-chart/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "application-charts.fullname" . }} 5 | labels: 6 | {{- include "application-charts.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "application-charts.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "application-charts.selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | serviceAccountName: {{ include "application-charts.serviceAccountName" . }} 28 | securityContext: 29 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 30 | containers: 31 | - name: {{ .Chart.Name }} 32 | securityContext: 33 | {{- toYaml .Values.securityContext | nindent 12 }} 34 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 35 | imagePullPolicy: {{ .Values.image.pullPolicy }} 36 | ports: 37 | - name: http 38 | containerPort: {{ .Values.service.port }} 39 | protocol: TCP 40 | livenessProbe: 41 | httpGet: 42 | path: / 43 | port: http 44 | readinessProbe: 45 | httpGet: 46 | path: / 47 | port: http 48 | resources: 49 | {{- toYaml .Values.resources | nindent 12 }} 50 | {{- with .Values.nodeSelector }} 51 | nodeSelector: 52 | {{- toYaml . | nindent 8 }} 53 | {{- end }} 54 | {{- with .Values.affinity }} 55 | affinity: 56 | {{- toYaml . | nindent 8 }} 57 | {{- end }} 58 | {{- with .Values.tolerations }} 59 | tolerations: 60 | {{- toYaml . | nindent 8 }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /helm-charts/application-chart/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "application-charts.fullname" . }} 6 | labels: 7 | {{- include "application-charts.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "application-charts.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /helm-charts/application-chart/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "application-charts.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "application-charts.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /helm-charts/application-chart/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "application-charts.fullname" . }} 5 | labels: 6 | {{- include "application-charts.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "application-charts.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /helm-charts/application-chart/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "application-charts.serviceAccountName" . }} 6 | labels: 7 | {{- include "application-charts.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /helm-charts/application-chart/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "application-charts.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "application-charts.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "application-charts.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /helm-charts/application-chart/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for application-charts. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: nginx 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: "" 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | 17 | serviceAccount: 18 | # Specifies whether a service account should be created 19 | create: true 20 | # Annotations to add to the service account 21 | annotations: {} 22 | # The name of the service account to use. 23 | # If not set and create is true, a name is generated using the fullname template 24 | name: "" 25 | 26 | podAnnotations: {} 27 | 28 | podSecurityContext: {} 29 | # fsGroup: 2000 30 | 31 | securityContext: {} 32 | # capabilities: 33 | # drop: 34 | # - ALL 35 | # readOnlyRootFilesystem: true 36 | # runAsNonRoot: true 37 | # runAsUser: 1000 38 | 39 | service: 40 | type: ClusterIP 41 | port: 80 42 | 43 | ingress: 44 | enabled: false 45 | className: "" 46 | annotations: {} 47 | # kubernetes.io/ingress.class: nginx 48 | # kubernetes.io/tls-acme: "true" 49 | hosts: 50 | - host: chart-example.local 51 | paths: 52 | - path: / 53 | pathType: ImplementationSpecific 54 | tls: [] 55 | # - secretName: chart-example-tls 56 | # hosts: 57 | # - chart-example.local 58 | 59 | resources: {} 60 | # We usually recommend not to specify default resources and to leave this as a conscious 61 | # choice for the user. This also increases chances charts run on environments with little 62 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 63 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 64 | # limits: 65 | # cpu: 100m 66 | # memory: 128Mi 67 | # requests: 68 | # cpu: 100m 69 | # memory: 128Mi 70 | 71 | autoscaling: 72 | enabled: false 73 | minReplicas: 1 74 | maxReplicas: 100 75 | targetCPUUtilizationPercentage: 80 76 | # targetMemoryUtilizationPercentage: 80 77 | 78 | nodeSelector: {} 79 | 80 | tolerations: [] 81 | 82 | affinity: {} 83 | -------------------------------------------------------------------------------- /helm-charts/helm-tenant-chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /helm-charts/helm-tenant-chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: helm-tenant-chart 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.0.1 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "0.0.1" 25 | -------------------------------------------------------------------------------- /helm-charts/helm-tenant-chart/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- range $appName, $appConfig := .Values.apps }} 2 | {{- if $appConfig.enabled }} 3 | --- 4 | apiVersion: apps/v1 5 | kind: Deployment 6 | metadata: 7 | name: {{ $.Values.tenantId }}-{{ $appName }} 8 | labels: 9 | app.kubernetes.io/name: "{{ $.Chart.Name }}-{{ $appName }}" 10 | app.kubernetes.io/instance: "{{ $.Release.Name }}" 11 | app.kubernetes.io/version: "{{ $.Chart.AppVersion }}" 12 | app.kubernetes.io/managed-by: "{{ $.Release.Service }}" 13 | app: {{ $.Values.tenantId }}-{{ $appName }} 14 | spec: 15 | replicas: {{ $appConfig.replicaCount }} 16 | selector: 17 | matchLabels: 18 | app.kubernetes.io/name: "{{ $.Chart.Name }}-{{ $appName }}" 19 | app.kubernetes.io/instance: "{{ $.Release.Name }}" 20 | app: {{ $.Values.tenantId }}-{{ $appName }} 21 | template: 22 | metadata: 23 | annotations: 24 | {{- toYaml $appConfig.podAnnotations | nindent 8 }} 25 | labels: 26 | app.kubernetes.io/name: "{{ $.Chart.Name }}-{{ $appName }}" 27 | app.kubernetes.io/instance: "{{ $.Release.Name }}" 28 | app: {{ $.Values.tenantId }}-{{ $appName }} 29 | spec: 30 | imagePullSecrets: 31 | {{- toYaml $appConfig.imagePullSecrets | nindent 8 }} 32 | serviceAccountName: {{ if $appConfig.serviceAccount.create }}{{ $.Values.tenantId }}-{{ $appName }}{{- else }}default{{- end }} 33 | securityContext: 34 | {{- toYaml $appConfig.podSecurityContext | nindent 8 }} 35 | containers: 36 | - name: {{ $appName }} 37 | securityContext: 38 | {{- toYaml $appConfig.securityContext | nindent 12 }} 39 | image: "{{ $appConfig.image.repository }}:{{ $appConfig.image.tag }}" 40 | imagePullPolicy: {{ $appConfig.image.pullPolicy }} 41 | env: 42 | - name: ENVIRONMENT 43 | value: "{{ $.Values.tenantId }}" 44 | ports: 45 | - name: http 46 | containerPort: {{ $appConfig.service.port }} 47 | protocol: TCP 48 | livenessProbe: 49 | httpGet: 50 | path: /{{ $appName }} 51 | port: http 52 | readinessProbe: 53 | httpGet: 54 | path: /{{ $appName }}/readiness-probe 55 | port: http 56 | initialDelaySeconds: 10 57 | periodSeconds: 5 58 | resources: 59 | {{- toYaml $appConfig.resources | nindent 12 }} 60 | nodeSelector: 61 | {{- toYaml $.Values.global.nodeSelector | nindent 8 }} 62 | affinity: 63 | {{- toYaml $appConfig.affinity | nindent 8 }} 64 | tolerations: 65 | {{- toYaml $.Values.global.tolerations | nindent 8 }} 66 | {{- end }} 67 | {{- end }} 68 | -------------------------------------------------------------------------------- /helm-charts/helm-tenant-chart/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- range $appName, $appConfig := .Values.apps }} 2 | {{- if and $appConfig.enabled $appConfig.autoscaling.enabled }} 3 | --- 4 | apiVersion: autoscaling/v2beta2 5 | kind: HorizontalPodAutoscaler 6 | metadata: 7 | name: {{ $.Values.tenantId }}-{{ $appName }}-hpa 8 | labels: 9 | app.kubernetes.io/name: {{ $.Chart.Name }}-{{ $appName }} 10 | app.kubernetes.io/instance: {{ $.Release.Name }} 11 | app.kubernetes.io/version: {{ $.Chart.AppVersion }} 12 | app.kubernetes.io/managed-by: {{ $.Release.Service }} 13 | spec: 14 | scaleTargetRef: 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | name: {{ $.Values.tenantId }}-{{ $appName }} 18 | minReplicas: {{ $appConfig.autoscaling.minReplicas }} 19 | maxReplicas: {{ $appConfig.autoscaling.maxReplicas }} 20 | metrics: 21 | {{- with $appConfig.autoscaling.targetCPUUtilizationPercentage }} 22 | - type: Resource 23 | resource: 24 | name: cpu 25 | target: 26 | type: Utilization 27 | averageUtilization: {{ . }} 28 | {{- end }} 29 | {{- with $appConfig.autoscaling.targetMemoryUtilizationPercentage }} 30 | - type: Resource 31 | resource: 32 | name: memory 33 | target: 34 | type: Utilization 35 | averageUtilization: {{ . }} 36 | {{- end }} 37 | {{- end }} 38 | {{- end }} -------------------------------------------------------------------------------- /helm-charts/helm-tenant-chart/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- range $appName, $appConfig := .Values.apps }} 2 | {{- if $appConfig.ingress.enabled }} 3 | --- 4 | apiVersion: networking.k8s.io/v1 5 | kind: Ingress 6 | metadata: 7 | name: "{{ $.Values.tenantId }}-ingress-{{ $appName }}" 8 | namespace: {{ if and (not $appConfig.enabled) $appConfig.ingress.enabled }}{{ $appConfig.envId }}{{ else }}{{ $.Values.tenantId }}{{ end }} 9 | labels: 10 | app.kubernetes.io/name: "{{ $.Chart.Name }}-{{ $appName }}" 11 | app.kubernetes.io/instance: "{{ $.Release.Name }}" 12 | app.kubernetes.io/version: "{{ $.Chart.AppVersion }}" 13 | app.kubernetes.io/managed-by: "Helm" 14 | annotations: 15 | alb.ingress.kubernetes.io/scheme: "internet-facing" 16 | alb.ingress.kubernetes.io/target-type: "ip" 17 | alb.ingress.kubernetes.io/healthcheck-path: "/{{ $appName }}" 18 | alb.ingress.kubernetes.io/group.name: "tenants-lb" 19 | alb.ingress.kubernetes.io/actions.{{ $.Values.tenantId }}-{{ $appName }}: > 20 | {"type":"fixed-response","fixedResponseConfig":{"contentType":"text/plain","statusCode":"200","messageBody":"HTTP header TenantID need to be set"}} 21 | alb.ingress.kubernetes.io/conditions.{{ $.Values.tenantId }}-{{ $appName }}: > 22 | [{"field":"http-header","httpHeaderConfig":{"httpHeaderName": "TenantID", "values":["{{ $.Values.tenantId }}"]}}] 23 | {{- with $appConfig.ingress.annotations }} 24 | {{- toYaml . | nindent 4 }} 25 | {{- end }} 26 | spec: 27 | ingressClassName: {{ $appConfig.ingress.className | quote }} 28 | rules: 29 | - http: 30 | paths: 31 | - path: /{{ $appName }} 32 | pathType: {{ $appConfig.ingress.pathType | default "ImplementationSpecific" }} 33 | backend: 34 | service: 35 | name: "{{ if $appConfig.enabled }}{{ $.Values.tenantId }}-{{ $appName }}{{ else }}{{ $.Values.tenantId }}-{{ $appName }}{{ end }}" 36 | port: 37 | number: {{ $appConfig.service.port }} 38 | {{- end }} 39 | {{- end }} 40 | -------------------------------------------------------------------------------- /helm-charts/helm-tenant-chart/templates/service.yaml: -------------------------------------------------------------------------------- 1 | {{- range $appName, $appConfig := .Values.apps }} 2 | {{- if $appConfig.ingress.enabled }} 3 | --- 4 | apiVersion: v1 5 | kind: Service 6 | metadata: 7 | name: "{{ $.Values.tenantId }}-{{ $appName }}" 8 | namespace: {{ if and (not $appConfig.enabled) $appConfig.ingress.enabled }}{{ $appConfig.envId }}{{ else }}{{ $.Values.tenantId }}{{ end }} 9 | labels: 10 | app.kubernetes.io/name: "{{ $.Chart.Name }}-{{ $appName }}" 11 | app.kubernetes.io/instance: "{{ $.Release.Name }}" 12 | app.kubernetes.io/version: "{{ $.Chart.AppVersion }}" 13 | app.kubernetes.io/managed-by: "{{ $.Release.Service }}" 14 | spec: 15 | type: {{ $appConfig.service.type }} 16 | ports: 17 | - port: {{ $appConfig.service.port }} 18 | targetPort: http 19 | protocol: TCP 20 | name: http 21 | selector: 22 | {{- if and $appConfig.enabled (or $appConfig.ingress.enabled) }} 23 | app: "{{ $.Values.tenantId }}-{{ $appName }}" 24 | {{- else }} 25 | app: "{{ $appConfig.envId }}-{{ $appName }}" 26 | {{- end }} 27 | {{- end }} 28 | {{- end }} -------------------------------------------------------------------------------- /helm-charts/helm-tenant-chart/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- range $appName, $appConfig := .Values.apps }} 2 | {{- if $appConfig.enabled }} 3 | {{- if $appConfig.serviceAccount.create }} 4 | --- 5 | apiVersion: v1 6 | kind: ServiceAccount 7 | metadata: 8 | name: "{{ $.Values.tenantId }}-{{ $appName }}" 9 | labels: 10 | app.kubernetes.io/name: "{{ $.Chart.Name }}-{{ $appName }}" 11 | app.kubernetes.io/instance: "{{ $.Release.Name }}" 12 | app.kubernetes.io/version: "{{ $.Chart.AppVersion }}" 13 | app.kubernetes.io/managed-by: "Helm" 14 | annotations: 15 | eks.amazonaws.com/role-arn: "arn:aws:iam::{{ $.Values.awsAccountId }}:role/{{ $appName }}-role-{{ $.Values.tenantId }}" 16 | {{- end }} 17 | {{- end }} 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /helm-charts/helm-tenant-chart/templates/terraform.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.infra -}} 2 | --- 3 | apiVersion: infra.contrib.fluxcd.io/v1alpha2 4 | kind: Terraform 5 | metadata: 6 | name: {{ .Values.tenantId }} 7 | namespace: flux-system 8 | spec: 9 | path: ./terraform/modules/tenant-apps 10 | interval: 1m 11 | approvePlan: auto 12 | destroyResourcesOnDeletion: true 13 | sourceRef: 14 | kind: GitRepository 15 | name: {{ .Values.infra.tfVersion }} 16 | vars: 17 | - name: tenant_id 18 | value: {{ .Values.tenantId }} 19 | {{- range $appName, $appConfig := .Values.apps }} 20 | - name: "enable_{{ $appName }}" 21 | value: {{ $appConfig.enabled }} 22 | {{- end }} 23 | writeOutputsToSecret: 24 | name: {{ .Values.tenantId }}-infra-output 25 | {{ end }} -------------------------------------------------------------------------------- /helm-charts/helm-tenant-chart/values.yaml.template: -------------------------------------------------------------------------------- 1 | # Default values for tenant-chart. 2 | awsAccountId: "{account_id}" 3 | tenantId: "example-tenant" 4 | 5 | # Global settings for node selection and tolerations 6 | global: 7 | nodeSelector: 8 | node-type: applications 9 | tolerations: 10 | - key: "applications" 11 | operator: "Exists" 12 | effect: "NoSchedule" 13 | 14 | # Infrastructure variables (controlled by templates) 15 | infra: 16 | tfVersion: "terraform-v0-0-1" # Version 1 is pre-created during template script execution 17 | 18 | # Application configurations 19 | apps: 20 | producer: 21 | enabled: true 22 | imagePullSecrets: [] 23 | replicaCount: 3 24 | image: 25 | repository: "{ecr_repository_urls_producer}" 26 | pullPolicy: "Always" 27 | tag: "0.1" # This tag is the one generated during template script execution 28 | serviceAccount: 29 | create: true 30 | podAnnotations: {} 31 | podSecurityContext: {} 32 | securityContext: {} 33 | service: 34 | type: "ClusterIP" 35 | port: 80 36 | resources: 37 | limits: 38 | cpu: "100m" 39 | memory: "128Mi" 40 | requests: 41 | cpu: "100m" 42 | memory: "128Mi" 43 | autoscaling: 44 | enabled: false 45 | minReplicas: 1 46 | maxReplicas: 100 47 | targetCPUUtilizationPercentage: 80 48 | affinity: {} 49 | envId: pool-1 50 | ingress: 51 | enabled: true 52 | className: "alb" 53 | pathType: "Prefix" 54 | tls: [] 55 | 56 | consumer: 57 | enabled: true 58 | imagePullSecrets: [] 59 | replicaCount: 3 60 | image: 61 | repository: "{ecr_repository_urls_consumer}" 62 | pullPolicy: "Always" 63 | tag: "0.1" 64 | serviceAccount: 65 | create: true 66 | podAnnotations: {} 67 | podSecurityContext: {} 68 | securityContext: {} 69 | service: 70 | type: "ClusterIP" 71 | port: 80 72 | resources: 73 | limits: 74 | cpu: "100m" 75 | memory: "128Mi" 76 | requests: 77 | cpu: "100m" 78 | memory: "128Mi" 79 | autoscaling: 80 | enabled: false 81 | minReplicas: 1 82 | maxReplicas: 100 83 | targetCPUUtilizationPercentage: 80 84 | affinity: {} 85 | envId: pool-1 86 | ingress: 87 | enabled: true 88 | className: "alb" 89 | pathType: "Prefix" 90 | tls: [] -------------------------------------------------------------------------------- /scripts/cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60" --silent) 3 | AWS_REGION=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed 's/\(.*\)[a-z]/\1/') 4 | TERRAFORM_FOLDER="/home/ec2-user/environment/workshop/eks-saas-gitops/terraform/workshop" #TODO: make it dynamic 5 | PUB_KEY_FILE_PATH="/home/ec2-user/environment/flux.pub" #TODO: make it dynamic 6 | IAM_USER_NAME="codecommit-user" 7 | 8 | ECR_REPOSITORIES=( 9 | "argoworkflow-container" 10 | "gitops-saas/helm-tenant-chart" 11 | "gitops-saas/application-chart" 12 | "consumer" 13 | "producer" 14 | "payments" 15 | "onboarding_service" 16 | ) 17 | 18 | delete_flux_managed_resources() { 19 | # stop flux reconciling from git 20 | flux delete source git flux-system --silent 21 | 22 | # get all helm releases 23 | RELEASES=$(helm list --all-namespaces --short) 24 | 25 | # delete helm releases with flux 26 | for name in $RELEASES; do 27 | flux delete helmrelease $name --silent 28 | done 29 | } 30 | 31 | # offboard tenants 32 | delete_flux_managed_resources 33 | 34 | # remove flux 35 | flux uninstall -s 36 | 37 | #delete code-commit user ssh key 38 | user_info=$(aws iam list-ssh-public-keys --user-name "$IAM_USER_NAME" ) 39 | key_ids=$(echo "$user_info" | jq -r '.SSHPublicKeys[]?.SSHPublicKeyId') 40 | for key_id in $key_ids; do 41 | echo "Deleting SSH Key: $key_id" 42 | aws iam delete-ssh-public-key --user-name "$IAM_USER_NAME" --ssh-public-key-id "$key_id" 43 | done 44 | 45 | # delete ECR repositories 46 | for repo in "${ECR_REPOSITORIES[@]}"; do 47 | aws ecr delete-repository --repository-name "$repo" --force 48 | done 49 | 50 | MAX_RETRIES=3 51 | COUNT=0 52 | SUCCESS=false 53 | 54 | while [ $COUNT -lt $MAX_RETRIES ]; do 55 | cd $TERRAFORM_FOLDER || exit 56 | 57 | # get terminating namespaces 58 | terminating_namespaces=$(kubectl get namespaces --field-selector status.phase=Terminating -o json | jq -r '.items[].metadata.name') 59 | 60 | # If there are no terminating namespaces, exit the script 61 | if [[ -z $terminating_namespaces ]]; then 62 | echo "No terminating namespaces found" 63 | fi 64 | 65 | #force terminate namespaces 66 | for ns in $terminating_namespaces; do 67 | echo "Terminating namespace: $ns" 68 | kubectl get namespace $ns -o json | sed 's/"kubernetes"//' | kubectl replace --raw "/api/v1/namespaces/$ns/finalize" -f - 69 | done 70 | 71 | #run destroy again 72 | timeout 1800 terraform destroy \ 73 | -var "aws_region=${AWS_REGION}" \ 74 | -var "public_key_file_path=${PUB_KEY_FILE_PATH}" \ 75 | --auto-approve 76 | 77 | if [ $? -eq 0 ]; then 78 | echo "Terraform destroy succeeded." 79 | SUCCESS=true 80 | break 81 | else 82 | echo "Terraform destroy failed. Retrying..." 83 | COUNT=$((COUNT+1)) 84 | fi 85 | done 86 | 87 | STATUS="SUCCESS" 88 | REASON="" 89 | if [ "$SUCCESS" = false ]; then 90 | STATUS="FAILED" 91 | REASON="Failed to delete resources managed by terraform. Please go to Cloud9 to delete them manually" 92 | fi 93 | 94 | #get cfn parameter from ssm 95 | CFN_PARAMETER="$(aws ssm get-parameter --name "eks-saas-gitops-custom-resource-event" --query "Parameter.Value" --output text)" 96 | 97 | #remove ssm parameter 98 | aws ssm delete-parameter --name "eks-saas-gitops-custom-resource-event" 99 | 100 | #set variables 101 | EVENT_STACK_ID=$(echo "$CFN_PARAMETER" | jq -r .StackId) 102 | EVENT_REQUEST_ID=$(echo "$CFN_PARAMETER" | jq -r .RequestId) 103 | EVENT_LOGICAL_RESOURCE_ID=$(echo "$CFN_PARAMETER" | jq -r .LogicalResourceId) 104 | EVENT_RESPONSE_URL=$(echo "$CFN_PARAMETER" | jq -r .ResponseURL) 105 | 106 | JSON_DATA='{ 107 | "Status": "'"$STATUS"'", 108 | "Reason": "'"$REASON"'", 109 | "StackId": "'"$EVENT_STACK_ID"'", 110 | "PhysicalResourceId": "Terraform", 111 | "RequestId": "'"$EVENT_REQUEST_ID"'", 112 | "LogicalResourceId": "'"$EVENT_LOGICAL_RESOURCE_ID"'" 113 | }' 114 | 115 | # Send the JSON data using curl 116 | curl -X PUT --data-binary "$JSON_DATA" "$EVENT_RESPONSE_URL" -------------------------------------------------------------------------------- /scripts/resize-cloud9-ebs-vol.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | 5 | # Specify the desired volume size in GiB as a command line argument. If not specified, default to 50 GiB. 6 | SIZE=${1:-50} 7 | 8 | # Get the ID of the environment host Amazon EC2 instance. 9 | TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60") 10 | INSTANCEID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null) 11 | REGION=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/placement/region 2> /dev/null) 12 | 13 | # Get the ID of the Amazon EBS volume associated with the instance. 14 | VOLUMEID=$(aws ec2 describe-instances \ 15 | --instance-id $INSTANCEID \ 16 | --query "Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId" \ 17 | --output text \ 18 | --region $REGION) 19 | 20 | # Resize the EBS volume. 21 | aws ec2 modify-volume --volume-id $VOLUMEID --size $SIZE 22 | 23 | # Wait for the resize to finish. 24 | while [ \ 25 | "$(aws ec2 describe-volumes-modifications \ 26 | --volume-id $VOLUMEID \ 27 | --filters Name=modification-state,Values="optimizing","completed" \ 28 | --query "length(VolumesModifications)"\ 29 | --output text)" != "1" ]; do 30 | sleep 1 31 | done 32 | 33 | # Check if we're on an NVMe filesystem 34 | if [[ -e "/dev/xvda" && $(readlink -f /dev/xvda) = "/dev/xvda" ]] 35 | then 36 | # Rewrite the partition table so that the partition takes up all the space that it can. 37 | sudo growpart /dev/xvda 1 38 | # Expand the size of the file system. 39 | # Check if we're on AL2 40 | STR=$(cat /etc/os-release) 41 | SUB="VERSION_ID=\"2\"" 42 | if [[ "$STR" == *"$SUB"* ]] 43 | then 44 | sudo xfs_growfs -d / 45 | else 46 | sudo resize2fs /dev/xvda1 47 | fi 48 | 49 | else 50 | # Rewrite the partition table so that the partition takes up all the space that it can. 51 | sudo growpart /dev/nvme0n1 1 52 | 53 | # Expand the size of the file system. 54 | # Check if we're on AL2 55 | STR=$(cat /etc/os-release) 56 | SUB="VERSION_ID=\"2\"" 57 | if [[ "$STR" == *"$SUB"* ]] 58 | then 59 | sudo xfs_growfs -d / 60 | else 61 | sudo resize2fs /dev/nvme0n1p1 62 | fi 63 | fi 64 | -------------------------------------------------------------------------------- /scripts/tenant-control.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #!/bin/bash 4 | 5 | # Function to display usage information 6 | function display_usage() { 7 | echo "Usage: $0 " 8 | echo -e "\nWhere:" 9 | echo -e " can be onboarding, offboarding, deployment, or testing." 10 | echo -e " is the tenant's tier." 11 | echo -e " is the version of the release for onboarding, offboarding, or deployment tasks." 12 | echo -e " is the ID of the tenant. For testing task, replace with ." 13 | echo -e "\nExample:" 14 | echo -e " $0 onboarding premium 1.0 tenant-123" 15 | echo -e " $0 testing basic tenant-456" 16 | exit 1 17 | } 18 | 19 | # Check if no arguments were passed 20 | if [ $# -eq 0 ]; then 21 | display_usage 22 | fi 23 | 24 | task=$1 25 | tenant_tier=$2 26 | release_version=$3 27 | tenant_id=$4 28 | 29 | # Check if argument is one of the allowed. 30 | if [[ ! $task =~ ^(onboarding|offboarding|deployment|testing)$ ]]; then 31 | echo "Invalid task: $task" 32 | display_usage 33 | fi 34 | 35 | if [ "$task" == "testing" ]; then 36 | if [ $# -ne 3 ]; then # Ensure correct number of arguments for testing 37 | echo "Testing task requires 3 arguments: task, tenant_tier, and tenant_id." 38 | display_usage 39 | fi 40 | tenant_id=$3 # In this case, is the 3rd argument for testing tenants 41 | else 42 | if [ $# -ne 4 ]; then # Ensure correct number of arguments for other tasks 43 | echo "$task task requires 4 arguments: task, tenant_tier, release_version, and tenant_id." 44 | display_usage 45 | fi 46 | fi 47 | 48 | 49 | if [ "$task" == "testing" ]; then 50 | tenant_id=$3 # In this case is 3 argument for testing tenants 51 | 52 | INGRESS_URL=$(kubectl get ingress -npool-1 | tail -1 | awk '{print $4}') 53 | curl --location --request POST "http://${INGRESS_URL}/producer" \ 54 | --header "tenantID: $tenant_id" \ 55 | --header "tier: $tenant_tier" 56 | exit 0 # Exit after printing usage information 57 | fi 58 | 59 | queue_url=$(aws sqs list-queues --queue-name-prefix "argoworkflows-$task" --query "QueueUrls[0]" --output text) 60 | 61 | # Check if the queue URL was successfully retrieved 62 | if [ -z "$queue_url" ]; then 63 | echo "Error: Queue URL for task $task not found." 64 | exit 1 65 | fi 66 | 67 | # Use valid JSON format for messages 68 | onboarding_msg="{\"tenant_id\": \"$tenant_id\", \"tenant_tier\": \"$tenant_tier\", \"release_version\": \"$release_version\"}" 69 | offboarding_msg="{\"tenant_id\": \"$tenant_id\", \"tenant_tier\": \"$tenant_tier\", \"release_version\": \"$release_version\"}" 70 | deployment_msg="{\"tenant_tier\": \"$tenant_tier\", \"release_version\": \"$release_version\"}" 71 | 72 | case $task in 73 | onboarding) 74 | msg=$onboarding_msg 75 | ;; 76 | offboarding) 77 | msg=$offboarding_msg 78 | ;; 79 | deployment) 80 | msg=$deployment_msg 81 | ;; 82 | esac 83 | 84 | echo "Task: $task, Tenant_id: $tenant_id, Queue: $queue_url, Release: $release_version" 85 | 86 | # Ensure that the variable expansions are quoted correctly 87 | aws sqs send-message --queue-url "$queue_url" --message-body "$msg" 88 | 89 | # Get Argo Workflow URL 90 | ARGO_WORKFLOW_URL=$(kubectl -n argo-workflows get service/argo-workflows-server -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') 91 | echo "Argo Workflow URL: http://$ARGO_WORKFLOW_URL:2746/workflows" 92 | -------------------------------------------------------------------------------- /static/github-repo-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/eks-saas-gitops/eb0094ad4f6d921c8fa2bd855ae13c7d1a8874db/static/github-repo-template.png -------------------------------------------------------------------------------- /tenant-microservices/consumer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9.17-slim 2 | COPY consumer.py / 3 | COPY requirements.txt / 4 | RUN pip install -r /requirements.txt 5 | EXPOSE 80 6 | ENTRYPOINT [ "python" ] 7 | CMD [ "/consumer.py" ] -------------------------------------------------------------------------------- /tenant-microservices/consumer/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | phases: 3 | pre_build: 4 | commands: 5 | - echo Logging in to Amazon ECR... 6 | - echo $(aws ecr get-login-password --region $AWS_DEFAULT_REGION) | docker login --username AWS --password-stdin $REPOSITORY_URI 7 | build: 8 | commands: 9 | - echo Build started on `date` 10 | - echo Building the Docker image... 11 | - docker build -t $REPOSITORY_URI:latest . 12 | - TIMESTAMP=$(date -u +"%Y%m%dT%H%M%SZ") 13 | - TAG="prd-$TIMESTAMP" 14 | - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$TAG 15 | post_build: 16 | commands: 17 | - echo Build completed on `date` 18 | - echo Pushing the Docker image... 19 | - docker push $REPOSITORY_URI:$TAG 20 | -------------------------------------------------------------------------------- /tenant-microservices/consumer/consumer.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask import request 3 | from threading import Thread 4 | from datetime import datetime 5 | import os 6 | import boto3 7 | import logging 8 | import sys 9 | import pytz 10 | 11 | app = Flask(__name__) 12 | logger = logging.getLogger("consumer") 13 | logger.setLevel(logging.INFO) 14 | logging.basicConfig(stream=sys.stdout) 15 | 16 | sts_client = boto3.client('sts') 17 | ssm_client = boto3.client("ssm") 18 | sqs_client = boto3.client("sqs") 19 | ddb_client = boto3.client("dynamodb") 20 | sms_queue_param_name_suffix = "consumer_sqs" 21 | sms_ddb_param_name_suffix = "consumer_ddb" 22 | 23 | environment = os.environ.get("ENVIRONMENT") 24 | service_name = "consumer" 25 | ms_version = "0.0.1" 26 | max_messages_to_read = 10 27 | wait_time_seconds = 20 28 | 29 | 30 | def get_queue_url(environment): 31 | response = ssm_client.get_parameter(Name=f"/{environment}/{sms_queue_param_name_suffix}") 32 | parts = response["Parameter"]["Value"].split(":") 33 | aws_region = parts[3] 34 | aws_account_id = parts[4] 35 | queue_name = parts[5].split("/")[-1] 36 | queue_url = f"https://sqs.{aws_region}.amazonaws.com/{aws_account_id}/{queue_name}" 37 | return queue_url 38 | 39 | 40 | def get_ddb_table_name(environment): 41 | response = ssm_client.get_parameter(Name=f"/{environment}/{sms_ddb_param_name_suffix}") 42 | parts = response["Parameter"]["Value"].split(":") 43 | table_name = parts[5].split("/")[-1] 44 | return table_name 45 | 46 | 47 | @app.route("/consumer", methods = ['GET']) 48 | def index(): 49 | tenant_id = request.headers.get("tenantID") 50 | return { 51 | "tenant_id": tenant_id, 52 | "environment": environment, 53 | "version": ms_version, 54 | "microservice": service_name 55 | } 56 | 57 | 58 | @app.route("/consumer/readiness-probe", methods = ['GET']) 59 | def probe(): 60 | try: 61 | sts_client.get_caller_identity() 62 | 63 | # Ready, lets start the thread to process messages. 64 | Thread(target = process_messages).start() 65 | 66 | return { "Status": "OK" } 67 | 68 | except Exception as e: 69 | return { "Status": "NotReady" }, 500 70 | 71 | 72 | def receive_message_from_sqs(queue_url, max_messages=5): 73 | response = sqs_client.receive_message( 74 | QueueUrl=queue_url, 75 | AttributeNames=["All"], 76 | MessageAttributeNames=["All"], 77 | WaitTimeSeconds=wait_time_seconds, 78 | MaxNumberOfMessages=max_messages 79 | ) 80 | return response.get("Messages", []) 81 | 82 | 83 | def get_utc_timestamp_string(): 84 | current_time = datetime.now(pytz.timezone("UTC")) 85 | return current_time.strftime("%Y-%m-%dT%H:%M:%S%z") 86 | 87 | 88 | def process_messages(): 89 | sqs_queue_url = get_queue_url(environment) 90 | ddb_table_name = get_ddb_table_name(environment) 91 | while True: 92 | try: 93 | logger.info(f"Searching for messages on queue: {sqs_queue_url}") 94 | messages = receive_message_from_sqs(sqs_queue_url, max_messages=max_messages_to_read) 95 | 96 | for message in messages: 97 | message_attributes = message["MessageAttributes"] 98 | message_id = message["MessageId"] 99 | tenant_id = message_attributes["tenant_id"]["StringValue"] 100 | producer_env = message_attributes["producer_environment"]["StringValue"] 101 | 102 | ddb_client.put_item( 103 | Item={ 104 | "tenant_id": { 105 | "S": tenant_id 106 | }, 107 | "message_id": { 108 | "S": message_id, 109 | }, 110 | "producer_environment": { 111 | "S": producer_env, 112 | }, 113 | "consumer_environment": { 114 | "S": environment, 115 | }, 116 | "timestamp": { 117 | "S": get_utc_timestamp_string(), 118 | }, 119 | }, 120 | TableName=ddb_table_name, 121 | ) 122 | logger.info(f"Message [{message_id}] persisted in DDB table: {ddb_table_name}") 123 | 124 | sqs_client.delete_message(QueueUrl=sqs_queue_url, ReceiptHandle=message["ReceiptHandle"]) 125 | logger.info(f"Message [{message_id}] deleted from queue {sqs_queue_url}") 126 | except Exception as e: 127 | logger.error("Exception raised! " + str(e)) 128 | 129 | 130 | if __name__ == "__main__": 131 | # run in 0.0.0.0 so that it can be accessed from outside the container 132 | app.run(host="0.0.0.0", port=80) -------------------------------------------------------------------------------- /tenant-microservices/consumer/requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.6.3 2 | click==8.1.7 3 | Flask==3.0.0 4 | itsdangerous==2.1.2 5 | Jinja2==3.1.3 6 | MarkupSafe==2.1.3 7 | Werkzeug==3.0.1 8 | boto3~=1.28.59 9 | botocore~=1.31.59 10 | pytz~=2022.1 -------------------------------------------------------------------------------- /tenant-microservices/payments/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9.17-slim 2 | COPY payments.py / 3 | COPY requirements.txt / 4 | RUN pip install -r /requirements.txt 5 | EXPOSE 80 6 | ENTRYPOINT [ "python" ] 7 | CMD [ "/payments.py" ] -------------------------------------------------------------------------------- /tenant-microservices/payments/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | phases: 3 | pre_build: 4 | commands: 5 | - echo Logging in to Amazon ECR... 6 | - echo $(aws ecr get-login-password --region $AWS_DEFAULT_REGION) | docker login --username AWS --password-stdin $REPOSITORY_URI 7 | build: 8 | commands: 9 | - echo Build started on `date` 10 | - echo Building the Docker image... 11 | - docker build -t $REPOSITORY_URI:latest . 12 | - TIMESTAMP=$(date -u +"%Y%m%dT%H%M%SZ") 13 | - TAG="prd-$TIMESTAMP" 14 | - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$TAG 15 | post_build: 16 | commands: 17 | - echo Build completed on `date` 18 | - echo Pushing the Docker image... 19 | - docker push $REPOSITORY_URI:$TAG 20 | -------------------------------------------------------------------------------- /tenant-microservices/payments/payments.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask import request 3 | import os 4 | 5 | app = Flask(__name__) 6 | 7 | environment = os.environ.get("ENVIRONMENT") 8 | service_name = "payments" 9 | ms_version = "1.0.0" 10 | 11 | @app.route("/payments") 12 | def index(): 13 | tenant_id = request.headers.get('tenantID') 14 | return { 15 | "tenant_id": tenant_id, 16 | "environment": environment, 17 | "version": ms_version, 18 | "microservice": service_name, 19 | } 20 | 21 | @app.route("/payments/readiness-probe", methods = ['GET']) 22 | def probe(): 23 | return { "Status": "OK" } 24 | 25 | if __name__ == "__main__": 26 | # run in 0.0.0.0 so that it can be accessed from outside the container 27 | app.run(host="0.0.0.0", port=80) -------------------------------------------------------------------------------- /tenant-microservices/payments/requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.6.3 2 | click==8.1.7 3 | Flask==3.0.0 4 | itsdangerous==2.1.2 5 | Jinja2==3.1.3 6 | MarkupSafe==2.1.3 7 | Werkzeug==3.0.1 -------------------------------------------------------------------------------- /tenant-microservices/producer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9.17-slim 2 | COPY producer.py / 3 | COPY requirements.txt / 4 | RUN pip install -r /requirements.txt 5 | EXPOSE 80 6 | ENTRYPOINT [ "python" ] 7 | CMD [ "/producer.py" ] -------------------------------------------------------------------------------- /tenant-microservices/producer/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | phases: 3 | pre_build: 4 | commands: 5 | - echo Logging in to Amazon ECR... 6 | - echo $(aws ecr get-login-password --region $AWS_DEFAULT_REGION) | docker login --username AWS --password-stdin $REPOSITORY_URI 7 | build: 8 | commands: 9 | - echo Build started on `date` 10 | - echo Building the Docker image... 11 | - docker build -t $REPOSITORY_URI:latest . 12 | - TIMESTAMP=$(date -u +"%Y%m%dT%H%M%SZ") 13 | - TAG="prd-$TIMESTAMP" 14 | - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$TAG 15 | post_build: 16 | commands: 17 | - echo Build completed on `date` 18 | - echo Pushing the Docker image... 19 | - docker push $REPOSITORY_URI:$TAG 20 | -------------------------------------------------------------------------------- /tenant-microservices/producer/producer.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask import request 3 | import boto3 4 | import os 5 | import logging 6 | 7 | app = Flask(__name__) 8 | app.logger.setLevel(logging.DEBUG) 9 | 10 | sts_client = boto3.client('sts') 11 | ssm_client = boto3.client("ssm") 12 | sqs_client = boto3.client("sqs") 13 | sms_queue_param_name_suffix = "consumer_sqs" 14 | 15 | environment = os.environ.get("ENVIRONMENT") 16 | service_name = "producer" 17 | ms_version = "0.0.1" 18 | 19 | 20 | def get_queue_url(tenant_id, tier): 21 | # basic tier uses pool environment parameters, otherwise tenant specific 22 | env_id = environment if tier == "basic" else tenant_id 23 | 24 | response = ssm_client.get_parameter(Name=f"/{env_id}/{sms_queue_param_name_suffix}") 25 | parts = response["Parameter"]["Value"].split(":") 26 | aws_region = parts[3] 27 | aws_account_id = parts[4] 28 | queue_name = parts[5].split("/")[-1] 29 | queue_url = f"https://sqs.{aws_region}.amazonaws.com/{aws_account_id}/{queue_name}" 30 | return queue_url 31 | 32 | 33 | @app.route("/producer/readiness-probe", methods = ['GET']) 34 | def probe(): 35 | try: 36 | sts_client.get_caller_identity() 37 | return { "Status": "OK" } 38 | except Exception as e: 39 | return { "Status": "NotReady" }, 500 40 | 41 | 42 | @app.route("/producer", methods = ['GET']) 43 | def get(): 44 | tenant_id = request.headers.get("tenantID") 45 | return { 46 | "tenant_id": tenant_id, 47 | "environment": environment, 48 | "version": ms_version, 49 | "microservice": service_name, 50 | } 51 | 52 | 53 | @app.route("/producer", methods = ['POST']) 54 | def post(): 55 | try: 56 | tenant_id = request.headers.get("tenantID") 57 | tier = request.headers.get("tier", default="basic") 58 | 59 | if (tier not in ["basic", "advanced", "premium"]): 60 | return { "msg": "BadRequest: invalid tier" }, 400 61 | 62 | if (tenant_id is None): 63 | return { "msg": "NotFound" }, 404 64 | 65 | response = sqs_client.send_message( 66 | QueueUrl=get_queue_url(tenant_id, tier), 67 | MessageAttributes={ 68 | "tenant_id": { 69 | "StringValue": tenant_id, 70 | "DataType": "String" 71 | }, 72 | "producer_environment": { 73 | "StringValue": environment, 74 | "DataType": "String" 75 | }, 76 | }, 77 | MessageBody=str({ "event": "Event raised!" }) 78 | ) 79 | 80 | message = { 81 | "tenant_id": tenant_id, 82 | "environment": environment, 83 | "version": ms_version, 84 | "microservice": service_name, 85 | "message_id": response["MessageId"] 86 | } 87 | app.logger.info(f"Message produced: {message}") 88 | return message 89 | 90 | except Exception as e: 91 | app.logger.error("Exception raised! " + str(e)) 92 | return { "msg": "Oops - please check application logs" }, 500 93 | 94 | 95 | if __name__ == "__main__": 96 | # run in 0.0.0.0 so that it can be accessed from outside the container 97 | app.run(host="0.0.0.0", port=80) -------------------------------------------------------------------------------- /tenant-microservices/producer/requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.6.3 2 | click==8.1.7 3 | Flask==3.0.0 4 | itsdangerous==2.1.2 5 | Jinja2==3.1.3 6 | MarkupSafe==2.1.3 7 | Werkzeug==3.0.1 8 | boto3~=1.28.59 9 | botocore~=1.31.59 10 | -------------------------------------------------------------------------------- /terraform/README.md: -------------------------------------------------------------------------------- 1 | # How to Provision This Stack 2 | 3 | This comprehensive guide is designed to assist you in efficiently setting up and provisioning the necessary stack. By adhering to the outlined steps and recommendations, you'll facilitate a seamless setup experience. 4 | 5 | ## Prerequisites 6 | 7 | Before initiating the setup process, please ensure the following tools are installed and configured on your system: 8 | 9 | - **Terraform**: Automate infrastructure management with ease. [Installation Guide](https://learn.hashicorp.com/tutorials/terraform/install-cli) 10 | - **Kubectl**: Interact with your Kubernetes cluster. [Installation Guide](https://kubernetes.io/docs/tasks/tools/) 11 | - **Flux CLI**: Manage GitOps for your cluster. [Installation Guide](https://fluxcd.io/flux/installation/) 12 | - **AWS CLI**: Control AWS services directly from your terminal. [Installation Guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) 13 | - **AWS Credentials**: Essential for authenticating AWS CLI and Terraform commands. [Configuration Guide](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) 14 | - **Key Pairs (Private and Public)**: Secure your connections with SSH keys. [SSH Key Generation Guide](https://en.wikibooks.org/wiki/Cryptography/Generate_a_keypair_using_OpenSSL) 15 | 16 | ## Installation Steps 17 | 18 | ### Step 1: Configure SSH Known Hosts 19 | To securely clone repositories, you must add AWS CodeCommit to your `known_hosts`. Replace `AWS_REGION` with your target AWS region: 20 | 21 | ```bash 22 | export AWS_REGION="" 23 | ssh-keyscan "git-codecommit.$AWS_REGION.amazonaws.com" >> ~/.ssh/known_hosts 24 | ``` 25 | 26 | ### Step 2: Run the Installation Script 27 | 28 | Replace the following variables with your own values. 29 | 30 | - `REPO_PATH`: Where to clone the created CodeCommit repositories. eg. `/tmp/workshop` 31 | - `PUBLIC_KEY`: Path to the public key generated previously 32 | - `PRIVATE_KEY`: Path to the private key generated previously 33 | - `KNOWN_HOSTS`: Path to known hosts file. 34 | 35 | ```bash 36 | export REPO_PATH="" 37 | export PUBLIC_KEY="" 38 | export PRIVATE_KEY="" 39 | export KNOWN_HOSTS="" 40 | ``` 41 | The `install.sh` script streamlines the provisioning process. 42 | 43 | ```bash 44 | ./install.sh $PUBLIC_KEY $PRIVATE_KEY $REPO_PATH $KNOWN_HOSTS 45 | ``` 46 | 47 | ### Step 3: Accessing the Environment 48 | 49 | Post-installation, use the `configure_kubectl` Terraform output to connect to your Kubernetes cluster: 50 | 51 | ```bash 52 | aws eks --region $AWS_REGION update-kubeconfig --name eks-saas-gitops 53 | ``` 54 | 55 | ### Step 4: Create git ssh keys in EKS for Argo Workflows 56 | 57 | Argo Workflows needs access to the git repository. Create a secret to store the private keys that Argo will use to clone and push changes to git during workflows. 58 | 59 | ```bash 60 | kubectl create secret generic github-ssh-key --from-file=ssh-privatekey= ~/.ssh/id_rsa --from-literal=ssh-privatekey.mode=0600 -nargo-workflows --kubeconfig ~/.kube/config 61 | ``` 62 | 63 | ## Ensuring Smooth Installation 64 | 65 | To guarantee a smooth installation: 66 | 67 | - Confirm the installation and configuration of all prerequisites. 68 | - Verify the AWS region in `echo $AWS_REGION` matches your intended provision region. 69 | - Ensure AWS credentials are correctly set to prevent any access or permission issues. 70 | 71 | ## Troubleshooting with quick_fix_flux.sh 72 | 73 | Occasionally, you might encounter errors due to race conditions during the provisioning process, such as failed Helm releases. Typical errors include: 74 | 75 | - Helm install failures due to webhook service unavailability. 76 | - Artifacts not being stored correctly for certain Helm releases. 77 | 78 | Should these or similar errors arise, run the `quick_fix_flux.sh` script to resolve them swiftly: 79 | 80 | ```bash 81 | ./quick_fix_flux.sh 82 | ``` 83 | 84 | This script dynamically identifies and deletes failed Helm releases, then reconciles your `flux-system` source to reattempt their installation. Running `quick_fix_flux.sh` ensures your environment stabilizes by rectifying transient errors that commonly occur due to race conditions during initial setup. 85 | 86 | ### How to Test the Architecture 87 | 88 | For a detailed guide on deploying and testing the architecture, including the deployment of tenants, setting up SQS queues, and managing Kubernetes deployments, please refer to the following Workshop: 89 | 90 | [Building SaaS applications on Amazon EKS using GitOps](https://catalog.workshops.aws/eks-saas-gitops). 91 | -------------------------------------------------------------------------------- /terraform/finish_cf_stack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script is used for AWS Provisioned environments only 3 | # Get cfn parameter from ssm created by Lambda function 4 | CFN_PARAMETER="$(aws ssm get-parameter --name "eks-saas-gitops-custom-resource-event" --query "Parameter.Value" --output text)" 5 | 6 | #set variables 7 | STATUS="SUCCESS" 8 | EVENT_STACK_ID=$(echo "$CFN_PARAMETER" | jq -r .StackId) 9 | EVENT_REQUEST_ID=$(echo "$CFN_PARAMETER" | jq -r .RequestId) 10 | EVENT_LOGICAL_RESOURCE_ID=$(echo "$CFN_PARAMETER" | jq -r .LogicalResourceId) 11 | EVENT_RESPONSE_URL=$(echo "$CFN_PARAMETER" | jq -r .ResponseURL) 12 | 13 | JSON_DATA='{ 14 | "Status": "'"$STATUS"'", 15 | "Reason": "Terraform executed successfully from Cloud9", 16 | "StackId": "'"$EVENT_STACK_ID"'", 17 | "PhysicalResourceId": "Terraform", 18 | "RequestId": "'"$EVENT_REQUEST_ID"'", 19 | "LogicalResourceId": "'"$EVENT_LOGICAL_RESOURCE_ID"'" 20 | }' 21 | 22 | # Send the JSON data using curl 23 | curl -X PUT --data-binary "$JSON_DATA" "$EVENT_RESPONSE_URL" -------------------------------------------------------------------------------- /terraform/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Set the base directory to the parent directory of this script 4 | BASE_DIR=$(dirname "$0") 5 | 6 | # Check if parameters are passed; if not, ask for them 7 | if [ -z "$1" ]; then 8 | read -p "Enter the path to your public key file: " public_key_file_path 9 | else 10 | public_key_file_path=$1 11 | fi 12 | 13 | if [ -z "$2" ]; then 14 | read -p "Enter the path to your private key file: " private_key_file_path 15 | else 16 | private_key_file_path=$2 17 | fi 18 | 19 | if [ -z "$3" ]; then 20 | read -p "Enter the clone directory: " clone_directory 21 | else 22 | clone_directory=$3 23 | fi 24 | 25 | if [ -z "$4" ]; then 26 | read -p "Enter the path to your known hosts file: " known_hosts 27 | else 28 | known_hosts=$4 29 | fi 30 | 31 | # Verify if all required inputs are provided 32 | if [ -z "$public_key_file_path" ] || [ -z "$private_key_file_path" ] || [ -z "$clone_directory" ] || [ -z "$known_hosts" ]; then 33 | echo "All inputs are required. Please provide the missing information." 34 | exit 1 35 | fi 36 | 37 | # Path where values.yaml will be created 38 | values_yaml_path="$BASE_DIR/workshop/flux-secrets.yaml" 39 | 40 | # Create values.yaml with the provided information 41 | cat < "$values_yaml_path" 42 | secret: 43 | create: true 44 | data: 45 | identity: |- 46 | $(sed 's/^/ /' "$private_key_file_path") 47 | identity.pub: |- 48 | $(sed 's/^/ /' "$public_key_file_path") 49 | known_hosts: |- 50 | $(sed 's/^/ /' "$known_hosts") 51 | EOF 52 | 53 | echo "$values_yaml_path" 54 | 55 | # Navigate to the workshop directory where the module implementations are 56 | cd "$BASE_DIR/workshop" || exit 57 | 58 | # Initialize Terraform 59 | terraform init 60 | terraform validate 61 | 62 | # Define the list of modules and resources to apply in order 63 | declare -a terraform_targets=( 64 | "module.vpc" 65 | "module.ebs_csi_irsa_role" 66 | "module.eks" 67 | "module.gitops_saas_infra" 68 | "null_resource.execute_templating_script" 69 | "module.flux_v2" 70 | ) 71 | 72 | # Apply the Terraform configurations in the specified order 73 | for target in "${terraform_targets[@]}"; do 74 | echo "Applying: $target" 75 | 76 | # Attempt counter 77 | attempt=1 78 | while [ $attempt -le 3 ]; do 79 | echo "Attempt $attempt of applying $target..." 80 | 81 | # Run Terraform apply 82 | terraform apply -target="$target" \ 83 | -var "public_key_file_path=$public_key_file_path" \ 84 | -var "clone_directory=$clone_directory" \ 85 | -var "aws_region=$AWS_REGION" \ 86 | -auto-approve 87 | 88 | # Check if Terraform apply was successful 89 | if [ $? -eq 0 ]; then 90 | echo "$target applied successfully." 91 | break # Exit the loop if apply was successful 92 | else 93 | echo "Failed to apply $target, retrying..." 94 | ((attempt++)) # Increment attempt counter 95 | 96 | # Optional: Add a sleep here if you want to wait before retrying 97 | # sleep 10 98 | fi 99 | 100 | # If reached maximum attempts and still failed 101 | if [ $attempt -gt 3 ]; then 102 | echo "Failed to apply $target after 3 attempts." 103 | exit 1 # Exit script with error 104 | fi 105 | done 106 | done 107 | 108 | echo "All specified Terraform modules and resources have been applied." 109 | -------------------------------------------------------------------------------- /terraform/modules/codebuild/README.md: -------------------------------------------------------------------------------- 1 | # AWS Codebuild Terraform Module 2 | 3 | ## Requirements 4 | 5 | | Name | Version | 6 | |------|---------| 7 | | [terraform](#requirement\_terraform) | >= 1.0 | 8 | | [aws](#requirement\_aws) | >= 5.0 | 9 | 10 | ## Providers 11 | 12 | | Name | Version | 13 | |------|---------| 14 | | [aws](#provider\_aws) | >= 5.0 | 15 | 16 | ## Modules 17 | 18 | No modules. 19 | 20 | ## Resources 21 | 22 | | Name | Type | 23 | |------|------| 24 | | [aws_codebuild_project.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codebuild_project) | resource | 25 | | [aws_iam_role.codebuild](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | 26 | | [aws_iam_role_policy.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | 27 | | [aws_security_group.code_build_default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | 28 | 29 | ## Inputs 30 | 31 | | Name | Description | Type | Default | Required | 32 | |------|-------------|------|---------|:--------:| 33 | | [bucket\_id](#input\_bucket\_id) | Amazon S3 bucket ID | `string` | n/a | yes | 34 | | [build\_timeout](#input\_build\_timeout) | AWS Codebuild build timeout | `string` | `"5"` | no | 35 | | [codebuild\_description](#input\_codebuild\_description) | Description for AWS Codebuild project | `string` | `"Sample Docker build"` | no | 36 | | [codebuild\_project\_name](#input\_codebuild\_project\_name) | Name for AWS Codebuild project | `string` | n/a | yes | 37 | | [iam\_policy](#input\_iam\_policy) | AWS Codebuild IAM Policy | `any` | `"{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"Stmt1652709134956\",\n \"Action\": \"*\",\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n"` | no | 38 | | [private\_subnet\_list](#input\_private\_subnet\_list) | List of the Amazon VPC Subnets | `list(string)` | `[]` | no | 39 | | [repo\_uri](#input\_repo\_uri) | Amazon ECR repo URI | `string` | n/a | yes | 40 | | [tags](#input\_tags) | Tags | `map(string)` | `{}` | no | 41 | | [vpc\_id](#input\_vpc\_id) | Amazon VPC ID | `string` | n/a | yes | 42 | 43 | ## Outputs 44 | 45 | | Name | Description | 46 | |------|-------------| 47 | | [arn](#output\_arn) | ARN of AWS CodeBuild project | 48 | | [id](#output\_id) | ID of AWS CodeBuild project | 49 | -------------------------------------------------------------------------------- /terraform/modules/codebuild/main.tf: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # IAM Roles 3 | ################################################################################ 4 | resource "aws_iam_role" "codebuild" { 5 | name = "codebuildrole-${var.codebuild_project_name}" 6 | 7 | assume_role_policy = < [terraform](#requirement\_terraform) | >= 1.0 | 9 | | [aws](#requirement\_aws) | >= 5.0 | 10 | 11 | ## Providers 12 | 13 | | Name | Version | 14 | |------|---------| 15 | | [aws](#provider\_aws) | >= 5.0 | 16 | 17 | ## Modules 18 | 19 | No modules. 20 | 21 | ## Resources 22 | 23 | | Name | Type | 24 | |------|------| 25 | | [aws_codepipeline.pipeline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codepipeline) | resource | 26 | | [aws_iam_role.codepipeline_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | 27 | | [aws_iam_role_policy.codepipeline_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | 28 | 29 | ## Inputs 30 | 31 | | Name | Description | Type | Default | Required | 32 | |------|-------------|------|---------|:--------:| 33 | | [branch\_name](#input\_branch\_name) | AWS Codecommit branch name | `string` | `"main"` | no | 34 | | [bucket\_id](#input\_bucket\_id) | Amazon S3 bucket ID | `string` | n/a | yes | 35 | | [codebuild\_project](#input\_codebuild\_project) | Name for AWS Codebuild project | `string` | n/a | yes | 36 | | [iam\_policy](#input\_iam\_policy) | AWS Codepipeline IAM Policy | `any` | `"{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"Stmt1652709134956\",\n \"Action\": \"*\",\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n"` | no | 37 | | [pipeline\_name](#input\_pipeline\_name) | Name for AWS Codepipeline | `string` | n/a | yes | 38 | | [repo\_name](#input\_repo\_name) | AWS Codecommit repo name | `string` | n/a | yes | 39 | 40 | ## Outputs 41 | 42 | No outputs. 43 | -------------------------------------------------------------------------------- /terraform/modules/codepipeline/main.tf: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # IAM Roles 3 | ################################################################################ 4 | resource "aws_iam_role" "codepipeline_role" { 5 | name = "codepipelinerole-${var.pipeline_name}" 6 | 7 | assume_role_policy = < [terraform](#requirement\_terraform) | >= 1.0 | 8 | | [aws](#requirement\_aws) | >= 5.0 | 9 | | [helm](#requirement\_helm) | >= 2.9 | 10 | | [kubernetes](#requirement\_kubernetes) | >= 2.20 | 11 | 12 | ## Providers 13 | 14 | | Name | Version | 15 | |------|---------| 16 | | [helm](#provider\_helm) | >= 2.9 | 17 | | [kubernetes](#provider\_kubernetes) | >= 2.20 | 18 | 19 | ## Modules 20 | 21 | No modules. 22 | 23 | ## Resources 24 | 25 | | Name | Type | 26 | |------|------| 27 | | [helm_release.flux2](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | 28 | | [helm_release.flux2-sync](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | 29 | | [kubernetes_namespace.flux_system](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace) | resource | 30 | 31 | ## Inputs 32 | 33 | | Name | Description | Type | Default | Required | 34 | |------|-------------|------|---------|:--------:| 35 | | [activate\_helm\_controller](#input\_activate\_helm\_controller) | Defines if Helm controller should be deployed | `bool` | `true` | no | 36 | | [activate\_image\_automation\_controller](#input\_activate\_image\_automation\_controller) | Defines if image automation controller should be activated | `bool` | `false` | no | 37 | | [activate\_image\_reflection\_controller](#input\_activate\_image\_reflection\_controller) | Defines if image automation controller should be activated | `bool` | `false` | no | 38 | | [activate\_kustomize\_controller](#input\_activate\_kustomize\_controller) | Defines if Kustomize controller should be activated | `bool` | `true` | no | 39 | | [activate\_notification\_controller](#input\_activate\_notification\_controller) | Defines if notification controller should be activated | `bool` | `true` | no | 40 | | [activate\_source\_controller](#input\_activate\_source\_controller) | Defines if source controller should be activated | `bool` | `true` | no | 41 | | [ca](#input\_ca) | Amazon EKS Certificate authority | `string` | n/a | yes | 42 | | [cluster\_endpoint](#input\_cluster\_endpoint) | Amazon EKS Cluster endpoint URL | `string` | n/a | yes | 43 | | [git\_branch](#input\_git\_branch) | Git branch name to be used by Flux | `string` | `"main"` | no | 44 | | [git\_url](#input\_git\_url) | Git URL to be used by Flux | `string` | n/a | yes | 45 | | [image\_automation\_controller\_sa\_annotations](#input\_image\_automation\_controller\_sa\_annotations) | Defines image automation controller SA annotations | `string` | `""` | no | 46 | | [image\_reflection\_controller\_sa\_annotations](#input\_image\_reflection\_controller\_sa\_annotations) | Defines image reflection controller SA annotations | `string` | `""` | no | 47 | | [kustomization\_path](#input\_kustomization\_path) | n/a | `string` | `"Path for Kustomization directory"` | no | 48 | | [namespace](#input\_namespace) | Flux default Kubernetes namespace | `string` | `"flux-system"` | no | 49 | | [token](#input\_token) | Amazon EKS Cluster token | `string` | n/a | yes | 50 | | [values\_path](#input\_values\_path) | Path for Helm values | `string` | n/a | yes | 51 | 52 | ## Outputs 53 | 54 | No outputs. 55 | -------------------------------------------------------------------------------- /terraform/modules/flux_cd/main.tf: -------------------------------------------------------------------------------- 1 | provider "kubernetes" { 2 | host = var.cluster_endpoint 3 | cluster_ca_certificate = base64decode(var.ca) 4 | token = var.token 5 | } 6 | 7 | provider "helm" { 8 | kubernetes { 9 | host = var.cluster_endpoint 10 | cluster_ca_certificate = base64decode(var.ca) 11 | token = var.token 12 | } 13 | } 14 | 15 | resource "kubernetes_namespace" "flux_system" { 16 | metadata { 17 | name = "flux-system" 18 | } 19 | } 20 | 21 | resource "helm_release" "flux2-sync" { 22 | name = "flux-system" 23 | namespace = var.namespace 24 | repository = "https://fluxcd-community.github.io/helm-charts" 25 | chart = "flux2-sync" 26 | version = var.flux2_sync_version 27 | 28 | values = [ 29 | file(var.flux2_sync_secret_values) 30 | ] 31 | 32 | set { 33 | name = "secret.create" 34 | value = true 35 | } 36 | 37 | set { 38 | name = "gitRepository.spec.ref.branch" 39 | value = var.git_branch 40 | } 41 | 42 | set { 43 | name = "gitRepository.spec.url" 44 | value = var.git_url # The repository URL, can be an HTTP/S or SSH address. 45 | } 46 | 47 | set { 48 | name = "kustomization.spec.path" 49 | value = var.kustomization_path 50 | } 51 | 52 | depends_on = [helm_release.flux2, kubernetes_namespace.flux_system] 53 | } 54 | 55 | # TODO: Implement IRSA and change the Service Account name, for Image Controller 56 | resource "helm_release" "flux2" { 57 | name = "flux2" 58 | namespace = var.namespace 59 | repository = "https://fluxcd-community.github.io/helm-charts" 60 | chart = "flux2" 61 | version = var.flux2_version 62 | 63 | set { 64 | name = "helmController.create" 65 | value = var.activate_helm_controller 66 | } 67 | 68 | set { 69 | name = "imageAutomationController.create" 70 | value = var.activate_image_automation_controller 71 | } 72 | 73 | set { 74 | name = "imageAutomationController.serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn" 75 | value = var.image_automation_controller_sa_annotations 76 | } 77 | 78 | set { 79 | name = "imageReflectionController.create" 80 | value = var.activate_image_reflection_controller 81 | } 82 | 83 | set { 84 | name = "imageReflectionController.serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn" 85 | value = var.image_reflection_controller_sa_annotations 86 | } 87 | 88 | set { 89 | name = "kustomizeController.create" 90 | value = var.activate_kustomize_controller 91 | } 92 | 93 | set { 94 | name = "notificationController.create" 95 | value = var.activate_notification_controller 96 | } 97 | 98 | set { 99 | name = "sourceController.create" 100 | value = var.activate_source_controller 101 | } 102 | 103 | depends_on = [kubernetes_namespace.flux_system] 104 | } 105 | -------------------------------------------------------------------------------- /terraform/modules/flux_cd/outputs.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/eks-saas-gitops/eb0094ad4f6d921c8fa2bd855ae13c7d1a8874db/terraform/modules/flux_cd/outputs.tf -------------------------------------------------------------------------------- /terraform/modules/flux_cd/variables.tf: -------------------------------------------------------------------------------- 1 | variable "cluster_endpoint" { 2 | description = "Amazon EKS Cluster endpoint URL" 3 | type = string 4 | } 5 | 6 | variable "ca" { 7 | description = "Amazon EKS Certificate authority" 8 | type = string 9 | } 10 | 11 | variable "token" { 12 | description = "Amazon EKS Cluster token" 13 | type = string 14 | } 15 | 16 | variable "git_branch" { 17 | description = "Git branch name to be used by Flux" 18 | type = string 19 | default = "main" 20 | } 21 | 22 | variable "git_url" { 23 | description = "Git URL to be used by Flux" 24 | type = string 25 | } 26 | 27 | variable "namespace" { 28 | description = "Flux default Kubernetes namespace" 29 | type = string 30 | default = "flux-system" 31 | } 32 | 33 | variable "activate_helm_controller" { 34 | description = "Defines if Helm controller should be deployed" 35 | type = bool 36 | default = true 37 | } 38 | 39 | variable "activate_image_automation_controller" { 40 | description = "Defines if image automation controller should be activated" 41 | type = bool 42 | default = true 43 | } 44 | 45 | variable "image_automation_controller_sa_annotations" { 46 | description = "Defines image automation controller SA annotations" 47 | type = string 48 | default = "" 49 | } 50 | 51 | variable "activate_image_reflection_controller" { 52 | description = "Defines if image automation controller should be activated" 53 | type = bool 54 | default = true 55 | } 56 | 57 | variable "image_reflection_controller_sa_annotations" { 58 | description = "Defines image reflection controller SA annotations" 59 | type = string 60 | default = "" 61 | } 62 | 63 | variable "activate_kustomize_controller" { 64 | description = "Defines if Kustomize controller should be activated" 65 | type = bool 66 | default = true 67 | } 68 | 69 | variable "activate_notification_controller" { 70 | description = "Defines if notification controller should be activated" 71 | type = bool 72 | default = true 73 | } 74 | 75 | variable "activate_source_controller" { 76 | description = "Defines if source controller should be activated" 77 | type = bool 78 | default = true 79 | } 80 | 81 | variable "kustomization_path" { 82 | default = "Path for Kustomization directory" 83 | type = string 84 | } 85 | 86 | # variable "known_hosts" { 87 | # description = "Path to known hosts file" 88 | # default = "" 89 | # } 90 | 91 | # variable "private_key_path" { 92 | # description = "Path to private key" 93 | # default = "" 94 | # } 95 | 96 | # variable "public_key_path" { 97 | # description = "Path to public key" 98 | # default = "" 99 | # } 100 | 101 | variable "flux2_sync_version" { 102 | description = "Flux2 sync helm chart version" 103 | type = string 104 | default = "1.8.2" 105 | } 106 | 107 | variable "flux2_version" { 108 | description = "Flux2 helm chart version" 109 | type = string 110 | default = "2.13.0" 111 | } 112 | 113 | variable "flux2_sync_secret_values" { 114 | default = "" 115 | } -------------------------------------------------------------------------------- /terraform/modules/flux_cd/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.0" 8 | } 9 | helm = { 10 | source = "hashicorp/helm" 11 | version = ">= 2.9" 12 | } 13 | kubernetes = { 14 | source = "hashicorp/kubernetes" 15 | version = ">= 2.20" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /terraform/modules/gitops-saas-infra/apps_needs.tf: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # ECR repositories for Utilities 3 | ################################################################################ 4 | resource "aws_ecr_repository" "tenant_helm_chart" { 5 | name = var.tenant_helm_chart_repo 6 | image_tag_mutability = "MUTABLE" 7 | 8 | image_scanning_configuration { 9 | scan_on_push = true 10 | } 11 | } 12 | 13 | resource "aws_ecr_repository" "application_helm_chart" { 14 | name = var.application_helm_chart_repo 15 | image_tag_mutability = "MUTABLE" 16 | 17 | image_scanning_configuration { 18 | scan_on_push = true 19 | } 20 | } 21 | 22 | resource "aws_ecr_repository" "argoworkflow_container" { 23 | name = var.argoworkflow_container_repo 24 | image_tag_mutability = "MUTABLE" 25 | 26 | image_scanning_configuration { 27 | scan_on_push = true 28 | } 29 | } 30 | 31 | ################################################################################ 32 | # Microsservices, ECR, CodeCommit, CodeBuild and CodePipeline 33 | ################################################################################ 34 | resource "random_uuid" "this" {} 35 | 36 | resource "aws_s3_bucket" "codeartifacts" { 37 | bucket = "codestack-artifacts-bucket-${random_uuid.this.result}" 38 | force_destroy = true 39 | } 40 | 41 | module "codecommit" { 42 | source = "lgallard/codecommit/aws" 43 | version = "0.2.1" 44 | for_each = var.microservices 45 | 46 | repository_name = each.key 47 | description = each.value.description 48 | default_branch = each.value.default_branch 49 | } 50 | 51 | 52 | resource "aws_ecr_repository" "microservice_container" { 53 | for_each = var.microservices 54 | 55 | name = each.key 56 | image_tag_mutability = "MUTABLE" 57 | 58 | image_scanning_configuration { 59 | scan_on_push = true 60 | } 61 | 62 | tags = { 63 | Name = each.key 64 | Description = each.value.description 65 | } 66 | } 67 | 68 | module "codebuild_project" { 69 | source = "../codebuild" 70 | for_each = var.microservices 71 | 72 | vpc_id = var.vpc_id 73 | codebuild_project_name = each.value.codebuild_project_name 74 | private_subnet_list = var.private_subnets 75 | bucket_id = aws_s3_bucket.codeartifacts.id 76 | repo_uri = aws_ecr_repository.microservice_container[each.key].repository_url 77 | } 78 | 79 | module "codepipeline" { 80 | source = "../codepipeline" 81 | for_each = var.microservices 82 | 83 | pipeline_name = each.value.pipeline_name 84 | codebuild_project = module.codebuild_project[each.key].name 85 | repo_name = module.codecommit[each.key].name 86 | bucket_id = aws_s3_bucket.codeartifacts.id 87 | } 88 | -------------------------------------------------------------------------------- /terraform/modules/gitops-saas-infra/data.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/eks-saas-gitops/eb0094ad4f6d921c8fa2bd855ae13c7d1a8874db/terraform/modules/gitops-saas-infra/data.tf -------------------------------------------------------------------------------- /terraform/modules/gitops-saas-infra/outputs.tf: -------------------------------------------------------------------------------- 1 | output "karpenter_node_role_arn" { 2 | value = aws_iam_role.karpenter_node_role.arn 3 | } 4 | 5 | output "tf_controller_irsa" { 6 | description = "IAM Role for TF Controller Service Account" 7 | value = module.tf_controller_irsa_role.iam_role_arn 8 | } 9 | 10 | output "lb_controller_irsa" { 11 | description = "IAM Role for LB Controller Service Account" 12 | value = module.lb_controller_irsa.iam_role_arn 13 | } 14 | 15 | output "karpenter_irsa" { 16 | description = "IAM Role for Karpenter Service Account" 17 | value = module.karpenter_irsa_role.iam_role_arn 18 | } 19 | 20 | output "argo_workflows_irsa" { 21 | description = "IAM Role for Argo Workflows Service Account" 22 | value = module.argo_workflows_eks_role.iam_role_arn 23 | } 24 | 25 | output "argo_events_irsa" { 26 | description = "IAM Role for Argo Events Service Account" 27 | value = module.argo_events_eks_role.iam_role_arn 28 | } 29 | 30 | output "argo_workflows_bucket_name" { 31 | description = "Amazon S3 bucket that Argo Workflows will store its artifacts" 32 | value = aws_s3_bucket.argo_artifacts.id 33 | } 34 | 35 | output "ecr_helm_chart_url" { 36 | description = "URL for Amazon ECR stored chart" 37 | value = aws_ecr_repository.tenant_helm_chart.repository_url 38 | } 39 | 40 | output "ecr_helm_chart_url_application" { 41 | description = "URL for Amazon ECR stored chart" 42 | value = aws_ecr_repository.application_helm_chart.repository_url 43 | } 44 | 45 | output "ecr_argoworkflow_container" { 46 | description = "URL for Amazon ECR stored Argo Workflows container" 47 | value = aws_ecr_repository.argoworkflow_container.repository_url 48 | } 49 | 50 | output "argoworkflows_onboarding_queue_url" { 51 | description = "URL for the ArgoWokflows Onboarding SQS Queue" 52 | value = aws_sqs_queue.argoworkflows_onboarding_queue.url 53 | } 54 | 55 | output "argoworkflows_offboarding_queue_url" { 56 | description = "URL for the ArgoWokflows Onboarding SQS Queue" 57 | value = aws_sqs_queue.argoworkflows_offboarding_queue.url 58 | } 59 | 60 | output "argoworkflows_deployment_queue_url" { 61 | description = "URL for the ArgoWokflows Onboarding SQS Queue" 62 | value = aws_sqs_queue.argoworkflows_deployment_queue.url 63 | } 64 | 65 | ################## 66 | # Flux Repo 67 | ################## 68 | output "aws_codecommit_flux_clone_url_http" { 69 | description = "AWS CodeCommit HTTP based clone URL" 70 | value = module.codecommit_flux.clone_url_http 71 | } 72 | 73 | output "aws_codecommit_flux_clone_url_ssh" { 74 | description = "AWS CodeCommit SSH based clone URL including the SSH public key ID for the Flux repository." 75 | value = replace( 76 | module.codecommit_flux.clone_url_ssh, 77 | "ssh://", 78 | format("ssh://%s@", aws_iam_user_ssh_key.codecommit_user.ssh_public_key_id) 79 | ) 80 | } 81 | 82 | 83 | output "ssh_public_key_id" { 84 | description = "The SSH public key ID for the CodeCommit user" 85 | value = aws_iam_user_ssh_key.codecommit_user.ssh_public_key_id 86 | } 87 | 88 | ################## 89 | # Applications 90 | ################## 91 | output "codecommit_repository_urls" { 92 | value = { 93 | for key, repo in module.codecommit : 94 | key => replace( 95 | repo.clone_url_ssh, 96 | "ssh://", 97 | format("ssh://%s@", aws_iam_user_ssh_key.codecommit_user.ssh_public_key_id) 98 | ) 99 | } 100 | description = "The SSH clone URLs of the CodeCommit repositories, including the SSH public key ID." 101 | } 102 | 103 | 104 | 105 | 106 | output "ecr_repository_urls" { 107 | value = { for key, repo in aws_ecr_repository.microservice_container : key => repo.repository_url } 108 | description = "The URLs of the ECR repositories." 109 | } 110 | 111 | -------------------------------------------------------------------------------- /terraform/modules/gitops-saas-infra/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Stack name" 3 | default = "eks-saas-gitops" 4 | } 5 | 6 | variable "cluster_name" { 7 | description = "Name of Amazon EKS Cluster" 8 | default = "eks-saas" 9 | } 10 | 11 | variable "cluster_oidc_provider_arn" { 12 | description = "OIDC ARN of Amazon EKS Cluster" 13 | default = "" 14 | } 15 | 16 | # TBD: This could be a loop like we are using for microsservices 17 | variable "tenant_helm_chart_repo" { 18 | description = "Repository for Tenant Helm chart" 19 | type = string 20 | default = "gitops-saas/helm-tenant-chart" 21 | } 22 | 23 | variable "application_helm_chart_repo" { 24 | description = "Repository for Application chart" 25 | type = string 26 | default = "gitops-saas/application-chart" 27 | } 28 | 29 | variable "argoworkflow_container_repo" { 30 | description = "Repository for Argo Workflows container image" 31 | type = string 32 | default = "argoworkflow-container" 33 | } 34 | 35 | variable "vpc_id" { 36 | description = "ID of the VPC to deploy CodeBuild projects" 37 | } 38 | 39 | variable "private_subnets" { 40 | description = "List of private subnets to place CodeBuild project" 41 | } 42 | 43 | variable "microservices" { 44 | description = "Configuration for each microservice" 45 | type = map(object({ 46 | description : string 47 | default_branch : string 48 | codebuild_project_name : string 49 | pipeline_name : string 50 | })) 51 | default = { 52 | "producer" = { 53 | description = "Producer microservice repository", 54 | default_branch = "main", 55 | codebuild_project_name = "producer-codebuild", 56 | pipeline_name = "producer-pipeline", 57 | }, 58 | "consumer" = { 59 | description = "Consumer microservice repository", 60 | default_branch = "main", 61 | codebuild_project_name = "consumer-codebuild", 62 | pipeline_name = "consumer-pipeline", 63 | }, 64 | "payments" = { 65 | description = "Payments microservice repository", 66 | default_branch = "main", 67 | codebuild_project_name = "payments-codebuild", 68 | pipeline_name = "payments-pipeline", 69 | }, 70 | "onboarding_service" = { 71 | description = "Onboarding microservice repository", 72 | default_branch = "main", 73 | codebuild_project_name = "onboarding-codebuild", 74 | pipeline_name = "onboarding-pipeline", 75 | } 76 | } 77 | } 78 | 79 | variable "public_key_file_path" { 80 | description = "Path to public key file" 81 | default = "~/.ssh/id_rsa.pub" 82 | } 83 | 84 | -------------------------------------------------------------------------------- /terraform/modules/tenant-apps/README.md: -------------------------------------------------------------------------------- 1 | # Example Tenant Apps Terraform module 2 | 3 | ## Requirements 4 | 5 | | Name | Version | 6 | |------|---------| 7 | | [terraform](#requirement\_terraform) | >= 1.0 | 8 | | [aws](#requirement\_aws) | >= 5.0 | 9 | | [random](#requirement\_random) | >= 2.0 | 10 | 11 | ## Providers 12 | 13 | | Name | Version | 14 | |------|---------| 15 | | [aws](#provider\_aws) | >= 5.0 | 16 | | [random](#provider\_random) | >= 2.0 | 17 | 18 | ## Modules 19 | 20 | | Name | Source | Version | 21 | |------|--------|---------| 22 | | [consumer\_irsa\_role](#module\_consumer\_irsa\_role) | terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks | n/a | 23 | | [producer\_irsa\_role](#module\_producer\_irsa\_role) | terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks | n/a | 24 | 25 | ## Resources 26 | 27 | | Name | Type | 28 | |------|------| 29 | | [aws_dynamodb_table.consumer_ddb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource | 30 | | [aws_iam_policy.consumer-iampolicy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | 31 | | [aws_iam_policy.producer-iampolicy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | 32 | | [aws_iam_role_policy_attachment.sto-readonly-role-policy-attach](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | 33 | | [aws_sqs_queue.consumer_sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | 34 | | [aws_ssm_parameter.dedicated_consumer_ddb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | 35 | | [aws_ssm_parameter.dedicated_consumer_sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | 36 | | [aws_ssm_parameter.shared_consumer_ddb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | 37 | | [aws_ssm_parameter.shared_consumer_sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | 38 | | [random_string.random_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource | 39 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | 40 | | [aws_eks_cluster.eks-saas-gitops](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/eks_cluster) | data source | 41 | | [aws_ssm_parameter.pool_1_consumer_ddb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | 42 | | [aws_ssm_parameter.pool_1_consumer_sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | 43 | 44 | ## Inputs 45 | 46 | | Name | Description | Type | Default | Required | 47 | |------|-------------|------|---------|:--------:| 48 | | [enable\_consumer](#input\_enable\_consumer) | Defines if the Consumer app infraestructure will be deployed | `bool` | `true` | no | 49 | | [enable\_payments](#input\_enable\_payments) | Defines if the Payments app infraestructure will be deployed | `bool` | `true` | no | 50 | | [enable\_producer](#input\_enable\_producer) | Defines if the Producer app infraestructure will be deployed | `bool` | `true` | no | 51 | | [tenant\_id](#input\_tenant\_id) | Tentant identification | `string` | n/a | yes | 52 | 53 | ## Outputs 54 | 55 | | Name | Description | 56 | |------|-------------| 57 | | [consumer\_irsa\_role](#output\_consumer\_irsa\_role) | IAM Role for Service account for Consumer microservice | 58 | | [producer\_irsa\_role](#output\_producer\_irsa\_role) | IAM Role for Service account for Producer microservice | 59 | -------------------------------------------------------------------------------- /terraform/modules/tenant-apps/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | 3 | data "aws_eks_cluster" "eks-saas-gitops" { 4 | name = "eks-saas-gitops" 5 | } 6 | 7 | data "aws_ssm_parameter" "pool_1_consumer_sqs" { 8 | count = var.enable_consumer == false ? 1 : 0 9 | name = "/pool-1/consumer_sqs" 10 | } 11 | 12 | data "aws_ssm_parameter" "pool_1_consumer_ddb" { 13 | count = var.enable_consumer == false ? 1 : 0 14 | name = "/pool-1/consumer_ddb" 15 | } 16 | 17 | # data "aws_ssm_parameter" "pool_1_payments_bucket" { 18 | # count = var.enable_payments == false ? 1 : 0 19 | # name = "/pooled-1/payments_bucket" 20 | # } -------------------------------------------------------------------------------- /terraform/modules/tenant-apps/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | irsa_principal_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${replace(data.aws_eks_cluster.eks-saas-gitops.identity[0].oidc[0].issuer, "https://", "")}" 3 | } 4 | 5 | resource "random_string" "random_suffix" { 6 | length = 3 7 | special = false 8 | upper = false 9 | 10 | lifecycle { 11 | ignore_changes = [ 12 | length 13 | ] 14 | } 15 | } 16 | 17 | ################################################################################ 18 | # PRODUCER Infrastructure 19 | ################################################################################ 20 | # IAM POLICY FOR PRODUCER TO ACCESS CONSUMER SQS QUEUE 21 | resource "aws_iam_policy" "producer-iampolicy" { 22 | count = var.enable_consumer == true ? 1 : 0 23 | name = "producer-policy-${var.tenant_id}" 24 | policy = jsonencode({ 25 | Version = "2012-10-17" 26 | Statement = [ 27 | { 28 | Action = [ 29 | "sqs:SendMessage", 30 | "ssm:GetParameter", 31 | ] 32 | Effect = "Allow" 33 | Resource = [ 34 | aws_sqs_queue.consumer_sqs[0].arn, 35 | aws_ssm_parameter.dedicated_consumer_sqs[0].arn 36 | ] 37 | }, 38 | ] 39 | }) 40 | } 41 | 42 | # IF DEDICATED PRODUCER AND CONSUMER: 43 | module "producer_irsa_role" { 44 | count = var.enable_producer == true && var.enable_consumer == true ? 1 : 0 45 | source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" 46 | version = "5.30.0" 47 | 48 | role_name = "producer-role-${var.tenant_id}" 49 | 50 | role_policy_arns = { 51 | policy = aws_iam_policy.producer-iampolicy[0].arn 52 | } 53 | 54 | oidc_providers = { 55 | main = { 56 | provider_arn = local.irsa_principal_arn 57 | namespace_service_accounts = ["${var.tenant_id}:${var.tenant_id}-producer"] 58 | } 59 | } 60 | } 61 | 62 | # IF SHARED PRODUCER AND DEDICATED CONSUMER (HYBRID): 63 | resource "aws_iam_role_policy_attachment" "sto-readonly-role-policy-attach" { 64 | count = var.enable_producer == false && var.enable_consumer == true ? 1 : 0 65 | role = "producer-role-pool-1" 66 | policy_arn = aws_iam_policy.producer-iampolicy[0].arn 67 | } 68 | 69 | ################################################################################ 70 | # CONSUMER Infrastructure 71 | ################################################################################ 72 | resource "aws_iam_policy" "consumer-iampolicy" { 73 | count = var.enable_consumer == true ? 1 : 0 74 | name = "consumer-policy-${var.tenant_id}" 75 | policy = jsonencode({ 76 | Version = "2012-10-17" 77 | Statement = [ 78 | { 79 | Action = [ 80 | "sqs:ListQueues", 81 | "sqs:GetQueueUrl", 82 | "sqs:ListDeadLetterSourceQueues", 83 | "sqs:ListMessageMoveTasks", 84 | "sqs:ReceiveMessage", 85 | "sqs:GetQueueAttributes", 86 | "sqs:ListQueueTags", 87 | "sqs:DeleteMessage", 88 | "dynamodb:PutItem", 89 | "ssm:GetParameter" 90 | ] 91 | Effect = "Allow" 92 | Resource = [ 93 | aws_dynamodb_table.consumer_ddb[0].arn, 94 | aws_sqs_queue.consumer_sqs[0].arn, 95 | aws_ssm_parameter.dedicated_consumer_sqs[0].arn, 96 | aws_ssm_parameter.dedicated_consumer_ddb[0].arn 97 | ] 98 | }, 99 | ] 100 | }) 101 | } 102 | 103 | module "consumer_irsa_role" { 104 | count = var.enable_consumer == true ? 1 : 0 105 | source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" 106 | version = "5.30.0" 107 | 108 | role_name = "consumer-role-${var.tenant_id}" 109 | 110 | role_policy_arns = { 111 | policy = aws_iam_policy.consumer-iampolicy[0].arn 112 | } 113 | 114 | oidc_providers = { 115 | main = { 116 | provider_arn = local.irsa_principal_arn 117 | namespace_service_accounts = ["${var.tenant_id}:${var.tenant_id}-consumer"] 118 | } 119 | } 120 | } 121 | 122 | resource "aws_sqs_queue" "consumer_sqs" { 123 | count = var.enable_consumer == true ? 1 : 0 124 | name = "consumer-${var.tenant_id}-${random_string.random_suffix.result}" 125 | 126 | tags = { 127 | Name = var.tenant_id 128 | } 129 | } 130 | 131 | resource "aws_ssm_parameter" "dedicated_consumer_sqs" { 132 | count = var.enable_consumer == true ? 1 : 0 133 | name = "/${var.tenant_id}/consumer_sqs" 134 | type = "String" 135 | value = aws_sqs_queue.consumer_sqs[0].arn 136 | } 137 | 138 | resource "aws_ssm_parameter" "shared_consumer_sqs" { 139 | count = var.enable_consumer == false ? 1 : 0 140 | name = "/${var.tenant_id}/consumer_sqs" 141 | type = "String" 142 | value = data.aws_ssm_parameter.pool_1_consumer_sqs[0].value 143 | } 144 | 145 | resource "aws_dynamodb_table" "consumer_ddb" { 146 | count = var.enable_consumer == true ? 1 : 0 147 | name = "consumer-${var.tenant_id}-${random_string.random_suffix.result}" 148 | hash_key = "tenant_id" 149 | range_key = "message_id" 150 | billing_mode = "PAY_PER_REQUEST" 151 | 152 | attribute { 153 | name = "tenant_id" 154 | type = "S" 155 | } 156 | 157 | attribute { 158 | name = "message_id" 159 | type = "S" 160 | } 161 | 162 | tags = { 163 | Name = var.tenant_id 164 | } 165 | } 166 | 167 | resource "aws_ssm_parameter" "dedicated_consumer_ddb" { 168 | count = var.enable_consumer == true ? 1 : 0 169 | name = "/${var.tenant_id}/consumer_ddb" 170 | type = "String" 171 | value = aws_dynamodb_table.consumer_ddb[0].arn 172 | } 173 | 174 | resource "aws_ssm_parameter" "shared_consumer_ddb" { 175 | count = var.enable_consumer == false ? 1 : 0 176 | name = "/${var.tenant_id}/consumer_ddb" 177 | type = "String" 178 | value = data.aws_ssm_parameter.pool_1_consumer_ddb[0].value 179 | } 180 | 181 | ################################################################################ 182 | # PAYMENTS Infrastructure 183 | ################################################################################ 184 | # Uncomment all lines below this one 185 | 186 | # resource "aws_s3_bucket" "payments_bucket" { 187 | # count = var.enable_payments == true ? 1 : 0 188 | # bucket = "${var.tenant_id}-payments-${random_string.random_suffix.result}" 189 | 190 | # tags = { 191 | # Name = var.tenant_id 192 | # } 193 | # } 194 | 195 | # resource "aws_ssm_parameter" "dedicated_payments_bucket" { 196 | # count = var.enable_payments == true ? 1 : 0 197 | # name = "/${var.tenant_id}/payments_bucket" 198 | # type = "String" 199 | # value = aws_s3_bucket.payments_bucket[0].arn 200 | # } 201 | 202 | # resource "aws_ssm_parameter" "shared_payments_bucket" { 203 | # count = var.enable_payments == false ? 1 : 0 204 | # name = "/${var.tenant_id}/payments_bucket" 205 | # type = "String" 206 | # value = data.aws_ssm_parameter.pool_1_payments_bucket[0].value 207 | # } 208 | 209 | # resource "aws_iam_policy" "payments-iampolicy" { 210 | # count = var.enable_payments == true ? 1 : 0 211 | # name = "payments-policy-${var.tenant_id}" 212 | # policy = jsonencode({ 213 | # Version = "2012-10-17" 214 | # Statement = [ 215 | # { 216 | # Action = ["s3:ListBucket"] 217 | # Effect = "Allow" 218 | # Resource = ["*"] 219 | # }, 220 | # ] 221 | # }) 222 | # } 223 | 224 | # module "payments_irsa_role" { 225 | # count = var.enable_payments == true ? 1 : 0 226 | # source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" 227 | # version = "5.30.0" 228 | 229 | # role_name = "payments-role-${var.tenant_id}" 230 | 231 | # role_policy_arns = { 232 | # policy = aws_iam_policy.payments-iampolicy[0].arn 233 | # } 234 | 235 | # oidc_providers = { 236 | # main = { 237 | # provider_arn = local.irsa_principal_arn 238 | # namespace_service_accounts = ["${var.tenant_id}:${var.tenant_id}-payments"] 239 | # } 240 | # } 241 | # } -------------------------------------------------------------------------------- /terraform/modules/tenant-apps/outputs.tf: -------------------------------------------------------------------------------- 1 | output "producer" { 2 | description = "Outputs for Producer microservice" 3 | value = { 4 | "irsa_role" : try(module.producer_irsa_role[0].iam_role_arn, null) 5 | } 6 | } 7 | 8 | output "consumer" { 9 | description = "Outputs for Consumer microservice" 10 | value = { 11 | "irsa_role" : try(module.consumer_irsa_role[0].iam_role_arn, null) 12 | } 13 | } 14 | 15 | # output "payments" { 16 | # description = "Outputs for Payments microservice" 17 | # value = { 18 | # "irsa_role": try(module.payments_irsa_role[0].iam_role_arn, null) 19 | # } 20 | # } 21 | -------------------------------------------------------------------------------- /terraform/modules/tenant-apps/variables.tf: -------------------------------------------------------------------------------- 1 | variable "tenant_id" { 2 | description = "Tentant identification" 3 | type = string 4 | } 5 | 6 | variable "enable_producer" { 7 | description = "Defines if the Producer app infraestructure will be deployed" 8 | type = bool 9 | default = true 10 | } 11 | 12 | variable "enable_consumer" { 13 | description = "Defines if the Consumer app infraestructure will be deployed" 14 | type = bool 15 | default = true 16 | } 17 | 18 | variable "enable_payments" { 19 | description = "Defines if the Payments app infraestructure will be deployed" 20 | type = bool 21 | default = true 22 | } -------------------------------------------------------------------------------- /terraform/modules/tenant-apps/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.0" 8 | } 9 | random = { 10 | source = "hashicorp/random" 11 | version = ">= 2.0" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /terraform/quick_fix_flux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Set the namespace 4 | namespace="flux-system" 5 | 6 | # Fetch the list of failed Helm releases using kubectl, filtering for "False" status 7 | readarray -t failed_releases < <(kubectl get helmrelease -n $namespace | grep -i 'False' | awk '{print $1}') 8 | 9 | # Check if there are any failed releases to process 10 | if [ ${#failed_releases[@]} -eq 0 ]; then 11 | echo "No failed Helm releases found in the $namespace namespace." 12 | else 13 | # Loop through the failed Helm releases and delete them using flux 14 | echo "Deleting failed Helm releases in the $namespace namespace..." 15 | for release in "${failed_releases[@]}"; do 16 | echo "Deleting Helm release $release in namespace $namespace..." 17 | flux delete helmrelease $release -n $namespace --silent 18 | done 19 | 20 | # Optionally, reconcile the source after deleting the failed releases 21 | echo "Reconciling source git 'flux-system' in the $namespace namespace..." 22 | flux reconcile source git flux-system -n $namespace 23 | 24 | echo "Operation completed." 25 | fi 26 | -------------------------------------------------------------------------------- /terraform/workshop/main.tf: -------------------------------------------------------------------------------- 1 | # Versions 2 | terraform { 3 | required_version = ">= 1.0" 4 | 5 | required_providers { 6 | aws = { 7 | source = "hashicorp/aws" 8 | version = ">= 4.47" 9 | } 10 | kubernetes = { 11 | source = "hashicorp/kubernetes" 12 | version = ">= 2.20" 13 | } 14 | random = { 15 | source = "hashicorp/random" 16 | version = ">= 2.0" 17 | } 18 | } 19 | } 20 | 21 | # DataSources 22 | data "aws_eks_cluster_auth" "this" { 23 | name = module.eks.cluster_name 24 | } 25 | 26 | data "aws_availability_zones" "available" {} 27 | 28 | data "aws_region" "current" {} 29 | 30 | data "aws_caller_identity" "current" {} 31 | 32 | # Providers 33 | provider "aws" {} 34 | 35 | provider "kubernetes" { 36 | host = module.eks.cluster_endpoint 37 | cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) 38 | 39 | exec { 40 | api_version = "client.authentication.k8s.io/v1beta1" 41 | command = "aws" 42 | # This requires the awscli to be installed locally where Terraform is executed 43 | args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name, "--region", local.region] 44 | } 45 | } 46 | 47 | ################################################################################ 48 | # Supporting Resources 49 | ################################################################################ 50 | locals { 51 | name = var.name 52 | region = coalesce(var.aws_region, data.aws_region.current.name) 53 | 54 | vpc_cidr = var.vpc_cidr 55 | azs = slice(data.aws_availability_zones.available.names, 0, 3) 56 | 57 | tags = { 58 | Blueprint = var.name 59 | } 60 | } 61 | 62 | module "vpc" { 63 | source = "terraform-aws-modules/vpc/aws" 64 | version = "~> 4.0" 65 | 66 | name = local.name 67 | cidr = local.vpc_cidr 68 | 69 | azs = local.azs 70 | private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] 71 | public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] 72 | 73 | enable_nat_gateway = true 74 | single_nat_gateway = true 75 | 76 | public_subnet_tags = { 77 | "kubernetes.io/role/elb" = 1 78 | } 79 | 80 | private_subnet_tags = { 81 | "kubernetes.io/role/internal-elb" = 1 82 | # Tags subnets for Karpenter auto-discovery 83 | "karpenter.sh/discovery" = local.name 84 | } 85 | 86 | tags = local.tags 87 | } 88 | 89 | module "ebs_csi_irsa_role" { 90 | source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" 91 | version = "5.30.0" 92 | 93 | role_name = "ebs-csi-${local.name}" 94 | 95 | attach_ebs_csi_policy = true 96 | 97 | oidc_providers = { 98 | main = { 99 | provider_arn = module.eks.oidc_provider_arn 100 | namespace_service_accounts = ["kube-system:ebs-csi-controller-sa"] 101 | } 102 | } 103 | } 104 | 105 | module "image_automation_irsa_role" { 106 | source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" 107 | version = "5.30.0" 108 | 109 | role_name = "image-automation-${local.name}" 110 | 111 | role_policy_arns = { 112 | policy = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" 113 | } 114 | 115 | oidc_providers = { 116 | main = { 117 | provider_arn = module.eks.oidc_provider_arn 118 | namespace_service_accounts = ["flux-system:image-automation-controller", "flux-system:image-reflector-controller"] 119 | } 120 | } 121 | } 122 | 123 | module "eks" { 124 | source = "terraform-aws-modules/eks/aws" 125 | version = "~> 19.12" 126 | 127 | cluster_name = local.name 128 | cluster_version = var.cluster_version 129 | cluster_endpoint_public_access = true 130 | 131 | node_security_group_tags = { 132 | "kubernetes.io/cluster/${local.name}" = null 133 | } 134 | 135 | cluster_addons = { 136 | aws-ebs-csi-driver = { 137 | service_account_role_arn = module.ebs_csi_irsa_role.iam_role_arn 138 | most_recent = true 139 | } 140 | coredns = { 141 | most_recent = true 142 | } 143 | kube-proxy = { 144 | most_recent = true 145 | } 146 | vpc-cni = { 147 | most_recent = true 148 | } 149 | } 150 | 151 | vpc_id = module.vpc.vpc_id 152 | subnet_ids = module.vpc.private_subnets 153 | 154 | manage_aws_auth_configmap = true 155 | aws_auth_roles = [ 156 | { 157 | rolearn = module.gitops_saas_infra.karpenter_node_role_arn 158 | username = "system:node:{{EC2PrivateDNSName}}" 159 | groups = [ 160 | "system:bootstrappers", 161 | "system:nodes", 162 | ] 163 | } 164 | ] 165 | eks_managed_node_groups = { 166 | baseline-infra = { 167 | instance_types = ["m5.large"] 168 | min_size = 3 169 | max_size = 5 170 | desired_size = 3 171 | labels = { 172 | node-type = "applications" 173 | } 174 | } 175 | } 176 | 177 | tags = merge(local.tags, { 178 | "karpenter.sh/discovery" = local.name 179 | }) 180 | } -------------------------------------------------------------------------------- /terraform/workshop/outputs.tf: -------------------------------------------------------------------------------- 1 | output "aws_region" { 2 | value = data.aws_region.current.name 3 | } 4 | 5 | output "cluster_endpoint" { 6 | description = "Amazon EKS Cluster Endpoint address" 7 | value = module.eks.cluster_endpoint 8 | } 9 | 10 | output "cluster_name" { 11 | description = "The Amazon Resource Name (ARN) of the cluster, use" 12 | value = module.eks.cluster_name 13 | } 14 | 15 | output "ssh_public_key_id" { 16 | value = module.gitops_saas_infra.ssh_public_key_id 17 | } 18 | 19 | output "codecommit_repository_urls_producer" { 20 | value = try(module.gitops_saas_infra.codecommit_repository_urls.producer, null) 21 | } 22 | 23 | output "codecommit_repository_urls_consumer" { 24 | value = try(module.gitops_saas_infra.codecommit_repository_urls.consumer, null) 25 | } 26 | 27 | output "codecommit_repository_urls_payments" { 28 | value = try(module.gitops_saas_infra.codecommit_repository_urls.payments, null) 29 | } 30 | 31 | output "codecommit_repository_urls_onboarding" { 32 | value = try(module.gitops_saas_infra.codecommit_repository_urls.onboarding_service, null) 33 | } 34 | 35 | # output "ecr_repository_urls" { 36 | # value = module.gitops_saas_infra.ecr_repository_urls 37 | # } 38 | 39 | output "ecr_repository_urls_producer" { 40 | value = try(module.gitops_saas_infra.ecr_repository_urls.producer, null) 41 | } 42 | 43 | output "ecr_repository_urls_consumer" { 44 | value = try(module.gitops_saas_infra.ecr_repository_urls.consumer, null) 45 | } 46 | 47 | output "ecr_repository_urls_payments" { 48 | value = try(module.gitops_saas_infra.ecr_repository_urls.payments, null) 49 | } 50 | 51 | output "ecr_repository_urls_onboarding" { 52 | value = try(module.gitops_saas_infra.ecr_repository_urls.onboarding_service, null) 53 | } 54 | 55 | output "aws_codecommit_flux_clone_url_ssh" { 56 | description = "AWS CodeCommit SSH based clone URL" 57 | value = module.gitops_saas_infra.aws_codecommit_flux_clone_url_ssh 58 | } 59 | 60 | output "ecr_argoworkflow_container" { 61 | description = "URL for Amazon ECR stored Argo Workflows container" 62 | value = module.gitops_saas_infra.ecr_argoworkflow_container 63 | } 64 | 65 | output "argoworkflows_onboarding_queue_url" { 66 | description = "URL for the ArgoWokflows Onboarding SQS Queue" 67 | value = module.gitops_saas_infra.argoworkflows_onboarding_queue_url 68 | } 69 | 70 | output "argoworkflows_offboarding_queue_url" { 71 | description = "URL for the ArgoWokflows Onboarding SQS Queue" 72 | value = module.gitops_saas_infra.argoworkflows_offboarding_queue_url 73 | } 74 | 75 | output "argoworkflows_deployment_queue_url" { 76 | description = "URL for the ArgoWokflows Onboarding SQS Queue" 77 | value = module.gitops_saas_infra.argoworkflows_deployment_queue_url 78 | } 79 | 80 | output "ecr_helm_chart_url_base" { 81 | value = join("/", slice(split("/", module.gitops_saas_infra.ecr_helm_chart_url), 0, length(split("/", module.gitops_saas_infra.ecr_helm_chart_url)) - 1)) 82 | } 83 | 84 | output "ecr_helm_chart_application_url" { 85 | description = "URL for the ECR stored application helm chart, including the application chart segment." 86 | value = module.gitops_saas_infra.ecr_helm_chart_url_application 87 | } 88 | 89 | output "ecr_helm_chart_url" { 90 | description = "URL for Amazon ECR stored chart" 91 | value = module.gitops_saas_infra.ecr_helm_chart_url 92 | } 93 | 94 | output "argo_workflows_bucket_name" { 95 | description = "Amazon S3 bucket that Argo Workflows will store its artifacts" 96 | value = module.gitops_saas_infra.argo_workflows_bucket_name 97 | } 98 | 99 | output "argo_workflows_irsa" { 100 | description = "IAM Role for Argo Workflows Service Account" 101 | value = module.gitops_saas_infra.argo_workflows_irsa 102 | } 103 | 104 | output "tf_controller_irsa" { 105 | description = "IAM Role for TF Controller Service Account" 106 | value = module.gitops_saas_infra.tf_controller_irsa 107 | } 108 | 109 | output "lb_controller_irsa" { 110 | description = "IAM Role for LB Controller Service Account" 111 | value = module.gitops_saas_infra.lb_controller_irsa 112 | } 113 | 114 | output "karpenter_irsa" { 115 | description = "IAM Role for Karpenter Service Account" 116 | value = module.gitops_saas_infra.karpenter_irsa 117 | } 118 | 119 | output "argo_events_irsa" { 120 | description = "IAM Role for Argo Events Service Account" 121 | value = module.gitops_saas_infra.argo_events_irsa 122 | } 123 | 124 | output "account_id" { 125 | value = data.aws_caller_identity.current.account_id 126 | } 127 | 128 | output "configure_kubectl" { 129 | description = "Configure kubectl: make sure you're logged in with the correct AWS profile and run the following command to update your kubeconfig" 130 | value = "aws eks --region ${data.aws_region.current.name} update-kubeconfig --name ${module.eks.cluster_name}" 131 | } -------------------------------------------------------------------------------- /terraform/workshop/saas_gitops.tf: -------------------------------------------------------------------------------- 1 | module "gitops_saas_infra" { 2 | source = "../modules/gitops-saas-infra" 3 | name = var.name 4 | cluster_name = module.eks.cluster_name 5 | cluster_oidc_provider_arn = module.eks.oidc_provider_arn 6 | vpc_id = module.vpc.vpc_id 7 | private_subnets = module.vpc.private_subnets 8 | public_key_file_path = var.public_key_file_path # Upload to user created by this module, local executer should have the private key as well 9 | 10 | depends_on = [data.aws_availability_zones.available, data.aws_caller_identity.current, data.aws_region.current] 11 | } 12 | 13 | resource "null_resource" "execute_templating_script" { 14 | provisioner "local-exec" { 15 | command = "bash ${path.module}/templating.sh ${var.clone_directory} " 16 | } 17 | 18 | depends_on = [module.gitops_saas_infra] 19 | } 20 | 21 | 22 | ################################################################################ 23 | # Flux 24 | ################################################################################ 25 | module "flux_v2" { 26 | source = "../modules/flux_cd" 27 | cluster_endpoint = module.eks.cluster_endpoint 28 | ca = module.eks.cluster_certificate_authority_data 29 | token = data.aws_eks_cluster_auth.this.token 30 | git_branch = var.git_branch 31 | git_url = module.gitops_saas_infra.aws_codecommit_flux_clone_url_ssh 32 | kustomization_path = var.kustomization_path 33 | flux2_sync_secret_values = var.flux2_sync_secret_values 34 | image_automation_controller_sa_annotations = module.image_automation_irsa_role.iam_role_arn 35 | image_reflection_controller_sa_annotations = module.image_automation_irsa_role.iam_role_arn 36 | } 37 | -------------------------------------------------------------------------------- /terraform/workshop/templating.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export_simple_outputs() { 4 | terraform output -json > "$1" 5 | 6 | # Use a temporary file to hold the jq output 7 | local temp_file=$(mktemp) 8 | jq -r 'to_entries[] | select(.value.type == "string") | "\(.key)=\(.value.value)"' "$1" > "$temp_file" 9 | 10 | # Read from the temporary file to export variables 11 | while IFS= read -r line; do 12 | eval "export $line" 13 | done < "$temp_file" 14 | 15 | # Clean up the temporary file 16 | rm "$temp_file" 17 | } 18 | 19 | 20 | clone_git_repos() { 21 | local git_urls="$1" # List of Git repository URLs separated by spaces 22 | local clone_dir="$2" # Directory where to clone the repositories 23 | 24 | # Ensure the clone directory exists 25 | mkdir -p "$clone_dir" 26 | 27 | # Iterate through each Git URL and clone it 28 | IFS=' ' read -ra urls <<< "$git_urls" 29 | for url in "${urls[@]}"; do 30 | # Extract the repository name from the URL 31 | repo_name=$(basename -s .git "$url") 32 | target_path="$clone_dir/$repo_name" 33 | 34 | echo "Cloning repository: $url into $target_path" 35 | 36 | # Clone only if the target directory doesn't exist or is empty 37 | if [ ! -d "$target_path" ] || [ -z "$(ls -A "$target_path")" ]; then 38 | git clone "$url" "$target_path" 39 | else 40 | echo "Skipping clone of '$url'. Target path '$target_path' already exists and is not empty." 41 | fi 42 | done 43 | } 44 | 45 | # Debug function to test template processing 46 | debug_process_templates() { 47 | local dir="$1" # Directory containing the template files 48 | 49 | # Find all .template files and show proposed changes 50 | find "$dir" -type f -name '*.template' | while IFS= read -r template_file; do 51 | echo "Debugging template: $template_file" 52 | 53 | # Read each line in the template file 54 | while IFS= read -r line; do 55 | # Attempt to replace placeholders with environment variable values 56 | modified_line="$line" 57 | for var_name in $(compgen -v); do 58 | # Construct the placeholder pattern based on the variable name 59 | placeholder="{$var_name}" 60 | # Check if the current line contains the placeholder 61 | if [[ "$modified_line" == *"$placeholder"* ]]; then 62 | # Replace placeholder with the value of the environment variable 63 | modified_line="${modified_line//${placeholder}/${!var_name}}" 64 | fi 65 | done 66 | echo "$modified_line" 67 | done < "$template_file" 68 | done 69 | } 70 | 71 | process_and_replace_templates() { 72 | local dir="$1" # Directory containing the template files 73 | 74 | find "$dir" -type f -name '*.template' | while IFS= read -r template_file; do 75 | echo "Processing template: $template_file" 76 | 77 | local new_file_path="${template_file%.template}" 78 | local temp_file="${new_file_path}.tmp" 79 | 80 | # Ensure the temp file is empty 81 | > "$temp_file" 82 | 83 | # Ensure the last line is read even if it doesn't end with a newline 84 | while IFS= read -r line || [[ -n "$line" ]]; do 85 | modified_line="$line" 86 | for var_name in $(compgen -v); do 87 | placeholder="{${var_name}}" 88 | if [[ "$modified_line" == *"${placeholder}"* ]]; then 89 | local var_value="${!var_name}" 90 | modified_line="${modified_line//${placeholder}/${var_value}}" 91 | fi 92 | done 93 | echo "$modified_line" >> "$temp_file" 94 | done < "$template_file" 95 | 96 | # Move the temp file to the new file path 97 | mv "$temp_file" "$new_file_path" 98 | 99 | echo "Processed and saved to: $new_file_path" 100 | 101 | # Delete the original template file 102 | rm -f "$template_file" 103 | 104 | echo "Deleted original template file: $template_file" 105 | done 106 | } 107 | 108 | build_and_push_image() { 109 | local service_dir="$1" # Directory name of the service 110 | local ecr_repo_url="$2" # ECR Repository URL for the service 111 | local image_version="0.1" # Define your image version here 112 | 113 | # Navigate to the service directory 114 | cd "${clone_dir}/${service_dir}" || exit 115 | 116 | echo "Building and pushing Docker image for ${service_dir}..." 117 | 118 | # Log in to Amazon ECR, region is outputed on terraform output 119 | aws ecr get-login-password --region "$aws_region" | docker login --username AWS --password-stdin "$account_id".dkr.ecr."$aws_region".amazonaws.com 120 | 121 | if [ $(uname -m) = "arm64" ]; then 122 | # Build the Docker image with a specific tag and for a specific platform 123 | docker buildx build --platform linux/amd64 --build-arg aws_region=$aws_region -t "${ecr_repo_url}:${image_version}" . --load 124 | else 125 | # Build without buildx for non-ARM 126 | docker build --build-arg aws_region=$aws_region -t "${ecr_repo_url}:${image_version}" . 127 | fi 128 | 129 | # Push the Docker image to Amazon ECR 130 | docker push "${ecr_repo_url}:${image_version}" 131 | 132 | echo "Image for ${service_dir} pushed successfully." 133 | } 134 | 135 | package_and_push_helm_chart() { 136 | local chart_dir="$1" # Directory of the Helm Chart 137 | local chart_name="$2" # Name of the Helm Chart 138 | local ecr_chart_url="$3" # ECR URL to push the Helm Chart 139 | 140 | echo "Packaging and Pushing Helm Chart $chart_name to ECR" 141 | cd "$chart_dir" || exit 142 | aws ecr get-login-password --region "$aws_region" | helm registry login --username AWS --password-stdin $account_id.dkr.ecr.$aws_region.amazonaws.com 143 | helm package "$chart_name" 144 | helm_chart_version=$(grep 'version:' "$chart_name/Chart.yaml" | awk '{print $2}') 145 | helm push "${chart_name}-${helm_chart_version}.tgz" oci://$ecr_chart_url 146 | cd - > /dev/null 2>&1 || return 147 | } 148 | 149 | # Script starts here 150 | repo_root="../.." # This could be a parameter 151 | clone_dir="$1" # No need to put / eg. /tmp/ 152 | terraform_output="./output.json" 153 | 154 | export_simple_outputs "$terraform_output" 155 | 156 | # Clone Git repositories 157 | git_list="$codecommit_repository_urls_consumer $codecommit_repository_urls_payments $codecommit_repository_urls_producer $aws_codecommit_flux_clone_url_ssh" 158 | clone_git_repos "$git_list" $clone_dir 159 | 160 | # Copy folders to cloned repos as is 161 | cp -r $repo_root/tenant-microservices/consumer/* $clone_dir/consumer 162 | cp -r $repo_root/tenant-microservices/payments/* $clone_dir/payments 163 | cp -r $repo_root/tenant-microservices/producer/* $clone_dir/producer 164 | cp -r $repo_root/* $clone_dir/eks-saas-gitops 165 | cp $repo_root/.gitignore $clone_dir/eks-saas-gitops/.gitignore 166 | 167 | # Process templates in cloned repos 168 | repo_dir="$clone_dir/eks-saas-gitops" 169 | process_and_replace_templates "$repo_dir" 170 | 171 | original_dir="$PWD" 172 | 173 | # Package and push Helm Charts to ECR 174 | package_and_push_helm_chart "$repo_dir/helm-charts" "helm-tenant-chart" "$ecr_helm_chart_url_base" 175 | package_and_push_helm_chart "$repo_dir/helm-charts" "application-chart" "$ecr_helm_chart_url_base" 176 | 177 | # Docker images for consumer, producer and payments 178 | build_and_push_image "consumer" "$ecr_repository_urls_consumer" 179 | build_and_push_image "producer" "$ecr_repository_urls_producer" 180 | build_and_push_image "payments" "$ecr_repository_urls_payments" 181 | build_and_push_image "eks-saas-gitops/workflow-scripts" "$ecr_argoworkflow_container" 182 | cd "$original_dir" || exit 183 | 184 | # Commit and push changes to Git 185 | echo "Committing and pushing changes to Git" 186 | cd $clone_dir/consumer || exit 187 | git checkout -b main 188 | git add . 189 | git commit -m "Initial Commit" 190 | git push origin main 191 | 192 | cd $clone_dir/producer || exit 193 | git checkout -b main 194 | git add . 195 | git commit -m "Initial Commit" 196 | git push origin main 197 | 198 | cd $clone_dir/payments || exit 199 | git checkout -b main 200 | git add . 201 | git commit -m "Initial Commit" 202 | git push origin main 203 | 204 | # remove unnecessary folders from cloud9 before pushing to CodeCommit 205 | rm -rf $clone_dir/eks-saas-gitops/helpers 206 | rm -rf $clone_dir/eks-saas-gitops/tenant-microservices 207 | 208 | cd $clone_dir/eks-saas-gitops || exit 209 | git checkout -b main 210 | git add . 211 | git commit -m "Initial Commit" 212 | git push origin main 213 | # Tagging last commit ID 214 | LAST_COMMIT_ID=$(aws codecommit get-branch --repository-name "$cluster_name" --branch-name main | jq -r '.branch.commitId') 215 | git tag v0.0.1 $LAST_COMMIT_ID 216 | git push origin v0.0.1 217 | cd "$original_dir" || exit -------------------------------------------------------------------------------- /terraform/workshop/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Stack name" 3 | type = string 4 | default = "eks-saas-gitops" 5 | } 6 | 7 | variable "aws_region" { 8 | description = "AWS region" 9 | type = string 10 | default = "" 11 | } 12 | 13 | variable "vpc_cidr" { 14 | description = "Amazon VPC CIDR Block" 15 | type = string 16 | default = "10.35.0.0/16" 17 | } 18 | 19 | variable "cluster_version" { 20 | description = "Amazon EKS Cluster version" 21 | type = string 22 | default = "1.30" 23 | } 24 | 25 | variable "public_key_file_path" { 26 | description = "Public key file path, used for clone CodeCommit repo, you should have private key locally" 27 | type = string 28 | default = "" 29 | } 30 | 31 | variable "clone_directory" { 32 | description = "Directory to clone CodeCommit repos" 33 | type = string 34 | default = "/tmp" 35 | } 36 | 37 | variable "flux2_sync_secret_values" { 38 | description = "This is created by install.sh script during execution" 39 | default = "./flux-secrets.yaml" 40 | } 41 | 42 | variable "git_branch" { 43 | description = "Branch of the Git repository" 44 | type = string 45 | default = "main" 46 | } 47 | 48 | variable "kustomization_path" { 49 | description = "Path for Kustomization tool" 50 | type = string 51 | default = "gitops/clusters/production" 52 | } -------------------------------------------------------------------------------- /workflow-scripts/00-validate-tenant.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TENANT_ID="$1" 4 | TENANT_TIER="$2" 5 | TENANTS_MANIFEST_PATH="/mnt/vol/eks-saas-gitops/gitops/application-plane/production/tenants/${TENANT_TIER}" 6 | TENANT_FILE_FOUND=false 7 | 8 | # Function to search for the tenant file in a given directory 9 | search_for_tenant() { 10 | local search_path="$1" 11 | for i in "${search_path}"*; do 12 | if [[ $i == *"${TENANT_ID}"* ]]; then 13 | TENANT_FILE_FOUND=true 14 | return 15 | fi 16 | done 17 | } 18 | 19 | # Search in the defined directories 20 | search_for_tenant "$TENANTS_MANIFEST_PATH" 21 | 22 | if $TENANT_FILE_FOUND; then 23 | echo "Tenant '${TENANT_ID}' already exists." 24 | exit 1 25 | else 26 | echo "Tenant '${TENANT_ID}' does not exist, proceed" 27 | fi -------------------------------------------------------------------------------- /workflow-scripts/01-tenant-clone-repo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | echo "Please provide the repo_url as the first argument." 5 | exit 1 6 | fi 7 | 8 | REPOSITORY_URL="$1" 9 | REPOSITORY_BRANCH="$2" 10 | 11 | git clone -b $REPOSITORY_BRANCH "$REPOSITORY_URL" -------------------------------------------------------------------------------- /workflow-scripts/02-tenant-onboarding.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # map templates and helm release folders -- this is mounted on 01-tenant-clone-repo.sh 4 | repo_root_path="/mnt/vol/eks-saas-gitops" 5 | tier_templates_path="${repo_root_path}/gitops/application-plane/production/tier-templates" 6 | manifests_path="${repo_root_path}/gitops/application-plane/production/tenants" 7 | 8 | main() { 9 | local tenant_id="$1" 10 | local release_version="$2" 11 | local tenant_tier="$3" 12 | local git_user_email="$4" 13 | local git_user_name="$5" 14 | local repository_branch="$6" 15 | 16 | # get tier template file based on the tier for the tenant being provisioned 17 | # (e.g. /mnt/vol/eks-saas-gitops/gitops/application-plane/production/tier-templates/premium_tenant_template.yaml) 18 | local tier_template_file 19 | tier_template_file=$(get_tier_template_file "$tenant_tier") 20 | 21 | # create the tenant helm release file based on the tier template file and tenant id 22 | # (e.g. /mnt/vol/eks-saas-gitops/gitops/application-plane/production/tenants/premium/tenant-1.yaml) 23 | create_helm_release "$tenant_id" "$tenant_tier" "$release_version" "$tier_template_file" 24 | 25 | # configure git user and ssh key so we can push changes to the gitops repo 26 | configure_git "${git_user_email}" "${git_user_name}" 27 | 28 | # push new helm release for the tenant and kustomization update to the gitops repo 29 | commit_files "${repository_branch}" "${tenant_id}" "${tenant_tier}" 30 | } 31 | 32 | create_helm_release() { 33 | local tenant_id="$1" 34 | local tenant_tier="$2" 35 | local release_version="$3" 36 | local tier_template_file="$4" 37 | 38 | # tenant helm release file name based on tenant_tier and tenant_id e.g. (premium/tenant-1.yaml) 39 | local tenant_manifest_file="${tenant_tier}/${tenant_id}.yaml" 40 | 41 | # make a copy of the tier template file onto the tenant helm release file 42 | cp "${tier_template_file}" "${manifests_path}/${tenant_manifest_file}" 43 | 44 | # replace the tenant id and release version in the tenant helm release file 45 | sed -i "s|{TENANT_ID}|${tenant_id}|g" "${manifests_path}/${tenant_manifest_file}" 46 | sed -i "s|{RELEASE_VERSION}|${release_version}|g" "${manifests_path}/${tenant_manifest_file}" 47 | 48 | # update the kustomization file with the new tenant helm release file 49 | printf "\n - %s.yaml" "${tenant_id}" >> "${manifests_path}/${tenant_tier}/kustomization.yaml" 50 | } 51 | 52 | get_tier_template_file() { 53 | local tenant_tier="$1" 54 | case "$tenant_tier" in 55 | "basic") echo "${tier_templates_path}/basic_tenant_template.yaml" ;; 56 | "premium") echo "${tier_templates_path}/premium_tenant_template.yaml" ;; 57 | "advanced") echo "${tier_templates_path}/advanced_tenant_template.yaml" ;; 58 | *) echo "Invalid tenant tier $tenant_tier"; exit 1 ;; 59 | esac 60 | } 61 | 62 | configure_git() { 63 | local git_user_email="$1" 64 | local git_user_name="$2" 65 | git config --global user.email "${git_user_email}" 66 | git config --global user.name "${git_user_name}" 67 | cat < /root/.ssh/config 68 | Host git-codecommit.*.amazonaws.com 69 | User ${git_user_name} 70 | IdentityFile /root/.ssh/id_rsa 71 | EOF 72 | chmod 600 /root/.ssh/config 73 | } 74 | 75 | commit_files() { 76 | local repository_branch="$1" 77 | local tenant_id="$2" 78 | local tenant_tier="$3" 79 | cd ${repo_root_path} || exit 1 80 | git status 81 | git pull 82 | git add . 83 | git commit -am "Adding new tenant ${tenant_id} in tier ${tenant_tier}" 84 | git push origin "${repository_branch}" 85 | } 86 | 87 | main "$@" -------------------------------------------------------------------------------- /workflow-scripts/03-tenant-deployment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # map templates and helm release folders -- this is mounted on 01-tenant-clone-repo.sh 4 | tier_templates_path="/mnt/vol/eks-saas-gitops/gitops/application-plane/production/tier-templates" 5 | manifests_path="/mnt/vol/eks-saas-gitops/gitops/application-plane/production/tenants" 6 | pooled_envs_path="/mnt/vol/eks-saas-gitops/gitops/application-plane/production/pooled-envs" 7 | pool_env_template_file="${tier_templates_path}/basic_env_template.yaml" 8 | 9 | main() { 10 | local release_version="$1" 11 | local tenant_tier="$2" 12 | local git_user_email="$3" 13 | local git_user_name="$4" 14 | local repository_branch="$5" 15 | 16 | # get tier template file based on the tier for the tenant being deployed 17 | # (e.g. /mnt/vol/eks-saas-gitops/gitops/application-plane/production/tier-templates/premium_tenant_template.yaml) 18 | local tier_template_file 19 | tier_template_file=$(get_tier_template_file "$tenant_tier") 20 | 21 | # update tenant helm releases for a given tier 22 | update_tenants "$release_version" "$tier_template_file" "$tenant_tier" 23 | 24 | # if tier is basic, update helm release for basic pool environment 25 | if [[ $tenant_tier == "basic" ]]; then 26 | update_pool_envs "$release_version" 27 | fi 28 | 29 | # configure git user and ssh key so we can push changes to the gitops repo 30 | configure_git "${git_user_email}" "${git_user_name}" 31 | 32 | # push updated helm releases 33 | commit_files "${repository_branch}" "${tenant_tier}" 34 | } 35 | 36 | get_tier_template_file() { 37 | local tenant_tier="$1" 38 | case "$tenant_tier" in 39 | "basic") echo "${tier_templates_path}/basic_tenant_template.yaml" ;; 40 | "premium") echo "${tier_templates_path}/premium_tenant_template.yaml" ;; 41 | "advanced") echo "${tier_templates_path}/advanced_tenant_template.yaml" ;; 42 | *) echo "Invalid tenant tier $tenant_tier"; exit 1 ;; 43 | esac 44 | } 45 | 46 | update_deployment_files() { 47 | local release_version="$1" 48 | local template_file="$2" 49 | local manifests_folder="$3" 50 | 51 | # loop through all tenant helm releases, recreate each file with template, and substitute release version 52 | for release_file in "${manifests_folder}"/*; do 53 | if [[ "$release_file" != *kustomization.yaml && "$release_file" != *dummy-configmap.yaml ]]; then #kustomization file doesn't need to change 54 | local id 55 | id=$(basename "$release_file" | cut -d '.' -f1) 56 | cp "$template_file" "${release_file}" 57 | sed -i "s|{TENANT_ID}|$id|g" "$release_file" 58 | sed -i "s|{RELEASE_VERSION}|${release_version}|g" "${release_file}" 59 | fi 60 | done 61 | } 62 | 63 | update_pool_envs() { 64 | local release_version="$1" 65 | local pool_env="pool-1" # This should be dynamic to allow creation of multiple pool environments, for testing and shard tenants 66 | update_deployment_files "$release_version" "$pool_env_template_file" "$pooled_envs_path" 67 | sed -i "s|{ENVIRONMENT_ID}|$pool_env|g" "${pooled_envs_path}/${pool_env}.yaml" 68 | } 69 | 70 | update_tenants() { 71 | local release_version="$1" 72 | local template_file="$2" 73 | local tenant_tier="$3" 74 | update_deployment_files "$release_version" "$template_file" "${manifests_path}/${tenant_tier}" 75 | } 76 | 77 | configure_git() { 78 | local git_user_email="$1" 79 | local git_user_name="$2" 80 | git config --global user.email "${git_user_email}" 81 | git config --global user.name "${git_user_name}" 82 | cat < /root/.ssh/config 83 | Host git-codecommit.*.amazonaws.com 84 | User ${git_user_name} 85 | IdentityFile /root/.ssh/id_rsa 86 | EOF 87 | chmod 600 /root/.ssh/config 88 | } 89 | 90 | commit_files() { 91 | local repository_branch="$1" 92 | local tenant_tier="$2" 93 | cd /mnt/vol/eks-saas-gitops/ || exit 1 94 | git status 95 | git add . 96 | git commit -am "Deploying to $tenant_tier tenants, version $release_version" 97 | git push origin "${repository_branch}" 98 | } 99 | 100 | main "$@" 101 | -------------------------------------------------------------------------------- /workflow-scripts/04-tenant-offboarding.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # map templates and helm release folders -- this is mounted on 01-tenant-clone-repo.sh 4 | manifests_path="/mnt/vol/eks-saas-gitops/gitops/application-plane/production/tenants" 5 | 6 | main() { 7 | local tenant_id="$1" 8 | local tenant_tier="$2" 9 | local git_user_email="$3" 10 | local git_user_name="$4" 11 | local repository_branch="$5" 12 | 13 | # remove tenant helm release file and update kustomization 14 | remove_tenant_helm_release "${tenant_id}" "${tenant_tier}" 15 | 16 | # configure git user and ssh key so we can push changes to the gitops repo 17 | configure_git "${git_user_email}" "${git_user_name}" 18 | 19 | # push updated helm releases 20 | commit_files "${repository_branch}" "${tenant_tier}" 21 | } 22 | 23 | remove_tenant_helm_release() { 24 | local tenant_id="$1" 25 | local tenant_tier="$2" 26 | 27 | # tenant helm release file name based on tenant_tier and tenant_id e.g. (premium/tenant-1.yaml) 28 | local tenant_manifest_file="${tenant_tier}/${tenant_id}.yaml" 29 | 30 | # full path for tenant helm release file 31 | local tenant_manifest_path="${manifests_path}/${tenant_manifest_file}" 32 | 33 | # remove tenant helm release file 34 | rm "${tenant_manifest_path}" 35 | 36 | # update kustomization file by removing the tenant helm release file 37 | sed -i "/- ${tenant_id}\.yaml/d" "${manifests_path}/${tenant_tier}/kustomization.yaml" 38 | } 39 | 40 | configure_git() { 41 | local git_user_email="$1" 42 | local git_user_name="$2" 43 | git config --global user.email "${git_user_email}" 44 | git config --global user.name "${git_user_name}" 45 | cat < /root/.ssh/config 46 | Host git-codecommit.*.amazonaws.com 47 | User ${git_user_name} 48 | IdentityFile /root/.ssh/id_rsa 49 | EOF 50 | chmod 600 /root/.ssh/config 51 | } 52 | 53 | commit_files() { 54 | local repository_branch="$1" 55 | local tenant_tier="$2" 56 | cd /mnt/vol/eks-saas-gitops/ || exit 1 57 | git status 58 | git add . 59 | git commit -am "Removing tenant ${tenant_id} in tier ${tenant_tier}" 60 | git push origin "${repository_branch}" 61 | } 62 | 63 | main "$@" 64 | -------------------------------------------------------------------------------- /workflow-scripts/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use a lightweight base image 2 | FROM alpine:latest 3 | 4 | ARG aws_region 5 | ENV AWS_REGION=$aws_region 6 | 7 | # Install necessary dependencies 8 | RUN apk update && \ 9 | apk add --no-cache \ 10 | ca-certificates \ 11 | git \ 12 | openssh \ 13 | openssl \ 14 | wget \ 15 | unzip \ 16 | jq \ 17 | yq \ 18 | bash \ 19 | curl # Added curl to fetch kubectl 20 | 21 | # Install Terraform 22 | ENV TERRAFORM_VERSION=1.5.1 23 | RUN wget --quiet https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \ 24 | unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /usr/bin && \ 25 | rm terraform_${TERRAFORM_VERSION}_linux_amd64.zip 26 | 27 | # Install kubectl 28 | ENV KUBECTL_VERSION=v1.27.0 29 | RUN curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \ 30 | chmod +x ./kubectl && \ 31 | mv ./kubectl /usr/local/bin/kubectl 32 | 33 | COPY . / 34 | # Display installed versions 35 | RUN terraform version && git version && kubectl version --client && mkdir -p /root/.ssh/ && ssh-keyscan "git-codecommit.${AWS_REGION}.amazonaws.com" >> /root/.ssh/known_hosts 36 | -------------------------------------------------------------------------------- /workflow-scripts/README.md: -------------------------------------------------------------------------------- 1 | # Workflow Scripts 2 | 3 | This folder is used to build the image that will be used in Argo Workflows. --------------------------------------------------------------------------------