├── .gitignore ├── apps ├── src │ ├── kart │ │ ├── app │ │ │ ├── auth │ │ │ │ ├── __init__.py │ │ │ │ └── auth.py │ │ │ ├── kart │ │ │ │ ├── __init__.py │ │ │ │ └── kart.py │ │ │ ├── __init__.py │ │ │ └── schema │ │ │ │ └── models.py │ │ ├── app.py │ │ ├── Makefile │ │ ├── requirements.txt │ │ └── Dockerfile │ ├── order │ │ ├── app │ │ │ ├── auth │ │ │ │ ├── __init__.py │ │ │ │ └── auth.py │ │ │ ├── order │ │ │ │ ├── __init__.py │ │ │ │ └── order.py │ │ │ ├── __init__.py │ │ │ └── schema │ │ │ │ └── models.py │ │ ├── app.py │ │ ├── Makefile │ │ ├── requirements.txt │ │ └── Dockerfile │ ├── product │ │ ├── app │ │ │ ├── auth │ │ │ │ ├── __init__.py │ │ │ │ └── auth.py │ │ │ ├── products │ │ │ │ ├── __init__.py │ │ │ │ └── products.py │ │ │ ├── __init__.py │ │ │ └── schema │ │ │ │ └── models.py │ │ ├── app.py │ │ ├── Makefile │ │ ├── requirements.txt │ │ └── Dockerfile │ ├── user │ │ ├── app │ │ │ ├── auth │ │ │ │ ├── __init__.py │ │ │ │ └── auth.py │ │ │ ├── user │ │ │ │ ├── __init__.py │ │ │ │ └── user.py │ │ │ ├── __init__.py │ │ │ └── schema │ │ │ │ ├── models.py.MemoryDB │ │ │ │ ├── models.py │ │ │ │ └── models.py.PostgreSQL │ │ ├── app.py │ │ ├── Makefile │ │ ├── requirements.txt │ │ └── Dockerfile │ ├── webapp │ │ ├── app │ │ │ ├── ajax │ │ │ │ ├── __init__.py │ │ │ │ └── ajax.py │ │ │ ├── auth │ │ │ │ ├── __init__.py │ │ │ │ ├── forms.py │ │ │ │ ├── static │ │ │ │ │ └── css │ │ │ │ │ │ ├── style.css │ │ │ │ │ │ └── navbar.css │ │ │ │ ├── templates │ │ │ │ │ └── auth │ │ │ │ │ │ ├── account.html │ │ │ │ │ │ ├── forgot_password.html │ │ │ │ │ │ ├── login.html │ │ │ │ │ │ └── signup.html │ │ │ │ └── auth.py │ │ │ ├── cart │ │ │ │ ├── __init__.py │ │ │ │ ├── templates │ │ │ │ │ └── cart │ │ │ │ │ │ ├── checkout.html │ │ │ │ │ │ ├── view.html │ │ │ │ │ │ ├── cart.html.save │ │ │ │ │ │ └── cart.html │ │ │ │ └── cart.py │ │ │ ├── static │ │ │ │ ├── js │ │ │ │ │ ├── cart.js │ │ │ │ │ ├── script.js │ │ │ │ │ ├── pagination.js │ │ │ │ │ └── ajax.js │ │ │ │ ├── css │ │ │ │ │ ├── navbar.css │ │ │ │ │ ├── head.css │ │ │ │ │ ├── style.css │ │ │ │ │ └── responsive.css │ │ │ │ └── images │ │ │ │ │ ├── logo.png │ │ │ │ │ ├── logos │ │ │ │ │ ├── logo1.png │ │ │ │ │ ├── logo2.png │ │ │ │ │ ├── logo3.png │ │ │ │ │ ├── logo4.png │ │ │ │ │ └── logo5.png │ │ │ │ │ ├── misc │ │ │ │ │ └── appstore.png │ │ │ │ │ ├── shoppingCart.png │ │ │ │ │ └── banners │ │ │ │ │ ├── banner.png │ │ │ │ │ └── banner-sale.jpg │ │ │ ├── visits │ │ │ │ ├── __init__.py │ │ │ │ └── visits.py │ │ │ ├── general │ │ │ │ ├── __init__.py │ │ │ │ ├── templates │ │ │ │ │ └── general │ │ │ │ │ │ ├── analytics.html │ │ │ │ │ │ ├── search_results.html │ │ │ │ │ │ ├── base.html │ │ │ │ │ │ └── index.html │ │ │ │ └── general.py │ │ │ ├── products │ │ │ │ ├── __init__.py │ │ │ │ ├── products.py │ │ │ │ └── templates │ │ │ │ │ └── products │ │ │ │ │ ├── view.html │ │ │ │ │ └── list.html │ │ │ ├── SessionStore.py │ │ │ ├── __init__.py │ │ │ └── models.py │ │ ├── app.py │ │ ├── logstash │ │ │ ├── 20-filter.conf │ │ │ ├── 10-input.conf │ │ │ └── 30-output.conf │ │ ├── Makefile │ │ ├── requirements.txt │ │ └── Dockerfile │ ├── cert │ │ └── create_certs.sh │ ├── README.md │ ├── Makefile │ ├── docker-compose.yml │ └── eks │ │ └── eksackapp.yaml └── production │ ├── namespace.yaml │ ├── kustomization.yaml │ ├── retailapp │ ├── kustomization.yaml │ ├── secrets.yaml │ ├── ingress.yaml │ ├── services.yaml │ └── deployments.yaml │ ├── rds-parameter-group.yaml │ ├── rdspg.yaml │ ├── memorydb-cluster.yaml │ └── aurora-pg.yaml ├── .DS_Store ├── infrastructure └── production │ ├── ack │ ├── namespace.yaml │ ├── rds-serviceaccount.yaml │ ├── memorydb-serviceaccount.yaml │ ├── kustomization.yaml │ ├── rds-release.yaml │ └── memorydb-release.yaml │ ├── sources │ ├── kustomization.yaml │ └── ack.yaml │ └── kustomization.yaml ├── CODE_OF_CONDUCT.md ├── clusters └── production │ ├── apps.yaml │ └── infrastructure.yaml ├── LICENSE ├── scripts ├── retailapp_tests.txt └── prereq.sh ├── CONTRIBUTING.md ├── README.md └── cfn └── ack-rds-cfn-base.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /apps/src/kart/app/auth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/kart/app/kart/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/order/app/auth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/order/app/order/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/product/app/auth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/user/app/auth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/user/app/user/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/webapp/app/ajax/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/webapp/app/auth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/webapp/app/cart/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/webapp/app/static/js/cart.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/webapp/app/visits/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/product/app/products/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/webapp/app/general/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/webapp/app/products/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/webapp/app/static/css/navbar.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/webapp/app/cart/templates/cart/checkout.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/src/webapp/app/cart/templates/cart/view.html: -------------------------------------------------------------------------------- 1 |

This is cart page

-------------------------------------------------------------------------------- /apps/src/webapp/app/static/css/head.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family:"Lato", sans-serif; 3 | } -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ack-rds-gitops-workshop/main/.DS_Store -------------------------------------------------------------------------------- /apps/src/webapp/app/general/templates/general/analytics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

This is Anlytics page

-------------------------------------------------------------------------------- /apps/production/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: retailapp 5 | -------------------------------------------------------------------------------- /infrastructure/production/ack/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: ack-system 5 | -------------------------------------------------------------------------------- /apps/src/webapp/app/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ack-rds-gitops-workshop/main/apps/src/webapp/app/static/images/logo.png -------------------------------------------------------------------------------- /apps/src/kart/app.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | from datetime import datetime; 3 | 4 | if __name__=="__main__": 5 | app.run(debug=True, host='0.0.0.0', port=8445) 6 | -------------------------------------------------------------------------------- /apps/src/user/app.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | from datetime import datetime; 3 | 4 | if __name__=="__main__": 5 | app.run(debug=True, host='0.0.0.0', port=8446) 6 | -------------------------------------------------------------------------------- /apps/src/order/app.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | from datetime import datetime; 3 | 4 | if __name__=="__main__": 5 | app.run(debug=True, host='0.0.0.0', port=8448) 6 | -------------------------------------------------------------------------------- /apps/src/product/app.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | from datetime import datetime; 3 | 4 | if __name__=="__main__": 5 | app.run(debug=True, host='0.0.0.0', port=8444) 6 | -------------------------------------------------------------------------------- /apps/src/webapp/app.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | from datetime import datetime; 3 | 4 | if __name__=="__main__": 5 | app.run(debug=True, host='0.0.0.0', port=8443) 6 | -------------------------------------------------------------------------------- /apps/src/webapp/app/static/images/logos/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ack-rds-gitops-workshop/main/apps/src/webapp/app/static/images/logos/logo1.png -------------------------------------------------------------------------------- /apps/src/webapp/app/static/images/logos/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ack-rds-gitops-workshop/main/apps/src/webapp/app/static/images/logos/logo2.png -------------------------------------------------------------------------------- /apps/src/webapp/app/static/images/logos/logo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ack-rds-gitops-workshop/main/apps/src/webapp/app/static/images/logos/logo3.png -------------------------------------------------------------------------------- /apps/src/webapp/app/static/images/logos/logo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ack-rds-gitops-workshop/main/apps/src/webapp/app/static/images/logos/logo4.png -------------------------------------------------------------------------------- /apps/src/webapp/app/static/images/logos/logo5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ack-rds-gitops-workshop/main/apps/src/webapp/app/static/images/logos/logo5.png -------------------------------------------------------------------------------- /apps/src/webapp/app/static/images/misc/appstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ack-rds-gitops-workshop/main/apps/src/webapp/app/static/images/misc/appstore.png -------------------------------------------------------------------------------- /apps/src/webapp/app/static/images/shoppingCart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ack-rds-gitops-workshop/main/apps/src/webapp/app/static/images/shoppingCart.png -------------------------------------------------------------------------------- /apps/src/webapp/app/static/images/banners/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ack-rds-gitops-workshop/main/apps/src/webapp/app/static/images/banners/banner.png -------------------------------------------------------------------------------- /apps/src/webapp/logstash/20-filter.conf: -------------------------------------------------------------------------------- 1 | filter { 2 | if [x_forwarded_for] != "-" { 3 | geoip { 4 | source => "x_forwarded_for" 5 | target => "geoip" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/src/webapp/app/static/images/banners/banner-sale.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ack-rds-gitops-workshop/main/apps/src/webapp/app/static/images/banners/banner-sale.jpg -------------------------------------------------------------------------------- /apps/src/webapp/logstash/10-input.conf: -------------------------------------------------------------------------------- 1 | input { 2 | file { 3 | path => "/octank/logs/octank.formatted.log" 4 | start_position => "beginning" 5 | codec => json 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/src/webapp/app/static/js/script.js: -------------------------------------------------------------------------------- 1 | 2 | $(document).ready(function() { 3 | $(document).on('click', '.dropdown-menu', function (e) { 4 | e.stopPropagation(); 5 | }); 6 | 7 | }) 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /infrastructure/production/sources/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: flux-system 4 | resources: 5 | # - ack.yaml # add controller as an infrastructure piece 6 | -------------------------------------------------------------------------------- /apps/production/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | # - ./namespace.yaml 5 | # - ./aurora-pg.yaml 6 | # - ./memorydb-cluster.yaml 7 | # - ./retailapp 8 | -------------------------------------------------------------------------------- /apps/production/retailapp/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: retailapp 4 | resources: 5 | - secrets.yaml 6 | - deployments.yaml 7 | - services.yaml 8 | - ingress.yaml 9 | -------------------------------------------------------------------------------- /apps/src/webapp/logstash/30-output.conf: -------------------------------------------------------------------------------- 1 | output { 2 | amazon_es { 3 | hosts => ["esendpoint.us-east-1.es.amazonaws.com"] 4 | index => "logstash-%{+YYYY.MM.dd}" 5 | region => "us-east-1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /infrastructure/production/ack/rds-serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | annotations: 5 | eks.amazonaws.com/role-arn: arn:aws:iam:::role/ack-rds-controller 6 | name: ack-rds-controller 7 | namespace: ack-system 8 | -------------------------------------------------------------------------------- /infrastructure/production/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | # - sources #uncomment this line to add HelmRepository 5 | # - ack #uncomment this line to add ACK HelmRelease for RDS and MemoryDB controller 6 | -------------------------------------------------------------------------------- /infrastructure/production/ack/memorydb-serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | annotations: 5 | eks.amazonaws.com/role-arn: arn:aws:iam:::role/ack-memorydb-controller 6 | name: ack-memorydb-controller 7 | namespace: ack-system 8 | -------------------------------------------------------------------------------- /infrastructure/production/sources/ack.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: HelmRepository 3 | metadata: 4 | name: ack-source 5 | namespace: flux-system 6 | spec: 7 | type: oci 8 | interval: 1m 9 | url: oci://public.ecr.aws/aws-controllers-k8s 10 | -------------------------------------------------------------------------------- /infrastructure/production/ack/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: ack-system 4 | resources: 5 | - namespace.yaml 6 | - rds-serviceaccount.yaml 7 | - memorydb-serviceaccount.yaml 8 | - rds-release.yaml 9 | - memorydb-release.yaml 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /apps/src/cert/create_certs.sh: -------------------------------------------------------------------------------- 1 | openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem 2 | openssl rsa -in privkey.pem -passin pass:abcd -out server.key 3 | openssl req -x509 -in server.req -text -key server.key -out server.crt 4 | chmod 600 server.key 5 | test $(uname -s) = Linux && chown 70 server.key 6 | -------------------------------------------------------------------------------- /clusters/production/apps.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 2 | kind: Kustomization 3 | metadata: 4 | name: apps 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: ./apps/production 14 | prune: true 15 | -------------------------------------------------------------------------------- /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 | interval: 10m0s 8 | sourceRef: 9 | kind: GitRepository 10 | name: flux-system 11 | path: ./infrastructure/production 12 | prune: true 13 | validation: client 14 | -------------------------------------------------------------------------------- /apps/src/order/Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | sudo docker build -t octank-order:latest ./ 3 | aws ecr get-login-password --region us-east-1 | sudo docker login --username AWS --password-stdin xyz.dkr.ecr.us-east-1.amazonaws.com/octank-order 4 | sudo docker tag octank-order:latest xyz.dkr.ecr.us-east-1.amazonaws.com/octank-order:latest 5 | sudo docker push xyz.dkr.ecr.us-east-1.amazonaws.com/octank-order:latest 6 | -------------------------------------------------------------------------------- /apps/src/product/Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | sudo docker build -t octank-product:latest ./ 3 | aws ecr get-login-password --region us-east-1 | sudo docker login --username AWS --password-stdin xyz.dkr.ecr.us-east-1.amazonaws.com/octank-product 4 | sudo docker tag octank-product:latest xyz.dkr.ecr.us-east-1.amazonaws.com/octank-product:latest 5 | sudo docker push xyz.dkr.ecr.us-east-1.amazonaws.com/octank-product:latest 6 | -------------------------------------------------------------------------------- /apps/src/kart/Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | sudo docker build -t octank-kart:latest ./ 3 | aws ecr get-login-password --region us-east-1 | sudo docker login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com/octank-kart 4 | sudo docker tag octank-kart:latest .dkr.ecr.us-east-1.amazonaws.com/octank-kart:latest 5 | sudo docker push .dkr.ecr.us-east-1.amazonaws.com/octank-kart:latest 6 | -------------------------------------------------------------------------------- /apps/production/rds-parameter-group.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rds.services.k8s.aws/v1alpha1 2 | kind: DBParameterGroup 3 | metadata: 4 | name: pg14cluparamg 5 | namespace: retailapp 6 | spec: 7 | description: "customer param group" 8 | family: postgres14 9 | name: pg14 10 | parameterOverrides: 11 | autovacuum_analyze_scale_factor: "0.01" 12 | tags: 13 | - key: env 14 | value: prod 15 | -------------------------------------------------------------------------------- /apps/src/user/Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | sudo docker build -t octank-user:latest ./ 3 | aws ecr get-login-password --region us-east-1 | sudo docker login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com/octank-user 4 | sudo docker tag octank-user:latest .dkr.ecr.us-east-1.amazonaws.com/octank-user:latest 5 | sudo docker push .dkr.ecr.us-east-1.amazonaws.com/octank-user:latest 6 | 7 | -------------------------------------------------------------------------------- /apps/src/webapp/Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | sudo docker build -t octank-webapp:latest ./ 3 | aws ecr get-login-password --region us-east-1 | sudo docker login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com/octank-webapp 4 | sudo docker tag octank-webapp:latest .dkr.ecr.us-east-1.amazonaws.com/octank-webapp:latest 5 | sudo docker push .dkr.ecr.us-east-1.amazonaws.com/octank-webapp:latest 6 | -------------------------------------------------------------------------------- /apps/src/webapp/app/ajax/ajax.py: -------------------------------------------------------------------------------- 1 | from app.models import Product 2 | import traceback 3 | from flask import Flask , Blueprint, jsonify, request, abort 4 | import time 5 | 6 | ajax_bp = Blueprint("ajax_bp", __name__) 7 | 8 | @ajax_bp.route("/search") 9 | def search_query(): 10 | query = request.args.get("query") 11 | product = Product() 12 | product_items = product.show_all_items() 13 | return jsonify([dict(p) for p in product_items if p['name'].lower().startswith(query)]) -------------------------------------------------------------------------------- /apps/src/kart/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi>=2020.4.5.1 2 | chardet<4 3 | click>=7.1.2 4 | Flask==1.1.2 5 | Flask-Cors>=3.0.9 6 | gunicorn>=20.0.4 7 | idna<3 8 | itsdangerous==1.1.0 9 | Jinja2==3.0.3 10 | MarkupSafe>=1.1.1 11 | requests==2.23.0 12 | six>=1.15.0 13 | urllib3==1.26.5 14 | Werkzeug==2.0.2 15 | flask_wtf 16 | email_validator 17 | flask-restful 18 | flask-images 19 | jsonify 20 | json-logging 21 | flask_session 22 | python-memcached 23 | msgpack 24 | redis-py-cluster 25 | psycopg2 26 | -------------------------------------------------------------------------------- /apps/src/order/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi>=2020.4.5.1 2 | chardet<4 3 | click>=7.1.2 4 | Flask==1.1.2 5 | Flask-Cors>=3.0.9 6 | gunicorn>=20.0.4 7 | idna<3 8 | itsdangerous==1.1.0 9 | Jinja2==3.0.3 10 | MarkupSafe>=1.1.1 11 | requests==2.23.0 12 | six>=1.15.0 13 | urllib3==1.26.5 14 | Werkzeug==2.0.2 15 | flask_wtf 16 | email_validator 17 | flask-restful 18 | flask-images 19 | jsonify 20 | json-logging 21 | flask_session 22 | python-memcached 23 | msgpack 24 | redis-py-cluster 25 | psycopg2 26 | -------------------------------------------------------------------------------- /apps/src/user/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi>=2020.4.5.1 2 | chardet<4 3 | click>=7.1.2 4 | Flask==1.1.2 5 | Flask-Cors>=3.0.9 6 | gunicorn>=20.0.4 7 | idna<3 8 | itsdangerous==1.1.0 9 | Jinja2==3.0.3 10 | MarkupSafe>=1.1.1 11 | requests==2.23.0 12 | six>=1.15.0 13 | urllib3==1.26.5 14 | Werkzeug==2.0.2 15 | flask_wtf 16 | email_validator 17 | flask-restful 18 | flask-images 19 | jsonify 20 | json-logging 21 | flask_session 22 | python-memcached 23 | msgpack 24 | redis-py-cluster 25 | psycopg2 26 | -------------------------------------------------------------------------------- /apps/src/webapp/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi>=2020.4.5.1 2 | chardet<4 3 | click>=7.1.2 4 | Flask==1.1.2 5 | Flask-Cors>=3.0.9 6 | gunicorn>=20.0.4 7 | idna<3 8 | itsdangerous==1.1.0 9 | Jinja2==3.0.3 10 | MarkupSafe>=1.1.1 11 | requests==2.23.0 12 | six>=1.15.0 13 | urllib3==1.26.5 14 | Werkzeug==2.0.2 15 | flask_wtf 16 | email_validator 17 | flask-restful 18 | flask-images 19 | jsonify 20 | json-logging 21 | flask_session 22 | python-memcached 23 | msgpack 24 | redis-py-cluster 25 | psycopg2 26 | -------------------------------------------------------------------------------- /apps/src/product/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi>=2020.4.5.1 2 | chardet<4 3 | click>=7.1.2 4 | Flask==1.1.2 5 | Flask-Cors>=3.0.9 6 | gunicorn>=20.0.4 7 | idna<3 8 | itsdangerous==1.1.0 9 | Jinja2==3.0.3 10 | MarkupSafe>=1.1.1 11 | requests==2.23.0 12 | six>=1.15.0 13 | urllib3==1.26.5 14 | Werkzeug==2.0.2 15 | flask_wtf 16 | email_validator 17 | flask-restful 18 | flask-images 19 | jsonify 20 | json-logging 21 | flask_session 22 | python-memcached 23 | msgpack 24 | redis-py-cluster 25 | psycopg2 26 | -------------------------------------------------------------------------------- /apps/src/webapp/app/static/js/pagination.js: -------------------------------------------------------------------------------- 1 | 2 | $(document).on("ready", function(){ 3 | const url= window.location.href 4 | const params = url.split("?") 5 | if (params.length ==1){ 6 | window.location.href="?page=1" 7 | } 8 | 9 | if (params[1] != undefined){ 10 | page = params[1].split("=") 11 | } 12 | else { 13 | page = 1 14 | } 15 | 16 | if(page[1] !=1){ 17 | $(".prev-btn").attr("href", `?page=${page[1]-1}`); 18 | } 19 | else { 20 | $(".prev-btn").attr("href","") 21 | } 22 | 23 | $(".next-btn").attr("href", `?page=${parseInt(page[1])+1}`) 24 | }) 25 | 26 | -------------------------------------------------------------------------------- /infrastructure/production/ack/rds-release.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 2 | kind: HelmRelease 3 | metadata: 4 | name: ack-rds-controller 5 | namespace: flux-system 6 | spec: 7 | releaseName: rds-chart 8 | chart: 9 | spec: 10 | chart: rds-chart 11 | version: v0.1.1 12 | reconcileStrategy: ChartVersion 13 | sourceRef: 14 | kind: HelmRepository 15 | name: ack-source 16 | namespace: flux-system 17 | interval: 1m0s 18 | install: 19 | remediation: 20 | retries: 3 21 | values: 22 | serviceAccount: 23 | create: false 24 | aws: 25 | region: 26 | -------------------------------------------------------------------------------- /infrastructure/production/ack/memorydb-release.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 2 | kind: HelmRelease 3 | metadata: 4 | name: ack-memorydb-controller 5 | namespace: flux-system 6 | spec: 7 | releaseName: memorydb-chart 8 | chart: 9 | spec: 10 | chart: memorydb-chart 11 | version: v0.0.2 12 | reconcileStrategy: ChartVersion 13 | sourceRef: 14 | kind: HelmRepository 15 | name: ack-source 16 | namespace: flux-system 17 | interval: 1m0s 18 | install: 19 | remediation: 20 | retries: 3 21 | values: 22 | serviceAccount: 23 | create: false 24 | aws: 25 | region: 26 | -------------------------------------------------------------------------------- /apps/src/webapp/app/general/templates/general/search_results.html: -------------------------------------------------------------------------------- 1 | {% include "base.html"} 2 | 3 | 4 |
5 | {% for product_items in search_results['products'] :%} 6 | 10 | 11 | {% endfor %} 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /apps/src/kart/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazonlinux:2 2 | 3 | ENV DATABASE_HOST="xyz.com" 4 | ENV DATABASE_USER="xyzuser" 5 | ENV DATABASE_PASSWORD="xyz" 6 | ENV DATABASE_DB_NAME="xyz" 7 | ENV DATABASE_PORT=5432 8 | ENV AUTHTOKEN=test 9 | 10 | RUN yum -y update && \ 11 | amazon-linux-extras enable postgresql14 && \ 12 | yum install -y postgresql postgresql-contrib postgresql-devel && \ 13 | yum -y install gcc gcc-devel && \ 14 | yum -y install python3 python3-devel initscripts && \ 15 | mkdir -p /eksack/app 16 | 17 | WORKDIR /eksack 18 | 19 | COPY app.py . 20 | COPY requirements.txt . 21 | COPY app/ ./app/ 22 | 23 | RUN pip3 install -r requirements.txt 24 | 25 | CMD ["python3", "app.py"] 26 | -------------------------------------------------------------------------------- /apps/src/order/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazonlinux:2 2 | 3 | ENV DATABASE_HOST="xyz.com" 4 | ENV DATABASE_USER="xyzuser" 5 | ENV DATABASE_PASSWORD="xyz" 6 | ENV DATABASE_DB_NAME="xyz" 7 | ENV DATABASE_PORT=5432 8 | ENV AUTHTOKEN=test 9 | 10 | RUN yum -y update && \ 11 | amazon-linux-extras enable postgresql14 && \ 12 | yum install -y postgresql postgresql-contrib postgresql-devel && \ 13 | yum -y install gcc gcc-devel && \ 14 | yum -y install python3 python3-devel initscripts && \ 15 | mkdir -p /eksack/app 16 | 17 | WORKDIR /eksack 18 | 19 | COPY app.py . 20 | COPY requirements.txt . 21 | COPY app/ ./app/ 22 | 23 | RUN pip3 install -r requirements.txt 24 | 25 | CMD ["python3", "app.py"] 26 | -------------------------------------------------------------------------------- /apps/src/user/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazonlinux:2 2 | 3 | ENV DATABASE_HOST="xyz.com" 4 | ENV DATABASE_USER="xyzuser" 5 | ENV DATABASE_PASSWORD="xyz" 6 | ENV DATABASE_DB_NAME="xyz" 7 | ENV DATABASE_PORT=5432 8 | ENV AUTHTOKEN=test 9 | 10 | RUN yum -y update && \ 11 | amazon-linux-extras enable postgresql14 && \ 12 | yum install -y postgresql postgresql-contrib postgresql-devel && \ 13 | yum -y install gcc gcc-devel && \ 14 | yum -y install python3 python3-devel initscripts && \ 15 | mkdir -p /eksack/app 16 | 17 | WORKDIR /eksack 18 | 19 | COPY app.py . 20 | COPY requirements.txt . 21 | COPY app/ ./app/ 22 | 23 | RUN pip3 install -r requirements.txt 24 | 25 | CMD ["python3", "app.py"] 26 | -------------------------------------------------------------------------------- /apps/src/product/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazonlinux:2 2 | 3 | ENV DATABASE_HOST="xyz.com" 4 | ENV DATABASE_USER="xyzuser" 5 | ENV DATABASE_PASSWORD="xyz" 6 | ENV DATABASE_DB_NAME="xyz" 7 | ENV DATABASE_PORT=5432 8 | ENV AUTHTOKEN=test 9 | 10 | RUN yum -y update && \ 11 | amazon-linux-extras enable postgresql14 && \ 12 | yum install -y postgresql postgresql-contrib postgresql-devel && \ 13 | yum -y install gcc gcc-devel && \ 14 | yum -y install python3 python3-devel initscripts && \ 15 | mkdir -p /eksack/app 16 | 17 | WORKDIR /eksack 18 | 19 | COPY app.py . 20 | COPY requirements.txt . 21 | COPY app/ ./app/ 22 | 23 | RUN pip3 install -r requirements.txt 24 | 25 | CMD ["python3", "app.py"] 26 | -------------------------------------------------------------------------------- /apps/src/README.md: -------------------------------------------------------------------------------- 1 | # A Demo Retail Application micro services using PostgreSQL & Redis as backend 2 | 3 | This [project](https://github.com/aws-samples/ack-rds-gitops-workshop) uses the [psycopg2](https://github.com/psycopg/psycopg2) library to connect to PostgreSQL databases. [psycopg2](https://github.com/psycopg/psycopg2) is released under the LGPL-3.0 license. 4 | 5 | ## Clone Git Repo 6 | 7 | ``` 8 | git clone https://github.com/aws-samples/ack-rds-gitops-workshop.git 9 | ``` 10 | 11 | ## Start applications, and database components 12 | 13 | ``` 14 | cd apps/src 15 | docker-compose up -d 16 | ``` 17 | 18 | ## Access application UI from web/browser. 19 | 20 | ``` 21 | http://localhost:8443 22 | ``` 23 | -------------------------------------------------------------------------------- /apps/src/webapp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazonlinux:2 2 | 3 | ENV DATABASE_HOST="xyz.com" 4 | ENV DATABASE_USER="xyzuser" 5 | ENV DATABASE_PASSWORD="xyz" 6 | ENV DATABASE_DB_NAME="xyz" 7 | ENV DATABASE_PORT=5432 8 | ENV ECHOST=xyz 9 | ENV SECRET_KEY=test 10 | 11 | 12 | RUN yum -y update && \ 13 | amazon-linux-extras enable postgresql14 && \ 14 | yum install -y postgresql postgresql-contrib postgresql-devel && \ 15 | yum -y install gcc gcc-devel && \ 16 | yum -y install python3 python3-devel initscripts && \ 17 | mkdir -p /eksack/app 18 | 19 | WORKDIR /eksack 20 | 21 | COPY app.py . 22 | COPY requirements.txt . 23 | COPY app/ ./app/ 24 | 25 | RUN pip3 install -r requirements.txt 26 | 27 | CMD ["python3", "app.py"] 28 | -------------------------------------------------------------------------------- /apps/production/rdspg.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: rdspg-password 5 | namespace: retailapp 6 | type: Opaque 7 | data: 8 | password: cG9zdGdyZXM= 9 | --- 10 | apiVersion: rds.services.k8s.aws/v1alpha1 11 | kind: DBInstance 12 | metadata: 13 | name: rdspg 14 | namespace: retailapp 15 | spec: 16 | allocatedStorage: 20 17 | dbInstanceClass: db.t4g.micro 18 | dbInstanceIdentifier: rdspg 19 | dbSubnetGroupName: 20 | vpcSecurityGroupIDs: 21 | - 22 | storageEncrypted: true 23 | engine: postgres 24 | engineVersion: "14" 25 | masterUsername: "postgres" 26 | masterUserPassword: 27 | namespace: retailapp 28 | name: "rdspg-password" 29 | key: password 30 | -------------------------------------------------------------------------------- /apps/src/webapp/app/static/css/style.css: -------------------------------------------------------------------------------- 1 | .search-results { 2 | position: absolute; 3 | z-index: 100; 4 | } 5 | 6 | .search-results .row .col{ 7 | cursor:pointer; 8 | 9 | } 10 | 11 | .gallery-wrap .img-big-wrap img { 12 | height: 450px; 13 | width: auto; 14 | display: inline-block; 15 | 16 | } 17 | 18 | 19 | .gallery-wrap .img-small-wrap .item-gallery { 20 | width: 60px; 21 | height: 60px; 22 | border: 1px solid #ddd; 23 | margin: 7px 2px; 24 | display: inline-block; 25 | overflow: hidden; 26 | } 27 | 28 | .gallery-wrap .img-small-wrap { 29 | text-align: center; 30 | } 31 | .gallery-wrap .img-small-wrap img { 32 | max-width: 100%; 33 | max-height: 100%; 34 | object-fit: cover; 35 | border-radius: 4px; 36 | 37 | } -------------------------------------------------------------------------------- /apps/production/retailapp/secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: eksack-secret 5 | namespace: retailapp 6 | type: Opaque 7 | data: 8 | database_password: ZWtzYWNrZGVtbw== 9 | database_user: ZGJ1c2VyMQ== 10 | database_db_name: ZWtzYWNrZGVtbw== 11 | database_rodb_name: ZWtzYWNrZGVtbw== 12 | secret_key: ZWtzZ2RiZGVtb3NlY3JldA== 13 | authtoken: ZWtzZ2RiZGVtb3Rva2Vu 14 | kart_service: aHR0cDovL2thcnQucmV0YWlsYXBwLnN2Yy5jbHVzdGVyLmxvY2FsOjg0NDU= 15 | product_service: aHR0cDovL3Byb2R1Y3QucmV0YWlsYXBwLnN2Yy5jbHVzdGVyLmxvY2FsOjg0NDQ= 16 | user_service: aHR0cDovL3VzZXIucmV0YWlsYXBwLnN2Yy5jbHVzdGVyLmxvY2FsOjg0NDY= 17 | order_service: aHR0cDovL29yZGVyLnJldGFpbGFwcC5zdmMuY2x1c3Rlci5sb2NhbDo4NDQ4 18 | memdb_user: ZGVmYXVsdA== 19 | memdb_pass: ZGVmYXVsdA== 20 | -------------------------------------------------------------------------------- /apps/src/webapp/app/auth/forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, PasswordField, BooleanField, SubmitField 3 | from wtforms.validators import DataRequired, Length, Email, EqualTo 4 | 5 | class RegistrationForm(FlaskForm): 6 | name= StringField('Name', 7 | validators=[DataRequired(), Length(min=2, 8 | max=20)]) 9 | email = StringField('Email', 10 | validators=[DataRequired(),Email()]) 11 | password = PasswordField('Password', 12 | validators=[DataRequired()]) 13 | confirm_pasword = PasswordField('Confirm Password', 14 | validators = [DataRequired(), EqualTo('password')] 15 | ) 16 | submit = SubmitField('Register') 17 | 18 | class LoginForm(FlaskForm): 19 | email = StringField('Email', 20 | validators=[DataRequired(),Email()]) 21 | password = PasswordField('Password', 22 | validators=[DataRequired()]) 23 | remember = BooleanField('Remember Me') 24 | submit = SubmitField('Register') 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /scripts/retailapp_tests.txt: -------------------------------------------------------------------------------- 1 | # Product service test 2 | http://product.retailapp.svc.cluster.local:8444/products/popularitems?top=5 3 | curl http://product.retailapp.svc.cluster.local:8444/products/getproducts?productlist=2 4 | 5 | # Order service test 6 | curl -XPOST -H 'Content-Type: application/json' \ 7 | -d '{"email": "user1@gmail.com", "items": [ { "item_id": 2, "qty": 1, "unit_price": 49.98 }, {"item_id": 6, "qty": 1, "unit_price": 37.75} ] }' \ 8 | http://localhost:8448/order/add 9 | 10 | # User service test 11 | curl -XPOST -H 'Content-Type: application/json' \ 12 | -d '{"fname": "user1", "lname": "lname1", "email": "user1@gmail.com", "password": "mytest"}' \ 13 | http://user.retailapp.svc.cluster.local:8446/user/add 14 | curl -XGET -H 'Content-Type: application/json' http://user.retailapp.svc.cluster.local:8446/user/getuser?email=user1@gmail.com 15 | 16 | # Kart service test 17 | curl http://kart.retailapp.svc.cluster.local:8445/kart/get?key=test3@test3.com:Kart 18 | -------------------------------------------------------------------------------- /apps/src/webapp/app/visits/visits.py: -------------------------------------------------------------------------------- 1 | import os, socket 2 | from flask import Blueprint, render_template, request, jsonify, url_for, redirect, Response 3 | from flask import session, redirect, escape, request 4 | 5 | visits_bp = Blueprint("visits_bp", __name__, template_folder="templates/visits") 6 | 7 | @visits_bp.route('/') 8 | def index(): 9 | username="" 10 | if session and 'email' in session: 11 | username = escape(session.get('email')) 12 | session['visits'] = session.get('visits', 0) + 1 13 | return u''' 14 | 15 | 16 | Session Stickyness Test 17 | 18 | 19 |

Session Stickyness Test


20 |

Logged in as {0} on host {1}.

21 |

Visits: {2}

22 | 23 | 24 | '''.format(username, socket.gethostname(), session.get('visits')) 25 | -------------------------------------------------------------------------------- /apps/src/webapp/app/auth/static/css/style.css: -------------------------------------------------------------------------------- 1 | .form-container{ 2 | width:100%; 3 | margin:auto; 4 | } 5 | button{ 6 | height: 30px; 7 | width:250px; 8 | border:1px solid #17a589; 9 | color:#17a589; 10 | margin:0; 11 | padding:0; 12 | } 13 | 14 | 15 | 16 | 17 | .input-field{ 18 | position: relative; 19 | width: 250px; 20 | height: 44px; 21 | line-height: 44px; 22 | } 23 | label { 24 | position: absolute; 25 | /* top: 0; 26 | left: 0;*/ 27 | top:0; 28 | left:0; 29 | color: #d3d3d3; 30 | transition: 0.2s all; 31 | cursor: text; 32 | } 33 | input { 34 | width: 100%; 35 | border: 0; 36 | outline: 0; 37 | padding: 0.5rem 0; 38 | border-bottom: 2px solid #d3d3d3; 39 | box-shadow: none; 40 | color: #111; 41 | } 42 | input:invalid { 43 | outline: 0; 44 | /* color: #ff2300; 45 | border-color: #ff2300;*/ 46 | } 47 | input:focus, 48 | input:valid { 49 | border-color: #17a589; 50 | } 51 | input:focus~label, 52 | input:valid~label { 53 | font-size: 14px; 54 | top: -24px; 55 | color: #17a589; 56 | } 57 | -------------------------------------------------------------------------------- /apps/src/webapp/app/SessionStore.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from rediscluster import RedisCluster 4 | import ast 5 | 6 | class Sessionstore: 7 | """Store session/Kart data in Memcached.""" 8 | 9 | def __init__(self, session_id): 10 | memdbhost = os.environ.get('MEMDB_HOST') 11 | memdbport = os.environ.get('MEMDB_PORT') 12 | memdbuser = os.environ.get('MEMDB_USER') 13 | memdbpass = os.environ.get('MEMDB_PASS') 14 | redis = RedisCluster(startup_nodes=[{"host": memdbhost, "port": memdbport}], 15 | decode_responses=True, skip_full_coverage_check=True, 16 | ssl=True) 17 | self.redis = redis 18 | self.sessid = "{}:{}".format('session', session_id) 19 | 20 | def set(self, key, value): 21 | print (value) 22 | return self.redis.set(self.sessid + ':' + key, str(value)) 23 | 24 | def get(self, key): 25 | value = self.redis.get(self.sessid + ':' + key) 26 | if value: 27 | return ast.literal_eval(value) 28 | return None 29 | 30 | -------------------------------------------------------------------------------- /apps/src/kart/app/kart/kart.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, Blueprint, jsonify, request, abort ,redirect, url_for 2 | from app.schema.models import Kart 3 | from app.auth.auth import requires_auth 4 | import ast 5 | 6 | kart_bp = Blueprint("kart_bp", __name__) 7 | 8 | @kart_bp.route("/get") 9 | def get(): 10 | key = request.args.get("key") 11 | print ("In Kart, for key value {}".format(key)) 12 | value = Kart().get(key) 13 | print ("In Kart, extracted for value {} from Model".format(value)) 14 | return jsonify({'key': key, 'value': value}) 15 | 16 | @kart_bp.route("/set", methods=['POST']) 17 | def set(): 18 | data = ast.literal_eval(request.data.decode('utf-8')) 19 | key = data.get("key") 20 | value = data.get("value") 21 | result = Kart().set(key, value) 22 | print (result) 23 | return jsonify({'key': key, 'value': value}) 24 | 25 | @kart_bp.route("/remove", methods=['POST']) 26 | def remove(): 27 | data = ast.literal_eval(request.data.decode('utf-8')) 28 | key = data.get("key") 29 | result = Kart().delete(key) 30 | return jsonify({'key': key, value: None}) 31 | -------------------------------------------------------------------------------- /apps/src/user/app/user/user.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, Blueprint, jsonify, request, abort ,redirect, url_for 2 | from app.schema.models import User 3 | from app.auth.auth import requires_auth 4 | import json 5 | import ast 6 | 7 | user_bp = Blueprint("user_bp", __name__) 8 | 9 | # @cross_origin(headers=['Content-Type', 'Authorization']) 10 | # @requires_auth 11 | 12 | @user_bp.route("/add", methods=['POST']) 13 | def add_user(): 14 | data = ast.literal_eval(request.data.decode('utf-8')) 15 | user = User() 16 | user.add(data.get('fname'), data.get('lname'), data.get('email'), data.get('password')) 17 | return jsonify({'title': 'User', 'result': True}) 18 | 19 | @user_bp.route("/getuser", methods=['GET']) 20 | def get_user(): 21 | email = request.args.get('email') 22 | user = User() 23 | userinfo = user.get(email) 24 | return jsonify({'title': 'User Info', 'result': userinfo}) 25 | 26 | @user_bp.route("/verify", methods=['GET']) 27 | def verify_user(): 28 | email = request.args.get('email') 29 | password = request.args.get('password') 30 | user = User() 31 | result = user.verify(email, password) 32 | return jsonify({'title': 'User Check', 'result': result}) 33 | -------------------------------------------------------------------------------- /apps/src/webapp/app/static/js/ajax.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | $(".search-results").css("display", "none") 3 | $("body").on("click", function(){ 4 | $(".search-results").css("display", "none") 5 | }) 6 | 7 | if($('[data-toggle="tooltip"]').length>0) { // check if element exists 8 | $('[data-toggle="tooltip"]').tooltip() 9 | } 10 | 11 | $(".search input").on("keyup", function(e){ 12 | value = $(".search input").val(); 13 | $(".search-results").css("display", "block") 14 | $.ajax({ 15 | url:`/ajax/search?query=${value.toLowerCase()}` 16 | }).done(function(data){ 17 | showResults(data) 18 | }) 19 | 20 | }) 21 | 22 | function showResults(data){ 23 | var output=""; 24 | if(data.length === 0 ){ 25 | output = "No Search Results Found" 26 | $(".search-results .container").html(output) 27 | } 28 | else if (data.length <=4 ){ 29 | data.forEach(item => { 30 | output += ` 31 |
32 |
33 | ${item.name} 34 | 35 |
36 |
37 | ` 38 | 39 | $(".search-results").html(output) 40 | }) 41 | 42 | } 43 | 44 | 45 | } 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /apps/src/order/app/order/order.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, Blueprint, jsonify, request, abort ,redirect, url_for 2 | from app.schema.models import Order 3 | from app.auth.auth import requires_auth 4 | import ast 5 | 6 | order_bp = Blueprint("order_bp", __name__) 7 | 8 | # @cross_origin(headers=['Content-Type', 'Authorization']) 9 | # @requires_auth 10 | 11 | @order_bp.route("/getorder") 12 | def get_order(): 13 | order_id = request.args.get("order_id") 14 | email = request.args.get("email") 15 | if not email: 16 | return jsonify({'title': 'Order Details', 'orders' : {}, 'status_code': 501, 'content': 'missing email'}) 17 | order = Order(email) 18 | order_details = order.get_orders(order_id) 19 | return jsonify({'title': 'Order Details', 'orders': order_details, 'status_code': 200}) 20 | 21 | @order_bp.route("/add", methods=['POST']) 22 | def add_order(): 23 | data = ast.literal_eval(request.data.decode('utf-8')) 24 | email = data.get('email') 25 | if not email: 26 | return jsonify({'title': 'Order Details', 'order_details' : {}, 'status_code': 501, 'content': 'missing email'}) 27 | order = Order(email) 28 | result = order.add(data) 29 | data['order_id'] = result 30 | return jsonify({'title': 'Orders', 'order_details': data, 'status_code': 200}) 31 | 32 | -------------------------------------------------------------------------------- /apps/production/memorydb-cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: memorydb.services.k8s.aws/v1alpha1 2 | kind: Cluster 3 | metadata: 4 | name: "memorydb-cluster" 5 | namespace: retailapp 6 | spec: 7 | name: "memorydb" 8 | nodeType: db.t4g.small 9 | aclName: open-access 10 | securityGroupIDs: 11 | - 12 | subnetGroupName: 13 | --- 14 | apiVersion: v1 15 | kind: ConfigMap 16 | metadata: 17 | name: memorydb-cluster-conn-cm 18 | namespace: retailapp 19 | data: {} 20 | --- 21 | apiVersion: services.k8s.aws/v1alpha1 22 | kind: FieldExport 23 | metadata: 24 | name: memorydb-cluster-host 25 | namespace: retailapp 26 | spec: 27 | to: 28 | name: memorydb-cluster-conn-cm 29 | kind: configmap 30 | from: 31 | path: ".status.clusterEndpoint.address" 32 | resource: 33 | group: memorydb.services.k8s.aws 34 | kind: Cluster 35 | name: memorydb-cluster 36 | --- 37 | apiVersion: services.k8s.aws/v1alpha1 38 | kind: FieldExport 39 | metadata: 40 | name: memorydb-cluster-port 41 | namespace: retailapp 42 | spec: 43 | to: 44 | name: memorydb-cluster-conn-cm 45 | kind: configmap 46 | from: 47 | path: ".status.clusterEndpoint.port" 48 | resource: 49 | group: memorydb.services.k8s.aws 50 | kind: Cluster 51 | name: memorydb-cluster 52 | -------------------------------------------------------------------------------- /apps/production/retailapp/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: webapp 5 | namespace: retailapp 6 | labels: 7 | app: eksack 8 | service: webapp 9 | annotations: 10 | kubernetes.io/ingress.class: alb 11 | alb.ingress.kubernetes.io/scheme: internet-facing 12 | alb.ingress.kubernetes.io/tags: Application=webapp 13 | alb.ingress.kubernetes.io/target-type: instance 14 | alb.ingress.kubernetes.io/healthcheck-protocol: HTTP 15 | alb.ingress.kubernetes.io/healthcheck-port: traffic-port 16 | alb.ingress.kubernetes.io/healthcheck-path: /healthcheck 17 | alb.ingress.kubernetes.io/healthcheck-interval-seconds: '60' 18 | alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '30' 19 | alb.ingress.kubernetes.io/success-codes: '200' 20 | alb.ingress.kubernetes.io/healthy-threshold-count: '2' 21 | alb.ingress.kubernetes.io/unhealthy-threshold-count: '5' 22 | alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true, load_balancing.algorithm.type=least_outstanding_requests 23 | spec: 24 | rules: 25 | - http: 26 | paths: 27 | - path: / 28 | pathType: Prefix 29 | backend: 30 | service: 31 | name: webappnp 32 | port: 33 | number: 80 34 | --- 35 | -------------------------------------------------------------------------------- /apps/src/kart/app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, abort , session, redirect 2 | 3 | from flask_cors import CORS 4 | import logging, sys, json_logging, flask, os 5 | from flask_session import Session 6 | import memcache 7 | from app.kart.kart import kart_bp 8 | from flask.json import JSONEncoder 9 | import decimal 10 | 11 | 12 | class JsonEncoder(JSONEncoder): 13 | def default(self, obj): 14 | if isinstance(obj, decimal.Decimal): 15 | return float(obj) 16 | return JSONEncoder.default(self, obj) 17 | 18 | app = Flask(__name__) 19 | json_logging.init_flask(enable_json=True) 20 | json_logging.init_request_instrument(app) 21 | cors = CORS(app) 22 | app.secret_key = os.environ.get('SECRET_KEY', "hhdhdhdhdh7788768") 23 | 24 | app.config['SESSION_PERMANENT'] = False 25 | app.config['SESSION_USE_SIGNER'] = True 26 | 27 | if os.environ.get('ECHOST'): 28 | app.config['SESSION_TYPE'] = 'memcached' 29 | echosts = [ x.split(':')[0] for x in os.environ.get('ECHOST').split(',') ] 30 | app.config['SESSION_MEMCACHED'] = memcache.Client( echosts ) 31 | else: 32 | app.config['SESSION_TYPE'] = 'filesystem' 33 | app.config['SESSION_FILE_DIR'] = '/tmp' 34 | app.config['SESSION_FILE_THRESHOLD'] = 100 35 | 36 | Session(app) 37 | 38 | app.json_encoder = JsonEncoder 39 | 40 | app.register_blueprint(kart_bp, url_prefix="/kart") 41 | 42 | -------------------------------------------------------------------------------- /apps/src/user/app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, abort , session, redirect 2 | 3 | from flask_cors import CORS 4 | import logging, sys, json_logging, flask, os 5 | from flask_session import Session 6 | import memcache 7 | from app.user.user import user_bp 8 | from flask.json import JSONEncoder 9 | import decimal 10 | 11 | 12 | class JsonEncoder(JSONEncoder): 13 | def default(self, obj): 14 | if isinstance(obj, decimal.Decimal): 15 | return float(obj) 16 | return JSONEncoder.default(self, obj) 17 | 18 | app = Flask(__name__) 19 | json_logging.init_flask(enable_json=True) 20 | json_logging.init_request_instrument(app) 21 | cors = CORS(app) 22 | app.secret_key = os.environ.get('SECRET_KEY', "hhdhdhdhdh7788768") 23 | 24 | app.config['SESSION_PERMANENT'] = False 25 | app.config['SESSION_USE_SIGNER'] = True 26 | 27 | if os.environ.get('ECHOST'): 28 | app.config['SESSION_TYPE'] = 'memcached' 29 | echosts = [ x.split(':')[0] for x in os.environ.get('ECHOST').split(',') ] 30 | app.config['SESSION_MEMCACHED'] = memcache.Client( echosts ) 31 | else: 32 | app.config['SESSION_TYPE'] = 'filesystem' 33 | app.config['SESSION_FILE_DIR'] = '/tmp' 34 | app.config['SESSION_FILE_THRESHOLD'] = 100 35 | 36 | Session(app) 37 | 38 | app.json_encoder = JsonEncoder 39 | 40 | app.register_blueprint(user_bp, url_prefix="/user") 41 | 42 | -------------------------------------------------------------------------------- /apps/src/order/app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, abort , session, redirect 2 | 3 | from flask_cors import CORS 4 | import logging, sys, json_logging, flask, os 5 | from flask_session import Session 6 | import memcache 7 | from app.order.order import order_bp 8 | from flask.json import JSONEncoder 9 | import decimal 10 | 11 | 12 | class JsonEncoder(JSONEncoder): 13 | def default(self, obj): 14 | if isinstance(obj, decimal.Decimal): 15 | return float(obj) 16 | return JSONEncoder.default(self, obj) 17 | 18 | app = Flask(__name__) 19 | json_logging.init_flask(enable_json=True) 20 | json_logging.init_request_instrument(app) 21 | cors = CORS(app) 22 | app.secret_key = os.environ.get('SECRET_KEY', "hhdhdhdhdh7788768") 23 | 24 | app.config['SESSION_PERMANENT'] = False 25 | app.config['SESSION_USE_SIGNER'] = True 26 | 27 | if os.environ.get('ECHOST'): 28 | app.config['SESSION_TYPE'] = 'memcached' 29 | echosts = [ x.split(':')[0] for x in os.environ.get('ECHOST').split(',') ] 30 | app.config['SESSION_MEMCACHED'] = memcache.Client( echosts ) 31 | else: 32 | app.config['SESSION_TYPE'] = 'filesystem' 33 | app.config['SESSION_FILE_DIR'] = '/tmp' 34 | app.config['SESSION_FILE_THRESHOLD'] = 100 35 | 36 | Session(app) 37 | 38 | app.json_encoder = JsonEncoder 39 | 40 | app.register_blueprint(order_bp, url_prefix="/order") 41 | 42 | -------------------------------------------------------------------------------- /apps/src/webapp/app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, abort , session, redirect 2 | from flask_images import resized_img_src 3 | from app.general.general import general_bp 4 | from app.ajax.ajax import ajax_bp 5 | from app.products.products import products_bp 6 | from app.auth.auth import auth_bp 7 | from app.cart.cart import cart_bp 8 | from app.visits.visits import visits_bp 9 | from flask_cors import CORS 10 | import logging, sys, json_logging, flask, os 11 | from flask_session import Session 12 | import memcache 13 | 14 | app = Flask(__name__) 15 | sess = Session() 16 | json_logging.init_flask(enable_json=True) 17 | json_logging.init_request_instrument(app) 18 | cors = CORS(app) 19 | app.secret_key = os.environ.get('SECRET_KEY', "hhdhdhdhdh7788768") 20 | 21 | app.config['SESSION_PERMANENT'] = False 22 | app.config['SESSION_USE_SIGNER'] = True 23 | app.config['SESSION_TYPE'] = 'filesystem' 24 | sess.init_app(app) 25 | 26 | @app.before_request 27 | def run(): 28 | if 'email' in session: 29 | redirect("/") 30 | else: 31 | redirect("/auth/login") 32 | app.register_blueprint(general_bp) 33 | app.register_blueprint(ajax_bp, url_prefix="/ajax") 34 | app.register_blueprint(products_bp, url_prefix="/products") 35 | app.register_blueprint(auth_bp, url_prefix="/auth") 36 | app.register_blueprint(cart_bp, url_prefix="/cart") 37 | app.register_blueprint(visits_bp, url_prefix="/visits") 38 | -------------------------------------------------------------------------------- /apps/src/product/app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, abort , session, redirect 2 | 3 | from flask_cors import CORS 4 | import logging, sys, json_logging, flask, os 5 | from flask_session import Session 6 | import memcache 7 | from app.products.products import products_bp 8 | from flask.json import JSONEncoder 9 | import decimal 10 | 11 | 12 | class JsonEncoder(JSONEncoder): 13 | def default(self, obj): 14 | if isinstance(obj, decimal.Decimal): 15 | return float(obj) 16 | return JSONEncoder.default(self, obj) 17 | 18 | app = Flask(__name__) 19 | json_logging.init_flask(enable_json=True) 20 | json_logging.init_request_instrument(app) 21 | cors = CORS(app) 22 | app.secret_key = os.environ.get('SECRET_KEY', "hhdhdhdhdh7788768") 23 | 24 | app.config['SESSION_PERMANENT'] = False 25 | app.config['SESSION_USE_SIGNER'] = True 26 | 27 | if os.environ.get('ECHOST'): 28 | app.config['SESSION_TYPE'] = 'memcached' 29 | echosts = [ x.split(':')[0] for x in os.environ.get('ECHOST').split(',') ] 30 | app.config['SESSION_MEMCACHED'] = memcache.Client( echosts ) 31 | else: 32 | app.config['SESSION_TYPE'] = 'filesystem' 33 | app.config['SESSION_FILE_DIR'] = '/tmp' 34 | app.config['SESSION_FILE_THRESHOLD'] = 100 35 | 36 | Session(app) 37 | 38 | app.json_encoder = JsonEncoder 39 | 40 | app.register_blueprint(products_bp, url_prefix="/products") 41 | 42 | -------------------------------------------------------------------------------- /apps/src/webapp/app/auth/templates/auth/account.html: -------------------------------------------------------------------------------- 1 | {%include "base.html" %} 2 |
3 | 4 | 5 |
6 |
7 |

Account Details

8 | 9 | {% for account in acctinfo %} 10 |
11 | 16 |
17 |
18 | 23 |
24 |
25 | 30 |
31 | {% endfor %} 32 | 33 |
34 |
35 | 36 |

37 | 38 | 39 | 40 |
41 | -------------------------------------------------------------------------------- /apps/src/webapp/app/auth/templates/auth/forgot_password.html: -------------------------------------------------------------------------------- 1 | {% include "base.html" %} 2 | 3 |
4 |
5 |
6 |
7 |
8 |
9 |

10 |

Forgot Password?

11 |

You can reset your password here.

12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 |
23 | 24 |
25 | 26 | 27 |
28 | 29 |
30 | 31 | 32 | 33 | 34 |
-------------------------------------------------------------------------------- /apps/src/Makefile: -------------------------------------------------------------------------------- 1 | 2 | run: 3 | AWS_REGION=$(shell curl -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.region') 4 | AWS_ACCOUNT_ID=$(shell aws sts get-caller-identity --output text --query Account) 5 | docker build -t $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/eksack/webapp:1.0 webapp/. 6 | docker build -t ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eksack/product:1.0 product/. 7 | docker build -t ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eksack/kart:1.0 kart/. 8 | docker build -t ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eksack/order:1.0 order/. 9 | docker build -t ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eksack/user:1.0 user/. 10 | aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com 11 | aws ecr create-repository --repository-name eksack/webapp 12 | docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eksack/webapp:1.0 13 | aws ecr create-repository --repository-name eksack/product 14 | docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eksack/product:1.0 15 | aws ecr create-repository --repository-name eksack/order 16 | docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eksack/order:1.0 17 | aws ecr create-repository --repository-name eksack/kart 18 | docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eksack/kart:1.0 19 | aws ecr create-repository --repository-name eksack/user 20 | docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eksack/user:1.0 21 | 22 | -------------------------------------------------------------------------------- /apps/src/webapp/app/static/css/responsive.css: -------------------------------------------------------------------------------- 1 | .mobile-block { 2 | display: none; } 3 | 4 | @media (max-width: 1200px) { 5 | /* tablet devices */ } 6 | @media (max-width: 992px) { 7 | /* small tablet devices */ 8 | .slider-main .item-slide { 9 | height: auto; } 10 | 11 | .table-shopping-cart tr td:first-child { 12 | width: 250px; } 13 | .table-shopping-cart .itemside .info { 14 | padding: 0; } 15 | .table-shopping-cart .itemside .aside { 16 | display: none; } } 17 | @media all and (max-width: 768px) { 18 | /* mobile devices */ 19 | .section-header .search { 20 | margin-top: 1rem; 21 | margin-bottom: 1rem; } 22 | 23 | .item-feature { 24 | margin-bottom: 20px; } 25 | 26 | .mobile-order-first { 27 | -webkit-box-ordinal-group: 0; 28 | -ms-flex-order: -1; 29 | order: -1; } 30 | 31 | .mobile-order-1 { 32 | -webkit-box-ordinal-group: 2; 33 | -ms-flex-order: 1; 34 | order: 1; } 35 | 36 | .mobile-order-2 { 37 | -webkit-box-ordinal-group: 3; 38 | -ms-flex-order: 2; 39 | order: 2; } 40 | 41 | .mobile-order-3 { 42 | -webkit-box-ordinal-group: 4; 43 | -ms-flex-order: 3; 44 | order: 3; } 45 | 46 | .mobile-block { 47 | display: block; } 48 | 49 | .section-header .logo { 50 | max-height: 40px; 51 | width: auto; } 52 | .section-header .search { 53 | margin-top: 1rem; } 54 | 55 | .navbar-expand .navbar-collapse { 56 | -webkit-box-orient: vertical; 57 | -webkit-box-direction: normal; 58 | -ms-flex-direction: column; 59 | flex-direction: column; } } 60 | 61 | /*# sourceMappingURL=responsive.css.map */ -------------------------------------------------------------------------------- /apps/src/webapp/app/general/general.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, session, Blueprint, render_template, request, jsonify, url_for, redirect 2 | import requests 3 | import json 4 | import os 5 | from .. import models 6 | 7 | general_bp = Blueprint("general_bp", __name__ , template_folder="templates/general", static_url_path="/static") 8 | @general_bp.route("/") 9 | def home(): 10 | products = models.Product("fashion") 11 | response = products.popular_items() 12 | if response.status_code != 200: 13 | abort(401) 14 | popular_items = response.json().get('product_items') 15 | return render_template("index.html", title="Home", products=popular_items) 16 | 17 | @general_bp.route("/apiproduct", methods = ['get']) 18 | def apiproduct(): 19 | r = None 20 | whereami = None 21 | content = {"Lab": "DAT312 Workshop"} 22 | whereami = models.Product().whereami() 23 | if whereami: 24 | whereami = whereami.json() 25 | content["Aurora"] = {"reader": whereami and whereami[0] and whereami[0].get("reader") and whereami[0].get("reader")[0] or None, 26 | "writer": whereami and whereami[0] and whereami[0].get("writer") and whereami[0].get("writer")[0] or None} 27 | try: 28 | r = requests.get('http://169.254.169.254/latest/dynamic/instance-identity/document', timeout=5) 29 | if r.status_code == 200: 30 | content["region"] = r.json().get("region") 31 | content["instanceId"] = r.json().get("instanceId") 32 | except: 33 | pass 34 | return jsonify(content), 200 35 | 36 | @general_bp.route("/healthcheck", methods=['get']) 37 | def healthcheck(): 38 | return {'status': 'success'}, 200 39 | 40 | @general_bp.route("/analytic") 41 | def analytics(): 42 | pass 43 | -------------------------------------------------------------------------------- /apps/src/user/app/schema/models.py.MemoryDB: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from rediscluster import RedisCluster 4 | 5 | class User: 6 | def __init__(self): 7 | memdbhost = os.environ.get('MEMDB_HOST') 8 | memdbport = os.environ.get('MEMDB_PORT') 9 | memdbuser = os.environ.get('MEMDB_USER') 10 | memdbpass = os.environ.get('MEMDB_PASS') 11 | redis = RedisCluster(startup_nodes=[{"host": memdbhost, "port": memdbport}], 12 | decode_responses=True, skip_full_coverage_check=True, 13 | ssl=True) 14 | # ssl=True, username=memdbuser, password=memdbpass) 15 | self.redis = redis 16 | self.user = None 17 | self.email = None 18 | 19 | def add(self, fname, lname, email, password): 20 | data = {'fname': fname, 'lname': lname, 'email': email, 'password': password} 21 | key = 'user:email:{}'.format(email) 22 | self.redis.hset(key, mapping=data) 23 | self.user = lname + ", " + fname 24 | self.email = email 25 | data['id'] = key 26 | return data 27 | 28 | def get(self, email): 29 | key = 'user:email:{}'.format(email) 30 | data = self.redis.hgetall(key) 31 | print (data) 32 | return [ data ] 33 | 34 | def verify(self, email ,password): 35 | key = 'user:email:{}'.format(email) 36 | data = self.redis.hgetall(key) 37 | print ('in verify') 38 | print (data) 39 | print (type(data)) 40 | row_count = len(data) 41 | if not data: 42 | return False 43 | if not data.get('password'): 44 | return False 45 | if data.get('password') == password: 46 | return True 47 | return False 48 | 49 | -------------------------------------------------------------------------------- /apps/src/webapp/app/auth/static/css/navbar.css: -------------------------------------------------------------------------------- 1 | 2 | .header { 3 | margin-top:40px; 4 | } 5 | .navbar { 6 | margin-bottom:60px; 7 | } 8 | .navbar nav ul li { 9 | list-style:none; 10 | display:inline-block; 11 | margin:10px; 12 | } 13 | 14 | #logo{ 15 | font-size:20px; 16 | font-weight:bold; 17 | } 18 | 19 | #logo-color { 20 | color:#17a589; 21 | } 22 | 23 | a{ 24 | text-decoration:none; 25 | color:#000; 26 | } 27 | 28 | .dropdown{ 29 | margin-left:40px; 30 | position:relative; 31 | } 32 | 33 | .dropdown:after, .languages-list:after { 34 | content:"\f0d7"; 35 | font-family:'Font Awesome 5 Free'; 36 | font-weight:bold; 37 | position: absolute; 38 | top: 13px; 39 | right: 10px; 40 | z-index: 5; 41 | color:#17a589; 42 | } 43 | .dropdown select { 44 | border: 1px solid rgb(241, 241, 241); 45 | padding:10px; 46 | border-radius:10px; 47 | color:#17a589; 48 | box-shadow:rgba(0, 0, 0, 0.08) 0 5px 10px; 49 | background-color:transparent; 50 | } 51 | 52 | 53 | .dropdown select option , .languages-list select option { 54 | width:150px; 55 | } 56 | 57 | .dropdown select, .languages-list select { 58 | -moz-appearance:none; 59 | } 60 | 61 | .dropdown select option { 62 | background-color:#fff; 63 | width:200px; 64 | } 65 | 66 | .profile-menu { 67 | position:absolute; 68 | right:10%; 69 | z-index:10px; 70 | } 71 | 72 | .profile-menu .profile-image { 73 | font-size:20px; 74 | } 75 | 76 | .profile-menu #profile-options { 77 | visibility:hidden; 78 | box-shadow:rgba(0,0,0,0.5) 0 5px 10px; 79 | padding:10px 20px; 80 | border-radius:10px; 81 | position:absolute; 82 | } 83 | 84 | .profile-menu .profile-option{ 85 | margin-bottom:5px; 86 | } 87 | 88 | .profile-option a:hover { 89 | color:#17a589; 90 | cursor:pointer; 91 | } -------------------------------------------------------------------------------- /apps/production/retailapp/services.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: kart 5 | namespace: retailapp 6 | labels: 7 | app: eksack 8 | service: kart 9 | spec: 10 | type: ClusterIP 11 | selector: 12 | app: eksack 13 | service: kart 14 | ports: 15 | - name: kart 16 | protocol: TCP 17 | port: 8445 18 | targetPort: 8445 19 | --- 20 | apiVersion: v1 21 | kind: Service 22 | metadata: 23 | name: product 24 | namespace: retailapp 25 | labels: 26 | app: eksack 27 | service: product 28 | spec: 29 | type: ClusterIP 30 | selector: 31 | app: eksack 32 | service: product 33 | ports: 34 | - name: product 35 | protocol: TCP 36 | port: 8444 37 | targetPort: 8444 38 | --- 39 | apiVersion: v1 40 | kind: Service 41 | metadata: 42 | name: user 43 | namespace: retailapp 44 | labels: 45 | app: eksack 46 | service: user 47 | spec: 48 | type: ClusterIP 49 | selector: 50 | app: eksack 51 | service: user 52 | ports: 53 | - name: user 54 | protocol: TCP 55 | port: 8446 56 | targetPort: 8446 57 | --- 58 | apiVersion: v1 59 | kind: Service 60 | metadata: 61 | name: order 62 | namespace: retailapp 63 | labels: 64 | app: eksack 65 | service: order 66 | spec: 67 | type: ClusterIP 68 | selector: 69 | app: eksack 70 | service: order 71 | ports: 72 | - name: order 73 | protocol: TCP 74 | port: 8448 75 | targetPort: 8448 76 | --- 77 | apiVersion: v1 78 | kind: Service 79 | metadata: 80 | name: webappnp 81 | namespace: retailapp 82 | labels: 83 | service: webappnp 84 | spec: 85 | type: NodePort 86 | selector: 87 | app: eksack 88 | service: webapp 89 | ports: 90 | - port: 80 91 | targetPort: 8443 92 | protocol: TCP 93 | -------------------------------------------------------------------------------- /apps/src/webapp/app/auth/templates/auth/login.html: -------------------------------------------------------------------------------- 1 | {%include "base.html" %} 2 |
3 | 4 | 5 |
6 |
7 |

Sign in

8 |
9 |   Sign in with Facebook 10 |   Sign in with Google 11 |
12 | 13 |
14 |
15 | 16 |
17 | 18 |
19 | Forgot password? 20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 | 29 |

Don't have account? Sign up

30 |

31 | 32 | 33 | 34 |
-------------------------------------------------------------------------------- /apps/src/kart/app/auth/auth.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from flask import request, make_response, jsonify 3 | import os 4 | 5 | class AuthError(Exception): 6 | def __init__(self, error, status_code): 7 | self.error = error 8 | self.status_code = status_code 9 | 10 | def get_token_auth_header(): 11 | """Obtains the access token from the Authorization Header 12 | """ 13 | auth = request.headers.get("Authorization", None) 14 | if not auth: 15 | raise AuthError({"code": "authorization_header_missing", 16 | "description": 17 | "Authorization header is expected"}, 401) 18 | 19 | parts = auth.split() 20 | 21 | if parts[0].lower() != "bearer": 22 | raise AuthError({"code": "invalid_header", 23 | "description": 24 | "Authorization header must start with" 25 | " Bearer"}, 401) 26 | elif len(parts) == 1: 27 | raise AuthError({"code": "invalid_header", 28 | "description": "Token not found"}, 401) 29 | elif len(parts) > 2: 30 | raise AuthError({"code": "invalid_header", 31 | "description": 32 | "Authorization header must be" 33 | " Bearer token"}, 401) 34 | 35 | token = parts[1] 36 | return token 37 | 38 | def requires_auth(f): 39 | """Determines if the access token is valid 40 | """ 41 | @wraps(f) 42 | def decorated(*args, **kwargs): 43 | token = get_token_auth_header() 44 | valid_token = os.environ.get('AUTHTOKEN', 'krishna') 45 | print (valid_token) 46 | print (token) 47 | if (token != valid_token): 48 | raise AuthError({"code": "invalid_token", 49 | "description": "invalid token"}, 401) 50 | return f(*args, **kwargs) 51 | return decorated 52 | 53 | -------------------------------------------------------------------------------- /apps/src/order/app/auth/auth.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from flask import request, make_response, jsonify 3 | import os 4 | 5 | class AuthError(Exception): 6 | def __init__(self, error, status_code): 7 | self.error = error 8 | self.status_code = status_code 9 | 10 | def get_token_auth_header(): 11 | """Obtains the access token from the Authorization Header 12 | """ 13 | auth = request.headers.get("Authorization", None) 14 | if not auth: 15 | raise AuthError({"code": "authorization_header_missing", 16 | "description": 17 | "Authorization header is expected"}, 401) 18 | 19 | parts = auth.split() 20 | 21 | if parts[0].lower() != "bearer": 22 | raise AuthError({"code": "invalid_header", 23 | "description": 24 | "Authorization header must start with" 25 | " Bearer"}, 401) 26 | elif len(parts) == 1: 27 | raise AuthError({"code": "invalid_header", 28 | "description": "Token not found"}, 401) 29 | elif len(parts) > 2: 30 | raise AuthError({"code": "invalid_header", 31 | "description": 32 | "Authorization header must be" 33 | " Bearer token"}, 401) 34 | 35 | token = parts[1] 36 | return token 37 | 38 | def requires_auth(f): 39 | """Determines if the access token is valid 40 | """ 41 | @wraps(f) 42 | def decorated(*args, **kwargs): 43 | token = get_token_auth_header() 44 | valid_token = os.environ.get('AUTHTOKEN', 'krishna') 45 | print (valid_token) 46 | print (token) 47 | if (token != valid_token): 48 | raise AuthError({"code": "invalid_token", 49 | "description": "invalid token"}, 401) 50 | return f(*args, **kwargs) 51 | return decorated 52 | 53 | -------------------------------------------------------------------------------- /apps/src/user/app/auth/auth.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from flask import request, make_response, jsonify 3 | import os 4 | 5 | class AuthError(Exception): 6 | def __init__(self, error, status_code): 7 | self.error = error 8 | self.status_code = status_code 9 | 10 | def get_token_auth_header(): 11 | """Obtains the access token from the Authorization Header 12 | """ 13 | auth = request.headers.get("Authorization", None) 14 | if not auth: 15 | raise AuthError({"code": "authorization_header_missing", 16 | "description": 17 | "Authorization header is expected"}, 401) 18 | 19 | parts = auth.split() 20 | 21 | if parts[0].lower() != "bearer": 22 | raise AuthError({"code": "invalid_header", 23 | "description": 24 | "Authorization header must start with" 25 | " Bearer"}, 401) 26 | elif len(parts) == 1: 27 | raise AuthError({"code": "invalid_header", 28 | "description": "Token not found"}, 401) 29 | elif len(parts) > 2: 30 | raise AuthError({"code": "invalid_header", 31 | "description": 32 | "Authorization header must be" 33 | " Bearer token"}, 401) 34 | 35 | token = parts[1] 36 | return token 37 | 38 | def requires_auth(f): 39 | """Determines if the access token is valid 40 | """ 41 | @wraps(f) 42 | def decorated(*args, **kwargs): 43 | token = get_token_auth_header() 44 | valid_token = os.environ.get('AUTHTOKEN', 'krishna') 45 | print (valid_token) 46 | print (token) 47 | if (token != valid_token): 48 | raise AuthError({"code": "invalid_token", 49 | "description": "invalid token"}, 401) 50 | return f(*args, **kwargs) 51 | return decorated 52 | 53 | -------------------------------------------------------------------------------- /apps/src/product/app/auth/auth.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from flask import request, make_response, jsonify 3 | import os 4 | 5 | class AuthError(Exception): 6 | def __init__(self, error, status_code): 7 | self.error = error 8 | self.status_code = status_code 9 | 10 | def get_token_auth_header(): 11 | """Obtains the access token from the Authorization Header 12 | """ 13 | auth = request.headers.get("Authorization", None) 14 | if not auth: 15 | raise AuthError({"code": "authorization_header_missing", 16 | "description": 17 | "Authorization header is expected"}, 401) 18 | 19 | parts = auth.split() 20 | 21 | if parts[0].lower() != "bearer": 22 | raise AuthError({"code": "invalid_header", 23 | "description": 24 | "Authorization header must start with" 25 | " Bearer"}, 401) 26 | elif len(parts) == 1: 27 | raise AuthError({"code": "invalid_header", 28 | "description": "Token not found"}, 401) 29 | elif len(parts) > 2: 30 | raise AuthError({"code": "invalid_header", 31 | "description": 32 | "Authorization header must be" 33 | " Bearer token"}, 401) 34 | 35 | token = parts[1] 36 | return token 37 | 38 | def requires_auth(f): 39 | """Determines if the access token is valid 40 | """ 41 | @wraps(f) 42 | def decorated(*args, **kwargs): 43 | token = get_token_auth_header() 44 | valid_token = os.environ.get('AUTHTOKEN', 'krishna') 45 | print (valid_token) 46 | print (token) 47 | if (token != valid_token): 48 | raise AuthError({"code": "invalid_token", 49 | "description": "invalid token"}, 401) 50 | return f(*args, **kwargs) 51 | return decorated 52 | 53 | -------------------------------------------------------------------------------- /apps/src/webapp/app/auth/auth.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, Flask , jsonify, render_template, session, request, redirect, url_for 2 | from app.models import User 3 | from .forms import RegistrationForm, LoginForm 4 | 5 | auth_bp = Blueprint("auth_bp", __name__, template_folder="templates/auth") 6 | 7 | @auth_bp.route("/login", methods=["GET", "POST"]) 8 | def main(): 9 | if request.method == "POST": 10 | form = LoginForm() 11 | user= User() 12 | email = request.form['email'] 13 | password = request.form['password'] 14 | result = user.verify(email, password) 15 | if result == True: 16 | session['email'] = email 17 | return redirect(url_for("general_bp.home")) 18 | return render_template("login.html", title="Login") 19 | 20 | @auth_bp.route("/register", methods=["GET","POST"]) 21 | def signup(): 22 | if request.method == "POST": 23 | form = LoginForm() 24 | user= User() 25 | fname = request.form['fname'] 26 | lname = request.form['lname'] 27 | email = request.form['email'] 28 | password = request.form['password'] 29 | user.add(fname,lname, email, password) 30 | return redirect(url_for("auth_bp.main")) 31 | else: 32 | return render_template("signup.html", title ="register") 33 | 34 | @auth_bp.route("/forgot_password", methods=["GET", "POST"]) 35 | def forgot_pass(): 36 | return render_template("forgot_password.html", title="forgot password") 37 | 38 | @auth_bp.route("/logout") 39 | def logout(): 40 | session.pop('user', None) 41 | session.pop('email', None) 42 | session.clear() 43 | #return redirect("/") 44 | return redirect(url_for("general_bp.home")) 45 | 46 | @auth_bp.route("/account") 47 | def account(): 48 | if session.get('email'): 49 | user = User() 50 | print (user.get(session['email'])) 51 | result = [dict(p) for p in user.get(session['email'])] 52 | return render_template("account.html", title="Account", acctinfo=result) 53 | else: 54 | return redirect(url_for("general_bp.home")) 55 | -------------------------------------------------------------------------------- /apps/src/webapp/app/cart/templates/cart/cart.html.save: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Your Cart 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 | {% if 'email' not in session: %} 19 |
20 | Sign In 21 |
22 | {% else %} 23 | 32 | {% endif %} 33 | 39 |
40 |
41 |

Shopping Cart

42 |
43 | {% for row in products %} 44 |
45 |
46 |
47 | 48 |
49 |
50 | {{row['name']}}
51 | In stock
52 | Remove 53 |
54 |
55 | ${{row['price']}} 56 |
57 |
58 | {% endfor %} 59 |
60 |
61 | Subtotal : ${{totalPrice}} 62 |
63 |
64 |
65 | Proceed to checkout 66 | 67 | 68 | -------------------------------------------------------------------------------- /apps/src/order/app/schema/models.py: -------------------------------------------------------------------------------- 1 | import psycopg2 2 | from psycopg2.extras import RealDictCursor 3 | import os 4 | 5 | 6 | def connect(): 7 | rds_host = os.environ['DATABASE_HOST'] 8 | db_user = os.environ['DATABASE_USER'] 9 | password = os.environ['DATABASE_PASSWORD'] 10 | db_name = os.environ['DATABASE_DB_NAME'] 11 | port = os.environ['DATABASE_PORT'] 12 | 13 | return psycopg2.connect(sslmode="require", host=rds_host, user=db_user, password=password, dbname=db_name, connect_timeout=10000, port=port, keepalives_interval=30) 14 | 15 | class Order: 16 | def __init__(self, email): 17 | self.email = email 18 | 19 | def get_orders(self, order_id=None): 20 | with connect() as dbconn: 21 | with dbconn.cursor(cursor_factory=RealDictCursor) as cur: 22 | sqlstmt = "select d.item_id, d.qty, d.unit_price, o.order_date from order_details d join orders o on o.order_id = d.order_id and o.email = '{}'".format(self.email) 23 | if order_id: 24 | sqlstmt = "{} where o.order_id = {}".format(sqlstmt, order_id) 25 | print (sqlstmt) 26 | cur.execute(sqlstmt) 27 | return cur.fetchall() 28 | 29 | def add(self, data): 30 | with connect() as dbconn: 31 | with dbconn.cursor(cursor_factory=RealDictCursor) as cur: 32 | sqlstmt = "select nextval('order_seq');" 33 | cur.execute(sqlstmt) 34 | order_id = cur.fetchone()['nextval'] 35 | items = data.get('items') 36 | total = 0 37 | for x in items: 38 | sqlstmt = 'insert into order_details(order_id, item_id, qty, unit_price) values({}, {}, {}, {});'.format(order_id, x.get('item_id'), x.get('qty'), x.get('unit_price')) 39 | cur.execute(sqlstmt) 40 | total += (x.get('qty') * x.get('unit_price')) 41 | sqlstmt = "insert into orders(order_id, order_date, order_total, email) values({}, now(), {}, '{}')".format(order_id, total, self.email) 42 | cur.execute(sqlstmt) 43 | dbconn.commit() 44 | return order_id 45 | -------------------------------------------------------------------------------- /apps/src/user/app/schema/models.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from rediscluster import RedisCluster 4 | 5 | class User: 6 | def __init__(self): 7 | memdbhost = os.environ.get('MEMDB_HOST') 8 | memdbport = os.environ.get('MEMDB_PORT') 9 | memdbuser = os.environ.get('MEMDB_USER') 10 | memdbpass = os.environ.get('MEMDB_PASS') 11 | print ("{} - {}".format(memdbhost, memdbport)) 12 | if memdbuser and memdbpass: 13 | redis = RedisCluster(startup_nodes=[{"host": memdbhost, "port": memdbport}], 14 | decode_responses=True, skip_full_coverage_check=True, ssl=True, username=memdbuser, password=memdbpass) 15 | elif memdbpass: 16 | redis = RedisCluster(startup_nodes=[{"host": memdbhost, "port": memdbport}], 17 | decode_responses=True, skip_full_coverage_check=True, ssl=True, password=memdbpass) 18 | else: 19 | redis = RedisCluster(startup_nodes=[{"host": memdbhost, "port": memdbport}], 20 | decode_responses=True, skip_full_coverage_check=True, ssl=True) 21 | self.redis = redis 22 | self.user = None 23 | self.email = None 24 | 25 | def add(self, fname, lname, email, password): 26 | data = {'fname': fname, 'lname': lname, 'email': email, 'password': password} 27 | key = 'user:email:{}'.format(email) 28 | self.redis.hset(key, mapping=data) 29 | self.user = lname + ", " + fname 30 | self.email = email 31 | data['id'] = key 32 | return data 33 | 34 | def get(self, email): 35 | key = 'user:email:{}'.format(email) 36 | data = self.redis.hgetall(key) 37 | print (data) 38 | return [ data ] 39 | 40 | def verify(self, email ,password): 41 | key = 'user:email:{}'.format(email) 42 | data = self.redis.hgetall(key) 43 | print ('in verify') 44 | print (data) 45 | print (type(data)) 46 | row_count = len(data) 47 | if not data: 48 | return False 49 | if not data.get('password'): 50 | return False 51 | if data.get('password') == password: 52 | return True 53 | return False 54 | 55 | -------------------------------------------------------------------------------- /apps/src/kart/app/schema/models.py: -------------------------------------------------------------------------------- 1 | import psycopg2 2 | from psycopg2.extras import RealDictCursor 3 | import os 4 | import json 5 | 6 | 7 | def connect(role="writer"): 8 | rds_host = os.environ['DATABASE_HOST'] 9 | db_user = os.environ['DATABASE_USER'] 10 | password = os.environ['DATABASE_PASSWORD'] 11 | db_name = os.environ['DATABASE_DB_NAME'] 12 | port = os.environ['DATABASE_PORT'] 13 | print ("x-{}-x-{}-x-{}-x-{}".format(rds_host, db_user, db_name, port)) 14 | 15 | return psycopg2.connect(sslmode="require", host=rds_host, user=db_user, password=password, dbname=db_name, connect_timeout=10000, port=port, keepalives_interval=30) 16 | 17 | class Kart: 18 | def __init__(self): 19 | pass 20 | 21 | def set(self, key, value): 22 | with connect("writer") as dbconn: 23 | with dbconn.cursor(cursor_factory=RealDictCursor) as cur: 24 | try: 25 | print ("in Kart inserting values {}, {}".format(key, value)) 26 | sqlstmt = "insert into SessionStore(key, value) values(%s, %s) on conflict(key) do update set value = EXCLUDED.value" 27 | cur.execute(sqlstmt, (key, json.dumps(value),)) 28 | dbconn.commit() 29 | msg = "Added successfully KRISHNA KRISHNA KRISHNA" 30 | except Exception as e: 31 | print (e) 32 | dbconn.rollback() 33 | msg = "Error Occurred" 34 | 35 | def get(self, key): 36 | print ("Extracting kart info for kart item {}".format(key)) 37 | sqlstmt = "select value from SessionStore where key='{}'".format(key) 38 | with connect("reader") as dbconn: 39 | with dbconn.cursor(cursor_factory=RealDictCursor) as cur: 40 | cur.execute(sqlstmt) 41 | r = cur.fetchone() 42 | print (r) 43 | return dict(r).get('value') if r else [] 44 | 45 | def delete(self, key): 46 | sqlstmt = "delete from SessionStore where key='{}'".format(key) 47 | with connect("writer") as dbconn: 48 | with dbconn.cursor(cursor_factory=RealDictCursor) as cur: 49 | cur.execute(sqlstmt) 50 | dbconn.commit() 51 | -------------------------------------------------------------------------------- /apps/src/webapp/app/products/products.py: -------------------------------------------------------------------------------- 1 | from flask import request, Flask , Blueprint , render_template, jsonify, request, abort ,redirect, url_for 2 | import requests 3 | from app.models import Product 4 | products_bp = Blueprint("products_bp", __name__, template_folder="templates/products") 5 | 6 | 7 | @products_bp.route("/") 8 | def main(product): 9 | product_name = product 10 | product = Product(product) 11 | response = product.return_items() 12 | if response.status_code != 200: 13 | abort(401) 14 | print (response) 15 | product_items = response.json().get('product_items') 16 | page = int(request.args.get("page") or 1) 17 | if len(product_items) <= 8: 18 | page = 1 19 | previous = page - 1 20 | start = previous * 8 21 | end = start + 8 22 | 23 | if product_items is None: 24 | abort(404) 25 | else: 26 | print (product_items) 27 | product_items= [dict(p) for p in product_items] 28 | print (product_items) 29 | return render_template("list.html", 30 | products= product_items[start:end], 31 | title=product_name , 32 | length=len(product_items)) 33 | 34 | @products_bp.route("//") 35 | def view_product(product, product_item): 36 | product = Product(product) 37 | response = product.return_items() 38 | if response.status_code != 200: 39 | abort(401) 40 | product_items = response.json().get('product_items') 41 | product_items = [dict(p) for p in product_items] 42 | product_name = [p for p in product_items if p['name'].lower() == product_item.lower()] 43 | if len(product_name) == 0: 44 | abort(404) 45 | else: 46 | return render_template("view.html", 47 | results={"item":product_name, 48 | "keyword":product_item}, 49 | title=product_item) 50 | 51 | @products_bp.route("/view") 52 | def view(): 53 | id = int(request.args.get("id")) 54 | product = Product() 55 | response = product.product.show_all_items() 56 | if response.status_code != 200: 57 | abort(401) 58 | product_items = response.json().get('product_items') 59 | product_items = [dict(p) for p in product_items if p['id'] == id] 60 | return render_template("view.html", 61 | results={"item":product_items}, 62 | title="Product View" 63 | ) 64 | 65 | @products_bp.route("/whereami") 66 | def whereami(): 67 | return Product().whereami 68 | 69 | -------------------------------------------------------------------------------- /apps/production/aurora-pg.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: aurora-pg-password 5 | namespace: retailapp 6 | type: Opaque 7 | data: 8 | password: cG9zdGdyZXM= 9 | --- 10 | apiVersion: rds.services.k8s.aws/v1alpha1 11 | kind: DBCluster 12 | metadata: 13 | name: ack-db 14 | namespace: retailapp 15 | spec: 16 | backupRetentionPeriod: 7 17 | serverlessV2ScalingConfiguration: 18 | maxCapacity: 4 19 | minCapacity: 0.5 20 | dbClusterIdentifier: ack-db 21 | dbSubnetGroupName: 22 | engine: aurora-postgresql 23 | engineVersion: "13" 24 | masterUsername: adminer 25 | masterUserPassword: 26 | namespace: retailapp 27 | name: aurora-pg-password 28 | key: password 29 | vpcSecurityGroupIDs: 30 | - 31 | --- 32 | apiVersion: rds.services.k8s.aws/v1alpha1 33 | kind: DBInstance 34 | metadata: 35 | name: ack-db-instance01 36 | namespace: retailapp 37 | spec: 38 | dbInstanceClass: db.serverless 39 | dbInstanceIdentifier: ack-db-instance01 40 | dbClusterIdentifier: ack-db 41 | dbSubnetGroupName: 42 | engine: aurora-postgresql 43 | engineVersion: "13" 44 | publiclyAccessible: false 45 | --- 46 | apiVersion: v1 47 | kind: ConfigMap 48 | metadata: 49 | name: asv2-db-instance-conn-cm 50 | namespace: retailapp 51 | data: {} 52 | --- 53 | apiVersion: services.k8s.aws/v1alpha1 54 | kind: FieldExport 55 | metadata: 56 | name: ack-db-instance01-host 57 | namespace: retailapp 58 | spec: 59 | to: 60 | name: asv2-db-instance-conn-cm 61 | kind: configmap 62 | from: 63 | path: ".status.endpoint.address" 64 | resource: 65 | group: rds.services.k8s.aws 66 | kind: DBInstance 67 | name: ack-db-instance01 68 | --- 69 | apiVersion: services.k8s.aws/v1alpha1 70 | kind: FieldExport 71 | metadata: 72 | name: ack-db-instance01-port 73 | namespace: retailapp 74 | spec: 75 | to: 76 | name: asv2-db-instance-conn-cm 77 | kind: configmap 78 | from: 79 | path: ".status.endpoint.port" 80 | resource: 81 | group: rds.services.k8s.aws 82 | kind: DBInstance 83 | name: ack-db-instance01 84 | --- 85 | apiVersion: services.k8s.aws/v1alpha1 86 | kind: FieldExport 87 | metadata: 88 | name: ack-db-instance01-user 89 | namespace: retailapp 90 | spec: 91 | to: 92 | name: asv2-db-instance-conn-cm 93 | kind: configmap 94 | from: 95 | path: ".spec.masterUsername" 96 | resource: 97 | group: rds.services.k8s.aws 98 | kind: DBInstance 99 | name: ack-db-instance01 100 | -------------------------------------------------------------------------------- /apps/src/user/app/schema/models.py.PostgreSQL: -------------------------------------------------------------------------------- 1 | import psycopg2 2 | from psycopg2.extras import RealDictCursor 3 | import os 4 | 5 | 6 | def connect(): 7 | rds_host = os.environ['DATABASE_HOST'] 8 | db_user = os.environ['DATABASE_USER'] 9 | password = os.environ['DATABASE_PASSWORD'] 10 | db_name = os.environ['DATABASE_DB_NAME'] 11 | port = os.environ['DATABASE_PORT'] 12 | 13 | return psycopg2.connect(sslmode="require", host=rds_host, user=db_user, password=password, dbname=db_name, connect_timeout=10000, port=port, keepalives_interval=30) 14 | 15 | 16 | class User: 17 | def __init__(self, db=connect()): 18 | try: 19 | self.cursor = db.cursor(cursor_factory=RealDictCursor) 20 | except: 21 | db=connect() 22 | self.cursor = db.cursor(cursor_factory=RealDictCursor) 23 | self.db = db 24 | self.user = None 25 | self.email = None 26 | 27 | def add(self, fname, lname, email, password): 28 | sql = f"INSERT INTO Users(fname, lname, email, password) VALUES(%s,%s,%s,%s);" 29 | data=(fname, lname, email, password) 30 | try: 31 | cur = self.db.cursor(cursor_factory=RealDictCursor) 32 | cur.execute(sql, data) 33 | except: 34 | self.db=connect() 35 | self.cursor = self.db.cursor(cursor_factory=RealDictCursor) 36 | cur = self.cursor 37 | cur.execute(sql, data) 38 | self.db.commit() 39 | cur.close() 40 | self.user = lname + ", " + fname 41 | self.email = email 42 | 43 | def get(self, email): 44 | sql = "select fname, lname, email, id from Users where email='{}'".format(email) 45 | try: 46 | cur = self.db.cursor(cursor_factory=RealDictCursor) 47 | cur.execute(sql) 48 | except: 49 | self.db=connect() 50 | self.cursor = self.db.cursor(cursor_factory=RealDictCursor) 51 | cur = self.cursor 52 | cur.execute(sql) 53 | return cur.fetchall() 54 | 55 | def verify(self, email ,password): 56 | sql = "SELECT email, password FROM Users WHERE email='{}' AND password='{}'".format(email, password) 57 | try: 58 | cur = self.db.cursor(cursor_factory=RealDictCursor) 59 | cur.execute(sql) 60 | except: 61 | self.db=connect() 62 | self.cursor = self.db.cursor(cursor_factory=RealDictCursor) 63 | cur = self.cursor 64 | cur.execute(sql) 65 | result = cur.fetchall() 66 | self.db.commit() 67 | cur.close() 68 | row_count = len(result) 69 | if row_count == 1 : 70 | return True 71 | else: 72 | return False 73 | 74 | -------------------------------------------------------------------------------- /apps/src/webapp/app/cart/templates/cart/cart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Your Cart 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 | {% if 'email' not in session: %} 19 |
20 | Sign In 21 |
22 | {% else %} 23 | 33 | {% endif %} 34 | 40 |
41 |
42 |

Shopping Cart

43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | {% for row in products %} 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {% endfor %} 61 |
{{row['name']}}Qty: {{row['qty']}}${{row['price']}}In stockRemove
62 |

63 |
64 | Subtotal : ${{totalPrice}} 65 |
66 |
67 |
68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /apps/src/webapp/app/products/templates/products/view.html: -------------------------------------------------------------------------------- 1 | {% include "base.html" %} 2 | 3 | 4 | 5 |
6 |
7 |
8 | 15 | 68 |
69 |
70 | 71 | 72 |
73 |
74 | -------------------------------------------------------------------------------- /apps/src/webapp/app/cart/cart.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, session, render_template, jsonify, request, redirect, url_for 2 | from flask_images import resized_img_src 3 | from app.models import Product, Kart 4 | import json 5 | 6 | cart_bp = Blueprint("cart_bp", __name__, template_folder = "templates") 7 | 8 | @cart_bp.route("/addToCart") 9 | def addToCart(): 10 | if not session or (session and 'email' not in session): 11 | return redirect(url_for('auth_bp.main')) 12 | else: 13 | productId = int(request.args.get('productId')) 14 | qty = request.args.get('qty').strip() 15 | if not qty: 16 | qty = 1 17 | qty = int(qty) 18 | kartSession = Kart(session['email']) 19 | cartList = kartSession.get('Kart') or [] 20 | found = False 21 | for x in cartList: 22 | if x.get('productId') == productId: 23 | found = True 24 | x['qty'] = qty 25 | break 26 | if not found: 27 | cartList.append({'productId': productId, 'qty': qty}) 28 | kartSession.set('Kart', cartList) 29 | #return redirect(url_for('products_bp.view') + "?id={}".format(productId)) 30 | return redirect(url_for('cart_bp.cart')) 31 | 32 | @cart_bp.route("/removeFromCart") 33 | def removeFromCart(): 34 | if 'email' not in session: 35 | return redirect(url_for('auth_bp.home')) 36 | email = session['email'] 37 | productId = int(request.args.get('productId')) 38 | kartSession = Kart(email) 39 | cartList = kartSession.get('Kart') or [] 40 | for x in cartList: 41 | if x.get('productId') == productId: 42 | if x['qty'] > 1: 43 | x['qty'] = x['qty'] - 1 44 | else: 45 | # remove array element 46 | cartList.remove(x) 47 | break 48 | kartSession.set('Kart', cartList) 49 | return redirect(url_for('cart_bp.cart')) 50 | 51 | @cart_bp.route("/view") 52 | def main(): 53 | return render_template("cart/view.html") 54 | 55 | @cart_bp.route("/cart") 56 | def cart(): 57 | if 'email' not in session: 58 | return redirect(url_for('auth_bp.main')) 59 | email = session['email'] 60 | kartSession = Kart(email) 61 | productsList = kartSession.get('Kart') or [] 62 | if not productsList: 63 | return redirect(url_for('general_bp.home')) 64 | productIdList = [ x.get('productId') for x in productsList ] 65 | response = Product().getProducts(productIdList) 66 | if response.status_code != 200: 67 | abort(401) 68 | products = response.json().get('products') 69 | for product in products: 70 | kartval = [ x['qty'] for x in productsList if x['productId'] == product['productid'] ] 71 | product['qty'] = kartval and kartval[0] or 0 72 | totalPrice = 0 73 | noOfItems = 0 74 | for row in products: 75 | totalPrice += row.get('price') * row.get('qty') 76 | noOfItems += 1 77 | return render_template("cart/cart.html", products = products, totalPrice=round(totalPrice,2), loggedIn=True, firstName=email, noOfItems=noOfItems) 78 | 79 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /apps/src/webapp/app/products/templates/products/list.html: -------------------------------------------------------------------------------- 1 | {% include "base.html" %} 2 | 3 |
4 |
5 | 6 |
7 |
Products/{{title.capitalize()}}
8 |
9 | 10 | 11 |
12 | {% for i in range(products|length//4+2):%} 13 |
14 |
15 | 16 |
17 | {{products[i]['name'].capitalize()}} 18 |
19 |
    20 |
  • 21 | 22 |
  • 23 |
  • 24 | 25 |
  • 26 |
27 | 34 reviws 28 |
29 |
{{products[i]['price']}}
30 |
31 |
32 |
33 | {% endfor %} 34 |
35 | 36 |
37 | {% for i in range(products|length//4 +2 , products|length):%} 38 |
39 |
40 | 41 |
42 | {{products[i]['name'].capitalize()}} 43 | 44 |
45 |
    46 |
  • 47 | 48 |
  • 49 |
  • 50 | 51 |
  • 52 |
53 | 34 reviws 54 |
55 |
{{products[i]['price']}}
56 |
57 |
58 |
59 | {% endfor %} 60 |
61 |
62 |
63 | 64 | 80 | 81 | -------------------------------------------------------------------------------- /apps/src/product/app/products/products.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, Blueprint, jsonify, request, abort ,redirect, url_for 2 | from app.schema.models import Product 3 | from app.auth.auth import requires_auth 4 | 5 | products_bp = Blueprint("products_bp", __name__) 6 | 7 | # @cross_origin(headers=['Content-Type', 'Authorization']) 8 | # @requires_auth 9 | 10 | @products_bp.route("/popularitems") 11 | def popular_items(): 12 | top = request.args.get("top", 5) and int(request.args.get("top", 5)) or None 13 | #interval = request.args.get("interval", 5) and int(request.args.get("interval", 5)) or None 14 | product = Product() 15 | product_items = product.popular_items(top) 16 | print (product_items) 17 | return jsonify({'title': 'Popular Products', 'product_items': product_items}) 18 | 19 | @products_bp.route("/") 20 | def main(product): 21 | product_name = product 22 | product = Product(product) 23 | product_items = product.return_items() 24 | id = request.args.get("id") and int(request.args.get("id")) or None 25 | 26 | if product_items is None: 27 | abort(404) 28 | else: 29 | if id: 30 | product_items = [dict(p) for p in product_items if p['id'] == id] 31 | else: 32 | product_items= [dict(p) for p in product_items] 33 | return jsonify({'title': product_name, 'product_items': product_items}) 34 | 35 | @products_bp.route("//") 36 | def view_product(product, product_item): 37 | product = Product(product) 38 | product_items = product.return_items() 39 | product_items = [dict(p) for p in product.return_items()] 40 | product_name = [p for p in product_items if p['name'].lower() == product_item.lower()] 41 | if len(product_name) == 0: 42 | abort(404) 43 | else: 44 | return jsonify({'title': product_item, 'product_items': product_items}) 45 | 46 | def get_product(product, id): 47 | product_items = product.show_all_items_new(id) 48 | product_items = [dict(p) for p in product_items] 49 | return jsonify({'title': 'Product View', 'product_items': product_items}) 50 | 51 | @products_bp.route("/view") 52 | def view(): 53 | id = request.args.get("id") and int(request.args.get("id")) or None 54 | product = Product() 55 | try: 56 | return get_product(product, id) 57 | except Exception as e: 58 | try: 59 | return get_product(product, id) 60 | except Exception as e: 61 | print ("EXCEPTION: {}".format(e)) 62 | abort(500) 63 | 64 | @products_bp.route("/getproducts") 65 | def getProducts(): 66 | productlist = request.args.get("productlist") 67 | print (productlist) 68 | product = Product() 69 | try: 70 | products = product.getProducts(productlist) 71 | except Exception as e: 72 | try: 73 | products = product.getProducts(productlist) 74 | except Exception as e: 75 | print ("EXCEPTION: {}".format(e)) 76 | abort(500) 77 | return jsonify({'title': 'Products List', 'products': products}) 78 | 79 | @products_bp.route("//add") 80 | def addProduct(): 81 | product = request.args.get("product") 82 | print (product) 83 | product = Product() 84 | try: 85 | result = product.addProduct(category, product) 86 | except Exception as e: 87 | try: 88 | result = product.addProduct(category, product) 89 | except Exception as e: 90 | print ("EXCEPTION: {}".format(e)) 91 | abort(500) 92 | return jsonify({'title': 'Product Add', 'category': category, 'product': result}) 93 | -------------------------------------------------------------------------------- /apps/src/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | redis-0: 6 | image: redis:latest 7 | restart: always 8 | ports: 9 | - 6379:6379 10 | command: ["redis-server", "--appendonly yes", "--cluster-enabled yes", "--loglevel verbose"] 11 | environment: 12 | - ALLOW_EMPTY_PASSWORD=yes 13 | volumes: 14 | - ./redisdata0:/data 15 | 16 | redis-1: 17 | image: redis:latest 18 | restart: always 19 | ports: 20 | - 6380:6379 21 | command: ["redis-server", "--appendonly yes", "--cluster-enabled yes", "--loglevel verbose"] 22 | environment: 23 | - ALLOW_EMPTY_PASSWORD=yes 24 | volumes: 25 | - ./redisdata1:/data 26 | 27 | postgresql: 28 | image: postgres:14 29 | ports: 30 | - 5432:5432 31 | environment: 32 | - POSTGRES_USER=postgres 33 | - POSTGRES_PASSWORD=postgres 34 | - PGDATA=/pgdata 35 | volumes: 36 | - ./setup_schema.sql:/docker-entrypoint-initdb.d/init.sql 37 | - ./pgdata:/pgdata 38 | 39 | webapp: 40 | depends_on: 41 | - postgresql 42 | build: 43 | context: ./webapp 44 | dockerfile: Dockerfile 45 | image: ack-rds-gitops-workshop/webapp:2.0 46 | ports: 47 | - 8443:8443 48 | volumes: 49 | - ./appdata:/appdata 50 | environment: 51 | - DATABASE_HOST=host.docker.internal 52 | - DATABASE_RO_HOST=host.docker.internal 53 | - ECHOST=host.docker.internal:11211 54 | - DATABASE_PORT=5432 55 | - DATABASE_USER=dbuser1 56 | - DATABASE_PASSWORD=eksackdemo 57 | - DATABASE_DB_NAME=eksackdemo 58 | - PRODUCTS_SERVICE=http://host.docker.internal:8444 59 | - USER_SERVICE=http://host.docker.internal:8446 60 | - ORDER_SERVICE=http://host.docker.internal:8448 61 | - KART_SERVICE=http://host.docker.internal:8445 62 | 63 | product: 64 | depends_on: 65 | - postgresql 66 | build: 67 | context: ./product 68 | dockerfile: Dockerfile 69 | image: ack-rds-gitops-workshop/product:2.0 70 | ports: 71 | - 8444:8444 72 | environment: 73 | - DATABASE_HOST=host.docker.internal 74 | - DATABASE_RO_HOST=host.docker.internal 75 | - DATABASE_PORT=5432 76 | - DATABASE_USER=dbuser1 77 | - DATABASE_PASSWORD=eksackdemo 78 | - DATABASE_DB_NAME=eksackdemo 79 | 80 | user: 81 | depends_on: 82 | - postgresql 83 | build: 84 | context: ./user 85 | dockerfile: Dockerfile 86 | image: ack-rds-gitops-workshop/user:2.0 87 | ports: 88 | - 8446:8446 89 | environment: 90 | - DATABASE_HOST=host.docker.internal 91 | - DATABASE_RO_HOST=host.docker.internal 92 | - DATABASE_PORT=5432 93 | - DATABASE_USER=dbuser1 94 | - DATABASE_PASSWORD=eksackdemo 95 | - DATABASE_DB_NAME=eksackdemo 96 | - MEMDB_HOST=host.docker.internal 97 | - MEMDB_PORT=6379 98 | - MEMDB_PASS=krishna 99 | 100 | order: 101 | depends_on: 102 | - postgresql 103 | build: 104 | context: ./order 105 | dockerfile: Dockerfile 106 | image: ack-rds-gitops-workshop/order:2.0 107 | ports: 108 | - 8448:8448 109 | environment: 110 | - DATABASE_HOST=host.docker.internal 111 | - DATABASE_RO_HOST=host.docker.internal 112 | - DATABASE_PORT=5432 113 | - DATABASE_USER=dbuser1 114 | - DATABASE_PASSWORD=eksackdemo 115 | - DATABASE_DB_NAME=eksackdemo 116 | 117 | kart: 118 | depends_on: 119 | - postgresql 120 | build: 121 | context: ./kart 122 | dockerfile: Dockerfile 123 | image: ack-rds-gitops-workshop/kart:2.0 124 | ports: 125 | - 8445:8445 126 | environment: 127 | - DATABASE_HOST=host.docker.internal 128 | - DATABASE_RO_HOST=host.docker.internal 129 | - DATABASE_PORT=5432 130 | - DATABASE_USER=dbuser1 131 | - DATABASE_PASSWORD=eksackdemo 132 | - DATABASE_DB_NAME=eksackdemo 133 | -------------------------------------------------------------------------------- /apps/src/webapp/app/auth/templates/auth/signup.html: -------------------------------------------------------------------------------- 1 | {% include "base.html"%} 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 |
11 |
12 |

Sign up

13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 | 26 | 27 | We'll never share your email with anyone else. 28 |
29 |
30 | 34 | 38 |
39 |
40 |
41 | 42 | 43 |
44 |
45 | 46 | 54 |
55 |
56 |
57 |
58 | 59 | 60 |
61 |
62 | 63 | 64 |
65 |
66 |
67 | 68 |
69 |
70 | 71 |
72 |
73 |
74 |
75 |

Have an account? Log In

76 |

77 | 78 | 79 | 80 |
-------------------------------------------------------------------------------- /apps/src/webapp/app/general/templates/general/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {{title.capitalize()}} |-reInvent workshop- Ecommerce Site 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 72 | 73 | 74 | 75 | 76 | 77 |
78 | 79 | 80 |
81 |
82 |
83 | 89 |
90 | 101 | 102 | 103 |
104 |
105 |
106 |
107 | 108 | 109 | 110 | 111 |
112 |
113 |
114 |
115 | 116 | {{ noOfItems }} 117 |
118 |
119 | 120 |
121 | Welcome! 122 | {% if not 'email' in session: %} 123 |
124 | Sign in | 125 | Register 126 |
127 | {%else%} 128 |
129 | {{session['email']}} | 130 | Sign Out 131 |
132 | {% endif %} 133 | 134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | 143 | 144 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /apps/src/webapp/app/models.py: -------------------------------------------------------------------------------- 1 | import requests, sys, json, os 2 | from functools import wraps 3 | import time 4 | 5 | _CONNECT_FAILURE_MAX_RETRIES = 3 # 500 errors retries 6 | _CONNECT_FAILURE_BACKOFF = 0.5 # 500 errors retry backoff factor 7 | _CONNECT_TIMEOUT = 3 # connect timeout 8 | _READ_TIMEOUT = 300 # read timeout 9 | 10 | class ModelsApiException(Exception): 11 | """exceptions raised by PatroniApi class""" 12 | def __init__(self, message, errors=None): 13 | if errors is not None: 14 | super(ModelsApiException, self).__init__(message + (': "{0!r}"'.format(errors))) 15 | self.errors = errors 16 | else: 17 | super(ModelsApiException, self).__init__(message) 18 | 19 | 20 | class ModelsApi(object): 21 | def __init__(self, api_url=None): 22 | self._session = requests.Session() 23 | self.api_url = api_url 24 | 25 | def _retry(exc_to_check, tries=_CONNECT_FAILURE_MAX_RETRIES, delay=3, backoff=1): 26 | def deco_retry(func): 27 | @wraps(func) 28 | def f_retry(*args, **kwargs): 29 | mtries, mdelay = tries, delay 30 | while mtries > 1: 31 | try: 32 | return func(*args, **kwargs) 33 | except exc_to_check: 34 | time.sleep(mdelay) 35 | mtries -= 1 36 | mdelay *= backoff 37 | return func(*args, **kwargs) 38 | return f_retry # true decorator 39 | return deco_retry 40 | 41 | @_retry((ModelsApiException, requests.exceptions.ConnectionError), tries=3, delay=30) 42 | def _get(self, urlpath, **kwargs): 43 | """ requsts get 44 | """ 45 | print ('Calling API, {}/{}'.format(self.api_url, urlpath)) 46 | response = self._session.get("{}/{}".format(self.api_url, urlpath), timeout=(_CONNECT_TIMEOUT, _READ_TIMEOUT), **kwargs) 47 | print (response) 48 | self._raise_on_error(response, sys._getframe().f_code.co_name) 49 | return response 50 | 51 | @_retry((ModelsApiException, requests.exceptions.ConnectionError), tries=3, delay=30) 52 | def _post(self, urlpath, data, **kwargs): 53 | """ requsts get 54 | """ 55 | print ('Calling API, {}/{}'.format(self.api_url, urlpath)) 56 | response = self._session.post("{}/{}".format(self.api_url, urlpath), data=json.dumps(data), timeout=(_CONNECT_TIMEOUT, _READ_TIMEOUT), **kwargs) 57 | print (response) 58 | self._raise_on_error(response, sys._getframe().f_code.co_name) 59 | return response 60 | 61 | def __enter__(self): 62 | return self 63 | 64 | def __exit__(self, exc_type, exc_value, traceback): 65 | try: 66 | self._session.close() 67 | except: 68 | pass 69 | 70 | def _raise_on_error(self, response, func_passed): 71 | """parse requests error if any""" 72 | if not 200 <= response.status_code <= 299: 73 | err_message = '' 74 | if response.text: 75 | try: 76 | err_dict = json.loads(response.text) 77 | err_message = self._parse_dict(err_dict) 78 | except ValueError: 79 | err_message = response.text 80 | raise ModelsApiException('HTTP {0}: "{1}"\n{2}'.format(response.status_code, err_message, response.url), errors=response) 81 | 82 | def _parse_dict(self, d): 83 | """helper function for parsing requests error""" 84 | for k, v in d.items(): 85 | if isinstance(v, dict): 86 | return '{0}: {1}'.format(k, self._parse_dict(v)) 87 | else: 88 | return '{0}: {1}'.format(k, v) 89 | 90 | class Product: 91 | def __init__(self, product_name=None): 92 | self.products_service = ModelsApi(os.environ.get('PRODUCTS_SERVICE') + "/products") 93 | self.product_name = product_name 94 | 95 | def whereami(self): 96 | return self.products_service._get('whereami') 97 | 98 | def return_items(self): 99 | return self.products_service._get(self.product_name) 100 | 101 | def popular_items(self, top=5): 102 | return self.products_service._get('popularitems?top={}'.format(top)) 103 | 104 | def show_all_items(self): 105 | return self.products_service._get('view') 106 | 107 | def getProducts(self, productListString): 108 | print (type(productListString)) 109 | print (productListString) 110 | if isinstance(productListString, list): 111 | productListString = ','.join([str(x) for x in productListString]) 112 | print (type(productListString)) 113 | print (productListString) 114 | return self.products_service._get('getproducts?productlist={}'.format(productListString)) 115 | 116 | class User: 117 | def __init__(self): 118 | self.user_service = ModelsApi(os.environ.get('USER_SERVICE') + "/user") 119 | self.user = None 120 | self.email = None 121 | 122 | def add(self, fname, lname, email, password): 123 | payload = {'fname': fname, 'lname': lname, 'email': email, 'password': password} 124 | response = self.user_service._post("add", payload) 125 | self.user = lname + ", " + fname 126 | self.email = email 127 | return response 128 | 129 | def get(self, email): 130 | response = self.user_service._get("getuser?email={}".format(email)) 131 | if response.status_code != 200: 132 | abort(401) 133 | return response.json().get('result') 134 | 135 | def verify(self, email ,password): 136 | response = self.user_service._get("verify?email={}&password={}".format(email, password)) 137 | if response.status_code != 200: 138 | return False 139 | return response.json().get('result') 140 | 141 | class Kart: 142 | def __init__(self, email): 143 | self.kart_service = ModelsApi(os.environ.get('KART_SERVICE') + "/kart") 144 | self.email = email 145 | 146 | def get(self, key): 147 | print (key) 148 | key = "{}:{}".format(self.email, key) 149 | response = self.kart_service._get('get?key={}'.format(key)) 150 | return response.json().get('value') 151 | 152 | def set(self, key, value): 153 | print (key) 154 | print (value) 155 | print (type(value)) 156 | key = "{}:{}".format(self.email, key) 157 | payload = {"key": key, "value": value} 158 | response = self.kart_service._post("set", payload) 159 | return response.json().get('value') 160 | 161 | class Review: 162 | def __init__(self): 163 | pass 164 | 165 | def __repr__(self): 166 | pass 167 | -------------------------------------------------------------------------------- /apps/src/webapp/app/general/templates/general/index.html: -------------------------------------------------------------------------------- 1 | {% include "base.html" %} 2 | 3 | 4 |
5 |
6 | 7 |
8 | 9 |
10 | 11 |
12 |
13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 | 26 |
27 |
Fast delivery
28 |
29 |
30 |
31 |
32 |
33 | 34 |
35 |
Creative Strategy
36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |
High secured
44 |
45 |
46 |
47 |
48 |
49 | 50 |
51 |
52 | 53 | 54 | 55 | 56 |
57 |
58 | 59 |
60 |

Popular products

61 |
62 | 63 | 64 |
65 | {% for product in products %} 66 |
67 |
68 | 69 |
70 | {{ product['name'] }} 71 | 72 |
73 |
    74 | {% if product['rating'] %} 75 |
  • 76 | {%else%} 77 |
  • 78 | {% endif %} 79 | 80 |
  • 81 |
  • 82 | 83 |
  • 84 |
85 | {% if product['rating'] %} 86 | {{ product['review_cnt'] }} reviews 87 | {%else%} 88 | {{ product['review_cnt'] }} reviews 89 | {% endif %} 90 |
91 |
${{ product['price'] }}
92 |
93 |
94 |
95 | {% endfor %} 96 |
97 | 98 |
99 |
100 | 101 | 102 | 103 | 104 | 105 |
106 |
107 | 108 |
109 |

New arrived

110 |
111 | 112 |
113 |
114 | 115 |
116 |
117 | 118 | 119 | 120 | 121 | 122 |
123 |
124 | 125 |
126 | See all 127 |

Recommended

128 |
129 | 130 |
131 |
132 | 133 |
134 |
135 | 136 | 137 | 138 |
139 |
140 |
141 |

Our Brands

142 |
143 | 144 |
145 | 146 |
147 | 151 |
152 | 153 |
154 |
155 |
156 | 157 | 158 | 159 | 160 | 161 |
162 |
163 | 164 |

Download reInvent Fashions app demo soon

165 | 166 | 167 | 168 |
169 |
170 | 171 | 172 | 173 | 174 | 175 | 176 |
177 |
178 | 179 | 194 |
195 |
196 | 197 | 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /apps/src/product/app/schema/models.py: -------------------------------------------------------------------------------- 1 | import psycopg2 2 | from psycopg2.extras import RealDictCursor 3 | import os 4 | 5 | 6 | def connect(role="writer"): 7 | rds_host = os.environ['DATABASE_HOST'] 8 | db_user = os.environ['DATABASE_USER'] 9 | password = os.environ['DATABASE_PASSWORD'] 10 | db_name = os.environ['DATABASE_DB_NAME'] 11 | port = os.environ['DATABASE_PORT'] 12 | 13 | return psycopg2.connect(sslmode="require", host=rds_host, user=db_user, password=password, dbname=db_name, connect_timeout=10000, port=port, keepalives_interval=30) 14 | 15 | class Product: 16 | def __init__(self, product_name=None): 17 | self.product_name = product_name 18 | self.db = connect() 19 | 20 | def fetch_data_new(self, sqlstmt): 21 | try: 22 | with self.db.cursor(cursor_factory=RealDictCursor) as cur: 23 | cur.execute(sqlstmt) 24 | return cur.fetchall() 25 | except Exception: 26 | with self.db.cursor(cursor_factory=RealDictCursor) as cur: 27 | cur.execute(sqlstmt) 28 | return cur.fetchall() 29 | 30 | def fetch_data(self, dbconn, sqlstmt): 31 | try: 32 | with dbconn.cursor(cursor_factory=RealDictCursor) as cur: 33 | cur.execute(sqlstmt) 34 | return cur.fetchall() 35 | except Exception: 36 | with connect() as dbconn: 37 | with dbconn.cursor(cursor_factory=RealDictCursor) as cur: 38 | cur.execute(sqlstmt) 39 | return cur.fetchall() 40 | 41 | def return_items(self): 42 | with connect() as dbconn: 43 | sqlstmt = "SELECT * FROM {}".format(self.product_name) 44 | return self.fetch_data(dbconn, sqlstmt) 45 | 46 | def popular_items(self, top=5, interval=180): 47 | with connect() as dbconn: 48 | sqlstmt = """ 49 | with items as ( 50 | select item_id 51 | from ( 52 | select item_id, cnt, 53 | rank() over (order by cnt desc) mrank 54 | from ( 55 | select item_id, count(1) as cnt 56 | from orders a join order_details b 57 | on a.order_id = b.order_id 58 | group by item_id 59 | ) t 60 | ) t where mrank <= {0} order by cnt desc 61 | ) 62 | SELECT id,name,price, description,img_url,'apparels' as category, count(1) review_cnt, round(avg(rating)*20) rating 63 | FROM apparels a join items i on i.item_id = a.id 64 | left outer join reviews r on r.category='apparels' and i.item_id = r.item_id 65 | GROUP BY id,name,price, description,img_url 66 | UNION 67 | SELECT id,name,price, description,img_url,'fashion' as category, count(1) review_cnt, round(avg(rating)*20) rating 68 | FROM fashion a join items i on i.item_id = a.id 69 | left outer join reviews r on r.category='fashion' and i.item_id = r.item_id 70 | GROUP BY id,name,price, description,img_url 71 | UNION 72 | SELECT id,name, price, description,img_url,'bicycles' as category, count(1) review_cnt, round(avg(rating)*20) rating 73 | FROM bicycles a join items i on i.item_id = a.id 74 | left outer join reviews r on r.category='bicycles' and i.item_id = r.item_id 75 | GROUP BY id,name,price, description,img_url 76 | UNION 77 | SELECT id,name, price, description,img_url,'jewelry' as category, count(1) review_cnt, round(avg(rating)*20) rating 78 | FROM jewelry a join items i on i.item_id = a.id 79 | left outer join reviews r on r.category='jewelry' and i.item_id = r.item_id 80 | GROUP BY id,name,price, description,img_url 81 | """.format(top) 82 | return self.fetch_data(dbconn, sqlstmt) 83 | 84 | def show_all_items(self, id=None): 85 | with connect() as dbconn: 86 | if id: 87 | sqlstmt = """ 88 | SELECT id,name,price, description,img_url FROM apparels where id = {0} 89 | UNION 90 | SELECT id,name,price, description,img_url FROM fashion where id = {0} 91 | UNION 92 | SELECT id,name, price, description,img_url FROM bicycles where id = {0} 93 | UNION 94 | SELECT id,name, price, description,img_url FROM jewelry where id = {0} 95 | ORDER BY name 96 | """.format(id) 97 | else: 98 | sqlstmt = """ 99 | SELECT id,name,price, description,img_url FROM apparels 100 | UNION 101 | SELECT id,name,price, description,img_url FROM fashion 102 | UNION 103 | SELECT id,name, price, description,img_url FROM bicycles 104 | UNION 105 | SELECT id,name, price, description,img_url FROM jewelry 106 | ORDER BY name 107 | """ 108 | return self.fetch_data(dbconn, sqlstmt) 109 | 110 | def show_all_items_new(self, id=None): 111 | if id: 112 | sqlstmt = """ 113 | SELECT id,name,price, description,img_url FROM apparels where id = {0} 114 | UNION 115 | SELECT id,name,price, description,img_url FROM fashion where id = {0} 116 | UNION 117 | SELECT id,name, price, description,img_url FROM bicycles where id = {0} 118 | UNION 119 | SELECT id,name, price, description,img_url FROM jewelry where id = {0} 120 | ORDER BY name 121 | """.format(id) 122 | else: 123 | sqlstmt = """ 124 | SELECT id,name,price, description,img_url FROM apparels 125 | UNION 126 | SELECT id,name,price, description,img_url FROM fashion 127 | UNION 128 | SELECT id,name, price, description,img_url FROM bicycles 129 | UNION 130 | SELECT id,name, price, description,img_url FROM jewelry 131 | ORDER BY name 132 | """ 133 | return self.fetch_data_new(sqlstmt) 134 | 135 | def getProducts(self, productListString): 136 | if isinstance(productListString, str): 137 | productListString=[productListString] 138 | elif isinstance(productListString, int): 139 | productListString=[productListString] 140 | print (productListString) 141 | if isinstance(productListString, list): 142 | productListString = ",".join(productListString) 143 | sqlstmt = """select id as productId, name, price, img_url from apparels a where id in ({0}) 144 | union 145 | select id as productId, name, price, img_url from fashion a where id in ({0}) 146 | union 147 | select id as productId, name, price, img_url from bicycles a where id in ({0}) 148 | union 149 | select id as productId, name, price, img_url from jewelry a where id in ({0}) 150 | """.format(productListString) 151 | with connect("reader") as dbconn: 152 | return self.fetch_data(dbconn, sqlstmt) 153 | 154 | def whereami(self): 155 | sqlstmt = "select inet_server_addr();" 156 | with connect("writer") as dbconn: 157 | writer = self.fetch_data(dbconn, sqlstmt, "writer") 158 | with connect("reader") as dbconn: 159 | reader = self.fetch_data(dbconn, sqlstmt, "writer") 160 | return [{"writer": writer, "reader": reader}] 161 | 162 | def addProduct(self, category, product): 163 | if not isinstance(product, list): 164 | product = ast.literal_eval(product) 165 | sqlstmt = """insert into {0} (name, description, img_url, category, inventory, price) 166 | values ({1}, {2}, {3}, {4}, {6}) returning *;""".format(category, product.get(name), product.get(description), product.get(img_url), product.get(category), product.get(inventory), product.get(price)) 167 | with connect("writer") as dbconn: 168 | return self.fetch_data(dbconn, sqlstmt, "writer") 169 | 170 | 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DAT312: Implement GitOps and manage your Amazon RDS resources using ACK from K8s 2 | 3 | In this workshop, learn to deploy a continuous integration and delivery (CI/CD) workflow using GitOps and the AWS Controllers for Kubernetes (ACK) service controller for Amazon RDS on Amazon EKS to create and manage Amazon Aurora Serverless v2 databases effectively. GitOps relies on Git as the single source of truth for declaratively managing containerized infrastructure and application components. With Git at the center of CI/CD pipelines, developers can automate and simplify application deployments and operations to Kubernetes. You must bring your laptop to participate. 4 | 5 | ## Getting started 6 | 7 | For this example we assume a scenario with two clusters: dev and production. The end goal is to leverage Flux and Kustomize to manage both clusters while minimizing duplicated declarations. 8 | 9 | We will configure Flux to install, set up Amazon Aurora Serverless v2, Amazon MemoryDB for Redis and a retail application using [Flux Source Controllers](https://fluxcd.io/flux/components/source/) [AWS CodeCommit](https://aws.amazon.com/codecommit/) source control [repository](https://fluxcd.io/flux/components/source/gitrepositories/), [Helm Repository](https://fluxcd.io/flux/components/source/helmrepositories/), and [Helm Charts](https://fluxcd.io/flux/components/source/helmcharts/) custom resources. Flux will monitor the AWS CodeCommit and Helm repository, and it will automatically upgrade the retail application components using CodeCommit releases, and Helm releases to their latest chart version based on semver ranges. 10 | 11 | ## Prerequisites 12 | 13 | You will need a Kubernetes cluster version 1.23 or newer and kubectl version 1.23 or newer. This lab utilizes AWS managed services such as Amazon Aurora Serverless v2, Amazon MemoryDB for Redis, AWS CodeCommit, and the lab works only on AWS EKS Cluster. 14 | 15 | Please follow the following steps in order to follow the guide: 16 | 17 | 1. Set up environment variables and verify IAM OpenID Connect (OIDC) provider for your cluster. 18 | 19 | ``` 20 | export AWS_ACCOUNT_ID='' 21 | export AWS_REGION='' 22 | export EKS_CLUSTER_NAME='' 23 | export AWS_REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.region') 24 | export VPCID= # VPC for setting up Aurora Serverless v2 and MemoryDB 25 | export SERVICE=rds 26 | export ACK_K8S_NAMESPACE=ack-system 27 | export ACK_SYSTEM_NAMESPACE=ack-system 28 | export RELEASE_VERSION=`curl -sL https://api.github.com/repos/aws-controllers-k8s/$SERVICE-controller/releases/latest | grep '"tag_name":' | cut -d'"' -f4` 29 | 30 | oidc_id=$(aws eks describe-cluster --name $EKS_CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text | cut -d '/' -f 5) 31 | aws iam list-open-id-connect-providers | grep $oidc_id 32 | 33 | ``` 34 | 35 | 2. Create ECR repositories for retail application, build and publish container images to ECR. 36 | 37 | ``` 38 | git clone https://github.com/aws-samples/eks-gitops-workshop.git 39 | cd eks-gitops-workshop/apps/src 40 | make 41 | ``` 42 | In order to follow the guide you'll need a GitHub account and a personal access token that can create repositories (check all permissions under repo). 43 | 44 | 3. Create secret for RDS. Please update the password in 45 | 46 | ``` 47 | aws secretsmanager create-secret \ 48 | --name dbCredential \ 49 | --description "RDS DB username/password" \ 50 | --secret-string "{\"dbuser\":\"\",\"password\":\"\"}" 51 | ``` 52 | 53 | 4. Install ACK for RDS. 54 | 55 | ``` 56 | aws ecr-public get-login-password --region $AWS_REGION | \ 57 | helm registry login --username AWS --password-stdin public.ecr.aws 58 | 59 | helm install --create-namespace -n "${ACK_SYSTEM_NAMESPACE}" "ack-${SERVICE}-controller" \ 60 | "oci://public.ecr.aws/aws-controllers-k8s/${SERVICE}-chart" --version="${RELEASE_VERSION}" \ 61 | --set=aws.region="${AWS_REGION}" \ 62 | --set=serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::${AWS_ACCOUNT_ID}:role/ack-${SERVICE}-controller 63 | ``` 64 | 65 | 5. To allow for Flux to use an AWS CodeCommit git repository in your GitOps pipeline, we need IAM user with `AWSCodeCommitFullAccess` IAM policy attached. 66 | 67 | ``` 68 | export AWS_IAM_GC_USER= 69 | export AWS_IAM_GC_PASS= 70 | ``` 71 | 72 | ## Prepare your AWS CodeCommit git repository for automation 73 | 74 | The Git repository contains the following top-level directories: 75 | 76 | apps contains manifest for retail application and databases to go along with it infrastructure contains infra tools such as the ACK controllers and Helm repository definitions clusters contains the Flux configuration per cluster 77 | 78 | ``` 79 | ├── apps 80 | │ ├── production 81 | │ └── dev 82 | ├── infrastructure 83 | │ ├── ack 84 | │ └── sources 85 | └── clusters 86 | ├── production 87 | └── dev 88 | ``` 89 | 90 | 6. Install Flux in your Amazon EKS Cluster. 91 | 92 | ``` 93 | flux install 94 | 95 | flux create source git flux-system \ 96 | --git-implementation=libgit2 \ 97 | --url=https://git-codecommit.${AWS_REGION}.amazonaws.com/v1/repos/ack-rds-gitops-workshop \ 98 | --branch=master \ 99 | --username=${AWS_IAM_GC_USER} \ 100 | --password=${AWS_IAM_GC_PASS} \ 101 | --interval=1m 102 | 103 | flux create kustomization flux-system \ 104 | --source=flux-system \ 105 | --path="./clusters/production" \ 106 | --prune=true \ 107 | --interval=1m 108 | ``` 109 | 110 | 7. Install the ACK for RDS and ACK for MemoryDB controllers. 111 | 112 | ``` 113 | cd - 114 | aws codecommit create-repository --repository-name ack-rds-gitops-workshop \ 115 | --repository-description "EKS GitOps RDS Workshop" 116 | git clone https://git-codecommit.${AWS_REGION}.amazonaws.com/v1/repos/ack-rds-gitops-workshop ack.codecommit 117 | cp -rp eks-gitops-workshop/* ack.codecommit/ 118 | cd ack.codecommit 119 | sed -i -e "s/^#//g" infrastructure/production/sources/kustomization.yaml 120 | sed -i -e "s/^#//g" infrastructure/production/kustomization.yaml 121 | git commit -m "Add ACK for RDS + ACK for MemoryDB controllers to prod infrastructure" 122 | git push 123 | ``` 124 | 125 | 8. You can monitor the status of Flux HelmRelease and Kustomization resources to ensure they are Ready, Active, and with the message Release reconciliation succeeded using the following command: 126 | 127 | ``` 128 | flux get helmreleases --all-namespaces 129 | watch flux get kustomizations --all-namespaces 130 | ``` 131 | 132 | 9. Now check to see that the both the controllers are installed in the cluster. You can do so with the following command: 133 | 134 | ``` 135 | flux get helmreleases --all-namespaces 136 | kubectl get pod -n "${ACK_K8S_NAMESPACE}" 137 | ``` 138 | 139 | ## Building a GitOps pipeline to deploy an app using Amazon RDS databases 140 | 141 | 10. Update application manifest, commit and push changes to CodeCommit repo. 142 | 143 | ``` 144 | sed -i -e "s/^#//g" apps/production/kustomization.yaml 145 | git commit -m "Deploy retail app w/Amazon Aurora + Amazon MemoryDB" 146 | git push 147 | ``` 148 | 149 | 11. Ensure that all of our requested resources are set up as we requested. We can do this with the following command: 150 | 151 | ``` 152 | flux get kustomizations --all-namespaces 153 | kubectl get DBInstance ack-db-instance01 -n retailapp -o jsonpath='{range }{.status.dbInstanceStatus}{"\n"}{end}' 154 | kubectl get Cluster memorydb-cluster -n retailapp -o jsonpath='{range }{.status.status}{"\n"}{end}' 155 | kubectl get pod -n retailapp 156 | ``` 157 | 158 | 12. Run the following bootstrap database once the database is in `available` state. 159 | 160 | ``` 161 | RDSHOST=$(kubectl get dbinstance -n retailapp ack-db-instance01 --output json | jq '.status.endpoint.address' | sed -e 's/"//g') 162 | export PGPASSWORD=$(aws secretsmanager get-secret-value --secret-id dbCredential --query "SecretString" --output text | jq -r '.password') 163 | psql -h $RDSHOST -d postgres -U adminer -f apps/src/setup_schema.sql 164 | ``` 165 | 166 | 13. Ensure that all Pods for retail application are in `Running` state. 167 | 168 | ``` 169 | kubectl get pod -n retailapp 170 | ``` 171 | 172 | 14. Get Ingress information for retail application. 173 | 174 | ``` 175 | kubectl get ingress -n retailapp 176 | ``` 177 | 178 | 15. Use web browser from your work station and open the URL from above Ingress. Ensure that the application UI has loaded successfully. 179 | 180 | ## Ongoing management of AWS resources through a GitOps pipeline 181 | 182 | 16. Update the `minCapacity` & `MaxCapacity` values in `apps/production/aurora-pg.yaml` in CodeCommit repo and push changes. 183 | 184 | ``` 185 | sed -i -e "s/minCapacity: .*$/minCapacity: 2/g" \ 186 | -e "s/maxCapacity: .*$/maxCapacity: 8/g" \ 187 | apps/production/aurora-pg.yaml 188 | 189 | git commit -a -m "Updating Min & Max ACU" 190 | git push 191 | ``` 192 | 193 | 17. We can verify that these changes were successful in a few ways. First, look at the logs from the ACK for RDS controller pod. 194 | 195 | ``` 196 | kubectl logs --selector k8s-app=rds-chart -n ack-system 197 | ``` 198 | 199 | 18. Apply updates to retail application. 200 | 201 | ``` 202 | sed -i -e "s/banner.png/banner-sale.jpg/g" apps/src/webapp/app/general/templates/general/index.html 203 | ``` 204 | 205 | 19. Build and publish new container image version. 206 | 207 | ``` 208 | cd ~/environment/ack.codecommit/apps/src/ 209 | aws ecr get-login-password --region ${AWS_REGION} | \ 210 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com 211 | docker build -t ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eksack/webapp:1.1 webapp/. 212 | docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eksack/webapp:1.1 213 | ``` 214 | 215 | 20. Update our application manifest to refer to the new application container image version. 216 | 217 | ``` 218 | cd - 219 | sed -i -e "s/webapp:1.0/webapp:1.1/g" apps/production/retailapp/deployments.yaml 220 | git commit -a -m "Webapp container version 1.1" 221 | git push 222 | ``` 223 | 224 | 21. Like our previous changes, Flux will detect that there is a new commit in the git repository. You can verify that the changes roll out using the command below. 225 | 226 | ``` 227 | watch flux get kustomizations --all-namespaces 228 | ``` 229 | 230 | -------------------------------------------------------------------------------- /scripts/prereq.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | function print_line() 4 | { 5 | echo "---------------------------------" 6 | } 7 | 8 | function install_packages() 9 | { 10 | sudo yum install -y jq > ${TERM} 2>&1 11 | print_line 12 | echo "Installing aws cli v2" 13 | print_line 14 | aws --version | grep aws-cli\/2 > /dev/null 2>&1 15 | if [ $? -eq 0 ] ; then 16 | cd $current_dir 17 | return 18 | fi 19 | current_dir=`pwd` 20 | cd /tmp 21 | curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" > ${TERM} 2>&1 22 | unzip -o awscliv2.zip > ${TERM} 2>&1 23 | sudo ./aws/install --update > ${TERM} 2>&1 24 | cd $current_dir 25 | } 26 | 27 | function install_k8s_utilities() 28 | { 29 | print_line 30 | echo "Installing Kubectl" 31 | print_line 32 | sudo curl -o /usr/local/bin/kubectl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" > ${TERM} 2>&1 33 | sudo chmod +x /usr/local/bin/kubectl > ${TERM} 2>&1 34 | print_line 35 | echo "Installing eksctl" 36 | print_line 37 | curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp > ${TERM} 2>&1 38 | sudo mv /tmp/eksctl /usr/local/bin 39 | sudo chmod +x /usr/local/bin/eksctl 40 | print_line 41 | echo "Installing helm" 42 | print_line 43 | curl -s https://fluxcd.io/install.sh | sudo bash > ${TERM} 2>&1 44 | curl -sSL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash > ${TERM} 2>&1 45 | 46 | } 47 | 48 | function install_postgresql() 49 | { 50 | print_line 51 | echo "Installing Postgresql client" 52 | print_line 53 | sudo amazon-linux-extras install -y postgresql14 > ${TERM} 2>&1 54 | } 55 | 56 | 57 | function update_kubeconfig() 58 | { 59 | print_line 60 | echo "Updating kubeconfig" 61 | print_line 62 | aws eks update-kubeconfig --name ${EKS_CLUSTER_NAME} 63 | } 64 | 65 | 66 | function update_eks() 67 | { 68 | print_line 69 | echo "Enabling clusters to use iam oidc" 70 | print_line 71 | eksctl utils associate-iam-oidc-provider --cluster ${EKS_CLUSTER_NAME} --region ${AWS_REGION} --approve 72 | } 73 | 74 | 75 | function chk_installation() 76 | { 77 | print_line 78 | echo "Checking the current installation" 79 | print_line 80 | for command in kubectl aws eksctl flux helm jq 81 | do 82 | which $command &>${TERM} && echo "$command present" || echo "$command NOT FOUND" 83 | done 84 | 85 | } 86 | 87 | 88 | function clone_git() 89 | { 90 | print_line 91 | echo "Cloning the git repository" 92 | print_line 93 | cd ${HOME}/environment 94 | rm -rf ack.gitlab ack.codecommit 95 | git clone https://github.com/aws-samples/ack-rds-gitops-workshop ack.gitlab 96 | git clone https://git-codecommit.${AWS_REGION}.amazonaws.com/v1/repos/ack-rds-gitops-workshop ack.codecommit 97 | cd ack.codecommit 98 | cp -rp ../ack.gitlab/* . 99 | print_line 100 | } 101 | 102 | function fix_git() 103 | { 104 | print_line 105 | echo "Fixing the git repository" 106 | print_line 107 | 108 | cd ${HOME}/environment/ack.codecommit 109 | 110 | # Update infrastructure manifests 111 | sed -i -e "s//$AWS_REGION/g" ./infrastructure/production/ack/*release*.yaml 112 | sed -i -e "s//$AWS_ACCOUNT_ID/g" ./infrastructure/production/ack/*serviceaccount.yaml 113 | 114 | # Update application manifests 115 | sed -i -e "s//$AWS_REGION/g" \ 116 | -e "s//$AWS_ACCOUNT_ID/g" \ 117 | -e "s//rds-db-subnet/g" \ 118 | -e "s//memorydb-db-subnet/g" \ 119 | -e "s//$VPCID/g" \ 120 | ./apps/production/retailapp/*.yaml 121 | 122 | sed -i -e "s//$AWS_REGION/g" \ 123 | -e "s//$AWS_ACCOUNT_ID/g" \ 124 | -e "s//rds-db-subnet/g" \ 125 | -e "s//memorydb-db-subnet/g" \ 126 | -e "s//$vpcsg/g" \ 127 | ./apps/production/*.yaml 128 | 129 | git add . 130 | git commit -a -m "Initial version" 131 | git push 132 | } 133 | 134 | function install_loadbalancer() 135 | { 136 | 137 | print_line 138 | echo "Installing load balancer" 139 | print_line 140 | eksctl create iamserviceaccount \ 141 | --cluster=${EKS_CLUSTER_NAME} \ 142 | --namespace=${EKS_NAMESPACE} \ 143 | --name=aws-load-balancer-controller \ 144 | --attach-policy-arn=arn:aws:iam::${AWS_ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy \ 145 | --override-existing-serviceaccounts \ 146 | --approve 147 | 148 | helm repo add eks https://aws.github.io/eks-charts 149 | 150 | kubectl apply -k "github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds?ref=master" 151 | 152 | helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ 153 | --set clusterName=${EKS_CLUSTER_NAME} \ 154 | --set serviceAccount.create=false \ 155 | --set region=${AWS_REGION} \ 156 | --set vpcId=${VPCID} \ 157 | --set serviceAccount.name=aws-load-balancer-controller \ 158 | -n ${EKS_NAMESPACE} 159 | 160 | } 161 | 162 | 163 | function chk_aws_environment() 164 | { 165 | print_line 166 | echo "Checking AWS environment" 167 | print_line 168 | for myenv in "${AWS_DEFAULT_REGION}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}" "${AWS_SESSION_TOKEN}" 169 | do 170 | if [ x"${myenv}" == "x" ] ; then 171 | echo "AWS environment is missing. Please import from event engine" 172 | exit 173 | fi 174 | done 175 | echo "AWS environment exists" 176 | 177 | } 178 | 179 | 180 | function run_kubectl() 181 | { 182 | print_line 183 | echo "kubectl get nodes -o wide" 184 | print_line 185 | kubectl get nodes -o wide 186 | print_line 187 | echo "kubectl get pods --all-namespaces" 188 | print_line 189 | kubectl get pods --all-namespaces 190 | } 191 | 192 | function create_iam_user() 193 | { 194 | print_line 195 | echo "Creating AWS IAM User for git" 196 | print_line 197 | aws iam create-user --user-name gituser 198 | if [[ $? -ne 0 ]]; then 199 | echo "ERROR: Failed to create user" 200 | fi 201 | print_line 202 | aws iam attach-user-policy --user-name gituser \ 203 | --policy-arn arn:aws:iam::aws:policy/AWSCodeCommitFullAccess 204 | if [[ $? -ne 0 ]]; then 205 | echo "ERROR: Failed to attach plicy to user" 206 | fi 207 | print_line 208 | } 209 | 210 | function build_and_publish_container_images() 211 | { 212 | print_line 213 | echo "Create docker container images for application and publish to ECR" 214 | cd ~/environment/ack.codecommit/apps/src 215 | export TERM=xterm 216 | make 217 | cd - 218 | print_line 219 | } 220 | 221 | function create_secret() 222 | { 223 | print_line 224 | aws secretsmanager create-secret --name dbCredential --description "RDS DB username/password" --secret-string "{\"dbuser\":\"adminer\",\"password\":\"postgres\"}" 225 | print_line 226 | } 227 | 228 | function install_c9() 229 | { 230 | print_line 231 | npm install -g c9 232 | print_line 233 | } 234 | 235 | function chk_cloud9_permission() 236 | { 237 | aws sts get-caller-identity | grep ${INSTANCE_ROLE} 238 | if [ $? -ne 0 ] ; then 239 | echo "Fixing the cloud9 permission" 240 | environment_id=`aws ec2 describe-instances --instance-id $(curl -s http://169.254.169.254/latest/meta-data/instance-id) --query "Reservations[*].Instances[*].Tags[?Key=='aws:cloud9:environment'].Value" --output text` 241 | aws cloud9 update-environment --environment-id ${environment_id} --region ${AWS_REGION} --managed-credentials-action DISABLE 242 | sleep 10 243 | ls -l $HOME/.aws/credentials > /dev/null 2>&1 244 | if [ $? -eq 0 ] ; then 245 | echo "!!! Credentials file exists" 246 | else 247 | echo "Credentials file does not exists" 248 | fi 249 | echo "After fixing the credentials. Current role" 250 | aws sts get-caller-identity | grep ${INSTANCE_ROLE} 251 | fi 252 | } 253 | 254 | 255 | function initial_cloud9_permission() 256 | { 257 | print_line 258 | echo "Checking initial cloud9 permission" 259 | typeset -i counter=0 260 | managed_role="FALSE" 261 | while [ ${counter} -lt 2 ] 262 | do 263 | aws sts get-caller-identity | grep ${INSTANCE_ROLE} 264 | if [ $? -eq 0 ] ; then 265 | echo "Called identity is Instance role .. Waiting - ${counter}" 266 | sleep 30 267 | counter=$counter+1 268 | else 269 | echo "Called identity is AWS Managed Role .. breaking" 270 | managed_role="TRUE" 271 | break 272 | fi 273 | done 274 | 275 | if [ ${managed_role} == "TRUE" ] ; then 276 | echo "Current role is AWS managed role" 277 | else 278 | echo "Current role is Instance role.. May cause issue later deployment. But still continuing" 279 | fi 280 | 281 | chk_cloud9_permission 282 | } 283 | 284 | 285 | function print_environment() 286 | { 287 | print_line 288 | echo "Current Region : ${AWS_REGION}" 289 | echo "EKS Namespace : ${EKS_NAMESPACE}" 290 | echo "EKS Cluster Name : ${EKS_CLUSTER_NAME}" 291 | echo "VPCID : ${VPCID}" 292 | echo "VPC SG : ${vpcsg}" 293 | print_line 294 | } 295 | 296 | # Main program starts here 297 | 298 | export INSTANCE_ROLE="C9Role" 299 | 300 | if [ ${1}X == "-xX" ] ; then 301 | TERM="/dev/tty" 302 | else 303 | TERM="/dev/null" 304 | fi 305 | 306 | echo "Process started at `date`" 307 | install_packages 308 | 309 | export AWS_REGION=`curl -s http://169.254.169.254/latest/dynamic/instance-identity/document | jq .region -r` 310 | initial_cloud9_permission 311 | export EKS_NAMESPACE="kube-system" 312 | export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) 313 | export VPCID=$(aws cloudformation describe-stacks --region $AWS_REGION --query 'Stacks[].Outputs[?OutputKey == `VPC`].OutputValue' --output text) 314 | 315 | install_k8s_utilities 316 | install_postgresql 317 | #create_iam_user 318 | clone_git 319 | chk_cloud9_permission 320 | export EKS_CLUSTER_NAME=$(aws cloudformation describe-stacks --query "Stacks[].Outputs[?(OutputKey == 'EKSClusterName')][].{OutputValue:OutputValue}" --output text) 321 | export vpcsg=$(aws ec2 describe-security-groups --filters Name=ip-permission.from-port,Values=5432 Name=ip-permission.to-port,Values=5432 --query "SecurityGroups[0].GroupId" --output text) 322 | print_environment 323 | fix_git 324 | update_kubeconfig 325 | chk_cloud9_permission 326 | update_eks 327 | chk_cloud9_permission 328 | install_loadbalancer 329 | chk_installation 330 | chk_cloud9_permission 331 | run_kubectl 332 | chk_cloud9_permission 333 | build_and_publish_container_images 334 | create_secret 335 | print_line 336 | install_c9 337 | print_line 338 | 339 | echo "Process completed at `date`" 340 | -------------------------------------------------------------------------------- /apps/production/retailapp/deployments.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: kart 5 | namespace: retailapp 6 | labels: 7 | app: eksack 8 | service: kart 9 | spec: 10 | replicas: 1 11 | strategy: 12 | rollingUpdate: 13 | maxSurge: 3 14 | maxUnavailable: 0 15 | selector: 16 | matchLabels: 17 | app: eksack 18 | service: kart 19 | template: 20 | metadata: 21 | labels: 22 | app: eksack 23 | service: kart 24 | spec: 25 | restartPolicy: Always 26 | containers: 27 | - name: kart 28 | image: .dkr.ecr..amazonaws.com/eksack/kart:1.0 29 | imagePullPolicy: Always 30 | ports: 31 | - containerPort: 8445 32 | env: 33 | - name: DATABASE_HOST 34 | valueFrom: 35 | configMapKeyRef: 36 | name: asv2-db-instance-conn-cm 37 | key: retailapp.ack-db-instance01-host 38 | - name: DATABASE_PORT 39 | valueFrom: 40 | configMapKeyRef: 41 | name: asv2-db-instance-conn-cm 42 | key: retailapp.ack-db-instance01-port 43 | - name: DATABASE_USER 44 | valueFrom: 45 | secretKeyRef: 46 | name: eksack-secret 47 | key: database_user 48 | - name: DATABASE_PASSWORD 49 | valueFrom: 50 | secretKeyRef: 51 | name: eksack-secret 52 | key: database_password 53 | - name: DATABASE_DB_NAME 54 | valueFrom: 55 | secretKeyRef: 56 | name: eksack-secret 57 | key: database_db_name 58 | - name: DATABASE_RODB_NAME 59 | valueFrom: 60 | secretKeyRef: 61 | name: eksack-secret 62 | key: database_rodb_name 63 | - name: SECRET_KEY 64 | valueFrom: 65 | secretKeyRef: 66 | name: eksack-secret 67 | key: secret_key 68 | - name: AUTHTOKEN 69 | valueFrom: 70 | secretKeyRef: 71 | name: eksack-secret 72 | key: authtoken 73 | - name: MEMDB_HOST 74 | valueFrom: 75 | configMapKeyRef: 76 | name: memorydb-cluster-conn-cm 77 | key: retailapp.memorydb-cluster-host 78 | - name: MEMDB_PORT 79 | valueFrom: 80 | configMapKeyRef: 81 | name: memorydb-cluster-conn-cm 82 | key: retailapp.memorydb-cluster-port 83 | - name: MEMDB_USER 84 | valueFrom: 85 | secretKeyRef: 86 | name: eksack-secret 87 | key: memdb_user 88 | - name: MEMDB_PASS 89 | valueFrom: 90 | secretKeyRef: 91 | name: eksack-secret 92 | key: memdb_pass 93 | --- 94 | apiVersion: apps/v1 95 | kind: Deployment 96 | metadata: 97 | name: product 98 | namespace: retailapp 99 | labels: 100 | app: eksack 101 | service: product 102 | spec: 103 | replicas: 1 104 | strategy: 105 | rollingUpdate: 106 | maxSurge: 3 107 | maxUnavailable: 0 108 | selector: 109 | matchLabels: 110 | app: eksack 111 | service: product 112 | template: 113 | metadata: 114 | labels: 115 | app: eksack 116 | service: product 117 | spec: 118 | restartPolicy: Always 119 | containers: 120 | - name: product 121 | image: .dkr.ecr..amazonaws.com/eksack/product:1.0 122 | imagePullPolicy: Always 123 | ports: 124 | - containerPort: 8444 125 | env: 126 | - name: DATABASE_HOST 127 | valueFrom: 128 | configMapKeyRef: 129 | name: asv2-db-instance-conn-cm 130 | key: retailapp.ack-db-instance01-host 131 | - name: DATABASE_PORT 132 | valueFrom: 133 | configMapKeyRef: 134 | name: asv2-db-instance-conn-cm 135 | key: retailapp.ack-db-instance01-port 136 | - name: DATABASE_USER 137 | valueFrom: 138 | secretKeyRef: 139 | name: eksack-secret 140 | key: database_user 141 | - name: DATABASE_PASSWORD 142 | valueFrom: 143 | secretKeyRef: 144 | name: eksack-secret 145 | key: database_password 146 | - name: DATABASE_DB_NAME 147 | valueFrom: 148 | secretKeyRef: 149 | name: eksack-secret 150 | key: database_db_name 151 | - name: DATABASE_RODB_NAME 152 | valueFrom: 153 | secretKeyRef: 154 | name: eksack-secret 155 | key: database_rodb_name 156 | - name: SECRET_KEY 157 | valueFrom: 158 | secretKeyRef: 159 | name: eksack-secret 160 | key: secret_key 161 | - name: AUTHTOKEN 162 | valueFrom: 163 | secretKeyRef: 164 | name: eksack-secret 165 | key: authtoken 166 | - name: MEMDB_HOST 167 | valueFrom: 168 | configMapKeyRef: 169 | name: memorydb-cluster-conn-cm 170 | key: retailapp.memorydb-cluster-host 171 | - name: MEMDB_PORT 172 | valueFrom: 173 | configMapKeyRef: 174 | name: memorydb-cluster-conn-cm 175 | key: retailapp.memorydb-cluster-port 176 | - name: MEMDB_USER 177 | valueFrom: 178 | secretKeyRef: 179 | name: eksack-secret 180 | key: memdb_user 181 | - name: MEMDB_PASS 182 | valueFrom: 183 | secretKeyRef: 184 | name: eksack-secret 185 | key: memdb_pass 186 | --- 187 | apiVersion: apps/v1 188 | kind: Deployment 189 | metadata: 190 | name: user 191 | namespace: retailapp 192 | labels: 193 | app: eksack 194 | service: user 195 | spec: 196 | replicas: 1 197 | strategy: 198 | rollingUpdate: 199 | maxSurge: 3 200 | maxUnavailable: 0 201 | selector: 202 | matchLabels: 203 | app: eksack 204 | service: user 205 | template: 206 | metadata: 207 | labels: 208 | app: eksack 209 | service: user 210 | spec: 211 | restartPolicy: Always 212 | containers: 213 | - name: user 214 | image: .dkr.ecr..amazonaws.com/eksack/user:1.0 215 | imagePullPolicy: Always 216 | ports: 217 | - containerPort: 8446 218 | env: 219 | - name: DATABASE_HOST 220 | valueFrom: 221 | configMapKeyRef: 222 | name: asv2-db-instance-conn-cm 223 | key: retailapp.ack-db-instance01-host 224 | - name: DATABASE_PORT 225 | valueFrom: 226 | configMapKeyRef: 227 | name: asv2-db-instance-conn-cm 228 | key: retailapp.ack-db-instance01-port 229 | - name: DATABASE_USER 230 | valueFrom: 231 | secretKeyRef: 232 | name: eksack-secret 233 | key: database_user 234 | - name: DATABASE_PASSWORD 235 | valueFrom: 236 | secretKeyRef: 237 | name: eksack-secret 238 | key: database_password 239 | - name: DATABASE_DB_NAME 240 | valueFrom: 241 | secretKeyRef: 242 | name: eksack-secret 243 | key: database_db_name 244 | - name: DATABASE_RODB_NAME 245 | valueFrom: 246 | secretKeyRef: 247 | name: eksack-secret 248 | key: database_rodb_name 249 | - name: SECRET_KEY 250 | valueFrom: 251 | secretKeyRef: 252 | name: eksack-secret 253 | key: secret_key 254 | - name: AUTHTOKEN 255 | valueFrom: 256 | secretKeyRef: 257 | name: eksack-secret 258 | key: authtoken 259 | - name: MEMDB_HOST 260 | valueFrom: 261 | configMapKeyRef: 262 | name: memorydb-cluster-conn-cm 263 | key: retailapp.memorydb-cluster-host 264 | - name: MEMDB_PORT 265 | valueFrom: 266 | configMapKeyRef: 267 | name: memorydb-cluster-conn-cm 268 | key: retailapp.memorydb-cluster-port 269 | - name: MEMDB_USER 270 | valueFrom: 271 | secretKeyRef: 272 | name: eksack-secret 273 | key: memdb_user 274 | - name: MEMDB_PASS 275 | valueFrom: 276 | secretKeyRef: 277 | name: eksack-secret 278 | key: memdb_pass 279 | --- 280 | apiVersion: apps/v1 281 | kind: Deployment 282 | metadata: 283 | name: webapp 284 | namespace: retailapp 285 | labels: 286 | app: eksack 287 | service: webapp 288 | spec: 289 | replicas: 1 290 | strategy: 291 | rollingUpdate: 292 | maxSurge: 2 293 | maxUnavailable: 0 294 | selector: 295 | matchLabels: 296 | app: eksack 297 | service: webapp 298 | template: 299 | metadata: 300 | labels: 301 | app: eksack 302 | service: webapp 303 | spec: 304 | restartPolicy: Always 305 | containers: 306 | - name: webapp 307 | image: .dkr.ecr..amazonaws.com/eksack/webapp:1.0 308 | imagePullPolicy: Always 309 | ports: 310 | - containerPort: 8443 311 | env: 312 | - name: DATABASE_HOST 313 | valueFrom: 314 | configMapKeyRef: 315 | name: asv2-db-instance-conn-cm 316 | key: retailapp.ack-db-instance01-host 317 | - name: DATABASE_PORT 318 | valueFrom: 319 | configMapKeyRef: 320 | name: asv2-db-instance-conn-cm 321 | key: retailapp.ack-db-instance01-port 322 | - name: DATABASE_USER 323 | valueFrom: 324 | secretKeyRef: 325 | name: eksack-secret 326 | key: database_user 327 | - name: DATABASE_PASSWORD 328 | valueFrom: 329 | secretKeyRef: 330 | name: eksack-secret 331 | key: database_password 332 | - name: DATABASE_DB_NAME 333 | valueFrom: 334 | secretKeyRef: 335 | name: eksack-secret 336 | key: database_db_name 337 | - name: DATABASE_RODB_NAME 338 | valueFrom: 339 | secretKeyRef: 340 | name: eksack-secret 341 | key: database_rodb_name 342 | - name: SECRET_KEY 343 | valueFrom: 344 | secretKeyRef: 345 | name: eksack-secret 346 | key: secret_key 347 | - name: AUTHTOKEN 348 | valueFrom: 349 | secretKeyRef: 350 | name: eksack-secret 351 | key: authtoken 352 | - name: PRODUCTS_SERVICE 353 | valueFrom: 354 | secretKeyRef: 355 | name: eksack-secret 356 | key: product_service 357 | - name: KART_SERVICE 358 | valueFrom: 359 | secretKeyRef: 360 | name: eksack-secret 361 | key: kart_service 362 | - name: USER_SERVICE 363 | valueFrom: 364 | secretKeyRef: 365 | name: eksack-secret 366 | key: user_service 367 | - name: ORDER_SERVICE 368 | valueFrom: 369 | secretKeyRef: 370 | name: eksack-secret 371 | key: order_service 372 | - name: MEMDB_HOST 373 | valueFrom: 374 | configMapKeyRef: 375 | name: memorydb-cluster-conn-cm 376 | key: retailapp.memorydb-cluster-host 377 | - name: MEMDB_PORT 378 | valueFrom: 379 | configMapKeyRef: 380 | name: memorydb-cluster-conn-cm 381 | key: retailapp.memorydb-cluster-port 382 | - name: MEMDB_USER 383 | valueFrom: 384 | secretKeyRef: 385 | name: eksack-secret 386 | key: memdb_user 387 | - name: MEMDB_PASS 388 | valueFrom: 389 | secretKeyRef: 390 | name: eksack-secret 391 | key: memdb_pass 392 | --- 393 | apiVersion: apps/v1 394 | kind: Deployment 395 | metadata: 396 | name: order-deployment 397 | namespace: retailapp 398 | labels: 399 | app: eksack 400 | service: order 401 | spec: 402 | replicas: 1 403 | strategy: 404 | rollingUpdate: 405 | maxSurge: 3 406 | maxUnavailable: 0 407 | selector: 408 | matchLabels: 409 | app: eksack 410 | service: order 411 | template: 412 | metadata: 413 | labels: 414 | app: eksack 415 | service: order 416 | spec: 417 | restartPolicy: Always 418 | containers: 419 | - name: order 420 | image: .dkr.ecr..amazonaws.com/eksack/order:1.0 421 | imagePullPolicy: Always 422 | ports: 423 | - containerPort: 8448 424 | env: 425 | - name: DATABASE_HOST 426 | valueFrom: 427 | configMapKeyRef: 428 | name: asv2-db-instance-conn-cm 429 | key: retailapp.ack-db-instance01-host 430 | - name: DATABASE_PORT 431 | valueFrom: 432 | configMapKeyRef: 433 | name: asv2-db-instance-conn-cm 434 | key: retailapp.ack-db-instance01-port 435 | - name: DATABASE_USER 436 | valueFrom: 437 | secretKeyRef: 438 | name: eksack-secret 439 | key: database_user 440 | - name: DATABASE_PASSWORD 441 | valueFrom: 442 | secretKeyRef: 443 | name: eksack-secret 444 | key: database_password 445 | - name: DATABASE_DB_NAME 446 | valueFrom: 447 | secretKeyRef: 448 | name: eksack-secret 449 | key: database_db_name 450 | - name: DATABASE_RODB_NAME 451 | valueFrom: 452 | secretKeyRef: 453 | name: eksack-secret 454 | key: database_rodb_name 455 | - name: SECRET_KEY 456 | valueFrom: 457 | secretKeyRef: 458 | name: eksack-secret 459 | key: secret_key 460 | - name: AUTHTOKEN 461 | valueFrom: 462 | secretKeyRef: 463 | name: eksack-secret 464 | key: authtoken 465 | - name: MEMDB_HOST 466 | valueFrom: 467 | configMapKeyRef: 468 | name: memorydb-cluster-conn-cm 469 | key: retailapp.memorydb-cluster-host 470 | - name: MEMDB_PORT 471 | valueFrom: 472 | configMapKeyRef: 473 | name: memorydb-cluster-conn-cm 474 | key: retailapp.memorydb-cluster-port 475 | - name: MEMDB_USER 476 | valueFrom: 477 | secretKeyRef: 478 | name: eksack-secret 479 | key: memdb_user 480 | - name: MEMDB_PASS 481 | valueFrom: 482 | secretKeyRef: 483 | name: eksack-secret 484 | key: memdb_pass 485 | -------------------------------------------------------------------------------- /cfn/ack-rds-cfn-base.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | Description: 'Cloudformation Template DAT312 ACK-RDS-GITOPS-Workshop' 4 | 5 | Metadata: 6 | 'AWS::CloudFormation::Interface': 7 | ParameterGroups: 8 | - Label: 9 | default: 'VPC Parameters' 10 | Parameters: 11 | - ClassB 12 | - Label: 13 | default: Cloud9 Configuration 14 | Parameters: 15 | - C9InstanceType 16 | ParameterLabels: 17 | Application: 18 | default: Application Name 19 | ClassB: 20 | default: ClassB 2nd Octet 21 | C9InstanceType: 22 | default: Cloud9 Instance Type 23 | 24 | Parameters: 25 | 26 | IsWorkshopStudioEnv: 27 | Type: String 28 | Default: "no" 29 | AllowedValues: 30 | - "no" 31 | - "yes" 32 | Description: Whether this stack is being deployed in a Workshop Studio environment or not. If not sure, leave as default of "no". 33 | 34 | Application: 35 | Description: 'Specify Application Name' 36 | Type: String 37 | Default: 'eksack' 38 | 39 | ClassB: 40 | Description: 'Specify the 2nd Octet of IPv4 CIDR block for the VPC (10.XXX.0.0/16) in the range [0-255]' 41 | Type: Number 42 | Default: 40 43 | ConstraintDescription: 'Must be in the range [0-255]' 44 | MinValue: 0 45 | MaxValue: 255 46 | 47 | C9InstanceType: 48 | AllowedValues: 49 | - t2.micro 50 | - t3.micro 51 | - t3.small 52 | - t3.medium 53 | Default: t3.medium 54 | Description: Amazon Cloud9 instance type 55 | Type: String 56 | 57 | AssetsBucketName: 58 | Description: Cloudformation template bucket Name 59 | Type: String 60 | Default: ee-assets-prod-us-east-1 61 | 62 | AssetsBucketPrefix: 63 | Description: Cloudformation template bucket Name 64 | Type: String 65 | Default: modules/ead3ed9cfcbe40ad871a44e121334d7f/v1/ 66 | 67 | Resources: 68 | 69 | #============================================================================# 70 | # VPC Configuration 71 | #============================================================================# 72 | 73 | VPC: 74 | Type: 'AWS::EC2::VPC' 75 | Properties: 76 | CidrBlock: !Sub '10.${ClassB}.0.0/16' 77 | EnableDnsSupport: true 78 | EnableDnsHostnames: true 79 | InstanceTenancy: default 80 | Tags: 81 | - Key: Name 82 | Value: !Sub '${AWS::StackName}-vpc' 83 | 84 | InternetGateway: 85 | Type: 'AWS::EC2::InternetGateway' 86 | Properties: 87 | Tags: 88 | - Key: Name 89 | Value: !Sub '${AWS::StackName}-igw' 90 | 91 | VPCGatewayAttachment: 92 | Type: 'AWS::EC2::VPCGatewayAttachment' 93 | Properties: 94 | VpcId: !Ref VPC 95 | InternetGatewayId: !Ref InternetGateway 96 | 97 | SubnetAPublic: 98 | Type: AWS::EC2::Subnet 99 | Properties: 100 | VpcId: !Ref VPC 101 | CidrBlock: !Sub '10.${ClassB}.64.0/20' 102 | AvailabilityZone: !Select [0, !GetAZs ] 103 | MapPublicIpOnLaunch: true 104 | Tags: 105 | - Key: Name 106 | Value: !Sub ${AWS::StackName}-pub-sub-a 107 | - Key: kubernetes.io/role/elb 108 | Value: 1 109 | 110 | SubnetBPublic: 111 | Type: AWS::EC2::Subnet 112 | Properties: 113 | VpcId: !Ref VPC 114 | CidrBlock: !Sub '10.${ClassB}.80.0/20' 115 | AvailabilityZone: !Select [1, !GetAZs ] 116 | MapPublicIpOnLaunch: true 117 | Tags: 118 | - Key: Name 119 | Value: !Sub ${AWS::StackName}-pub-sub-b 120 | - Key: kubernetes.io/role/elb 121 | Value: 1 122 | 123 | RouteTable: 124 | Type: 'AWS::EC2::RouteTable' 125 | Properties: 126 | VpcId: !Ref VPC 127 | Tags: 128 | - Key: Name 129 | Value: !Sub '${AWS::StackName}-vpc-rt' 130 | 131 | RouteTablePublicInternetRoute: 132 | Type: 'AWS::EC2::Route' 133 | DependsOn: VPCGatewayAttachment 134 | Properties: 135 | RouteTableId: !Ref RouteTable 136 | DestinationCidrBlock: 0.0.0.0/0 137 | GatewayId: !Ref InternetGateway 138 | 139 | SubnetRouteTableAAssociation: 140 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 141 | Properties: 142 | SubnetId: !Ref SubnetAPublic 143 | RouteTableId: !Ref RouteTable 144 | 145 | SubnetRouteTableBAssociation: 146 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 147 | Properties: 148 | SubnetId: !Ref SubnetBPublic 149 | RouteTableId: !Ref RouteTable 150 | 151 | natEip: 152 | Type: AWS::EC2::EIP 153 | Properties: 154 | Domain: vpc 155 | 156 | vpcNgw: 157 | Type: AWS::EC2::NatGateway 158 | DependsOn: VPCGatewayAttachment 159 | Properties: 160 | AllocationId: !GetAtt natEip.AllocationId 161 | SubnetId: !Ref SubnetAPublic 162 | 163 | SubnetAPrivate: 164 | Type: 'AWS::EC2::Subnet' 165 | Properties: 166 | AvailabilityZone: !Select [0, !GetAZs ''] 167 | CidrBlock: !Sub '10.${ClassB}.16.0/20' 168 | VpcId: !Ref VPC 169 | Tags: 170 | - Key: Name 171 | Value: !Sub ${AWS::StackName}-pvt-a 172 | - Key: kubernetes.io/role/internal-elb 173 | Value: 1 174 | 175 | SubnetBPrivate: 176 | Type: 'AWS::EC2::Subnet' 177 | Properties: 178 | AvailabilityZone: !Select [1, !GetAZs ''] 179 | CidrBlock: !Sub '10.${ClassB}.32.0/20' 180 | VpcId: !Ref VPC 181 | Tags: 182 | - Key: Name 183 | Value: !Sub ${AWS::StackName}-pvt-b 184 | - Key: kubernetes.io/role/internal-elb 185 | Value: 1 186 | 187 | SubnetCPrivate: 188 | Type: 'AWS::EC2::Subnet' 189 | Properties: 190 | AvailabilityZone: !Select [2, !GetAZs ''] 191 | CidrBlock: !Sub '10.${ClassB}.48.0/20' 192 | VpcId: !Ref VPC 193 | Tags: 194 | - Key: Name 195 | Value: !Sub ${AWS::StackName}-pvt-c 196 | - Key: kubernetes.io/role/internal-elb 197 | Value: 1 198 | 199 | RouteTablePrivate: 200 | Type: 'AWS::EC2::RouteTable' 201 | Properties: 202 | VpcId: !Ref VPC 203 | Tags: 204 | - Key: Name 205 | Value: !Sub ${AWS::StackName}-pvt-rt 206 | 207 | rteToNgw: 208 | Type: AWS::EC2::Route 209 | Properties: 210 | RouteTableId: !Ref RouteTablePrivate 211 | DestinationCidrBlock: 0.0.0.0/0 212 | NatGatewayId: !Ref vpcNgw 213 | 214 | RouteTableAssociationAPrivate: 215 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 216 | Properties: 217 | SubnetId: !Ref SubnetAPrivate 218 | RouteTableId: !Ref RouteTablePrivate 219 | 220 | RouteTableAssociationBPrivate: 221 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 222 | Properties: 223 | SubnetId: !Ref SubnetBPrivate 224 | RouteTableId: !Ref RouteTablePrivate 225 | 226 | RouteTableAssociationCPrivate: 227 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 228 | Properties: 229 | SubnetId: !Ref SubnetCPrivate 230 | RouteTableId: !Ref RouteTablePrivate 231 | 232 | NetworkAclPublic: 233 | Type: 'AWS::EC2::NetworkAcl' 234 | Properties: 235 | VpcId: !Ref VPC 236 | Tags: 237 | - Key: Name 238 | Value: !Sub ${AWS::StackName}-nacl-pub 239 | 240 | NetworkAclEntryInPublicAllowVPC: 241 | Type: 'AWS::EC2::NetworkAclEntry' 242 | Properties: 243 | NetworkAclId: !Ref NetworkAclPublic 244 | RuleNumber: 99 245 | Protocol: -1 246 | RuleAction: allow 247 | Egress: false 248 | CidrBlock: '0.0.0.0/0' 249 | 250 | NetworkAclEntryOutPublicAllowVPC: 251 | Type: 'AWS::EC2::NetworkAclEntry' 252 | Properties: 253 | NetworkAclId: !Ref NetworkAclPublic 254 | RuleNumber: 99 255 | Protocol: -1 256 | RuleAction: allow 257 | Egress: true 258 | CidrBlock: '0.0.0.0/0' 259 | 260 | SubnetNetworkAclAssociationAPublic: 261 | Type: 'AWS::EC2::SubnetNetworkAclAssociation' 262 | Properties: 263 | SubnetId: !Ref SubnetAPublic 264 | NetworkAclId: !Ref NetworkAclPublic 265 | 266 | SubnetNetworkAclAssociationBPublic: 267 | Type: 'AWS::EC2::SubnetNetworkAclAssociation' 268 | Properties: 269 | SubnetId: !Ref SubnetBPublic 270 | NetworkAclId: !Ref NetworkAclPublic 271 | 272 | NetworkAclPrivate: 273 | Type: 'AWS::EC2::NetworkAcl' 274 | Properties: 275 | VpcId: !Ref VPC 276 | Tags: 277 | - Key: Name 278 | Value: !Sub ${AWS::StackName}-nacl-pvt 279 | 280 | SubnetNetworkAclAssociationAPrivate: 281 | Type: 'AWS::EC2::SubnetNetworkAclAssociation' 282 | Properties: 283 | SubnetId: !Ref SubnetAPrivate 284 | NetworkAclId: !Ref NetworkAclPrivate 285 | 286 | SubnetNetworkAclAssociationBPrivate: 287 | Type: 'AWS::EC2::SubnetNetworkAclAssociation' 288 | Properties: 289 | SubnetId: !Ref SubnetBPrivate 290 | NetworkAclId: !Ref NetworkAclPrivate 291 | 292 | SubnetNetworkAclAssociationCPrivate: 293 | Type: 'AWS::EC2::SubnetNetworkAclAssociation' 294 | Properties: 295 | SubnetId: !Ref SubnetCPrivate 296 | NetworkAclId: !Ref NetworkAclPrivate 297 | 298 | NetworkAclEntryInPrivateAllowVPC: 299 | Type: 'AWS::EC2::NetworkAclEntry' 300 | Properties: 301 | NetworkAclId: !Ref NetworkAclPrivate 302 | RuleNumber: 99 303 | Protocol: -1 304 | RuleAction: allow 305 | Egress: false 306 | CidrBlock: '0.0.0.0/0' 307 | 308 | NetworkAclEntryOutPrivateAllowVPC: 309 | Type: 'AWS::EC2::NetworkAclEntry' 310 | Properties: 311 | NetworkAclId: !Ref NetworkAclPrivate 312 | RuleNumber: 99 313 | Protocol: -1 314 | RuleAction: allow 315 | Egress: true 316 | CidrBlock: '0.0.0.0/0' 317 | 318 | SecretSecurityGroup: 319 | Type: AWS::EC2::SecurityGroup 320 | Properties: 321 | GroupDescription: !Join [ " - ", [ "Security group for Secrets Access ENIs", !Ref 'AWS::StackName' ] ] 322 | VpcId: !Ref VPC 323 | SecurityGroupIngress: 324 | - IpProtocol: -1 325 | CidrIp: !Sub '10.${ClassB}.0.0/16' 326 | Tags: 327 | - Key: Name 328 | Value: !Sub '${AWS::StackName}-SecretSecurityGroup' 329 | SecurityGroupEgress: 330 | - IpProtocol: -1 331 | FromPort: 0 332 | ToPort: 65535 333 | CidrIp: '0.0.0.0/0' 334 | 335 | 336 | SecretsManagerVPCEndpoint: 337 | Type: 'AWS::EC2::VPCEndpoint' 338 | Properties: 339 | VpcEndpointType: 'Interface' 340 | PrivateDnsEnabled: true 341 | VpcId: !Ref VPC 342 | SubnetIds: 343 | - !Ref SubnetAPrivate 344 | - !Ref SubnetBPrivate 345 | - !Ref SubnetCPrivate 346 | SecurityGroupIds: 347 | - !Ref SecretSecurityGroup 348 | ServiceName: !Join 349 | - '' 350 | - - com.amazonaws. 351 | - !Ref 'AWS::Region' 352 | - .secretsmanager 353 | 354 | #============================================================================# 355 | # Code Commit 356 | #============================================================================# 357 | 358 | CodeCommitRepository: 359 | Type: AWS::CodeCommit::Repository 360 | Properties: 361 | RepositoryDescription: Empty Code Commit Repository for GitOps workshop 362 | RepositoryName: ack-rds-gitops-workshop 363 | 364 | 365 | #============================================================================# 366 | # Setting up AWSQS 367 | #============================================================================# 368 | 369 | CFNExecutionRole: 370 | Type: AWS::IAM::Role 371 | Properties: 372 | MaxSessionDuration: 8400 373 | AssumeRolePolicyDocument: 374 | Version: '2012-10-17' 375 | Statement: 376 | - Effect: Allow 377 | Principal: 378 | Service: [resources.cloudformation.amazonaws.com, cloudformation.amazonaws.com, lambda.amazonaws.com, ec2.amazonaws.com] 379 | Action: sts:AssumeRole 380 | Path: "/" 381 | Policies: 382 | - PolicyName: ResourceTypePolicy 383 | PolicyDocument: 384 | Version: '2012-10-17' 385 | Statement: 386 | - Effect: Allow 387 | Action: 388 | - "sts:GetCallerIdentity" 389 | - "eks:CreateCluster" 390 | - "eks:DeleteCluster" 391 | - "eks:DescribeCluster" 392 | - "eks:ListTagsForResource" 393 | - "eks:UpdateClusterVersion" 394 | - "eks:UpdateClusterConfig" 395 | - "eks:TagResource" 396 | - "eks:UntagResource" 397 | - "iam:PassRole" 398 | - "sts:AssumeRole" 399 | - "lambda:UpdateFunctionConfiguration" 400 | - "lambda:DeleteFunction" 401 | - "lambda:GetFunction" 402 | - "lambda:InvokeFunction" 403 | - "lambda:CreateFunction" 404 | - "lambda:UpdateFunctionCode" 405 | - "ec2:DescribeVpcs" 406 | - "ec2:DescribeSubnets" 407 | - "ec2:DescribeSecurityGroups" 408 | - "kms:CreateGrant" 409 | - "kms:DescribeKey" 410 | - "logs:CreateLogGroup" 411 | - "logs:CreateLogStream" 412 | - "logs:DescribeLogGroups" 413 | - "logs:DescribeLogStreams" 414 | - "logs:PutLogEvents" 415 | - "cloudwatch:ListMetrics" 416 | - "cloudwatch:PutMetricData" 417 | Resource: "*" 418 | 419 | AWSQSGrants: 420 | Type: Custom::AWSQSGrants 421 | Properties: 422 | ServiceToken: !GetAtt 'AWSQSGrantsLambda.Arn' 423 | awsqs_execution_role: !GetAtt CFNExecutionRole.Arn 424 | 425 | AWSQSGrantsRole: 426 | Type: AWS::IAM::Role 427 | DependsOn: 428 | - CFNExecutionRole 429 | Properties: 430 | AssumeRolePolicyDocument: 431 | Version: '2012-10-17' 432 | Statement: 433 | - Effect: Allow 434 | Principal: 435 | Service: lambda.amazonaws.com 436 | Action: sts:AssumeRole 437 | ManagedPolicyArns: 438 | - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 439 | Path: / 440 | Policies: 441 | - PolicyName: AWSQSGrants-policy 442 | PolicyDocument: 443 | Version: '2012-10-17' 444 | Statement: 445 | - Effect: Allow 446 | Action: 447 | - eks:DescribeCluster 448 | - iam:CreateRole 449 | - iam:AttachRolePolicy 450 | - iam:GetRole 451 | - iam:CreatePolicy 452 | - ec2:DescribeSubnets 453 | - memorydb:CreateSubnetGroup 454 | - cloudformation:* 455 | Resource: 456 | - '*' 457 | - Effect: Allow 458 | Action: 459 | - iam:ListInstanceProfiles 460 | - iam:PassRole 461 | Resource: "arn:aws:iam::*:role/*" 462 | 463 | AWSQSGrantsLambda: 464 | Type: AWS::Lambda::Function 465 | Properties: 466 | Description: Adding AWSQS public resource to cloudformation 467 | Handler: index.lambda_handler 468 | Runtime: python3.9 469 | Role: !GetAtt 'AWSQSGrantsRole.Arn' 470 | Timeout: 300 471 | Code: 472 | ZipFile: | 473 | import boto3 474 | import sys 475 | import os 476 | import urllib.request 477 | import cfnresponse 478 | import traceback 479 | from botocore.exceptions import ClientError 480 | 481 | def lambda_handler(event, context): 482 | status = cfnresponse.SUCCESS 483 | data = {} 484 | execution_role = event['ResourceProperties']['awsqs_execution_role'] 485 | print(execution_role) 486 | print(event) 487 | try: 488 | client = boto3.client("cloudformation") 489 | response = client.activate_type( 490 | Type='RESOURCE', 491 | TypeName='AWSQS::EKS::Cluster', 492 | PublisherId='408988dff9e863704bcc72e7e13f8d645cee8311', 493 | AutoUpdate=True, 494 | ExecutionRoleArn= execution_role) 495 | 496 | except Exception as e: 497 | logging.error(e, exc_info=True) 498 | data = {'Error': str(traceback.format_exc(e))} 499 | status = cfnresponse.FAILED 500 | cfnresponse.send(event, context, status, data, 'CustomResourcePhysicalID') 501 | 502 | EKSControlPlane: 503 | Type: AWS::CloudFormation::Stack 504 | DependsOn: [AWSQSGrants] 505 | Properties: 506 | TemplateURL: !Sub 'https://s3.amazonaws.com/${AssetsBucketName}/${AssetsBucketPrefix}ack-rds-cfn-eks.yaml' 507 | Parameters: 508 | VPC: !Ref VPC 509 | SubnetAPrivate: !Ref SubnetAPrivate 510 | SubnetBPrivate: !Ref SubnetBPrivate 511 | SubnetCPrivate: !Ref SubnetCPrivate 512 | IsWorkshopStudioEnv: !Ref IsWorkshopStudioEnv 513 | C9InstanceType: !Ref C9InstanceType 514 | SubnetAPublic: !Ref SubnetAPublic 515 | 516 | Outputs: 517 | TemplateID: 518 | Description: 'Template ID' 519 | Value: 'DAT312' 520 | 521 | Region: 522 | Description: 'Region' 523 | Value: !Sub '${AWS::Region}' 524 | 525 | StackName: 526 | Description: 'Stack name' 527 | Value: !Sub '${AWS::StackName}' 528 | 529 | VPC: 530 | Description: 'VPC' 531 | Value: !Ref VPC 532 | Export: 533 | Name: !Sub '${AWS::StackName}-VPC' 534 | 535 | SubnetAPrivate: 536 | Description: 'SubnetAPrivate' 537 | Value: !Ref SubnetAPrivate 538 | 539 | SubnetBPrivate: 540 | Description: 'SubnetBPrivate' 541 | Value: !Ref SubnetBPrivate 542 | 543 | SubnetCPrivate: 544 | Description: 'SubnetCPrivate' 545 | Value: !Ref SubnetCPrivate 546 | 547 | SecretsManagerVPCEndpoint: 548 | Description: Secrets Manager VPC Endpoint 549 | Value: !Ref SecretsManagerVPCEndpoint 550 | Export: 551 | Name: !Sub '${AWS::StackName}-SecretsManagerVPCEndpoint' 552 | 553 | ApplicationName: 554 | Description: Name of the Application 555 | Value: !Ref Application 556 | 557 | WorkShopStudio: 558 | Description: Running on WorkshopStudio 559 | Value: !Ref IsWorkshopStudioEnv 560 | -------------------------------------------------------------------------------- /apps/src/eks/eksackapp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: eksack 5 | labels: 6 | app: eksack 7 | --- 8 | kind: StorageClass 9 | apiVersion: storage.k8s.io/v1 10 | metadata: 11 | name: efs-sc 12 | provisioner: efs.csi.aws.com 13 | --- 14 | apiVersion: v1 15 | kind: Secret 16 | metadata: 17 | name: eksack-secret 18 | namespace: eksack 19 | type: Opaque 20 | data: 21 | database_password: ZWtzZ2RiZGVtbw== 22 | database_user: ZGJ1c2VyMQ== 23 | database_host: cmRzcGcxLmN0ajkzdXBjcGNudy51cy1lYXN0LTIucmRzLmFtYXpvbmF3cy5jb20= 24 | database_port: NTQzMg== 25 | database_db_name: ZWtzYWNrZGVtbw== 26 | database_rodb_name: ZWtzYWNrZGVtbw== 27 | secret_key: ZWtzZ2RiZGVtb3NlY3JldA== 28 | authtoken: ZWtzZ2RiZGVtb3Rva2Vu 29 | kart_service: aHR0cDovL2thcnQuYWNrZWNrLnN2Yy5jbHVzdGVyLmxvY2FsOjg0NDU= 30 | product_service: aHR0cDovL3Byb2R1Y3QuZWtzYWNrLnN2Yy5jbHVzdGVyLmxvY2FsOjg0NDQ= 31 | user_service: aHR0cDovL3VzZXIuZWtzYWNrLnN2Yy5jbHVzdGVyLmxvY2FsOjg0NDY= 32 | order_service: aHR0cDovL29yZGVyLmVrc2Fjay5zdmMuY2x1c3Rlci5sb2NhbDo4NDQ4 33 | memdb_host: Y2x1c3RlcmNmZy5tZW1kYjEucnlrb3N3Lm1lbW9yeWRiLnVzLWVhc3QtMi5hbWF6b25hd3MuY29t 34 | memdb_port: NjM3OQ== 35 | memdb_user: ZGVmYXVsdA== 36 | memdb_pass: ZGVmYXVsdA== 37 | --- 38 | apiVersion: apps/v1 39 | kind: Deployment 40 | metadata: 41 | name: kart-deployment 42 | namespace: eksack 43 | labels: 44 | app: eksack 45 | service: kart 46 | spec: 47 | replicas: 2 48 | strategy: 49 | rollingUpdate: 50 | maxSurge: 3 51 | maxUnavailable: 0 52 | selector: 53 | matchLabels: 54 | app: eksack 55 | service: kart 56 | template: 57 | metadata: 58 | labels: 59 | app: eksack 60 | service: kart 61 | spec: 62 | restartPolicy: Always 63 | containers: 64 | - name: kart 65 | image: 194322967097.dkr.ecr.us-east-2.amazonaws.com/eksack/kart:1.5 66 | imagePullPolicy: Always 67 | ports: 68 | - containerPort: 8445 69 | env: 70 | - name: DATABASE_HOST 71 | valueFrom: 72 | secretKeyRef: 73 | name: eksack-secret 74 | key: database_host 75 | - name: DATABASE_PORT 76 | valueFrom: 77 | secretKeyRef: 78 | name: eksack-secret 79 | key: database_port 80 | - name: DATABASE_USER 81 | valueFrom: 82 | secretKeyRef: 83 | name: eksack-secret 84 | key: database_user 85 | - name: DATABASE_PASSWORD 86 | valueFrom: 87 | secretKeyRef: 88 | name: eksack-secret 89 | key: database_password 90 | - name: DATABASE_DB_NAME 91 | valueFrom: 92 | secretKeyRef: 93 | name: eksack-secret 94 | key: database_db_name 95 | - name: DATABASE_RODB_NAME 96 | valueFrom: 97 | secretKeyRef: 98 | name: eksack-secret 99 | key: database_rodb_name 100 | - name: SECRET_KEY 101 | valueFrom: 102 | secretKeyRef: 103 | name: eksack-secret 104 | key: secret_key 105 | - name: AUTHTOKEN 106 | valueFrom: 107 | secretKeyRef: 108 | name: eksack-secret 109 | key: authtoken 110 | - name: MEMDB_HOST 111 | valueFrom: 112 | secretKeyRef: 113 | name: eksack-secret 114 | key: memdb_host 115 | - name: MEMDB_PORT 116 | valueFrom: 117 | secretKeyRef: 118 | name: eksack-secret 119 | key: memdb_port 120 | - name: MEMDB_USER 121 | valueFrom: 122 | secretKeyRef: 123 | name: eksack-secret 124 | key: memdb_user 125 | - name: MEMDB_PASS 126 | valueFrom: 127 | secretKeyRef: 128 | name: eksack-secret 129 | key: memdb_pass 130 | --- 131 | apiVersion: apps/v1 132 | kind: Deployment 133 | metadata: 134 | name: product-deployment 135 | namespace: eksack 136 | labels: 137 | app: eksack 138 | service: product 139 | spec: 140 | replicas: 2 141 | strategy: 142 | rollingUpdate: 143 | maxSurge: 3 144 | maxUnavailable: 0 145 | selector: 146 | matchLabels: 147 | app: eksack 148 | service: product 149 | template: 150 | metadata: 151 | labels: 152 | app: eksack 153 | service: product 154 | spec: 155 | restartPolicy: Always 156 | containers: 157 | - name: product 158 | image: 194322967097.dkr.ecr.us-east-2.amazonaws.com/eksack/product:1.5 159 | imagePullPolicy: Always 160 | ports: 161 | - containerPort: 8444 162 | env: 163 | - name: DATABASE_HOST 164 | valueFrom: 165 | secretKeyRef: 166 | name: eksack-secret 167 | key: database_host 168 | - name: DATABASE_PORT 169 | valueFrom: 170 | secretKeyRef: 171 | name: eksack-secret 172 | key: database_port 173 | - name: DATABASE_USER 174 | valueFrom: 175 | secretKeyRef: 176 | name: eksack-secret 177 | key: database_user 178 | - name: DATABASE_PASSWORD 179 | valueFrom: 180 | secretKeyRef: 181 | name: eksack-secret 182 | key: database_password 183 | - name: DATABASE_DB_NAME 184 | valueFrom: 185 | secretKeyRef: 186 | name: eksack-secret 187 | key: database_db_name 188 | - name: DATABASE_RODB_NAME 189 | valueFrom: 190 | secretKeyRef: 191 | name: eksack-secret 192 | key: database_rodb_name 193 | - name: SECRET_KEY 194 | valueFrom: 195 | secretKeyRef: 196 | name: eksack-secret 197 | key: secret_key 198 | - name: AUTHTOKEN 199 | valueFrom: 200 | secretKeyRef: 201 | name: eksack-secret 202 | key: authtoken 203 | - name: MEMDB_HOST 204 | valueFrom: 205 | secretKeyRef: 206 | name: eksack-secret 207 | key: memdb_host 208 | - name: MEMDB_PORT 209 | valueFrom: 210 | secretKeyRef: 211 | name: eksack-secret 212 | key: memdb_port 213 | - name: MEMDB_USER 214 | valueFrom: 215 | secretKeyRef: 216 | name: eksack-secret 217 | key: memdb_user 218 | - name: MEMDB_PASS 219 | valueFrom: 220 | secretKeyRef: 221 | name: eksack-secret 222 | key: memdb_pass 223 | --- 224 | apiVersion: apps/v1 225 | kind: Deployment 226 | metadata: 227 | name: user-deployment 228 | namespace: eksack 229 | labels: 230 | app: eksack 231 | service: user 232 | spec: 233 | replicas: 2 234 | strategy: 235 | rollingUpdate: 236 | maxSurge: 3 237 | maxUnavailable: 0 238 | selector: 239 | matchLabels: 240 | app: eksack 241 | service: user 242 | template: 243 | metadata: 244 | labels: 245 | app: eksack 246 | service: user 247 | spec: 248 | restartPolicy: Always 249 | containers: 250 | - name: user 251 | image: 194322967097.dkr.ecr.us-east-2.amazonaws.com/eksack/user:1.5 252 | imagePullPolicy: Always 253 | ports: 254 | - containerPort: 8446 255 | env: 256 | - name: DATABASE_HOST 257 | valueFrom: 258 | secretKeyRef: 259 | name: eksack-secret 260 | key: database_host 261 | - name: DATABASE_PORT 262 | valueFrom: 263 | secretKeyRef: 264 | name: eksack-secret 265 | key: database_port 266 | - name: DATABASE_USER 267 | valueFrom: 268 | secretKeyRef: 269 | name: eksack-secret 270 | key: database_user 271 | - name: DATABASE_PASSWORD 272 | valueFrom: 273 | secretKeyRef: 274 | name: eksack-secret 275 | key: database_password 276 | - name: DATABASE_DB_NAME 277 | valueFrom: 278 | secretKeyRef: 279 | name: eksack-secret 280 | key: database_db_name 281 | - name: DATABASE_RODB_NAME 282 | valueFrom: 283 | secretKeyRef: 284 | name: eksack-secret 285 | key: database_rodb_name 286 | - name: SECRET_KEY 287 | valueFrom: 288 | secretKeyRef: 289 | name: eksack-secret 290 | key: secret_key 291 | - name: AUTHTOKEN 292 | valueFrom: 293 | secretKeyRef: 294 | name: eksack-secret 295 | key: authtoken 296 | - name: MEMDB_HOST 297 | valueFrom: 298 | secretKeyRef: 299 | name: eksack-secret 300 | key: memdb_host 301 | - name: MEMDB_PORT 302 | valueFrom: 303 | secretKeyRef: 304 | name: eksack-secret 305 | key: memdb_port 306 | - name: MEMDB_USER 307 | valueFrom: 308 | secretKeyRef: 309 | name: eksack-secret 310 | key: memdb_user 311 | - name: MEMDB_PASS 312 | valueFrom: 313 | secretKeyRef: 314 | name: eksack-secret 315 | key: memdb_pass 316 | --- 317 | apiVersion: apps/v1 318 | kind: Deployment 319 | metadata: 320 | name: webapp 321 | namespace: eksack 322 | labels: 323 | app: eksack 324 | service: webapp 325 | spec: 326 | replicas: 2 327 | strategy: 328 | rollingUpdate: 329 | maxSurge: 2 330 | maxUnavailable: 0 331 | selector: 332 | matchLabels: 333 | app: eksack 334 | service: webapp 335 | template: 336 | metadata: 337 | labels: 338 | app: eksack 339 | service: webapp 340 | spec: 341 | restartPolicy: Always 342 | containers: 343 | - name: webapp 344 | image: 194322967097.dkr.ecr.us-east-2.amazonaws.com/eksack/webapp:1.5 345 | imagePullPolicy: Always 346 | ports: 347 | - containerPort: 8443 348 | env: 349 | - name: DATABASE_HOST 350 | valueFrom: 351 | secretKeyRef: 352 | name: eksack-secret 353 | key: database_host 354 | - name: DATABASE_PORT 355 | valueFrom: 356 | secretKeyRef: 357 | name: eksack-secret 358 | key: database_port 359 | - name: DATABASE_USER 360 | valueFrom: 361 | secretKeyRef: 362 | name: eksack-secret 363 | key: database_user 364 | - name: DATABASE_PASSWORD 365 | valueFrom: 366 | secretKeyRef: 367 | name: eksack-secret 368 | key: database_password 369 | - name: DATABASE_DB_NAME 370 | valueFrom: 371 | secretKeyRef: 372 | name: eksack-secret 373 | key: database_db_name 374 | - name: DATABASE_RODB_NAME 375 | valueFrom: 376 | secretKeyRef: 377 | name: eksack-secret 378 | key: database_rodb_name 379 | - name: SECRET_KEY 380 | valueFrom: 381 | secretKeyRef: 382 | name: eksack-secret 383 | key: secret_key 384 | - name: AUTHTOKEN 385 | valueFrom: 386 | secretKeyRef: 387 | name: eksack-secret 388 | key: authtoken 389 | - name: PRODUCTS_SERVICE 390 | valueFrom: 391 | secretKeyRef: 392 | name: eksack-secret 393 | key: product_service 394 | - name: KART_SERVICE 395 | valueFrom: 396 | secretKeyRef: 397 | name: eksack-secret 398 | key: kart_service 399 | - name: USER_SERVICE 400 | valueFrom: 401 | secretKeyRef: 402 | name: eksack-secret 403 | key: user_service 404 | - name: ORDER_SERVICE 405 | valueFrom: 406 | secretKeyRef: 407 | name: eksack-secret 408 | key: order_service 409 | - name: MEMDB_HOST 410 | valueFrom: 411 | secretKeyRef: 412 | name: eksack-secret 413 | key: memdb_host 414 | - name: MEMDB_PORT 415 | valueFrom: 416 | secretKeyRef: 417 | name: eksack-secret 418 | key: memdb_port 419 | - name: MEMDB_USER 420 | valueFrom: 421 | secretKeyRef: 422 | name: eksack-secret 423 | key: memdb_user 424 | - name: MEMDB_PASS 425 | valueFrom: 426 | secretKeyRef: 427 | name: eksack-secret 428 | key: memdb_pass 429 | --- 430 | apiVersion: apps/v1 431 | kind: Deployment 432 | metadata: 433 | name: order-deployment 434 | namespace: eksack 435 | labels: 436 | app: eksack 437 | service: order 438 | spec: 439 | replicas: 2 440 | strategy: 441 | rollingUpdate: 442 | maxSurge: 3 443 | maxUnavailable: 0 444 | selector: 445 | matchLabels: 446 | app: eksack 447 | service: order 448 | template: 449 | metadata: 450 | labels: 451 | app: eksack 452 | service: order 453 | spec: 454 | restartPolicy: Always 455 | containers: 456 | - name: order 457 | image: 194322967097.dkr.ecr.us-east-2.amazonaws.com/eksack/order:1.5 458 | imagePullPolicy: Always 459 | ports: 460 | - containerPort: 8448 461 | env: 462 | - name: DATABASE_HOST 463 | valueFrom: 464 | secretKeyRef: 465 | name: eksack-secret 466 | key: database_host 467 | - name: DATABASE_PORT 468 | valueFrom: 469 | secretKeyRef: 470 | name: eksack-secret 471 | key: database_port 472 | - name: DATABASE_USER 473 | valueFrom: 474 | secretKeyRef: 475 | name: eksack-secret 476 | key: database_user 477 | - name: DATABASE_PASSWORD 478 | valueFrom: 479 | secretKeyRef: 480 | name: eksack-secret 481 | key: database_password 482 | - name: DATABASE_DB_NAME 483 | valueFrom: 484 | secretKeyRef: 485 | name: eksack-secret 486 | key: database_db_name 487 | - name: DATABASE_RODB_NAME 488 | valueFrom: 489 | secretKeyRef: 490 | name: eksack-secret 491 | key: database_rodb_name 492 | - name: SECRET_KEY 493 | valueFrom: 494 | secretKeyRef: 495 | name: eksack-secret 496 | key: secret_key 497 | - name: AUTHTOKEN 498 | valueFrom: 499 | secretKeyRef: 500 | name: eksack-secret 501 | key: authtoken 502 | - name: MEMDB_HOST 503 | valueFrom: 504 | secretKeyRef: 505 | name: eksack-secret 506 | key: memdb_host 507 | - name: MEMDB_PORT 508 | valueFrom: 509 | secretKeyRef: 510 | name: eksack-secret 511 | key: memdb_port 512 | - name: MEMDB_USER 513 | valueFrom: 514 | secretKeyRef: 515 | name: eksack-secret 516 | key: memdb_user 517 | - name: MEMDB_PASS 518 | valueFrom: 519 | secretKeyRef: 520 | name: eksack-secret 521 | key: memdb_pass 522 | --- 523 | apiVersion: v1 524 | kind: Service 525 | metadata: 526 | name: kart 527 | namespace: eksack 528 | labels: 529 | app: eksack 530 | service: kart 531 | spec: 532 | type: ClusterIP 533 | selector: 534 | app: eksack 535 | service: kart 536 | ports: 537 | - name: kart 538 | protocol: TCP 539 | port: 8445 540 | targetPort: 8445 541 | --- 542 | apiVersion: v1 543 | kind: Service 544 | metadata: 545 | name: product 546 | namespace: eksack 547 | labels: 548 | app: eksack 549 | service: product 550 | spec: 551 | type: ClusterIP 552 | selector: 553 | app: eksack 554 | service: product 555 | ports: 556 | - name: product 557 | protocol: TCP 558 | port: 8444 559 | targetPort: 8444 560 | --- 561 | apiVersion: v1 562 | kind: Service 563 | metadata: 564 | name: user 565 | namespace: eksack 566 | labels: 567 | app: eksack 568 | service: user 569 | spec: 570 | type: ClusterIP 571 | selector: 572 | app: eksack 573 | service: user 574 | ports: 575 | - name: user 576 | protocol: TCP 577 | port: 8446 578 | targetPort: 8446 579 | --- 580 | apiVersion: v1 581 | kind: Service 582 | metadata: 583 | name: order 584 | namespace: eksack 585 | labels: 586 | app: eksack 587 | service: order 588 | spec: 589 | type: ClusterIP 590 | selector: 591 | app: eksack 592 | service: order 593 | ports: 594 | - name: order 595 | protocol: TCP 596 | port: 8448 597 | targetPort: 8448 598 | --- 599 | apiVersion: v1 600 | kind: Service 601 | metadata: 602 | name: webappnp 603 | namespace: eksack 604 | labels: 605 | service: webappnp 606 | spec: 607 | type: NodePort 608 | selector: 609 | app: eksack 610 | service: webapp 611 | ports: 612 | - port: 80 613 | targetPort: 8443 614 | protocol: TCP 615 | --- 616 | apiVersion: networking.k8s.io/v1 617 | kind: Ingress 618 | metadata: 619 | name: webapp 620 | namespace: eksack 621 | labels: 622 | app: eksack 623 | service: webapp 624 | annotations: 625 | kubernetes.io/ingress.class: alb 626 | alb.ingress.kubernetes.io/scheme: internet-facing 627 | alb.ingress.kubernetes.io/tags: Application=webapp 628 | alb.ingress.kubernetes.io/target-type: instance 629 | alb.ingress.kubernetes.io/healthcheck-protocol: HTTP 630 | alb.ingress.kubernetes.io/healthcheck-port: traffic-port 631 | alb.ingress.kubernetes.io/healthcheck-path: /healthcheck 632 | alb.ingress.kubernetes.io/healthcheck-interval-seconds: '60' 633 | alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '30' 634 | alb.ingress.kubernetes.io/success-codes: '200' 635 | alb.ingress.kubernetes.io/healthy-threshold-count: '2' 636 | alb.ingress.kubernetes.io/unhealthy-threshold-count: '5' 637 | alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true, load_balancing.algorithm.type=least_outstanding_requests 638 | spec: 639 | rules: 640 | - http: 641 | paths: 642 | - path: / 643 | pathType: Prefix 644 | backend: 645 | service: 646 | name: webappnp 647 | port: 648 | number: 80 649 | --- 650 | --------------------------------------------------------------------------------