diff --git a/Docker/Dockerfile-backend b/Application/backend/Dockerfile similarity index 50% rename from Docker/Dockerfile-backend rename to Application/backend/Dockerfile index b2beaa8576b1b8ebfac46357a6647f4facadbeef..03e53c452f94cade3e3c8515f35fda5915d715cb 100644 --- a/Docker/Dockerfile-backend +++ b/Application/backend/Dockerfile @@ -1,22 +1,14 @@ -# Use Python 3.11 slim image as base -FROM python:3.11-slim +# Base image +FROM python:3.12-alpine # Set working directory WORKDIR /app # Set environment variables ENV PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 \ - FLASK_APP=backend/main.py \ + FLASK_APP=main.py \ FLASK_ENV=development -# Install system dependencies -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - gcc \ - python3-dev \ - && rm -rf /var/lib/apt/lists/* - # Copy requirements first to leverage Docker cache COPY requirements.txt . @@ -26,12 +18,12 @@ RUN pip install --no-cache-dir -r requirements.txt # Copy the rest of the application COPY . . -# Create a non-root user and switch to it -RUN useradd -m appuser && chown -R appuser:appuser /app +# Create a non-root user +RUN addgroup -S appgroup && adduser -S appuser -G appgroup USER appuser # Expose the port the app runs on EXPOSE 8000 # Command to run the application -CMD ["python3", "backend/main.py"] \ No newline at end of file +CMD ["python", "main.py"] diff --git a/Application/backend/main.py b/Application/backend/main.py index 4b6bff85441a7eb8eab1b1ec07a9b1b6e0a5b6ab..35e954aa45185de10c64bd1abd398955eaa39ccd 100644 --- a/Application/backend/main.py +++ b/Application/backend/main.py @@ -2,7 +2,7 @@ from flask import Flask, request, jsonify from flask_cors import CORS import boto3 -import json +# import json import logging from functools import wraps from dotenv import load_dotenv @@ -33,8 +33,9 @@ def handle_aws_errors(f): app = Flask(__name__) CORS(app) -# Initialize S3 client TODO: Add your S3 credentials as ENV variables or in -# file '~/.aws/credentials' +# Initialize S3 client +# TODO: Add your S3 credentials as ENV variables or in file +# '~/.aws/credentials' s3_client = boto3.client( 's3', aws_access_key_id=os.getenv('SWITCH_ACCESS_KEY_ID'), @@ -116,7 +117,8 @@ def logout(): def unenroll(): """ Endpoint to handle user unenrollment - Expected JSON payload: {'email': 'user@example.com', 'password': 'userpassword'} + Expected JSON payload: {'email': 'user@example.com'} + or (bonus): {'email': 'user@example.com', 'password': 'userpassword'} """ pass @@ -125,6 +127,6 @@ if __name__ == '__main__': logger.info("Starting Flask application...") # Create the bucket if it doesn't exist s3_client.create_bucket(Bucket=BUCKET_NAME) - app.run(host='0.0.0.0', port=8000) + app.run(host='0.0.0.0', port=8000, debug=True) except Exception as e: logger.critical(f"Failed to start application: {str(e)}") diff --git a/Docker/Dockerfile-frontend b/Application/frontend/Dockerfile similarity index 54% rename from Docker/Dockerfile-frontend rename to Application/frontend/Dockerfile index c238aa09f66db3b4d2150591d0de4835c42d4001..67aa6d1d022bc247ad9ac6025cbd948db1b5da3f 100644 --- a/Docker/Dockerfile-frontend +++ b/Application/frontend/Dockerfile @@ -1,9 +1,14 @@ -# Use Python 3.9 slim image as base -FROM python:3.9-slim +# Base image +FROM python:3.12-alpine # Set working directory in container WORKDIR /app +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 \ + FLASK_APP=main.py \ + FLASK_ENV=development + # Copy requirements first to leverage Docker cache COPY requirements.txt . @@ -13,12 +18,12 @@ RUN pip install --no-cache-dir -r requirements.txt # Copy the rest of the application COPY . . -# Create a non-root user for security -RUN useradd -m myuser -USER myuser +# Create a non-root user +RUN addgroup -S appgroup && adduser -S appuser -G appgroup +USER appuser # Expose the port the app runs on EXPOSE 3000 # Command to run the application -CMD ["python", "main.py"] \ No newline at end of file +CMD ["python", "main.py"] diff --git a/Application/frontend/views/dashboard.html b/Application/frontend/views/dashboard.html index 44872c69b2a204c84a0a7c3bf45596188dd0f227..456c2fe261d4b5b49973bc066717819eb28c31c5 100644 --- a/Application/frontend/views/dashboard.html +++ b/Application/frontend/views/dashboard.html @@ -1,78 +1,101 @@ <!DOCTYPE html> <html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Dashboard</title> - <style> - body { - font-family: Arial, sans-serif; - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - margin: 0; - background-color: #f0f2f5; - } - .container { - text-align: center; - padding: 20px; - background-color: white; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } - .button { - display: inline-block; - padding: 10px 20px; - margin: 10px; - border: none; - border-radius: 5px; - background-color: #1877f2; - color: white; - text-decoration: none; - cursor: pointer; - } - .button:hover { - background-color: #166fe5; - } - .welcome-text { - color: #1c1e21; - margin-bottom: 20px; - } - </style> -</head> -<body> - <div class="container"> - <h1 class="welcome-text">Welcome to Dashboard</h1> - <p>You are successfully logged in!</p> - <button onclick="logout()" class="button">Logout</button> - </div> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Dashboard</title> + <style> + body { + font-family: Arial, sans-serif; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + background-color: #f0f2f5; + } + .container { + text-align: center; + padding: 20px; + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + .button { + display: inline-block; + padding: 10px 20px; + margin: 10px; + border: none; + border-radius: 5px; + background-color: #1877f2; + color: white; + text-decoration: none; + cursor: pointer; + } + .button:hover { + background-color: #166fe5; + } + .welcome-text { + color: #1c1e21; + margin-bottom: 20px; + } + </style> + </head> + <body> + <div class="container"> + <h1 class="welcome-text">Welcome to Dashboard</h1> + <p>You are successfully logged in!</p> + <button onclick="logout()" class="button">Logout</button> + <button onclick="unenroll()" class="button">Remove account</button> + </div> - <script> - // Get email from URL or localStorage (you should implement proper state management) - const email = localStorage.getItem('userEmail'); - console.log(email); - async function logout() { - try { - const response = await fetch('http://localhost:8000/logout', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ "email": email }), - }); - const data = await response.json(); - - if (data.status === 'OK') { - localStorage.removeItem('userEmail'); - window.location.href = 'index.html'; - } else { - alert('Logout failed: ' + data.status); - } - } catch (error) { - alert('Error during logout'); - } - } - </script> -</body> -</html> \ No newline at end of file + <script> + /* Get email from URL or localStorage (you should implement proper state + management) */ + const email = localStorage.getItem('userEmail'); + console.log(email); + async function logout() { + try { + const response = await fetch('http://localhost:8000/logout', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ "email": email }), + }); + const data = await response.json(); + + if (data.status === 'OK') { + localStorage.removeItem('userEmail'); + window.location.href = 'index.html'; + } else { + alert('Logout failed: ' + data.status); + } + } catch (error) { + alert('Error during logout. ' + error); + } + } + async function unenroll() { + try { + const response = await fetch('http://localhost:8000/unenroll', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ "email": email }), + }); + const data = await response.json(); + + if (data.status === 'OK') { + localStorage.removeItem('userEmail'); + window.location.href = 'index.html'; + } else { + alert('Unenroll failed: ' + data.status); + } + } catch (error) { + alert('Error during unenroll. ' + error); + } + } + </script> + </body> +</html> diff --git a/Application/frontend/views/index.html b/Application/frontend/views/index.html index dc2a953b185a3e2415afbcece7a9e70beb01fd16..1c8f3cc5feb081af9015ea490e206fa4cc079fa2 100644 --- a/Application/frontend/views/index.html +++ b/Application/frontend/views/index.html @@ -1,50 +1,49 @@ <!DOCTYPE html> <html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Welcome</title> - <style> - body { - font-family: Arial, sans-serif; - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - margin: 0; - background-color: #f0f2f5; - } - .container { - text-align: center; - padding: 20px; - background-color: white; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } - .button { - display: inline-block; - padding: 10px 20px; - margin: 10px; - border: none; - border-radius: 5px; - background-color: #1877f2; - color: white; - text-decoration: none; - cursor: pointer; - } - .button:hover { - background-color: #166fe5; - } - </style> -</head> -<body> - <div class="container"> - <h1>Welcome</h1> - <div> - <a href="login.html" class="button">Login</a> - <a href="signup.html" class="button">Sign Up</a> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Welcome</title> + <style> + body { + font-family: Arial, sans-serif; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + background-color: #f0f2f5; + } + .container { + text-align: center; + padding: 20px; + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + .button { + display: inline-block; + padding: 10px 20px; + margin: 10px; + border: none; + border-radius: 5px; + background-color: #1877f2; + color: white; + text-decoration: none; + cursor: pointer; + } + .button:hover { + background-color: #166fe5; + } + </style> + </head> + <body> + <div class="container"> + <h1>Welcome</h1> + <div> + <a href="login.html" class="button">Login</a> + <a href="signup.html" class="button">Sign Up</a> + </div> </div> - </div> -</body> -</html> - + </body> +</html> diff --git a/Application/frontend/views/login.html b/Application/frontend/views/login.html index 47ca6b0890b6f0aa61dab3855020f79461da498d..c5e5098a9a09e15398e5dd547e3effda148f20d7 100644 --- a/Application/frontend/views/login.html +++ b/Application/frontend/views/login.html @@ -1,103 +1,103 @@ <!DOCTYPE html> <html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Login</title> - <style> - body { - font-family: Arial, sans-serif; - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - margin: 0; - background-color: #f0f2f5; - } - .container { - padding: 20px; - background-color: white; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } - .form-group { - margin-bottom: 15px; - } - label { - display: block; - margin-bottom: 5px; - } - input { - width: 100%; - padding: 8px; - border: 1px solid #ddd; - border-radius: 4px; - box-sizing: border-box; - } - .button { - width: 100%; - padding: 10px; - border: none; - border-radius: 5px; - background-color: #1877f2; - color: white; - cursor: pointer; - } - .button:hover { - background-color: #166fe5; - } - .back-link { - display: block; - margin-top: 15px; - text-align: center; - color: #1877f2; - text-decoration: none; - } - </style> -</head> -<body> - <div class="container"> - <h2>Login</h2> - <form id="loginForm"> - <div class="form-group"> - <label for="email">Email:</label> - <input type="email" id="email" name="email" required> - </div> - <div class="form-group"> - <label for="password">Password:</label> - <input type="password" id="password" name="password" required> - </div> - <button type="submit" class="button">Login</button> - </form> - <a href="index.html" class="back-link">Back to Home</a> - </div> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Login</title> + <style> + body { + font-family: Arial, sans-serif; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + background-color: #f0f2f5; + } + .container { + padding: 20px; + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + .form-group { + margin-bottom: 15px; + } + label { + display: block; + margin-bottom: 5px; + } + input { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + box-sizing: border-box; + } + .button { + width: 100%; + padding: 10px; + border: none; + border-radius: 5px; + background-color: #1877f2; + color: white; + cursor: pointer; + } + .button:hover { + background-color: #166fe5; + } + .back-link { + display: block; + margin-top: 15px; + text-align: center; + color: #1877f2; + text-decoration: none; + } + </style> + </head> + <body> + <div class="container"> + <h2>Login</h2> + <form id="loginForm"> + <div class="form-group"> + <label for="email">Email:</label> + <input type="email" id="email" name="email" required> + </div> + <div class="form-group"> + <label for="password">Password:</label> + <input type="password" id="password" name="password" required> + </div> + <button type="submit" class="button">Login</button> + </form> + <a href="index.html" class="back-link">Back to Home</a> + </div> - <script> - document.getElementById('loginForm').addEventListener('submit', async (e) => { - e.preventDefault(); - const email = document.getElementById('email').value; - const password = document.getElementById('password').value; + <script> + document.getElementById('loginForm').addEventListener('submit', async (e) => { + e.preventDefault(); + const email = document.getElementById('email').value; + const password = document.getElementById('password').value; - try { - const response = await fetch('http://localhost:8000/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ email, password }), - }); - const data = await response.json(); - - if (data.status === 'OK') { - localStorage.setItem('userEmail', email); - window.location.href = 'dashboard.html'; - } else { - alert('Login failed: ' + data.status); - } - } catch (error) { - alert('Error during login'); - } - }); - </script> -</body> -</html> \ No newline at end of file + try { + const response = await fetch('http://localhost:8000/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email, password }), + }); + const data = await response.json(); + + if (data.status === 'OK') { + localStorage.setItem('userEmail', email); + window.location.href = 'dashboard.html'; + } else { + alert('Login failed: ' + data.status); + } + } catch (error) { + alert('Error during login. ' + error); + } + }); + </script> + </body> +</html> diff --git a/Application/frontend/views/signup.html b/Application/frontend/views/signup.html index 02769c5cc61ec032e8d7e7af450a4a87b8000ada..a0b5aa88961c1edbf8c760899933a4c77236c842 100644 --- a/Application/frontend/views/signup.html +++ b/Application/frontend/views/signup.html @@ -1,103 +1,103 @@ <!DOCTYPE html> <html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Sign Up</title> - <style> - body { - font-family: Arial, sans-serif; - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - margin: 0; - background-color: #f0f2f5; - } - .container { - padding: 20px; - background-color: white; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } - .form-group { - margin-bottom: 15px; - } - label { - display: block; - margin-bottom: 5px; - } - input { - width: 100%; - padding: 8px; - border: 1px solid #ddd; - border-radius: 4px; - box-sizing: border-box; - } - .button { - width: 100%; - padding: 10px; - border: none; - border-radius: 5px; - background-color: #1877f2; - color: white; - cursor: pointer; - } - .button:hover { - background-color: #166fe5; - } - .back-link { - display: block; - margin-top: 15px; - text-align: center; - color: #1877f2; - text-decoration: none; - } - </style> -</head> -<body> - <div class="container"> - <h2>Sign Up</h2> - <form id="signupForm"> - <div class="form-group"> - <label for="email">Email:</label> - <input type="email" id="email" name="email" required> - </div> - <div class="form-group"> - <label for="password">Password:</label> - <input type="password" id="password" name="password" required> - </div> - <button type="submit" class="button">Sign Up</button> - </form> - <a href="index.html" class="back-link">Back to Home</a> - </div> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Sign Up</title> + <style> + body { + font-family: Arial, sans-serif; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + background-color: #f0f2f5; + } + .container { + padding: 20px; + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + .form-group { + margin-bottom: 15px; + } + label { + display: block; + margin-bottom: 5px; + } + input { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + box-sizing: border-box; + } + .button { + width: 100%; + padding: 10px; + border: none; + border-radius: 5px; + background-color: #1877f2; + color: white; + cursor: pointer; + } + .button:hover { + background-color: #166fe5; + } + .back-link { + display: block; + margin-top: 15px; + text-align: center; + color: #1877f2; + text-decoration: none; + } + </style> + </head> + <body> + <div class="container"> + <h2>Sign Up</h2> + <form id="signupForm"> + <div class="form-group"> + <label for="email">Email:</label> + <input type="email" id="email" name="email" required> + </div> + <div class="form-group"> + <label for="password">Password:</label> + <input type="password" id="password" name="password" required> + </div> + <button type="submit" class="button">Sign Up</button> + </form> + <a href="index.html" class="back-link">Back to Home</a> + </div> - <script> - document.getElementById('signupForm').addEventListener('submit', async (e) => { - e.preventDefault(); - const email = document.getElementById('email').value; - const password = document.getElementById('password').value; + <script> + document.getElementById('signupForm').addEventListener('submit', async (e) => { + e.preventDefault(); + const email = document.getElementById('email').value; + const password = document.getElementById('password').value; - try { - const response = await fetch('http://localhost:8000/enroll', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ email, password }), - }); - const data = await response.json(); - - if (data.status === 'OK') { - alert('Sign up successful! Please login.'); - window.location.href = 'login.html'; - } else { - alert('Sign up failed: ' + data.status); - } - } catch (error) { - alert('Error during sign up'); - } - }); - </script> -</body> -</html> \ No newline at end of file + try { + const response = await fetch('http://localhost:8000/enroll', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email, password }), + }); + const data = await response.json(); + + if (data.status === 'OK') { + alert('Sign up successful! Please login.'); + window.location.href = 'login.html'; + } else { + alert('Sign up failed: ' + data.status); + } + } catch (error) { + alert('Error during sign up. ' + error); + } + }); + </script> + </body> +</html> diff --git a/Application/run.sh b/Application/run.sh index 29e8cdbb42c725e86e9bbae01c358e1d8d997304..ed3797f8524df56a7db85391be788fbaa29dea28 100755 --- a/Application/run.sh +++ b/Application/run.sh @@ -22,6 +22,8 @@ switch_access_key_id=${SWITCH_ACCESS_KEY_ID:?'Please set ENV variable'} switch_secret_access_key=${SWITCH_SECRET_ACCESS_KEY:?'Please set ENV variable'} s3_bucket_name=${S3_BUCKET_NAME:?'Please set ENV variable'} +export SWITCH_ENDPOINT_URL SWITCH_ENDPOINT_URL SWITCH_ACCESS_KEY_ID SWITCH_SECRET_ACCESS_KEY S3_BUCKET_NAME + backend_pid= backend_log="${web_sso_root}/backend/main.log" backend_main="${web_sso_root}/backend/main.py" diff --git a/Docker/README.md b/Docker/README.md deleted file mode 100644 index 6abc4b984f6f168e7c0467c3145df000c96119a9..0000000000000000000000000000000000000000 --- a/Docker/README.md +++ /dev/null @@ -1 +0,0 @@ -Docker files here diff --git a/Makefile b/Makefile index ecb6c8fa6924f017d4ccc28dd0dadb8de987bb7b..2663054b5f915a61e219782bd86deab626dd5103 100644 --- a/Makefile +++ b/Makefile @@ -12,13 +12,22 @@ backend_port := 8000 portal_url := "http://localhost:$(frontend_port)" terminal := xfce4-terminal term_opts := -term_title := "Web SSO application" + +app_components := frontend backend + +docker_username := $(DOCKER_USERNAME) +docker_bcname := web-sso + +define app_ports := +frontend:$(frontend_port) +backend:$(backend_port) +endef ifeq ($(terminal), xterm) - term_opts += -T $(term_title) -hold + term_opts += -hold else # should catch most other emulators out there - term_opts += -T $(term_title) -H + term_opts += -H endif define _help_msg := @@ -26,16 +35,33 @@ Usage: make [command] -Commands +Basic commands help ...guess what ^^ (v)install install the application locally (in a Python virtual env that will be created) (t)run (open a terminal and) run the application +The following Docker commands acts on all application images/containers + + dlogs stream app logs to separate terminal emulators + docker => docker-build + docker-push + docker-build build images + docker-push push images to Docker Hub -- needs env DOCKER_USERNAME + drestart restart app containers + drm remove (stopped) containers + drun launch app containers from images + dstart start app containers + dstatus list app containers + dstop stop app containers + Options + -s silent mode + docker_username set your Docker Hub user name. Default from env + DOCKER_USERNAME, currently '${DOCKER_USERNAME}' sandbox_dir where to install the app (under '$(prefix)/'). Default: $(sandbox_dir) - terminal the terminal emulator to be lunched with command 'trun'. Default: $(terminal). Supported: xterm, xfce4-terminal. + terminal the terminal emulator to be lunched with command + 'trun'. Default: $(terminal). Supported: xterm, xfce4-terminal. Examples @@ -55,6 +81,10 @@ Examples Run the application in terminal emulator 'xterm': make terminal=xterm trun + + Build and push your app images to Docker Hub: + + make docker endef define _s3_creds := @@ -62,8 +92,6 @@ SWITCH_ENDPOINT_URL='<your-s3-endpoint>' SWITCH_ACCESS_KEY_ID='<your-s3-access-key-id>' SWITCH_SECRET_ACCESS_KEY='<your-s3-secret-key>' S3_BUCKET_NAME='project-web-sso' - -export SWITCH_ENDPOINT_URL SWITCH_ENDPOINT_URL SWITCH_ACCESS_KEY_ID SWITCH_SECRET_ACCESS_KEY S3_BUCKET_NAME endef define echoo @@ -74,7 +102,16 @@ define echoe $(echoo) >&2 endef -.PHONY: help _sandbox _venv _s3cred install vinstall trun run +define log-info + printf >&2 "[info] %b\n" +endef + +define log-error + printf >&2 "[error] %b\n" +endef + + +.PHONY: _check_docker _check_term _danyop_by_cid _danyop_by_cname dlogs docker docker-build docker-push drestart drm drun dstart dstatus dstop help _install install run _s3cred _sandbox _term trun _venv vinstall all: help @@ -86,20 +123,22 @@ _venv: _sandbox _install: _sandbox rsync -Cabhv -L --suffix=.bak $(app_sdir) $(sandbox_dir) || exit 1 - pip install $(pip_opts) $(application_dir)/frontend/requirements.txt || - exit 1 - pip install $(pip_opts) $(application_dir)/backend/requirements.txt || - exit 1 + for comp in $(app_components); do + $(log-info) "$${comp}: installing app component's dependencies..." + pip -q install $(pip_opts) $(application_dir)/$${comp}/requirements.txt || + exit 1 + done $(MAKE) _s3cred _s3cred: [ -r $(s3_cred_file) ] && { - $(echoe) "[info] $(s3_cred_file): file exists. Won't touch it..." + $(log-info) "$(s3_cred_file): file exists. Won't touch it..." exit 0 } $(echoo) "$(_s3_creds)" > $(s3_cred_file) || exit 1 chmod 0600 $(s3_cred_file) - $(echoe) "[info] Please adapt your AWS/S3 credentials in file '$(s3_cred_file)'" + ln -s $(application_dir)/backend/.env $(s3_cred_file) + $(log-info) "Please adapt your AWS/S3 credentials in file '$(s3_cred_file)'" install: $(MAKE) pip_opts='$(pip_opts) --user' _install || exit 1 @@ -107,23 +146,111 @@ install: vinstall: _venv source $(sandbox_dir)/bin/activate $(MAKE) _install - $(echoe) "[ok] Application installed in '$(application_dir)'" - $(echoe) "[info] To activate your virtual env, please, open a terminal and run:\n\ + $(log-info) "Application installed in '$(application_dir)'" + $(log-info) "To activate your virtual env, please, open a terminal and run:\n\ cd $(sandbox_dir) && source bin/activate" run: export WEB_SSO_ROOT=$(application_dir) $(application_dir)/run.sh -trun: + +_check_term: which $(terminal) 2> /dev/null || { - $(echoe) "[error] $(terminal): not found. Please set your terminal program and retry:\n\tmake terminal=YOUR_TERMINAL_PROGRAM run" + $(log-error) "$(terminal): not found. Please set your terminal program and retry:\n\tmake terminal=YOUR_TERMINAL_EMULATOR trun" + exit 1 + } + +_term: + $(terminal) $(term_opts) -T "$(term_title)" -e "$(in_term_command)" & + +trun: _check_term + $(MAKE) term_title="Web SSO application" \ + in_term_command="sh -c 'export WEB_SSO_ROOT=$(application_dir); \ + $(application_dir)/run.sh'" _term + +_check_docker: + docker ps -q || { + $(log-error) "Please install and start docker" + exit 1 + } + [ "$(docker_username)" ] || { + $(log-error) "Please set: docker_username=DOCKER_USERNAME" exit 1 } - $(terminal) $(term_opts) \ - -e "sh -c 'export WEB_SSO_ROOT=$(application_dir); \ - $(application_dir)/run.sh'" & +docker-build: _check_docker + for comp in $(app_components); do + docker build --tag $(docker_username)/$(docker_bcname)-$${comp} \ + $(application_dir)/$${comp} + done + +docker-push: _check_docker + for comp in $(app_components); do + docker push $(docker_username)/$(docker_bcname)-$${comp} + done + +docker: + $(MAKE) docker-build && $(MAKE) docker-push + +# Use --name to avoid duplicates +drun: + ports="$(app_ports)" + for comp in $(app_components); do + port=$$(printf "%b" "$${ports}" | \ + sed -rn "s/^$${comp}:([[:digit:]]+)$$/\\1/p") + docker run --detach --name=$(docker_bcname)-$${comp} \ + --publish=$${port}:$${port} --env-file=$(s3_cred_file) \ + $(docker_username)/$(docker_bcname)-$${comp} + done + + +# Docker generic operation $dop loop by container id +_danyop_by_cid: + [ "$(dop)" ] || { + $(log-error) "No docker operation specified" + exit 1 + } + for comp in $(app_components); do + cid=$$(docker ps -aq \ + --filter="name=$(docker_bcname)-$${comp}") + if [ "$${cid}" ]; then docker $(dop) $${cid}; fi + done + +# Docker generic operation $dop loop by container name +_danyop_by_cname: + [ "$(dop)" ] || { + $(log-error) "No docker operation specified" + exit 1 + } + for comp in $(app_components); do + docker $(dop) $(docker_bcname)-$${comp} + done + +drm: + $(MAKE) dop=rm _danyop_by_cname + +dstart: + $(MAKE) dop=start _danyop_by_cname + +dstop: + $(MAKE) dop=stop _danyop_by_cname + +dstatus: + docker ps -a --filter="name=$(docker_bcname)" + $(echoe) "IP ADDRESSES" + $(MAKE) dop="inspect --format='{{.NetworkSettings.IPAddress}} {{.Name}}'" \ + _danyop_by_cname + +dlogs: _check_term + for comp in $(app_components); do + $(MAKE) term_title="Web SSO application $${comp}" \ + in_term_command="sh -c \ + 'docker container logs --follow $(docker_bcname)-$${comp}'" _term + done + +drestart: + $(MAKE) dop=restart _danyop_by_cname help: @: $(info $(_help_msg)) diff --git a/README.md b/README.md index 89cbb364eab8173a4da7412d05751e0bae9aaff5..8144a4223ec84e4c15eb2346bc1c72ce29890289 100644 --- a/README.md +++ b/README.md @@ -337,7 +337,7 @@ map: └── conf <= Cloud-init files ``` -You shall: +:hammer_and_wrench: You shall: 1. Fork this repository. 2. Complete the Python back-end file `Application/backend/main.py`. See the @@ -390,11 +390,11 @@ You will get bonus for any of the following improvements. To test your application before deploying it as a K8s service, you can install and run it locally in your workstation. -:bulb: It is strongly recommended to install Python's "virtualenv": +:hammer_and_wrench: **It is strongly recommended to install Python's "virtualenv":** * [virtualenv](https://virtualenv.pypa.io/en/latest/index.html), and * [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/). -At the top-level of your repo clone, get an overview of administrative +At the top-level of your Git repo clone, get an overview of administrative commands and options: ``` shell @@ -407,28 +407,64 @@ Then, install the application in a virtual env (automatically created) $ make vinstall ``` -Otherwise, plain install (you might need to install python dependencies -manually): +Otherwise, plain install -- you might need to install python dependencies +*manually*: ``` shell $ make install ``` +Put your SwitchEngines EC2 credentials in file +`<sandbox>/Application/s3_credentials.env`, which should be already there. + Finally, run the application in the current console: ``` shell $ make run ``` -Or, run in another terminal: +Or, run it in its own terminal: ``` shell $ make trun ``` -#### Rebuild the Docker images +#### Managing the containerized application with Docker + +:hammer_and_wrench: **Install the following Docker packages on your workstation according +to your distribution's instructions: docker(.io), docker-cli, +(docker-)buildx** + +:bulb: Please, peruse the `Makefile` to learn the real Docker commands. + +After installation, switch back to your git clone directory, then run: +``` shell +$ make docker-build +``` + +Then, run the containers: +``` shell +$ make drun +``` + +Get useful information -- IP addresses, ports, etc.: +``` shell +$ make -s dstatus +``` + +Watch logs of running containers with: +``` shell +$ make -s dlogs +``` + +When ready for deployment on your VM (you'll be asked for your password/token): +``` shell +$ docker login +$ make -s docker-push +``` -:construction: **need details** +You can manage your containers with the other make commands: dstop, dstart, +drm, etc. #### Test workflow