Skip to content

Add migration subcommand#446

Merged
ehelms merged 15 commits intotheforeman:masterfrom
archanaserver:create-migration-subcommand
Apr 27, 2026
Merged

Add migration subcommand#446
ehelms merged 15 commits intotheforeman:masterfrom
archanaserver:create-migration-subcommand

Conversation

@archanaserver
Copy link
Copy Markdown
Contributor

@archanaserver archanaserver commented Apr 8, 2026

Fixes #52

Create foremanctl migration subcommand to convert foreman-installer answer files to foremanctl YAML configuration.

Mappings include:

  • Database configuration (host, port, credentials, mode)
  • Foreman admin credentials
  • Certificate paths

Implements SAT-43246

@ehelms
Copy link
Copy Markdown
Member

ehelms commented Apr 8, 2026

Good to reference the repository issue -- #52

Comment thread docs/migrate-command.md Outdated
@@ -0,0 +1,128 @@
# Migrate Command
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'd consider making this guide about migrating from foreman-installer to foremanctl rather than the command itself.

Comment thread docs/migrate-command.md Outdated

### Command Options

- `--answer-file PATH` - Path to the foreman-installer answer file (default: `/etc/foreman-installer/scenarios.d/satellite.yaml`)
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.

Suggested change
- `--answer-file PATH` - Path to the foreman-installer answer file (default: `/etc/foreman-installer/scenarios.d/satellite.yaml`)
- `--answer-file PATH` - Path to the foreman-installer answer file (default: `/etc/foreman-installer/scenarios.d/foreman-answers.yaml`)

Comment thread docs/migrate-command.md Outdated

- `--answer-file PATH` - Path to the foreman-installer answer file (default: `/etc/foreman-installer/scenarios.d/satellite.yaml`)
- `--output PATH` - Path for the migrated configuration (default: stdout)
- `--root PATH` - Root directory for finding the answer file, useful for migrations from backups (default: /)
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.

Is the expectation when using --root that there is a directory that has this /backup/foreman-answers.yaml ? If so, would it be better to make the user be explicit here and just have the --answer-file parameter?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm I see the --root parameter adds little complexity but my intent was to simplify backup/restore scenarios where you have a full system backup mounted (e.g., at /backup) - using --root /backup is shorter than typing the full path. However, I agree the explicit --answer-file approach is clearer. Does foreman-installer have a similar --root parameter that users might expect? If not, I'm happy to remove it and keep things simple - just using --answer-file makes the code easier to understand. What's your preference?

ex:

  • With --root: foremanctl migrate --root /backup --output config.yaml
  • Without --root: foremanctl migrate --answer-file /backup/etc/foreman-installer/scenarios.d/last_scenario.yaml --output config.yaml

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 using --root here and pointing it at a backup gives a false idea of the migration concept/flow. A backup (as it's taken today) does not contain the answer file directly -- you'd need to unpack config_files.tar.gz and fetch the relevant yamls from there. And while at it, also grab the certs, as you'll need them for the migration too.

Ideally foremanctl will be able to do all that: point it at a valid backup and it will restore it including the migration from packages/puppet to containers/ansible. But I'd not put it here in this PR and focus solely on providing an entrypoint to start the "simple" "in-place" migration.

Comment thread docs/migrate-command.md Outdated
foremanctl migrate --output /tmp/new-config.yaml

# Migrate from custom location
foremanctl migrate --answer-file /path/to/answers.yaml --output /tmp/config.yaml
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.

My first thought is what do I as a user do with this file once I generate it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch! Updated doc now makes this clear upfront.

Comment thread docs/migrate-command.md Outdated
Comment thread docs/migrate-command.md Outdated
Comment thread docs/migrate-command.md Outdated
help: |
Migrate foreman-installer answer file to foremanctl configuration format.

This command reads the old Satellite 6.18 answer file and converts it to the
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.

This is overly Satellite specific.

Comment thread src/playbooks/migrate/migrate.yaml Outdated
@archanaserver archanaserver force-pushed the create-migration-subcommand branch from 89cac1f to 10c4843 Compare April 14, 2026 10:07
@archanaserver archanaserver requested review from ehelms and evgeni April 14, 2026 10:22
Comment thread src/playbooks/migrate/migrate.yaml Outdated
Comment thread src/playbooks/migrate/library/migrate_answers.py Outdated
Comment thread src/plugins/modules/migrate_answers.py
Comment thread src/playbooks/migrate/library/migrate_answers.py Outdated
Comment thread src/playbooks/migrate/library/migrate_answers.py Outdated
Comment thread docs/migration-guide.md
Comment thread src/playbooks/migrate/migrate.yaml Outdated

- name: Run migration
migrate_answers:
answer_file: "{{ answer_file | default(resolved_answer_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 would have done answer_file | default(omit) and do the "load last_scenario and find answer file there" logic in the migrate_answers module instead.

not blocking, just an opinion :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah, sounds clean moving all the logic in the module. on it

Comment thread docs/migration-guide.md

## Overview

When upgrading from foreman-installer to foremanctl, the `foremanctl migrate` command helps convert your existing configuration to the new format.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do you think it would be good to have steps here to install foremanctl on foreman-installer based setups?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

not sure about it, but my thinking is installation steps are probably better suited for the main README or deployment docs rather than the migration guide docs or can add a simple "prerequisites" section with a link to installation docs. WDYT?

@ehelms @evgeni thoughts?

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 since this guide is about how to upgrade from foreman-installer to foremanctl, that it should cover those steps for now. Like, prerequisites (e.g. foreman-installer installed, foremanctl present on the system) then run the steps. Or point to docs and say what steps should be run.

answer_file:
help: Path to the foreman-installer answer file to migrate. If not specified, attempts to read from the default location.
output:
help: Path where the migrated configuration should be written. If not specified, outputs to stdout.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If not specified, it outputs to stdout currently, so just a thought should we also put those into default config /var/lib/foremanctl/parameters.yaml as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I kept stdout as the default for the safer side, so that users can review output before it overwrites existing config. However, I believe we could document the recommended command: foremanctl migrate --output /var/lib/foremanctl/parameters.yaml. What's your preference?

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.

That logic makes sense to me.

Comment thread src/playbooks/migrate/migrate.yaml Outdated
Comment on lines +13 to +17
- name: Read scenario file
ansible.builtin.slurp:
src: "{{ default_scenario_file }}"
register: scenario_content

Copy link
Copy Markdown

@Gauravtalreja1 Gauravtalreja1 Apr 15, 2026

Choose a reason for hiding this comment

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

Currently, if I touch any random file and run the migration by passing that file with --answer-file, it passes with Migration completed successfully!, so I suggest adding a YAML validation check before reading it, to check if its valid yaml and valid answers file

Comment thread src/playbooks/migrate/metadata.obsah.yaml
Comment thread src/playbooks/migrate/migrate.yaml Outdated
Comment on lines +18 to +25
- name: Parse scenario file
ansible.builtin.set_fact:
scenario_data: "{{ scenario_content['content'] | b64decode | from_yaml }}"

- name: Extract answer file path from scenario
ansible.builtin.set_fact:
resolved_answer_file: "{{ scenario_data[':answer_file'] | default('') }}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can we combine this both set_fact tasks, as scenario_data seems redundant, can you try something like
(scenario_content.content | b64decode | from_yaml).get(':answer_file', '')?

not blocking, just an opinion to optimize?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

already addressed! I moved all scenario file reading logic to the module

- name: Run migration
migrate_answers:
answer_file: "{{ answer_file | default(resolved_answer_file) }}"
output: "{{ output | default(omit) }}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

if I pass --output diff.yaml , where file doesn't exist previously, and the file gets created under /usr/share/foremanctl/src/playbooks/migrate/diff.yaml instead of present working directory, is this expected?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

no it was not expected, i see a bug now, let me fix that and address other comments as well

Comment thread docs/migration-guide.md
@@ -0,0 +1,129 @@
# Migrating from foreman-installer to foremanctl
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.

Given the title and focus of the doc, I think the filename could use an update to reflect this.

Comment thread docs/migration-guide.md
- `--answer-file PATH` - Path to the foreman-installer answer file. If not specified, reads the currently active scenario and extracts the answer file path from it.
- `--output PATH` - Path for the migrated configuration (default: stdout)

## Parameter Mappings
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.

We have an overlap now with parameters.md. I think either combine these two, or link out and capture this information in there.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i've linked it, looks better?

Comment thread src/playbooks/migrate/library/migrate_answers.py Outdated
@archanaserver archanaserver force-pushed the create-migration-subcommand branch from 9dbc23c to 4694516 Compare April 17, 2026 09:54
Comment on lines +106 to +107
if isinstance(value, dict):
items.extend(flatten_nested_dict(value, key).items())
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.

This removes the "root" key from the result, which makes it harder to detect enabled plugins (you can detect them by being "non False", so an empty dict is an enabled plugin.

Suggested change
if isinstance(value, dict):
items.extend(flatten_nested_dict(value, key).items())
if isinstance(value, dict):
items.append(key, True)
items.extend(flatten_nested_dict(value, key).items())

This would add key with a True value to the list and we could then later process it.

At the same time, this is nothing that we need in the initial "here is the command", so feel free to say we add it later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm I was checking that earlier, it's small change but gonna differ here, and needs testing and more. but let's add this enhancement in follow-up to keep this PR focused on the migration functionality for now.

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've added this to my list of things that we need to implement for the final version.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You mean in that prepare work epic related to this one, right?

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.

Yes

Comment thread docs/migration-guide.md Outdated

Before migrating, ensure the following:

1. **foreman-installer is installed** - You should have an existing foreman-installer setup with answer files to migrate from.
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'd phrase that a bit differently, as the main point is not that foreman-installler is installed, but you have a Foreman installed by foreman-installer, so how about:

Suggested change
1. **foreman-installer is installed** - You should have an existing foreman-installer setup with answer files to migrate from.
1. **Foreman deployment using foreman-installer** - You should have an existing Foreman deployment has been installed using foreman-installer and has an answers file to migrate from.

Comment thread docs/migration-guide.md Outdated
```yaml
database_host: database.example.com
database_port: 5432
database_mode: internal
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.

Suggested change
database_mode: internal
database_mode: external

not that it technically matters, but your example uses a db host, so this is external db :)

Comment thread docs/migration-guide.md Outdated
Comment on lines +112 to +115
Warning: The following parameters could not be mapped:
- katello::enable_ostree
- foreman::some_other_param
```
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.

the warning format changed in one of the recent pushes, so this probably deserves an update to match code

@evgeni
Copy link
Copy Markdown
Member

evgeni commented Apr 17, 2026

There is a failing test, and I posted a few nitpicks, but I think this reflects what we wanted to expose as the first version of the migrate command (read: ACK once my comments are addressed ;) )

Comment thread src/playbooks/migrate/library/migrate_answers.py Outdated
Comment on lines +30 to +33
# Certificate configuration
('foreman', 'server_ssl_cert'): 'server_certificate',
('foreman', 'server_ssl_key'): 'server_key',
('foreman', 'server_ssl_ca'): 'ca_certificate',
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 this mapping is wrong, we should rather map the data from certs (so ('certs', 'server_cert'), (…, 'server_key') and (…, 'server_ca_cert') to the custom cert variables introduced in #421, but we don't need to wait on that and can update the mapping later, the current setup does not break anything

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ACK, I'll update the certificate mappings in a follow-up PR.

Comment thread src/plugins/modules/migrate_answers.py
Comment thread src/playbooks/migrate/migrate.yaml Outdated
@archanaserver archanaserver force-pushed the create-migration-subcommand branch from 199ac68 to 0b538d1 Compare April 22, 2026 11:22
Comment thread docs/migration-guide.md

When the migration completes, you may see warnings like:

> [!WARNING]
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.

This is going to be tricky in practice. Users will need guidance on parameters that could not be mapped. Perhaps for now we can include as part of the warning output the high-level possible reasons they could not be mapped?

For example:

  1. There was an error trying to map it
  2. The value has no equivalent parameter / is unsupported
  3. other reasons?

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 that's something that we'll have to learn (and document) as we go.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that is something we can focus on follow up work as well. Thanks for pointing it out.

@archanaserver
Copy link
Copy Markdown
Contributor Author

All green now, can we get this in, or is there anything else this needs to be covered? @evgeni @ehelms

@ehelms ehelms merged commit 2aa04c7 into theforeman:master Apr 27, 2026
25 of 29 checks passed
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.

How will RPM-based and foreman-installer installations migrate to a container-based installation?

4 participants