Skip to content

Feature Request: Plugins Phase 2 #1378

Closed
@estroz

Description

@estroz

Overview

Phase 1 of the proposed scaffold plugin system is being implemented (#1290) at the time of writing. Progress on phase 2, which adds support for chaining plugins together (more below), will begin shortly afterwards, starting with a proposal doc. Before writing that doc, which will contain implementation details for chaining, I'd like to start a general discussion of how chaining will occur.

Phase 2 requirements, summarized from previous discussions
  1. mdbook-like structure
    1. A Plugin receives a JSON blob "universe" from upstream plugins containing file "nodes", each of which contain file metadata.
    2. The plugin can add or delete nodes (nodes are not mutable), then returns the modified universe so downstream plugins can do the same.
  2. A debug function dumps the universe with configurable verbosity and some intelligence as to which node failed (latter is a nice-to-have).
  3. Pre-generate a project file (for complex plugin config use cases).
  4. Enhanced "layout" PROJECT file key.
    1. Either and ordered list of plugin {name}/{version}, or ordered map of {name}/{version} to plugin-specific config.
    2. Order matters for certain plugins, ex. a bazel plugin would need to run after all Go files are generated.

Initial thoughts

Hypothetical CLI setup: a global --plugins flag that takes an ordered list of plugin names. The subcommand invoked with --plugins executes "downstream" plugins from that list that match those the kubebuilder binary knows about.

Given that we want to pass some initial universe between plugins, that universe should be initialized by the plugin invoked via CLI (the "base" plugin). For example, kubebuilder init --plugins addon will invoke an Init plugin, which passes its generated state to the addon plugin, modifying scaffolded files then writing them to disk.

Open questions

Assuming this flow is what we want, a few questions pop up:

  1. For the initial state: at what point do we pass a base plugin's state to downstream plugins?
    1. Approach 1: aggregate files being scaffolded and generated by the base plugin in a universe and pass that universe to a downstream plugin
      • Pro: fully encapsulates all plugin logic within Run().
      • Pro: all files have been scaffolded and post-scaffolding has been run by this point so we don't have to thread a bunch of plugin execution logic through scaffolds, just filepath.Walk the whole directory.
      • Con: requires reading the entire directory after scaffolding and post-scaffolding code runs, since external generators (controller-gen or go mod init) will have modified on-disk state.
    2. Approach 2: pass a universe subset to plugins on each scaffold event (each call to Scaffold.Execute())
      • Pro: plumbing already exists to do this; we'd need to change method signatures of existing plugins, some types etc. In this case, the universe is a model.Universe and a node is a model.File.
      • Pro: the scaffolder controls what is being written to disk, with all the features that the current plumbing provides.
      • Pro: writing a debug function may be easier than in approach 1, since we have more fine-grained control with per-scaffold event plugin execution.
      • Con: model.Universe will have to be modified to guarantee node immutability.
      • Con: A downstream plugin won't receive the full universe, just portions on each scaffold event, and will never "see" post-scaffolded files.
      • Con: each downstream plugin will be run on each scaffolding event, which may cause issues (in the naive implementation) if Run() is not idempotent. A non-naive implementation may require aggregation of scaffold events.
  2. How can we encourage, if not enforce, modification of a universe over just writing files to disk in Run()?
    • Perhaps we could modify Run() to return a list of files to add/delete from a universe rather than having Run() write such logic itself.
  3. Do we want PROJECT to be present in the universe passed between plugins?
    • Probably not, since we gate access via Read() vs. Load().

For 1., the first approach makes the most sense to me given its pros, but reading the full directory after Run() completes feels like a heavy cost to incur for one plugin execution.

More context

Related issues: #1249
Related PR's: #1250, #1290

/kind feature

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/featureCategorizes issue or PR as related to a new feature.lifecycle/frozenIndicates that an issue or PR should not be auto-closed due to staleness.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions