"""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), ) # NOTE this is non-final, and might get removed shortly environment_classes = db.Table( 'environment_classes', db.Column('environment_id', db.ForeignKey('puppet_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), db.UniqueConstraint('class_id', 'file_id'), ) # class HostClasses(db.Model): # __tablename__ = 'host_classes' # id = db.Column(db.Integer, primary_key=True) # host_id = db.Column(db.Integer, db.ForeignKey('host.id'), nullable=False) # class_id = db.Column(db.Integer, db.ForeignKey('puppet_class.id'), nullable=False) class PuppetEnvironment(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__ = 'puppet_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') 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'{PuppetEnvironment.__tablename__}.id')) environment = db.relationship('PuppetEnvironment', back_populates='hosts') # classes = db.relationship('HostClasses', backref='host', lazy='dynamic') 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 0 to many puppet classes. Each file is uniquely identified by the pair (path, environment). """ __tablename__ = 'puppet_file' id = db.Column(db.Integer, primary_key=True) # Where we found the file (path inside environment) # e.g. /etc/puppetlabs/code/environments// path = db.Column(db.Text, nullable=False) # Puppet environment this file belongs in environment = db.Column(db.Integer, db.ForeignKey(f'{PuppetEnvironment.__tablename__}.id'), nullable=False) # Checksum of the content, should be usable as a key in PuppetFileContent checksum = db.Column(db.Text, nullable=False) # When we last read data into json last_parse = db.Column(db.Float) classes = db.relationship('PuppetClass', back_populates='files', secondary=class_files) content = db.relationship('PuppetFileContent', backref='file') __table_args__ = ( db.UniqueConstraint('path', 'environment'), ) 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. """ __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 checksum = db.Column(db.Text, nullable=False) # Output of 'puppet parser dump --format json ' json = db.Column(db.Text, nullable=False) # TODO class environment mappings? # - the same class can exist in multiple environmentns # - the same class in multiple environments might be different # - the class can come or go when the file is changed (??) # - when a node changes environment it still has its classes, but they # refer to something else now # Possibly: # nodes holds its list of classes as a list of strings # I have tables which maps class names to files per environment # What happens when two different environments have separate classes # which share a name 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) class_name = db.Column(db.Text, nullable=False, unique=True) hosts = db.relationship( 'Host', back_populates='classes', secondary=host_classes) environments = db.relationship( 'PuppetEnvironment', 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)