Soft delete left orphaned executables on disk and let a deleted name collide with a fresh import, so delete is now a hard delete that removes the associated files. The destructive paths (delete, revert-to-record) require a typed OK because neither can be undone. Installers keep their filename at import and nest per application/version, which stops repeat imports doubling up. Version now comes from the same PE detection the endpoints agent uses; when no version can be read the caller is asked for one rather than silently storing a blank. Applications also surface Version and a monitorable toggle, with launch path and process name required only while monitorable is ticked. Fleet shows the PC alias, matching PC Details.
87 lines
2.8 KiB
Python
87 lines
2.8 KiB
Python
"""Purge soft-deleted applications that still reserve their names.
|
|
|
|
Delete used to set isactive=False and leave the row in place. The uniqueness
|
|
check on create looks at every row regardless of isactive, so each of those
|
|
rows kept its name reserved permanently: creating an application with the same
|
|
name failed with "already exists" against a row no screen would display, and
|
|
nothing in the UI could clear it.
|
|
|
|
Delete is a hard delete now, but that only fixes rows deleted from here on.
|
|
This clears the ones already stranded, in every environment rather than just
|
|
the one where the bug was noticed.
|
|
|
|
Dependent rows are cleared the same way the API does it, and for the same
|
|
reasons: appversions and computerinstalledapps are NOT NULL and meaningless
|
|
without their application, while knowledgebase.appid is nullable and the
|
|
article is somebody's writing, so it is unlinked rather than destroyed.
|
|
|
|
Uploaded installers belonging to these rows are deliberately NOT touched. A
|
|
migration that deletes files cannot be rolled back and cannot be dry-run, and a
|
|
stray file is harmless next to that risk. Deletes made through the API do clean
|
|
them up.
|
|
|
|
Revision ID: 7d45_purge_inactive_apps
|
|
Revises: 7d44_app_monitorable
|
|
"""
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
revision = '7d45_purge_inactive_apps'
|
|
down_revision = '7d44_app_monitorable'
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def _table_exists(bind, name):
|
|
"""True when a table is present.
|
|
|
|
The plugin tables are only there when the plugin is installed, so each is
|
|
checked before being touched.
|
|
"""
|
|
return sa.inspect(bind).has_table(name)
|
|
|
|
|
|
def upgrade():
|
|
bind = op.get_bind()
|
|
|
|
if not _table_exists(bind, 'applications'):
|
|
return
|
|
|
|
doomed = [row[0] for row in bind.execute(sa.text(
|
|
'SELECT appid FROM applications WHERE isactive = false'
|
|
))]
|
|
|
|
if not doomed:
|
|
return
|
|
|
|
for appid in doomed:
|
|
params = {'appid': appid}
|
|
|
|
if _table_exists(bind, 'knowledgebase'):
|
|
bind.execute(sa.text(
|
|
'UPDATE knowledgebase SET appid = NULL WHERE appid = :appid'
|
|
), params)
|
|
|
|
if _table_exists(bind, 'computerinstalledapps'):
|
|
bind.execute(sa.text(
|
|
'DELETE FROM computerinstalledapps WHERE appid = :appid'
|
|
), params)
|
|
|
|
if _table_exists(bind, 'appversions'):
|
|
bind.execute(sa.text(
|
|
'DELETE FROM appversions WHERE appid = :appid'
|
|
), params)
|
|
|
|
bind.execute(sa.text(
|
|
'DELETE FROM applications WHERE appid = :appid'
|
|
), params)
|
|
|
|
|
|
def downgrade():
|
|
# The rows are gone and their contents were not captured, so there is
|
|
# nothing to restore. Failing loudly is better than pretending otherwise.
|
|
raise NotImplementedError(
|
|
'Purged applications cannot be restored; recover from a backup.')
|