Skip to content

feat: add general-purpose lifecycle hooks framework (#1529)#1798

Open
sergio-sisternes-epam wants to merge 9 commits into
mainfrom
sergio-sisternes-epam/feat-installation-analytics-hooks
Open

feat: add general-purpose lifecycle hooks framework (#1529)#1798
sergio-sisternes-epam wants to merge 9 commits into
mainfrom
sergio-sisternes-epam/feat-installation-analytics-hooks

Conversation

@sergio-sisternes-epam

@sergio-sisternes-epam sergio-sisternes-epam commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

TL;DR

Adds a Copilot CLI-aligned lifecycle hooks framework that fires events on install, update, and uninstall -- enabling enterprise analytics, CI integrations, and custom automation via standalone JSON hook files.

Problem (WHY)

Enterprise users need opt-in analytics and extensibility around package lifecycle events (install, update, uninstall). There was no event system to hook into -- users could not build integrations like Slack notifications, audit logging, or telemetry collection on top of APM's package operations. See #1529.

Approach (WHAT)

Follow the Copilot CLI hooks model: standalone JSON hook files discovered from well-known locations, with two hook types (command and HTTP). All hooks are additive across sources and policy hooks cannot be disabled.

Discovery sources (priority order)

Priority Source Path Who controls
1 Policy /etc/apm/policy.d/*.json Platform/IT team
2 User ~/.apm/hooks/*.json Individual user
3 Project .apm/hooks.json Project

Lifecycle events

pre-install, post-install, pre-update, post-update, pre-uninstall, post-uninstall

Hook types

  • command -- runs a shell command, receives event JSON on stdin
  • http -- sends HTTPS POST with event JSON body, supports header env-var expansion

CLI commands

  • apm hooks -- list all discovered hooks
  • apm hooks init -- scaffold a starter .apm/hooks.json
  • apm hooks test -- dry-run a synthetic event
  • apm hooks validate -- check all hook files for errors

Hook output logging

All hook execution output is logged to ~/.apm/logs/hooks.log with timestamps, event name, hook type, target, status, and stdout/stderr.

Implementation (HOW)

New files

  • src/apm_cli/core/lifecycle_hooks.py -- HookEntry model, discovery, runner
  • src/apm_cli/core/hook_executors.py -- command/HTTP executors, log file writer
  • src/apm_cli/commands/hooks.py -- CLI group with init/test/validate subcommands
  • docs/src/content/docs/enterprise/lifecycle-hooks.md -- full documentation

Modified files

  • src/apm_cli/install/service.py -- fire pre/post-install hooks
  • src/apm_cli/commands/uninstall/cli.py -- fire pre/post-uninstall hooks
  • src/apm_cli/commands/update.py -- fire pre/post-update hooks
  • src/apm_cli/cli.py -- register hooks command group

Tests

  • tests/unit/core/test_lifecycle_hooks.py -- 33 tests (schema, parsing, discovery, runner)
  • tests/unit/core/test_hook_executors.py -- 35 tests (executors, env expansion, cwd, logging)
  • tests/unit/commands/test_hooks.py -- 25 tests (CLI commands)

Validation

  • All 17,232 unit tests pass
  • Full lint chain passes (ruff check, ruff format, pylint R0801, auth-signal lint)
  • Integration tested all 6 lifecycle events end-to-end
  • Hook output log file verified with real command hooks

Closes #1529

apm-spec-waiver: lifecycle hooks are opt-in fire-and-forget observers that do not alter install/update/uninstall contract behaviour

Sergio Sisternes and others added 5 commits June 13, 2026 15:52
Implement a lifecycle hook framework that fires custom actions at
install, update, and uninstall time. Three hook types are supported:
shell commands, HTTP webhooks, and executable scripts.

Hooks can be configured at project level (apm.yml), global level
(~/.apm/config.json), and policy level (apm-policy.yml). Policy hooks
run first and cannot be removed by the project. All hooks are
fire-and-forget with error isolation -- failures never block the CLI.

New files:
- src/apm_cli/core/lifecycle_hooks.py: event model, hook definitions,
  runner, discovery, and build_runner_from_context convenience function
- src/apm_cli/core/hook_executors.py: webhook (HTTPS-only, 2s timeout,
  daemon thread), command (subprocess, 30s timeout), and script
  (path-traversal guard) executors

Wiring:
- InstallService.run(): pre-install / post-install hooks
- uninstall/cli.py: pre-uninstall / post-uninstall hooks
- update.py: pre-update / post-update hooks

Supporting changes:
- apm_package.py: lifecycle_hooks field and parsing
- policy/schema.py: LifecycleHooksPolicy (require, deny_types)
- policy/parser.py: lifecycle_hooks policy parsing
- config.py: get/set/unset_lifecycle_hooks for global config

Tests: 50 unit tests covering models, runner, executors, collection,
deduplication, error isolation, and security guards.

Docs: enterprise/lifecycle-hooks.md guide page.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace inline lifecycle_hooks config (apm.yml, config.json, policy)
with standalone JSON hook files discovered from well-known directories:

- Policy:  /etc/apm/policy.d/*.json
- User:    ~/.apm/hooks/*.json
- Project: .apm/hooks/*.json

Key changes:
- Two hook types: command (stdin delivery) and http (HTTPS POST)
- Drop script type (subsumed by command with bash field)
- Drop token_env (replaced by headers with $ENV_VAR expansion)
- Event payload delivered via stdin for commands (not env var)
- Add working_directory field to event payload
- Remove lifecycle_hooks from APMPackage, ApmPolicy, config.py
- Rewrite executors, tests (58 passing), and documentation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Hook stdout, stderr, exit code, and execution status are now appended
to ~/.apm/logs/hooks.log (or $APM_HOME/logs/hooks.log) after every
hook execution.  This gives administrators an audit trail without
requiring verbose CLI output.

Log writing is fire-and-forget -- failures are silently swallowed to
ensure logging never breaks the CLI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three new sub-commands under 'apm hooks':

- hooks init   -- scaffold a starter hook JSON file
- hooks test   -- dry-run a synthetic event through all hooks
- hooks validate -- check all hook files for schema errors

Includes 26 tests and documentation updates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Change project-level hook discovery from a directory (.apm/hooks/*.json)
to a single file (.apm/hooks.json). Update init, validate commands,
all tests, and documentation accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 16, 2026 08:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a lifecycle hooks framework to APM so installs/updates/uninstalls can emit structured events to user/admin-defined “command” and “http” hooks, plus a new apm hooks CLI group and accompanying enterprise documentation.

Changes:

  • Introduces hook discovery + event models + runner (lifecycle_hooks.py) and executors with stdout/stderr logging (hook_executors.py).
  • Fires lifecycle events from install/update/uninstall flows and registers the new CLI command group.
  • Adds unit tests covering parsing/discovery/execution and documentation for enterprise usage.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
tests/unit/core/test_lifecycle_hooks.py New unit tests for hook models, discovery, and runner behavior.
tests/unit/core/test_hook_executors.py New unit tests for HTTP/command executors, env expansion, cwd, and hooks log output.
tests/unit/commands/test_hooks.py New unit tests for apm hooks list/init/test/validate commands.
src/apm_cli/install/service.py Fires pre-install/post-install hooks around the install pipeline and adds helper builders.
src/apm_cli/core/lifecycle_hooks.py Adds lifecycle event schema, hook entry model, JSON parsing, and discovery across policy/user/project.
src/apm_cli/core/hook_executors.py Implements command + HTTP hook execution and appends results to hooks.log.
src/apm_cli/commands/update.py Fires pre-update/post-update hooks around the update pipeline.
src/apm_cli/commands/uninstall/cli.py Fires pre-uninstall/post-uninstall hooks around uninstall.
src/apm_cli/commands/hooks.py New apm hooks command group (list/init/test/validate).
src/apm_cli/cli.py Registers the new hooks command group.
docs/src/content/docs/enterprise/lifecycle-hooks.md Adds enterprise documentation for hook format, discovery, security, logging, and CLI usage.

Comment thread src/apm_cli/install/service.py Outdated
Comment thread src/apm_cli/install/service.py Outdated
Comment thread src/apm_cli/install/service.py Outdated
Comment thread src/apm_cli/core/lifecycle_hooks.py
Comment thread src/apm_cli/commands/hooks.py Outdated
Comment thread src/apm_cli/core/hook_executors.py Outdated
Comment thread src/apm_cli/core/hook_executors.py
Comment thread src/apm_cli/commands/update.py
Comment thread src/apm_cli/core/hook_executors.py Outdated
Comment thread src/apm_cli/commands/hooks.py
@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label Jun 19, 2026
Sergio Sisternes and others added 4 commits June 19, 2026 11:18
- Add credential denylist to _expand_env_vars: blocks TOKEN, SECRET,
  PAT, KEY, PASSWORD, CREDENTIAL pattern vars from HTTP header expansion
- Strip credential-pattern vars from _build_hook_env so command hooks
  cannot inherit GITHUB_APM_PAT, ADO_APM_PAT, etc.
- Fix console import in hooks.py: replace nonexistent 'console' symbol
  with _get_console() calls (fixes dead Rich display layer)
- Add HTTP thread drain in 'apm hooks test' so log entries are written
  before CLI exits (bounded 15s join per thread)
- Update security.md: retract absolute 'no network calls' and 'no code
  execution' claims to reflect opt-in lifecycle hooks reality
- Update execute_hook/fire() signatures to return threads for drain
- Change verbose message from 'sent' to 'dispatched' (accurate wording)
- Add comprehensive tests for credential denylist and env stripping

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nes-epam/feat-installation-analytics-hooks

# Conflicts:
#	docs/src/content/docs/enterprise/security.md
- Move pre-install hooks after import check so hooks do not fire when
  the install pipeline is not available
- Add return type annotations to _build_hook_runner and _build_event
- Fix effective_command to prefer command over bash on Windows
- Use urlparse for URL validation, reject embedded credentials
- Add hooks_for_event() public API, stop reaching into _hooks
- Remove Rich markup in panel title that renders as italic
- Clarify synchronous execution in _execute_command docstring
- Add type annotations to _fire_update_hooks parameters
- Redact URL credentials before writing to hooks.log
- Add apm hooks commands to commands.md reference

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nt (#1529)

- Move hooks commands into dedicated 'Lifecycle hooks' section in commands.md
- Add tests for _redact_url_credentials (plain, user:pass, user-only)
- Add tests for hooks_for_event public API (matching, empty result)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

panel-review Trigger the apm-review-panel gh-aw workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Installation analytics

3 participants