Skip to content

Conversation

@evgeni
Copy link
Member

@evgeni evgeni commented Jun 16, 2025

This PR introduces two new concepts: flavors and features.

feature

A feature is an abstract representation of "the deployed system can now do X", usually implemented by enabling a Foreman/Pulp/Hammer plugin (or a collection of these).

flavor

A flavor is a set of features that are enabled by default and can not be disabled. This is to allow our common deployment types like "vanilla foreman", "katello", "satellite" and similar.

workflow

The idea is to allow people to select a baseline using --flavor and then further adjust it to their liking using --feature and the code then figures out what foreman/hammer/pulp plugin (or combination of these) needs to be enabled for that.

Copy link
Member

@ekohl ekohl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking out loud: we also have Pulp plugins and not all are enabled by default (https://github.com/theforeman/puppet-foreman_proxy_content/blob/11cadd50b6cf19baa2edaea44bd6f85e87282fce/manifests/init.pp#L82-L88). Will this plugin naming start to bite us? Or are we going to build different pulp images for that?

Slightly related to theforeman/pulp-oci-images#2 (comment).

@evgeni
Copy link
Member Author

evgeni commented Jun 16, 2025

That's a great question! So far, I've not thought about that, and honestly would prefer to build as few containers as possible.

That said, maybe we should stop thinking in plugins and start thinking in features? So users can say "I want content-deb" or "compute-azure" and the rest is handled by the code.

@ekohl
Copy link
Member

ekohl commented Jun 16, 2025

I like the idea of features. Especially if we can extend it to the proxy too. Fewer implementation details is better for users.

For Pulp it's mostly the content types. Downsides are larger images (because of code & deps), more code to load (longer startup times, more memory), more DB migrations. Perhaps Pulp understands or can be taught a similar trick to Foreman where we ship the code but don't load it. Certainly simplifies the building process. Image selection is a painful and crude way.

@evgeni
Copy link
Member Author

evgeni commented Jun 16, 2025

For pulp it could also be storage backends (if we end up having more than "file")

@ekohl
Copy link
Member

ekohl commented Jun 16, 2025

Oh yes, good point

@evgeni
Copy link
Member Author

evgeni commented Jun 18, 2025

pulp natively supports selecting plugins via ENABLED_PLUGINS, so we can have a large image and then pick and choose

@evgeni evgeni force-pushed the plugins branch 2 times, most recently from 18a2985 to ecbf19c Compare June 26, 2025 12:25
- "../../vars/{{ certificate_source }}_certificates.yml"
vars:
hammer_ca_certificate: "{{ server_ca_certificate }}"
hammer_plugins: "{{ plugins | map('replace', 'foreman-tasks', 'foreman_tasks') }}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything is lower case, except foreman-tasks. Perhaps we should from a foremanctl perspective just consider it lower case and then special case it where needed?

Perhaps even already fix https://github.com/theforeman/foreman-oci-images/blob/220306c20253c7bbb3ae223fbdf5656ab7887c65/images/foreman/Containerfile#L17-L19 before we merge this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean snake case, I guess?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW: I've not forgot this, but plan to address it in the metadata we intend to introduce, making the feature tasks, and hiding the names of the gems somewhere.

@evgeni evgeni force-pushed the plugins branch 2 times, most recently from 1dfe2f8 to 2f3eead Compare July 14, 2025 12:11
@evgeni evgeni force-pushed the plugins branch 4 times, most recently from 09ad3d8 to a05aea3 Compare August 13, 2025 07:30
@evgeni evgeni changed the title make plugins configurable make features configurable Aug 13, 2025
Comment on lines 40 to 17
features:
parameter: --feature
help: Features to enable in this deployment.
action: append
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do define a default feature set below:

features:
  - foreman-tasks
  - foreman_remote_execution
  - katello
  - content/container
  - content/rpm

This has the problem that once you pass --feature X that default set is overridden completely. So if you want to change the default, you need to know the default to alter it correctly.

We could introduce --additional-feature or something, but that also feels weird, and still doesn't allow to remove things from the default set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can I see the list of features as a user? And will this verify that whatever I pass in is a valid feature to enable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now not at all, and it's not validated (but also doesn't hurt in the "additional wrong plugin" case as if a plugin is not installed it also won't be enabled -- obviously hurts if you're missing a plugin)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our current installer we had a similar problem and never solved it well.

I wonder if --feature +foreman-tasks is a reasonable syntax to add a value to the current list. It may end up weird if you repeat it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about having --additional-feature <X>, which would be IMHO better readable, but behave the same as your suggestion, but then was stuck with "but what's the baseline for additional?"

Like, if the baseline is "Foreman", then --feature already does that, done. If the baseline is Katello, then we also need --remove-feature for people who don't want Katello. And boom we're back to having scenarios again.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ability to remove a feature seems like it should be dependent upon whether said feature supports that or not. I would be in favor of maintaining that list more explicitly:

  • features that can be enabled
  • features that can be disabled

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It touches on how we ship Foreman. If we default to Katello then the UX to install plain Foreman becomes foremanctl deploy --remove-feature katello. But you can only do that on the very first run before you installed anything.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems easy -- do not default to Katello :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't mind that, but if we then extend it to our documentation: do we completely drop the distinction between running Foreman on EL and with Katello? I wouldn't mind that (quite the opposite), but it has a lot of implications. It's basically finally a huge step towards making Katello more like a regular plugin.

@evgeni
Copy link
Member Author

evgeni commented Aug 13, 2025

@archanaserver @ehelms @ekohl -- I think this is now in a state that works as a PoC and we can start talking about how we want to expose it to the users.

Do we have any user stories defined around feature management already?

@evgeni evgeni marked this pull request as ready for review August 13, 2025 09:24
@ekohl
Copy link
Member

ekohl commented Aug 13, 2025

Not yet, but I thought about it this way in the past.

We deliver 3 containers, matching the Foreman repos:

  • vanilla: plain Foreman, no plugins. Building this without the plugins repo should be possible.
  • plugins: Foreman with plugins. Effectively enable core & plugins repo, then install foreman-plugin-*. I think none of the plugins should be enabled by default.
  • Katello: Foreman with Katello. Now all repos are enabled and all plugins are installed. Only Katello and its dependencies are enabled by default

I don't know if this is feasible.

@ekohl
Copy link
Member

ekohl commented Aug 13, 2025

On my phone so linking us very hard, but in https://github.com/theforeman/foreman/blob/develop/config/application.rb we have Bundler.require code. We can probably build in env var support for production rather than write complex things in the container building.

@evgeni
Copy link
Member Author

evgeni commented Aug 13, 2025

building multiple plugins (and layering them) should be absolutely feasible, but seems more like an implementation detail? like, you can totally run a core-only install with the katello container (and all plugins disabled) if you configure it properly, but the way how you expose this configuration is what's interesting here?

@ekohl
Copy link
Member

ekohl commented Aug 13, 2025

It certainly is.

My idea was that every container has defaults, but can be modified. Previously we talked about writing our own code, but perhaps we can reuse BUNDLE_WITH and BUNDLE_WITHOUT for this. In Bundler you can mark a group as optional which I think matches the defaults with enable/disable pattern. Some experimentation here is needed.

My first aim was to figure out those defaults.

One thing I don't know how to solve properly is disabling previously enabled plugins.

Question is: what should be done if the user supplied something like ENABLED_PLUGINS and removes one. If it starts up and is missing a plugin, should it error out? Could it still load the plugin or is that too late in the loading cycle?

First, we would need to detect it at all. I wonder if DB migrations should write a list of enabled plugins.

I think puma has a phase you can hook into during loading which might be usable.

@evgeni
Copy link
Member Author

evgeni commented Aug 14, 2025

I am not sure BUNDLE_* would trivially translate, given we don't use Bundler in prod, but I think we could enhance the Plugin registry in core to "reject" plugins if a list is explicitly configured?

And yes, removing plugins is hard. If we can detect "bad" removals (those that leave db entries dangling), I'd be in favor of refusing to start without the plugin.

@ekohl
Copy link
Member

ekohl commented Aug 14, 2025

We can probably move to using plain bundler in prod as well. These days bundler_ext doesn't really serve a purpose anymore: we actually want to enforce the versions specified and enforce it at the RPM level.

For removal I was thinking about a common remover plugin that you can enhance for each plugin. Not strictly needed at first, but long term it can be useful. The compute resource part is easy to write in a common way.

@evgeni
Copy link
Member Author

evgeni commented Aug 14, 2025

That'd still require plugins to change their bundler.d file to set a group, right?

Poking PluginsRegistry has the benefit that it's a single place to change

@ehelms
Copy link
Member

ehelms commented Aug 14, 2025

Does using a source-container help with any of this design? I know we had previously tabled that but I am leaning more and more towards the value of the source container for rapid feedback, and my own view of the barriers to that being removed.

@evgeni evgeni changed the title make features and flavor configurable make features and flavors configurable Nov 20, 2025
Comment on lines +30 to +31
- A feature is an abstract representation of "the deployed system can now do X", usually implemented by enabling a Foreman/Pulp/Hammer plugin (or a collection of these).
- A flavor is a set of features that are enabled by default and can not be disabled. This is to allow common deployment types like "vanilla foreman", "katello", "satellite" and similar.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A flavor is something you deploy first and then add features on top of it? If so, I suggest you switch the order in which you list them (here and elsewhere, like in the PR's description or on line 28).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yepp.

I mainly listed them here in that order as flavor refers to feature, but you (well, my brain, really) can't talk about features without defining them first.

@ehelms
Copy link
Member

ehelms commented Nov 24, 2025

If we go with flavor can we call the things we enable toppings ? 🍨

I think I always envisioned as there are clearly two distinct entities: a foreman server or a host for services (think Capsule). And therefore we didn't need flavors, we just just opt for two commands focused on these two baseline entities with features that you can add to each.

I think the current approach is fine based on how things work today (e.g. flavors / features)

hosts:
- quadlet
become: true
vars_files:
Copy link
Contributor

@stejskalleos stejskalleos Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

./foremanctl deploy --help
  • [--flavor FLAVOR] Is there a way to list available flavours?
  • [--reset-flavor] How is this going to work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#337 and #336

help: Additional features to enable in this deployment.
action: append_unique
remove_features:
parameter: --remove-feature
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this going to work? For example I'll run:

./foremanctl deploy --add-feature foreman-proxy
./foremanctl deploy --remove-feature foreman-proxy

How does the remove- task know what it has to remove, disable, and update in the DB?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/theforeman/foremanctl/pull/188/files#diff-2fc200de05abc2d83ea118cae7634c213de69b3e8640146a18644935bfce14f2

Today it will just make that feature "unmanaged", like in the old installer.
We can add "cleanup" as a follow up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add "cleanup" as a follow up.

Can you please create an issue or Jira ticket so we can keep track of it? thx

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parameter: --add-feature
help: Additional features to enable in this deployment.
action: append_unique
remove_features:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a task for listing available features?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also pointed that out in #188 (comment). @evgeni have talked offline and IIRC he wanted to tackle that in a follow up which I'm good with.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, LGTM, @evgeni, Can we create an issue for it so we can track it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ekohl
Copy link
Member

ekohl commented Nov 27, 2025

@evgeni there's a merge conflict. Also wondering if these are the final commits that you intend to get merged. I'd love to see this merged sooner rather than later because other work builds on it.

@evgeni
Copy link
Member Author

evgeni commented Nov 29, 2025

rebased and squashed.
opened #336 to track the removal part we discussed

@ekohl
Copy link
Member

ekohl commented Dec 1, 2025

@evgeni mind rebasing again to resolve merge conflicts? I'd like to merge this so we can build on it.

Copy link
Member

@ekohl ekohl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless there are any more objections I'd like to merge this tomorrow. We can always refactor, but this is the foundation on which we're going to build various features.

@ekohl ekohl mentioned this pull request Dec 1, 2025
Copy link

@Gauravtalreja1 Gauravtalreja1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK, with a few non-blocking comments

- 'foreman-client-key,type=mount,target=/etc/foreman/client_key.pem'
env:
FOREMAN_PUMA_WORKERS: "{{ foreman_puma_workers }}"
FOREMAN_ENABLED_PLUGINS: "{{ foreman_plugins | join(' ') }}"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, how this env var for foreman_plugins will be used on the containers ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugins inside the container have the following snippet in their respective bundler files:

gem '$PLUGIN' if ENV.fetch('FOREMAN_ENABLED_PLUGINS', '').split.include?('$PLUGIN') || ENV.fetch('FOREMAN_ENABLED_PLUGINS', nil).nil?

Which essentially means:
Execute gem '$PLUGIN' (thus loading the plugin) if one of the following is true:

  • FOREMAN_ENABLED_PLUGINS environment variable contains the plugin name
  • FOREMAN_ENABLED_PLUGINS is unset

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks

env:
DYNFLOW_REDIS_URL: "redis://localhost:6379/6"
REDIS_PROVIDER: "DYNFLOW_REDIS_URL"
FOREMAN_ENABLED_PLUGINS: "{{ foreman_plugins | join(' ') }}"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, is this required on Dynflow containers as well? do we enable any plugins on those containers too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need the same code loaded in the dynflow containers, as otherwise plugin-specific tasks wouldn't work correctly

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks

foreman_client_certificate: "{{ client_certificate }}"
foreman_oauth_consumer_key: abcdefghijklmnopqrstuvwxyz123456
foreman_oauth_consumer_secret: abcdefghijklmnopqrstuvwxyz123456
foreman_plugins: "{{ enabled_features | reject('contains', 'content/') | reject('eq', 'hammer') | reject('eq', 'foreman-proxy') | reject('eq', 'foreman') }}"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can use difference filter here instead of 3 reject filters?

Comment on lines +5 to +6
features:
parameter: --add-feature

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also consider adding a --list-feature option to display visible features, but that can be done outside this PR, wdyt?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#337 is tracking this, yes

This PR introduces two new concepts: `flavor`s and `feature`s.

A feature is an abstract representation of "the deployed system can now do X", usually implemented by enabling a Foreman/Pulp/Hammer plugin (or a collection of these).

A flavor is a set of features that are enabled by default and can not be disabled. This is to allow our common deployment types like "vanilla foreman", "katello", "satellite" and similar.

The idea is to allow people to select a baseline using `--flavor` and then further adjust it to their liking using `--feature` and the code then figures out what foreman/hammer/pulp plugin (or combination of these) needs to be enabled for that.
@evgeni
Copy link
Member Author

evgeni commented Dec 2, 2025

Thanks everyone!

@evgeni evgeni merged commit 3d19362 into master Dec 2, 2025
9 checks passed
@evgeni evgeni deleted the plugins branch December 2, 2025 10:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants