├── .github └── workflows │ ├── apply-terraform_and_ansible.yaml │ ├── cfe.yaml │ ├── destroy-terraform.yaml │ ├── inventory.yaml │ └── main.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── Dockerfile ├── README.md ├── devops ├── ansible │ ├── ansible.cfg │ ├── inventory.ini │ ├── main.yaml │ ├── playbooks │ │ ├── loadbalancer.yaml │ │ └── webapps.yaml │ ├── roles │ │ ├── docker │ │ │ ├── handlers │ │ │ │ └── main.yaml │ │ │ └── tasks │ │ │ │ └── main.yaml │ │ ├── docker_containers │ │ │ ├── handlers │ │ │ │ └── main.yaml │ │ │ └── tasks │ │ │ │ └── main.yaml │ │ ├── nginx │ │ │ ├── handlers │ │ │ │ └── main.yaml │ │ │ └── tasks │ │ │ │ └── main.yaml │ │ └── nginx_purge │ │ │ └── tasks │ │ │ └── main.yaml │ └── templates │ │ ├── nginx.conf │ │ └── nginx.default.html └── tf │ ├── .terraform.lock.hcl │ ├── locals.tf │ ├── main.tf │ ├── output.tf │ ├── templates │ └── ansible-inventory.tpl │ └── variables.tf ├── docker-compose.yaml ├── entrypoint.sh ├── iac-ansible.code-workspace ├── nginx ├── Dockerfile └── nginx.conf ├── pytest.ini ├── pyvenv.cfg ├── requirements.txt ├── runtime.txt └── src ├── __init__.py ├── main.py └── test_views.py /.github/workflows/apply-terraform_and_ansible.yaml: -------------------------------------------------------------------------------- 1 | name: Apply IaC via Terraform & Ansible 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | terraform: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | - uses: actions/setup-python@v2 13 | with: 14 | python-version: '3.8' 15 | - name: Setup Terraform 16 | uses: hashicorp/setup-terraform@v1 17 | with: 18 | terraform_version: 1.0.9 19 | - name: Add Terraform Backend for S3 20 | run: | 21 | cat << EOF > devops/tf/backend 22 | skip_credentials_validation = true 23 | skip_region_validation = true 24 | bucket="${{ secrets.LINODE_OBJECT_STORAGE_BUCKET }}" 25 | key="tryiac-ansible.tfstate" 26 | region="${{ secrets.LINODE_REGION }}" 27 | endpoint="${{ secrets.LINODE_REGION }}.linodeobjects.com" 28 | access_key="${{ secrets.LINODE_OBJECT_STORAGE_ACCESS_KEY }}" 29 | secret_key="${{ secrets.LINODE_OBJECT_STORAGE_SECRET_KEY }}" 30 | EOF 31 | - name: Add Terraform TFVars 32 | run: | 33 | cat << EOF > devops/tf/terraform.tfvars 34 | linode_pa_token="${{ secrets.LINODE_ACCESS_TOKEN }}" 35 | authorized_key="${{ secrets.SSH_PUB_KEY }}" 36 | root_user_pw="${{ secrets.ROOT_USER_PW }}" 37 | linode_instance_count="${{ secrets.WEBAPPS_COUNT }}" 38 | EOF 39 | - name: Terraform Init 40 | run: terraform -chdir=./devops/tf init -backend-config=backend 41 | - name: Terraform Validate 42 | run: terraform -chdir=./devops/tf validate -no-color 43 | - name: Terraform Apply Changes 44 | run: terraform -chdir=./devops/tf apply -auto-approve 45 | - name: Install Ansible 46 | run: | 47 | pip install ansible 48 | - name: Create PEM Key 49 | run: | 50 | cat << EOF > devops/ansible/private.pem 51 | ${{ secrets.ANSIBLE_PRIVATE_KEY }} 52 | EOF 53 | - name: Update key permissions 54 | run: | 55 | chmod 400 devops/ansible/private.pem 56 | - name: Add PEM Key Path to Ansible Config 57 | run: | 58 | cat << EOF > devops/ansible/ansible.cfg 59 | [defaults] 60 | ansible_python_interpreter='/usr/bin/python3' 61 | deprecation_warnings=False 62 | inventory=./inventory.ini 63 | remote_user="root" 64 | retries=2 65 | private_key_file = ./private.pem 66 | host_key_checking = False 67 | EOF 68 | - name: Run main playbook 69 | run: | 70 | cd devops/ansible 71 | ansible-playbook main.yaml 72 | -------------------------------------------------------------------------------- /.github/workflows/cfe.yaml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Ansible CICD via CFE CLI 4 | 5 | # Controls when the workflow will run 6 | on: 7 | workflow_dispatch: 8 | # Triggers the workflow on push or pull request events but only for the main branch 9 | # push: 10 | # branches: [ main ] 11 | # pull_request: 12 | # branches: [ main ] 13 | # Allows you to run this workflow manually from the Actions tab 14 | 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2 24 | - uses: actions/setup-python@v2 25 | with: 26 | python-version: '3.8' 27 | - name: Install Ansible & CFE CLI 28 | run: | 29 | pip install ansible cfe 30 | - name: Create PEM Key 31 | run: | 32 | cat << EOF > devops/ansible/private.pem 33 | ${{ secrets.ANSIBLE_PRIVATE_KEY }} 34 | EOF 35 | - name: Update key permissions 36 | run: | 37 | chmod 400 devops/ansible/private.pem 38 | - name: Create Inventory via CLI 39 | run: | 40 | export LINODE_ACCESS_TOKEN=${{ secrets.LINODE_ACCESS_TOKEN }} 41 | cfe services linode inventory --save-path='devops/ansible/inventory.ini' --tags "webapps,loadbalancer" 42 | - name: Add PEM Key Path to Ansible Config 43 | run: | 44 | cat << EOF > devops/ansible/ansible.cfg 45 | [defaults] 46 | ansible_python_interpreter='/usr/bin/python3' 47 | deprecation_warnings=False 48 | inventory=./inventory.ini 49 | remote_user="root" 50 | retries=2 51 | private_key_file = ./private.pem 52 | EOF 53 | - name: Run main playbook 54 | run: | 55 | cd devops/ansible 56 | ansible-playbook main.yaml 57 | -------------------------------------------------------------------------------- /.github/workflows/destroy-terraform.yaml: -------------------------------------------------------------------------------- 1 | name: Destroy Infrastructure via Terraform 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | terraform: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | - name: Setup Terraform 13 | uses: hashicorp/setup-terraform@v1 14 | with: 15 | terraform_version: 1.0.9 16 | - name: Add Terraform Backend for S3 17 | run: | 18 | cat << EOF > devops/tf/backend 19 | skip_credentials_validation = true 20 | skip_region_validation = true 21 | bucket="${{ secrets.LINODE_OBJECT_STORAGE_BUCKET }}" 22 | key="tryiac-ansible.tfstate" 23 | region="${{ secrets.LINODE_REGION }}" 24 | endpoint="${{ secrets.LINODE_REGION }}.linodeobjects.com" 25 | access_key="${{ secrets.LINODE_OBJECT_STORAGE_ACCESS_KEY }}" 26 | secret_key="${{ secrets.LINODE_OBJECT_STORAGE_SECRET_KEY }}" 27 | EOF 28 | - name: Add Terraform TFVars 29 | run: | 30 | cat << EOF > devops/tf/terraform.tfvars 31 | linode_pa_token="${{ secrets.LINODE_ACCESS_TOKEN }}" 32 | authorized_key="${{ secrets.SSH_PUB_KEY }}" 33 | root_user_pw="${{ secrets.ROOT_USER_PW }}" 34 | EOF 35 | - name: Terraform Init 36 | run: terraform -chdir=./devops/tf init -backend-config=backend 37 | - name: Terraform Validate 38 | run: terraform -chdir=./devops/tf validate -no-color 39 | - name: Terraform Apply Changes 40 | run: terraform -chdir=./devops/tf apply -destroy -auto-approve 41 | -------------------------------------------------------------------------------- /.github/workflows/inventory.yaml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Ansible CICD via Secret Inventory 4 | 5 | # Controls when the workflow will run 6 | on: 7 | workflow_dispatch: 8 | # Triggers the workflow on push or pull request events but only for the main branch 9 | # push: 10 | # branches: [ main ] 11 | # pull_request: 12 | # branches: [ main ] 13 | # Allows you to run this workflow manually from the Actions tab 14 | 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2 24 | - uses: actions/setup-python@v2 25 | with: 26 | python-version: '3.8' 27 | - name: Install Ansible 28 | run: | 29 | pip install ansible 30 | - name: Create PEM Key 31 | run: | 32 | cat << EOF > devops/ansible/private.pem 33 | ${{ secrets.ANSIBLE_PRIVATE_KEY }} 34 | EOF 35 | - name: Update key permissions 36 | run: | 37 | chmod 400 devops/ansible/private.pem 38 | - name: Create Inventory 39 | run: | 40 | cat << EOF > devops/ansible/inventory.ini 41 | ${{ secrets.ANSIBLE_INVENTORY }} 42 | EOF 43 | - name: Add PEM Key Path to Ansible Config 44 | run: | 45 | cat << EOF > devops/ansible/ansible.cfg 46 | [defaults] 47 | ansible_python_interpreter='/usr/bin/python3' 48 | deprecation_warnings=False 49 | inventory=./inventory.ini 50 | remote_user="root" 51 | retries=2 52 | private_key_file = ./private.pem 53 | EOF 54 | - name: Run main playbook 55 | run: | 56 | cd devops/ansible 57 | ansible-playbook main.yaml 58 | 59 | # python3.8 60 | # install ansible 61 | # private key 62 | # update ansible cfg 63 | # inventory file 64 | # run playbooks -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Ansible CICD via Repo Inventory 4 | 5 | # Controls when the workflow will run 6 | on: 7 | workflow_dispatch: 8 | # Triggers the workflow on push or pull request events but only for the main branch 9 | # push: 10 | # branches: [ main ] 11 | # pull_request: 12 | # branches: [ main ] 13 | # Allows you to run this workflow manually from the Actions tab 14 | 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2 24 | - uses: actions/setup-python@v2 25 | with: 26 | python-version: '3.8' 27 | - name: Install Ansible 28 | run: | 29 | pip install ansible 30 | - name: Create PEM Key 31 | run: | 32 | cat << EOF > devops/ansible/private.pem 33 | ${{ secrets.ANSIBLE_PRIVATE_KEY }} 34 | EOF 35 | - name: Update key permissions 36 | run: | 37 | chmod 400 devops/ansible/private.pem 38 | - name: Add PEM Key Path to Ansible Config 39 | run: | 40 | cat << EOF > devops/ansible/ansible.cfg 41 | [defaults] 42 | ansible_python_interpreter='/usr/bin/python3' 43 | deprecation_warnings=False 44 | inventory=./inventory.ini 45 | remote_user="root" 46 | retries=2 47 | private_key_file = ./private.pem 48 | EOF 49 | - name: Run main playbook 50 | run: | 51 | cd devops/ansible 52 | ansible-playbook main.yaml 53 | 54 | # python3.8 55 | # install ansible 56 | # private key 57 | # update ansible cfg 58 | # inventory file 59 | # run playbooks -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | # Local .terraform directories 3 | **/.terraform/* 4 | 5 | # .tfstate files 6 | *.tfstate 7 | *.tfstate.* 8 | 9 | # Crash log files 10 | crash.log 11 | 12 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as 13 | # password, private keys, and other secrets. These should not be part of version 14 | # control as they are data points which are potentially sensitive and subject 15 | # to change depending on the environment. 16 | # 17 | *.tfvars 18 | 19 | # Ignore override files as they are usually used to override resources locally and so 20 | # are not checked in 21 | override.tf 22 | override.tf.json 23 | *_override.tf 24 | *_override.tf.json 25 | 26 | # Include override files you do wish to add to version control using negated pattern 27 | # 28 | # !example_override.tf 29 | 30 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 31 | # example: *tfplan* 32 | 33 | # Ignore CLI configuration files 34 | .terraformrc 35 | terraform.rc 36 | 37 | # Byte-compiled / optimized / DLL files 38 | __pycache__/ 39 | *.py[cod] 40 | *$py.class 41 | 42 | # C extensions 43 | *.so 44 | 45 | # Distribution / packaging 46 | .Python 47 | build/ 48 | develop-eggs/ 49 | dist/ 50 | downloads/ 51 | eggs/ 52 | .eggs/ 53 | lib/ 54 | lib64/ 55 | parts/ 56 | sdist/ 57 | var/ 58 | wheels/ 59 | share/python-wheels/ 60 | *.egg-info/ 61 | .installed.cfg 62 | *.egg 63 | MANIFEST 64 | 65 | # PyInstaller 66 | # Usually these files are written by a python script from a template 67 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 68 | *.manifest 69 | *.spec 70 | 71 | # Installer logs 72 | pip-log.txt 73 | pip-delete-this-directory.txt 74 | 75 | # Unit test / coverage reports 76 | htmlcov/ 77 | .tox/ 78 | .nox/ 79 | .coverage 80 | .coverage.* 81 | .cache 82 | nosetests.xml 83 | coverage.xml 84 | *.cover 85 | *.py,cover 86 | .hypothesis/ 87 | .pytest_cache/ 88 | cover/ 89 | 90 | # Translations 91 | *.mo 92 | *.pot 93 | 94 | # Django stuff: 95 | *.log 96 | local_settings.py 97 | db.sqlite3 98 | db.sqlite3-journal 99 | 100 | # Flask stuff: 101 | instance/ 102 | .webassets-cache 103 | 104 | # Scrapy stuff: 105 | .scrapy 106 | 107 | # Sphinx documentation 108 | docs/_build/ 109 | 110 | # PyBuilder 111 | .pybuilder/ 112 | target/ 113 | 114 | # Jupyter Notebook 115 | .ipynb_checkpoints 116 | 117 | # IPython 118 | profile_default/ 119 | ipython_config.py 120 | 121 | # pyenv 122 | # For a library or package, you might want to ignore these files since the code is 123 | # intended to run in multiple environments; otherwise, check them in: 124 | # .python-version 125 | 126 | # pipenv 127 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 128 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 129 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 130 | # install all needed dependencies. 131 | #Pipfile.lock 132 | 133 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 134 | __pypackages__/ 135 | 136 | # Celery stuff 137 | celerybeat-schedule 138 | celerybeat.pid 139 | 140 | # SageMath parsed files 141 | *.sage.py 142 | 143 | # Environments 144 | .env 145 | .venv 146 | env/ 147 | venv/ 148 | ENV/ 149 | env.bak/ 150 | venv.bak/ 151 | 152 | # Spyder project settings 153 | .spyderproject 154 | .spyproject 155 | 156 | # Rope project settings 157 | .ropeproject 158 | 159 | # mkdocs documentation 160 | /site 161 | 162 | # mypy 163 | .mypy_cache/ 164 | .dmypy.json 165 | dmypy.json 166 | 167 | # Pyre type checker 168 | .pyre/ 169 | 170 | # pytype static type analyzer 171 | .pytype/ 172 | 173 | # Cython debug symbols 174 | cython_debug/ -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v3.2.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: check-yaml 7 | - id: check-added-large-files 8 | - repo: local 9 | hooks: 10 | - id: pytest-check 11 | name: Run PyTest 12 | entry: pytest 13 | language: system 14 | pass_filenames: false 15 | always_run: true 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-slim 2 | 3 | COPY . /app 4 | WORKDIR /app 5 | 6 | RUN apt-get update && \ 7 | apt-get install -y \ 8 | build-essential \ 9 | python3-dev \ 10 | python3-setuptools \ 11 | gcc \ 12 | make 13 | 14 | # Create a virtual environment in /opt 15 | RUN python3 -m venv /opt/venv 16 | 17 | # Install requirments to new virtual environment 18 | RUN /opt/venv/bin/pip install -r requirements.txt 19 | 20 | # purge unused 21 | RUN apt-get remove -y --purge make gcc build-essential \ 22 | && apt-get autoremove -y \ 23 | && rm -rf /var/lib/apt/lists/* 24 | 25 | # make entrypoint.sh executable 26 | RUN chmod +x entrypoint.sh 27 | 28 | CMD [ "./entrypoint.sh" ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IaC Python Web App 2 | 3 | Coming soon -------------------------------------------------------------------------------- /devops/ansible/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | ansible_python_interpreter='/usr/bin/python3' 3 | deprecation_warnings=False 4 | inventory=./inventory.ini 5 | remote_user="root" 6 | retries=2 7 | host_key_checking = False -------------------------------------------------------------------------------- /devops/ansible/inventory.ini: -------------------------------------------------------------------------------- 1 | [webapps] 2 | 72.14.176.118 3 | 45.33.127.153 4 | 5 | [loadbalancer] 6 | 23.239.25.5 -------------------------------------------------------------------------------- /devops/ansible/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update Webapps 3 | import_playbook: ./playbooks/webapps.yaml 4 | 5 | - name: Configure LoadBalancer 6 | import_playbook: ./playbooks/loadbalancer.yaml -------------------------------------------------------------------------------- /devops/ansible/playbooks/loadbalancer.yaml: -------------------------------------------------------------------------------- 1 | 2 | - hosts: loadbalancer 3 | become: yes 4 | roles: 5 | - ./../roles/docker 6 | - ./../roles/docker_containers 7 | vars: 8 | - nginx_config_dest: /var/www/nginx.conf 9 | tasks: 10 | - name: Verify /var/www/ exists 11 | file: 12 | path: /var/www 13 | state: directory 14 | mode: 0755 15 | - name: Add Nginx Config 16 | template: 17 | src: ./../templates/nginx.conf 18 | dest: "{{ nginx_config_dest }}" 19 | register: nginx_conf_dict 20 | - debug: msg="{{ nginx_conf_dict }}" 21 | - set_fact: 22 | nginx_lb_conf_path: "{{ nginx_config_dest }}" 23 | - debug: msg="{{ nginx_lb_conf_path }}" 24 | - name: Debug Docker-based Nginx Conf 25 | vars: 26 | - _nginx_lb_path: /etc/nginx/conf.d/default.conf 27 | debug: msg="{{ nginx_lb_conf_path | default(_nginx_lb_path) }}" 28 | - name: Trigger Docker Container Changes 29 | shell: echo "Triggering docker changes" 30 | notify: 31 | - docker stop containers 32 | - docker remove containers 33 | - docker run nginx lb 34 | when: nginx_conf_dict.changed == true 35 | -------------------------------------------------------------------------------- /devops/ansible/playbooks/webapps.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: webapps 3 | become: yes 4 | vars: 5 | root_dir: "./../../../" 6 | dest_dir: /var/www/app 7 | docker_app_name: app 8 | roles: 9 | - ./../roles/docker 10 | - ./../roles/docker_containers 11 | tasks: 12 | - name: Setup /var/www/src 13 | file: 14 | path: "{{ dest_dir }}/src" 15 | state: directory 16 | mode: 0755 17 | - name: Copy Src folder 18 | copy: 19 | src: "{{ root_dir }}/src/" 20 | dest: "{{ dest_dir }}/src/" 21 | register: app_folder 22 | - copy: 23 | src: "{{ root_dir }}/{{ item }}" 24 | dest: "{{ dest_dir }}" 25 | register: app_files 26 | with_items: 27 | - Dockerfile 28 | - requirements.txt 29 | - entrypoint.sh 30 | - name: Trigger Build & Run 31 | shell: echo "Running build" 32 | # when: (app_files.changed) or (app_folder.changed) 33 | notify: 34 | - docker build 35 | - docker stop containers 36 | - docker remove containers 37 | - docker run app 38 | -------------------------------------------------------------------------------- /devops/ansible/roles/docker/handlers/main.yaml: -------------------------------------------------------------------------------- 1 | - name: exec docker script 2 | shell: /tmp/get-docker.sh -------------------------------------------------------------------------------- /devops/ansible/roles/docker/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | - name: Grab Docker Install Script 2 | get_url: 3 | url: https://get.docker.com 4 | dest: /tmp/get-docker.sh 5 | mode: 0755 6 | notify: exec docker script 7 | 8 | - name: Verify Docker Command 9 | shell: command -v docker >/dev/null 2>&1 10 | ignore_errors: yes 11 | register: docker_exists 12 | 13 | - debug: msg="{{ docker_exists.rc }}" 14 | 15 | - name: Trigger docker install script if docker not running 16 | shell: echo "Docker command" 17 | when: docker_exists.rc != 0 18 | notify: exec docker script 19 | -------------------------------------------------------------------------------- /devops/ansible/roles/docker_containers/handlers/main.yaml: -------------------------------------------------------------------------------- 1 | - name: docker build 2 | vars: 3 | _docker_app_name: app 4 | shell: 5 | cmd: docker build -f Dockerfile -t "{{ docker_app_name | default(_docker_app_name) }}" . 6 | chdir: "{{ dest_dir }}" 7 | 8 | 9 | - name: docker stop containers 10 | shell: docker stop $(docker ps -aq) 11 | ignore_errors: yes 12 | when: containers_running.rc == 0 13 | 14 | - name: docker remove containers 15 | shell: docker rm $(docker ps -aq) 16 | ignore_errors: yes 17 | when: containers_running.rc == 0 18 | 19 | - name: docker run nginx lb 20 | vars: 21 | - _nginx_lb_path: /etc/nginx/conf.d/default.conf 22 | shell: | 23 | docker run \ 24 | --restart always \ 25 | -v {{ nginx_lb_conf_path | default(_nginx_lb_path) }}:/etc/nginx/conf.d/default.conf:ro \ 26 | -p 80:80 \ 27 | -d nginx 28 | 29 | 30 | - name: docker run app 31 | vars: 32 | _docker_app_name: app 33 | shell: | 34 | docker run \ 35 | --restart always \ 36 | -p 80:8001 \ 37 | -e PORT=8001 \ 38 | -d "{{ docker_app_name | default(_docker_app_name) }}" -------------------------------------------------------------------------------- /devops/ansible/roles/docker_containers/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | - name: Has Running Docker Images 2 | shell: docker ps -aq >/dev/null 2>&1 3 | register: containers_running 4 | ignore_errors: yes 5 | -------------------------------------------------------------------------------- /devops/ansible/roles/nginx/handlers/main.yaml: -------------------------------------------------------------------------------- 1 | - name: reload nginx 2 | service: 3 | name: nginx 4 | state: reloaded 5 | 6 | - name: restart nginx 7 | service: 8 | name: nginx 9 | state: restarted -------------------------------------------------------------------------------- /devops/ansible/roles/nginx/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | - name: Install Nginx 2 | apt: 3 | name: nginx 4 | state: present 5 | update_cache: yes -------------------------------------------------------------------------------- /devops/ansible/roles/nginx_purge/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | - name: Remove Nginx 2 | apt: 3 | name: "{{ item }}" 4 | state: absent 5 | purge: yes 6 | with_items: 7 | - nginx 8 | - name: Stop Nginx Services 9 | service: 10 | name: "{{ item }}" 11 | state: stopped 12 | with_items: 13 | - nginx 14 | -------------------------------------------------------------------------------- /devops/ansible/templates/nginx.conf: -------------------------------------------------------------------------------- 1 | upstream myproxy { 2 | {% for host in groups['webapps'] %} 3 | server {{ host }}; 4 | {% endfor %} 5 | } 6 | 7 | server { 8 | listen 80; 9 | server_name {{ inventory_hostname }}; 10 | root /var/www/html/; 11 | 12 | location / { 13 | proxy_pass http://myproxy; 14 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 15 | proxy_set_header Host $host; 16 | proxy_redirect off; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /devops/ansible/templates/nginx.default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

{{ title }} - {{ inventory_hostname }}

5 |

{{ description }}

6 |
7 | {% for host in groups['webapps'] %} 8 |

{{ host }}

9 | {% endfor %} 10 | {{ groups['loadbalancer'] }} 11 |
12 | 13 | -------------------------------------------------------------------------------- /devops/tf/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/local" { 5 | version = "2.1.0" 6 | hashes = [ 7 | "h1:KfieWtVyGWwplSoLIB5usKAUnrIkDQBkWaR5TI+4WYg=", 8 | "zh:0f1ec65101fa35050978d483d6e8916664b7556800348456ff3d09454ac1eae2", 9 | "zh:36e42ac19f5d68467aacf07e6adcf83c7486f2e5b5f4339e9671f68525fc87ab", 10 | "zh:6db9db2a1819e77b1642ec3b5e95042b202aee8151a0256d289f2e141bf3ceb3", 11 | "zh:719dfd97bb9ddce99f7d741260b8ece2682b363735c764cac83303f02386075a", 12 | "zh:7598bb86e0378fd97eaa04638c1a4c75f960f62f69d3662e6d80ffa5a89847fe", 13 | "zh:ad0a188b52517fec9eca393f1e2c9daea362b33ae2eb38a857b6b09949a727c1", 14 | "zh:c46846c8df66a13fee6eff7dc5d528a7f868ae0dcf92d79deaac73cc297ed20c", 15 | "zh:dc1a20a2eec12095d04bf6da5321f535351a594a636912361db20eb2a707ccc4", 16 | "zh:e57ab4771a9d999401f6badd8b018558357d3cbdf3d33cc0c4f83e818ca8e94b", 17 | "zh:ebdcde208072b4b0f8d305ebf2bfdc62c926e0717599dcf8ec2fd8c5845031c3", 18 | "zh:ef34c52b68933bedd0868a13ccfd59ff1c820f299760b3c02e008dc95e2ece91", 19 | ] 20 | } 21 | 22 | provider "registry.terraform.io/linode/linode" { 23 | version = "1.22.0" 24 | constraints = "1.22.0" 25 | hashes = [ 26 | "h1:/LjoN/opCxTgYFf83+8BQxCxF3A8JPg80YpwGQKBitM=", 27 | "zh:0af0d71a071857318e6e1f29c7817174ed41d25bdec67144da9619f659ada0bb", 28 | "zh:11048427ac13297415149969ec8531593a09fb04a4d12e497d2ab82713883d51", 29 | "zh:1613f83edde935d6c3509c7766e068d8edd76c86a5c47c503f8482f0f4795b07", 30 | "zh:40657fa11cbbb1dd23adbb484ca03b6d20aaafe99202132cbcdafcfe0dc8829e", 31 | "zh:6f3429853cdfc278834fa76bdd8c8aac2515262e006fa83d850ff2b726e86a7c", 32 | "zh:80e6b395ef866df3939e8bb1bd0993d05099a9b38d2f8154d3990c8033037d65", 33 | "zh:85f1606572649d85d8f4e3f47c5867520a4da0c375cccc03702ade074af9ee56", 34 | "zh:8d461ca74eb35de4e6ce57844b23fc4cd9e38d130bc081087502a962e151c92b", 35 | "zh:b8e5ad4f0a887a87d4e1172d08747b38265460a5f741991830e73e308026539c", 36 | "zh:c1c84978812f46d3985c49559312a39538bf2a07352cec0a5adb87e78b7cb8ef", 37 | "zh:dcc6e6cd26cf4dab223ece4365ed16763e58fb7eb2ccddf20200df403216a73b", 38 | "zh:ef7911d407a0cd26509b64422d0f1a4c0b11860c68b12c3879474bd75ce2f312", 39 | "zh:f246b68f3ca150f2a526b7d1bdae49be5cfbe389a9bee0f8c03feb82523061b0", 40 | "zh:ff499cbe8336d2ca9763028bc2e83eb3e9ba53d97799514dc9ede35133819cdd", 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /devops/tf/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | root_dir = "${abspath(path.root)}" 3 | devops_dir = "${dirname(local.root_dir)}" 4 | project_dir = "${dirname(local.devops_dir)}" 5 | templates_dir = "${local.root_dir}/templates/" 6 | } -------------------------------------------------------------------------------- /devops/tf/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.15" 3 | required_providers { 4 | linode = { 5 | source = "linode/linode" 6 | version = "1.22.0" 7 | } 8 | } 9 | backend "s3" { 10 | skip_credentials_validation = true 11 | skip_region_validation = true 12 | } 13 | } 14 | 15 | provider "linode" { 16 | token = var.linode_pa_token 17 | } 18 | 19 | resource "linode_instance" "cfe-loadbalancer" { 20 | image = "linode/ubuntu18.04" 21 | label = "loadbalancer" 22 | group = "CFE_Terrafrom_PROJECT" 23 | region = var.region 24 | type = "g6-nanode-1" 25 | authorized_keys = [ var.authorized_key ] 26 | root_pass = var.root_user_pw 27 | private_ip = true 28 | tags = ["loadbalancer"] 29 | } 30 | 31 | 32 | resource "linode_instance" "cfe-pyapp" { 33 | count = var.linode_instance_count 34 | image = "linode/ubuntu18.04" 35 | label = "pyapp-${count.index + 1}" 36 | group = "CFE_Terrafrom_PROJECT" 37 | region = var.region 38 | type = "g6-nanode-1" 39 | authorized_keys = [ var.authorized_key ] 40 | root_pass = var.root_user_pw 41 | private_ip = true 42 | tags = ["webapps"] 43 | } 44 | 45 | resource "local_file" "ansible_inventory" { 46 | content = templatefile("${local.templates_dir}/ansible-inventory.tpl", { webapps=[for host in linode_instance.cfe-pyapp.*: "${host.ip_address}"], loadbalancer="${linode_instance.cfe-loadbalancer.ip_address}" }) 47 | filename = "${local.devops_dir}/ansible/inventory.ini" 48 | } 49 | -------------------------------------------------------------------------------- /devops/tf/output.tf: -------------------------------------------------------------------------------- 1 | output "webapp_first_deploy" { 2 | value = "${linode_instance.cfe-pyapp.0.label} : ${linode_instance.cfe-pyapp.0.ip_address} - ${var.region}" 3 | } 4 | 5 | output "webapps" { 6 | value = [for host in linode_instance.cfe-pyapp.*: "${host.label} : ${host.ip_address}"] 7 | } 8 | 9 | output "loadbalancer" { 10 | value = "${linode_instance.cfe-loadbalancer.ip_address}" 11 | } -------------------------------------------------------------------------------- /devops/tf/templates/ansible-inventory.tpl: -------------------------------------------------------------------------------- 1 | [webapps] 2 | %{ for host in webapps ~} 3 | ${host} 4 | %{ endfor ~} 5 | 6 | [loadbalancer] 7 | ${loadbalancer} -------------------------------------------------------------------------------- /devops/tf/variables.tf: -------------------------------------------------------------------------------- 1 | variable "linode_pa_token" { 2 | sensitive = true 3 | } 4 | 5 | variable "linode_instance_count" { 6 | default = "3" 7 | } 8 | 9 | variable "authorized_key" { 10 | sensitive = true 11 | } 12 | 13 | variable "root_user_pw" { 14 | sensitive = true 15 | } 16 | 17 | variable "region" { 18 | default = "us-east" 19 | } 20 | 21 | variable "git_repo" { 22 | default = "https://github.com/codingforentrepreneurs/iac-python" 23 | } -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | web_thunder: 4 | restart: always 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | expose: 9 | - 8812 10 | environment: 11 | - PORT=8812 12 | nginx: 13 | restart: always 14 | build: 15 | context: ./nginx 16 | dockerfile: Dockerfile 17 | ports: 18 | - 80:80 19 | depends_on: 20 | - web_thunder -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | APP_PORT=${PORT:-8000} 4 | 5 | /opt/venv/bin/gunicorn -k uvicorn.workers.UvicornWorker src.main:app --bind "0.0.0.0:${APP_PORT}" -------------------------------------------------------------------------------- /iac-ansible.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:latest 2 | 3 | COPY ./nginx.conf /etc/nginx/conf.d/default.conf -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | upstream app_server { 2 | server web_thunder:8812; 3 | keepalive 32; 4 | } 5 | 6 | server { 7 | listen 80; 8 | server_name localhost; 9 | root /www/data/; 10 | 11 | location / { 12 | proxy_read_timeout 300s; 13 | proxy_pass http://app_server; 14 | proxy_set_header Host $host; 15 | proxy_set_header X-Real-Ip $remote_addr; 16 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 17 | } 18 | } -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | norecursedirs = lib/* bin/* include/* -------------------------------------------------------------------------------- /pyvenv.cfg: -------------------------------------------------------------------------------- 1 | home = /Library/Frameworks/Python.framework/Versions/3.8/bin 2 | include-system-site-packages = false 3 | version = 3.8.2 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | uvicorn 3 | gunicorn 4 | requests 5 | pytest 6 | pre-commit -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.8.2 2 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/iac-ansible/2ac502f473ea7ddbd17c8a14159440161f5d89c0/src/__init__.py -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | app = FastAPI() 4 | 5 | @app.get("/") 6 | def home_view(): 7 | return {"hello": "world", "cron-build": "works"} 8 | 9 | 10 | @app.post("/") 11 | def home_post_view(): 12 | return {"hello": "world"} 13 | -------------------------------------------------------------------------------- /src/test_views.py: -------------------------------------------------------------------------------- 1 | from fastapi.testclient import TestClient 2 | 3 | from src.main import app 4 | 5 | client = TestClient(app) 6 | 7 | def test_get_home(): 8 | response = client.get("/") # requests.get("") # python requests 9 | assert response.status_code == 200 10 | assert "application/json" == response.headers['content-type'] 11 | --------------------------------------------------------------------------------