Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions docs/feature-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Feature Metadata

Users want to enable abstract features, which means the deployment needs to know how to translate a feature name to a set of changes (configuration files, services, etc).

The metadata is a Hash with the feature name as the key and the feature definition as the value.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This file describes how the metadata looks like, but not where it's stored.

#309 stores it as a Hash in a Python file, but it's probably best suited as a standalone YAML file?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree -- a yaml file will be easier.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree that it needs to be a file. Do you think we need a single file per the whole foreman, or does it make more sense to have one per feature/plugin?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

what would the benefit be if we have file-per-plugin? (I prefer single-file, unless there is a good reason not to)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Each plugin will own its metadata - it is easier to manage it in the plugin repo, than to know that a specific obscure file needs to be changed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

right now it'd be a feature that has both foreman and foreman_proxy fields empty, but then something special in role

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Each plugin will own its metadata - it is easier to manage it in the plugin repo, than to know that a specific obscure file needs to be changed.

How would the file from the repo come to Ansible?
How do people manage that today with Puppet?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

you're right - it seems like a chicken and egg problem. You can't start the installation without metadata file, but if the metadata is in the plugin/close to the code itself, there is no way foremanctl can't know about it.

Not sure it would be a good idea, but we can use the pattern from gemfile: where you can reference a specific git commit/tag:

gem "gem_name", git: "https://github.com", ref: "SHA1_COMMIT_HASH"

So the central file will not need to be updated frequently, but the installation procedure will live close to the feature code itself.

What if there are features that do not map to a plugin? Where would that metadata live?

I suppose you will still have some code for the feature, so the metadata will live there. If the feature is a kind of "meta-feature" (just a set of dependencies without extra code, then we won't specify the source at all, so it will work fine. The only thing that I think is missing here is a feature that just needs to add some configuration (without code), although I am not sure if this is something that we should take into account.
In the worst case, we can put such features in the foremanctl repo too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OK, let's keep that discussion for the next version. I think for the MVP we can start with a single file, without over-engineering this part.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The feature definition itself is again a Hash with the various properties of the feature.

```yaml
feature_one:
property1: value_of_property1
property2: value_of_property2
feature_two:
property1: another_value_of_property1
property2: another_value_of_property2
```

The following properties are defined:
* `description` (_String_): A human-readable description of the feature, can be used in documentation/help output.
* `internal` (_Boolean_): Whether the feature is user visible (shows up in documentation/help) or internal (just to perform additional configuration without user interaction).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In the current foremanctl, what's considered an internal feature?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

dynflow in #309 would be one. so far no others.

* `foreman` (_Hash_): How this feature should be applied on the "main" system that offers the main user interaction via UI/API.
* `plugin_name` (_String_): The name of the Foreman plugin to be enabled (via `FOREMAN_ENABLED_PLUGINS`).
If `roles/foreman/tasks/feature/{{ foreman_plugin }}.yaml` exists, it will be executed to perform any plugin-specific setup.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What would be the difference between task file and role below?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The role is "standalone" while the task file runs inside the context of the existing foreman role and thus can rely on the main foreman service being already done etc.
Basically "if you're a plugin to foreman, use the task file, if you're a standalone service like iop, use the role"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should capture that statement as part of our design guidance.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I tried to add that to the metadata description. Does that work?
Or do you want it as a explicit standalone statement?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

sounds OK to me.
Not blocking: is it plugin_name OR role or both? If it's both what would be the order of execution?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I was aiming at XOR, as both leads to ambiguity, as you point out.

* **FIXME**: task file not implemented yet
* `role` (_String_): The name of the Ansible role to be executed if the feature to be placed on the main system but is not implemented as a Foreman plugin.
* `foreman_proxy` (_Hash_): How this feature should be applied to a secondary system that is connected using the Proxy API to the main system.
* `plugin_name` (_String_): The name of the Foreman Proxy plugin to be enabled (by deploying `roles/foreman_proxy/templates/settings.d/{{ foreman_proxy_plugin }}.yml.j2` to `/etc/foreman-proxy/settings.d`).
If `roles/foreman/tasks/feature/{{ foreman_proxy_plugin }}.yaml` exists, it will be executed to perform any plugin-specific setup.
* `role` (_String_): The name of the Ansible role to be executed if the feature is not implemented as a Foreman Proxy plugin.
* `hammer` (_String_): The name of the Hammer plugin to be enabled.
* **FIXME**: Not implemented, right now we use the same list as Foreman plugins, but needs modification for foreman-tasks and friends
* `dependencies` (_Array_ of _String_): List of features that are automatically enabled when the user requests this feature. Usually will point at features with `internal: true`.

Properties can be omitted.

## Examples

### REX + Dynflow

```yaml
dynflow:
internal: true
foreman_proxy: dynflow

remote_execution:
description: Foreman Remote Execution support
foreman: foreman_remote_execution
foreman_proxy: remote_execution_ssh
dependencies:
- dynflow
```

The `remote_execution` feature will enable the `foreman_remote_execution` plugin for Foreman and the `remote_execution_ssh` and `dynflow` plugins for Foreman Proxy.
The `dynflow` feature is hidden from the user and only present so that `remote_execution` can pull it in.

### RH Cloud + Katello
```yaml
katello:
description: Katello
foreman: katello

rh_cloud:
description: Foreman RH Cloud
foreman: foreman_rh_cloud
dependencies:
- katello
```

The `rh_cloud` feature can't be enabled unless the `katello` feature is also present in the deployment.

### Katello + tasks
```yaml
foreman-tasks:
foreman: foreman-tasks
hammer: foreman_tasks

katello:
description: Katello
foreman: katello
dependencies:
- foreman-tasks
```

The `foreman-tasks` feature is automatically enabled when the user requests Katello, thus also gaining the Hammer integration for tasks which would be missing if we'd only let the Ruby gem dependency pull in `foreman-tasks`.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To see if I understand everything correctly, if I combine the last two examples, what we would have is:

dynflow:
  internal: true
  foreman_proxy: dynflow

foreman-tasks:
  foreman: foreman-tasks
  hammer: foreman_tasks
  dependencies:
    - dynflow

katello:
  description: Katello
  foreman: katello
  dependencies:
    - foreman-tasks
   
rh_cloud:
  description: Foreman RH Cloud
  foreman: foreman_rh_cloud
  requirements:
    - katello

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah. That would be the fuller example, I rather skipped multiple levels not to become too verbose (unless you think that's helpful?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I needed to see this bigger, combined example to make sure I understood it will be one big hash with everything rather than a bunch of small hashes.


### another example

```yaml
dynflow:
internal: true
foreman_proxy:
plugin_name: dynflow

foreman-tasks:
foreman:
plugin_name: foreman-tasks
hammer: foreman_tasks
dependencies:
- dynflow

katello:
description: Katello
foreman:
plugin_name: katello
dependencies:
- foreman-tasks

rh_cloud:
description: Foreman RH Cloud
foreman:
plugin_name: foreman_rh_cloud
dependencies:
- katello

remote_execution:
description: REX
foreman:
plugin_name: foreman_remote_execution
foreman_proxy:
plugin_name: smart_proxy_remote_execution_ssh
dependencies:
- dynflow

openscap:
description: OpenSCAP
foreman:
plugin_name: foreman_openscap
foreman_proxy:
plugin_name: smart_proxy_openscap

iop:
description: IoP
foreman:
role: iop_core

container_gateway:
description: gw is a proxy-only plugin
foreman_proxy:
plugin_name: smart_proxy_container_gateway
```
Loading