From 39132bb3ceddd990f366412ed96ab71ab6009dfd Mon Sep 17 00:00:00 2001 From: Evgeni Golov Date: Thu, 16 Nov 2023 09:31:31 +0100 Subject: [PATCH] add registration_command module Fixes: #1284 --- meta/runtime.yml | 1 + plugins/modules/registration_command.py | 194 +++++++++++++++ .../fixtures/apidoc/registration_command.json | 1 + .../fixtures/registration_command-0.yml | 233 ++++++++++++++++++ tests/test_playbooks/registration_command.yml | 39 +++ .../tasks/registration_command.yml | 18 ++ 6 files changed, 486 insertions(+) create mode 100644 plugins/modules/registration_command.py create mode 120000 tests/fixtures/apidoc/registration_command.json create mode 100644 tests/test_playbooks/fixtures/registration_command-0.yml create mode 100644 tests/test_playbooks/registration_command.yml create mode 100644 tests/test_playbooks/tasks/registration_command.yml diff --git a/meta/runtime.yml b/meta/runtime.yml index 9e34e240ec..068fce2f69 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -53,6 +53,7 @@ action_groups: - puppet_environment - puppetclasses_import - realm + - registration_command - repository - repository_info - repository_set diff --git a/plugins/modules/registration_command.py b/plugins/modules/registration_command.py new file mode 100644 index 0000000000..21b6e3f346 --- /dev/null +++ b/plugins/modules/registration_command.py @@ -0,0 +1,194 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) Evgeni Golov +# +# 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 . + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: registration_command +version_added: 4.0.0 +short_description: Manage Registration Command +description: + - Manage Registration Command +author: + - "Evgeni Golov (@evgeni)" +options: + activation_keys: + description: + - Activation keys for subscription-manager client, required for CentOS and Red Hat + Enterprise Linux. + - Required only if host group has no activation keys. + required: false + type: list + elements: str + force: + description: + - "Clear any previous registration and run C(subscription-manager) with C(--force)." + required: false + type: bool + hostgroup: + description: + - Host group to register the host in + required: false + type: str + ignore_subman_errors: + description: + - Ignore C(subscription-manager) errors for C(subscription-manager register) command + required: false + type: bool + insecure: + description: + - Enable insecure argument for the initial C(curl) + required: false + type: bool + jwt_expiration: + description: + - Expiration of the authorization token (in hours) + required: false + type: int + lifecycle_environment: + description: + - Lifecycle environment for the host. + required: false + type: str + operatingsystem: + description: + - Operating System to register the host in. + - Operating system must have a C(host_init_config) template assigned. + required: false + type: str + packages: + description: + - Packages to install on the host when registered. + required: false + type: str + remote_execution_interface: + description: + - Identifier of the Host interface for Remote execution + required: false + type: str + repo: + description: + - Repository URL (yum/dnf) or full sources.list entry (apt). + required: false + type: str + repo_gpg_key_url: + description: + - URL of the GPG key for the repository + required: false + type: str + setup_insights: + description: + - If this is set to C(true), insights client will be installed + and registered on Red Hat family operating systems. + required: false + type: bool + setup_remote_execution: + description: + - If this is set to true, SSH keys will be installed on the host. + required: false + type: bool + setup_remote_execution_pull: + description: + - If this is set to true, pull provider client will be deployed on the host. + required: false + type: bool + smart_proxy: + description: + - Name of Smart Proxy. + - This Proxy must have both the C(Templates) and C(Registration) features enabled. + required: false + type: str + update_packages: + description: + - Update all packages on the host + required: false + type: bool + organization: + description: + - Organization to register the host in + required: false + type: str + location: + description: + - Location to register the host in + required: false + type: str + +extends_documentation_fragment: + - theforeman.foreman.foreman +''' + +EXAMPLES = ''' +''' + +RETURN = ''' +registration_command: + description: The generated registration command. + returned: success + type: str +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanAnsibleModule + + +class ForemanRegistrationCommandModule(ForemanAnsibleModule): + pass + + +def main(): + module = ForemanRegistrationCommandModule( + foreman_spec=dict( + hostgroup=dict(required=False, type='entity'), + operatingsystem=dict(required=False, type='entity'), + smart_proxy=dict(required=False, type='entity'), + setup_insights=dict(required=False, type='bool'), + setup_remote_execution=dict(required=False, type='bool'), + jwt_expiration=dict(required=False, type='int'), + insecure=dict(required=False, type='bool'), + packages=dict(required=False, type='str'), + update_packages=dict(required=False, type='bool'), + repo=dict(required=False, type='str'), + repo_gpg_key_url=dict(required=False, type='str', no_log=False), + remote_execution_interface=dict(required=False, type='str'), + setup_remote_execution_pull=dict(required=False, type='bool'), + activation_keys=dict(required=False, type='list', elements='str', no_log=False), + lifecycle_environment=dict(required=False, type='entity'), + force=dict(required=False, type='bool'), + ignore_subman_errors=dict(required=False, type='bool'), + organization=dict(type='entity'), + location=dict(type='entity'), + ), + required_plugins=[ + ('katello', ['activation_key', 'activation_keys', 'lifecycle_environment', 'ignore_subman_errors', 'force']), + ('remote_execution', ['remote_execution_interface', 'setup_remote_execution_pull']), + ], + ) + + with module.api_connection(): + module.auto_lookup_entities() + if not module.check_mode: + command = module.ensure_entity('registration_commands', module.foreman_params, None, state='present')['registration_command'] + else: + command = "curl | bash" + module.exit_json(registration_command=command) + + +if __name__ == '__main__': + main() diff --git a/tests/fixtures/apidoc/registration_command.json b/tests/fixtures/apidoc/registration_command.json new file mode 120000 index 0000000000..f9e401512c --- /dev/null +++ b/tests/fixtures/apidoc/registration_command.json @@ -0,0 +1 @@ +foreman.json \ No newline at end of file diff --git a/tests/test_playbooks/fixtures/registration_command-0.yml b/tests/test_playbooks/fixtures/registration_command-0.yml new file mode 100644 index 0000000000..9058bb86d1 --- /dev/null +++ b/tests/test_playbooks/fixtures/registration_command-0.yml @@ -0,0 +1,233 @@ +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: https://foreman.example.org/api/status + response: + body: + string: '{"result":"ok","status":200,"version":"3.9.0-develop","api_version":2}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - Keep-Alive + Content-Length: + - '70' + 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.9.0-develop + Keep-Alive: + - timeout=15, max=100 + Strict-Transport-Security: + - max-age=631139040; includeSubdomains + 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: https://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\":\"2023-11-16 + 08:49:34 UTC\",\"updated_at\":\"2023-11-16 08:49:34 UTC\",\"id\":4,\"name\":\"Test + Organization\",\"title\":\"Test Organization\",\"description\":\"A test organization\"}]\n}\n" + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - Keep-Alive + Content-Length: + - '412' + 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.9.0-develop + Keep-Alive: + - timeout=15, max=99 + Strict-Transport-Security: + - max-age=631139040; includeSubdomains + 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: https://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\":\"2023-11-16 + 08:49:33 UTC\",\"updated_at\":\"2023-11-16 08:49:33 UTC\",\"id\":3,\"name\":\"Test + Location\",\"title\":\"Test Location\",\"description\":null}]\n}\n" + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - Keep-Alive + Content-Length: + - '384' + 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.9.0-develop + Keep-Alive: + - timeout=15, max=98 + Strict-Transport-Security: + - max-age=631139040; includeSubdomains + 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}}' + headers: + Accept: + - application/json;version=2 + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '106' + Content-Type: + - application/json + User-Agent: + - apypie (https://github.com/Apipie/apypie) + method: POST + uri: https://foreman.example.org/api/registration_commands + response: + body: + string: '{"registration_command":"curl -sS ''https://centos8-stream-foreman-nightly.tanso.example.com/register?location_id=3\u0026organization_id=4'' + -H ''Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0LCJpYXQiOjE3MDAxMjQ1NzUsImp0aSI6ImFkYWI4Yjk3ODM2YjE3MDMyODkzZDFhZjdhZjVmZWU3NWQ5ODc4OWRjNThjMzY1NmRhYTdkMTZkY2Y2ODdjYjAiLCJleHAiOjE3MDAxMzg5NzUsInNjb3BlIjoicmVnaXN0cmF0aW9uI2dsb2JhbCByZWdpc3RyYXRpb24jaG9zdCJ9.Yhj65zPC-9D91P5SdBxKThVsskRyq5sGCAAGdG4fgis'' + | bash"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - Keep-Alive + Content-Length: + - '465' + 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.9.0-develop + Keep-Alive: + - timeout=15, max=97 + Strict-Transport-Security: + - max-age=631139040; includeSubdomains + 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 diff --git a/tests/test_playbooks/registration_command.yml b/tests/test_playbooks/registration_command.yml new file mode 100644 index 0000000000..f162988814 --- /dev/null +++ b/tests/test_playbooks/registration_command.yml @@ -0,0 +1,39 @@ +--- +- hosts: localhost + collections: + - theforeman.foreman + 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: + - include_tasks: tasks/registration_command.yml + +- hosts: localhost + collections: + - theforeman.foreman + gather_facts: false + vars_files: + - vars/server.yml + tasks: + - include_tasks: tasks/location.yml + vars: + location_state: "absent" + - include_tasks: tasks/organization.yml + vars: + organization_state: "absent" diff --git a/tests/test_playbooks/tasks/registration_command.yml b/tests/test_playbooks/tasks/registration_command.yml new file mode 100644 index 0000000000..e8bb2a618c --- /dev/null +++ b/tests/test_playbooks/tasks/registration_command.yml @@ -0,0 +1,18 @@ +--- +- name: "Generate Registration Command" + vars: + rc_organization: Test Organization + rc_location: Test Location + registration_command: + username: "{{ foreman_username }}" + password: "{{ foreman_password }}" + server_url: "{{ foreman_server_url }}" + validate_certs: "{{ foreman_validate_certs }}" + organization: "{{ rc_organization }}" + location: "{{ rc_location }}" + register: result +- assert: + fail_msg: "Ensuring registering command is generated failed!" + that: + - "'curl' in result.registration_command" +...