Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions ansible/ansible.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[defaults]
roles_path = ../ansible
connect_timeout = 30
inventory = inventory.ini
host_key_checking = False
inventory = ./inventory.ini
retry_files_enabled = False
8 changes: 8 additions & 0 deletions ansible/inventory.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[todo_app]
app ansible_connection=local db_host=192.168.11.1

[todo_db]
db ansible_host=192.168.11.1 ansible_user=borodin_2 ansible_ssh_common_args='-o PubkeyAuthentication=no -o PreferredAuthentications=password'

[all:vars]
ansible_python_interpreter=/usr/bin/python3
103 changes: 82 additions & 21 deletions ansible/playbook.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,84 @@
###############################
# WEB TO DO APPLICATION #
###############################

---
- name: "Installing Docker and docker compose"
- name: Install Docker on all hosts
hosts: all
roles:
- docker-install

- name: "Run Docker compose service: database"
hosts: database
vars_files:
- "./docker-service/vars/database.yml"
roles:
- docker-service

- name: "Run Docker compose service: web app"
hosts: webapp
vars_files:
- "./docker-service/vars/webapp.yml"
roles:
- docker-service
become: yes
tasks:
- name: Update apt cache
apt:
update_cache: yes

- name: Install docker and docker-compose
apt:
name:
- docker.io
- docker-compose
state: present

- name: Enable docker service
service:
name: docker
state: started
enabled: yes


- name: Deploy MySQL database
hosts: todo_db
become: yes
tasks:
- name: Create database directory
file:
path: /opt/todo-db
state: directory
mode: '0755'

- name: Copy database compose file
template:
src: templates/db-compose.yml.j2
dest: /opt/todo-db/docker-compose.yml

- name: Start MySQL container
shell: docker-compose up -d
args:
chdir: /opt/todo-db

- name: Wait for database port
wait_for:
host: 127.0.0.1
port: 3306
timeout: 120

- name: Wait extra time for MariaDB initialization
pause:
seconds: 20


- name: Deploy Todo application
hosts: todo_app
become: yes
tasks:
- name: Create app directory
file:
path: /opt/todo-app
state: directory
mode: '0755'

- name: Copy application files
copy:
src: ../app/
dest: /opt/todo-app/

- name: Copy app compose file
template:
src: templates/app-compose.yml.j2
dest: /opt/todo-app/docker-compose.yml

- name: Start Todo application
shell: docker-compose up -d --build --force-recreate
args:
chdir: /opt/todo-app

- name: Wait for Todo application port
wait_for:
host: 127.0.0.1
port: 8080
timeout: 120
15 changes: 15 additions & 0 deletions ansible/templates/app-compose.yml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: '3'

services:
todo-app:
build: .
container_name: todo-app
restart: always
ports:
- "8080:5000"
environment:
DB_HOST: "{{ db_host }}"
DB_NAME: todo
DB_USER: todo
DB_PASSWORD: todo_pass

19 changes: 19 additions & 0 deletions ansible/templates/db-compose.yml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: '3'

services:
mysql:
image: mariadb:10.6
container_name: todo-mysql
restart: always
environment:
MARIADB_ROOT_PASSWORD: root_pass
MARIADB_DATABASE: todo
MARIADB_USER: todo
MARIADB_PASSWORD: todo_pass
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql

volumes:
mysql_data:
11 changes: 11 additions & 0 deletions app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

CMD ["python", "app.py"]

118 changes: 118 additions & 0 deletions app/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import os
import time
import mysql.connector
from flask import Flask, request, redirect, render_template_string

app = Flask(__name__)

DB_HOST = os.getenv("DB_HOST", "127.0.0.1")
DB_NAME = os.getenv("DB_NAME", "todo")
DB_USER = os.getenv("DB_USER", "todo")
DB_PASSWORD = os.getenv("DB_PASSWORD", "todo_pass")


def get_connection():
return mysql.connector.connect(
host=DB_HOST,
database=DB_NAME,
user=DB_USER,
password=DB_PASSWORD
)


def init_db():
for _ in range(30):
try:
conn = get_connection()
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS todos (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL
)
""")
conn.commit()
cursor.close()
conn.close()
return
except Exception as error:
print(f"Waiting for database: {error}", flush=True)
time.sleep(5)


@app.route("/", methods=["GET"])
def index():
conn = get_connection()
cursor = conn.cursor(dictionary=True)
cursor.execute("SELECT id, title FROM todos ORDER BY id DESC")
todos = cursor.fetchall()
cursor.close()
conn.close()

return render_template_string("""
<!DOCTYPE html>
<html>
<head>
<title>Todo App</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; background: #f6f7fb; }
.container { max-width: 700px; margin: auto; background: white; padding: 30px; border-radius: 12px; }
h1 { color: #222; }
form { margin-bottom: 20px; }
input { padding: 10px; width: 70%; }
button { padding: 10px 16px; cursor: pointer; }
li { margin: 10px 0; }
.delete { margin-left: 20px; }
</style>
</head>
<body>
<div class="container">
<h1>Todo App</h1>
<form method="post" action="/add">
<input name="title" placeholder="New task" required>
<button type="submit">Add</button>
</form>

<ul>
{% for todo in todos %}
<li>
{{ todo.title }}
<form class="delete" method="post" action="/delete/{{ todo.id }}" style="display:inline;">
<button type="submit">Delete</button>
</form>
</li>
{% endfor %}
</ul>
</div>
</body>
</html>
""", todos=todos)


@app.route("/add", methods=["POST"])
def add():
title = request.form.get("title")
if title:
conn = get_connection()
cursor = conn.cursor()
cursor.execute("INSERT INTO todos (title) VALUES (%s)", (title,))
conn.commit()
cursor.close()
conn.close()
return redirect("/")


@app.route("/delete/<int:todo_id>", methods=["POST"])
def delete(todo_id):
conn = get_connection()
cursor = conn.cursor()
cursor.execute("DELETE FROM todos WHERE id = %s", (todo_id,))
conn.commit()
cursor.close()
conn.close()
return redirect("/")


if __name__ == "__main__":
init_db()
app.run(host="0.0.0.0", port=5000)
3 changes: 3 additions & 0 deletions app/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
flask
mysql-connector-python

Binary file added assets/images/01_ansible_success.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/02_app_container.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/03_db_container.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/04_http_200.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/05_todo_browser.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading