Skip to content

Latest commit

 

History

History
171 lines (113 loc) · 12.7 KB

File metadata and controls

171 lines (113 loc) · 12.7 KB

PLCC Data Validation Rules

This document describes the rules that plcc2fbc uses to validate data from the Red Hat Product Life Cycle Checker (PLCC) API before generating FBC (File-Based Catalog) lifecycle blobs.


Overview

After PLCC data is fetched, each product passes through four stages:

  1. PLCC filtering (pkg/plcc/plcc.go): basic structural filtering before any validation runs. Drops products with no package name (FilterPackages). When -p is specified, keeps only the requested packages (FilterByPackageNames) and returns an error for any names not found.
  2. PLCC-level validation (pkg/plcc/validation.go): validators that check raw PLCC data quality (tier, release cadence, phase completeness, dates, duplicates). Organized into three groups: syntax, semantic, and catalog. By default these filter out failing packages; with --permissive they produce warnings only. Use --validators to select which validators to run (by label or group: all, syntax, semantic, catalog).
  3. FBC Converter Pipeline (pkg/fbc/conversion.go): type-checked field translation from plcc.Version to fbc.Version. Each converter validates one field and populates the corresponding output. Organized in converterRegistry. Always run — cannot be disabled.
  4. FBC Filter Pipeline (pkg/fbc/filter.go): an ordered sequence of Filter callbacks that clean the translated FBC output (e.g., drop incomplete phases). Organized in filterRegistry.

Summary of filtering behavior

Condition Stage Effect
Product has no package name PLCC filtering Silently skipped
Requested -p package not found PLCC filtering Error (exit 3); with --permissive warning only
Package maps to multiple products PLCC-level validation All copies removed; with --permissive warning only
Invalid version name, timestamp, or OCP format FBC converter pipeline Entire package rejected
Phase with nil start or end date FBC filter pipeline Phase silently removed

PLCC-Level Validation

Before the FBC pipelines run, main.go calls PLCC-level validators on each raw plcc.Product. These validators live in pkg/plcc/validation.go alongside the data types they check. By default, failing packages are filtered out and logged as structured JSON to stderr. Use --permissive to keep failing packages in the output (warnings only). Use --validators to select which validators to run (by label or group), and --list-validators to see available options.

Validators are split into three groups: SyntaxValidators() (data format/structure), SemanticValidators() (business/lifecycle rules), and catalog (cross-product checks). DefaultValidators() composes syntax + semantic; DefaultCatalogValidators() returns catalog-level checks. All three groups are included in --validators all (the default). Catalog-level checks run via catalog.Validate().

Pre-tier-model skip

The three-tier lifecycle model (Platform Aligned / Platform Agnostic / Rolling Stream) was introduced with OCP 4.14, which GA'd on 2023-10-31 (TierModelCutoffDate). Versions whose earliest parseable phase start date predates this cutoff — or that have no phases with parseable dates — are considered pre-tier-model and are exempt from tier-specific validators. This prevents false positives on legacy versions that were published before the tier model existed.

Tier-specific validators that apply the skip: ValidateTierSelected (REQ-TIER-ALL-02), ValidatePlatformAlignedPhases (REQ-TIER-PA-01), ValidatePlatformAlignedOCP (REQ-TIER-PA-02), ValidatePlatformAgnosticPhases (REQ-TIER-AG-01), ValidatePlatformAgnosticEUSPhases (REQ-TIER-AG-03), ValidatePlatformAgnosticEUSOCP (REQ-TIER-AG-04), ValidateRollingStreamPhases (REQ-TIER-RS-01), ValidateRollingStreamForbiddenPhases (REQ-TIER-RS-02), ValidateOCPFormat (REQ-FIELD-02), ValidateOCPFormatAll (CUSTOM-04).

Universal invariants that always apply regardless of version age: ValidateDatesStatic (REQ-DATE-02), ValidateDatesClean (REQ-DATE-03), ValidateDatesContiguity (REQ-DATE-04), ValidateVersionNames (REQ-VER-01), ValidatePhaseEndAfterStart (CUSTOM-03), ValidateReleaseCadence (REQ-TIER-ALL-01), ValidateIsOperator (CUSTOM-01), ValidateHasVersions (CUSTOM-02).

Syntax Validators

# Function Label Skip Purpose
1 ValidateIsOperator CUSTOM-01 Product must have package name and be flagged as operator
2 ValidateHasVersions CUSTOM-02 Product must have ≥1 version
3 ValidateDatesStatic REQ-DATE-02 Dates must be static values (checks _format field)
4 ValidateDatesClean REQ-DATE-03 Non-empty, non-N/A dates must cleanly parse
5 ValidateDatesContiguity REQ-DATE-04 Consecutive phases must start 1 day after previous ends
6 ValidatePhaseEndAfterStart CUSTOM-03 Phase end date must be after start date
7 ValidateVersionNames REQ-VER-01 Version names must match MAJOR.MINOR
8 ValidateOCPFormat REQ-FIELD-02 pre-tier OCP compatibility on aligned versions must match MAJOR.MINOR
9 ValidateOCPFormatAll CUSTOM-04 pre-tier OCP compatibility format on non-aligned versions

Semantic Validators

# Function Label Skip Purpose
1 ValidateReleaseCadence REQ-TIER-ALL-01 Operators must have release cadence specified
2 ValidateTierSelected REQ-TIER-ALL-02 pre-tier Operator versions must have lifecycle tier selected
3 ValidatePlatformAlignedPhases REQ-TIER-PA-01 pre-tier Aligned: required phases with parseable dates (OCP cross-referenced)
4 ValidatePlatformAlignedOCP REQ-TIER-PA-02 pre-tier Aligned: OCP compatibility must be specified
5 ValidatePlatformAgnosticPhases REQ-TIER-AG-01 pre-tier Agnostic: Full Support and Maintenance with parseable dates
6 ValidatePlatformAgnosticEUSPhases REQ-TIER-AG-03 pre-tier EUS-aligned agnostic: all 3 EUS terms with parseable dates, or none
7 ValidatePlatformAgnosticEUSOCP REQ-TIER-AG-04 pre-tier EUS-aligned agnostic: OCP compatibility must be specified
8 ValidateRollingStreamPhases REQ-TIER-RS-01 pre-tier Rolling: Full Support with parseable dates
9 ValidateRollingStreamForbiddenPhases REQ-TIER-RS-02 pre-tier Rolling: must not include Maintenance or EUS phases

OCP Cross-Reference (REQ-TIER-PA-01)

ValidatePlatformAlignedPhases checks that platform-aligned operator versions have the required lifecycle phases with parseable dates. Rather than hardcoding all five phases (Full Support, Maintenance, EUS Terms 1/2/3), the validator cross-references the OCP (OpenShift Container Platform) product from the PLCC catalog to determine which phases are actually required.

Why: As of this writing, OCP only has EUS phases on even-numbered minor versions (4.14, 4.16, 4.18, ...). Odd-numbered versions (4.15, 4.17, ...) have EUS phase slots in PLCC with "N/A" dates. This pattern could change in future OCP releases. Rather than hardcoding even/odd logic, the validator reads the OCP product's actual PLCC data to determine which phases have real dates — so it will automatically adapt if the EUS policy changes. Without this cross-referencing, operators on odd OCP versions always fail because they lack EUS phases that OCP itself doesn't have.

Resolution logic (resolveRequiredPhases):

  1. Always required: Full Support and Maintenance.
  2. Conditionally required: Each EUS phase (Terms 1, 2, 3) is required only if ANY OCP version referenced in the operator version's openshift_compatibility field has that phase with parseable (non-N/A, non-empty) start and end dates.
  3. Fallback (conservative): If OCP product data is unavailable, the OCP version is not found, or the operator has no openshift_compatibility value, all five phases are required. This prevents false negatives.

How OCP context is injected:

  • LookupValidators(deps, ...) calls initPlatformAlignedPhases(deps) for the REQ-TIER-PA-01 entry, which creates an OCP-aware closure via ValidatePlatformAlignedPhases(ocpProduct).
  • The OCP product is extracted from the full PLCC catalog before package-name filtering (since OCP itself is not an operator package and would be filtered out).

Example:

An operator version with openshift_compatibility: "4.17":

  • OCP 4.17 has Full Support and Maintenance with dates, but EUS Terms 1/2/3 have "N/A" dates.
  • Only Full Support and Maintenance are required for this operator version.

An operator version with openshift_compatibility: "4.16, 4.18":

  • OCP 4.16 has all five phases with dates → all five phases required (union semantics: if ANY referenced OCP version has EUS, the operator must too).

Catalog-Level Validators (group: catalog)

Catalog validators are cross-product checks selectable via --validators catalog or by label (e.g. --validators REQ-VAL-01). They are included in --validators all (the default). By default, all products with a duplicated package name are removed (all copies, since we cannot determine which is authoritative). With --permissive, duplicates produce warnings only.

# Function Label Skip Purpose
1 ValidateNoDuplicates REQ-VAL-01 No package name appears in multiple products

FBC Converter Pipeline

The converter pipeline is defined by converterRegistry in pkg/fbc/conversion.go. Each converter has the signature:

type Converter func(src plcc.Version, dst *Version) []error

Converters validate and translate one field of a PLCC version into the corresponding FBC version field. They always run during translation — they cannot be disabled. If any converter returns errors, the entire package is rejected.

# Function Label Purpose
1 ConvertVersionName FBC-VER-01 Parse version name as MAJOR.MINOR
2 ConvertPhases FBC-PHASE-01 Translate phase timestamps to FBC dates
3 ConvertOCPCompatibility FBC-OCP-01 Parse OCP compatibility versions as MAJOR.MINOR

DefaultConverters() returns all converters from the registry in order. translateVersion() iterates them to build each fbc.Version.


FBC Filter Pipeline

The pipeline is defined by filterRegistry in pkg/fbc/filter.go. Each filter has the signature:

type Filter func(*Package) []string

Filters mutate packages and enforce structural invariants on translated FBC output. Mutation filters clean up data (e.g., drop incomplete phases); invariant validators reject packages that violate FBC schema requirements. Returning a non-empty []string rejects the package; returning nil means the package passes.

Filters run in order, and the pipeline short-circuits: the first filter that returns reasons stops execution.

The default pipeline:

# Function Label Kind Purpose
1 FilterIncompletePhases FBC-MUTATE-01 mutate Drop phases where either date is nil
2 ValidatePackageHasVersions FBC-VAL-01 invariant Package must have ≥1 version
3 ValidateVersionsHavePhases FBC-VAL-02 invariant Every version must have ≥1 phase
4 ValidatePhaseDates FBC-VAL-03 invariant Every phase must have non-nil start and end dates
5 ValidateDateOrdering FBC-VAL-04 invariant Phase start date must not be after end date
6 ValidatePhaseContiguity FBC-VAL-05 invariant Consecutive phases must be contiguous (end + 1 day = next start)

FilterIncompletePhases

Removes phases where either startDate or endDate is nil. This includes N/A phases (both nil) and point-in-time phases (one set, one nil).

This filter always returns nil — it mutates the package but never rejects it.


Adding a New FBC Filter

Filters live in pkg/fbc/filter.go and handle output cleanup (mutations) and FBC structural invariants (validation). PLCC-level data quality checks belong in the PLCC validators (pkg/plcc/validation.go).

  1. Write a new function with signature func(p *Package) []string.

    • Mutate the package p as needed (e.g., drop or rewrite data).
    • Return nil to accept or a list of reason strings to reject.
  2. Add an entry to filterRegistry with a label (e.g. FBC-MUTATE-02 for mutations, FBC-VAL-06 for invariants) and group. Embed the label in all reason strings.

  3. Add a test in pkg/fbc/filter_test.go.

Note: DefaultFilters() reads from filterRegistry and returns a fresh slice each time, so callers can safely append or reorder filters for custom pipelines without affecting the default.