Hard delete applications, nest installer packages, detect PE versions
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.
This commit is contained in:
136
migrations/versions/7d46_nest_app_packages.py
Normal file
136
migrations/versions/7d46_nest_app_packages.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""Move uploaded installers into per-application directories.
|
||||
|
||||
Installers used to be stored flat as "application-<id>.<ext>", which threw away
|
||||
the name the vendor shipped. That is what made a re-import look like new
|
||||
software: the operator had no way to see that the file already in the catalogue
|
||||
was the same one they were about to upload again, so duplicate applications and
|
||||
versions accumulated. Installers now keep their filename, namespaced by
|
||||
application id so a generic "setup.exe" from two vendors cannot collide.
|
||||
|
||||
That only helps files uploaded from here on. This moves the ones already stored,
|
||||
so existing download links keep working instead of 404ing against a path the
|
||||
route no longer serves.
|
||||
|
||||
The original filename cannot be recovered - the old scheme discarded it at
|
||||
upload - so these land as "installer.<ext>". That is no worse than what they
|
||||
were called before, and re-uploading the installer restores the real name.
|
||||
|
||||
Files are MOVED, not copied or deleted: a move is reversible, which is what
|
||||
lets downgrade() put them back. This is deliberately unlike 7d45, which refused
|
||||
to touch files because it could only have deleted them.
|
||||
|
||||
The database is the part that must not be left inconsistent, so installpath is
|
||||
rewritten for every affected row whether or not its file was found on disk. A
|
||||
row pointing at a missing file is a broken download; a row pointing at the old
|
||||
scheme is a broken download that also cannot be repaired by re-uploading.
|
||||
|
||||
Revision ID: 7d46_nest_app_packages
|
||||
Revises: 7d45_purge_inactive_apps
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = '7d46_nest_app_packages'
|
||||
down_revision = '7d45_purge_inactive_apps'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
OLD_PREFIX = '/api/applications/package/'
|
||||
|
||||
# application-<id>.<ext>, the only shape the old uploader produced.
|
||||
OLD_NAME = re.compile(r'^application-(\d+)\.([A-Za-z0-9]+)$')
|
||||
|
||||
|
||||
def _package_dir():
|
||||
"""The instance-relative directory installers are stored in.
|
||||
|
||||
Resolved the same way the API resolves it. If the app context is not
|
||||
available the migration still rewrites the database and simply leaves the
|
||||
files alone, rather than failing the whole upgrade over a path lookup.
|
||||
"""
|
||||
try:
|
||||
from flask import current_app
|
||||
return os.path.join(current_app.instance_path, 'applicationpackages')
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
rows = bind.execute(sa.text(
|
||||
"SELECT appid, installpath FROM applications "
|
||||
"WHERE installpath LIKE :prefix"
|
||||
), {'prefix': OLD_PREFIX + '%'}).fetchall()
|
||||
|
||||
directory = _package_dir()
|
||||
|
||||
for appid, installpath in rows:
|
||||
tail = installpath[len(OLD_PREFIX):]
|
||||
match = OLD_NAME.match(tail)
|
||||
if not match:
|
||||
# Already nested (contains a slash) or otherwise not the old shape.
|
||||
# Leaving it alone is correct: it is already being served.
|
||||
continue
|
||||
|
||||
ext = match.group(2).lower()
|
||||
stored = f'installer.{ext}'
|
||||
|
||||
if directory:
|
||||
source = os.path.join(directory, tail)
|
||||
target_dir = os.path.join(directory, str(appid))
|
||||
if os.path.isfile(source):
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
try:
|
||||
os.replace(source, os.path.join(target_dir, stored))
|
||||
except OSError:
|
||||
# Best effort. The row is still repointed below so the
|
||||
# schema is consistent; a missing file is a re-upload.
|
||||
pass
|
||||
|
||||
bind.execute(sa.text(
|
||||
"UPDATE applications SET installpath = :path WHERE appid = :appid"
|
||||
), {'path': f'{OLD_PREFIX}{appid}/{stored}', 'appid': appid})
|
||||
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
rows = bind.execute(sa.text(
|
||||
"SELECT appid, installpath FROM applications "
|
||||
"WHERE installpath LIKE :prefix"
|
||||
), {'prefix': OLD_PREFIX + '%'}).fetchall()
|
||||
|
||||
directory = _package_dir()
|
||||
|
||||
for appid, installpath in rows:
|
||||
tail = installpath[len(OLD_PREFIX):]
|
||||
if '/' not in tail:
|
||||
continue
|
||||
nested_id, _, stored = tail.partition('/')
|
||||
if nested_id != str(appid):
|
||||
continue
|
||||
|
||||
ext = os.path.splitext(stored)[1].lstrip('.').lower()
|
||||
if not ext:
|
||||
continue
|
||||
flat = f'application-{appid}.{ext}'
|
||||
|
||||
if directory:
|
||||
source = os.path.join(directory, str(appid), stored)
|
||||
if os.path.isfile(source):
|
||||
try:
|
||||
os.replace(source, os.path.join(directory, flat))
|
||||
# Remove the now-empty directory so a re-upgrade does not
|
||||
# find a stale one. Only if empty: a directory with other
|
||||
# files in it is not ours to remove.
|
||||
os.rmdir(os.path.join(directory, str(appid)))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
bind.execute(sa.text(
|
||||
"UPDATE applications SET installpath = :path WHERE appid = :appid"
|
||||
), {'path': OLD_PREFIX + flat, 'appid': appid})
|
||||
Reference in New Issue
Block a user