Skip to content

Create a more robust module description file #5493

@TheSyscall

Description

@TheSyscall

Is your feature request related to a problem? Please describe.

module.info describes the basics of a module.

It is currently possible to require a specific library or other module, but this mechanism seems prone to errors and lacks some features.

There is no mechanism to express:

  • Which PHP version a module needs
  • Which PHP extensions need to be active
  • Which version of icingaweb a module is compatible with
  • State that this module is incompatible with another module
  • Add "optional dependencies" or "suggestions" for other modules
  • Define "replacements" when when a package can stand in for a library. See "icinga-php-library" vs "ipl-*"
  • There is no way for a module to communicate that it is abandoned/deprecated or that it has been superseded by another module. See: "monitoring"

As a result, modules are just assumed to be ok to be loaded and are can be activated even when their requirements are not met, leading to runtime errors or undefined behavior that break icingaweb to the point of complete breakdown.

Describe the solution you'd like

A metadata file per module that declares its requirements just like the existing module.info file, but in a more extensible format (JSON/YAML).
This file is read when the state of modules change and the resulting dependency tree is resolved.
Modules that do not satisfy their requirements are not activated.
Activation failures cascade: if module A depends on module B and B fails to activate, A also fails.

Why YAML

YAML is the primary format for the metadata file for the following reasons:

  • Comments: module authors can document why a dependency exists or why a specific version range is required, directly in the file.
  • Readability: the format is compact and human-friendly without the punctuation overhead of JSON (no closing braces, minimal quoting).
  • Native array support: the OR constraint syntax relies on YAML sequences, which map naturally to the format without any additional syntax invention.
  • Familiarity: YAML is already widely used as a configuration format.

JSON is also accepted as an alternative for tooling that generates metadata programmatically, since it is easier to produce without a dedicated serializer.
But lacks support for comments, has higher overhead and is slightly harder to read by humans.

INI was considered but rejected due to limited nesting support and no native array type, both of which are required by this format.

Metadata fields

Top-level:

  • name: module identifier
  • version: module version
  • description: human-readable description
  • abandoned: false (default), true, or a string pointing to a replacement

depends: hard requirements. If any are unmet the module does not activate.

Supported keys:

  • php: required PHP version
  • icingaweb: required Icinga Web version
  • ext-*: required PHP extensions
  • libraries: map of library names to version constraints
  • modules: map of module names to version constraints

suggests: optional enhancements. Does not affect activation, but is surfaced in the Icinga Web modules configuration when the suggested module is not active. Each entry under modules is either a reason string or an object with an optional version constraint and a reason string.

conflicts: modules or libraries that cannot be active at the same time as this one. Accepts libraries and modules sub-sections.

replaces: declares that this module provides specific libraries or supersedes specific modules at given versions, satisfying any dependencies on those targets. Accepts libraries and modules sub-sections. Implicitly conflicts with all replaced targets.

Version constraint operators

Operator Meaning
* Any version
=, == Exact match
>= Greater than or equal
<= Less than or equal
> Strictly greater than
< Strictly less than
^ SemVer-compatible range (^ 1.2.3 = >= 1.2.3, < 2.0.0)
~ SemVer-compatible minor range (~ 2.13.2 = >= 2.13.2, < 2.14.0)

^ has special handling for versions starting with "0". In this case ^0.1.2 = >= 0.1.2, < 0.2.0.

PHP's built-in version_compare function is suitable for evaluating most of these operators.

Combining constraints

Multiple constraints can be combined using AND and OR logic.

AND: comma-separated within a single string.
All constraints must be satisfied:

ipl-web: ">= 1.0.0, < 2.0.0"

OR: an array of constraint strings.
At least one item must be satisfied:

ipl-web:
  - ">= 1.0.0, < 2.0.0"
  - ">= 3.0.0"

Each array item is itself an AND group, making the full structure Disjunctive Normal Form.
A plain string is treated as a single-element OR group internally.

Examples

name: my-module
version: 1.3.0
description: Provides monitoring dashboards for infrastructure metrics

depends:
  php: ">= 8.1"
  icingaweb: ">= 2.4.0"
  ext-json: "*"
  ext-curl: ">= 7.0"
  libraries:
    icinga-php-library:
      - ">= 1.0.5, < 1.2.5"
      - ">= 2.1.0"
  modules:
    some-other-module: ">= 1.0.0"

suggests:
  modules:
    optional-module: Extends details page with interesting information
    another-module:
      version: ">= 1.5.0"
      reason: Enables PDF export for dashboard reports

conflicts:
  modules:
    legacy-module: "*"

Module acting as a drop-in replacement for another:

name: feeds
version: 1.2.0

replaces:
  modules:
    rss: "*"

An abandoned module pointing to its migration guide.

name: legacy-module
version: 1.2.3
abandoned: "https://icinga.com/docs/icinga-db-web/latest/doc/10-Migration/"

Activation algorithm

  1. List all enabled modules
  2. For each module: check its own requirements (php, icingaweb, ext-*, libraries, conflicts) — mark as failed if unmet
  3. Propagate failures: any module whose depends target has failed also fails
  4. Repeat step 3 until no new failures occur
  5. Activate all modules not marked as failed

Describe alternatives you've considered

Flat requirement lists without propagation: a simple pass/fail check per module without graph traversal would miss the case where module A is technically satisfied but depends on module B which is itself broken. Both should fail together.

Using OR inside the version string: considered a dedicated || operator inside the constraint string (as npm does). Rejected in favor of using a YAML/JSON array, which is more readable and quicker to parse.

A flat dependency map without sub-sections: considered using a single flat map for all dependency types under depends, relying on naming conventions or a registry lookup to distinguish libraries from modules. Rejected because Icinga Web cannot determine the type of a missing dependency from the registry alone, which would prevent meaningful error messages from being shown to the user. Explicit libraries and modules sub-sections make the type unambiguous regardless of whether the target is installed.

Additional context

  • replaces implicitly creates a conflicts relationship: a bundle and the individual packages it replaces cannot be active simultaneously.
  • suggests without a version uses *: The reason field is surfaced in the UI to explain what capability the suggested module unlocks.
  • The abandoned field is surfaced in the UI: true shows a generic warning, a string value links to or names the recommended replacement.
  • name could be a name spaced identifier so modules from different vendors don't collide by accident.

Open issues

  • This proposal doesn't handle the case where there are multiple possible options for the same dependency. Example: businessprocess requires one of icingadb or monitoring.
    It is possible to define both as suggests but the module really does require at least one of them to be useful at all.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions