Skip to content

Support content_view_environment_id for host and hostgroup#1977

Open
jeremylenz wants to merge 1 commit into
theforeman:developfrom
jeremylenz:deprecate-cv-lce-params
Open

Support content_view_environment_id for host and hostgroup#1977
jeremylenz wants to merge 1 commit into
theforeman:developfrom
jeremylenz:deprecate-cv-lce-params

Conversation

@jeremylenz

@jeremylenz jeremylenz commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

On newer Katello versions where the content_view_id/lifecycle_environment_id API params have been removed, the host and hostgroup modules internally convert content_view/lifecycle_environment to the new API params:

  • Hostgroups: content_view_environment_id (singular)
  • Hosts: content_view_environment_ids (array in content_facet_attributes)

Multi-CV entities are protected from accidental overwrite. Backward compatible — on older Katello versions the conversion is skipped and old params flow through natively.

Split from #1982 (activation_key changes, merged). Related Katello PR: Katello/katello#11753

Test plan

All 9 tests pass against both old and new Katello:

Test Description
1 AK create with deprecated params
2 AK idempotency with deprecated params
3 AK create with content_view_environments
4 AK content_view_environments idempotency
5 Multi-CV AK protection
6 Hostgroup create with deprecated params
7 Hostgroup idempotency with deprecated params
8 Host create with deprecated params
9 Host idempotency with deprecated params
Test vars (test_cv_lce_vars.yml)
foreman_server_url: "https://your-foreman.example.com"
foreman_username: "admin"
foreman_password: "changeme"
foreman_validate_certs: false
test_organization: "Default Organization"
test_lifecycle_environment: "Library"
test_content_view: "Default Organization View"
# The default CVE label is just the LCE label (e.g. "Library"),
# not "Library/Default_Organization_View". Non-default CVEs use "lce_label/cv_label".
test_cve_label: "Library"
test_lifecycle_environment_2: "dev"
test_content_view_2: "cv1"
Test playbook (test_deprecate_cv_lce.yml)

Run with: ansible-playbook test_deprecate_cv_lce.yml -e @test_cv_lce_vars.yml

Prerequisites: allow_multiple_content_views setting enabled + a second CV promoted to a second LCE (for test 5)

---
- name: "Test deprecated CV/LCE param handling"
  hosts: localhost
  collections:
    - theforeman.foreman
  gather_facts: false
  vars:
    foreman_conn: &foreman_conn
      server_url: "{{ foreman_server_url }}"
      username: "{{ foreman_username }}"
      password: "{{ foreman_password }}"
      validate_certs: "{{ foreman_validate_certs }}"
  tasks:

    # =========================================================================
    # CLEANUP (before tests, so playbook is re-runnable)
    # =========================================================================

    - name: "Setup - Remove leftover test resources"
      block:
        - host:
            <<: *foreman_conn
            name: "test-deprecated-cv-lce-host.example.com"
            state: absent
          ignore_errors: true
        - activation_key:
            <<: *foreman_conn
            organization: "{{ test_organization }}"
            name: "{{ item }}"
            state: absent
          loop:
            - "test-deprecated-cv-lce"
            - "test-new-cve-param"
            - "test-multi-cv-ak"
          ignore_errors: true
        - hostgroup:
            <<: *foreman_conn
            name: "test-deprecated-cv-lce-hg"
            state: absent
          ignore_errors: true

    # =========================================================================
    # ACTIVATION KEY TESTS
    # =========================================================================

    - name: "Test 1 - AK create with deprecated params (should warn + succeed)"
      activation_key:
        <<: *foreman_conn
        organization: "{{ test_organization }}"
        name: "test-deprecated-cv-lce"
        lifecycle_environment: "{{ test_lifecycle_environment }}"
        content_view: "{{ test_content_view }}"
        state: present
      register: ak_create
    - debug:
        msg: "Test 1: changed={{ ak_create.changed }}, warnings={{ ak_create.warnings | default([]) }}"
    - assert:
        that: ak_create.changed
        fail_msg: "Test 1 FAILED - AK should have been created"

    - name: "Test 2 - AK idempotency with deprecated params (should not change)"
      activation_key:
        <<: *foreman_conn
        organization: "{{ test_organization }}"
        name: "test-deprecated-cv-lce"
        lifecycle_environment: "{{ test_lifecycle_environment }}"
        content_view: "{{ test_content_view }}"
        state: present
      register: ak_idempotent
    - debug:
        msg: "Test 2: changed={{ ak_idempotent.changed }}"
    - assert:
        that: not ak_idempotent.changed
        fail_msg: "Test 2 FAILED - AK should be idempotent (no change)"

    - name: "Test 3 - AK create with content_view_environments param (no deprecation warning)"
      activation_key:
        <<: *foreman_conn
        organization: "{{ test_organization }}"
        name: "test-new-cve-param"
        content_view_environments:
          - "{{ test_cve_label }}"
        state: present
      register: ak_cve
    - debug:
        msg: "Test 3: changed={{ ak_cve.changed }}, warnings={{ ak_cve.warnings | default([]) }}"
    - assert:
        that: ak_cve.changed
        fail_msg: "Test 3 FAILED - AK with content_view_environments should have been created"

    - name: "Test 4 - AK with content_view_environments idempotency"
      activation_key:
        <<: *foreman_conn
        organization: "{{ test_organization }}"
        name: "test-new-cve-param"
        content_view_environments:
          - "{{ test_cve_label }}"
        state: present
      register: ak_cve_idempotent
    - debug:
        msg: "Test 4: changed={{ ak_cve_idempotent.changed }}"
    - assert:
        that: not ak_cve_idempotent.changed
        fail_msg: "Test 4 FAILED - AK with content_view_environments should be idempotent"

    - name: "Test 5 - AK multi-CV protection (should FAIL if multi-CV)"
      when: test_content_view_2 is defined and test_lifecycle_environment_2 is defined
      block:
        - name: "Test 5a - Set up multi-CV activation key"
          activation_key:
            <<: *foreman_conn
            organization: "{{ test_organization }}"
            name: "test-multi-cv-ak"
            content_view_environments:
              - "{{ test_cve_label }}"
              - "{{ test_lifecycle_environment_2 }}/{{ test_content_view_2 }}"
            state: present

        - name: "Test 5b - Try deprecated params on multi-CV AK"
          activation_key:
            <<: *foreman_conn
            organization: "{{ test_organization }}"
            name: "test-multi-cv-ak"
            lifecycle_environment: "{{ test_lifecycle_environment }}"
            content_view: "{{ test_content_view }}"
            state: present
          register: ak_multi_cv
          ignore_errors: true
        - debug:
            msg: >-
              Test 5: failed={{ ak_multi_cv.failed }},
              changed={{ ak_multi_cv.changed | default('N/A') }},
              msg={{ ak_multi_cv.msg | default('') }}
        # New API: should fail with multi-CV protection error
        # Old API: should succeed but not change (old API silently ignores single params on multi-CV AKs)
        - assert:
            that: ak_multi_cv.failed or not ak_multi_cv.changed
            fail_msg: "Test 5 FAILED - Should either fail (new API) or be a no-op (old API), not change the AK"

    # =========================================================================
    # HOSTGROUP TESTS
    # =========================================================================

    - name: "Test 6 - Hostgroup create with deprecated params (should warn + succeed)"
      hostgroup:
        <<: *foreman_conn
        organization: "{{ test_organization }}"
        name: "test-deprecated-cv-lce-hg"
        lifecycle_environment: "{{ test_lifecycle_environment }}"
        content_view: "{{ test_content_view }}"
        state: present
      register: hg_create
    - debug:
        msg: "Test 6: changed={{ hg_create.changed }}, warnings={{ hg_create.warnings | default([]) }}"
    - assert:
        that: hg_create.changed
        fail_msg: "Test 6 FAILED - Hostgroup should have been created"

    - name: "Test 7 - Hostgroup idempotency with deprecated params"
      hostgroup:
        <<: *foreman_conn
        organization: "{{ test_organization }}"
        name: "test-deprecated-cv-lce-hg"
        lifecycle_environment: "{{ test_lifecycle_environment }}"
        content_view: "{{ test_content_view }}"
        state: present
      register: hg_idempotent
    - debug:
        msg: "Test 7: changed={{ hg_idempotent.changed }}"
    - assert:
        that: not hg_idempotent.changed
        fail_msg: "Test 7 FAILED - Hostgroup should be idempotent (no change)"

    # =========================================================================
    # HOST TESTS
    # =========================================================================

    - name: "Test 8 - Host create with deprecated params (should succeed)"
      host:
        <<: *foreman_conn
        organization: "{{ test_organization }}"
        location: "{{ test_location | default('Default Location') }}"
        name: "test-deprecated-cv-lce-host.example.com"
        managed: false
        lifecycle_environment: "{{ test_lifecycle_environment }}"
        content_view: "{{ test_content_view }}"
        state: present
      register: host_create
    - debug:
        msg: "Test 8: changed={{ host_create.changed }}, warnings={{ host_create.warnings | default([]) }}"
    - assert:
        that: host_create.changed
        fail_msg: "Test 8 FAILED - Host should have been created"

    - name: "Test 9 - Host idempotency with deprecated params"
      host:
        <<: *foreman_conn
        organization: "{{ test_organization }}"
        location: "{{ test_location | default('Default Location') }}"
        name: "test-deprecated-cv-lce-host.example.com"
        managed: false
        lifecycle_environment: "{{ test_lifecycle_environment }}"
        content_view: "{{ test_content_view }}"
        state: present
      register: host_idempotent
    - debug:
        msg: "Test 9: changed={{ host_idempotent.changed }}"
    - assert:
        that: not host_idempotent.changed
        fail_msg: "Test 9 FAILED - Host should be idempotent (no change)"

    # =========================================================================
    # CLEANUP (after tests)
    # =========================================================================

    - name: "Cleanup - Remove test host"
      host:
        <<: *foreman_conn
        name: "test-deprecated-cv-lce-host.example.com"
        state: absent
      ignore_errors: true

    - name: "Cleanup - Remove test activation keys"
      activation_key:
        <<: *foreman_conn
        organization: "{{ test_organization }}"
        name: "{{ item }}"
        state: absent
      loop:
        - "test-deprecated-cv-lce"
        - "test-new-cve-param"
        - "test-multi-cv-ak"
      ignore_errors: true

    - name: "Cleanup - Remove test hostgroup"
      hostgroup:
        <<: *foreman_conn
        name: "test-deprecated-cv-lce-hg"
        state: absent
      ignore_errors: true

🤖 Generated with Claude Code

@jeremylenz

Copy link
Copy Markdown
Contributor Author
Pushed. The CI failures break down as:

  - Sanity tests (all versions): Fixed — was pylint complaining about _ as a variable name in our tuple unpacking. Renamed to _filtered.
  - Build tests (devel, 2.21, 3.13, 3.14): Pre-existing callback fixture mismatches from newer ansible-core versions. Unrelated to our changes — these would fail on any PR right now.

@jeremylenz

Copy link
Copy Markdown
Contributor Author

Fixed an issue with the test playbook; it should now run on both Katello master and PR branch.

@jeremylenz

Copy link
Copy Markdown
Contributor Author

requesting review from @evgeni when you have a chance :)

Comment thread plugins/module_utils/foreman_helper.py
module.foreman_spec['content_view']['ensure'] = False
module.foreman_spec['lifecycle_environment']['ensure'] = False

if content_view_environments is not None and (converted_from_deprecated or (content_view is None and lifecycle_environment is None)):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The conversion logic in activation_key.py (lines ~312-361) and HostMixin._convert_cv_lce_to_cve are largely identical: API version detection, multi-CV guard, deprecation warning, lookup, fallback, CVE resolution, and ensure=False. The activation_key version differs only in how it extracts fallback IDs from the entity and what it does with the result (sets content_view_environments labels vs content_view_environment_id).

Not sure if it is worth to consider whether the common flow (detect version, guard multi-CV, warn, lookup, resolve CVE, suppress old params) could live in a shared helper, with the AK-specific parts (entity structure, output format) parameterized. It's up to you 😄

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.

Fair point — the two paths do share a lot of structure. I kept them separate for now since the differences are meaningful: AK converts to label strings and feeds into the existing content_view_environments handling, while HostMixin sets a numeric content_view_environment_id directly. The entity response structures also differ. But if we add more resources that need this, extracting a shared helper would make sense.

Comment thread changelogs/fragments/deprecate-cv-lce-params.yml Outdated

@lfu lfu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All tests passed for ansible-playbook test_deprecate_cv_lce.yml -e @test_cv_lce_vars.yml.

@jeremylenz

Copy link
Copy Markdown
Contributor Author

@evgeni hi again! Seems test failures are unrelated, right?

Comment on lines +13 to +16
bugfixes:
- foreman_helper - use safe dictionary access for content_view_id/environment_id
in update workaround to prevent KeyError on newer Katello versions
(https://github.com/theforeman/foreman-ansible-modules/pull/1977)

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.

IMHO this is not a bugfix for something that is user-visible today, so we don't need to mention it.

Comment on lines +5 to +7
- host, hostgroup - the ``content_view`` and ``lifecycle_environment`` parameters
are deprecated and will be removed in a future version
(https://github.com/theforeman/foreman-ansible-modules/pull/1977)

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 is the replacement for the deprecated parametes? Host and Hostgroup modules do not accept content_view_environments like AK does.

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.

For hosts, the API accepts both content_view_environments (labels) and content_view_environment_ids. For host groups, it's a single content_view_environment or content_view_environment_id.

should I update those modules here?

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 know what the API accepts. But the modules need to expose that (well, the list thing, not the ids, that's something we do not expose usually) for users to be able to use it.

Comment thread plugins/module_utils/foreman_helper.py Outdated
"The deprecated 'content_view' and 'lifecycle_environment' "
"parameters cannot safely update it — they would overwrite "
"the existing multi-CV assignment. Use "
"'content_view_environments' or 'content_view_environment_ids' "

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.

There is no content_view_environments parameter and content_view_environment_ids is correctly (!) marked as invisible so it can't be set by users either.

@evgeni

evgeni commented Jul 9, 2026

Copy link
Copy Markdown
Member

@evgeni hi again! Seems test failures are unrelated, right?

Correct. Fixed them in #1981

@jeremylenz

Copy link
Copy Markdown
Contributor Author

AK changes have been split out into #1982 per @evgeni's suggestion.

This PR will be rebased once #1982 merges, and the outstanding review comments about host/hostgroup will be addressed here:

  • Error message referencing content_view_environments/content_view_environment_ids params that don't exist as user-facing params on host/hostgroup
  • Deprecation notice needs a replacement param (which doesn't exist yet)
  • Changelog entries to be updated for host/hostgroup only

@jeremylenz jeremylenz marked this pull request as draft July 9, 2026 14:22
@jeremylenz jeremylenz force-pushed the deprecate-cv-lce-params branch from 800adbe to d0e5da9 Compare July 9, 2026 14:45
@jeremylenz jeremylenz marked this pull request as ready for review July 9, 2026 14:45
@jeremylenz jeremylenz changed the title Deprecate content_view and lifecycle_environment parameters Support content_view_environment_id for host and hostgroup Jul 9, 2026
@jeremylenz jeremylenz force-pushed the deprecate-cv-lce-params branch from d0e5da9 to 75cbe96 Compare July 9, 2026 14:51
@jeremylenz

Copy link
Copy Markdown
Contributor Author

@evgeni I removed the deprecation warning from plugins/module_utils/foreman_helper.py since there's no replacement param yet. Do you want to add those replacement params here so we can do the deprecation? Or push that to a future PR?

@evgeni

evgeni commented Jul 9, 2026

Copy link
Copy Markdown
Member

This makes it work for 4.21 users, right?
So I think it's a fair enhancement and we can merge it without the deprecation given we have no replacement yet.

@jeremylenz jeremylenz force-pushed the deprecate-cv-lce-params branch from 75cbe96 to 56c9bb7 Compare July 9, 2026 15:17
On newer Katello versions where the old content_view_id/
lifecycle_environment_id API params have been removed, internally
convert to content_view_environment_id. Multi-CV entities are
protected from accidental overwrite.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jeremylenz

Copy link
Copy Markdown
Contributor Author

@evgeni Updated the host logic and updated the test playbook (expandable in the description).

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.

3 participants