Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Module for generating registration command #1283

Closed
wants to merge 1 commit into from
Closed
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: 1 addition & 1 deletion plugins/module_utils/foreman_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def __init__(self, **kwargs):

def run(self, **kwargs):
new_entity = super(NestedParametersMixin, self).run(**kwargs)
if new_entity:
if new_entity and 'id' in new_entity:
Copy link
Member

Choose a reason for hiding this comment

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

Is this another hint, that it really should not be an entity module?

scope = {'{0}_id'.format(self.entity_name): new_entity['id']}
self.ensure_scoped_parameters(scope)
return new_entity
Expand Down
89 changes: 89 additions & 0 deletions plugins/modules/registration_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2021 Stejskal Leos, lstejska@redhat.com
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from __future__ import absolute_import, division, print_function
__metaclass__ = type

DOCUMENTATION = '''
---
module: registration_command
version_added: 1.0.0
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
version_added: 1.0.0
version_added: 2.3.0

short_description: Generate registration command
description:
- Generate registration command
author:
- "Stejskal Leos (@lstejska)"

'''

EXAMPLES = '''
- name: "Generate registration command"
theforeman.foreman.registration_command:
username: "admin"
password: "changeme"
server_url: "https://foreman.example.com"
'''

RETURN = '''
entity:
description: Final state of the affected entities grouped by their type.
returned: success
type: dict
contains:
registration_commands:
description: List of registration commands
type: list
elements: dict
'''

from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule, NestedParametersMixin


class ForemanRegistrationCommandModule(NestedParametersMixin, ForemanEntityAnsibleModule):
pass


def main():
module = ForemanRegistrationCommandModule(
foreman_spec=dict(
organization=dict(type='entity'),
location=dict(type='entity'),
hostgroup=dict(type='entity'),
operatingsystem=dict(type='entity'),
smart_proxy=dict(type='entity'),
insecure=dict(type='bool'),
setup_remote_execution=dict(type='bool'),
setup_insights=dict(type='bool'),
packages=dict(type='str'),
update_packages=dict(type='bool'),
repo=dict(type='str'),
repo_gpg_key_url=dict(type='str'),
jwt_expiration=dict(type='int', default=4),
remote_execution_interface=dict(type='str'),
lifecycle_environment=dict(type='entity', flat_name='lifecycle_environment_id'),
ignore_subman_errors=dict(type='bool'),
force=dict(type='bool'),
activation_keys=dict(type='list', elements='str'),
Comment on lines +63 to +80
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please document these in the DOCUMENTATION string above, under options:? The activation key module has a nice example of what it looks like:

options:
name:
description:
- Name of the activation key
required: true
type: str
description:
description:
- Description of the activation key
type: str
lifecycle_environment:
description:
- Name of the lifecycle environment
type: str
content_view:
description:
- Name of the content view
type: str
subscriptions:
description:
- List of subscriptions that include either Name, Pool ID, or Upstream Pool ID.
- Pool IDs are preferred since Names and Upstream Pool IDs are not guaranteed to be unique. The module will fail if it finds more than one match.
type: list
elements: dict
suboptions:
name:
description:
- Name of the Subscription to be added.
- Mutually exclusive with I(pool_id) and I(upstream_pool_id).
type: str
required: false
pool_id:
description:
- Pool ID of the Subscription to be added.
- Mutually exclusive with I(name) and I(upstream_pool_id).
- Also named C(Candlepin Id) in the CSV export of the subscriptions,
- it is as well the C(UUID) as output by C(hammer subscription list).
type: str
required: false
upstream_pool_id:
description:
- Upstream Pool ID of the Subscription to be added.
- Mutually exclusive with I(name) and I(pool_id).
- Also named C(Master Pools) in the Red Hat Portal.
type: str
required: false
host_collections:
description:
- List of host collections to add to activation key
type: list
elements: str
content_overrides:
description:
- List of content overrides that include label and override state ('enabled', 'disabled' or 'default')
type: list
elements: dict
suboptions:
label:
description:
- Label of the content override
type: str
required: true
override:
description:
- Override value
choices:
- enabled
- disabled
- default
type: str
required: true
auto_attach:
description:
- Set Auto-Attach on or off
type: bool
release_version:
description:
- Set the content release version
type: str
service_level:
description:
- Set the service level
choices:
- Self-Support
- Standard
- Premium
type: str
max_hosts:
description:
- Maximum number of registered content hosts.
- Required if I(unlimited_hosts=false)
type: int
unlimited_hosts:
description:
- Can the activation key have unlimited hosts
type: bool
purpose_usage:
description:
- Sets the system purpose usage
type: str
purpose_role:
description:
- Sets the system purpose role
type: str
purpose_addons:
description:
- Sets the system purpose add-ons
type: list
elements: str
state:
description:
- State of the Activation Key
- If C(copied) the key will be copied to a new one with I(new_name) as the name and all other fields left untouched
- C(present_with_defaults) will ensure the entity exists, but won't update existing ones
default: present
choices:
- present
- present_with_defaults
- absent
- copied
type: str
new_name:
description:
- Name of the new activation key when state == copied
type: str
extends_documentation_fragment:
- theforeman.foreman.foreman
- theforeman.foreman.foreman.organization

),
)

with module.api_connection():
module.run()


if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions tests/fixtures/apidoc/registration_command.json
209 changes: 209 additions & 0 deletions tests/test_playbooks/fixtures/registration_command-0.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json;version=2
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
User-Agent:
- apypie (https://github.com/Apipie/apypie)
method: GET
uri: http://foreman.example.org/api/status
response:
body:
string: '{"result":"ok","status":200,"version":"3.1.0-develop","api_version":2}'
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Security-Policy:
- 'default-src ''self''; child-src ''self''; connect-src ''self'' ws: wss:;
img-src ''self'' data:; script-src ''unsafe-eval'' ''unsafe-inline'' ''self'';
style-src ''unsafe-inline'' ''self'''
Content-Type:
- application/json; charset=utf-8
Foreman_api_version:
- '2'
Foreman_current_location:
- ; ANY
Foreman_current_organization:
- ; ANY
Foreman_version:
- 3.1.0-develop
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Frame-Options:
- sameorigin
X-Permitted-Cross-Domain-Policies:
- none
X-XSS-Protection:
- 1; mode=block
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json;version=2
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
User-Agent:
- apypie (https://github.com/Apipie/apypie)
method: GET
uri: http://foreman.example.org/api/organizations?search=name%3D%22Test+Organization%22&per_page=4294967296
response:
body:
string: "{\n \"total\": 2,\n \"subtotal\": 1,\n \"page\": 1,\n \"per_page\":
4294967296,\n \"search\": \"name=\\\"Test Organization\\\"\",\n \"sort\":
{\n \"by\": null,\n \"order\": null\n },\n \"results\": [{\"ancestry\":null,\"parent_id\":null,\"parent_name\":null,\"created_at\":\"2021-09-09
15:12:38 UTC\",\"updated_at\":\"2021-09-09 15:12:38 UTC\",\"id\":4,\"name\":\"Test
Organization\",\"title\":\"Test Organization\",\"description\":\"A test organization\"}]\n}\n"
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Security-Policy:
- 'default-src ''self''; child-src ''self''; connect-src ''self'' ws: wss:;
img-src ''self'' data:; script-src ''unsafe-eval'' ''unsafe-inline'' ''self'';
style-src ''unsafe-inline'' ''self'''
Content-Type:
- application/json; charset=utf-8
Foreman_api_version:
- '2'
Foreman_current_location:
- ; ANY
Foreman_current_organization:
- ; ANY
Foreman_version:
- 3.1.0-develop
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Frame-Options:
- sameorigin
X-Permitted-Cross-Domain-Policies:
- none
X-XSS-Protection:
- 1; mode=block
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json;version=2
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
User-Agent:
- apypie (https://github.com/Apipie/apypie)
method: GET
uri: http://foreman.example.org/api/locations?search=title%3D%22Test+Location%22&per_page=4294967296
response:
body:
string: "{\n \"total\": 2,\n \"subtotal\": 1,\n \"page\": 1,\n \"per_page\":
4294967296,\n \"search\": \"title=\\\"Test Location\\\"\",\n \"sort\": {\n
\ \"by\": null,\n \"order\": null\n },\n \"results\": [{\"ancestry\":null,\"parent_id\":null,\"parent_name\":null,\"created_at\":\"2021-09-09
15:12:37 UTC\",\"updated_at\":\"2021-09-09 15:12:37 UTC\",\"id\":3,\"name\":\"Test
Location\",\"title\":\"Test Location\",\"description\":null}]\n}\n"
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Security-Policy:
- 'default-src ''self''; child-src ''self''; connect-src ''self'' ws: wss:;
img-src ''self'' data:; script-src ''unsafe-eval'' ''unsafe-inline'' ''self'';
style-src ''unsafe-inline'' ''self'''
Content-Type:
- application/json; charset=utf-8
Foreman_api_version:
- '2'
Foreman_current_location:
- ; ANY
Foreman_current_organization:
- ; ANY
Foreman_version:
- 3.1.0-develop
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Frame-Options:
- sameorigin
X-Permitted-Cross-Domain-Policies:
- none
X-XSS-Protection:
- 1; mode=block
status:
code: 200
message: OK
- request:
body: '{"location_id": 3, "organization_id": 4, "registration_command": {"organization_id":
4, "location_id": 3, "jwt_expiration": 4}}'
headers:
Accept:
- application/json;version=2
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '127'
Content-Type:
- application/json
User-Agent:
- apypie (https://github.com/Apipie/apypie)
method: POST
uri: http://foreman.example.org/api/registration_commands
response:
body:
string: '{"registration_command":"curl -sS ''http://localhost:3000/register?location_id=3\u0026organization_id=4''
-H ''Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0LCJpYXQiOjE2MzEyMDA3NjcsImp0aSI6ImE1NWE2MDZmZDI3ZGY5NGRiMjFkMmZkYzI5NWVmODIxZDI2YmRiZmVmMGZjNzcyZTVlZTkxYmM1MTlmMzVmNWIiLCJleHAiOjE2MzEyMTUxNjcsInNjb3BlIjoicmVnaXN0cmF0aW9uI2dsb2JhbCByZWdpc3RyYXRpb24jaG9zdCJ9.3BzVTEVe5qS_cYO_HMO_BKrtFty6c-ROOBIqfKmfSO4''
| bash"}'
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Security-Policy:
- 'default-src ''self''; child-src ''self''; connect-src ''self'' ws: wss:;
img-src ''self'' data:; script-src ''unsafe-eval'' ''unsafe-inline'' ''self'';
style-src ''unsafe-inline'' ''self'''
Content-Type:
- application/json; charset=utf-8
Foreman_api_version:
- '2'
Foreman_current_location:
- 3; Test Location
Foreman_current_organization:
- 4; Test Organization
Foreman_version:
- 3.1.0-develop
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Frame-Options:
- sameorigin
X-Permitted-Cross-Domain-Policies:
- none
X-XSS-Protection:
- 1; mode=block
status:
code: 200
message: OK
version: 1
28 changes: 28 additions & 0 deletions tests/test_playbooks/registration_command.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
- hosts: localhost
collections:
- theforeman.foreman
tags:
- setup
gather_facts: false
vars_files:
- vars/server.yml
tasks:
- include_tasks: tasks/location.yml
vars:
location_state: "present"
- include_tasks: tasks/organization.yml
vars:
organization_state: "present"

- hosts: tests
collections:
- theforeman.foreman
tags:
- test
gather_facts: false
vars_files:
- vars/server.yml
tasks:
# generate registration command
- include: tasks/registration_command.yml
21 changes: 21 additions & 0 deletions tests/test_playbooks/tasks/registration_command.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
- name: "Ensure registration command is {{ rc_state }}"
vars:
rc_organization: Test Organization
rc_location: Test Location
rc_state: present
registration_command:
username: "{{ foreman_username }}"
password: "{{ foreman_password }}"
server_url: "{{ foreman_server_url }}"
validate_certs: "{{ foreman_validate_certs }}"
organization: "{{ rc_organization }}"
location: "{{ rc_location }}"
state: "{{ rc_state }}"
Copy link
Contributor

Choose a reason for hiding this comment

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

Is passing some state necessary for this module? What would it mean to ensure state for a registration command?

The documentation in the module has no state in the example:

EXAMPLES = '''
- name: "Generate registration command"
  theforeman.foreman.registration_command:
    username: "admin"
    password: "changeme"
    server_url: "https://foreman.example.com"
'''

register: result
- assert:
fail_msg: "Ensuring registering command is {{ rc_state }} failed! (expected_change: {{ expected_change | default('unknown') }})"
that:
- result.changed == expected_change
when: expected_change is defined
...