407 lines
18 KiB
Python
407 lines
18 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import Optional
|
|
|
|
from sqlalchemy import (
|
|
DateTime,
|
|
Enum as SAEnum,
|
|
ForeignKey,
|
|
Index,
|
|
JSON,
|
|
String,
|
|
Text,
|
|
UniqueConstraint,
|
|
text,
|
|
)
|
|
from sqlalchemy.dialects.mysql import BIGINT, INTEGER, TINYINT
|
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
"""Base declarative class for all models."""
|
|
|
|
|
|
# Enumerations centralized to avoid magic strings across the codebase.
|
|
class CredentialType(str, Enum):
|
|
ACCESS_KEY = "ACCESS_KEY"
|
|
ASSUME_ROLE = "ASSUME_ROLE"
|
|
|
|
|
|
class RoleName(str, Enum):
|
|
ADMIN = "ADMIN"
|
|
CUSTOMER_ADMIN = "CUSTOMER_ADMIN"
|
|
CUSTOMER_USER = "CUSTOMER_USER"
|
|
|
|
|
|
class InstanceStatus(str, Enum):
|
|
PENDING = "PENDING"
|
|
RUNNING = "RUNNING"
|
|
STOPPING = "STOPPING"
|
|
STOPPED = "STOPPED"
|
|
SHUTTING_DOWN = "SHUTTING_DOWN"
|
|
TERMINATED = "TERMINATED"
|
|
UNKNOWN = "UNKNOWN"
|
|
|
|
|
|
class InstanceDesiredStatus(str, Enum):
|
|
RUNNING = "RUNNING"
|
|
STOPPED = "STOPPED"
|
|
TERMINATED = "TERMINATED"
|
|
|
|
|
|
class JobType(str, Enum):
|
|
SYNC_INSTANCES = "SYNC_INSTANCES"
|
|
START_INSTANCES = "START_INSTANCES"
|
|
STOP_INSTANCES = "STOP_INSTANCES"
|
|
REBOOT_INSTANCES = "REBOOT_INSTANCES"
|
|
TERMINATE_INSTANCES = "TERMINATE_INSTANCES"
|
|
CREATE_INSTANCES = "CREATE_INSTANCES"
|
|
|
|
|
|
class JobStatus(str, Enum):
|
|
PENDING = "PENDING"
|
|
RUNNING = "RUNNING"
|
|
SUCCESS = "SUCCESS"
|
|
FAILED = "FAILED"
|
|
|
|
|
|
class JobItemResourceType(str, Enum):
|
|
INSTANCE = "INSTANCE"
|
|
OTHER = "OTHER"
|
|
|
|
|
|
class JobItemAction(str, Enum):
|
|
CREATE = "CREATE"
|
|
START = "START"
|
|
STOP = "STOP"
|
|
REBOOT = "REBOOT"
|
|
TERMINATE = "TERMINATE"
|
|
SYNC = "SYNC"
|
|
|
|
|
|
class JobItemStatus(str, Enum):
|
|
PENDING = "PENDING"
|
|
RUNNING = "RUNNING"
|
|
SUCCESS = "SUCCESS"
|
|
FAILED = "FAILED"
|
|
SKIPPED = "SKIPPED"
|
|
|
|
|
|
class AuditAction(str, Enum):
|
|
LOGIN = "LOGIN"
|
|
LOGOUT = "LOGOUT"
|
|
INSTANCE_CREATE = "INSTANCE_CREATE"
|
|
INSTANCE_START = "INSTANCE_START"
|
|
INSTANCE_STOP = "INSTANCE_STOP"
|
|
INSTANCE_REBOOT = "INSTANCE_REBOOT"
|
|
INSTANCE_TERMINATE = "INSTANCE_TERMINATE"
|
|
INSTANCE_SYNC = "INSTANCE_SYNC"
|
|
CREDENTIAL_CREATE = "CREDENTIAL_CREATE"
|
|
CREDENTIAL_UPDATE = "CREDENTIAL_UPDATE"
|
|
CREDENTIAL_DELETE = "CREDENTIAL_DELETE"
|
|
CUSTOMER_CREATE = "CUSTOMER_CREATE"
|
|
CUSTOMER_UPDATE = "CUSTOMER_UPDATE"
|
|
CUSTOMER_DELETE = "CUSTOMER_DELETE"
|
|
USER_CREATE = "USER_CREATE"
|
|
USER_UPDATE = "USER_UPDATE"
|
|
USER_DELETE = "USER_DELETE"
|
|
OTHER = "OTHER"
|
|
|
|
|
|
class AuditResourceType(str, Enum):
|
|
USER = "USER"
|
|
CUSTOMER = "CUSTOMER"
|
|
AWS_CREDENTIAL = "AWS_CREDENTIAL"
|
|
INSTANCE = "INSTANCE"
|
|
JOB = "JOB"
|
|
OTHER = "OTHER"
|
|
|
|
|
|
class Role(Base):
|
|
__tablename__ = "roles"
|
|
__table_args__ = (UniqueConstraint("name", name="uniq_role_name"),)
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True)
|
|
name: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
description: Mapped[Optional[str]] = mapped_column(String(255))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False
|
|
)
|
|
|
|
users: Mapped[list["User"]] = relationship("User", back_populates="role")
|
|
|
|
|
|
class Customer(Base):
|
|
__tablename__ = "customers"
|
|
__table_args__ = (UniqueConstraint("name", name="uniq_customer_name"),)
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True)
|
|
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
contact_email: Mapped[Optional[str]] = mapped_column(String(128))
|
|
is_active: Mapped[int] = mapped_column(TINYINT(1), server_default=text("1"), nullable=False)
|
|
quota_instances: Mapped[Optional[int]] = mapped_column(INTEGER(unsigned=True))
|
|
notes: Mapped[Optional[str]] = mapped_column(String(255))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False
|
|
)
|
|
|
|
users: Mapped[list["User"]] = relationship("User", back_populates="customer")
|
|
instances: Mapped[list["Instance"]] = relationship("Instance", back_populates="customer")
|
|
customer_credentials: Mapped[list["CustomerCredential"]] = relationship(
|
|
"CustomerCredential", back_populates="customer"
|
|
)
|
|
jobs: Mapped[list["Job"]] = relationship("Job", back_populates="customer")
|
|
audit_logs: Mapped[list["AuditLog"]] = relationship("AuditLog", back_populates="customer")
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
__table_args__ = (
|
|
UniqueConstraint("username", name="uniq_username"),
|
|
UniqueConstraint("email", name="uniq_email"),
|
|
Index("idx_users_role", "role_id"),
|
|
Index("idx_users_customer", "customer_id"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True)
|
|
username: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
email: Mapped[Optional[str]] = mapped_column(String(128))
|
|
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
role_id: Mapped[int] = mapped_column(ForeignKey("roles.id", ondelete="RESTRICT", onupdate="CASCADE"), nullable=False)
|
|
customer_id: Mapped[Optional[int]] = mapped_column(
|
|
ForeignKey("customers.id", ondelete="SET NULL", onupdate="CASCADE")
|
|
)
|
|
is_active: Mapped[int] = mapped_column(TINYINT(1), server_default=text("1"), nullable=False)
|
|
last_login_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False
|
|
)
|
|
|
|
role: Mapped["Role"] = relationship("Role", back_populates="users")
|
|
customer: Mapped[Optional["Customer"]] = relationship("Customer", back_populates="users")
|
|
jobs: Mapped[list["Job"]] = relationship("Job", back_populates="created_by_user")
|
|
audit_logs: Mapped[list["AuditLog"]] = relationship("AuditLog", back_populates="user")
|
|
|
|
|
|
class AWSCredential(Base):
|
|
__tablename__ = "aws_credentials"
|
|
__table_args__ = (
|
|
UniqueConstraint("account_id", "name", name="uniq_credential_account_name"),
|
|
Index("idx_credential_account", "account_id"),
|
|
Index("idx_credential_active", "is_active"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True)
|
|
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
account_id: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
credential_type: Mapped[CredentialType] = mapped_column(
|
|
SAEnum(CredentialType), nullable=False, server_default=text("'ACCESS_KEY'")
|
|
)
|
|
access_key_id: Mapped[Optional[str]] = mapped_column(String(128))
|
|
secret_access_key: Mapped[Optional[str]] = mapped_column(String(256))
|
|
role_arn: Mapped[Optional[str]] = mapped_column(String(256))
|
|
external_id: Mapped[Optional[str]] = mapped_column(String(128))
|
|
default_region: Mapped[str] = mapped_column(String(32), nullable=False, server_default=text("'ap-northeast-1'"))
|
|
is_active: Mapped[int] = mapped_column(TINYINT(1), server_default=text("1"), nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False
|
|
)
|
|
|
|
instances: Mapped[list["Instance"]] = relationship("Instance", back_populates="credential")
|
|
customer_credentials: Mapped[list["CustomerCredential"]] = relationship(
|
|
"CustomerCredential", back_populates="credential"
|
|
)
|
|
|
|
|
|
class CustomerCredential(Base):
|
|
__tablename__ = "customer_credentials"
|
|
__table_args__ = (
|
|
UniqueConstraint("customer_id", "credential_id", name="uniq_customer_credential"),
|
|
Index("idx_cc_customer", "customer_id"),
|
|
Index("idx_cc_credential", "credential_id"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True)
|
|
customer_id: Mapped[int] = mapped_column(
|
|
ForeignKey("customers.id", ondelete="CASCADE", onupdate="CASCADE"), nullable=False
|
|
)
|
|
credential_id: Mapped[int] = mapped_column(
|
|
ForeignKey("aws_credentials.id", ondelete="CASCADE", onupdate="CASCADE"), nullable=False
|
|
)
|
|
is_allowed: Mapped[int] = mapped_column(TINYINT(1), server_default=text("1"), nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False
|
|
)
|
|
|
|
customer: Mapped["Customer"] = relationship("Customer", back_populates="customer_credentials")
|
|
credential: Mapped["AWSCredential"] = relationship("AWSCredential", back_populates="customer_credentials")
|
|
|
|
|
|
class Instance(Base):
|
|
__tablename__ = "instances"
|
|
__table_args__ = (
|
|
UniqueConstraint("account_id", "region", "instance_id", name="uniq_instance_cloud"),
|
|
Index("idx_instances_customer", "customer_id"),
|
|
Index("idx_instances_status", "status"),
|
|
Index("idx_instances_region", "region"),
|
|
Index("idx_instances_last_sync", "last_sync"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True)
|
|
customer_id: Mapped[int] = mapped_column(
|
|
ForeignKey("customers.id", ondelete="CASCADE", onupdate="CASCADE"), nullable=False
|
|
)
|
|
credential_id: Mapped[Optional[int]] = mapped_column(
|
|
ForeignKey("aws_credentials.id", ondelete="SET NULL", onupdate="CASCADE")
|
|
)
|
|
account_id: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
region: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
az: Mapped[Optional[str]] = mapped_column(String(32))
|
|
instance_id: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
name_tag: Mapped[Optional[str]] = mapped_column(String(255))
|
|
instance_type: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
ami_id: Mapped[Optional[str]] = mapped_column(String(64))
|
|
key_name: Mapped[Optional[str]] = mapped_column(String(128))
|
|
public_ip: Mapped[Optional[str]] = mapped_column(String(45))
|
|
private_ip: Mapped[Optional[str]] = mapped_column(String(45))
|
|
status: Mapped[InstanceStatus] = mapped_column(
|
|
SAEnum(InstanceStatus), nullable=False, server_default=text("'UNKNOWN'")
|
|
)
|
|
desired_status: Mapped[Optional[InstanceDesiredStatus]] = mapped_column(SAEnum(InstanceDesiredStatus))
|
|
security_groups: Mapped[Optional[dict]] = mapped_column(JSON)
|
|
subnet_id: Mapped[Optional[str]] = mapped_column(String(64))
|
|
vpc_id: Mapped[Optional[str]] = mapped_column(String(64))
|
|
launched_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
|
terminated_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
|
last_sync: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
|
last_cloud_state: Mapped[Optional[dict]] = mapped_column(JSON)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False
|
|
)
|
|
|
|
customer: Mapped["Customer"] = relationship("Customer", back_populates="instances")
|
|
credential: Mapped[Optional["AWSCredential"]] = relationship("AWSCredential", back_populates="instances")
|
|
job_items: Mapped[list["JobItem"]] = relationship("JobItem", back_populates="instance")
|
|
|
|
|
|
class Job(Base):
|
|
__tablename__ = "jobs"
|
|
__table_args__ = (
|
|
UniqueConstraint("job_uuid", name="uniq_job_uuid"),
|
|
Index("idx_jobs_type", "job_type"),
|
|
Index("idx_jobs_status", "status"),
|
|
Index("idx_jobs_created_at", "created_at"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True)
|
|
job_uuid: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
job_type: Mapped[JobType] = mapped_column(SAEnum(JobType), nullable=False)
|
|
status: Mapped[JobStatus] = mapped_column(SAEnum(JobStatus), nullable=False, server_default=text("'PENDING'"))
|
|
progress: Mapped[int] = mapped_column(TINYINT(unsigned=True), nullable=False, server_default=text("0"))
|
|
total_count: Mapped[Optional[int]] = mapped_column(INTEGER(unsigned=True), server_default=text("0"))
|
|
success_count: Mapped[Optional[int]] = mapped_column(INTEGER(unsigned=True), server_default=text("0"))
|
|
fail_count: Mapped[Optional[int]] = mapped_column(INTEGER(unsigned=True), server_default=text("0"))
|
|
skipped_count: Mapped[Optional[int]] = mapped_column(INTEGER(unsigned=True), server_default=text("0"))
|
|
payload: Mapped[Optional[dict]] = mapped_column(JSON)
|
|
error_message: Mapped[Optional[str]] = mapped_column(String(512))
|
|
created_by_user_id: Mapped[Optional[int]] = mapped_column(
|
|
ForeignKey("users.id", ondelete="SET NULL", onupdate="CASCADE")
|
|
)
|
|
created_for_customer: Mapped[Optional[int]] = mapped_column(
|
|
ForeignKey("customers.id", ondelete="SET NULL", onupdate="CASCADE")
|
|
)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False)
|
|
started_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
|
finished_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False
|
|
)
|
|
|
|
created_by_user: Mapped[Optional["User"]] = relationship("User", back_populates="jobs")
|
|
customer: Mapped[Optional["Customer"]] = relationship("Customer", back_populates="jobs")
|
|
items: Mapped[list["JobItem"]] = relationship("JobItem", back_populates="job")
|
|
|
|
|
|
class JobItem(Base):
|
|
__tablename__ = "job_items"
|
|
__table_args__ = (
|
|
Index("idx_job_items_job", "job_id"),
|
|
Index("idx_job_items_instance", "resource_id"),
|
|
Index("idx_job_items_status", "status"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True)
|
|
job_id: Mapped[int] = mapped_column(ForeignKey("jobs.id", ondelete="CASCADE", onupdate="CASCADE"), nullable=False)
|
|
resource_type: Mapped[JobItemResourceType] = mapped_column(
|
|
SAEnum(JobItemResourceType), nullable=False, server_default=text("'INSTANCE'")
|
|
)
|
|
resource_id: Mapped[Optional[int]] = mapped_column(
|
|
ForeignKey("instances.id", ondelete="SET NULL", onupdate="CASCADE")
|
|
)
|
|
account_id: Mapped[Optional[str]] = mapped_column(String(32))
|
|
region: Mapped[Optional[str]] = mapped_column(String(32))
|
|
instance_id: Mapped[Optional[str]] = mapped_column(String(32))
|
|
action: Mapped[JobItemAction] = mapped_column(SAEnum(JobItemAction), nullable=False)
|
|
status: Mapped[JobItemStatus] = mapped_column(
|
|
SAEnum(JobItemStatus), nullable=False, server_default=text("'PENDING'")
|
|
)
|
|
error_message: Mapped[Optional[str]] = mapped_column(String(512))
|
|
extra: Mapped[Optional[dict]] = mapped_column(JSON)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False
|
|
)
|
|
|
|
job: Mapped["Job"] = relationship("Job", back_populates="items")
|
|
instance: Mapped[Optional["Instance"]] = relationship("Instance", back_populates="job_items")
|
|
|
|
|
|
class AuditLog(Base):
|
|
__tablename__ = "audit_logs"
|
|
__table_args__ = (
|
|
Index("idx_audit_customer", "customer_id"),
|
|
Index("idx_audit_action", "action"),
|
|
Index("idx_audit_created_at", "created_at"),
|
|
Index("idx_audit_resource", "resource_type", "resource_id"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True)
|
|
user_id: Mapped[Optional[int]] = mapped_column(
|
|
ForeignKey("users.id", ondelete="SET NULL", onupdate="CASCADE")
|
|
)
|
|
customer_id: Mapped[Optional[int]] = mapped_column(
|
|
ForeignKey("customers.id", ondelete="SET NULL", onupdate="CASCADE")
|
|
)
|
|
action: Mapped[AuditAction] = mapped_column(SAEnum(AuditAction), nullable=False)
|
|
resource_type: Mapped[AuditResourceType] = mapped_column(SAEnum(AuditResourceType), nullable=False)
|
|
resource_id: Mapped[Optional[int]] = mapped_column(BIGINT(unsigned=True))
|
|
description: Mapped[Optional[str]] = mapped_column(String(512))
|
|
payload: Mapped[Optional[dict]] = mapped_column(JSON)
|
|
ip_address: Mapped[Optional[str]] = mapped_column(String(45))
|
|
user_agent: Mapped[Optional[str]] = mapped_column(String(255))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False)
|
|
|
|
user: Mapped[Optional["User"]] = relationship("User", back_populates="audit_logs")
|
|
customer: Mapped[Optional["Customer"]] = relationship("Customer", back_populates="audit_logs")
|
|
|
|
|
|
class Setting(Base):
|
|
__tablename__ = "settings"
|
|
__table_args__ = (UniqueConstraint("k", name="uniq_settings_key"),)
|
|
|
|
id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True)
|
|
k: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
v: Mapped[Optional[str]] = mapped_column(Text)
|
|
description: Mapped[Optional[str]] = mapped_column(String(255))
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False
|
|
)
|