fix(lint): format SNMP and credential profile files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-22 18:42:28 -05:00
parent e22163c55f
commit 231154d28b
8 changed files with 69 additions and 105 deletions

View File

@@ -41,12 +41,8 @@ def upgrade() -> None:
""") """)
) )
conn.execute( conn.execute(sa.text("ALTER TABLE credential_profiles ENABLE ROW LEVEL SECURITY"))
sa.text("ALTER TABLE credential_profiles ENABLE ROW LEVEL SECURITY") conn.execute(sa.text("ALTER TABLE credential_profiles FORCE ROW LEVEL SECURITY"))
)
conn.execute(
sa.text("ALTER TABLE credential_profiles FORCE ROW LEVEL SECURITY")
)
conn.execute( conn.execute(
sa.text(""" sa.text("""
@@ -63,20 +59,13 @@ def upgrade() -> None:
""") """)
) )
conn.execute( conn.execute(sa.text("GRANT SELECT ON credential_profiles TO poller_user"))
sa.text("GRANT SELECT ON credential_profiles TO poller_user") conn.execute(sa.text("GRANT SELECT, INSERT, UPDATE, DELETE ON credential_profiles TO app_user"))
)
conn.execute(
sa.text("GRANT SELECT, INSERT, UPDATE, DELETE ON credential_profiles TO app_user")
)
def downgrade() -> None: def downgrade() -> None:
conn = op.get_bind() conn = op.get_bind()
conn.execute( conn.execute(
sa.text( sa.text("DROP POLICY IF EXISTS credential_profiles_tenant_isolation ON credential_profiles")
"DROP POLICY IF EXISTS credential_profiles_tenant_isolation"
" ON credential_profiles"
)
) )
op.drop_table("credential_profiles") op.drop_table("credential_profiles")

View File

@@ -630,12 +630,8 @@ def upgrade() -> None:
) )
# -- RLS: system profiles visible to all tenants ----------------------- # -- RLS: system profiles visible to all tenants -----------------------
conn.execute( conn.execute(sa.text("ALTER TABLE snmp_profiles ENABLE ROW LEVEL SECURITY"))
sa.text("ALTER TABLE snmp_profiles ENABLE ROW LEVEL SECURITY") conn.execute(sa.text("ALTER TABLE snmp_profiles FORCE ROW LEVEL SECURITY"))
)
conn.execute(
sa.text("ALTER TABLE snmp_profiles FORCE ROW LEVEL SECURITY")
)
conn.execute( conn.execute(
sa.text(""" sa.text("""
CREATE POLICY snmp_profiles_tenant_isolation CREATE POLICY snmp_profiles_tenant_isolation
@@ -648,12 +644,8 @@ def upgrade() -> None:
""") """)
) )
conn.execute( conn.execute(sa.text("GRANT SELECT ON snmp_profiles TO poller_user"))
sa.text("GRANT SELECT ON snmp_profiles TO poller_user") conn.execute(sa.text("GRANT SELECT, INSERT, UPDATE, DELETE ON snmp_profiles TO app_user"))
)
conn.execute(
sa.text("GRANT SELECT, INSERT, UPDATE, DELETE ON snmp_profiles TO app_user")
)
# -- Seed 6 system profiles -------------------------------------------- # -- Seed 6 system profiles --------------------------------------------
for profile in SEED_PROFILES: for profile in SEED_PROFILES:
@@ -679,10 +671,5 @@ def upgrade() -> None:
def downgrade() -> None: def downgrade() -> None:
conn = op.get_bind() conn = op.get_bind()
conn.execute( conn.execute(sa.text("DROP POLICY IF EXISTS snmp_profiles_tenant_isolation ON snmp_profiles"))
sa.text(
"DROP POLICY IF EXISTS snmp_profiles_tenant_isolation"
" ON snmp_profiles"
)
)
op.drop_table("snmp_profiles") op.drop_table("snmp_profiles")

View File

@@ -29,25 +29,12 @@ def upgrade() -> None:
conn.execute(sa.text("SET lock_timeout = '3s'")) conn.execute(sa.text("SET lock_timeout = '3s'"))
conn.execute( conn.execute(
sa.text( sa.text("ALTER TABLE devices ADD COLUMN device_type TEXT NOT NULL DEFAULT 'routeros'")
"ALTER TABLE devices"
" ADD COLUMN device_type TEXT NOT NULL DEFAULT 'routeros'"
)
) )
conn.execute( conn.execute(sa.text("ALTER TABLE devices ADD COLUMN snmp_port INTEGER DEFAULT 161"))
sa.text(
"ALTER TABLE devices"
" ADD COLUMN snmp_port INTEGER DEFAULT 161"
)
)
conn.execute( conn.execute(sa.text("ALTER TABLE devices ADD COLUMN snmp_version TEXT"))
sa.text(
"ALTER TABLE devices"
" ADD COLUMN snmp_version TEXT"
)
)
conn.execute( conn.execute(
sa.text( sa.text(
@@ -69,18 +56,8 @@ def upgrade() -> None:
def downgrade() -> None: def downgrade() -> None:
conn = op.get_bind() conn = op.get_bind()
conn.execute( conn.execute(sa.text("ALTER TABLE devices DROP COLUMN IF EXISTS credential_profile_id"))
sa.text("ALTER TABLE devices DROP COLUMN IF EXISTS credential_profile_id") conn.execute(sa.text("ALTER TABLE devices DROP COLUMN IF EXISTS snmp_profile_id"))
) conn.execute(sa.text("ALTER TABLE devices DROP COLUMN IF EXISTS snmp_version"))
conn.execute( conn.execute(sa.text("ALTER TABLE devices DROP COLUMN IF EXISTS snmp_port"))
sa.text("ALTER TABLE devices DROP COLUMN IF EXISTS snmp_profile_id") conn.execute(sa.text("ALTER TABLE devices DROP COLUMN IF EXISTS device_type"))
)
conn.execute(
sa.text("ALTER TABLE devices DROP COLUMN IF EXISTS snmp_version")
)
conn.execute(
sa.text("ALTER TABLE devices DROP COLUMN IF EXISTS snmp_port")
)
conn.execute(
sa.text("ALTER TABLE devices DROP COLUMN IF EXISTS device_type")
)

View File

@@ -44,11 +44,7 @@ def upgrade() -> None:
conn.execute(sa.text("SELECT create_hypertable('snmp_metrics', 'time')")) conn.execute(sa.text("SELECT create_hypertable('snmp_metrics', 'time')"))
conn.execute( conn.execute(sa.text("SELECT add_retention_policy('snmp_metrics', INTERVAL '90 days')"))
sa.text(
"SELECT add_retention_policy('snmp_metrics', INTERVAL '90 days')"
)
)
conn.execute( conn.execute(
sa.text(""" sa.text("""
@@ -57,12 +53,8 @@ def upgrade() -> None:
""") """)
) )
conn.execute( conn.execute(sa.text("ALTER TABLE snmp_metrics ENABLE ROW LEVEL SECURITY"))
sa.text("ALTER TABLE snmp_metrics ENABLE ROW LEVEL SECURITY") conn.execute(sa.text("ALTER TABLE snmp_metrics FORCE ROW LEVEL SECURITY"))
)
conn.execute(
sa.text("ALTER TABLE snmp_metrics FORCE ROW LEVEL SECURITY")
)
conn.execute( conn.execute(
sa.text(""" sa.text("""
@@ -75,9 +67,7 @@ def upgrade() -> None:
""") """)
) )
conn.execute( conn.execute(sa.text("GRANT SELECT, INSERT ON snmp_metrics TO app_user"))
sa.text("GRANT SELECT, INSERT ON snmp_metrics TO app_user")
)
def downgrade() -> None: def downgrade() -> None:

View File

@@ -113,7 +113,10 @@ async def update_profile(
"""Update a credential profile. Requires operator role or above.""" """Update a credential profile. Requires operator role or above."""
await _check_tenant_access(current_user, tenant_id, db) await _check_tenant_access(current_user, tenant_id, db)
return await credential_profile_service.update_profile( return await credential_profile_service.update_profile(
db=db, tenant_id=tenant_id, profile_id=profile_id, data=data, db=db,
tenant_id=tenant_id,
profile_id=profile_id,
data=data,
user_id=current_user.user_id, user_id=current_user.user_id,
) )
@@ -136,7 +139,9 @@ async def delete_profile(
""" """
await _check_tenant_access(current_user, tenant_id, db) await _check_tenant_access(current_user, tenant_id, db)
await credential_profile_service.delete_profile( await credential_profile_service.delete_profile(
db=db, tenant_id=tenant_id, profile_id=profile_id, db=db,
tenant_id=tenant_id,
profile_id=profile_id,
user_id=current_user.user_id, user_id=current_user.user_id,
) )

View File

@@ -31,7 +31,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings from app.config import settings
from app.database import get_db from app.database import get_db
from app.middleware.rbac import require_operator_or_above, require_scope, require_tenant_admin_or_above from app.middleware.rbac import (
require_operator_or_above,
require_scope,
require_tenant_admin_or_above,
)
from app.middleware.tenant_context import CurrentUser, get_current_user from app.middleware.tenant_context import CurrentUser, get_current_user
from app.routers.devices import _check_tenant_access from app.routers.devices import _check_tenant_access
from app.schemas.snmp_profile import ( from app.schemas.snmp_profile import (
@@ -252,7 +256,7 @@ async def update_profile(
sql = f""" sql = f"""
UPDATE snmp_profiles UPDATE snmp_profiles
SET {', '.join(set_clauses)} SET {", ".join(set_clauses)}
WHERE id = :profile_id AND tenant_id = :tenant_id WHERE id = :profile_id AND tenant_id = :tenant_id
RETURNING id, tenant_id, name, description, sys_object_id, vendor, RETURNING id, tenant_id, name, description, sys_object_id, vendor,
category, is_system, created_at, updated_at category, is_system, created_at, updated_at

View File

@@ -46,9 +46,7 @@ class CredentialProfileCreate(BaseModel):
@classmethod @classmethod
def validate_credential_type(cls, v: str) -> str: def validate_credential_type(cls, v: str) -> str:
if v not in VALID_CREDENTIAL_TYPES: if v not in VALID_CREDENTIAL_TYPES:
raise ValueError( raise ValueError(f"credential_type must be one of: {', '.join(VALID_CREDENTIAL_TYPES)}")
f"credential_type must be one of: {', '.join(VALID_CREDENTIAL_TYPES)}"
)
return v return v
@model_validator(mode="after") @model_validator(mode="after")
@@ -141,9 +139,7 @@ class CredentialProfileUpdate(BaseModel):
if v is None: if v is None:
return v return v
if v not in VALID_CREDENTIAL_TYPES: if v not in VALID_CREDENTIAL_TYPES:
raise ValueError( raise ValueError(f"credential_type must be one of: {', '.join(VALID_CREDENTIAL_TYPES)}")
f"credential_type must be one of: {', '.join(VALID_CREDENTIAL_TYPES)}"
)
return v return v
@model_validator(mode="after") @model_validator(mode="after")
@@ -151,9 +147,14 @@ class CredentialProfileUpdate(BaseModel):
"""Validate credential fields only when credential_type or credential fields change.""" """Validate credential fields only when credential_type or credential fields change."""
# Collect which credential fields were provided # Collect which credential fields were provided
cred_fields = { cred_fields = {
"username", "password", "community", "username",
"security_level", "auth_protocol", "auth_passphrase", "password",
"priv_protocol", "priv_passphrase", "community",
"security_level",
"auth_protocol",
"auth_passphrase",
"priv_protocol",
"priv_passphrase",
} }
has_cred_changes = any(getattr(self, f) is not None for f in cred_fields) has_cred_changes = any(getattr(self, f) is not None for f in cred_fields)

View File

@@ -65,7 +65,9 @@ def _build_credential_json(data: CredentialProfileCreate | CredentialProfileUpda
raise ValueError(f"Unknown credential_type: {ct}") raise ValueError(f"Unknown credential_type: {ct}")
def _profile_response(profile: CredentialProfile, device_count: int = 0) -> CredentialProfileResponse: def _profile_response(
profile: CredentialProfile, device_count: int = 0
) -> CredentialProfileResponse:
"""Build a CredentialProfileResponse from an ORM instance.""" """Build a CredentialProfileResponse from an ORM instance."""
return CredentialProfileResponse( return CredentialProfileResponse(
id=profile.id, id=profile.id,
@@ -116,9 +118,11 @@ async def get_profiles(
credential_type: str | None = None, credential_type: str | None = None,
) -> CredentialProfileListResponse: ) -> CredentialProfileListResponse:
"""List all credential profiles for a tenant.""" """List all credential profiles for a tenant."""
query = select(CredentialProfile).where( query = (
CredentialProfile.tenant_id == tenant_id select(CredentialProfile)
).order_by(CredentialProfile.name) .where(CredentialProfile.tenant_id == tenant_id)
.order_by(CredentialProfile.name)
)
if credential_type: if credential_type:
query = query.where(CredentialProfile.credential_type == credential_type) query = query.where(CredentialProfile.credential_type == credential_type)
@@ -141,10 +145,7 @@ async def get_profiles(
for row in count_result: for row in count_result:
device_counts[row.credential_profile_id] = row.cnt device_counts[row.credential_profile_id] = row.cnt
responses = [ responses = [_profile_response(p, device_count=device_counts.get(p.id, 0)) for p in profiles]
_profile_response(p, device_count=device_counts.get(p.id, 0))
for p in profiles
]
return CredentialProfileListResponse(profiles=responses) return CredentialProfileListResponse(profiles=responses)
@@ -211,9 +212,14 @@ async def update_profile(
# Determine if credential re-encryption is needed # Determine if credential re-encryption is needed
cred_fields = { cred_fields = {
"username", "password", "community", "username",
"security_level", "auth_protocol", "auth_passphrase", "password",
"priv_protocol", "priv_passphrase", "community",
"security_level",
"auth_protocol",
"auth_passphrase",
"priv_protocol",
"priv_passphrase",
} }
has_cred_changes = any(getattr(data, f) is not None for f in cred_fields) has_cred_changes = any(getattr(data, f) is not None for f in cred_fields)
type_changed = data.credential_type is not None type_changed = data.credential_type is not None
@@ -241,13 +247,18 @@ async def update_profile(
action="credential_profile.update", action="credential_profile.update",
resource_type="credential_profile", resource_type="credential_profile",
resource_id=str(profile.id), resource_id=str(profile.id),
details={"name": profile.name, "updated_fields": list(data.model_dump(exclude_unset=True).keys())}, details={
"name": profile.name,
"updated_fields": list(data.model_dump(exclude_unset=True).keys()),
},
) )
return _profile_response(profile, device_count=dc) return _profile_response(profile, device_count=dc)
def _merge_update(data: CredentialProfileUpdate, profile: CredentialProfile) -> CredentialProfileUpdate: def _merge_update(
data: CredentialProfileUpdate, profile: CredentialProfile
) -> CredentialProfileUpdate:
"""For partial credential updates, overlay data onto existing profile type. """For partial credential updates, overlay data onto existing profile type.
When credential_type is not changing but individual credential fields are, When credential_type is not changing but individual credential fields are,