pyfsr.exports

Read and validate FortiSOAR Export Wizard bundles – offline, no appliance needed.

A FortiSOAR .zip comes in two flavours that share one payload format:

  • a configuration exportinfo.json carries exported_from / exported_by and nothing that names the bundle. It installs through import_file().

  • a solution pack – the same payload plus an identity (name + version, which is what makes a bundle installable by name, upgradable and uninstallable) and publishing metadata (label, publisher, dependencies, iconLarge, postInstallConfig, …). It installs through install_from_file().

Everything under the single export_<uuid>/ root directory follows the same rules in both: one directory per content category, a data.json manifest in the categories that need installers, and payload JSON named after the record.

What this module checks is structural – that the manifest and the files agree, that every declared installer resolves, that category keys map to directories that exist. It deliberately does not try to decide whether a field is valid for some target, because that depends on the target’s schema; for that, run the export through dry_run(), which lets the appliance itself analyse the bundle.

Example

>>> from pyfsr.exports import Export
>>> arc = Export.open("myPack-1.0.0.zip")
>>> arc.kind
<ExportKind.SOLUTION_PACK: 'solutionpack'>
>>> [f.code for f in arc.problems() if f.is_error]
[]

Attributes

Exceptions

ExportError

The file could not be read as a FortiSOAR export bundle at all.

ExportValidationError

Export.validate() found error-severity problems.

Classes

ExportKind

Which of the two bundle flavours an export is.

Severity

str(object='') -> str

Finding

One problem found in an export.

Installer

An entry in a category data.json that carries or references a payload.

Export

An opened FortiSOAR export bundle.

Module Contents

exception pyfsr.exports.ExportError[source]

Bases: Exception

The file could not be read as a FortiSOAR export bundle at all.

exception pyfsr.exports.ExportValidationError(findings: list[Finding])[source]

Bases: Exception

Export.validate() found error-severity problems.

findings[source]
class pyfsr.exports.ExportKind[source]

Bases: str, enum.Enum

Which of the two bundle flavours an export is.

CONFIG_EXPORT = 'export'[source]
SOLUTION_PACK = 'solutionpack'[source]
class pyfsr.exports.Severity[source]

Bases: str, enum.Enum

str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.

ERROR = 'error'[source]
WARNING = 'warning'[source]
INFO = 'info'[source]
class pyfsr.exports.Finding[source]

One problem found in an export.

code is stable and machine-matchable; message is for humans.

severity: Severity[source]
code: str[source]
message: str[source]
path: str | None = None[source]
property is_error: bool[source]
pyfsr.exports.CATEGORY_DIRS: dict[str, str][source]
class pyfsr.exports.Installer[source]

An entry in a category data.json that carries or references a payload.

install_mode == "tgz" means the tarball travels inside the export at <category>/<installer_path>; "rpm" means the target fetches it from a repository, so nothing is bundled and nothing can be checked offline.

category: str[source]
name: str[source]
version: str | None[source]
install_mode: str | None[source]
installer_path: str | None[source]
member: str | None[source]
class pyfsr.exports.Export[source]

An opened FortiSOAR export bundle.

Use open(). The zip stays open for the object’s lifetime; use it as a context manager, or call close(), if you care about the handle.

path: pathlib.Path[source]
zf: zipfile.ZipFile[source]
root: str[source]
info: dict[str, Any][source]
members: list[str] = [][source]
classmethod open(path: str | pathlib.Path) Export[source]

Open path and parse its info.json.

Raises ExportError if the file is not a zip, has no info.json, or has more than one export root.

close() None[source]
property kind: ExportKind[source]

Solution pack (has an identity) or plain configuration export.

property name: str | None[source]
property version: str | None[source]
property label: str | None[source]
property min_compatibility: str | None[source]
property contents: dict[str, Any][source]

The raw contents map. Values are lists for most categories and dicts keyed by apiName for modules / viewTemplates / views.

entries(category: str) list[Any][source]

The entries of one contents category, list- or dict-shaped alike.

directories() set[str][source]

Top-level directories present under the export root.

read_json(member: str) Any[source]

Read one member (path relative to the export root) as JSON.

data_manifest(category: str) list[dict[str, Any]][source]

The <category>/data.json rows, or [] when there is no manifest.

installers() list[Installer][source]

Every installer entry declared by a category data.json.

playbook_members() collections.abc.Iterator[str][source]

Every playbook payload file (skips the collection metadata sidecars).

problems(*, target_version: str | None = None) list[Finding][source]

Every structural problem found, most severe first.

target_version enables the fsrMinCompatibility check against the appliance you intend to import into.

validate(*, target_version: str | None = None, strict: bool = False) list[Finding][source]

Like problems(), but raises on errors.

Returns the non-fatal findings so a caller can log them. With strict=True warnings are fatal too.

Raises:

ExportValidationError – if any error (or, when strict, warning) was found.

external_connectors() set[str][source]

Connectors the content calls that the export does not account for.

Not a defect – shipped Fortinet packs rely on the target already having these – but it is the install prerequisite list, and nothing else computes it. Platform builtins and Jinja-templated (dynamically dispatched) connector names are excluded.