From 5e669e73bd33386c0fc68d826de8adbd6ab3b3d2 Mon Sep 17 00:00:00 2001 From: Celestino Rey Date: Tue, 30 Jul 2024 14:59:19 +0200 Subject: [PATCH] Con makefiles y migraciones --- RepostajesPy/K8S/Makefile | 13 ++ RepostajesPy/K8S/pv-local-repostajes.yaml | 14 +++ RepostajesPy/K8S/repostajes-deployment.yaml | 56 +++++++++ ...eypostajes-prod-persistentvolumeclaim.yaml | 14 +++ RepostajesPy/README.md | 44 +++++++ RepostajesPy/servicios/Dockerfile | 4 +- RepostajesPy/servicios/Makefile | 8 ++ RepostajesPy/servicios/migrations/README | 1 + RepostajesPy/servicios/migrations/alembic.ini | 50 ++++++++ RepostajesPy/servicios/migrations/env.py | 113 ++++++++++++++++++ .../servicios/migrations/script.py.mako | 24 ++++ RepostajesPy/servicios/repostajes/__init__.py | 17 ++- RepostajesPy/servicios/repostajes/models.py | 2 + RepostajesPy/servicios/requirements.txt | 18 ++- 14 files changed, 374 insertions(+), 4 deletions(-) create mode 100644 RepostajesPy/K8S/Makefile create mode 100644 RepostajesPy/K8S/pv-local-repostajes.yaml create mode 100644 RepostajesPy/K8S/repostajes-deployment.yaml create mode 100644 RepostajesPy/K8S/reypostajes-prod-persistentvolumeclaim.yaml create mode 100644 RepostajesPy/README.md create mode 100644 RepostajesPy/servicios/Makefile create mode 100644 RepostajesPy/servicios/migrations/README create mode 100644 RepostajesPy/servicios/migrations/alembic.ini create mode 100644 RepostajesPy/servicios/migrations/env.py create mode 100644 RepostajesPy/servicios/migrations/script.py.mako diff --git a/RepostajesPy/K8S/Makefile b/RepostajesPy/K8S/Makefile new file mode 100644 index 0000000..4e134ae --- /dev/null +++ b/RepostajesPy/K8S/Makefile @@ -0,0 +1,13 @@ +export IMG_VERSION = 7.5 +# limpia todo +all: imagen clean install + +imagen: + cd ../servicios; make + +install: + envsubst < repostajes-deployment.yaml |kubectl create -f - + +clean: + envsubst < repostajes-deployment.yaml |kubectl delete -f - + diff --git a/RepostajesPy/K8S/pv-local-repostajes.yaml b/RepostajesPy/K8S/pv-local-repostajes.yaml new file mode 100644 index 0000000..fa2f716 --- /dev/null +++ b/RepostajesPy/K8S/pv-local-repostajes.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: repostajes-app-folder + labels: + app: repostajes +spec: + capacity: + storage: 100Mi + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/Externo/repostajes" + diff --git a/RepostajesPy/K8S/repostajes-deployment.yaml b/RepostajesPy/K8S/repostajes-deployment.yaml new file mode 100644 index 0000000..3470ee8 --- /dev/null +++ b/RepostajesPy/K8S/repostajes-deployment.yaml @@ -0,0 +1,56 @@ +apiVersion: v1 +kind: Service +metadata: + name: repostajes +spec: + type: NodePort + ports: + - name: http + port: 5000 + nodePort: 30340 + targetPort: repostajes + selector: + app: repostajes +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: repostajes + labels: + app: repostajes +spec: + replicas: 1 + selector: + matchLabels: + app: repostajes + strategy: + type: Recreate + template: + metadata: + labels: + app: repostajes + spec: + containers: + - args: + - gunicorn + - --bind + - 0.0.0.0:5000 + - repostajes:create_app() + image: creylopez/repostajes:$IMG_VERSION + name: repostajes + env: + - name: SALUDO_DEMO + value: "Hola, mundo" + ports: + - containerPort: 5000 + name: repostajes + resources: {} + volumeMounts: + - mountPath: /repostajes/instance + name: repostajes-prod + restartPolicy: Always + volumes: + - name: repostajes-prod + persistentVolumeClaim: + claimName: repostajes-prod +status: {} diff --git a/RepostajesPy/K8S/reypostajes-prod-persistentvolumeclaim.yaml b/RepostajesPy/K8S/reypostajes-prod-persistentvolumeclaim.yaml new file mode 100644 index 0000000..436b253 --- /dev/null +++ b/RepostajesPy/K8S/reypostajes-prod-persistentvolumeclaim.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + creationTimestamp: null + labels: + io.kompose.service: repostajes-prod + name: repostajes-prod +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +status: {} diff --git a/RepostajesPy/README.md b/RepostajesPy/README.md new file mode 100644 index 0000000..0920ba1 --- /dev/null +++ b/RepostajesPy/README.md @@ -0,0 +1,44 @@ +# Arreglar el fallo del nombre de constrain en migraciones + +https://stackoverflow.com/questions/62640576/flask-migrate-valueerror-constraint-must-have-a-name + +# Uso de migraciones + +https://www.digitalocean.com/community/tutorials/how-to-perform-flask-sqlalchemy-migrations-using-flask-migrate + +# Migración de la base de datos. + +Cada vez que se haga un cambio en la bd + +- flask db migrate -m "Mensaje" +- flask db upgrade + +## Migration Workflow + +From this point on you have a project that is fully enabled to use database migrations. The normal migration process goes as follows: + + - You will make some changes to your models in your Python source code. + - You will then run flask db migrate to generate a new database migration for these changes. + - You will finally apply the changes to the database by running flask db upgrade. + +This cycle repeats every time new changes to the database schema are needed. + +# Insertar los datos en la base de datos desde fichero sql + +1. Borrar la base de datos. +2. flask db init +3. flask db migrate +4. flask db upgrade + +con lo anterior queda creada la base de datos. + +5. Insertar los ficheros SQL con: + + sqlite3 repostajes.db < vehiculos.sql + sqlite3 repostajes.db < repostajes.sql + +¡ojo! estos ficheros son un volcado de mysql. Hay que 'tocarlos' para ajustarse al nuevo modelo. Los que hay aquí ya están adaptados + + + + diff --git a/RepostajesPy/servicios/Dockerfile b/RepostajesPy/servicios/Dockerfile index 7957188..f974546 100644 --- a/RepostajesPy/servicios/Dockerfile +++ b/RepostajesPy/servicios/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.8-slim-buster -WORKDIR /repostajespy +WORKDIR /repostajes # set environment variables ENV PYTHONDONTWRITEBYTECODE=1 @@ -17,4 +17,4 @@ RUN pip3 install -r requirements.txt COPY . . # run entrypoint.sh -ENTRYPOINT ["/repostajespy/entrypoint.sh"] +ENTRYPOINT ["/repostajes/entrypoint.sh"] diff --git a/RepostajesPy/servicios/Makefile b/RepostajesPy/servicios/Makefile new file mode 100644 index 0000000..2de7478 --- /dev/null +++ b/RepostajesPy/servicios/Makefile @@ -0,0 +1,8 @@ +install: + + echo "Creando imagen con version ${IMG_VERSION}" + + docker build --no-cache -t creylopez/repostajes:${IMG_VERSION} . + docker push creylopez/repostajes:${IMG_VERSION} + + diff --git a/RepostajesPy/servicios/migrations/README b/RepostajesPy/servicios/migrations/README new file mode 100644 index 0000000..0e04844 --- /dev/null +++ b/RepostajesPy/servicios/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/RepostajesPy/servicios/migrations/alembic.ini b/RepostajesPy/servicios/migrations/alembic.ini new file mode 100644 index 0000000..ec9d45c --- /dev/null +++ b/RepostajesPy/servicios/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/RepostajesPy/servicios/migrations/env.py b/RepostajesPy/servicios/migrations/env.py new file mode 100644 index 0000000..4c97092 --- /dev/null +++ b/RepostajesPy/servicios/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/RepostajesPy/servicios/migrations/script.py.mako b/RepostajesPy/servicios/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/RepostajesPy/servicios/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/RepostajesPy/servicios/repostajes/__init__.py b/RepostajesPy/servicios/repostajes/__init__.py index 0b022a5..6ff2bb8 100644 --- a/RepostajesPy/servicios/repostajes/__init__.py +++ b/RepostajesPy/servicios/repostajes/__init__.py @@ -2,9 +2,22 @@ import os from flask import Flask, url_for from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user +from flask_migrate import Migrate +from sqlalchemy import MetaData + +convention = { + "ix": 'ix_%(column_0_label)s', + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s" +} # init SQLAlchemy so we can use it later in our models -db = SQLAlchemy() +metadata = MetaData(naming_convention=convention) + +db = SQLAlchemy(metadata=metadata) +migrate = Migrate(render_as_batch=True) from repostajes import paginas, auth @@ -12,6 +25,7 @@ def create_app(): app = Flask(__name__) app.config.from_prefixed_env() + app.config['SECRET_KEY'] = 'secret-key-goes-here' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///repostajes.db' app.config['UPLOAD_FOLDER'] = os.path.join(app.instance_path, 'uploads') @@ -22,6 +36,7 @@ def create_app(): from .models import db db.init_app(app) + migrate.init_app(app, db) login_manager = LoginManager() login_manager.login_view = 'auth.login' diff --git a/RepostajesPy/servicios/repostajes/models.py b/RepostajesPy/servicios/repostajes/models.py index 6191e63..2782952 100644 --- a/RepostajesPy/servicios/repostajes/models.py +++ b/RepostajesPy/servicios/repostajes/models.py @@ -3,9 +3,11 @@ from flask_login import UserMixin from . import db + class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(150), unique=True, nullable=False) + email = db.Column(db.String(150), unique=True, nullable=True) password = db.Column(db.String(150), nullable=False) photo = db.Column(db.String(150), nullable=False) diff --git a/RepostajesPy/servicios/requirements.txt b/RepostajesPy/servicios/requirements.txt index 60d258e..0e30389 100644 --- a/RepostajesPy/servicios/requirements.txt +++ b/RepostajesPy/servicios/requirements.txt @@ -11,4 +11,20 @@ MarkupSafe==2.1.5 packaging==24.1 SQLAlchemy==2.0.31 typing_extensions==4.12.2 -Werkzeug==3.0.3 \ No newline at end of file +Werkzeug==3.0.3alembic==1.13.2 +blinker==1.8.2 +click==8.1.7 +Flask==3.0.3 +Flask-Login==0.6.3 +Flask-Migrate==4.0.7 +Flask-SQLAlchemy==3.1.1 +greenlet==3.0.3 +gunicorn==22.0.0 +itsdangerous==2.2.0 +Jinja2==3.1.4 +Mako==1.3.5 +MarkupSafe==2.1.5 +packaging==24.1 +SQLAlchemy==2.0.31 +typing_extensions==4.12.2 +Werkzeug==3.0.3