"""Database model for application.""" from flask_sqlalchemy import SQLAlchemy # db = SQLAlchemy(session_options={"autoflush": False}) db = SQLAlchemy() def init_app(app): """Adds database bindings to a Flask App.""" db.init_app(app) import logging # logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) host_classes = db.Table( 'host_classes', db.Column('host_id', db.ForeignKey('host.id'), primary_key=True), db.Column('class_id', db.ForeignKey('puppet_class.id'), primary_key=True), ) environment_classes = db.Table( 'environment_classes', db.Column('environment_id', db.ForeignKey('environment.id'), primary_key=True), db.Column('class_id', db.ForeignKey('puppet_class.id'), primary_key=True), ) class_files = db.Table( 'class_files', db.Column('class_id', db.ForeignKey('puppet_class.id'), primary_key=True), db.Column('file_id', db.ForeignKey('puppet_file.id'), primary_key=True), ) class Environment(db.Model): """ A puppet environment. An enviromnet is a collection of modules, but here we only keep the files of the modules, in PuppetFile. """ __tablename__ = 'environment' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Text, nullable=False, unique=True) classes = db.relationship( 'PuppetClass', back_populates='environments', secondary=environment_classes) hosts = db.relationship( 'Host', back_populates='environment') files = db.relationship( 'PuppetFile', back_populates='environment') class Host(db.Model): """ Single computer. A computer has a name (machine.example.com.), an environment (production) and a list of puppet classes. (TODO and direct values?) """ __tablename__ = 'host' id = db.Column(db.Integer, primary_key=True) fqdn = db.Column(db.Text, nullable=False, unique=True) environment_id = db.Column(db.Integer, db.ForeignKey(f'{Environment.__tablename__}.id')) environment = db.relationship('Environment', back_populates='hosts') classes = db.relationship( 'PuppetClass', back_populates='hosts', secondary=host_classes) def serialize(self): # pylint: disable=missing-function-docstring return {column.name: self.__getattribute__(column.name) for column in self.__table__.columns} class PuppetFile(db.Model): """ Puppet source code file. Keeps track of known puppet files. Each file contains any number of puppet classes. Each file optionally references a PuppetFileContent object, which contains the (parsed) contents of the file. Multiple PuppetFile can reference the same PuppetFileContent. (child) """ __tablename__ = 'puppet_file' id = db.Column(db.Integer, primary_key=True) # Where we found the file (path inside environment_id) # e.g. /etc/puppetlabs/code/environments// path = db.Column(db.Text, nullable=False) # Puppet environment_id this file belongs in environment_id = db.Column(db.Integer, db.ForeignKey(f'{Environment.__tablename__}.id'), nullable=False) environment = db.relationship('Environment', back_populates='files') checksum = db.Column(db.Text, #db.ForeignKey('puppet_file_content.checksum'), nullable=False) content = db.relationship('PuppetFileContent', uselist=False, primaryjoin=lambda: PuppetFile.checksum == db.foreign(PuppetFileContent.checksum)) # When we last read data into json last_parse = db.Column(db.Float) classes = db.relationship('PuppetClass', back_populates='files', secondary=class_files) __table_args__ = ( db.UniqueConstraint('path', 'environment_id'), ) class PuppetFileContent(db.Model): """ (Parsed) contents of puppet source files. Separate from PuppetFile since many environments can share files, and I don't want to store reduntand data. (parent) """ __tablename__ = 'puppet_file_content' # id = db.Column(db.Integer, primary_key=True) # file_id = db.Column(db.Integer, db.ForeignKey(f'{PuppetFile.__tablename__}.id')) # Checksum of the original file # NOT marked as a foreign key which references # PuppetFile.checksum, since content without a coresponding file # is fine, and will be collected later checksum = db.Column(db.Text, primary_key=True) # files = db.relationship('PuppetFile', back_populates='content', # foreign_keys=[checksum], # primaryjoin=lambda: PuppetFile.checksum == PuppetFileContent.checksum) files = db.relationship('PuppetFile', primaryjoin=lambda: PuppetFileContent.checksum == db.foreign(PuppetFile.checksum)) # Output of 'puppet parser dump --format json ' json = db.Column(db.Text, nullable=False) class PuppetClass(db.Model): """ A puppet class. The class itself only keeps track of its name here, and mostly ensures that only existing classes can be added to a given node/host. """ __tablename__ = 'puppet_class' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Text, nullable=False, unique=True) hosts = db.relationship( 'Host', back_populates='classes', secondary=host_classes) environments = db.relationship( 'Environment', back_populates='classes', secondary=environment_classes) files = db.relationship( 'PuppetFile', back_populates='classes', secondary=class_files) class Misc(db.Model): __tablename__ = 'misc' id = db.Column(db.Integer, primary_key=True) key = db.Column(db.Text, nullable=False) value = db.Column(db.Text)