Skip to content

[deckhouse-cli] New plugins contract support - impl [2/2]#347

Open
Glitchy-Sheep wants to merge 12 commits into
mainfrom
feat/new-plugins-contract-stage2-implementation
Open

[deckhouse-cli] New plugins contract support - impl [2/2]#347
Glitchy-Sheep wants to merge 12 commits into
mainfrom
feat/new-plugins-contract-stage2-implementation

Conversation

@Glitchy-Sheep
Copy link
Copy Markdown
Contributor

@Glitchy-Sheep Glitchy-Sheep commented May 12, 2026

Summary

This PR implements support for the new plugin contract format:

  • New cluster-side requirements (Kubernetes / Deckhouse) with version constraints
  • Module dependencies have three flavours:
    • mandatory
    • conditional
    • anyOf
  • Friendlier error messages when a contract is malformed
  • Cluster-side checks themselves are log-only stubs - declared constraints are reported to the operator but not yet enforced against a live cluster

The refactor stage (PR [1/2]) is the base of this branch and must be merged first.


⭐ What the new contract format enables

  • Deckhouse version constraints - new top-level deckhouse field for declaring "this plugin needs Deckhouse >= 1.65".
  • Conditional dependencies:
    • mandatory for "always required"
    • conditional for "only enforced if the dependency is actually installed". Useful when a plugin wants to say "if module X is enabled, I need at least v2.40 of it" without forcing X to be present.
    • anyOf you need at least one of N modules (e.g. some ingress controller, doesn't matter which).

🐛 Alongside the format change, this PR also fixes one latent bug that existed in v1 and would have carried over:

  • Conflict detection - the reverse-conflict check compared installedPlugin.Version against itself, which was tautologically true, so real conflicts were silently accepted. Now compares against the new plugin's version.

📄 Contract shape: before vs after

image

▶️ Example Flow with new Contracts

example

🚶‍♂️‍➡️ Implementation walkthrough

  1. Structured Requirements DTO.

    • Typed Kubernetes, Deckhouse, ModuleRequirementsGroupDTO, PluginRequirementsGroupDTO replace the flat arrays.
    • ContractToDomain / DomainToContract factored into 6 small per-field helpers.
  2. Validator split + conflict-check fix.

    • mandatory is soft (resolvable with --resolve); conditional is a hard error only when the dependency is actually installed.
    • Conflict check now compares against the new plugin's version (was tautologically self-comparing), and walks both .Mandatory and .Conditional of every installed plugin.
  3. Cluster-side requirements as log-only notices.

    • validateKubernetesRequirement / validateDeckhouseRequirement / validateModuleRequirement parse the constraints and print a two-line stderr record:
    !  plugin declares a Deckhouse version requirement but enforcement is not implemented yet
          plugin: package
          constraint: >= 1.65
    

    Real cluster connectivity (kubeconfig + clientset + module discovery) is out of scope - lands in a follow-up PR.

  4. Friendlier error messages.

    • New unmarshalContract helper rewrites encoding/json errors as user-actionable messages, shared by the registry-annotation and local-file paths:

    Before:

    Error: failed to unmarshal contract: json: cannot unmarshal array into Go struct field PluginContract.requirements.modules of type service.ModuleRequirementsGroupDTO
    

    After:

    Error: invalid contract: field "requirements.modules" must be an object with mandatory/conditional sections, got a JSON array
    

Verification

  • go build ./... clean at every commit.
  • Unit tests cover the v2 contract round-trip, the validatePluginConflict regression suite (bug regression, happy path, conditional detection), and the invalid-contract error paths.

Follow-up

Real cluster-side enforcement (kubeconfig + clientset + module discovery against a live API server) is intentionally out of scope. The contract surface, validation skeleton, and operator-facing notices land in this PR; runtime enforcement lands later.

…to internal

This is for consistency with other commands and their layouts

Signed-off-by: Roman Berezkin <roman.berezkin@flant.com>
Some of them are likely copied from mirror command, but in plugins there is no use of them.

Signed-off-by: Roman Berezkin <roman.berezkin@flant.com>
…mand and plugins in general

Signed-off-by: Roman Berezkin <roman.berezkin@flant.com>
…th builder functions)

Signed-off-by: Roman Berezkin <roman.berezkin@flant.com>
- `runInstalledPlugin` returns errors, so the run path is testable and the command body just logs and exits on failure.
- `ensureInstallRoot` centralizes the homedir fallback used by both `NewCommand` and `NewPluginCommand`.
- `cachedDescription` wraps the contract cache lookup and returns "" when the file is missing or unreadable.
- All path joins flow through the `layout` package (`PluginsRoot`, `CurrentLinkPath`, `ContractFile`).

Signed-off-by: Roman Berezkin <roman.berezkin@flant.com>
- Add v2 schema types: deckhouse, plugins.{mandatory,conditional},
  modules.{mandatory,conditional,anyOf}
- Smart UnmarshalJSON on group types: v1 flat arrays land in .Mandatory of v2
- Marshal always emits v2 (cache and `plugins contract` migrate over time)
- Factor ContractToDomain/DomainToContract into 6 helpers
- Validators: minimal compile-only updates (range over .Mandatory)
- 14 unit tests for smart unmarshal + round-trip

No behaviour change for v1 contracts. New fields populated but
not yet enforced.

Signed-off-by: Roman Berezkin <roman.berezkin@flant.com>
… conflict-check bug

- Fix bug in validatePluginConflict: compare against plugin.Version (the NEW plugin) instead of installedPlugin.Version (which made the reverse conflict check a tautology)
- Iterate both .Mandatory and .Conditional sections of an installed plugin's requirements during conflict check - Split validatePluginRequirement into Mandatory (soft, --resolve-able) and Conditional (hard error if installed + version fails) variants
- Orchestrator validateRequirements calls both, in order
- 3 unit tests on pure validatePluginConflict covering bug regression, happy path, and .Conditional section detection

- nit: Remove obvious comments

Signed-off-by: Roman Berezkin <roman.berezkin@flant.com>
- Add validateKubernetesRequirement and validateDeckhouseRequirement:
  Warn-log when the constraint is declared, return nil (no enforcement)
- Replace empty validateModuleRequirement stub with per-section logging
  for .Mandatory / .Conditional / .AnyOf groups
- Wire all three into validateRequirements after plugin validators
- d8 now understands the v2 contract end-to-end and informs operators
  about declared-but-not-enforced cluster-side requirements

Real cluster connectivity (kubeconfig + clientset + module discovery)
deferred to a follow-up.

Signed-off-by: Roman Berezkin <roman.berezkin@flant.com>
…ince we haven't released any support)

- Remove UnmarshalJSON from PluginRequirementsGroupDTO and
  ModuleRequirementsGroupDTO; standard reflection unmarshal applies
- Drop encoding/json and fmt imports from dto.go (no longer needed)
- Slim dto_test.go to 2 cases: v2 positive end-to-end + v1 rejection
- Update fixture in TestGetPluginContract_Success to v2 form

Nothing released yet, so accepting both shapes is unnecessary tech
debt. Any old v1 flat-array contract now surfaces a clear unmarshal
error pinpointing the offending field.

Signed-off-by: Roman Berezkin <roman.berezkin@flant.com>
Signed-off-by: Roman Berezkin <roman.berezkin@flant.com>
…structured log

Signed-off-by: Roman Berezkin <roman.berezkin@flant.com>
@Glitchy-Sheep Glitchy-Sheep self-assigned this May 12, 2026
@Glitchy-Sheep Glitchy-Sheep added the enhancement New feature or request label May 12, 2026
@Glitchy-Sheep Glitchy-Sheep marked this pull request as ready for review May 12, 2026 16:17
@Glitchy-Sheep Glitchy-Sheep requested a review from ldmonster as a code owner May 12, 2026 16:17
Base automatically changed from feat/new-plugins-contract-stage1-refactor to main May 13, 2026 14:02
…-plugins-contract-stage2-implementation

Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants