├── src ├── __init__.py ├── requirements.txt ├── main.py ├── test_gunicorn.py └── test_app.py ├── README.md ├── ops ├── 0-tf-secret.yaml ├── 1-tf-configmap.yaml ├── 3-tf-service.yaml ├── 8-tf-knative-service-demo.yaml ├── 9-tf-knative-virtualservice.yaml ├── 4-tf-ingress.yaml ├── 7-tf-knative-service.yaml ├── 2-tf-deployment.yaml ├── 5-tf-statefulset.yaml └── 6-tf-redis.yaml ├── Dockerfile ├── tf.code-workspace ├── reference.md ├── infra └── main.tf ├── .github └── workflows │ ├── py-rnd.yml │ ├── container-builld-push.yaml │ ├── k8s-apply.yaml │ ├── infra-sync.yaml │ └── infra-destroy.yaml ├── LICENSE ├── scripts └── install-knative.sh └── .gitignore /src/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | uvicorn 3 | gunicorn 4 | requests 5 | pytest 6 | httpx 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tf-python 2 | A simple hello world Python application. 3 | 4 | Review branches to see various code states for the related project. 5 | -------------------------------------------------------------------------------- /ops/0-tf-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: tf-python-secret 5 | stringData: 6 | SECRET_MESSAGE: Not a good secret -------------------------------------------------------------------------------- /ops/1-tf-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: tf-python-cm 5 | data: 6 | ENV_MESSAGE: This is a configmap! 7 | MG: abc -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11 2 | 3 | COPY ./src /app 4 | WORKDIR /app 5 | 6 | RUN pip install -r requirements.txt 7 | 8 | # CMD ["python", "-m", "http.server", "8080"] 9 | 10 | CMD ["gunicorn", "main:app", "--workers", "1", "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8080"] -------------------------------------------------------------------------------- /ops/3-tf-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: tf-python 5 | spec: 6 | type: ClusterIP # delete my nodebalancer from Linode 7 | ports: 8 | - name: http 9 | port: 80 10 | targetPort: 8080 11 | protocol: TCP 12 | selector: 13 | app: tf-python -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from fastapi import FastAPI 3 | 4 | app = FastAPI() 5 | 6 | def get_env_message(): 7 | return os.environ.get("ENV_MESSAGE") or "Nothing to report" 8 | 9 | 10 | def get_secret_message(): 11 | return os.environ.get("SECRET_MESSAGE") or "Nothing lurking" 12 | 13 | 14 | @app.get("/") 15 | def home_view(): 16 | return {"hello": "world", "cron": "smooth-cronjob", "watchtower": "working", "env-message": get_env_message(), "secret-message": get_secret_message()} 17 | -------------------------------------------------------------------------------- /ops/8-tf-knative-service-demo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: demo 5 | --- 6 | apiVersion: serving.knative.dev/v1 7 | kind: Service 8 | metadata: 9 | name: tf-python 10 | namespace: demo # tf-python.demo.svc.cluster.local # tf-python.demo.pythonkeras.com 11 | spec: 12 | template: 13 | spec: 14 | containers: 15 | - name: cfe-nginx-c 16 | image: codingforentrepreneurs/cfe-nginx:latest 17 | ports: 18 | - containerPort: 80 -------------------------------------------------------------------------------- /tf.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "files.autoSave": "afterDelay", 9 | "terminal.integrated.env.osx": { 10 | "KUBECONFIG": "${workspaceFolder}/.kube/kubeconfig.yaml", 11 | "KUBE_EDITOR": "nano", 12 | }, 13 | "terminal.integrated.env.windows": { 14 | "KUBECONFIG": "${workspaceFolder}\\.kube\\kubeconfig.yaml" 15 | }, 16 | "terminal.integrated.env.linux": { 17 | "KUBECONFIG": "${workspaceFolder}/.kube/kubeconfig.yaml" 18 | }, 19 | } 20 | } -------------------------------------------------------------------------------- /reference.md: -------------------------------------------------------------------------------- 1 | ```dockerfile 2 | FROM some_image:some_tag 3 | 4 | COPY ./from/local/path /container/dest/path 5 | WORKDIR /container/dest/path 6 | 7 | # install anything 8 | RUN apt-get install -y nginx 9 | 10 | CMD ["what", "command", "to", "run", "by", "default"] 11 | ``` 12 | 13 | 14 | 15 | ``` 16 | docker build -t tf-python -f Dockerfile . 17 | ``` 18 | 19 | 20 | ``` 21 | docker run -p 8080:8080 --rm --name my-tf-python tf-python 22 | ``` 23 | 24 | ``` 25 | docker ps 26 | ``` 27 | 28 | ``` 29 | docker exec -it my-tf-python /bin/bash 30 | ``` 31 | 32 | ``` 33 | docker run -e ENV_MESSAGE="hello from the cli" -p 8080:8080 --rm --name my-tf-python tf-python 34 | ``` -------------------------------------------------------------------------------- /infra/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.15" 3 | required_providers { 4 | linode = { 5 | source = "linode/linode" 6 | # version = "1.30.0" 7 | } 8 | } 9 | backend "s3" {} # object storage 10 | } 11 | 12 | provider "linode" { 13 | token = var.linode_api_token 14 | } 15 | 16 | variable "linode_api_token" { 17 | description = "Your Linode API Personal Access Token. (required)" 18 | sensitive = true 19 | } 20 | 21 | resource "linode_lke_cluster" "terraform_k8s" { 22 | k8s_version="1.26" 23 | label="tf-k8s" 24 | region="us-east" 25 | tags=["tf-k8s"] 26 | pool { 27 | type = "g6-standard-4" 28 | count = 3 29 | } 30 | } -------------------------------------------------------------------------------- /ops/9-tf-knative-virtualservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: VirtualService 3 | metadata: 4 | name: tf-python-root 5 | namespace: apps 6 | spec: 7 | gateways: 8 | - knative-shared-gateway.knative-serving.svc.cluster.local 9 | - knative-serving/knative-ingress-gateway 10 | hosts: 11 | - pythonkeras.com 12 | - www.pythonkeras.com 13 | http: 14 | - name: http-route 15 | match: 16 | - uri: 17 | prefix: "/" # http://tf-python.apps.pythonkeras.com/ 18 | rewrite: 19 | authority: tf-python.apps.pythonkeras.com 20 | route: 21 | - destination: 22 | host: tf-python.apps.svc.cluster.local 23 | port: 24 | number: 80 25 | weight: 100 -------------------------------------------------------------------------------- /.github/workflows/py-rnd.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Test Python Application 5 | 6 | on: 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | defaults: 16 | run: 17 | working-directory: src/ 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Set up Python 3.10 21 | uses: actions/setup-python@v3 22 | with: 23 | python-version: "3.10" 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip pytest 27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 28 | - name: Run tests 29 | run: | 30 | pytest 31 | -------------------------------------------------------------------------------- /ops/4-tf-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: tf-ingress 5 | annotations: 6 | kubernetes.io/ingress.class: nginx 7 | # cert-manager.io/cluster-issuer: "letsencrypt" 8 | # nginx.ingress.kubernetes.io/force-ssl-redirect: "true" 9 | # nginx.ingress.kubernetes.io/ssl-passthrough: "true" 10 | spec: 11 | rules: 12 | - host: www.pythonkeras.com 13 | http: 14 | paths: 15 | - path: / 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: tf-python 20 | port: 21 | name: http 22 | - host: pythonkeras.com 23 | http: 24 | paths: 25 | - path: / 26 | pathType: Prefix 27 | backend: 28 | service: 29 | name: tf-python 30 | port: 31 | name: http 32 | -------------------------------------------------------------------------------- /src/test_gunicorn.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import time 4 | 5 | import requests 6 | 7 | def test_gunicorn_start(): 8 | gunicorn_process = subprocess.Popen( 9 | ["gunicorn", "-w", "1", "-k", "uvicorn.workers.UvicornWorker", "main:app", "-b", "127.0.0.1:8000"] 10 | ) 11 | time.sleep(2) # Give Gunicorn some time to start 12 | 13 | try: 14 | response = requests.get("http://127.0.0.1:8000") 15 | assert response.status_code == 200 16 | assert response.json() == { 17 | "hello": "world", 18 | "cron": "smooth-cronjob", 19 | "watchtower": "working", 20 | "env-message": os.environ.get("ENV_MESSAGE") or "Nothing to report", 21 | "secret-message": os.environ.get("SECRET_MESSAGE") or "Nothing lurking" 22 | } 23 | finally: 24 | gunicorn_process.terminate() 25 | gunicorn_process.wait() 26 | -------------------------------------------------------------------------------- /.github/workflows/container-builld-push.yaml: -------------------------------------------------------------------------------- 1 | name: Build Docker Container & Push to Docker Hub 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | docker: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | - name: Set up QEMU 13 | uses: docker/setup-qemu-action@v2 14 | - name: Set up Docker Buildx 15 | uses: docker/setup-buildx-action@v2 16 | - name: Login to DockerHub 17 | uses: docker/login-action@v2 18 | with: 19 | username: ${{ secrets.DOCKERHUB_USERNAME }} 20 | password: ${{ secrets.DOCKERHUB_TOKEN }} 21 | - name: Build web container image 22 | run: | 23 | docker build -f Dockerfile \ 24 | -t jmitchel3/tf-python:latest \ 25 | -t jmitchel3/tf-python:${GITHUB_SHA::7}-${GITHUB_RUN_ID::5} \ 26 | . 27 | - name: Push container 28 | run: | 29 | docker push jmitchel3/tf-python --all-tags -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Coding For Entrepreneurs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test_app.py: -------------------------------------------------------------------------------- 1 | import os 2 | from fastapi.testclient import TestClient 3 | import pytest 4 | 5 | # Assuming your FastAPI code is in a file named `main.py` 6 | from .main import app, get_env_message, get_secret_message 7 | 8 | client = TestClient(app) 9 | 10 | def test_home_view(): 11 | response = client.get("/") 12 | 13 | assert response.status_code == 200 14 | assert response.json() == { 15 | "hello": "world", 16 | "cron": "smooth-cronjob", 17 | "watchtower": "working", 18 | "env-message": get_env_message(), 19 | "secret-message": get_secret_message(), 20 | } 21 | 22 | @pytest.fixture(autouse=True) 23 | def clear_env_message(monkeypatch): 24 | monkeypatch.delenv("SECRET_MESSAGE", raising=False) 25 | monkeypatch.delenv("ENV_MESSAGE", raising=False) 26 | 27 | 28 | def test_messages_set(monkeypatch): 29 | monkeypatch.setenv("ENV_MESSAGE", "Test message") 30 | monkeypatch.setenv("SECRET_MESSAGE", "Test secret message") 31 | response = client.get("/") 32 | assert response.status_code == 200 33 | data = response.json() 34 | assert data["env-message"] == "Test message" 35 | assert data["secret-message"] == "Test secret message" 36 | 37 | -------------------------------------------------------------------------------- /ops/7-tf-knative-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: apps 5 | --- 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: tf-python-cm 10 | namespace: apps 11 | data: 12 | ENV_MESSAGE: This is a configmap! 13 | MG: abc 14 | NEW_ONE: abc 15 | 16 | --- 17 | apiVersion: serving.knative.dev/v1 18 | kind: Service 19 | metadata: 20 | name: tf-python 21 | namespace: apps # tf-python.apps.svc.cluster.local 22 | spec: 23 | template: 24 | spec: 25 | containers: 26 | - name: tf-py-container 27 | image: jmitchel3/tf-python:latest 28 | ports: 29 | - containerPort: 8080 30 | env: 31 | - name: VERSION 32 | value: "1.0.1" 33 | - name: ENV_MESSAGE 34 | valueFrom: 35 | configMapKeyRef: 36 | name: tf-python-cm 37 | key: ENV_MESSAGE 38 | # securityContext: 39 | # allowPrivilegeEscalation: false 40 | # runAsNonRoot: false 41 | # capabilities: 42 | # drop: 43 | # - ALL 44 | # seccompProfile: 45 | # type: RuntimeDefault 46 | 47 | -------------------------------------------------------------------------------- /ops/2-tf-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: tf-python 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | app: tf-python 10 | template: 11 | metadata: 12 | labels: 13 | app: tf-python 14 | spec: 15 | containers: 16 | - name: tf-python 17 | image: jmitchel3/tf-python:latest 18 | ports: 19 | - containerPort: 8080 20 | env: 21 | - name: PORT 22 | value: "8080" 23 | - name: VERSION 24 | value: "1.0.0" 25 | - name: KNATIVE_URL 26 | value: "http://tf-python.apps.svc.cluster.local" 27 | - name: ENV_MESSAGE 28 | valueFrom: 29 | configMapKeyRef: 30 | name: tf-python-cm 31 | key: ENV_MESSAGE 32 | - name: SECRET_MESSAGE 33 | valueFrom: 34 | secretKeyRef: 35 | name: tf-python-secret 36 | key: SECRET_MESSAGE 37 | # envFrom: 38 | # - configMapRef: 39 | # name: tf-python-cm 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /scripts/install-knative.sh: -------------------------------------------------------------------------------- 1 | # Get version at https://knative.dev/docs/install/yaml-install/serving/install-serving-with-yaml/ 2 | # 3 | export KNATIVE_VERSION="v1.10.1" # ensure ISTIO install matches this version too 4 | 5 | # Install knative serving 6 | # Ref: https://knative.dev/docs/install/yaml-install/serving/install-serving-with-yaml/#install-the-knative-serving-component 7 | kubectl apply -f https://github.com/knative/serving/releases/download/knative-$KNATIVE_VERSION/serving-crds.yaml 8 | kubectl apply -f https://github.com/knative/serving/releases/download/knative-$KNATIVE_VERSION/serving-core.yaml 9 | 10 | 11 | # install istio 12 | # Ref: https://knative.dev/docs/install/yaml-install/serving/install-serving-with-yaml/#install-a-networking-layer 13 | kubectl apply -l knative.dev/crd-install=true -f https://github.com/knative/net-istio/releases/download/knative-$KNATIVE_VERSION/istio.yaml 14 | kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-$KNATIVE_VERSION/istio.yaml 15 | kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-$KNATIVE_VERSION/net-istio.yaml 16 | 17 | # Confirm installed: 18 | kubectl --namespace istio-system get service istio-ingressgateway 19 | export KNATIVE_INGRESS_IP=$(kubectl --namespace istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 20 | 21 | echo "Your IP Address is: $KNATIVE_INGRESS_IP" 22 | echo "Add a cname record for your domain using the above IP address." -------------------------------------------------------------------------------- /ops/5-tf-statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: tf-python 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: tf-python 10 | template: 11 | metadata: 12 | labels: 13 | app: tf-python 14 | spec: 15 | containers: 16 | - name: tf-python 17 | image: jmitchel3/tf-python:latest 18 | ports: 19 | - containerPort: 8080 20 | env: 21 | - name: PORT 22 | value: "8080" 23 | - name: ENV_MESSAGE 24 | valueFrom: 25 | configMapKeyRef: 26 | name: tf-python-cm 27 | key: ENV_MESSAGE 28 | - name: SECRET_MESSAGE 29 | valueFrom: 30 | secretKeyRef: 31 | name: tf-python-secret 32 | key: SECRET_MESSAGE 33 | volumeMounts: 34 | - name: tf-volume 35 | mountPath: /data 36 | # initContainers: 37 | # - name: delete-existing-data 38 | # image: alpine:latest 39 | # command: ["sh", "-c", "rm -rf /mnt/*"] 40 | # volumeMounts: 41 | # - name: tf-volume 42 | # mountPath: /mnt 43 | volumeClaimTemplates: 44 | - metadata: 45 | name: tf-volume 46 | spec: 47 | accessModes: 48 | - ReadWriteOnce 49 | resources: 50 | requests: 51 | storage: 10Gi 52 | storageClassName: linode-block-storage 53 | -------------------------------------------------------------------------------- /.github/workflows/k8s-apply.yaml: -------------------------------------------------------------------------------- 1 | name: Apply Kubectl 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | apply_k8s: 7 | name: Verify K8s Service Account 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | - uses: azure/setup-kubectl@v3 13 | - name: Create/Verify `.kube` directory 14 | run: mkdir -p ~/.kube/ 15 | - name: Create kubectl config 16 | run: | 17 | cat << EOF >> ~/.kube/kubeconfig.yaml 18 | ${{ secrets.KUBECONFIG }} 19 | EOF 20 | - name: Add Secret 21 | run: | 22 | if [ -f ops/0-tf-secret.yaml ]; then 23 | rm ops/0-tf-secret.yaml 24 | fi 25 | cat << EOF >> ops/0-tf-secret.json 26 | { 27 | "apiVersion": "v1", 28 | "kind": "Secret", 29 | "metadata": { 30 | "name": "tf-python-secret" 31 | }, 32 | "stringData": { 33 | "SECRET_MESSAGE": "${{ secrets.ENV_SECRET_MESSAGE }}" 34 | } 35 | } 36 | EOF 37 | - name: Apply Kubernetes Config 38 | run: | 39 | KUBECONFIG=~/.kube/kubeconfig.yaml kubectl apply -f ops/ 40 | - name: Rollout tf-python 41 | run: | 42 | KUBECONFIG=~/.kube/kubeconfig.yaml kubectl rollout restart deployment/tf-python 43 | - name: Echo deployments 44 | run: | 45 | KUBECONFIG=~/.kube/kubeconfig.yaml kubectl get deployments 46 | - name: Echo Services 47 | run: | 48 | KUBECONFIG=~/.kube/kubeconfig.yaml kubectl get services 49 | -------------------------------------------------------------------------------- /ops/6-tf-redis.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: redis-statefulset 5 | labels: 6 | app: redis-statefulset 7 | spec: 8 | replicas: 1 9 | # serviceName: redis-service 10 | selector: 11 | matchLabels: 12 | app: redis-statefulset 13 | template: 14 | metadata: 15 | labels: 16 | app: redis-statefulset 17 | spec: 18 | containers: 19 | - name: redis-container 20 | image: redis:latest 21 | imagePullPolicy: IfNotPresent 22 | command: 23 | - redis-server 24 | ports: 25 | - name: redis-port 26 | containerPort: 6379 27 | volumeMounts: 28 | - name: redis-data 29 | mountPath: /data 30 | initContainers: 31 | - name: delete-existing-data 32 | image: alpine:latest 33 | command: ["sh", "-c", "rm -rf /mnt/*"] 34 | volumeMounts: 35 | - name: redis-data 36 | mountPath: /mnt 37 | volumeClaimTemplates: 38 | - metadata: 39 | name: redis-data 40 | spec: 41 | accessModes: 42 | - ReadWriteOnce 43 | resources: 44 | requests: 45 | storage: 100Gi 46 | storageClassName: linode-block-storage 47 | 48 | --- 49 | apiVersion: v1 50 | kind: Service 51 | metadata: 52 | name: redis-db 53 | labels: 54 | app: redis-db 55 | spec: 56 | type: ClusterIP # 57 | ports: 58 | - protocol: TCP 59 | port: 6379 60 | targetPort: redis-port 61 | selector: 62 | app: redis-statefulset 63 | 64 | # redis://redis-db.default.svc.cluster.local:6379 65 | -------------------------------------------------------------------------------- /.github/workflows/infra-sync.yaml: -------------------------------------------------------------------------------- 1 | name: Sync Infrastructure via Terraform 2 | on: 3 | workflow_dispatch: 4 | # push: 5 | # branches: 6 | # - main 7 | # paths: 8 | # - 'infra/**' 9 | # - 'config/**' 10 | # - '.github/workflows/infra-sync.yaml' 11 | 12 | jobs: 13 | terraform: 14 | name: Apply Terraform 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | # setup terraform 20 | # Terraform Backend -> s3 bucket 21 | # Terraform TFVars -> pat 22 | # init terraform 23 | # validate 24 | # auto-apply 25 | - name: Setup Terraform 26 | uses: hashicorp/setup-terraform@v2 27 | with: 28 | terraform_version: 1.4.6 29 | - name: Add Terraform Backend for S3 30 | run: | 31 | cat << EOF > infra/backend 32 | skip_credentials_validation=true 33 | skip_region_validation=true 34 | bucket="${{ secrets.LINODE_OBJECT_STORAGE_BUCKET }}" 35 | key="tf-k8s.tfstate" 36 | region="us-east-1" 37 | endpoint="us-east-1.linodeobjects.com" 38 | access_key="${{ secrets.LINODE_OBJECT_STORAGE_ACCESS_KEY }}" 39 | secret_key="${{ secrets.LINODE_OBJECT_STORAGE_SECRET_KEY }}" 40 | EOF 41 | - name: Add Terraform TFVars 42 | run: | 43 | cat << EOF > infra/terraform.tfvars 44 | linode_api_token="${{ secrets.LINODE_PA_TOKEN }}" 45 | EOF 46 | - name: Terraform Init 47 | run: terraform -chdir=./infra init -backend-config=backend 48 | - name: Terraform Validate 49 | run: terraform -chdir=./infra validate -no-color 50 | - name: Terraform Apply Changes 51 | run: terraform -chdir=./infra apply -auto-approve -------------------------------------------------------------------------------- /.github/workflows/infra-destroy.yaml: -------------------------------------------------------------------------------- 1 | name: Destroy Infrastructure via Terraform 2 | on: 3 | workflow_dispatch: 4 | # push: 5 | # branches: 6 | # - main 7 | # paths: 8 | # - 'infra/**' 9 | # - 'config/**' 10 | # - '.github/workflows/infra-sync.yaml' 11 | 12 | jobs: 13 | terraform: 14 | name: Apply Terraform 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | # setup terraform 20 | # Terraform Backend -> s3 bucket 21 | # Terraform TFVars -> pat 22 | # init terraform 23 | # validate 24 | # auto-apply 25 | - name: Setup Terraform 26 | uses: hashicorp/setup-terraform@v2 27 | with: 28 | terraform_version: 1.4.6 29 | - name: Add Terraform Backend for S3 30 | run: | 31 | cat << EOF > infra/backend 32 | skip_credentials_validation=true 33 | skip_region_validation=true 34 | bucket="${{ secrets.LINODE_OBJECT_STORAGE_BUCKET }}" 35 | key="tf-k8s.tfstate" 36 | region="us-east-1" 37 | endpoint="us-east-1.linodeobjects.com" 38 | access_key="${{ secrets.LINODE_OBJECT_STORAGE_ACCESS_KEY }}" 39 | secret_key="${{ secrets.LINODE_OBJECT_STORAGE_SECRET_KEY }}" 40 | EOF 41 | - name: Add Terraform TFVars 42 | run: | 43 | cat << EOF > infra/terraform.tfvars 44 | linode_api_token="${{ secrets.LINODE_PA_TOKEN }}" 45 | EOF 46 | - name: Terraform Init 47 | run: terraform -chdir=./infra init -backend-config=backend 48 | - name: Terraform Validate 49 | run: terraform -chdir=./infra validate -no-color 50 | - name: Terraform Apply Changes 51 | run: terraform -chdir=./infra apply -auto-approve -destroy 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | infra/backend 2 | infra/terraform.tfvars 3 | .kube/ 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # poetry 102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 103 | # This is especially recommended for binary packages to ensure reproducibility, and is more 104 | # commonly ignored for libraries. 105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 106 | #poetry.lock 107 | 108 | # pdm 109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 110 | #pdm.lock 111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 112 | # in version control. 113 | # https://pdm.fming.dev/#use-with-ide 114 | .pdm.toml 115 | 116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 117 | __pypackages__/ 118 | 119 | # Celery stuff 120 | celerybeat-schedule 121 | celerybeat.pid 122 | 123 | # SageMath parsed files 124 | *.sage.py 125 | 126 | # Environments 127 | .env 128 | .venv 129 | env/ 130 | venv/ 131 | ENV/ 132 | env.bak/ 133 | venv.bak/ 134 | 135 | # Spyder project settings 136 | .spyderproject 137 | .spyproject 138 | 139 | # Rope project settings 140 | .ropeproject 141 | 142 | # mkdocs documentation 143 | /site 144 | 145 | # mypy 146 | .mypy_cache/ 147 | .dmypy.json 148 | dmypy.json 149 | 150 | # Pyre type checker 151 | .pyre/ 152 | 153 | # pytype static type analyzer 154 | .pytype/ 155 | 156 | # Cython debug symbols 157 | cython_debug/ 158 | 159 | # PyCharm 160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 162 | # and can be added to the global gitignore or merged into this file. For a more nuclear 163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 164 | #.idea/ 165 | --------------------------------------------------------------------------------