Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions changelogs/fragments/activation_key-cv_environments.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- activation_key - add ``content_view_environments`` parameter to support multi CV (https://github.com/theforeman/foreman-ansible-modules/pull/1935)
44 changes: 44 additions & 0 deletions plugins/modules/activation_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@
description:
- Name of the content view
type: str
content_view_environments:
description:
- List of content view environments to add to activation key.
- Mutually exclusive with C(content_view) and C(lifecycle_environment).
type: list
elements: str
version_added: 5.8.0
subscriptions:
description:
- List of subscriptions that include either Name, Pool ID, or Upstream Pool ID.
Expand Down Expand Up @@ -183,6 +190,24 @@
auto_attach: false
release_version: 7Server
service_level: Standard

- name: "Create client activation key with multiple content views"
theforeman.foreman.activation_key:
username: "admin"
password: "changeme"
server_url: "https://foreman.example.com"
name: "Clients"
organization: "Default Organization"
content_view_environments:
- prod/base_rhel_9
- test/third_party
host_collections:
- rhel9-servers
- rhel9-production
- rhel9-third-party-test
auto_attach: false
release_version: 9
service_level: Standard
'''

RETURN = '''
Expand Down Expand Up @@ -227,6 +252,7 @@ def main():
description=dict(),
lifecycle_environment=dict(type='entity', flat_name='environment_id', scope=['organization']),
content_view=dict(type='entity', scope=['organization']),
content_view_environments=dict(type='list', elements='str'),
Copy link
Member

Choose a reason for hiding this comment

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

When I tried implementing CV Environments for hosts in #1866, I used an "entity list", not a string list here (as CV Environments are entities that you can search for etc).

However, now that you did it differently, I wonder what's the most "correct" way is.

  • The user needs to pass in the environment labels in both cases, so the user interface doesn't change.
  • Using a "real" entity has the benefit of having a more direct error message if the CVE label can't be found, at the cost of an additional API request (see below)

Error with your code:

fatal: [localhost]: FAILED! => {"changed": false, "msg": "Failed to ensure entity state: ForemanApiException: Error while performing create on activation_keys: 422 Client Error: Unprocessable Content for url: https://centos9-stream-katello-nightly.juuni.example.com/katello/api/activation_keys - {'displayMessage': 'No content view environments found with names: RHEL9/blafasel', 'errors': ['No content view environments found with names: RHEL9/blafasel']}"}

Error with my code:

fatal: [localhost]: FAILED! => {"changed": false, "msg": "Found no results while searching for content_view_environments with label=\"RHEL9/blafasel\""}

I think paying an API request just to validate the label is too much, so I'd go with your solution, but wanted to highlight the possibility nevertheless

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should also say that I based my work on how the API currently does things.

Right now, the API lets you pass in a list of content-view-environment labels or a list of environment IDs (entities). I implemented the first one. The second one would push things more toward an entity-list approach, like you mentioned.

TBH, I just went with the more pragmatic option because I want to use this feature soon and needed something that works. I played around with an entity-list version for a bit, but since I didn’t have the time or the deeper knowledge of the code, I decided to drop it for now.

host_collections=dict(type='entity_list', scope=['organization']),
auto_attach=dict(type='bool'),
release_version=dict(),
Expand All @@ -237,6 +263,10 @@ def main():
purpose_role=dict(),
purpose_addons=dict(type='list', elements='str'),
),
mutually_exclusive=[
['lifecycle_environment', 'content_view_environments'],
['content_view', 'content_view_environments']
],
argument_spec=dict(
subscriptions=dict(type='list', elements='dict', options=dict(
name=dict(),
Expand Down Expand Up @@ -273,6 +303,20 @@ def main():
if not module.desired_absent:
module.lookup_entity('host_collections')
host_collections = module.foreman_params.pop('host_collections', None)

content_view_environments = module.foreman_params.pop('content_view_environments', None)
content_view = module.foreman_params.get("content_view")
lifecycle_environment = module.foreman_params.get("lifecycle_environment")
# only use content view environments if content view and lifecycle environment are not specified
if content_view_environments is not None and content_view is None and lifecycle_environment is None:
desired_content_view_environments = set(content_view_environments)
if not entity:
current_content_view_environments = set()
else:
current_content_view_environments = set(entity.get('content_view_environment_labels', '').split(","))
if desired_content_view_environments != current_content_view_environments:
module.foreman_params["content_view_environments"] = sorted(list(desired_content_view_environments))

activation_key = module.run()

# only update subscriptions of newly created or updated AKs
Expand Down
5 changes: 3 additions & 2 deletions roles/activation_keys/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
organization: "{{ item.organization | default(foreman_organization) }}"
name: "{{ item.name }}"
description: "{{ item.description | default(omit) }}"
lifecycle_environment: "{{ item.lifecycle_environment | default('Library') }}"
content_view: "{{ item.content_view | default('Default Organization View') }}"
lifecycle_environment: "{{ item.lifecycle_environment | default(omit) if (item.content_view_environments | default([]) | length > 0) else item.lifecycle_environment | default('Library') }}"
content_view: "{{ item.content_view | default(omit) if (item.content_view_environments | default([]) | length > 0) else item.content_view | default('Default Organization View') }}"
content_view_environments: "{{ item.content_view_environments | default(omit) }}"
host_collections: "{{ item.host_collections | default(omit) }}"
subscriptions: "{{ item.subscriptions | default(omit) }}"
content_overrides: "{{ item.content_overrides | default(omit) }}"
Expand Down
151 changes: 151 additions & 0 deletions tests/test_playbooks/activation_key.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,37 @@
vars:
activation_key_state: absent
activation_key_name: Test Activation Key Copy
- include_tasks: tasks/lifecycle_environment.yml
vars:
lifecycle_environment_state: present
lifecycle_environment_name: "{{ item.name }}"
lifecycle_environment_label: "{{ item.label }}"
lifecycle_environment_prior: "{{ item.prior }}"
loop:
- name: Dev
prior: Library
- name: Test
prior: Dev
- name: Prod
prior: Test
- include_tasks: tasks/content_view.yml
vars:
content_view_name: "{{ item }}"
loop:
- Test Content View 1
- Test Content View 2
- Test Content View 3
- include_tasks: tasks/content_view_version.yml
vars:
content_view_name: "{{ item }}"
lifecycle_environments:
- Dev
- Test
- Prod
loop:
- Test Content View 1
- Test Content View 2
- Test Content View 3

- hosts: tests
collections:
Expand Down Expand Up @@ -250,6 +281,102 @@
activation_key_state: absent
expected_change: false

- name: create AK with multiple CVs
include_tasks: tasks/activation_key.yml
vars:
activation_key_name: "Test Activation Key with multiple CVs"
activation_key_content_view_environments:
- "Dev/Test_Content_View_1"
- "Test/Test_Content_View_2"
expected_change: true
expected_diff: true
expected_diff_before: "{}"
# Dev/Test_Content_View_1 must appear
# Test/Test_Content_View_2 must appear
# Default Organization View must NOT appear
expected_diff_after: '^(?=.*Dev/Test_Content_View_1)(?=.*Test/Test_Content_View_2)(?!.*Default Organization View).*'
- name: swap content-view-environments, nothing should happen
include_tasks: tasks/activation_key.yml
vars:
activation_key_name: "Test Activation Key with multiple CVs"
activation_key_content_view_environments:
- "Test/Test_Content_View_2"
- "Dev/Test_Content_View_1"
expected_change: false
- name: add another content-view-environment
include_tasks: tasks/activation_key.yml
vars:
activation_key_name: "Test Activation Key with multiple CVs"
activation_key_content_view_environments:
- "Dev/Test_Content_View_1"
- "Test/Test_Content_View_2"
- "Prod/Test_Content_View_3"
expected_change: true
expected_diff: true
# Dev/Test_Content_View_1 must appear
# Test/Test_Content_View_2 must appear
# Default Organization View must NOT appear
expected_diff_before: '^(?=.*Dev/Test_Content_View_1)(?=.*Test/Test_Content_View_2)(?!.*Default Organization View).*'
# Dev/Test_Content_View_1 must appear
# Test/Test_Content_View_2 must appear
# Prod/Test_Content_View_3 must appear
# Default Organization View must NOT appear
expected_diff_after: '^(?=.*Dev/Test_Content_View_1)(?=.*Test/Test_Content_View_2)(?=.*Prod/Test_Content_View_3)(?!.*Default Organization View).*'
- name: remove AK with multiple CVs
include_tasks: tasks/activation_key.yml
vars:
activation_key_name: "Test Activation Key with multiple CVs"
activation_key_state: absent
expected_change: true

- name: create AK with multiple CVs
include_tasks: tasks/activation_key.yml
vars:
activation_key_content_view_environments:
- "Dev/Test_Content_View_1"
- "Test/Test_Content_View_2"
expected_change: true
expected_diff: true
expected_diff_before: "{}"
# Dev/Test_Content_View_1 must appear
# Test/Test_Content_View_2 must appear
# Default Organization View must NOT appear
expected_diff_after: "^(?=.*Dev/Test_Content_View_1)(?=.*Test/Test_Content_View_2)(?!.*Default Organization View).*"
- name: change AK to single CV mode
include_tasks: tasks/activation_key.yml
vars:
activation_key_content_view: "Default Organization View"
activation_key_lifecycle_environment: Library
expected_change: true
# Dev/Test_Content_View_1 must appear
# Test/Test_Content_View_2 must appear
# Default Organization View must NOT appear
expected_diff_before: "^(?=.*Dev/Test_Content_View_1)(?=.*Test/Test_Content_View_2)(?!.*Default Organization View).*"
# Dev/Test_Content_View_1 must NOT appear
# Test/Test_Content_View_2 must NOT appear
# Default Organization View must appear
expected_diff_after: "^(?!.*(Dev/Test_Content_View_1|Test/Test_Content_View_2))(?=.*Default Organization View).*"
- name: change AK back to multiple CV mode
include_tasks: tasks/activation_key.yml
vars:
activation_key_content_view_environments:
- "Dev/Test_Content_View_1"
- "Test/Test_Content_View_2"
expected_change: true
# Dev/Test_Content_View_1 must NOT appear
# Test/Test_Content_View_2 must NOT appear
# Default Organization View must appear
expected_diff_before: "^(?!.*(Dev/Test_Content_View_1|Test/Test_Content_View_2))(?=.*Default Organization View).*"
# Dev/Test_Content_View_1 must appear
# Test/Test_Content_View_2 must appear
# Default Organization View must NOT appear
expected_diff_after: "^(?=.*Dev/Test_Content_View_1)(?=.*Test/Test_Content_View_2)(?!.*Default Organization View).*"
- name: remove AK
include_tasks: tasks/activation_key.yml
vars:
activation_key_state: absent
expected_change: true

- hosts: localhost
collections:
- theforeman.foreman
Expand All @@ -265,6 +392,30 @@
- include_tasks: tasks/product.yml
vars:
product_state: absent
- include_tasks: tasks/lifecycle_environment.yml
vars:
lifecycle_environment_state: absent
lifecycle_environment_name: "{{ item }}"
loop:
- Test
- Dev
- include_tasks: tasks/content_view_version.yml
vars:
state: absent
content_view_name: "{{ item }}"
lifecycle_environments:
- Dev
- Test
loop:
- Test Content View 1
- Test Content View 2
- include_tasks: tasks/content_view.yml
vars:
content_view_state: absent
content_view_name: "{{ item }}"
loop:
- Test Content View 1
- Test Content View 2
- include_tasks: tasks/organization.yml
vars:
organization_state: absent
Loading