Merge the wilm delivery work into main
Ben Baker's installed-software collection, software delivery (run_script and
update_app), agentless adoption tool and command-queue hardening, with his
authorship preserved.
Reviewed before promotion. Fixed: the endpoints plugin tests were never collected
by pytest, the SBOM snapshot table's key would exhaust on MySQL, ingest did not
truncate two columns strict MySQL would reject, and update_app's version branch
pinned a digest for a file that route never serves. Agent packaging moved off
require_role('admin') onto a new endpoints.agentpackage permission.
Contract 0.27.0. Full suite 2231, frontend 314.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
"""endpoint installed-software (SBOM) snapshot table.
|
||||
|
||||
Adds endpointapplications: one row per installed application discovered on an
|
||||
endpoint, reported by the agent as {displayname, displayversion, publisher}.
|
||||
Stored as a per-asset snapshot (replaced on each report), the same way the
|
||||
services/ports inventories are kept as the newest list rather than history.
|
||||
|
||||
appid is groundwork for a future link to the core applications catalog (the
|
||||
"Managed Applications" concept): a nullable FK with ON DELETE SET NULL so
|
||||
retiring a managed Application does not delete discovered rows. No
|
||||
discovery-to-catalog matcher exists yet; the column is laid down now so the
|
||||
association can be added without a second migration.
|
||||
|
||||
Idempotent: creation checks the live schema first so a db.create_all() site
|
||||
(which already built the table) re-runs clean on strict MySQL 8.
|
||||
"""
|
||||
from alembic import op
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'endpoints0004apps'
|
||||
down_revision = 'endpoints0003cmdtarget'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _has_table(table_name):
|
||||
from sqlalchemy import inspect
|
||||
|
||||
return inspect(op.get_bind()).has_table(table_name)
|
||||
|
||||
|
||||
def upgrade():
|
||||
if _has_table('endpointapplications'):
|
||||
return
|
||||
|
||||
op.create_table(
|
||||
'endpointapplications',
|
||||
# BigInteger, not Integer. This is a SNAPSHOT table: every check-in
|
||||
# deletes the endpoint's rows and reinserts them, so the surrogate key
|
||||
# burns through values at (endpoints x installed apps) per report and
|
||||
# never reuses them. A 32-bit key exhausts on MySQL - a few hundred
|
||||
# endpoints reporting a few hundred apps every 15 minutes reaches 2.1
|
||||
# billion inside a couple of years, and the failure is an insert that
|
||||
# stops working forever. SQLite has no such ceiling, so no test would
|
||||
# have caught it. Changed while the table is still unreleased.
|
||||
sa.Column('endpointapplicationid',
|
||||
sa.BigInteger().with_variant(sa.Integer, 'sqlite'),
|
||||
nullable=False),
|
||||
sa.Column('assetid', sa.Integer(), nullable=False),
|
||||
sa.Column('displayname', sa.String(length=255), nullable=False),
|
||||
sa.Column('displayversion', sa.String(length=128), nullable=True),
|
||||
sa.Column('publisher', sa.String(length=255), nullable=True),
|
||||
sa.Column('appid', sa.Integer(), nullable=True),
|
||||
sa.Column('recordedat', sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('endpointapplicationid'),
|
||||
sa.ForeignKeyConstraint(
|
||||
['assetid'], ['assets.assetid'],
|
||||
name='fk_endpointapplications_assetid',
|
||||
ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(
|
||||
['appid'], ['applications.appid'],
|
||||
name='fk_endpointapplications_appid',
|
||||
ondelete='SET NULL'),
|
||||
)
|
||||
op.create_index(
|
||||
'idx_endpointapplications_assetid',
|
||||
'endpointapplications', ['assetid'])
|
||||
op.create_index(
|
||||
'idx_endpointapplications_appid',
|
||||
'endpointapplications', ['appid'])
|
||||
op.create_index(
|
||||
'idx_endpointapplications_displayname',
|
||||
'endpointapplications', ['displayname'])
|
||||
op.create_index(
|
||||
'idx_endpointapplications_assetname',
|
||||
'endpointapplications', ['assetid', 'displayname'])
|
||||
|
||||
|
||||
def downgrade():
|
||||
if not _has_table('endpointapplications'):
|
||||
return
|
||||
|
||||
op.drop_index('idx_endpointapplications_assetname',
|
||||
table_name='endpointapplications')
|
||||
op.drop_index('idx_endpointapplications_displayname',
|
||||
table_name='endpointapplications')
|
||||
op.drop_index('idx_endpointapplications_appid',
|
||||
table_name='endpointapplications')
|
||||
op.drop_index('idx_endpointapplications_assetid',
|
||||
table_name='endpointapplications')
|
||||
op.drop_table('endpointapplications')
|
||||
Reference in New Issue
Block a user