diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
commit | a453ac31f3428614cceb99027f8efbdb9258a40b (patch) | |
tree | f61f87408f32a8511cbd91799f9cececb53e0374 /collections-debian-merged/ansible_collections/theforeman | |
parent | Initial commit. (diff) | |
download | ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.tar.xz ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.zip |
Adding upstream version 2.10.7+merged+base+2.10.8+dfsg.upstream/2.10.7+merged+base+2.10.8+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'collections-debian-merged/ansible_collections/theforeman')
78 files changed, 17473 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/CHANGELOG.rst b/collections-debian-merged/ansible_collections/theforeman/foreman/CHANGELOG.rst new file mode 100644 index 00000000..1944ba62 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/CHANGELOG.rst @@ -0,0 +1,220 @@ +================================ +theforeman.foreman Release Notes +================================ + +.. contents:: Topics + +This changelog describes changes after version 0.8.1. + +v1.5.1 +====== + +Bugfixes +-------- + +- content_view_version - make the ``version`` parameter not fail when the version was entered without a minor part (https://github.com/theforeman/foreman-ansible-modules/issues/1087) +- host - fix subnet/domain assignment when multiple interfaces are defined (https://github.com/theforeman/foreman-ansible-modules/issues/1095) + +v1.5.0 +====== + +Minor Changes +------------- + +- content_upload - use ``to_native`` to decode RPM headers if needed (RPM 4.15+ returns strings) +- content_view_version - provide examples how to obtain detailed information about content view versions (https://bugzilla.redhat.com/show_bug.cgi?id=1868145) +- content_view_version_cleanup - new role for cleaning up unused content view versions (https://github.com/theforeman/foreman-ansible-modules/issues/497) +- host - allow management of interfaces (https://github.com/theforeman/foreman-ansible-modules/issues/757) +- inventory plugin - add support for the Report API present in Foreman 1.24 and later +- inventory plugin - allow to compose the ``inventory_hostname`` (https://github.com/theforeman/foreman-ansible-modules/issues/1070) +- manifest - new role for easier handling of subscription manifest workflows +- subnet - add new ``externalipam_group`` parameter +- update vendored ``apypie`` to 0.3.2 + +Bugfixes +-------- + +- content_upload - Fix upload of files bigger than 2MB in Pulp3-based setups (https://github.com/theforeman/foreman-ansible-modules/issues/1043) +- job_invocation - properly submit ``ssh``, ``recurrence``, ``scheduling`` and ``concurrency_control`` to the server +- repository - don't emit a false warning about ``organization_id`` not being supported by the server (https://github.com/theforeman/foreman-ansible-modules/issues/1055) +- repository_set, repository - clarify documentation which module should be used for Red Hat Repositories (https://github.com/theforeman/foreman-ansible-modules/issues/1059) + +v1.4.0 +====== + +Minor Changes +------------- + +- global_parameter - allow to set hidden flag (https://github.com/theforeman/foreman-ansible-modules/issues/1024) +- job_template - stricter validation of ``template_inputs`` sub-options +- redhat_manifest - allow configuring content access mode (https://github.com/theforeman/foreman-ansible-modules/issues/820) +- subnet - verify the server has the ``remote_execution`` plugin when specifying ``remote_execution_proxies`` +- the ``apypie`` library is vendored inside the collection, so users only have to install ``requests`` manually now. + +Bugfixes +-------- + +- Don't try to update an entity, if only parameters that aren't supported by the server are detected as changed. (https://github.com/theforeman/foreman-ansible-modules/issues/975) +- allow to pass an empty string when refering to entities, thus unsetting the value (https://github.com/theforeman/foreman-ansible-modules/issues/969) +- compute_profile - don't fail when trying to update compute attributes of a profile (https://github.com/theforeman/foreman-ansible-modules/issues/997) +- host, hostgroup - support ``None`` as the ``pxe_loader`` (https://github.com/theforeman/foreman-ansible-modules/issues/971) +- job_template - don't fail when trying to update template_inputs +- os_default_template - document possible template kind choices (https://bugzilla.redhat.com/show_bug.cgi?id=1889952) +- smart_class_parameters - don't fail when trying to update override_values + +New Modules +----------- + +- theforeman.foreman.job_invocation - Invoke Remote Execution Jobs +- theforeman.foreman.smart_proxy - Manage Smart Proxies + +v1.3.0 +====== + +Minor Changes +------------- + +- external_usergroup - rename the ``auth_source_ldap`` parameter to ``auth_source`` (``auth_source_ldap`` is still supported via an alias) +- server URL and credentials can now also be specified using environment variables (https://github.com/theforeman/foreman-ansible-modules/issues/837) +- subnet - add support for external IPAM (https://github.com/theforeman/foreman-ansible-modules/issues/966) + +Bugfixes +-------- + +- content_view - remove CVs from lifecycle environments before deleting them (https://bugzilla.redhat.com/show_bug.cgi?id=1875314) +- external_usergroup - support non-LDAP external groups (https://github.com/theforeman/foreman-ansible-modules/issues/956) +- host - properly scope image lookups by the compute resource (https://bugzilla.redhat.com/show_bug.cgi?id=1878693) +- inventory plugin - include empty parent groups in the inventory (https://github.com/theforeman/foreman-ansible-modules/issues/919) + +New Modules +----------- + +- theforeman.foreman.status_info - Get status info + +v1.2.0 +====== + +Minor Changes +------------- + +- compute_resource - added ``caching_enabled`` option for VMware compute resources +- domain, host, hostgroup, operatingsystem, subnet - manage parameters in a single API call (https://bugzilla.redhat.com/show_bug.cgi?id=1855008) +- host - add ``compute_attributes`` parameter to module (https://bugzilla.redhat.com/show_bug.cgi?id=1871815) +- provisioning_template - update list of possible template kinds (https://bugzilla.redhat.com/show_bug.cgi?id=1871978) +- repository - update supported parameters (https://github.com/theforeman/foreman-ansible-modules/issues/935) + +Bugfixes +-------- + +- image - fix quoting of search values (https://github.com/theforeman/foreman-ansible-modules/issues/927) + +v1.1.0 +====== + +Minor Changes +------------- + +- activation_key - add ``description`` parameter (https://github.com/theforeman/foreman-ansible-modules/issues/915) +- callback plugin - add reporter to report logs sent to Foreman (https://github.com/theforeman/foreman-ansible-modules/issues/836) +- document return values of modules (https://github.com/theforeman/foreman-ansible-modules/pull/901) +- inventory plugin - allow to control batch size when pulling hosts from Foreman (https://github.com/theforeman/foreman-ansible-modules/pull/865) +- subnet - Require mask/cidr only on ipv4 (https://github.com/theforeman/foreman-ansible-modules/issues/878) + +Bugfixes +-------- + +- inventory plugin - fix want_params handling (https://github.com/theforeman/foreman-ansible-modules/issues/847) + +New Modules +----------- + +- theforeman.foreman.http_proxy - Manage HTTP Proxies + +v1.0.1 +====== + +Release Summary +--------------- + +Documentation fixes to reflect the correct module names. + + +v1.0.0 +====== + +Release Summary +--------------- + +This is the first stable release of the ``theforeman.foreman`` collection. + + +Breaking Changes / Porting Guide +-------------------------------- + +- All modules were renamed to drop the ``foreman_`` and ``katello_`` prefixes. + Additionally to the prefix removal, the following modules were further ranamed: + + * katello_upload to content_upload + * katello_sync to repository_sync + * katello_manifest to subscription_manifest + * foreman_search_facts to resource_info + * foreman_ptable to partition_table + * foreman_model to hardware_model + * foreman_environment to puppet_environment + +New Modules +----------- + +- theforeman.foreman.activation_key - Manage Activation Keys +- theforeman.foreman.architecture - Manage Architectures +- theforeman.foreman.auth_source_ldap - Manage LDAP Authentication Sources +- theforeman.foreman.bookmark - Manage Bookmarks +- theforeman.foreman.compute_attribute - Manage Compute Attributes +- theforeman.foreman.compute_profile - Manage Compute Profiles +- theforeman.foreman.compute_resource - Manage Compute Resources +- theforeman.foreman.config_group - Manage (Puppet) Config Groups +- theforeman.foreman.content_credential - Manage Content Credentials +- theforeman.foreman.content_upload - Upload content to a repository +- theforeman.foreman.content_view - Manage Content Views +- theforeman.foreman.content_view_filter - Manage Content View Filters +- theforeman.foreman.content_view_version - Manage Content View Versions +- theforeman.foreman.domain - Manage Domains +- theforeman.foreman.external_usergroup - Manage External User Groups +- theforeman.foreman.global_parameter - Manage Global Parameters +- theforeman.foreman.hardware_model - Manage Hardware Models +- theforeman.foreman.host - Manage Hosts +- theforeman.foreman.host_collection - Manage Host Collections +- theforeman.foreman.host_power - Manage Power State of Hosts +- theforeman.foreman.hostgroup - Manage Hostgroups +- theforeman.foreman.image - Manage Images +- theforeman.foreman.installation_medium - Manage Installation Media +- theforeman.foreman.job_template - Manage Job Templates +- theforeman.foreman.lifecycle_environment - Manage Lifecycle Environments +- theforeman.foreman.location - Manage Locations +- theforeman.foreman.operatingsystem - Manage Operating Systems +- theforeman.foreman.organization - Manage Organizations +- theforeman.foreman.os_default_template - Manage Default Template Associations To Operating Systems +- theforeman.foreman.partition_table - Manage Partition Table Templates +- theforeman.foreman.product - Manage Products +- theforeman.foreman.provisioning_template - Manage Provisioning Templates +- theforeman.foreman.puppet_environment - Manage Puppet Environments +- theforeman.foreman.realm - Manage Realms +- theforeman.foreman.redhat_manifest - Interact with a Red Hat Satellite Subscription Manifest +- theforeman.foreman.repository - Manage Repositories +- theforeman.foreman.repository_set - Enable/disable Repositories in Repository Sets +- theforeman.foreman.repository_sync - Sync a Repository or Product +- theforeman.foreman.resource_info - Gather information about resources +- theforeman.foreman.role - Manage Roles +- theforeman.foreman.scap_content - Manage SCAP content +- theforeman.foreman.scap_tailoring_file - Manage SCAP Tailoring Files +- theforeman.foreman.scc_account - Manage SUSE Customer Center Accounts +- theforeman.foreman.scc_product - Subscribe SUSE Customer Center Account Products +- theforeman.foreman.setting - Manage Settings +- theforeman.foreman.smart_class_parameter - Manage Smart Class Parameters +- theforeman.foreman.snapshot - Manage Snapshots +- theforeman.foreman.subnet - Manage Subnets +- theforeman.foreman.subscription_manifest - Manage Subscription Manifests +- theforeman.foreman.sync_plan - Manage Sync Plans +- theforeman.foreman.templates_import - Sync Templates from a repository +- theforeman.foreman.user - Manage Users +- theforeman.foreman.usergroup - Manage User Groups diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/FILES.json b/collections-debian-merged/ansible_collections/theforeman/foreman/FILES.json new file mode 100644 index 00000000..dcf07db2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/FILES.json @@ -0,0 +1,649 @@ +{ + "files": [ + { + "name": ".", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "LICENSE", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ceb4b9ee5adedde47b31e975c1d90c73ad27b6b165a1dcd80c7c545eb65b903", + "format": 1 + }, + { + "name": "meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "meta/runtime.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "251cab18ad8cc5f080343fc75f5d09ebf715302f0bfd85eeea6188c652c375fa", + "format": 1 + }, + { + "name": "changelogs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/changelog.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b8bb9a94da3173ea258a95c46c38b61355ca15785da9ba5aba6a57d4211f5b38", + "format": 1 + }, + { + "name": "CHANGELOG.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b3116be9e0fd1371e0b680b7758b0750c82eb8bada695d11bc972c187179b870", + "format": 1 + }, + { + "name": "roles", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/content_view_version_cleanup", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/content_view_version_cleanup/README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "25a8689391665716a475abf28c928d5c8105231f75eaedd2aaf3028a474f0085", + "format": 1 + }, + { + "name": "roles/content_view_version_cleanup/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/content_view_version_cleanup/tasks/delete_cv_versions.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c9396f2f2cf619098640ae68fcc77f50a7a67b51d773d73a76cbab5b1ea1d29e", + "format": 1 + }, + { + "name": "roles/content_view_version_cleanup/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8d620f94d76b1a1f09f990f02fddd2ab715520b13b897a475b2fc7dfed5fbef7", + "format": 1 + }, + { + "name": "roles/content_view_version_cleanup/tasks/find_and_delete_unused_cv_versions.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6708c713d86f45e867dba4ac16f69dc0b99b1d4345ca67621d238b4be81f2c19", + "format": 1 + }, + { + "name": "roles/manifest", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/manifest/README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0ad87a0e14dfc1f81f5c4487e61091d3c9fb20a9a37143956a18250217cf4fa5", + "format": 1 + }, + { + "name": "roles/manifest/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/manifest/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "566b63374c1314708b7cbc30df0f3f692e8c5e5b26f84272103bf3bdaa801b22", + "format": 1 + }, + { + "name": "roles/manifest/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "roles/manifest/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "edd78b64b342c2c352fcbbca78afc5fcb3c6a2e8f2888e6dba1fd6a62609e2a1", + "format": 1 + }, + { + "name": "README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "15a626543432f50f1ea52603085037a239dcf037fd0cf86df5934d3d200ac3dc", + "format": 1 + }, + { + "name": "requirements.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "876da4d933e6561c29737e2b9fa47f2df0b309c3ee1dba11d400ba443630cf2f", + "format": 1 + }, + { + "name": "plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/foreman_helper.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "90a0cf17d3053c46b033ee83867af5e4a6599006610464c8d69bd5c96a2ffb5d", + "format": 1 + }, + { + "name": "plugins/module_utils/_apypie.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "64b1fb65d27a25332af56815aeba02226dc9b663c09aeb303b543e5dcc1510c5", + "format": 1 + }, + { + "name": "plugins/doc_fragments", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/doc_fragments/foreman.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40264f3f6b0b5c6f0946a1d9f031783aacd6a38c5ae795b3b0a2a484a5da7cd3", + "format": 1 + }, + { + "name": "plugins/inventory", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/inventory/foreman.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5f1e5dd54d965de2ccfe89b5ecd0f51220a9418f16a932c0b8be7b57c2cfec17", + "format": 1 + }, + { + "name": "plugins/filter", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/filter/foreman.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0f7c635e75c475f67afd1a19caecee672466d75057f40e91100a7c243beb1e45", + "format": 1 + }, + { + "name": "plugins/callback", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/callback/foreman.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "269bd44bec22d9d7d77fec84ebb3739a3f669e2b78140987b547cbb0c5b325a0", + "format": 1 + }, + { + "name": "plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/modules/os_default_template.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "02070e18599495b8ba97aec4f51e08564648065ddee5fd3c42fdaf1660e9dab8", + "format": 1 + }, + { + "name": "plugins/modules/host_power.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c3a1e191165d94b708546db6a3b0a5a38362d757158da64fe72406b5e151085e", + "format": 1 + }, + { + "name": "plugins/modules/domain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6643d6b0fabfcfea13112df106a494e9d97f4989b0cad629b828b3373d65642b", + "format": 1 + }, + { + "name": "plugins/modules/partition_table.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "36dafbc1ad3fbc567c66da0c774f0f36c26bfbdf4ecfb1482d6bad43f6ea77d6", + "format": 1 + }, + { + "name": "plugins/modules/product.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "be17c4b4e3999b12362193e01a05b76307800f873fa76eaf9b8638e60b2cd236", + "format": 1 + }, + { + "name": "plugins/modules/location.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0c2a4b67562c308ef4597d62d4043c53ce16c0179007dc1ad5c534e43abb3b20", + "format": 1 + }, + { + "name": "plugins/modules/host_collection.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3394c4154396ac4912c89afed2797e66970d70fed584e4f1849551a44d130f5b", + "format": 1 + }, + { + "name": "plugins/modules/job_invocation.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a25ab0d2378891b05b8ebbd78334d95880cff65d666d9da7486a774b49302c24", + "format": 1 + }, + { + "name": "plugins/modules/snapshot.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3ef069505f9b162049a7ec6beac1f88c26c83d6ec84da8248e01007e15a24131", + "format": 1 + }, + { + "name": "plugins/modules/subnet.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "311f19300ce9dfd59a79e6f15ffa7c712b243e79e40f525b8fb0c3615e952e3c", + "format": 1 + }, + { + "name": "plugins/modules/realm.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e5087b0c705cf6a608aecea02e5ea0dee9b9c2e139b755199b488898eea13abc", + "format": 1 + }, + { + "name": "plugins/modules/repository_sync.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "efd3b5b4b949f646a13d365c8573d72a31bf3af29c03c11d4be9d0479eb473f3", + "format": 1 + }, + { + "name": "plugins/modules/status_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b40d8c6a67c098fefcc47b37add57e107033205b56cd5a0cc53fcbdce8bd74b5", + "format": 1 + }, + { + "name": "plugins/modules/content_view.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6edce4c618db46ffbd2f2fda8591dd1d56f522b654b31564cd15458b9a94eaae", + "format": 1 + }, + { + "name": "plugins/modules/setting.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "41bed9eae667d8249ca8b23c44f790b877a9b4aad75a1bb40c60fe6da23ff942", + "format": 1 + }, + { + "name": "plugins/modules/puppet_environment.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d0dc12829c3bacfefc8a85d43c875c912058cb422742d0f2dcab064d8c9a5557", + "format": 1 + }, + { + "name": "plugins/modules/role.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "00414395f4d86fc05078a56801435087334719ce25d8e4f1676ffd0a31cd1ef0", + "format": 1 + }, + { + "name": "plugins/modules/resource_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b25cba164ae06d8ffc8efc48e8fd2eaff136f02ad8650799eb45f5953f1a160", + "format": 1 + }, + { + "name": "plugins/modules/compute_attribute.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "251360fdd219f87cd4947fe4d4f6e5600bfa8082ba31399451bd84d60406298e", + "format": 1 + }, + { + "name": "plugins/modules/installation_medium.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "508344224fd3d2702b919f75ef779b1f8366c72899c39823217bf06ed4c53b3a", + "format": 1 + }, + { + "name": "plugins/modules/auth_source_ldap.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3930456eccd979fd7e8e5aac71b4443488d3cacf9b6e5083028ac26ea30f2bd7", + "format": 1 + }, + { + "name": "plugins/modules/config_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3da0833b6177a7a9b6c4c8a84db5a7209ea3c586a9e7b86c0a592a447108079e", + "format": 1 + }, + { + "name": "plugins/modules/hardware_model.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "db598a957fa07c32cb6ea465f79444a5d103de4c16b4eda575e2981359fc02d9", + "format": 1 + }, + { + "name": "plugins/modules/lifecycle_environment.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f2c67c928966f90f6893b7c29effe82a97a462191449e74bd84999e05bc40378", + "format": 1 + }, + { + "name": "plugins/modules/scap_content.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ccb415652f59e34cf1f9fe4a4b0844c0d0503fb655239ed133f52c84dbda9ce3", + "format": 1 + }, + { + "name": "plugins/modules/sync_plan.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d7a5ca9d2e951ee698f8f84ee1f4f24fec2afc3eabc31b3af97856d0f13797b8", + "format": 1 + }, + { + "name": "plugins/modules/scc_product.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0329cbdb46ad9f8248c7a0cc88149a253dc385bde41c003ed5565b173a46d646", + "format": 1 + }, + { + "name": "plugins/modules/usergroup.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6a9d6df5f428d2a0865205ae22720901f14d28d4037a6f8f80ab7c228350e67d", + "format": 1 + }, + { + "name": "plugins/modules/job_template.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "783c4d284880ba70fa72aade7492b29012e38cc8d331b119c1186fd0b2483cc3", + "format": 1 + }, + { + "name": "plugins/modules/subscription_manifest.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "451c758d5bc07be5d369348054a4aac9e70994ec502d3080eaee5ec5392db554", + "format": 1 + }, + { + "name": "plugins/modules/content_view_version.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2ed93cc365852f53011ea7bb2a7950f2534271c202cdf149f7318967c95a97b7", + "format": 1 + }, + { + "name": "plugins/modules/compute_resource.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "115e0b156f1b954f939dd343b375b94b8020faeb1b638fbd84577a4c522bacf4", + "format": 1 + }, + { + "name": "plugins/modules/host.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af93f542925d6544a782ebfa4199ce5217aa2c7a2b453d80e4716afb2ef7a488", + "format": 1 + }, + { + "name": "plugins/modules/user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "097dd0e9d695f71c0c3dca0852e2a8de907a06a800e0241cc10631d59a1b5f83", + "format": 1 + }, + { + "name": "plugins/modules/image.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0ca38bc9900d5075c8fd3cae1004c81e75f6717bb9c0f05390703c920bc8a5d0", + "format": 1 + }, + { + "name": "plugins/modules/repository_set.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e8468f2a452fe545438ddbbc3103399c36fbe1f687b3a4bd5edbce64b1b49104", + "format": 1 + }, + { + "name": "plugins/modules/architecture.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "26759610a81cb370d14544f34b5743a3d192f281da41ce0b44c4d9b43adc9570", + "format": 1 + }, + { + "name": "plugins/modules/organization.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "99d085b356e2eae9d0d3bb93702197098c5a3cc6c697fdd09576c305211154e7", + "format": 1 + }, + { + "name": "plugins/modules/compute_profile.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7eda6e801abb5a2df1c1c4bb85779faba8352fb4dd713a402d839cb2ee8f8723", + "format": 1 + }, + { + "name": "plugins/modules/repository.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "aff676227d5a752c2295dc449bd13bbd4c1d2c66f122037af6d14bd59b653689", + "format": 1 + }, + { + "name": "plugins/modules/scap_tailoring_file.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8e0a89ec41681f48f104cccac1a22caf9f9267274ed99b1a441638718a445ae2", + "format": 1 + }, + { + "name": "plugins/modules/external_usergroup.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "83ea8c11105802fa62274f6fbafdebda111137fedbc646f221fc8eb59bdb1bda", + "format": 1 + }, + { + "name": "plugins/modules/content_upload.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "49c5ef52213e410b9fa0c1f52e986480700d2463497d90bc83ad6c71cc6bd7bf", + "format": 1 + }, + { + "name": "plugins/modules/activation_key.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4c5d2f72bfe3ca74257f49ca48a097a5561f933e01909bbaf209204b97a51bd8", + "format": 1 + }, + { + "name": "plugins/modules/smart_proxy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1ded286f6fdb92804ea50a0c96c2660dcb784df72fff8bc310bee62bce3087ca", + "format": 1 + }, + { + "name": "plugins/modules/templates_import.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "29387dd51b003d83a04c5f86b799d4f7bf637db00b424acd76320caba52278b6", + "format": 1 + }, + { + "name": "plugins/modules/redhat_manifest.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "22014a38ec66bc54b0264790e564f04c01f50754aa35b91d07d7b922b71ab21c", + "format": 1 + }, + { + "name": "plugins/modules/bookmark.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "76c52fe346b5b20ef4997b3f816d2b996c1c5a4f8cee7f73cb90c97fe0adc820", + "format": 1 + }, + { + "name": "plugins/modules/content_credential.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c74ae794aeaa5f4c7dee41580dbc03566ac62948a99fec7c5591d45fade1f6ca", + "format": 1 + }, + { + "name": "plugins/modules/provisioning_template.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "21454ed2a31e17e3a25c5ce84239efac76aa5d1997142c0e4aa09f74a675d86c", + "format": 1 + }, + { + "name": "plugins/modules/content_view_filter.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "933805fc3f2c62b1ef320a956c38d1386d04fdede979f8a46a50da29dbe5306f", + "format": 1 + }, + { + "name": "plugins/modules/smart_class_parameter.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "79880750644d397c1ee6aaf996c27bcabc15e85273215a0f1b6820eb2d7b6c78", + "format": 1 + }, + { + "name": "plugins/modules/http_proxy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "582b6fc66821f769e7040bc38ac4d2a064112d5f2c0e9a9b69740ffd1104c983", + "format": 1 + }, + { + "name": "plugins/modules/scc_account.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6f14b325fad9499fada3a2021493097e29cda439af8ba62ca3fadaf43d9d575d", + "format": 1 + }, + { + "name": "plugins/modules/operatingsystem.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "496ac64dd4716d50ff8905a6a5e6cdccbc0ca12c4d04b9c87be51f9336cdda65", + "format": 1 + }, + { + "name": "plugins/modules/hostgroup.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3203f34537f2da5e1788aa93dda1e65ef52183d7c7eea9229b03b82605001b44", + "format": 1 + }, + { + "name": "plugins/modules/global_parameter.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0dc94762c660dc3773b51171d332a8cecef448e2f07868828ba532b4d8922782", + "format": 1 + } + ], + "format": 1 +}
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/LICENSE b/collections-debian-merged/ansible_collections/theforeman/foreman/LICENSE new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/MANIFEST.json b/collections-debian-merged/ansible_collections/theforeman/foreman/MANIFEST.json new file mode 100644 index 00000000..490579fa --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/MANIFEST.json @@ -0,0 +1,92 @@ +{ + "collection_info": { + "namespace": "theforeman", + "name": "foreman", + "version": "1.5.1", + "authors": [ + "AlanCoding <arominge@redhat.com>", + "Andrew Kofink <ajkofink@gmail.com>", + "Anthony Green <green@moxielogic.com>", + "Anton <nesanton@gmail.com>", + "Baptiste Agasse <baptiste.agasse@gmail.com>", + "Bernhard Hopfenm\u00fcller <hopfenmueller@atix.de>", + "Bernhard Suttner <sbernhard@users.noreply.github.com>", + "Bryan Kearney <bkearney@redhat.com>", + "Chris Forkner <gen2fish@gmail.com>", + "Chris Snell <chsnell@users.noreply.github.com>", + "Christoffer Reijer <ephracis@gmail.com>", + "Deric Crago <deric.crago@gmail.com>", + "Eric D. Helms <ericdhelms@gmail.com>", + "Ethan <smithe2413@gmail.com>", + "Evgeni Golov <evgeni@golov.de>", + "Ewoud Kohl van Wijngaarden <ewoud@kohlvanwijngaarden.nl>", + "Greg Swift <gregswift@gmail.com>", + "Ismael Puerto <ismaelpuerto@users.noreply.github.com>", + "Jameer Pathan <jpathan@redhat.com>", + "James Stuart <james@stuart.name>", + "Jeffrey van Pelt <jeff@vanpelt.one>", + "Jeremy Albinet <jalbinet@scaleway.com>", + "John Berninger <john.berninger@gmail.com>", + "Josh Swanson <jswanson@redhat.com>", + "Kirill Shirinkin <fodojyko@gmail.com>", + "Lester Claudio <claudiol@redhat.com>", + "Manisha Singhal <manisha1595@gmail.com>", + "Manuel Bonk <githubMB@bonkii.com>", + "Marcelo Moreira de Mello <tchello.mello@gmail.com>", + "Marek Czernek <mczernek@redhat.com>", + "Mark Hlawatschek <hlawatschek@atix.de>", + "Markus Bucher <bucher@atix.de>", + "Matthias Dellweg <2500@gmx.de>", + "Nikhil Jain <jainnikhil30@gmail.com>", + "Olivier <oliverf1ca@yahoo.com>", + "Ondrej Prazak <oprazak@redhat.com>", + "Ond\u0159ej Ezr <oezr@redhat.com>", + "Patrick Creech <pcreech@redhat.com>", + "Paul Armstrong <parmstro@users.noreply.github.com>", + "Paul Gration <pmgration@gmail.com>", + "Peter Ondrejka <pondrejk@redhat.com>", + "Philipp <philipp98.joos@gmail.com>", + "Quirin Pamp <Q.Pamp.2009@my.bristol.ac.uk>", + "Richard Stempfl <richielatk@gmail.com>", + "Sam <samcalvert@me.com>", + "Samir Jha <sjha4@ncsu.edu>", + "Sean O'Keeffe <seanokeeffe797@gmail.com>", + "Stoned Elipot <stoned.elipot@gmail.com>", + "TTherouanne <thomas@therouanne.com>", + "William Bradford Clark <wclark@redhat.com>", + "achevalet <anthony.chevalet@gmail.com>", + "calvingsmith <4283930+calvingsmith@users.noreply.github.com>", + "furhouse <furhouse@users.noreply.github.com>", + "jerrejkw <43955357+jerrejkw@users.noreply.github.com>", + "marco <marco.markgraf@transporeon.com>", + "metalcated <mike.gomon@gmail.com>", + "russianguppie <46544650+russianguppie@users.noreply.github.com>", + "willtome <willtome@gmail.com>" + ], + "readme": "README.md", + "tags": [ + "foreman", + "katello", + "satellite", + "orcharhino" + ], + "description": "Ansible Modules to manage Foreman and Katello installations", + "license": [ + "GPL-3.0-or-later" + ], + "license_file": null, + "dependencies": {}, + "repository": "https://github.com/theforeman/foreman-ansible-modules", + "documentation": "https://theforeman.org/plugins/foreman-ansible-modules/", + "homepage": "https://theforeman.org/", + "issues": "https://github.com/theforeman/foreman-ansible-modules/issues" + }, + "file_manifest_file": { + "name": "FILES.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "63c9a888dacd545b228bf68a7744c38b76a6c6dca41e7d3d93b81edbf10f4a45", + "format": 1 + }, + "format": 1 +}
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/README.md b/collections-debian-merged/ansible_collections/theforeman/foreman/README.md new file mode 100644 index 00000000..8a0f270c --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/README.md @@ -0,0 +1,93 @@ +# Foreman Ansible Modules ![Build Status](https://github.com/theforeman/foreman-ansible-modules/workflows/CI/badge.svg) + +Ansible modules for interacting with the Foreman API and various plugin APIs such as Katello. + +## Documentation + +A list of all modules and their documentation can be found at [theforeman.org/plugins/foreman-ansible-modules](https://theforeman.org/plugins/foreman-ansible-modules/). + +## Support + +### Supported Foreman and plugins versions + +Modules should support any currently stable Foreman release and the matching set of plugins. +Some modules have additional features/arguments that are only applied when the corresponding plugin is installed. + +We actively test the modules against the latest stable Foreman release and the matching set of plugins. + +### Supported Ansible Versions + +The supported Ansible versions are aligned with currently maintained Ansible versions that support Collections (2.8+). +You can find the list of maintained Ansible versions [here](https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html#release-status). + +### Supported Python Versions + +Starting with Ansible 2.7, Ansible only supports Python 2.7 and 3.5 (and higher). These are also the only Python versions we develop and test the modules against. + +### Known issues + +* Some modules, e.g. `repository_sync` and `content_view_version`, trigger long running tasks on the server side. It might be beneficial to your playbook to wait for their completion in an asynchronous manner. + As Ansible has facilities to do so, the modules will wait unconditionally. See the [Ansible documentation](https://docs.ansible.com/ansible/latest/user_guide/playbooks_async.html) for putting tasks in the background. + +* According to [Ansible documentation](https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html), using loop over Ansible resources can leak sensitive data. This applies to all modules, but especially those which require more secrets than the API credentials (`auth_source_ldap`, `compute_resource`, `host`, `hostgroup`, `http_proxy`, `image`, `repository`, `scc_account`, `user`). You can prevent this by using `no_log: yes` on the task. + + eg: + ```yaml + - name: Create compute resources + theforeman.foreman.compute_resource: + server_url: https://foreman.example.com + username: admin + password: changeme + validate_certs: yes + name: "{{ item.name }}" + organizations: "{{ item.organizations | default(omit) }}" + locations: "{{ item.locations | default(omit) }}" + description: "{{ item.description | default(omit) }}" + provider: "{{ item.provider }}" + provider_params: "{{ item.provider_params | default(omit) }}" + state: "{{ item.state | default('present') }}" + loop: "{{ compute_resources }}" + no_log: yes + ``` + +## Installation + +There are currently two ways to use the modules in your setup: install from Ansible Galaxy or via RPM. + +### Installation from Ansible Galaxy + +You can install the collection from [Ansible Galaxy](https://galaxy.ansible.com/theforeman/foreman) by running `ansible-galaxy collection install theforeman.foreman` (Ansible 2.9 and later) or `mazer install theforeman.foreman` (Ansible 2.8). + +After the installation, the modules are available as `theforeman.foreman.<module_name>`. Please see the [Using Ansible collections documentation](https://docs.ansible.com/ansible/devel/user_guide/collections_using.html) for further details. + +### Installation via RPM + +The collection is also available as `ansible-collection-theforeman-foreman` from the `client` repository on `yum.theforeman.org`. + +After installing the RPM, you can use the modules in the same way as when they are installed directly from Ansible Galaxy. + +## Dependencies + +These dependencies are required for the Ansible controller, not the Foreman server. + +* [`PyYAML`](https://pypi.org/project/PyYAML/) +* [`requests`](https://pypi.org/project/requests/) +* [`ipaddress`](https://pypi.org/project/ipaddress/) for the `subnet` module on Python 2.7 +* `rpm` for the RPM support in the `content_upload` module +* `debian` for the DEB support in the `content_upload` module + +# Foreman Ansible Roles + +Roles using the Foreman Ansible Modules to configure Foreman and its plugins. + +## Documentation + +For individual role documentation, check the README defined at `roles/rolename/README.md`. + +### Common Role Variables + +- `server_url`: URL of the Foreman server. +- `username`: Username accessing the Foreman server. +- `password`: Password of the user accessing the Foreman server. +- `validate_certs`: Whether or not to verify the TLS certificates of the Foreman server. +- `organization`: Organization where configuration will be applied. diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/changelogs/changelog.yaml b/collections-debian-merged/ansible_collections/theforeman/foreman/changelogs/changelog.yaml new file mode 100644 index 00000000..d03b1575 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/changelogs/changelog.yaml @@ -0,0 +1,340 @@ +ancestor: 0.8.1 +releases: + 1.0.0: + changes: + breaking_changes: + - | + All modules were renamed to drop the ``foreman_`` and ``katello_`` prefixes. + Additionally to the prefix removal, the following modules were further ranamed: + + * katello_upload to content_upload + * katello_sync to repository_sync + * katello_manifest to subscription_manifest + * foreman_search_facts to resource_info + * foreman_ptable to partition_table + * foreman_model to hardware_model + * foreman_environment to puppet_environment + release_summary: | + This is the first stable release of the ``theforeman.foreman`` collection. + modules: + - description: Manage Activation Keys + name: activation_key + namespace: '' + - description: Manage Architectures + name: architecture + namespace: '' + - description: Manage LDAP Authentication Sources + name: auth_source_ldap + namespace: '' + - description: Manage Bookmarks + name: bookmark + namespace: '' + - description: Manage Compute Attributes + name: compute_attribute + namespace: '' + - description: Manage Compute Profiles + name: compute_profile + namespace: '' + - description: Manage Compute Resources + name: compute_resource + namespace: '' + - description: Manage (Puppet) Config Groups + name: config_group + namespace: '' + - description: Manage Content Credentials + name: content_credential + namespace: '' + - description: Upload content to a repository + name: content_upload + namespace: '' + - description: Manage Content Views + name: content_view + namespace: '' + - description: Manage Content View Filters + name: content_view_filter + namespace: '' + - description: Manage Content View Versions + name: content_view_version + namespace: '' + - description: Manage Domains + name: domain + namespace: '' + - description: Manage External User Groups + name: external_usergroup + namespace: '' + - description: Manage Global Parameters + name: global_parameter + namespace: '' + - description: Manage Hardware Models + name: hardware_model + namespace: '' + - description: Manage Hosts + name: host + namespace: '' + - description: Manage Host Collections + name: host_collection + namespace: '' + - description: Manage Power State of Hosts + name: host_power + namespace: '' + - description: Manage Hostgroups + name: hostgroup + namespace: '' + - description: Manage Images + name: image + namespace: '' + - description: Manage Installation Media + name: installation_medium + namespace: '' + - description: Manage Job Templates + name: job_template + namespace: '' + - description: Manage Lifecycle Environments + name: lifecycle_environment + namespace: '' + - description: Manage Locations + name: location + namespace: '' + - description: Manage Operating Systems + name: operatingsystem + namespace: '' + - description: Manage Organizations + name: organization + namespace: '' + - description: Manage Default Template Associations To Operating Systems + name: os_default_template + namespace: '' + - description: Manage Partition Table Templates + name: partition_table + namespace: '' + - description: Manage Products + name: product + namespace: '' + - description: Manage Provisioning Templates + name: provisioning_template + namespace: '' + - description: Manage Puppet Environments + name: puppet_environment + namespace: '' + - description: Manage Realms + name: realm + namespace: '' + - description: Interact with a Red Hat Satellite Subscription Manifest + name: redhat_manifest + namespace: '' + - description: Manage Repositories + name: repository + namespace: '' + - description: Enable/disable Repositories in Repository Sets + name: repository_set + namespace: '' + - description: Sync a Repository or Product + name: repository_sync + namespace: '' + - description: Gather information about resources + name: resource_info + namespace: '' + - description: Manage Roles + name: role + namespace: '' + - description: Manage SCAP content + name: scap_content + namespace: '' + - description: Manage SCAP Tailoring Files + name: scap_tailoring_file + namespace: '' + - description: Manage SUSE Customer Center Accounts + name: scc_account + namespace: '' + - description: Subscribe SUSE Customer Center Account Products + name: scc_product + namespace: '' + - description: Manage Settings + name: setting + namespace: '' + - description: Manage Smart Class Parameters + name: smart_class_parameter + namespace: '' + - description: Manage Snapshots + name: snapshot + namespace: '' + - description: Manage Subnets + name: subnet + namespace: '' + - description: Manage Subscription Manifests + name: subscription_manifest + namespace: '' + - description: Manage Sync Plans + name: sync_plan + namespace: '' + - description: Sync Templates from a repository + name: templates_import + namespace: '' + - description: Manage Users + name: user + namespace: '' + - description: Manage User Groups + name: usergroup + namespace: '' + release_date: '2020-06-19' + 1.0.1: + changes: + release_summary: | + Documentation fixes to reflect the correct module names. + release_date: '2020-06-29' + 1.1.0: + changes: + bugfixes: + - inventory plugin - fix want_params handling (https://github.com/theforeman/foreman-ansible-modules/issues/847) + minor_changes: + - activation_key - add ``description`` parameter (https://github.com/theforeman/foreman-ansible-modules/issues/915) + - callback plugin - add reporter to report logs sent to Foreman (https://github.com/theforeman/foreman-ansible-modules/issues/836) + - document return values of modules (https://github.com/theforeman/foreman-ansible-modules/pull/901) + - inventory plugin - allow to control batch size when pulling hosts from Foreman + (https://github.com/theforeman/foreman-ansible-modules/pull/865) + - subnet - Require mask/cidr only on ipv4 (https://github.com/theforeman/foreman-ansible-modules/issues/878) + fragments: + - 836-add-reporter-to-callback.yaml + - 847-want_params-fix.yaml + - 865-limit-inventory-per-host.yaml + - 878-cidr-only-v4.yaml + - 901-document-return-values.yaml + - 915-activation_key-description.yaml + modules: + - description: Manage HTTP Proxies + name: http_proxy + namespace: '' + release_date: '2020-08-17' + 1.2.0: + changes: + bugfixes: + - image - fix quoting of search values (https://github.com/theforeman/foreman-ansible-modules/issues/927) + minor_changes: + - compute_resource - added ``caching_enabled`` option for VMware compute resources + - domain, host, hostgroup, operatingsystem, subnet - manage parameters in a + single API call (https://bugzilla.redhat.com/show_bug.cgi?id=1855008) + - host - add ``compute_attributes`` parameter to module (https://bugzilla.redhat.com/show_bug.cgi?id=1871815) + - provisioning_template - update list of possible template kinds (https://bugzilla.redhat.com/show_bug.cgi?id=1871978) + - repository - update supported parameters (https://github.com/theforeman/foreman-ansible-modules/issues/935) + fragments: + - 927-image-quoting.yaml + - 932-host-add-compute_attributes.yaml + - 935-repository-params.yaml + - 939-compute_resource-added_caching_enabled.yaml + - bz1855008-single_call_parameters.yaml + - bz1871978-template_kinds.yaml + release_date: '2020-09-03' + 1.3.0: + changes: + bugfixes: + - content_view - remove CVs from lifecycle environments before deleting them + (https://bugzilla.redhat.com/show_bug.cgi?id=1875314) + - external_usergroup - support non-LDAP external groups (https://github.com/theforeman/foreman-ansible-modules/issues/956) + - host - properly scope image lookups by the compute resource (https://bugzilla.redhat.com/show_bug.cgi?id=1878693) + - inventory plugin - include empty parent groups in the inventory (https://github.com/theforeman/foreman-ansible-modules/issues/919) + minor_changes: + - external_usergroup - rename the ``auth_source_ldap`` parameter to ``auth_source`` + (``auth_source_ldap`` is still supported via an alias) + - server URL and credentials can now also be specified using environment variables + (https://github.com/theforeman/foreman-ansible-modules/issues/837) + - subnet - add support for external IPAM (https://github.com/theforeman/foreman-ansible-modules/issues/966) + fragments: + - 837-env-fallback.yaml + - 919-include-empty-parent-groups.yml + - 956-external_usergroup-non-ldap.yaml + - 966-subnet-external_ipam.yaml + - bz1875314-content_view-remove.yaml + - bz1878693-scope_image_lookups.yaml + modules: + - description: Get status info + name: status_info + namespace: '' + release_date: '2020-09-22' + 1.4.0: + changes: + bugfixes: + - Don't try to update an entity, if only parameters that aren't supported by + the server are detected as changed. (https://github.com/theforeman/foreman-ansible-modules/issues/975) + - allow to pass an empty string when refering to entities, thus unsetting the + value (https://github.com/theforeman/foreman-ansible-modules/issues/969) + - compute_profile - don't fail when trying to update compute attributes of a + profile (https://github.com/theforeman/foreman-ansible-modules/issues/997) + - host, hostgroup - support ``None`` as the ``pxe_loader`` (https://github.com/theforeman/foreman-ansible-modules/issues/971) + - job_template - don't fail when trying to update template_inputs + - os_default_template - document possible template kind choices (https://bugzilla.redhat.com/show_bug.cgi?id=1889952) + - smart_class_parameters - don't fail when trying to update override_values + minor_changes: + - global_parameter - allow to set hidden flag (https://github.com/theforeman/foreman-ansible-modules/issues/1024) + - job_template - stricter validation of ``template_inputs`` sub-options + - redhat_manifest - allow configuring content access mode (https://github.com/theforeman/foreman-ansible-modules/issues/820) + - subnet - verify the server has the ``remote_execution`` plugin when specifying + ``remote_execution_proxies`` + - the ``apypie`` library is vendored inside the collection, so users only have + to install ``requests`` manually now. + fragments: + - 820-redhat_manifest-sca.yaml + - 969-allow-unset-entity.yml + - 971-pxe_loader-none.yaml + - 975-filter-update-payload.yml + - 997-compute_profile-update.yaml + - bz1889952-os_default_template-kind.yaml + - global_parameter-hidden_values.yaml + - job_template-template_inputs-validation.yaml + - rex-proxy-subnet-check.yaml + - venored_apypie.yaml + modules: + - description: Invoke Remote Execution Jobs + name: job_invocation + namespace: '' + - description: Manage Smart Proxies + name: smart_proxy + namespace: '' + release_date: '2020-10-21' + 1.5.0: + changes: + bugfixes: + - content_upload - Fix upload of files bigger than 2MB in Pulp3-based setups + (https://github.com/theforeman/foreman-ansible-modules/issues/1043) + - job_invocation - properly submit ``ssh``, ``recurrence``, ``scheduling`` and + ``concurrency_control`` to the server + - repository - don't emit a false warning about ``organization_id`` not being + supported by the server (https://github.com/theforeman/foreman-ansible-modules/issues/1055) + - repository_set, repository - clarify documentation which module should be + used for Red Hat Repositories (https://github.com/theforeman/foreman-ansible-modules/issues/1059) + minor_changes: + - content_upload - use ``to_native`` to decode RPM headers if needed (RPM 4.15+ + returns strings) + - content_view_version - provide examples how to obtain detailed information + about content view versions (https://bugzilla.redhat.com/show_bug.cgi?id=1868145) + - content_view_version_cleanup - new role for cleaning up unused content view + versions (https://github.com/theforeman/foreman-ansible-modules/issues/497) + - host - allow management of interfaces (https://github.com/theforeman/foreman-ansible-modules/issues/757) + - inventory plugin - add support for the Report API present in Foreman 1.24 + and later + - inventory plugin - allow to compose the ``inventory_hostname`` (https://github.com/theforeman/foreman-ansible-modules/issues/1070) + - manifest - new role for easier handling of subscription manifest workflows + - subnet - add new ``externalipam_group`` parameter + - update vendored ``apypie`` to 0.3.2 + fragments: + - 1043-chunked_content_upload.yml + - 1059-repository_set-docs.yml + - 1062-warnings.yml + - 1070-compose_inventory_hostname.yml + - 757-host_interfaces.yml + - bz1868145-cv_version_examples.yml + - content_upload-decode-old-rpm.yml + - inventory_plugin_report_api.yml + - subnet-externalipam_group.yml + - theforeman.foreman.content_view_version_cleanup_role.yml + - theforeman.foreman.manifest_role.yml + release_date: '2020-12-03' + 1.5.1: + changes: + bugfixes: + - content_view_version - make the ``version`` parameter not fail when the version + was entered without a minor part (https://github.com/theforeman/foreman-ansible-modules/issues/1087) + - host - fix subnet/domain assignment when multiple interfaces are defined (https://github.com/theforeman/foreman-ansible-modules/issues/1095) + fragments: + - 1087-content_view_version-safenet.yml + - 1095-nested_list-lookup-fix.yml + release_date: '2020-12-14' diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/meta/runtime.yml b/collections-debian-merged/ansible_collections/theforeman/foreman/meta/runtime.yml new file mode 100644 index 00000000..76292261 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/meta/runtime.yml @@ -0,0 +1,108 @@ +--- +requires_ansible: '>=2.8' +plugin_routing: + modules: + foreman_architecture: + redirect: architecture + foreman_auth_source_ldap: + redirect: auth_source_ldap + foreman_bookmark: + redirect: bookmark + foreman_compute_attribute: + redirect: compute_attribute + foreman_compute_profile: + redirect: compute_profile + foreman_compute_resource: + redirect: compute_resource + foreman_config_group: + redirect: config_group + foreman_domain: + redirect: domain + foreman_environment: + redirect: puppet_environment + foreman_external_usergroup: + redirect: external_usergroup + foreman_global_parameter: + redirect: global_parameter + foreman_hostgroup: + redirect: hostgroup + foreman_host_power: + redirect: host_power + foreman_host: + redirect: host + foreman_image: + redirect: image + foreman_installation_medium: + redirect: installation_medium + foreman_job_template: + redirect: job_template + foreman_location: + redirect: location + foreman_model: + redirect: hardware_model + foreman_operatingsystem: + redirect: operatingsystem + foreman_organization: + redirect: organization + foreman_os_default_template: + redirect: os_default_template + foreman_provisioning_template: + redirect: provisioning_template + foreman_ptable: + redirect: partition_table + foreman_realm: + redirect: realm + foreman_role: + redirect: role + foreman_scap_content: + redirect: scap_content + foreman_scap_tailoring_file: + redirect: scap_tailoring_file + foreman_scc_account: + redirect: scc_account + foreman_scc_product: + redirect: scc_product + foreman_search_facts: + redirect: resource_info + foreman_setting: + redirect: setting + foreman_smart_class_parameter: + redirect: smart_class_parameter + foreman_snapshot: + redirect: snapshot + foreman_subnet: + redirect: subnet + foreman_templates_import: + redirect: templates_import + foreman_usergroup: + redirect: usergroup + foreman_user: + redirect: user + katello_activation_key: + redirect: activation_key + katello_content_credential: + redirect: content_credential + katello_content_view_filter: + redirect: content_view_filter + katello_content_view: + redirect: content_view + katello_content_view_version: + redirect: content_view_version + katello_host_collection: + redirect: host_collection + katello_lifecycle_environment: + redirect: lifecycle_environment + katello_manifest: + redirect: subscription_manifest + katello_product: + redirect: product + katello_repository: + redirect: repository + katello_repository_set: + redirect: repository_set + katello_sync_plan: + redirect: sync_plan + katello_sync: + redirect: repository_sync + katello_upload: + redirect: content_upload diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/callback/foreman.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/callback/foreman.py new file mode 100644 index 00000000..f34e71a1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/callback/foreman.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- +# (c) 2015, 2016 Daniel Lobato <elobatocs@gmail.com> +# (c) 2016 Guido Günther <agx@sigxcpu.org> +# (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# pylint: disable=super-with-arguments + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' + callback: theforeman.foreman.foreman + type: notification + short_description: Sends events to Foreman + description: + - This callback will report facts and task events to Foreman + requirements: + - whitelisting in configuration + - requests (python library) + options: + url: + description: + - URL of the Foreman server. + env: + - name: FOREMAN_URL + - name: FOREMAN_SERVER_URL + - name: FOREMAN_SERVER + required: True + default: http://localhost:3000 + ini: + - section: callback_foreman + key: url + client_cert: + description: + - X509 certificate to authenticate to Foreman if https is used + env: + - name: FOREMAN_SSL_CERT + default: /etc/foreman/client_cert.pem + ini: + - section: callback_foreman + key: ssl_cert + - section: callback_foreman + key: client_cert + aliases: [ ssl_cert ] + client_key: + description: + - the corresponding private key + env: + - name: FOREMAN_SSL_KEY + default: /etc/foreman/client_key.pem + ini: + - section: callback_foreman + key: ssl_key + - section: callback_foreman + key: client_key + aliases: [ ssl_key ] + verify_certs: + description: + - Toggle to decide whether to verify the Foreman certificate. + - It can be set to '1' to verify SSL certificates using the installed CAs or to a path pointing to a CA bundle. + - Set to '0' to disable certificate checking. + env: + - name: FOREMAN_SSL_VERIFY + default: 1 + ini: + - section: callback_foreman + key: verify_certs +''' + +import os +from datetime import datetime +from collections import defaultdict +import json +import time + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +from ansible.module_utils._text import to_text +from ansible.plugins.callback import CallbackBase + + +class CallbackModule(CallbackBase): + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'notification' + CALLBACK_NAME = 'theforeman.foreman.foreman' + CALLBACK_NEEDS_WHITELIST = True + + FOREMAN_HEADERS = { + "Content-Type": "application/json", + "Accept": "application/json" + } + TIME_FORMAT = "%Y-%m-%d %H:%M:%S %f" + + def __init__(self): + super(CallbackModule, self).__init__() + self.items = defaultdict(list) + self.start_time = int(time.time()) + + def set_options(self, task_keys=None, var_options=None, direct=None): + + super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct) + + self.FOREMAN_URL = self.get_option('url') + self.FOREMAN_SSL_CERT = (self.get_option('client_cert'), self.get_option('client_key')) + self.FOREMAN_SSL_VERIFY = str(self.get_option('verify_certs')) + + self.ssl_verify = self._ssl_verify() + + if HAS_REQUESTS: + requests_major = int(requests.__version__.split('.')[0]) + if requests_major < 2: + self._disable_plugin('The `requests` python module is too old.') + else: + self._disable_plugin('The `requests` python module is not installed.') + + if self.FOREMAN_URL.startswith('https://'): + if not os.path.exists(self.FOREMAN_SSL_CERT[0]): + self._disable_plugin('FOREMAN_SSL_CERT %s not found.' % self.FOREMAN_SSL_CERT[0]) + + if not os.path.exists(self.FOREMAN_SSL_CERT[1]): + self._disable_plugin('FOREMAN_SSL_KEY %s not found.' % self.FOREMAN_SSL_CERT[1]) + + def _disable_plugin(self, msg): + self.disabled = True + if msg: + self._display.warning(msg + ' Disabling the Foreman callback plugin.') + else: + self._display.warning('Disabling the Foreman callback plugin.') + + def _ssl_verify(self): + if self.FOREMAN_SSL_VERIFY.lower() in ["1", "true", "on"]: + verify = True + elif self.FOREMAN_SSL_VERIFY.lower() in ["0", "false", "off"]: + requests.packages.urllib3.disable_warnings() + self._display.warning("SSL verification of %s disabled" % + self.FOREMAN_URL) + verify = False + else: # Set to a CA bundle: + verify = self.FOREMAN_SSL_VERIFY + return verify + + def send_facts(self, host, data): + """ + Sends facts to Foreman, to be parsed by foreman_ansible fact + parser. The default fact importer should import these facts + properly. + """ + data["_type"] = "ansible" + data["_timestamp"] = datetime.now().strftime(self.TIME_FORMAT) + facts = {"name": host, + "facts": data, + } + try: + r = requests.post(url=self.FOREMAN_URL + '/api/v2/hosts/facts', + data=json.dumps(facts), + headers=self.FOREMAN_HEADERS, + cert=self.FOREMAN_SSL_CERT, + verify=self.ssl_verify) + r.raise_for_status() + except requests.exceptions.RequestException as err: + print(to_text(err)) + + def _build_log(self, data): + logs = [] + for entry in data: + source, msg = entry + if 'failed' in msg: + level = 'err' + elif 'changed' in msg and msg['changed']: + level = 'notice' + else: + level = 'info' + logs.append({ + "log": { + 'sources': { + 'source': source + }, + 'messages': { + 'message': json.dumps(msg) + }, + 'level': level + } + }) + return logs + + def send_reports(self, stats): + """ + Send reports to Foreman to be parsed by its config report + importer. THe data is in a format that Foreman can handle + without writing another report importer. + """ + status = defaultdict(lambda: 0) + metrics = {} + + for host in stats.processed.keys(): + sum = stats.summarize(host) + status["applied"] = sum['changed'] + status["failed"] = sum['failures'] + sum['unreachable'] + status["skipped"] = sum['skipped'] + log = self._build_log(self.items[host]) + metrics["time"] = {"total": int(time.time()) - self.start_time} + now = datetime.now().strftime(self.TIME_FORMAT) + report = { + "config_report": { + "host": host, + "reported_at": now, + "metrics": metrics, + "status": status, + "logs": log, + "reporter": "ansible", + } + } + try: + r = requests.post(url=self.FOREMAN_URL + '/api/v2/config_reports', + data=json.dumps(report), + headers=self.FOREMAN_HEADERS, + cert=self.FOREMAN_SSL_CERT, + verify=self.ssl_verify) + r.raise_for_status() + except requests.exceptions.RequestException as err: + print(to_text(err)) + self.items[host] = [] + + def append_result(self, result): + name = result._task.get_name() + host = result._host.get_name() + self.items[host].append((name, result._result)) + + # Ansible callback API + def v2_runner_on_failed(self, result, ignore_errors=False): + self.append_result(result) + + def v2_runner_on_unreachable(self, result): + self.append_result(result) + + def v2_runner_on_async_ok(self, result, jid): + self.append_result(result) + + def v2_runner_on_async_failed(self, result, jid): + self.append_result(result) + + def v2_playbook_on_stats(self, stats): + self.send_reports(stats) + + def v2_runner_on_ok(self, result): + res = result._result + module = result._task.action + + if module == 'setup' or 'ansible_facts' in res: + host = result._host.get_name() + self.send_facts(host, res) + else: + self.append_result(result) diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/doc_fragments/foreman.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/doc_fragments/foreman.py new file mode 100644 index 00000000..266ef8e4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/doc_fragments/foreman.py @@ -0,0 +1,320 @@ +# (c) 2019, Evgeni Golov <evgeni@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 + + +class ModuleDocFragment(object): + + # Foreman documentation fragment + DOCUMENTATION = ''' +requirements: + - requests +options: + server_url: + description: + - URL of the Foreman server. + - If the value is not specified in the task, the value of environment variable C(FOREMAN_SERVER_URL) will be used instead. + required: true + type: str + username: + description: + - Username accessing the Foreman server. + - If the value is not specified in the task, the value of environment variable C(FOREMAN_USERNAME) will be used instead. + required: true + type: str + password: + description: + - Password of the user accessing the Foreman server. + - If the value is not specified in the task, the value of environment variable C(FOREMAN_PASSWORD) will be used instead. + required: true + type: str + validate_certs: + description: + - Whether or not to verify the TLS certificates of the Foreman server. + - If the value is not specified in the task, the value of environment variable C(FOREMAN_VALIDATE_CERTS) will be used instead. + default: true + type: bool +''' + + NESTED_PARAMETERS = ''' +options: + parameters: + description: + - Entity domain specific host parameters + required: false + type: list + elements: dict + suboptions: + name: + description: + - Name of the parameter + required: true + type: str + value: + description: + - Value of the parameter + required: true + type: raw + parameter_type: + description: + - Type of the parameter + default: 'string' + choices: + - 'string' + - 'boolean' + - 'integer' + - 'real' + - 'array' + - 'hash' + - 'yaml' + - 'json' + type: str +''' + + OS_FAMILY = ''' +options: + os_family: + description: + - The OS family the entity shall be assigned with. + required: false + choices: + - AIX + - Altlinux + - Archlinux + - Coreos + - Debian + - Freebsd + - Gentoo + - Junos + - NXOS + - Rancheros + - Redhat + - Solaris + - Suse + - Windows + - Xenserver + type: str +''' + + TAXONOMY = ''' +options: + organizations: + description: List of organizations the entity should be assigned to + type: list + elements: str + locations: + description: List of locations the entity should be assigned to + type: list + elements: str +''' + + ENTITY_STATE = ''' +options: + state: + description: + - State of the entity + default: present + choices: + - present + - absent + type: str +''' + + ENTITY_STATE_WITH_DEFAULTS = ''' +options: + state: + description: + - State of the entity + - C(present_with_defaults) will ensure the entity exists, but won't update existing ones + default: present + choices: + - present + - present_with_defaults + - absent + type: str +''' + + HOST_OPTIONS = ''' +options: + compute_resource: + description: Compute resource name + required: false + type: str + compute_profile: + description: Compute profile name + required: false + type: str + domain: + description: Domain name + required: false + type: str + subnet: + description: IPv4 Subnet name + required: false + type: str + subnet6: + description: IPv6 Subnet name + required: false + type: str + root_pass: + description: + - Root password. + - Will result in the entity always being updated, as the current password cannot be retrieved. + type: str + required: false + realm: + description: Realm name + required: false + type: str + architecture: + description: Architecture name + required: False + type: str + medium: + aliases: [ media ] + description: + - Medium name + - Mutually exclusive with I(kickstart_repository). + required: False + type: str + pxe_loader: + description: PXE Bootloader + required: false + choices: + - PXELinux BIOS + - PXELinux UEFI + - Grub UEFI + - Grub2 BIOS + - Grub2 ELF + - Grub2 UEFI + - Grub2 UEFI SecureBoot + - Grub2 UEFI HTTP + - Grub2 UEFI HTTPS + - Grub2 UEFI HTTPS SecureBoot + - iPXE Embedded + - iPXE UEFI HTTP + - iPXE Chain BIOS + - iPXE Chain UEFI + - None + type: str + ptable: + description: Partition table name + required: False + type: str + environment: + description: Puppet environment name + required: false + type: str + puppetclasses: + description: List of puppet classes to include in this host group. Must exist for hostgroup's puppet environment. + required: false + type: list + elements: str + config_groups: + description: Config groups list + required: false + type: list + elements: str + puppet_proxy: + description: Puppet server proxy name + required: false + type: str + puppet_ca_proxy: + description: Puppet CA proxy name + required: false + type: str + openscap_proxy: + description: + - OpenSCAP proxy name. + - Only available when the OpenSCAP plugin is installed. + required: false + type: str + content_source: + description: + - Content source. + - Only available for Katello installations. + required: false + type: str + lifecycle_environment: + description: + - Lifecycle environment. + - Only available for Katello installations. + required: false + type: str + kickstart_repository: + description: + - Kickstart repository name. + - You need to provide this to use the "Synced Content" feature. + - Mutually exclusive with I(medium). + - Only available for Katello installations. + required: false + type: str + content_view: + description: + - Content view. + - Only available for Katello installations. + required: false + type: str +''' + + ORGANIZATION = ''' +options: + organization: + description: + - Organization that the entity is in + required: true + type: str +''' + + SCAP_DATASTREAM = ''' +options: + scap_file: + description: + - File containing XML DataStream content. + - Required when creating a new DataStream. + required: false + type: path + original_filename: + description: + - Original file name of the XML file. + - If unset, the filename of I(scap_file) will be used. + required: false + type: str +''' + + OPERATINGSYSTEMS = ''' +options: + operatingsystems: + description: + - List of operating systems the entity should be assigned to. + - Operating systems are looked up by their title which is composed as "<name> <major>.<minor>". + - You can omit the version part as long as you only have one operating system by that name. + required: false + type: list + elements: str +''' + + OPERATINGSYSTEM = ''' +options: + operatingsystem: + description: + - Operating systems are looked up by their title which is composed as "<name> <major>.<minor>". + - You can omit the version part as long as you only have one operating system by that name. + type: str + required: False +''' diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/filter/foreman.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/filter/foreman.py new file mode 100644 index 00000000..e5e7871d --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/filter/foreman.py @@ -0,0 +1,24 @@ +# Copyright (c) 2019 Matthias Dellweg +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +import re + + +def cp_label(value): + p = re.compile(r'[^-\w]+') + return p.sub('_', value) + + +# ---- Ansible filters ---- +class FilterModule(object): + ''' Foreman filter ''' + + def filters(self): + return { + 'cp_label': cp_label, + } diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/inventory/foreman.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/inventory/foreman.py new file mode 100644 index 00000000..a69ba84f --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/inventory/foreman.py @@ -0,0 +1,643 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016 Guido Günther <agx@sigxcpu.org>, Daniel Lobato Garcia <dlobatog@redhat.com> +# Copyright (c) 2018 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# pylint: disable=raise-missing-from +# pylint: disable=super-with-arguments + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' + name: foreman + plugin_type: inventory + short_description: Foreman inventory source + requirements: + - requests >= 1.1 + description: + - Get inventory hosts from Foreman. + - Uses a YAML configuration file that ends with ``foreman.(yml|yaml)``. + extends_documentation_fragment: + - inventory_cache + - constructed + options: + plugin: + description: token that ensures this is a source file for the C(foreman) plugin. + required: True + choices: ['theforeman.foreman.foreman'] + url: + description: + - URL of the Foreman server. + default: 'http://localhost:3000' + env: + - name: FOREMAN_SERVER + - name: FOREMAN_SERVER_URL + - name: FOREMAN_URL + user: + description: + - Username accessing the Foreman server. + required: True + env: + - name: FOREMAN_USER + - name: FOREMAN_USERNAME + password: + description: + - Password of the user accessing the Foreman server. + required: True + env: + - name: FOREMAN_PASSWORD + validate_certs: + description: + - Whether or not to verify the TLS certificates of the Foreman server. + type: boolean + default: False + env: + - name: FOREMAN_VALIDATE_CERTS + group_prefix: + description: prefix to apply to foreman groups + default: foreman_ + vars_prefix: + description: prefix to apply to host variables, does not include facts nor params + default: foreman_ + want_facts: + description: Toggle, if True the plugin will retrieve host facts from the server + type: boolean + default: False + want_params: + description: Toggle, if true the inventory will retrieve 'all_parameters' information as host vars + type: boolean + default: False + want_hostcollections: + description: Toggle, if true the plugin will create Ansible groups for host collections + type: boolean + default: False + legacy_hostvars: + description: + - Toggle, if true the plugin will build legacy hostvars present in the foreman script + - Places hostvars in a dictionary with keys `foreman`, `foreman_facts`, and `foreman_params` + type: boolean + default: False + host_filters: + description: This can be used to restrict the list of returned host + type: string + batch_size: + description: Number of hosts per batch that will be retrieved from the Foreman API per individual call + type: int + default: 250 + use_reports_api: + description: Use Reporting API. + type: boolean + default: False + foreman: + description: + - Foreman server related configuration, deprecated. + - You can pass I(use_reports_api) in this dict to enable the Reporting API. + - Only for backward compatibility. + report: + description: + - Report API specific configuration, deprecated. + - You can pass the Report API specific params as part of this dict, instead of the main configuration. + - Only for backward compatibility. + type: dict + poll_interval: + description: The polling interval between 2 calls to the report_data endpoint while polling. + type: int + default: 10 + max_timeout: + description: Timeout before falling back to old host API when using report_data endpoint while polling. + type: int + default: 600 + want_organization: + description: Toggle, if true the inventory will fetch organization the host belongs to and create groupings for the same. + type: boolean + default: True + want_location: + description: Toggle, if true the inventory will fetch location the host belongs to and create groupings for the same. + type: boolean + default: True + want_ipv4: + description: Toggle, if true the inventory will fetch ipv4 address of the host. + type: boolean + default: True + want_ipv6: + description: Toggle, if true the inventory will fetch ipv6 address of the host. + type: boolean + default: True + want_host_group: + description: Toggle, if true the inventory will fetch host_groups and create groupings for the same. + type: boolean + default: True + want_subnet: + description: Toggle, if true the inventory will fetch subnet. + type: boolean + default: True + want_subnet_v6: + description: Toggle, if true the inventory will fetch ipv6 subnet. + type: boolean + default: True + want_smart_proxies: + description: Toggle, if true the inventory will fetch smart proxy that the host is registered to. + type: boolean + default: True + want_content_facet_attributes: + description: Toggle, if true the inventory will fetch content view details that the host is tied to. + type: boolean + default: True + hostnames: + description: + - A list of templates in order of precedence to compose inventory_hostname. + - If the template results in an empty string or None value it is ignored. + type: list + default: ['name'] +''' + +EXAMPLES = ''' +# my.foreman.yml +plugin: theforeman.foreman.foreman +url: https://foreman.example.com +user: ansibleinventory +password: changeme +host_filters: 'organization="Web Engineering"' + +# shortname.foreman.yml +plugin: theforeman.foreman.foreman +url: https://foreman.example.com +user: ansibleinventory +password: changeme +hostnames: + - name.split('.')[0] +''' +import json +from distutils.version import LooseVersion +from time import sleep +from ansible.errors import AnsibleError +from ansible.module_utils._text import to_bytes, to_native, to_text +from ansible.module_utils.common._collections_compat import MutableMapping +from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable, to_safe_group_name, Constructable + +# 3rd party imports +try: + import requests + if LooseVersion(requests.__version__) < LooseVersion('1.1.0'): + raise ImportError +except ImportError: + raise AnsibleError('This script requires python-requests 1.1 as a minimum version') + +from requests.auth import HTTPBasicAuth + + +class InventoryModule(BaseInventoryPlugin, Cacheable, Constructable): + ''' Host inventory parser for ansible using foreman as source. ''' + + NAME = 'theforeman.foreman.foreman' + + def __init__(self): + + super(InventoryModule, self).__init__() + self.MINIMUM_FOREMAN_VERSION_FOR_REPORTING_API = '1.24.0' + # from config + self.foreman_url = None + + self.session = None + self.cache_key = None + self.use_cache = None + + def verify_file(self, path): + + valid = False + if super(InventoryModule, self).verify_file(path): + if path.endswith(('foreman.yaml', 'foreman.yml')): + valid = True + else: + self.display.vvv('Skipping due to inventory source not ending in "foreman.yaml" nor "foreman.yml"') + return valid + + def _get_session(self): + if not self.session: + self.session = requests.session() + self.session.auth = HTTPBasicAuth(self.get_option('user'), to_bytes(self.get_option('password'))) + self.session.verify = self.get_option('validate_certs') + return self.session + + def _get_json(self, url, ignore_errors=None, params=None): + + if not self.use_cache or url not in self._cache.get(self.cache_key, {}): + + if self.cache_key not in self._cache: + self._cache[self.cache_key] = {url: ''} + + results = [] + s = self._get_session() + if params is None: + params = {} + params['page'] = 1 + params['per_page'] = self.get_option('batch_size') + while True: + ret = s.get(url, params=params) + if ignore_errors and ret.status_code in ignore_errors: + break + ret.raise_for_status() + json = ret.json() + + # process results + # FIXME: This assumes 'return type' matches a specific query, + # it will break if we expand the queries and they dont have different types + if 'results' not in json: # pylint: disable=no-else-break + # /hosts/:id dos not have a 'results' key + results = json + break + elif isinstance(json['results'], MutableMapping): + # /facts are returned as dict in 'results' + results = json['results'] + break + else: + # /hosts 's 'results' is a list of all hosts, returned is paginated + results = results + json['results'] + + # check for end of paging + if len(results) >= json['subtotal']: + break + if len(json['results']) == 0: + self.display.warning("Did not make any progress during loop. expected %d got %d" % (json['subtotal'], len(results))) + break + + # get next page + params['page'] += 1 + + self._cache[self.cache_key][url] = results + + return self._cache[self.cache_key][url] + + def _get_hosts(self): + url = "%s/api/v2/hosts" % self.foreman_url + params = {} + if self.get_option('host_filters'): + params['search'] = self.get_option('host_filters') + return self._get_json(url, params=params) + + def _get_all_params_by_id(self, hid): + url = "%s/api/v2/hosts/%s" % (self.foreman_url, hid) + ret = self._get_json(url, [404]) + if not ret or not isinstance(ret, MutableMapping) or not ret.get('all_parameters', False): + return {} + return ret.get('all_parameters') + + def _get_facts_by_id(self, hid): + url = "%s/api/v2/hosts/%s/facts" % (self.foreman_url, hid) + return self._get_json(url) + + def _get_host_data_by_id(self, hid): + url = "%s/api/v2/hosts/%s" % (self.foreman_url, hid) + return self._get_json(url) + + def _get_facts(self, host): + """Fetch all host facts of the host""" + + ret = self._get_facts_by_id(host['id']) + if len(ret.values()) == 0: + facts = {} + elif len(ret.values()) == 1: + facts = list(ret.values())[0] + else: + raise ValueError("More than one set of facts returned for '%s'" % host) + return facts + + def _get_hostvars(self, host, vars_prefix='', omitted_vars=()): + hostvars = {} + for k, v in host.items(): + if k not in omitted_vars: + hostvars[vars_prefix + k] = v + return hostvars + + def _fetch_params(self): + options = ("no", "yes") + params = dict() + + report_options = self.get_option('report') or {} + + self.want_location = report_options.get('want_location', self.get_option('want_location')) + self.want_organization = report_options.get('want_organization', self.get_option('want_organization')) + self.want_IPv4 = report_options.get('want_ipv4', self.get_option('want_ipv4')) + self.want_IPv6 = report_options.get('want_ipv6', self.get_option('want_ipv6')) + self.want_host_group = report_options.get('want_host_group', self.get_option('want_host_group')) + self.want_hostcollections = report_options.get('want_hostcollections', self.get_option('want_hostcollections')) + self.want_subnet = report_options.get('want_subnet', self.get_option('want_subnet')) + self.want_subnet_v6 = report_options.get('want_subnet_v6', self.get_option('want_subnet_v6')) + self.want_smart_proxies = report_options.get('want_smart_proxies', self.get_option('want_smart_proxies')) + self.want_content_facet_attributes = report_options.get('want_content_facet_attributes', self.get_option('want_content_facet_attributes')) + self.want_params = self.get_option('want_params') + self.want_facts = self.get_option('want_facts') + self.host_filters = self.get_option('host_filters') + + params["Organization"] = options[self.want_organization] + params["Location"] = options[self.want_location] + params["IPv4"] = options[self.want_IPv4] + params["IPv6"] = options[self.want_IPv6] + params["Facts"] = options[self.want_facts] + params["Host Group"] = options[self.want_host_group] + params["Host Collections"] = options[self.want_hostcollections] + params["Subnet"] = options[self.want_subnet] + params["Subnet v6"] = options[self.want_subnet_v6] + params["Smart Proxies"] = options[self.want_smart_proxies] + params["Content Attributes"] = options[self.want_content_facet_attributes] + params["Host Parameters"] = options[self.want_params] + if self.host_filters: + params["Hosts"] = self.host_filters + return params + + def _use_inventory_report(self): + use_inventory_report = self.get_option('use_reports_api') + # backward compatibility + try: + use_inventory_report = self.get_option('foreman').get('use_reports_api') + except Exception: + pass + if not use_inventory_report: + return False + status_url = "%s/api/v2/status" % self.foreman_url + result = self._get_json(status_url) + foreman_version = (LooseVersion(result.get('version')) >= LooseVersion(self.MINIMUM_FOREMAN_VERSION_FOR_REPORTING_API)) + return foreman_version + + def _post_request(self): + url = "%s/ansible/api/v2/ansible_inventories/schedule" % self.foreman_url + session = self._get_session() + params = {'input_values': self._fetch_params()} + self.poll_interval = self.get_option('poll_interval') + self.max_timeout = self.get_option('max_timeout') + # backward compatibility + try: + self.poll_interval = int(self.get_option('report').get('poll_interval')) + self.max_timeout = int(self.get_option('report').get('max_timeout')) + except Exception: + pass + max_polls = self.max_timeout / self.poll_interval + ret = session.post(url, json=params) + if not ret: + raise Exception("Error scheduling inventory report on foreman. Please check foreman logs!") + url = "{0}/{1}".format(self.foreman_url, ret.json().get('data_url')) + polls = 0 + response = session.get(url) + while response: + if response.status_code != 204 or polls > max_polls: + break + sleep(self.poll_interval) + polls += 1 + response = session.get(url) + if not response: + raise Exception("Error receiving inventory report from foreman. Please check foreman logs!") + elif (response.status_code == 204 and polls > max_polls): + raise Exception("Timeout receiving inventory report from foreman. Check foreman server and max_timeout in foreman.yml") + else: + return response.json() + + def _populate(self): + if self._use_inventory_report(): + self._populate_report_api() + else: + self._populate_host_api() + + def _get_hostname(self, properties, hostnames, strict=False): + hostname = None + errors = [] + + for preference in hostnames: + try: + hostname = self._compose(preference, properties) + except Exception as e: # pylint: disable=broad-except + if strict: + raise AnsibleError("Could not compose %s as hostnames - %s" % (preference, to_native(e))) + else: + errors.append( + (preference, str(e)) + ) + if hostname: + return to_text(hostname) + + raise AnsibleError( + 'Could not template any hostname for host, errors for each preference: %s' % ( + ', '.join(['%s: %s' % (pref, err) for pref, err in errors]) + ) + ) + + def _populate_report_api(self): + self.groups = dict() + self.hosts = dict() + try: + inventory_report_response = self._post_request() + except Exception as exc: + self.display.warning("Failed to use Reports API, falling back to Hosts API: {0}".format(exc)) + self._populate_host_api() + return + self.group_prefix = self.get_option('group_prefix') + + hostnames = self.get_option('hostnames') + strict = self.get_option('strict') + + host_data = json.loads(inventory_report_response) + for host in host_data: + if not(host): + continue + + composed_host_name = self._get_hostname(host, hostnames, strict=strict) + + if (composed_host_name in self._cache.keys()): + continue + + host_name = self.inventory.add_host(composed_host_name) + + group_name = host.get('hostgroup_title', host.get('hostgroup_name')) + if group_name: + group_name = to_safe_group_name('%s%s' % (self.get_option('group_prefix'), group_name.lower().replace(" ", ""))) + group_name = self.inventory.add_group(group_name) + self.inventory.add_child(group_name, host_name) + + host_params = host.pop('host_parameters', {}) + fact_list = host.pop('facts', {}) + + if self.get_option('legacy_hostvars'): + hostvars = self._get_hostvars(host) + self.inventory.set_variable(host_name, 'foreman', hostvars) + else: + omitted_vars = ('name', 'hostgroup_title', 'hostgroup_name') + hostvars = self._get_hostvars(host, self.get_option('vars_prefix'), omitted_vars) + + for k, v in hostvars.items(): + try: + self.inventory.set_variable(host_name, k, v) + except ValueError as e: + self.display.warning("Could not set host info hostvar for %s, skipping %s: %s" % (host, k, to_text(e))) + + content_facet_attributes = host.get('content_attributes', {}) or {} + if self.get_option('want_facts'): + self.inventory.set_variable(host_name, 'foreman_facts', fact_list) + + # Create ansible groups for hostgroup + group = 'host_group' + group_name = host.get(group) + if group_name: + parent_name = None + group_label_parts = [] + for part in group_name.split('/'): + group_label_parts.append(part.lower().replace(" ", "")) + gname = to_safe_group_name('%s%s' % (self.get_option('group_prefix'), '/'.join(group_label_parts))) + result_gname = self.inventory.add_group(gname) + if parent_name: + self.inventory.add_child(parent_name, result_gname) + parent_name = result_gname + self.inventory.add_child(result_gname, host_name) + + # Create ansible groups for environment, location and organization + for group in ['environment', 'location', 'organization']: + val = host.get('%s' % group) + if val: + safe_key = to_safe_group_name('%s%s_%s' % ( + to_text(self.group_prefix), + group, + to_text(val).lower() + )) + env_lo_org = self.inventory.add_group(safe_key) + self.inventory.add_child(env_lo_org, host_name) + + for group in ['lifecycle_environment', 'content_view']: + val = content_facet_attributes.get('%s_name' % group) + if val: + safe_key = to_safe_group_name('%s%s_%s' % ( + to_text(self.group_prefix), + group, + to_text(val).lower() + )) + le_cv_group = self.inventory.add_group(safe_key) + self.inventory.add_child(le_cv_group, host_name) + params = host_params + + if self.want_hostcollections: + hostcollections = host.get('host_collections') + + if hostcollections: + # Create Ansible groups for host collections + for hostcollection in hostcollections: + try: + host_collection_group_name = to_safe_group_name('%shostcollection_%s' % ( + to_text(self.group_prefix), + to_text(hostcollection).lower() + )) + hostcollection_group = self.inventory.add_group(host_collection_group_name) + self.inventory.add_child(hostcollection_group, host_name) + except ValueError as e: + self.display.warning("Could not create groups for host collections for %s, skipping: %s" % (host_name, to_text(e))) + + # set host vars from params + if self.get_option('want_params'): + if self.get_option('legacy_hostvars'): + self.inventory.set_variable(host_name, 'foreman_params', params) + else: + for k, v in params.items(): + try: + self.inventory.set_variable(host_name, k, v) + except ValueError as e: + self.display.warning("Could not set hostvar %s to '%s' for the '%s' host, skipping: %s" % + (k, to_native(v), host, to_native(e))) + hostvars = self.inventory.get_host(host_name).get_vars() + self._set_composite_vars(self.get_option('compose'), hostvars, host_name, strict) + self._add_host_to_composed_groups(self.get_option('groups'), hostvars, host_name, strict) + self._add_host_to_keyed_groups(self.get_option('keyed_groups'), hostvars, host_name, strict) + + def _populate_host_api(self): + hostnames = self.get_option('hostnames') + strict = self.get_option('strict') + for host in self._get_hosts(): + if not(host): + continue + + composed_host_name = self._get_hostname(host, hostnames, strict=strict) + + if (composed_host_name in self._cache.keys()): + continue + + host_name = self.inventory.add_host(composed_host_name) + + # create directly mapped groups + group_name = host.get('hostgroup_title', host.get('hostgroup_name')) + if group_name: + parent_name = None + group_label_parts = [] + for part in group_name.split('/'): + group_label_parts.append(part.lower().replace(" ", "")) + gname = to_safe_group_name('%s%s' % (self.get_option('group_prefix'), '/'.join(group_label_parts))) + result_gname = self.inventory.add_group(gname) + if parent_name: + self.inventory.add_child(parent_name, result_gname) + parent_name = result_gname + self.inventory.add_child(result_gname, host_name) + + if self.get_option('legacy_hostvars'): + hostvars = self._get_hostvars(host) + self.inventory.set_variable(host_name, 'foreman', hostvars) + else: + omitted_vars = ('name', 'hostgroup_title', 'hostgroup_name') + hostvars = self._get_hostvars(host, self.get_option('vars_prefix'), omitted_vars) + + for k, v in hostvars.items(): + try: + self.inventory.set_variable(host_name, k, v) + except ValueError as e: + self.display.warning("Could not set host info hostvar for %s, skipping %s: %s" % (host, k, to_text(e))) + + # set host vars from params + if self.get_option('want_params'): + params = self._get_all_params_by_id(host['id']) + filtered_params = {} + for p in params: + if 'name' in p and 'value' in p: + filtered_params[p['name']] = p['value'] + + if self.get_option('legacy_hostvars'): + self.inventory.set_variable(host_name, 'foreman_params', filtered_params) + else: + for k, v in filtered_params.items(): + try: + self.inventory.set_variable(host_name, k, v) + except ValueError as e: + self.display.warning("Could not set hostvar %s to '%s' for the '%s' host, skipping: %s" % + (k, to_native(v), host, to_native(e))) + + # set host vars from facts + if self.get_option('want_facts'): + self.inventory.set_variable(host_name, 'foreman_facts', self._get_facts(host)) + + # create group for host collections + if self.get_option('want_hostcollections'): + host_data = self._get_host_data_by_id(host['id']) + hostcollections = host_data.get('host_collections') + if hostcollections: + # Create Ansible groups for host collections + for hostcollection in hostcollections: + try: + hostcollection_group = to_safe_group_name('%shostcollection_%s' % (self.get_option('group_prefix'), + hostcollection['name'].lower().replace(" ", ""))) + hostcollection_group = self.inventory.add_group(hostcollection_group) + self.inventory.add_child(hostcollection_group, host_name) + except ValueError as e: + self.display.warning("Could not create groups for host collections for %s, skipping: %s" % (host_name, to_text(e))) + + hostvars = self.inventory.get_host(host_name).get_vars() + self._set_composite_vars(self.get_option('compose'), hostvars, host_name, strict) + self._add_host_to_composed_groups(self.get_option('groups'), hostvars, host_name, strict) + self._add_host_to_keyed_groups(self.get_option('keyed_groups'), hostvars, host_name, strict) + + def parse(self, inventory, loader, path, cache=True): + + super(InventoryModule, self).parse(inventory, loader, path) + + # read config from file, this sets 'options' + self._read_config_data(path) + + # get connection host + self.foreman_url = self.get_option('url') + self.cache_key = self.get_cache_key(path) + self.use_cache = cache and self.get_option('cache') + + # actually populate inventory + self._populate() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/module_utils/_apypie.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/module_utils/_apypie.py new file mode 100644 index 00000000..8052d1a8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/module_utils/_apypie.py @@ -0,0 +1,907 @@ +# pylint: disable=ansible-format-automatic-specification,raise-missing-from +from __future__ import absolute_import, division, print_function +__metaclass__ = type +try: + from typing import Any, Iterable, List, Optional, Tuple # pylint: disable=unused-import +except ImportError: + pass + + +""" +Apypie Action module +""" + +try: + base_string = basestring +except NameError: # Python 3 has no base_string + base_string = str # pylint: disable=invalid-name,redefined-builtin + + +class Action(object): + """ + Apipie Action + """ + + def __init__(self, name, resource, api): + # type: (str, str, Api) -> None + self.name = name + self.resource = resource + self.api = api + + @property + def apidoc(self): + # type: () -> dict + """ + The apidoc of this action. + + :returns: The apidoc. + """ + + resource_methods = self.api.apidoc['docs']['resources'][self.resource]['methods'] + return [method for method in resource_methods if method['name'] == self.name][0] + + @property + def routes(self): + # type: () -> List[Route] + """ + The routes this action can be invoked by. + + :returns: The routes + """ + + return [Route(route['api_url'], route['http_method'], route['short_description']) for route in self.apidoc['apis']] + + @property + def params(self): + # type: () -> List[Param] + """ + The params accepted by this action. + + :returns: The params. + """ + + return [Param(**param) for param in self.apidoc['params']] + + @property + def examples(self): + # type: () -> List[Example] + """ + The examples of this action. + + :returns: The examples. + """ + + return [Example.parse(example) for example in self.apidoc['examples']] + + def call(self, params=None, headers=None, options=None, data=None, files=None): # pylint: disable=too-many-arguments + # type: (dict, Optional[dict], Optional[dict], Optional[Any], Optional[dict]) -> dict + """ + Call the API to execute the action. + + :param params: The params that should be passed to the API. + :param headers: Additional headers to be passed to the API. + :param options: Options + :param data: Binary data to be submitted to the API. + :param files: Files to be submitted to the API. + + :returns: The API response. + """ + + return self.api.call(self.resource, self.name, params, headers, options, data, files) + + def find_route(self, params=None): + # type: (Optional[dict]) -> Route + """ + Find the best matching route for a given set of params. + + :param params: Params that should be submitted to the API. + + :returns: The best route. + """ + + param_keys = set(self.filter_empty_params(params).keys()) + sorted_routes = sorted(self.routes, key=lambda route: [-1 * len(route.params_in_path), route.path]) + for route in sorted_routes: + if set(route.params_in_path) <= param_keys: + return route + return sorted_routes[-1] + + def validate(self, values, data=None, files=None): + # type: (dict, Optional[Any], Optional[dict]) -> None + """ + Validate a given set of parameter values against the required set of parameters. + + :param values: The values to validate. + :param data: Additional binary data to validate. + :param files: Additional files to validate. + """ + + self._validate(self.params, values, data, files) + + @staticmethod + def _add_to_path(path=None, additions=None): + # type: (Optional[str], Optional[List[str]]) -> str + if path is None: + path = '' + if additions is None: + additions = [] + for addition in additions: + if path == '': + path = "{}".format(addition) + else: + path = "{}[{}]".format(path, addition) + return path + + def _validate(self, params, values, data=None, files=None, path=None): # pylint: disable=too-many-arguments,too-many-locals + # type: (Iterable[Param], dict, Optional[Any], Optional[dict], Optional[str]) -> None + if not isinstance(values, dict): + raise InvalidArgumentTypesError + given_params = set(values.keys()) + given_files = set((files or {}).keys()) + given_data = set((data or {}).keys()) + required_params = {param.name for param in params if param.required} + missing_params = required_params - given_params - given_files - given_data + if missing_params: + missing_params_with_path = [self._add_to_path(path, [param]) for param in missing_params] + message = "The following required parameters are missing: {}".format(', '.join(missing_params_with_path)) + raise MissingArgumentsError(message) + + for param, value in values.items(): + param_descriptions = [p for p in params if p.name == param] + if param_descriptions: + param_description = param_descriptions[0] + if param_description.params and value is not None: + if param_description.expected_type == 'array': + for num, item in enumerate(value): + self._validate(param_description.params, item, path=self._add_to_path(path, [param_description.name, str(num)])) + elif param_description.expected_type == 'hash': + self._validate(param_description.params, value, path=self._add_to_path(path, [param_description.name])) + if (param_description.expected_type == 'numeric' and isinstance(value, base_string)): + try: + value = int(value) + except ValueError: + # this will be caught in the next check + pass + if (not param_description.allow_nil and value is None): + raise ValueError("{} can't be {}".format(param, value)) + # pylint: disable=too-many-boolean-expressions + if (value is not None + and ((param_description.expected_type == 'boolean' and not isinstance(value, bool) and not (isinstance(value, int) and value in [0, 1])) + or (param_description.expected_type == 'numeric' and not isinstance(value, int)) + or (param_description.expected_type == 'string' and not isinstance(value, (base_string, int))))): + raise ValueError("{} ({}): {}".format(param, value, param_description.validator)) + + @staticmethod + def filter_empty_params(params=None): + # type: (Optional[dict]) -> dict + """ + Filter out any params that have no value. + + :param params: The params to filter. + + :returns: The filtered params. + """ + result = {} + if params is not None: + if isinstance(params, dict): + result = {k: v for k, v in params.items() if v is not None} + else: + raise InvalidArgumentTypesError + return result + + def prepare_params(self, input_dict): + # type: (dict) -> dict + """ + Transform a dict with data into one that can be accepted as params for calling the action. + + This will ignore any keys that are not accepted as params when calling the action. + It also allows generating nested params without forcing the user to care about them. + + :param input_dict: a dict with data that should be used to fill in the params + :return: :class:`dict` object + :rtype: dict + + Usage:: + + >>> action.prepare_params({'id': 1}) + {'user': {'id': 1}} + """ + params = self._prepare_params(self.params, input_dict) + route_params = self._prepare_route_params(input_dict) + params.update(route_params) + return params + + def _prepare_params(self, action_params, input_dict): + # type: (Iterable[Param], dict) -> dict + result = {} + + for param in action_params: + if param.expected_type == 'hash' and param.params: + nested_dict = input_dict.get(param.name, input_dict) + nested_result = self._prepare_params(param.params, nested_dict) + if nested_result: + result[param.name] = nested_result + elif param.name in input_dict: + result[param.name] = input_dict[param.name] + + return result + + def _prepare_route_params(self, input_dict): + # type: (dict) -> dict + result = {} + + route = self.find_route(input_dict) + + for url_param in route.params_in_path: + if url_param in input_dict: + result[url_param] = input_dict[url_param] + + return result + + +""" +Apypie Api module +""" + + +import errno +import glob +import json +try: + import requests +except ImportError: + pass +try: + from json.decoder import JSONDecodeError # type: ignore +except ImportError: + JSONDecodeError = ValueError # type: ignore +import os +try: + from urlparse import urljoin # type: ignore +except ImportError: + from urllib.parse import urljoin # type: ignore + + +def _qs_param(param): + # type: (Any) -> Any + if isinstance(param, bool): + return str(param).lower() + return param + + +class Api(object): + """ + Apipie API bindings + + :param uri: base URL of the server + :param username: username to access the API + :param password: username to access the API + :param api_version: version of the API. Defaults to `1` + :param language: prefered locale for the API description + :param apidoc_cache_base_dir: base directory for building apidoc_cache_dir. Defaults to `~/.cache/apipie_bindings`. + :param apidoc_cache_dir: where to cache the JSON description of the API. Defaults to `apidoc_cache_base_dir/<URI>`. + :param apidoc_cache_name: name of the cache file. If there is cache in the `apidoc_cache_dir`, it is used. Defaults to `default`. + :param verify_ssl: should the SSL certificate be verified. Defaults to `True`. + + Usage:: + + >>> import apypie + >>> api = apypie.Api(uri='https://api.example.com', username='admin', password='changeme') + """ + + def __init__(self, **kwargs): + self.uri = kwargs.get('uri') + self.api_version = kwargs.get('api_version', 1) + self.language = kwargs.get('language') + + # Find where to put the cache by default according to the XDG spec + # Not using just get('XDG_CACHE_HOME', '~/.cache') because the spec says + # that the defaut should be used if "$XDG_CACHE_HOME is either not set or empty" + xdg_cache_home = os.environ.get('XDG_CACHE_HOME', None) + if not xdg_cache_home: + xdg_cache_home = '~/.cache' + + apidoc_cache_base_dir = kwargs.get('apidoc_cache_base_dir', os.path.join(os.path.expanduser(xdg_cache_home), 'apypie')) + apidoc_cache_dir_default = os.path.join(apidoc_cache_base_dir, self.uri.replace(':', '_').replace('/', '_'), 'v{}'.format(self.api_version)) + self.apidoc_cache_dir = kwargs.get('apidoc_cache_dir', apidoc_cache_dir_default) + self.apidoc_cache_name = kwargs.get('apidoc_cache_name', self._find_cache_name()) + + self._session = requests.Session() + self._session.verify = kwargs.get('verify_ssl', True) + + self._session.headers['Accept'] = 'application/json;version={}'.format(self.api_version) + self._session.headers['User-Agent'] = 'apypie (https://github.com/Apipie/apypie)' + if self.language: + self._session.headers['Accept-Language'] = self.language + + if kwargs.get('username') and kwargs.get('password'): + self._session.auth = (kwargs['username'], kwargs['password']) + + self._apidoc = None + + @property + def apidoc(self): + # type: () -> dict + """ + The full apidoc. + + The apidoc will be fetched from the server, if that didn't happen yet. + + :returns: The apidoc. + """ + + if self._apidoc is None: + self._apidoc = self._load_apidoc() + return self._apidoc + + @property + def apidoc_cache_file(self): + # type: () -> str + """ + Full local path to the cached apidoc. + """ + + return os.path.join(self.apidoc_cache_dir, '{0}{1}'.format(self.apidoc_cache_name, self.cache_extension)) + + def _cache_dir_contents(self): + # type: () -> Iterable[str] + return glob.iglob(os.path.join(self.apidoc_cache_dir, '*{}'.format(self.cache_extension))) + + def _find_cache_name(self, default='default'): + cache_file = next(self._cache_dir_contents(), None) + cache_name = default + if cache_file: + cache_name = os.path.basename(cache_file)[:-len(self.cache_extension)] + return cache_name + + def validate_cache(self, cache_name): + # type: (str) -> None + """ + Ensure the cached apidoc matches the one presented by the server. + + :param cache_name: The name of the apidoc on the server. + """ + + if cache_name is not None and cache_name != self.apidoc_cache_name: + self.clean_cache() + self.apidoc_cache_name = os.path.basename(os.path.normpath(cache_name)) + + def clean_cache(self): + # type: () -> None + """ + Remove any locally cached apidocs. + """ + + self._apidoc = None + for filename in self._cache_dir_contents(): + os.unlink(filename) + + @property + def resources(self): + # type: () -> Iterable + """ + List of available resources. + + Usage:: + + >>> api.resources + ['comments', 'users'] + """ + return sorted(self.apidoc['docs']['resources'].keys()) + + def resource(self, name): + # type: (str) -> Resource + """ + Get a resource. + + :param name: the name of the resource to load + :return: :class:`Resource <Resource>` object + :rtype: apypie.Resource + + Usage:: + + >>> api.resource('users') + """ + if name in self.resources: + return Resource(self, name) + message = "Resource '{}' does not exist in the API. Existing resources: {}".format(name, ', '.join(sorted(self.resources))) + raise KeyError(message) + + def _load_apidoc(self): + # type: () -> dict + try: + with open(self.apidoc_cache_file, 'r') as apidoc_file: + api_doc = json.load(apidoc_file) + except (IOError, JSONDecodeError): + api_doc = self._retrieve_apidoc() + return api_doc + + def _retrieve_apidoc(self): + # type: () -> dict + try: + os.makedirs(self.apidoc_cache_dir) + except OSError as err: + if err.errno != errno.EEXIST or not os.path.isdir(self.apidoc_cache_dir): + raise + response = None + if self.language: + response = self._retrieve_apidoc_call('/apidoc/v{0}.{1}.json'.format(self.api_version, self.language), safe=True) + language_family = self.language.split('_')[0] + if not response and language_family != self.language: + response = self._retrieve_apidoc_call('/apidoc/v{0}.{1}.json'.format(self.api_version, language_family), safe=True) + if not response: + try: + response = self._retrieve_apidoc_call('/apidoc/v{}.json'.format(self.api_version)) + except Exception as exc: + raise DocLoadingError("""Could not load data from {0}: {1} + - is your server down? + - was rake apipie:cache run when using apipie cache? (typical production settings)""".format(self.uri, exc)) + with open(self.apidoc_cache_file, 'w') as apidoc_file: + apidoc_file.write(json.dumps(response)) + return response + + def _retrieve_apidoc_call(self, path, safe=False): + try: + return self.http_call('get', path) + except requests.exceptions.HTTPError: + if not safe: + raise + + def call(self, resource_name, action_name, params=None, headers=None, options=None, data=None, files=None): # pylint: disable=too-many-arguments + """ + Call an action in the API. + + It finds most fitting route based on given parameters + with other attributes necessary to do an API call. + + :param resource_name: name of the resource + :param action_name: action_name name of the action + :param params: Dict of parameters to be sent in the request + :param headers: Dict of headers to be sent in the request + :param options: Dict of options to influence the how the call is processed + * `skip_validation` (Bool) *false* - skip validation of parameters + :param data: Binary data to be sent in the request + :param files: Binary files to be sent in the request + :return: :class:`dict` object + :rtype: dict + + Usage:: + + >>> api.call('users', 'show', {'id': 1}) + """ + if options is None: + options = {} + if params is None: + params = {} + + resource = Resource(self, resource_name) + action = resource.action(action_name) + if not options.get('skip_validation', False): + action.validate(params, data, files) + + return self._call_action(action, params, headers, data, files) + + def _call_action(self, action, params=None, headers=None, data=None, files=None): # pylint: disable=too-many-arguments + if params is None: + params = {} + + route = action.find_route(params) + get_params = {key: value for key, value in params.items() if key not in route.params_in_path} + return self.http_call( + route.method, + route.path_with_params(params), + get_params, + headers, data, files) + + def http_call(self, http_method, path, params=None, headers=None, data=None, files=None): # pylint: disable=too-many-arguments + """ + Execute an HTTP request. + + :param params: Dict of parameters to be sent in the request + :param headers: Dict of headers to be sent in the request + :param data: Binary data to be sent in the request + :param files: Binary files to be sent in the request + + :return: :class:`dict` object + :rtype: dict + """ + + full_path = urljoin(self.uri, path) + kwargs = { + 'verify': self._session.verify, + } + + if headers: + kwargs['headers'] = headers + + if params: + if http_method in ['get', 'head']: + kwargs['params'] = {k: _qs_param(v) for k, v in params.items()} + else: + kwargs['json'] = params + elif http_method in ['post', 'put', 'patch'] and not data and not files: + kwargs['json'] = {} + + if files: + kwargs['files'] = files + + if data: + kwargs['data'] = data + + request = self._session.request(http_method, full_path, **kwargs) + request.raise_for_status() + self.validate_cache(request.headers.get('apipie-checksum')) + if request.status_code == requests.codes['no_content']: + return None + return request.json() + + @property + def cache_extension(self): + """ + File extension for the local cache file. + + Will include the language if set. + """ + + if self.language: + ext = '.{}.json'.format(self.language) + else: + ext = '.json' + return ext + + +""" +Apypie Example module +""" + + +import re + +EXAMPLE_PARSER = re.compile(r'(\w+)\s+([^\n]*)\n?(.*)\n(\d+)\n(.*)', re.DOTALL) + + +class Example(object): # pylint: disable=too-few-public-methods + """ + Apipie Example + """ + + def __init__(self, http_method, path, args, status, response): # pylint: disable=too-many-arguments + # type: (str, str, str, str, str) -> None + self.http_method = http_method + self.path = path + self.args = args + self.status = int(status) + self.response = response + + @classmethod + def parse(cls, example): + """ + Parse an example from an apidoc string + + :returns: The parsed :class:`Example` + """ + parsed = EXAMPLE_PARSER.match(example) + return cls(*parsed.groups()) + + +""" +Apypie Exceptions +""" + + +class DocLoadingError(Exception): + """ + Exception to be raised when apidoc could not be loaded. + """ + + +class MissingArgumentsError(Exception): + """ + Exception to be raised when required arguments are missing. + """ + + +class InvalidArgumentTypesError(Exception): + """ + Exception to be raised when arguments are of the wrong type. + """ + + +""" +Apypie Inflector module + +Based on ActiveSupport Inflector (https://github.com/rails/rails.git) +Inflection rules taken from davidcelis's Inflections (https://github.com/davidcelis/inflections.git) +""" + + +import re + + +class Inflections(object): + """ + Inflections - rules how to convert words from singular to plural and vice versa. + """ + + def __init__(self): + self.plurals = [] + self.singulars = [] + self.uncountables = [] + self.humans = [] + self.acronyms = {} + self.acronym_regex = r'/(?=a)b/' + + def acronym(self, word): + # type: (str) -> None + """ + Add a new acronym. + """ + + self.acronyms[word.lower()] = word + self.acronym_regex = '|'.join(self.acronyms.values()) + + def plural(self, rule, replacement): + # type: (str, str) -> None + """ + Add a new plural rule. + """ + + if rule in self.uncountables: + self.uncountables.remove(rule) + if replacement in self.uncountables: + self.uncountables.remove(replacement) + + self.plurals.insert(0, (rule, replacement)) + + def singular(self, rule, replacement): + # type: (str, str) -> None + """ + Add a new singular rule. + """ + + if rule in self.uncountables: + self.uncountables.remove(rule) + if replacement in self.uncountables: + self.uncountables.remove(replacement) + + self.singulars.insert(0, (rule, replacement)) + + def irregular(self, singular, plural): + # type: (str, str) -> None + """ + Add a new irregular rule + """ + + if singular in self.uncountables: + self.uncountables.remove(singular) + if plural in self.uncountables: + self.uncountables.remove(plural) + + sfirst = singular[0] + srest = singular[1:] + + pfirst = plural[0] + prest = plural[1:] + + if sfirst.upper() == pfirst.upper(): + self.plural(r'(?i)({}){}$'.format(sfirst, srest), r'\1' + prest) + self.plural(r'(?i)({}){}$'.format(pfirst, prest), r'\1' + prest) + + self.singular(r'(?i)({}){}$'.format(sfirst, srest), r'\1' + srest) + self.singular(r'(?i)({}){}$'.format(pfirst, prest), r'\1' + srest) + else: + self.plural(r'{}(?i){}$'.format(sfirst.upper(), srest), pfirst.upper() + prest) + self.plural(r'{}(?i){}$'.format(sfirst.lower(), srest), pfirst.lower() + prest) + self.plural(r'{}(?i){}$'.format(pfirst.upper(), prest), pfirst.upper() + prest) + self.plural(r'{}(?i){}$'.format(pfirst.lower(), prest), pfirst.lower() + prest) + + self.singular(r'{}(?i){}$'.format(sfirst.upper(), srest), sfirst.upper() + srest) + self.singular(r'{}(?i){}$'.format(sfirst.lower(), srest), sfirst.lower() + srest) + self.singular(r'{}(?i){}$'.format(pfirst.upper(), prest), sfirst.upper() + srest) + self.singular(r'{}(?i){}$'.format(pfirst.lower(), prest), sfirst.lower() + srest) + + def uncountable(self, *words): + """ + Add new uncountables. + """ + + self.uncountables.extend(words) + + def human(self, rule, replacement): + # type: (str, str) -> None + """ + Add a new humanize rule. + """ + + self.humans.insert(0, (rule, replacement)) + + +class Inflector(object): + """ + Inflector - perform inflections + """ + + def __init__(self): + # type: () -> None + self.inflections = Inflections() + self.inflections.plural(r'$', 's') + self.inflections.plural(r'(?i)([sxz]|[cs]h)$', r'\1es') + self.inflections.plural(r'(?i)([^aeiouy]o)$', r'\1es') + self.inflections.plural(r'(?i)([^aeiouy])y$', r'\1ies') + + self.inflections.singular(r'(?i)s$', r'') + self.inflections.singular(r'(?i)(ss)$', r'\1') + self.inflections.singular(r'([sxz]|[cs]h)es$', r'\1') + self.inflections.singular(r'([^aeiouy]o)es$', r'\1') + self.inflections.singular(r'(?i)([^aeiouy])ies$', r'\1y') + + self.inflections.irregular('child', 'children') + self.inflections.irregular('man', 'men') + self.inflections.irregular('medium', 'media') + self.inflections.irregular('move', 'moves') + self.inflections.irregular('person', 'people') + self.inflections.irregular('self', 'selves') + self.inflections.irregular('sex', 'sexes') + + self.inflections.uncountable('equipment', 'information', 'money', 'species', 'series', 'fish', 'sheep', 'police') + + def pluralize(self, word): + # type: (str) -> str + """ + Pluralize a word. + """ + + return self._apply_inflections(word, self.inflections.plurals) + + def singularize(self, word): + # type: (str) -> str + """ + Singularize a word. + """ + + return self._apply_inflections(word, self.inflections.singulars) + + def _apply_inflections(self, word, rules): + # type: (str, Iterable[Tuple[str, str]]) -> str + result = word + + if word != '' and result.lower() not in self.inflections.uncountables: + for (rule, replacement) in rules: + result = re.sub(rule, replacement, result) + if result != word: + break + + return result + + +""" +Apypie Param module +""" + + +import re + +HTML_STRIP = re.compile(r'<\/?[^>]+?>') + + +class Param(object): # pylint: disable=too-many-instance-attributes,too-few-public-methods + """ + Apipie Param + """ + + def __init__(self, **kwargs): + self.allow_nil = kwargs.get('allow_nil') + self.description = HTML_STRIP.sub('', kwargs.get('description')) + self.expected_type = kwargs.get('expected_type') + self.full_name = kwargs.get('full_name') + self.name = kwargs.get('name') + self.params = [Param(**param) for param in kwargs.get('params', [])] + self.required = bool(kwargs.get('required')) + self.validator = kwargs.get('validator') + + +""" +Apypie Resource module +""" + + +class Resource(object): + """ + Apipie Resource + """ + + def __init__(self, api, name): + # type: (Api, str) -> None + self.api = api + self.name = name + + @property + def actions(self): + # type: () -> List + """ + Actions available for this resource. + + :returns: The actions. + """ + return sorted([method['name'] for method in self.api.apidoc['docs']['resources'][self.name]['methods']]) + + def action(self, name): + # type: (str) -> Action + """ + Get an :class:`Action` for this resource. + + :param name: The name of the action. + """ + if self.has_action(name): + return Action(name, self.name, self.api) + message = "Unknown action '{}'. Supported actions: {}".format(name, ', '.join(sorted(self.actions))) + raise KeyError(message) + + def has_action(self, name): + # type: (str) -> bool + """ + Check whether the resource has a given action. + + :param name: The name of the action. + """ + return name in self.actions + + def call(self, action, params=None, headers=None, options=None, data=None, files=None): # pylint: disable=too-many-arguments + # type: (str, Optional[dict], Optional[dict], Optional[dict], Optional[Any], Optional[dict]) -> dict + """ + Call the API to execute an action for this resource. + + :param action: The action to call. + :param params: The params that should be passed to the API. + :param headers: Additional headers to be passed to the API. + :param options: Options + :param data: Binary data to be submitted to the API. + :param files: Files to be submitted to the API. + + :returns: The API response. + """ + + return self.api.call(self.name, action, params, headers, options, data, files) + + +""" +Apypie Route module +""" + + +class Route(object): + """ + Apipie Route + """ + + def __init__(self, path, method, description=""): + # type: (str, str, str) -> None + self.path = path + self.method = method.lower() + self.description = description + + @property + def params_in_path(self): + # type: () -> List + """ + Params that can be passed in the path (URL) of the route. + + :returns: The params. + """ + return [part[1:] for part in self.path.split('/') if part.startswith(':')] + + def path_with_params(self, params=None): + # type: (Optional[dict]) -> str + """ + Fill in the params into the path. + + :returns: The path with params. + """ + result = self.path + if params is not None: + for param in self.params_in_path: + if param in params: + result = result.replace(':{}'.format(param), str(params[param])) + else: + raise KeyError("missing param '{}' in parameters".format(param)) + return result diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/module_utils/foreman_helper.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/module_utils/foreman_helper.py new file mode 100644 index 00000000..b27e474d --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/module_utils/foreman_helper.py @@ -0,0 +1,1704 @@ +# -*- coding: utf-8 -*- +# (c) Matthias Dellweg (ATIX AG) 2017 + +# pylint: disable=raise-missing-from +# pylint: disable=super-with-arguments + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import hashlib +import json +import os +import operator +import re +import time +import traceback + +from contextlib import contextmanager + +from collections import defaultdict +from functools import wraps + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback +from ansible.module_utils._text import to_bytes, to_native +from ansible.module_utils import six + +from distutils.version import LooseVersion + +try: + try: + from ansible_collections.theforeman.foreman.plugins.module_utils import _apypie as apypie + except ImportError: + from plugins.module_utils import _apypie as apypie + import requests.exceptions + HAS_APYPIE = True + inflector = apypie.Inflector() +except ImportError: + HAS_APYPIE = False + APYPIE_IMP_ERR = traceback.format_exc() + +try: + import yaml + HAS_PYYAML = True +except ImportError: + HAS_PYYAML = False + PYYAML_IMP_ERR = traceback.format_exc() + +parameter_foreman_spec = dict( + id=dict(invisible=True), + name=dict(required=True), + value=dict(type='raw', required=True), + parameter_type=dict(default='string', choices=['string', 'boolean', 'integer', 'real', 'array', 'hash', 'yaml', 'json']), +) + +parameter_ansible_spec = {k: v for (k, v) in parameter_foreman_spec.items() if k != 'id'} + +_PLUGIN_RESOURCES = { + 'discovery': 'discovery_rules', + 'katello': 'subscriptions', + 'openscap': 'scap_contents', + 'remote_execution': 'remote_execution_features', + 'scc_manager': 'scc_accounts', + 'snapshot_management': 'snapshots', + 'templates': 'templates', +} + +ENTITY_KEYS = dict( + hostgroups='title', + locations='title', + operatingsystems='title', + # TODO: Organizations should be search by title (as foreman allows nested orgs) but that's not the case ATM. + # Applying this will need to record a lot of tests that is out of scope for the moment. + # organizations='title', + scap_contents='title', + scc_products='friendly_name', + users='login', +) + + +class NoEntity(object): + pass + + +def _exception2fail_json(msg='Generic failure: {0}'): + """ + Decorator to convert Python exceptions into Ansible errors that can be reported to the user. + """ + + def decor(f): + @wraps(f) + def inner(self, *args, **kwargs): + try: + return f(self, *args, **kwargs) + except Exception as e: + err_msg = "{0}: {1}".format(e.__class__.__name__, to_native(e)) + self.fail_from_exception(e, msg.format(err_msg)) + return inner + return decor + + +def _check_patch_needed(introduced_version=None, fixed_version=None, plugins=None): + """ + Decorator to check whether a specific apidoc patch is required. + + :param introduced_version: The version of Foreman the API bug was introduced. + :type introduced_version: str, optional + :param fixed_version: The version of Foreman the API bug was fixed. + :type fixed_version: str, optional + :param plugins: Which plugins are required for this patch. + :type plugins: list, optional + """ + + def decor(f): + @wraps(f) + def inner(self, *args, **kwargs): + if plugins is not None and not all(self.has_plugin(plugin) for plugin in plugins): + return + + if fixed_version is not None and self.foreman_version >= LooseVersion(fixed_version): + return + + if introduced_version is not None and self.foreman_version < LooseVersion(introduced_version): + return + + return f(self, *args, **kwargs) + return inner + return decor + + +class KatelloMixin(): + """ + Katello Mixin to extend a :class:`ForemanAnsibleModule` (or any subclass) to work with Katello entities. + + This includes: + + * add a required ``organization`` parameter to the module + * add Katello to the list of required plugins + """ + + def __init__(self, **kwargs): + foreman_spec = dict( + organization=dict(type='entity', required=True), + ) + foreman_spec.update(kwargs.pop('foreman_spec', {})) + required_plugins = kwargs.pop('required_plugins', []) + required_plugins.append(('katello', ['*'])) + super(KatelloMixin, self).__init__(foreman_spec=foreman_spec, required_plugins=required_plugins, **kwargs) + + +class TaxonomyMixin(object): + """ + Taxonomy Mixin to extend a :class:`ForemanAnsibleModule` (or any subclass) to work with taxonomic entities. + + This adds optional ``organizations`` and ``locations`` parameters to the module. + """ + + def __init__(self, **kwargs): + foreman_spec = dict( + organizations=dict(type='entity_list'), + locations=dict(type='entity_list'), + ) + foreman_spec.update(kwargs.pop('foreman_spec', {})) + super(TaxonomyMixin, self).__init__(foreman_spec=foreman_spec, **kwargs) + + +class ParametersMixin(object): + """ + Parameters Mixin to extend a :class:`ForemanAnsibleModule` (or any subclass) to work with entities that support parameters. + + This allows to submit parameters to Foreman in the same request as modifying the main entity, thus making the parameters + available to any action that might be triggered when the entity is saved. + + By default, parametes are submited to the API using the ``<entity_name>_parameters_attributes`` key. + If you need to override this, set the ``PARAMETERS_FLAT_NAME`` attribute to the key that shall be used instead. + + This adds optional ``parameters`` parameter to the module. It also enhances the ``run()`` method to properly handle the + provided parameters. + """ + + def __init__(self, **kwargs): + self.entity_name = kwargs.pop('entity_name', self.entity_name_from_class) + parameters_flat_name = getattr(self, "PARAMETERS_FLAT_NAME", None) or '{0}_parameters_attributes'.format(self.entity_name) + foreman_spec = dict( + parameters=dict(type='list', elements='dict', options=parameter_ansible_spec, flat_name=parameters_flat_name), + ) + foreman_spec.update(kwargs.pop('foreman_spec', {})) + super(ParametersMixin, self).__init__(foreman_spec=foreman_spec, **kwargs) + + def run(self, **kwargs): + entity = self.lookup_entity('entity') + if not self.desired_absent: + if entity and 'parameters' in entity: + entity['parameters'] = parameters_list_to_str_list(entity['parameters']) + parameters = self.foreman_params.get('parameters') + if parameters is not None: + self.foreman_params['parameters'] = parameters_list_to_str_list(parameters) + + return super(ParametersMixin, self).run(**kwargs) + + +class NestedParametersMixin(object): + """ + Nested Parameters Mixin to extend a :class:`ForemanAnsibleModule` (or any subclass) to work with entities that support parameters, + but require them to be managed in separate API requests. + + This adds optional ``parameters`` parameter to the module. It also enhances the ``run()`` method to properly handle the + provided parameters. + """ + + def __init__(self, **kwargs): + foreman_spec = dict( + parameters=dict(type='nested_list', foreman_spec=parameter_foreman_spec), + ) + foreman_spec.update(kwargs.pop('foreman_spec', {})) + super(NestedParametersMixin, self).__init__(foreman_spec=foreman_spec, **kwargs) + + def run(self, **kwargs): + new_entity = super(NestedParametersMixin, self).run(**kwargs) + if new_entity: + scope = {'{0}_id'.format(self.entity_name): new_entity['id']} + self.ensure_scoped_parameters(scope) + return new_entity + + def ensure_scoped_parameters(self, scope): + parameters = self.foreman_params.get('parameters') + if parameters is not None: + entity = self.lookup_entity('entity') + if self.state == 'present' or (self.state == 'present_with_defaults' and entity is None): + if entity: + current_parameters = {parameter['name']: parameter for parameter in self.list_resource('parameters', params=scope)} + else: + current_parameters = {} + desired_parameters = {parameter['name']: parameter for parameter in parameters} + + for name in desired_parameters: + desired_parameter = desired_parameters[name] + desired_parameter['value'] = parameter_value_to_str(desired_parameter['value'], desired_parameter['parameter_type']) + current_parameter = current_parameters.pop(name, None) + if current_parameter: + if 'parameter_type' not in current_parameter: + current_parameter['parameter_type'] = 'string' + current_parameter['value'] = parameter_value_to_str(current_parameter['value'], current_parameter['parameter_type']) + self.ensure_entity( + 'parameters', desired_parameter, current_parameter, state="present", foreman_spec=parameter_foreman_spec, params=scope) + for current_parameter in current_parameters.values(): + self.ensure_entity( + 'parameters', None, current_parameter, state="absent", foreman_spec=parameter_foreman_spec, params=scope) + + +class HostMixin(ParametersMixin): + """ + Host Mixin to extend a :class:`ForemanAnsibleModule` (or any subclass) to work with host-related entities (Hosts, Hostgroups). + + This adds many optional parameters that are specific to Hosts and Hostgroups to the module. + It also includes :class:`ParametersMixin`. + """ + + def __init__(self, **kwargs): + foreman_spec = dict( + compute_resource=dict(type='entity'), + compute_profile=dict(type='entity'), + domain=dict(type='entity'), + subnet=dict(type='entity'), + subnet6=dict(type='entity', resource_type='subnets'), + root_pass=dict(no_log=True), + realm=dict(type='entity'), + architecture=dict(type='entity'), + operatingsystem=dict(type='entity'), + medium=dict(aliases=['media'], type='entity'), + ptable=dict(type='entity'), + pxe_loader=dict(choices=['PXELinux BIOS', 'PXELinux UEFI', 'Grub UEFI', 'Grub2 BIOS', 'Grub2 ELF', + 'Grub2 UEFI', 'Grub2 UEFI SecureBoot', 'Grub2 UEFI HTTP', 'Grub2 UEFI HTTPS', + 'Grub2 UEFI HTTPS SecureBoot', 'iPXE Embedded', 'iPXE UEFI HTTP', 'iPXE Chain BIOS', 'iPXE Chain UEFI', 'None']), + environment=dict(type='entity'), + puppetclasses=dict(type='entity_list', resolve=False), + config_groups=dict(type='entity_list'), + puppet_proxy=dict(type='entity', resource_type='smart_proxies'), + puppet_ca_proxy=dict(type='entity', resource_type='smart_proxies'), + openscap_proxy=dict(type='entity', resource_type='smart_proxies'), + content_source=dict(type='entity', scope=['organization'], resource_type='smart_proxies'), + lifecycle_environment=dict(type='entity', scope=['organization']), + kickstart_repository=dict(type='entity', scope=['organization'], resource_type='repositories'), + content_view=dict(type='entity', scope=['organization']), + ) + foreman_spec.update(kwargs.pop('foreman_spec', {})) + required_plugins = kwargs.pop('required_plugins', []) + [ + ('katello', ['content_source', 'lifecycle_environment', 'kickstart_repository', 'content_view']), + ('openscap', ['openscap_proxy']), + ] + mutually_exclusive = kwargs.pop('mutually_exclusive', []) + [['medium', 'kickstart_repository']] + super(HostMixin, self).__init__(foreman_spec=foreman_spec, required_plugins=required_plugins, mutually_exclusive=mutually_exclusive, **kwargs) + + +class ForemanAnsibleModule(AnsibleModule): + """ Baseclass for all foreman related Ansible modules. + It handles connection parameters and adds the concept of the `foreman_spec`. + This adds automatic entities resolution based on provided attributes/ sub entities options. + + It adds the following options to foreman_spec 'entity' and 'entity_list' types: + + * search_by (str): Field used to search the sub entity. Defaults to 'name' unless `parent` was set, in which case it defaults to `title`. + * search_operator (str): Operator used to search the sub entity. Defaults to '='. For fuzzy search use '~'. + * resource_type (str): Resource type used to build API resource PATH. Defaults to pluralized entity key. + * resolve (boolean): Defaults to 'True'. If set to false, the sub entity will not be resolved automatically + * ensure (boolean): Defaults to 'True'. If set to false, it will be removed before sending data to the foreman server. + """ + + def __init__(self, **kwargs): + # State recording for changed and diff reporting + self._changed = False + self._before = defaultdict(list) + self._after = defaultdict(list) + self._after_full = defaultdict(list) + + self.foreman_spec, gen_args = _foreman_spec_helper(kwargs.pop('foreman_spec', {})) + argument_spec = dict( + server_url=dict(required=True, fallback=(env_fallback, ['FOREMAN_SERVER_URL', 'FOREMAN_SERVER', 'FOREMAN_URL'])), + username=dict(required=True, fallback=(env_fallback, ['FOREMAN_USERNAME', 'FOREMAN_USER'])), + password=dict(required=True, no_log=True, fallback=(env_fallback, ['FOREMAN_PASSWORD'])), + validate_certs=dict(type='bool', default=True, fallback=(env_fallback, ['FOREMAN_VALIDATE_CERTS'])), + ) + argument_spec.update(gen_args) + argument_spec.update(kwargs.pop('argument_spec', {})) + supports_check_mode = kwargs.pop('supports_check_mode', True) + + self.required_plugins = kwargs.pop('required_plugins', []) + + super(ForemanAnsibleModule, self).__init__(argument_spec=argument_spec, supports_check_mode=supports_check_mode, **kwargs) + + aliases = {alias for arg in argument_spec.values() for alias in arg.get('aliases', [])} + self.foreman_params = _recursive_dict_without_none(self.params, aliases) + + self.check_requirements() + + self._foremanapi_server_url = self.foreman_params.pop('server_url') + self._foremanapi_username = self.foreman_params.pop('username') + self._foremanapi_password = self.foreman_params.pop('password') + self._foremanapi_validate_certs = self.foreman_params.pop('validate_certs') + + self.task_timeout = 60 + self.task_poll = 4 + + self._thin_default = False + self.state = 'undefined' + + @contextmanager + def api_connection(self): + """ + Execute a given code block after connecting to the API. + + When the block has finished, call :func:`exit_json` to report that the module has finished to Ansible. + """ + + self.connect() + yield + self.exit_json() + + @property + def changed(self): + return self._changed + + def set_changed(self): + self._changed = True + + @_check_patch_needed(fixed_version='2.0.0') + def _patch_templates_resource_name(self): + """ + Need to support both singular and plural form. + Not checking for the templates plugin here, as the check relies on the new name. + The resource was made plural per https://projects.theforeman.org/issues/28750 + """ + if 'template' in self.foremanapi.apidoc['docs']['resources']: + self.foremanapi.apidoc['docs']['resources']['templates'] = self.foremanapi.apidoc['docs']['resources']['template'] + + @_check_patch_needed(fixed_version='1.23.0') + def _patch_location_api(self): + """This is a workaround for the broken taxonomies apidoc in foreman. + see https://projects.theforeman.org/issues/10359 + """ + + _location_organizations_parameter = { + u'validations': [], + u'name': u'organization_ids', + u'show': True, + u'description': u'\n<p>Organization IDs</p>\n', + u'required': False, + u'allow_nil': True, + u'allow_blank': False, + u'full_name': u'location[organization_ids]', + u'expected_type': u'array', + u'metadata': None, + u'validator': u'', + } + _location_methods = self.foremanapi.apidoc['docs']['resources']['locations']['methods'] + + _location_create = next(x for x in _location_methods if x['name'] == 'create') + _location_create_params_location = next(x for x in _location_create['params'] if x['name'] == 'location') + _location_create_params_location['params'].append(_location_organizations_parameter) + + _location_update = next(x for x in _location_methods if x['name'] == 'update') + _location_update_params_location = next(x for x in _location_update['params'] if x['name'] == 'location') + _location_update_params_location['params'].append(_location_organizations_parameter) + + @_check_patch_needed(fixed_version='2.2.0', plugins=['remote_execution']) + def _patch_subnet_rex_api(self): + """ + This is a workaround for the broken subnet apidoc in foreman remote execution. + See https://projects.theforeman.org/issues/19086 and https://projects.theforeman.org/issues/30651 + """ + + _subnet_rex_proxies_parameter = { + u'validations': [], + u'name': u'remote_execution_proxy_ids', + u'show': True, + u'description': u'\n<p>Remote Execution Proxy IDs</p>\n', + u'required': False, + u'allow_nil': True, + u'allow_blank': False, + u'full_name': u'subnet[remote_execution_proxy_ids]', + u'expected_type': u'array', + u'metadata': None, + u'validator': u'', + } + _subnet_methods = self.foremanapi.apidoc['docs']['resources']['subnets']['methods'] + + _subnet_create = next(x for x in _subnet_methods if x['name'] == 'create') + _subnet_create_params_subnet = next(x for x in _subnet_create['params'] if x['name'] == 'subnet') + _subnet_create_params_subnet['params'].append(_subnet_rex_proxies_parameter) + + _subnet_update = next(x for x in _subnet_methods if x['name'] == 'update') + _subnet_update_params_subnet = next(x for x in _subnet_update['params'] if x['name'] == 'subnet') + _subnet_update_params_subnet['params'].append(_subnet_rex_proxies_parameter) + + @_check_patch_needed(introduced_version='2.1.0', fixed_version='2.3.0') + def _patch_subnet_externalipam_group_api(self): + """ + This is a workaround for the broken subnet apidoc for External IPAM. + See https://projects.theforeman.org/issues/30890 + """ + + _subnet_externalipam_group_parameter = { + u'validations': [], + u'name': u'externalipam_group', + u'show': True, + u'description': u'\n<p>External IPAM group - only relevant when IPAM is set to external</p>\n', + u'required': False, + u'allow_nil': True, + u'allow_blank': False, + u'full_name': u'subnet[externalipam_group]', + u'expected_type': u'string', + u'metadata': None, + u'validator': u'', + } + _subnet_methods = self.foremanapi.apidoc['docs']['resources']['subnets']['methods'] + + _subnet_create = next(x for x in _subnet_methods if x['name'] == 'create') + _subnet_create_params_subnet = next(x for x in _subnet_create['params'] if x['name'] == 'subnet') + _subnet_create_params_subnet['params'].append(_subnet_externalipam_group_parameter) + + _subnet_update = next(x for x in _subnet_methods if x['name'] == 'update') + _subnet_update_params_subnet = next(x for x in _subnet_update['params'] if x['name'] == 'subnet') + _subnet_update_params_subnet['params'].append(_subnet_externalipam_group_parameter) + + @_check_patch_needed(fixed_version='1.24.0', plugins=['katello']) + def _patch_content_uploads_update_api(self): + """ + This is a workaround for the broken content_uploads update apidoc in Katello. + See https://projects.theforeman.org/issues/27590 + """ + + _content_upload_methods = self.foremanapi.apidoc['docs']['resources']['content_uploads']['methods'] + + _content_upload_update = next(x for x in _content_upload_methods if x['name'] == 'update') + _content_upload_update_params_id = next(x for x in _content_upload_update['params'] if x['name'] == 'id') + _content_upload_update_params_id['expected_type'] = 'string' + + _content_upload_destroy = next(x for x in _content_upload_methods if x['name'] == 'destroy') + _content_upload_destroy_params_id = next(x for x in _content_upload_destroy['params'] if x['name'] == 'id') + _content_upload_destroy_params_id['expected_type'] = 'string' + + @_check_patch_needed(plugins=['katello']) + def _patch_organization_update_api(self): + """ + This is a workaround for the broken organization update apidoc in Katello. + See https://projects.theforeman.org/issues/27538 + """ + + _organization_methods = self.foremanapi.apidoc['docs']['resources']['organizations']['methods'] + + _organization_update = next(x for x in _organization_methods if x['name'] == 'update') + _organization_update_params_organization = next(x for x in _organization_update['params'] if x['name'] == 'organization') + _organization_update_params_organization['required'] = False + + @_check_patch_needed(fixed_version='1.24.0', plugins=['katello']) + def _patch_subscription_index_api(self): + """ + This is a workaround for the broken subscriptions apidoc in Katello. + See https://projects.theforeman.org/issues/27575 + """ + + _subscription_methods = self.foremanapi.apidoc['docs']['resources']['subscriptions']['methods'] + + _subscription_index = next(x for x in _subscription_methods if x['name'] == 'index') + _subscription_index_params_organization_id = next(x for x in _subscription_index['params'] if x['name'] == 'organization_id') + _subscription_index_params_organization_id['required'] = False + + @_check_patch_needed(fixed_version='1.24.0', plugins=['katello']) + def _patch_sync_plan_api(self): + """ + This is a workaround for the broken sync_plan apidoc in Katello. + See https://projects.theforeman.org/issues/27532 + """ + + _organization_parameter = { + u'validations': [], + u'name': u'organization_id', + u'show': True, + u'description': u'\n<p>Filter sync plans by organization name or label</p>\n', + u'required': False, + u'allow_nil': False, + u'allow_blank': False, + u'full_name': u'organization_id', + u'expected_type': u'numeric', + u'metadata': None, + u'validator': u'Must be a number.', + } + + _sync_plan_methods = self.foremanapi.apidoc['docs']['resources']['sync_plans']['methods'] + + _sync_plan_add_products = next(x for x in _sync_plan_methods if x['name'] == 'add_products') + if next((x for x in _sync_plan_add_products['params'] if x['name'] == 'organization_id'), None) is None: + _sync_plan_add_products['params'].append(_organization_parameter) + + _sync_plan_remove_products = next(x for x in _sync_plan_methods if x['name'] == 'remove_products') + if next((x for x in _sync_plan_remove_products['params'] if x['name'] == 'organization_id'), None) is None: + _sync_plan_remove_products['params'].append(_organization_parameter) + + @_check_patch_needed(plugins=['katello']) + def _patch_cv_filter_rule_api(self): + """ + This is a workaround for missing params of CV Filter Rule update controller in Katello. + See https://projects.theforeman.org/issues/30908 + """ + + _content_view_filter_rule_methods = self.foremanapi.apidoc['docs']['resources']['content_view_filter_rules']['methods'] + + _content_view_filter_rule_create = next(x for x in _content_view_filter_rule_methods if x['name'] == 'create') + _content_view_filter_rule_update = next(x for x in _content_view_filter_rule_methods if x['name'] == 'update') + + for param_name in ['uuid', 'errata_ids', 'date_type', 'module_stream_ids']: + create_param = next((x for x in _content_view_filter_rule_create['params'] if x['name'] == param_name), None) + update_param = next((x for x in _content_view_filter_rule_update['params'] if x['name'] == param_name), None) + if create_param is not None and update_param is None: + _content_view_filter_rule_update['params'].append(create_param) + + def check_requirements(self): + if not HAS_APYPIE: + self.fail_json(msg=missing_required_lib("requests"), exception=APYPIE_IMP_ERR) + + @_exception2fail_json(msg="Failed to connect to Foreman server: {0}") + def connect(self): + """ + Connect to the Foreman API. + + This will create a new ``apypie.Api`` instance using the provided server information, + check that the API is actually reachable (by calling :func:`status`), + apply any required patches to the apidoc and ensure the server has all the plugins installed + that are required by the module. + """ + + self.foremanapi = apypie.Api( + uri=self._foremanapi_server_url, + username=to_bytes(self._foremanapi_username), + password=to_bytes(self._foremanapi_password), + api_version=2, + verify_ssl=self._foremanapi_validate_certs, + ) + + _status = self.status() + self.foreman_version = LooseVersion(_status.get('version', '0.0.0')) + self.apply_apidoc_patches() + self.check_required_plugins() + + def apply_apidoc_patches(self): + """ + Apply patches to the local apidoc representation. + When adding another patch, consider that the endpoint in question may depend on a plugin to be available. + If possible, make the patch only execute on specific server/plugin versions. + """ + + self._patch_templates_resource_name() + self._patch_location_api() + self._patch_subnet_rex_api() + self._patch_subnet_externalipam_group_api() + + # Katello + self._patch_content_uploads_update_api() + self._patch_organization_update_api() + self._patch_subscription_index_api() + self._patch_sync_plan_api() + self._patch_cv_filter_rule_api() + + @_exception2fail_json(msg="Failed to connect to Foreman server: {0}") + def status(self): + """ + Call the ``status`` API endpoint to ensure the server is reachable. + + :return: The full API response + :rtype: dict + """ + + return self.foremanapi.resource('home').call('status') + + def _resource(self, resource): + if resource not in self.foremanapi.resources: + raise Exception("The server doesn't know about {0}, is the right plugin installed?".format(resource)) + return self.foremanapi.resource(resource) + + def _resource_call(self, resource, *args, **kwargs): + return self._resource(resource).call(*args, **kwargs) + + def _resource_prepare_params(self, resource, action, params): + api_action = self._resource(resource).action(action) + return api_action.prepare_params(params) + + @_exception2fail_json(msg='Failed to show resource: {0}') + def show_resource(self, resource, resource_id, params=None): + """ + Execute the ``show`` action on an entity. + + :param resource: Plural name of the api resource to show + :type resource: str + :param resource_id: The ID of the entity to show + :type resource_id: int + :param params: Lookup parameters (i.e. parent_id for nested entities) + :type params: Union[dict,None], optional + """ + + if params is None: + params = {} + else: + params = params.copy() + + params['id'] = resource_id + + params = self._resource_prepare_params(resource, 'show', params) + + return self._resource_call(resource, 'show', params) + + @_exception2fail_json(msg='Failed to list resource: {0}') + def list_resource(self, resource, search=None, params=None): + """ + Execute the ``index`` action on an resource. + + :param resource: Plural name of the api resource to show + :type resource: str + :param search: Search string as accepted by the API to limit the results + :type search: str, optional + :param params: Lookup parameters (i.e. parent_id for nested entities) + :type params: Union[dict,None], optional + """ + + if params is None: + params = {} + else: + params = params.copy() + + if search is not None: + params['search'] = search + params['per_page'] = 2 << 31 + + params = self._resource_prepare_params(resource, 'index', params) + + return self._resource_call(resource, 'index', params)['results'] + + def find_resource(self, resource, search, params=None, failsafe=False, thin=None): + list_params = {} + if params is not None: + list_params.update(params) + if thin is None: + thin = self._thin_default + list_params['thin'] = thin + results = self.list_resource(resource, search, list_params) + if len(results) == 1: + result = results[0] + elif failsafe: + result = None + else: + if len(results) > 1: + error_msg = "too many ({0})".format(len(results)) + else: + error_msg = "no" + self.fail_json(msg="Found {0} results while searching for {1} with {2}".format(error_msg, resource, search)) + if result and not thin: + result = self.show_resource(resource, result['id'], params=params) + return result + + def find_resource_by(self, resource, search_field, value, **kwargs): + if not value: + return NoEntity + search = '{0}{1}"{2}"'.format(search_field, kwargs.pop('search_operator', '='), value) + return self.find_resource(resource, search, **kwargs) + + def find_resource_by_name(self, resource, name, **kwargs): + return self.find_resource_by(resource, 'name', name, **kwargs) + + def find_resource_by_title(self, resource, title, **kwargs): + return self.find_resource_by(resource, 'title', title, **kwargs) + + def find_resource_by_id(self, resource, obj_id, **kwargs): + return self.find_resource_by(resource, 'id', obj_id, **kwargs) + + def find_resources_by_name(self, resource, names, **kwargs): + return [self.find_resource_by_name(resource, name, **kwargs) for name in names] + + def find_operatingsystem(self, name, failsafe=False, **kwargs): + result = self.find_resource_by_title('operatingsystems', name, failsafe=True, **kwargs) + if not result: + result = self.find_resource_by('operatingsystems', 'title', name, search_operator='~', failsafe=failsafe, **kwargs) + return result + + def find_puppetclass(self, name, environment=None, params=None, failsafe=False, thin=None): + if thin is None: + thin = self._thin_default + if environment: + scope = {'environment_id': environment} + else: + scope = {} + if params is not None: + scope.update(params) + search = 'name="{0}"'.format(name) + results = self.list_resource('puppetclasses', search, params=scope) + + # verify that only one puppet module is returned with only one puppet class inside + # as provided search results have to be like "results": { "ntp": [{"id": 1, "name": "ntp" ...}]} + # and get the puppet class id + if len(results) == 1 and len(list(results.values())[0]) == 1: + result = list(results.values())[0][0] + if thin: + return {'id': result['id']} + else: + return result + + if failsafe: + return None + else: + self.fail_json(msg='No data found for name="%s"' % search) + + def find_puppetclasses(self, names, **kwargs): + return [self.find_puppetclass(name, **kwargs) for name in names] + + def scope_for(self, key): + return {'{0}_id'.format(key): self.lookup_entity(key)['id']} + + def set_entity(self, key, entity): + self.foreman_params[key] = entity + self.foreman_spec[key]['resolved'] = True + + def lookup_entity(self, key, params=None): + if key not in self.foreman_params: + return None + + entity_spec = self.foreman_spec[key] + if entity_spec.get('resolved') or entity_spec.get('type') not in ('entity', 'entity_list'): + # Already looked up or not an entity(_list) so nothing to do + return self.foreman_params[key] + + result = self._lookup_entity(self.foreman_params[key], entity_spec, params) + self.set_entity(key, result) + return result + + def _lookup_entity(self, identifier, entity_spec, params=None): + resource_type = entity_spec['resource_type'] + failsafe = entity_spec.get('failsafe', False) + thin = entity_spec.get('thin', True) + if params: + params = params.copy() + else: + params = {} + try: + if 'scope' in entity_spec: + for scope in entity_spec['scope']: + params.update(self.scope_for(scope)) + except TypeError: + if failsafe: + if entity_spec.get('type') == 'entity': + result = None + else: + result = [None for value in identifier] + else: + self.fail_json(msg="Failed to lookup scope {0} while searching for {1}.".format(entity_spec['scope'], resource_type)) + else: + # No exception happend => scope is in place + if resource_type == 'operatingsystems': + if entity_spec.get('type') == 'entity': + result = self.find_operatingsystem(identifier, params=params, failsafe=failsafe, thin=thin) + else: + result = [self.find_operatingsystem(value, params=params, failsafe=failsafe, thin=thin) for value in identifier] + elif resource_type == 'puppetclasses': + if entity_spec.get('type') == 'entity': + result = self.find_puppetclass(identifier, params=params, failsafe=failsafe, thin=thin) + else: + result = [self.find_puppetclass(value, params=params, failsafe=failsafe, thin=thin) for value in identifier] + else: + if entity_spec.get('type') == 'entity': + result = self.find_resource_by( + resource=resource_type, + value=identifier, + search_field=entity_spec.get('search_by', ENTITY_KEYS.get(resource_type, 'name')), + search_operator=entity_spec.get('search_operator', '='), + failsafe=failsafe, thin=thin, params=params, + ) + else: + result = [self.find_resource_by( + resource=resource_type, + value=value, + search_field=entity_spec.get('search_by', ENTITY_KEYS.get(resource_type, 'name')), + search_operator=entity_spec.get('search_operator', '='), + failsafe=failsafe, thin=thin, params=params, + ) for value in identifier] + return result + + def auto_lookup_entities(self): + self.auto_lookup_nested_entities() + return [ + self.lookup_entity(key) + for key, entity_spec in self.foreman_spec.items() + if entity_spec.get('resolve', True) and entity_spec.get('type') in {'entity', 'entity_list'} + ] + + def auto_lookup_nested_entities(self): + for key, entity_spec in self.foreman_spec.items(): + if entity_spec.get('type') in {'nested_list'}: + for nested_key, nested_spec in self.foreman_spec[key]['foreman_spec'].items(): + for item in self.foreman_params.get(key, []): + if (nested_key in item and nested_spec.get('resolve', True) + and item[nested_key] is not None and not isinstance(item[nested_key], (dict, list)) + and nested_spec.get('type') in {'entity', 'entity_list'}): + item[nested_key] = self._lookup_entity(item[nested_key], nested_spec) + + def record_before(self, resource, entity): + self._before[resource].append(entity) + + def record_after(self, resource, entity): + self._after[resource].append(entity) + + def record_after_full(self, resource, entity): + self._after_full[resource].append(entity) + + @_exception2fail_json(msg='Failed to ensure entity state: {0}') + def ensure_entity(self, resource, desired_entity, current_entity, params=None, state=None, foreman_spec=None): + """ + Ensure that a given entity has a certain state + + :param resource: Plural name of the api resource to manipulate + :type resource: str + :param desired_entity: Desired properties of the entity + :type desired_entity: dict + :param current_entity: Current properties of the entity or None if nonexistent + :type current_entity: Union[dict,None] + :param params: Lookup parameters (i.e. parent_id for nested entities) + :type params: dict, optional + :param state: Desired state of the entity (optionally taken from the module) + :type state: str, optional + :param foreman_spec: Description of the entity structure (optionally taken from module) + :type foreman_spec: dict, optional + + :return: The new current state of the entity + :rtype: Union[dict,None] + """ + if state is None: + state = self.state + if foreman_spec is None: + foreman_spec = self.foreman_spec + else: + foreman_spec, _dummy = _foreman_spec_helper(foreman_spec) + + updated_entity = None + + self.record_before(resource, _flatten_entity(current_entity, foreman_spec)) + + if state == 'present_with_defaults': + if current_entity is None: + updated_entity = self._create_entity(resource, desired_entity, params, foreman_spec) + elif state == 'present': + if current_entity is None: + updated_entity = self._create_entity(resource, desired_entity, params, foreman_spec) + else: + updated_entity = self._update_entity(resource, desired_entity, current_entity, params, foreman_spec) + elif state == 'copied': + if current_entity is not None: + updated_entity = self._copy_entity(resource, desired_entity, current_entity, params) + elif state == 'reverted': + if current_entity is not None: + updated_entity = self._revert_entity(resource, current_entity, params) + elif state == 'absent': + if current_entity is not None: + updated_entity = self._delete_entity(resource, current_entity, params) + else: + self.fail_json(msg='Not a valid state: {0}'.format(state)) + + self.record_after(resource, _flatten_entity(updated_entity, foreman_spec)) + self.record_after_full(resource, updated_entity) + + return updated_entity + + def _validate_supported_payload(self, resource, action, payload): + """ + Check whether the payload only contains supported keys. + Emits a warning for keys that are not part of the apidoc. + + :param resource: Plural name of the api resource to check + :type resource: str + :param action: Name of the action to check payload against + :type action: str + :param payload: API paylod to be checked + :type payload: dict + + :return: The payload as it can be submitted to the API + :rtype: dict + """ + filtered_payload = self._resource_prepare_params(resource, action, payload) + # On Python 2 dict.keys() is just a list, but we need a set here. + unsupported_parameters = set(payload.keys()) - set(_recursive_dict_keys(filtered_payload)) + if unsupported_parameters: + warn_msg = "The following parameters are not supported by your server when performing {0} on {1}: {2}. They were ignored." + self.warn(warn_msg.format(action, resource, unsupported_parameters)) + return filtered_payload + + def _create_entity(self, resource, desired_entity, params, foreman_spec): + """ + Create entity with given properties + + :param resource: Plural name of the api resource to manipulate + :type resource: str + :param desired_entity: Desired properties of the entity + :type desired_entity: dict + :param params: Lookup parameters (i.e. parent_id for nested entities) + :type params: dict, optional + :param foreman_spec: Description of the entity structure + :type foreman_spec: dict + + :return: The new current state of the entity + :rtype: dict + """ + payload = _flatten_entity(desired_entity, foreman_spec) + self._validate_supported_payload(resource, 'create', payload) + if not self.check_mode: + if params: + payload.update(params) + return self.resource_action(resource, 'create', payload) + else: + fake_entity = desired_entity.copy() + fake_entity['id'] = -1 + self.set_changed() + return fake_entity + + def _update_entity(self, resource, desired_entity, current_entity, params, foreman_spec): + """ + Update a given entity with given properties if any diverge + + :param resource: Plural name of the api resource to manipulate + :type resource: str + :param desired_entity: Desired properties of the entity + :type desired_entity: dict + :param current_entity: Current properties of the entity + :type current_entity: dict + :param params: Lookup parameters (i.e. parent_id for nested entities) + :type params: dict, optional + :param foreman_spec: Description of the entity structure + :type foreman_spec: dict + + :return: The new current state of the entity + :rtype: dict + """ + payload = {} + desired_entity = _flatten_entity(desired_entity, foreman_spec) + current_entity = _flatten_entity(current_entity, foreman_spec) + for key, value in desired_entity.items(): + foreman_type = foreman_spec[key].get('type', 'str') + new_value = value + old_value = current_entity.get(key) + # String comparison needs extra care in face of unicode + if foreman_type == 'str': + old_value = to_native(old_value) + new_value = to_native(new_value) + # ideally the type check would happen via foreman_spec.elements + # however this is not set for flattened entries and setting it + # confuses _flatten_entity + elif foreman_type == 'list' and value and isinstance(value[0], dict): + if 'name' in value[0]: + sort_key = 'name' + else: + sort_key = list(value[0].keys())[0] + new_value = sorted(new_value, key=operator.itemgetter(sort_key)) + old_value = sorted(old_value, key=operator.itemgetter(sort_key)) + if new_value != old_value: + payload[key] = value + if self._validate_supported_payload(resource, 'update', payload): + payload['id'] = current_entity['id'] + if not self.check_mode: + if params: + payload.update(params) + return self.resource_action(resource, 'update', payload) + else: + # In check_mode we emulate the server updating the entity + fake_entity = current_entity.copy() + fake_entity.update(payload) + self.set_changed() + return fake_entity + else: + # Nothing needs changing + return current_entity + + def _copy_entity(self, resource, desired_entity, current_entity, params): + """ + Copy a given entity + + :param resource: Plural name of the api resource to manipulate + :type resource: str + :param desired_entity: Desired properties of the entity + :type desired_entity: dict + :param current_entity: Current properties of the entity + :type current_entity: dict + :param params: Lookup parameters (i.e. parent_id for nested entities) + :type params: dict, optional + + :return: The new current state of the entity + :rtype: dict + """ + payload = { + 'id': current_entity['id'], + 'new_name': desired_entity['new_name'], + } + if params: + payload.update(params) + return self.resource_action(resource, 'copy', payload) + + def _revert_entity(self, resource, current_entity, params): + """ + Revert a given entity + + :param resource: Plural name of the api resource to manipulate + :type resource: str + :param current_entity: Current properties of the entity + :type current_entity: dict + :param params: Lookup parameters (i.e. parent_id for nested entities) + :type params: dict, optional + + :return: The new current state of the entity + :rtype: dict + """ + payload = {'id': current_entity['id']} + if params: + payload.update(params) + return self.resource_action(resource, 'revert', payload) + + def _delete_entity(self, resource, current_entity, params): + """ + Delete a given entity + + :param resource: Plural name of the api resource to manipulate + :type resource: str + :param current_entity: Current properties of the entity + :type current_entity: dict + :param params: Lookup parameters (i.e. parent_id for nested entities) + :type params: dict, optional + + :return: The new current state of the entity + :rtype: Union[dict,None] + """ + payload = {'id': current_entity['id']} + if params: + payload.update(params) + entity = self.resource_action(resource, 'destroy', payload) + + # this is a workaround for https://projects.theforeman.org/issues/26937 + if entity and isinstance(entity, dict) and 'error' in entity and 'message' in entity['error']: + self.fail_json(msg=entity['error']['message']) + + return None + + def resource_action(self, resource, action, params, options=None, data=None, files=None, + ignore_check_mode=False, record_change=True, ignore_task_errors=False): + resource_payload = self._resource_prepare_params(resource, action, params) + if options is None: + options = {} + try: + result = None + if ignore_check_mode or not self.check_mode: + result = self._resource_call(resource, action, resource_payload, options=options, data=data, files=files) + is_foreman_task = isinstance(result, dict) and 'action' in result and 'state' in result and 'started_at' in result + if is_foreman_task: + result = self.wait_for_task(result, ignore_errors=ignore_task_errors) + except Exception as e: + msg = 'Error while performing {0} on {1}: {2}'.format( + action, resource, to_native(e)) + self.fail_from_exception(e, msg) + if record_change and not ignore_check_mode: + # If we were supposed to ignore check_mode we can assume this action was not a changing one. + self.set_changed() + return result + + def wait_for_task(self, task, ignore_errors=False): + duration = self.task_timeout + while task['state'] not in ['paused', 'stopped']: + duration -= self.task_poll + if duration <= 0: + self.fail_json(msg="Timout waiting for Task {0}".format(task['id'])) + time.sleep(self.task_poll) + + resource_payload = self._resource_prepare_params('foreman_tasks', 'show', {'id': task['id']}) + task = self._resource_call('foreman_tasks', 'show', resource_payload) + if not ignore_errors and task['result'] != 'success': + self.fail_json(msg='Task {0}({1}) did not succeed. Task information: {2}'.format(task['action'], task['id'], task['humanized']['errors'])) + return task + + def fail_from_exception(self, exc, msg): + fail = {'msg': msg} + if isinstance(exc, requests.exceptions.HTTPError): + try: + response = exc.response.json() + if 'error' in response: + fail['error'] = response['error'] + else: + fail['error'] = response + except Exception: + fail['error'] = exc.response.text + self.fail_json(**fail) + + def exit_json(self, changed=False, **kwargs): + kwargs['changed'] = changed or self.changed + if 'diff' not in kwargs and (self._before or self._after): + kwargs['diff'] = {'before': self._before, + 'after': self._after} + if 'entity' not in kwargs and self._after_full: + kwargs['entity'] = self._after_full + super(ForemanAnsibleModule, self).exit_json(**kwargs) + + def has_plugin(self, plugin_name): + try: + resource_name = _PLUGIN_RESOURCES[plugin_name] + except KeyError: + raise Exception("Unknown plugin: {0}".format(plugin_name)) + return resource_name in self.foremanapi.resources + + def check_required_plugins(self): + missing_plugins = [] + for (plugin, params) in self.required_plugins: + for param in params: + if (param in self.foreman_params or param == '*') and not self.has_plugin(plugin): + if param == '*': + param = 'the whole module' + missing_plugins.append("{0} (for {1})".format(plugin, param)) + if missing_plugins: + missing_msg = "The server is missing required plugins: {0}.".format(', '.join(missing_plugins)) + self.fail_json(msg=missing_msg) + + +class ForemanStatelessEntityAnsibleModule(ForemanAnsibleModule): + """ Base class for Foreman entities without a state. To use it, subclass it with the following convention: + To manage my_entity entity, create the following sub class:: + + class ForemanMyEntityModule(ForemanStatelessEntityAnsibleModule): + pass + + and use that class to instantiate module:: + + module = ForemanMyEntityModule( + argument_spec=dict( + [...] + ), + foreman_spec=dict( + [...] + ), + ) + + It adds the following attributes: + + * entity_key (str): field used to search current entity. Defaults to value provided by `ENTITY_KEYS` or 'name' if no value found. + * entity_name (str): name of the current entity. + By default deduce the entity name from the class name (eg: 'ForemanProvisioningTemplateModule' class will produce 'provisioning_template'). + * entity_opts (dict): Dict of options for base entity. Same options can be provided for subentities described in foreman_spec. + + The main entity is referenced with the key `entity` in the `foreman_spec`. + """ + + def __init__(self, **kwargs): + self.entity_key = kwargs.pop('entity_key', 'name') + self.entity_name = kwargs.pop('entity_name', self.entity_name_from_class) + entity_opts = kwargs.pop('entity_opts', {}) + + super(ForemanStatelessEntityAnsibleModule, self).__init__(**kwargs) + + if 'resource_type' not in entity_opts: + entity_opts['resource_type'] = inflector.pluralize(self.entity_name) + if 'thin' not in entity_opts: + # Explicit None to trigger the _thin_default mechanism lazily + entity_opts['thin'] = None + if 'failsafe' not in entity_opts: + entity_opts['failsafe'] = True + if 'search_operator' not in entity_opts: + entity_opts['search_operator'] = '=' + if 'search_by' not in entity_opts: + entity_opts['search_by'] = ENTITY_KEYS.get(entity_opts['resource_type'], 'name') + + self.foreman_spec.update(_foreman_spec_helper(dict( + entity=dict( + type='entity', + flat_name='id', + ensure=False, + **entity_opts + ), + ))[0]) + + if 'parent' in self.foreman_spec and self.foreman_spec['parent'].get('type') == 'entity': + if 'resouce_type' not in self.foreman_spec['parent']: + self.foreman_spec['parent']['resource_type'] = self.foreman_spec['entity']['resource_type'] + current, parent = split_fqn(self.foreman_params[self.entity_key]) + if isinstance(self.foreman_params.get('parent'), six.string_types): + if parent: + self.fail_json(msg="Please specify the parent either separately, or as part of the title.") + parent = self.foreman_params['parent'] + elif parent: + self.foreman_params['parent'] = parent + self.foreman_params[self.entity_key] = current + self.foreman_params['entity'] = build_fqn(current, parent) + else: + self.foreman_params['entity'] = self.foreman_params.get(self.entity_key) + + @property + def entity_name_from_class(self): + """ + The entity name derived from the class name. + + The class name must follow the following name convention: + + * It starts with ``Foreman`` or ``Katello``. + * It ends with ``Module``. + + This will convert the class name ``ForemanMyEntityModule`` to the entity name ``my_entity``. + + Examples: + + * ``ForemanArchitectureModule`` => ``architecture`` + * ``ForemanProvisioningTemplateModule`` => ``provisioning_template`` + * ``KatelloProductMudule`` => ``product`` + """ + # Convert current class name from CamelCase to snake_case + class_name = re.sub(r'(?<=[a-z])[A-Z]|[A-Z](?=[^A-Z])', r'_\g<0>', self.__class__.__name__).lower().strip('_') + # Get entity name from snake case class name + return '_'.join(class_name.split('_')[1:-1]) + + +class ForemanEntityAnsibleModule(ForemanStatelessEntityAnsibleModule): + """ Base class for Foreman entities. To use it, subclass it with the following convention: + To manage my_entity entity, create the following sub class:: + + class ForemanMyEntityModule(ForemanEntityAnsibleModule): + pass + + and use that class to instantiate module:: + + module = ForemanMyEntityModule( + argument_spec=dict( + [...] + ), + foreman_spec=dict( + [...] + ), + ) + + This adds a `state` parameter to the module and provides the `run` method for the most + common usecases. + """ + + def __init__(self, **kwargs): + argument_spec = dict( + state=dict(choices=['present', 'absent'], default='present'), + ) + argument_spec.update(kwargs.pop('argument_spec', {})) + super(ForemanEntityAnsibleModule, self).__init__(argument_spec=argument_spec, **kwargs) + + self.state = self.foreman_params.pop('state') + self.desired_absent = self.state == 'absent' + self._thin_default = self.desired_absent + + def run(self, **kwargs): + """ lookup entities, ensure entity, remove sensitive data, manage parameters. + """ + if ('parent' in self.foreman_spec and self.foreman_spec['parent'].get('type') == 'entity' + and self.desired_absent and 'parent' in self.foreman_params and self.lookup_entity('parent') is None): + # Parent does not exist so just exit here + return None + if not self.desired_absent: + self.auto_lookup_entities() + entity = self.lookup_entity('entity') + + if not self.desired_absent: + updated_key = "updated_" + self.entity_key + if entity and updated_key in self.foreman_params: + self.foreman_params[self.entity_key] = self.foreman_params.pop(updated_key) + + params = kwargs.get('params', {}) + entity_scope = self.foreman_spec['entity'].get('scope') + if entity_scope: + for scope in entity_scope: + params.update(self.scope_for(scope)) + new_entity = self.ensure_entity(self.foreman_spec['entity']['resource_type'], self.foreman_params, entity, params=params) + new_entity = self.remove_sensitive_fields(new_entity) + + return new_entity + + def remove_sensitive_fields(self, entity): + """ Set fields with 'no_log' option to None """ + if entity: + for blacklisted_field in self.blacklisted_fields: + entity[blacklisted_field] = None + return entity + + @property + def blacklisted_fields(self): + return [key for key, value in self.foreman_spec.items() if value.get('no_log', False)] + + +class ForemanTaxonomicAnsibleModule(TaxonomyMixin, ForemanAnsibleModule): + """ + Combine :class:`ForemanAnsibleModule` with the :class:`TaxonomyMixin` Mixin. + """ + + pass + + +class ForemanTaxonomicEntityAnsibleModule(TaxonomyMixin, ForemanEntityAnsibleModule): + """ + Combine :class:`ForemanEntityAnsibleModule` with the :class:`TaxonomyMixin` Mixin. + """ + + pass + + +class ForemanScapDataStreamModule(ForemanTaxonomicEntityAnsibleModule): + def __init__(self, **kwargs): + foreman_spec = dict( + original_filename=dict(type='str'), + scap_file=dict(type='path'), + ) + foreman_spec.update(kwargs.pop('foreman_spec', {})) + super(ForemanScapDataStreamModule, self).__init__(foreman_spec=foreman_spec, **kwargs) + + def run(self, **kwargs): + entity = self.lookup_entity('entity') + + if not self.desired_absent: + if not entity and 'scap_file' not in self.foreman_params: + self.fail_json(msg="Content of scap_file not provided. XML containing SCAP content is required.") + + if 'scap_file' in self.foreman_params and 'original_filename' not in self.foreman_params: + self.foreman_params['original_filename'] = os.path.basename(self.foreman_params['scap_file']) + + if 'scap_file' in self.foreman_params: + with open(self.foreman_params['scap_file']) as input_file: + self.foreman_params['scap_file'] = input_file.read() + + if entity and 'scap_file' in self.foreman_params: + digest = hashlib.sha256(self.foreman_params['scap_file'].encode("utf-8")).hexdigest() + # workaround for https://projects.theforeman.org/issues/29409 + digest_stripped = hashlib.sha256(self.foreman_params['scap_file'].strip().encode("utf-8")).hexdigest() + if entity['digest'] in [digest, digest_stripped]: + self.foreman_params.pop('scap_file') + + super(ForemanScapDataStreamModule, self).run(**kwargs) + + +class KatelloAnsibleModule(KatelloMixin, ForemanAnsibleModule): + """ + Combine :class:`ForemanAnsibleModule` with the :class:`KatelloMixin` Mixin. + """ + + pass + + +class KatelloEntityAnsibleModule(KatelloMixin, ForemanEntityAnsibleModule): + """ + Combine :class:`ForemanEntityAnsibleModule` with the :class:`KatelloMixin` Mixin. + + Enforces scoping of entities by ``organization`` as required by Katello. + """ + + def __init__(self, **kwargs): + entity_opts = kwargs.pop('entity_opts', {}) + if 'scope' not in entity_opts: + entity_opts['scope'] = ['organization'] + elif 'organization' not in entity_opts['scope']: + entity_opts['scope'].append('organization') + super(KatelloEntityAnsibleModule, self).__init__(entity_opts=entity_opts, **kwargs) + + +def _foreman_spec_helper(spec): + """Extend an entity spec by adding entries for all flat_names. + Extract Ansible compatible argument_spec on the way. + """ + foreman_spec = {} + argument_spec = {} + + _FILTER_SPEC_KEYS = { + 'ensure', + 'failsafe', + 'flat_name', + 'foreman_spec', + 'invisible', + 'resolve', + 'resource_type', + 'scope', + 'search_by', + 'search_operator', + 'thin', + 'type', + } + _VALUE_SPEC_KEYS = { + 'ensure', + 'type', + } + _ENTITY_SPEC_KEYS = { + 'failsafe', + 'resolve', + 'resource_type', + 'scope', + 'search_by', + 'search_operator', + 'thin', + } + + # _foreman_spec_helper() is called before we call check_requirements() in the __init__ of ForemanAnsibleModule + # and thus before the if HAS APYPIE check happens. + # We have to ensure that apypie is available before using it. + # There is two cases where we can call _foreman_spec_helper() without apypie available: + # * When the user calls the module but doesn't have the right Python libraries installed. + # In this case nothing will works and the module will warn the user to install the required library. + # * When Ansible generates docs from the argument_spec. As the inflector is only used to build foreman_spec and not argument_spec, + # This is not a problem. + # + # So in conclusion, we only have to verify that apypie is available before using it. + # Lazy evaluation helps there. + for key, value in spec.items(): + foreman_value = {k: v for (k, v) in value.items() if k in _VALUE_SPEC_KEYS} + argument_value = {k: v for (k, v) in value.items() if k not in _FILTER_SPEC_KEYS} + + foreman_type = value.get('type') + ansible_invisible = value.get('invisible', False) + flat_name = value.get('flat_name') + + if foreman_type == 'entity': + if not flat_name: + flat_name = '{0}_id'.format(key) + foreman_value['resource_type'] = HAS_APYPIE and inflector.pluralize(key) + foreman_value.update({k: v for (k, v) in value.items() if k in _ENTITY_SPEC_KEYS}) + elif foreman_type == 'entity_list': + argument_value['type'] = 'list' + argument_value['elements'] = value.get('elements', 'str') + if not flat_name: + flat_name = '{0}_ids'.format(HAS_APYPIE and inflector.singularize(key)) + foreman_value['resource_type'] = key + foreman_value.update({k: v for (k, v) in value.items() if k in _ENTITY_SPEC_KEYS}) + elif foreman_type == 'nested_list': + argument_value['type'] = 'list' + argument_value['elements'] = 'dict' + foreman_value['foreman_spec'], argument_value['options'] = _foreman_spec_helper(value['foreman_spec']) + foreman_value['ensure'] = value.get('ensure', False) + elif foreman_type: + argument_value['type'] = foreman_type + + if flat_name: + foreman_value['flat_name'] = flat_name + foreman_spec[flat_name] = {} + # When translating to a flat name, the flattened entry should get the same "type" + # as Ansible expects so that comparison still works for non-strings + if argument_value.get('type') is not None: + foreman_spec[flat_name]['type'] = argument_value['type'] + + foreman_spec[key] = foreman_value + + if not ansible_invisible: + argument_spec[key] = argument_value + + return foreman_spec, argument_spec + + +def _flatten_entity(entity, foreman_spec): + """Flatten entity according to spec""" + result = {} + if entity is None: + entity = {} + for key, value in entity.items(): + if key in foreman_spec and foreman_spec[key].get('ensure', True) and value is not None: + spec = foreman_spec[key] + flat_name = spec.get('flat_name', key) + property_type = spec.get('type', 'str') + if property_type == 'entity': + if value is not NoEntity: + result[flat_name] = value['id'] + else: + result[flat_name] = None + elif property_type == 'entity_list': + result[flat_name] = sorted(val['id'] for val in value) + elif property_type == 'nested_list': + result[flat_name] = [_flatten_entity(ent, foreman_spec[key]['foreman_spec']) for ent in value] + else: + result[flat_name] = value + return result + + +def _recursive_dict_keys(a_dict): + """Find all keys of a nested dictionary""" + keys = set(a_dict.keys()) + for _k, v in a_dict.items(): + if isinstance(v, dict): + keys.update(_recursive_dict_keys(v)) + return keys + + +def _recursive_dict_without_none(a_dict, exclude=None): + """ + Remove all entries with `None` value from a dict, recursively. + Also drops all entries with keys in `exclude` in the top level. + """ + if exclude is None: + exclude = [] + + result = {} + + for (k, v) in a_dict.items(): + if v is not None and k not in exclude: + if isinstance(v, dict): + v = _recursive_dict_without_none(v) + elif isinstance(v, list) and v and isinstance(v[0], dict): + v = [_recursive_dict_without_none(element) for element in v] + result[k] = v + + return result + + +# Helper for (global, operatingsystem, ...) parameters +def parameter_value_to_str(value, parameter_type): + """Helper to convert the value of parameters to string according to their parameter_type.""" + if parameter_type in ['real', 'integer']: + parameter_string = str(value) + elif parameter_type in ['array', 'hash', 'yaml', 'json']: + parameter_string = json.dumps(value, sort_keys=True) + else: + parameter_string = value + return parameter_string + + +# Helper for converting lists of parameters +def parameters_list_to_str_list(parameters): + filtered_params = [] + for param in parameters: + new_param = {k: v for (k, v) in param.items() if k in parameter_ansible_spec.keys()} + new_param['value'] = parameter_value_to_str(new_param['value'], new_param['parameter_type']) + filtered_params.append(new_param) + return filtered_params + + +# Helper for templates +def parse_template(template_content, module): + if not HAS_PYYAML: + module.fail_json(msg=missing_required_lib("PyYAML"), exception=PYYAML_IMP_ERR) + + try: + template_dict = {} + data = re.search( + r'<%#([^%]*([^%]*%*[^>%])*%*)%>', template_content) + if data: + datalist = data.group(1) + if datalist[-1] == '-': + datalist = datalist[:-1] + template_dict = yaml.safe_load(datalist) + # No metadata, import template anyway + template_dict['template'] = template_content + except Exception as e: + module.fail_json(msg='Error while parsing template: ' + to_native(e)) + return template_dict + + +def parse_template_from_file(file_name, module): + try: + with open(file_name) as input_file: + template_content = input_file.read() + template_dict = parse_template(template_content, module) + except Exception as e: + module.fail_json(msg='Error while reading template file: ' + to_native(e)) + return template_dict + + +# Helper for titles +def split_fqn(title): + """ Split fully qualified name (title) in name and parent title """ + fqn = title.split('/') + if len(fqn) > 1: + name = fqn.pop() + return (name, '/'.join(fqn)) + else: + return (title, None) + + +def build_fqn(name, parent=None): + if parent: + return "%s/%s" % (parent, name) + else: + return name + + +# Helper for puppetclasses +def ensure_puppetclasses(module, entity_type, entity, expected_puppetclasses=None): + puppetclasses_resource = '{0}_classes'.format(entity_type) + if expected_puppetclasses: + expected_puppetclasses = module.find_puppetclasses(expected_puppetclasses, environment=entity['environment_id'], thin=True) + current_puppetclasses = entity.pop('puppetclass_ids', []) + if expected_puppetclasses: + for puppetclass in expected_puppetclasses: + if puppetclass['id'] in current_puppetclasses: + current_puppetclasses.remove(puppetclass['id']) + else: + payload = {'{0}_id'.format(entity_type): entity['id'], 'puppetclass_id': puppetclass['id']} + module.ensure_entity(puppetclasses_resource, {}, None, params=payload, state='present', foreman_spec={}) + if len(current_puppetclasses) > 0: + for leftover_puppetclass in current_puppetclasses: + module.ensure_entity(puppetclasses_resource, {}, {'id': leftover_puppetclass}, {'hostgroup_id': entity['id']}, state='absent', foreman_spec={}) + + +# Helper constants +OS_LIST = ['AIX', + 'Altlinux', + 'Archlinux', + 'Coreos', + 'Debian', + 'Freebsd', + 'Gentoo', + 'Junos', + 'NXOS', + 'Rancheros', + 'Redhat', + 'Solaris', + 'Suse', + 'Windows', + 'Xenserver', + ] + +TEMPLATE_KIND_LIST = [ + 'Bootdisk', + 'cloud-init', + 'finish', + 'iPXE', + 'job_template', + 'kexec', + 'POAP', + 'provision', + 'ptable', + 'PXEGrub', + 'PXEGrub2', + 'PXELinux', + 'registration', + 'script', + 'user_data', + 'ZTP', +] + +# interface specs +interfaces_spec = dict( + id=dict(invisible=True), + mac=dict(), + ip=dict(), + ip6=dict(), + type=dict(choices=['interface', 'bmc', 'bond', 'bridge']), + name=dict(), + subnet=dict(type='entity'), + subnet6=dict(type='entity', resource_type='subnets'), + domain=dict(type='entity'), + identifier=dict(), + managed=dict(type='bool'), + primary=dict(type='bool'), + provision=dict(type='bool'), + username=dict(), + password=dict(no_log=True), + provider=dict(choices=['IPMI', 'SSH']), + virtual=dict(type='bool'), + tag=dict(), + mtu=dict(type='int'), + attached_to=dict(), + mode=dict(choices=[ + 'balance-rr', + 'active-backup', + 'balance-xor', + 'broadcast', + '802.3ad', + 'balance-tlb', + 'balance-alb', + ]), + attached_devices=dict(type='list', elements='str'), + bond_options=dict(), + compute_attributes=dict(type='dict'), +) diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/activation_key.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/activation_key.py new file mode 100644 index 00000000..e6d23ce4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/activation_key.py @@ -0,0 +1,380 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017, Andrew Kofink <ajkofink@gmail.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: activation_key +version_added: 1.0.0 +short_description: Manage Activation Keys +description: + - Create and manage activation keys +author: "Andrew Kofink (@akofink)" +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 or Pool ID. + - Pool IDs are preferred since Names are not unique and the module will fail if it finds more than one subscription with the same name. + type: list + elements: dict + suboptions: + name: + description: + - Name of the Subscription to be added. + - Mutually exclusive with I(pool_id). + type: str + required: false + pool_id: + description: + - Pool ID of the Subscription to be added. + - Mutually exclusive with I(name). + 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 +''' + +EXAMPLES = ''' +- name: "Create client activation key" + theforeman.foreman.activation_key: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "Clients" + organization: "Default Organization" + lifecycle_environment: "Library" + content_view: 'client content view' + host_collections: + - rhel7-servers + - rhel7-production + subscriptions: + - pool_id: "8a88e9826db22df5016dd018abdd029b" + - pool_id: "8a88e9826db22df5016dd01a23270344" + - name: "Red Hat Enterprise Linux" + content_overrides: + - label: rhel-7-server-optional-rpms + override: enabled + auto_attach: False + release_version: 7Server + service_level: Standard +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + activation_keys: + description: List of activation keys. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule + + +def override_to_boolnone(override): + value = None + if isinstance(override, bool): + value = override + else: + override = override.lower() + if override == 'enabled': + value = True + elif override == 'disabled': + value = False + elif override == 'default': + value = None + return value + + +class KatelloActivationKeyModule(KatelloEntityAnsibleModule): + pass + + +def main(): + module = KatelloActivationKeyModule( + foreman_spec=dict( + name=dict(required=True), + new_name=dict(), + description=dict(), + lifecycle_environment=dict(type='entity', flat_name='environment_id', scope=['organization']), + content_view=dict(type='entity', scope=['organization']), + host_collections=dict(type='entity_list', scope=['organization']), + auto_attach=dict(type='bool'), + release_version=dict(), + service_level=dict(choices=['Self-Support', 'Standard', 'Premium']), + max_hosts=dict(type='int'), + unlimited_hosts=dict(type='bool'), + purpose_usage=dict(), + purpose_role=dict(), + purpose_addons=dict(type='list', elements='str'), + ), + argument_spec=dict( + subscriptions=dict(type='list', elements='dict', options=dict( + name=dict(), + pool_id=dict(), + ), + required_one_of=[['name', 'pool_id']], + mutually_exclusive=[['name', 'pool_id']], + ), + content_overrides=dict(type='list', elements='dict', options=dict( + label=dict(required=True), + override=dict(required=True, choices=['enabled', 'disabled', 'default']), + )), + state=dict(default='present', choices=['present', 'present_with_defaults', 'absent', 'copied']), + ), + required_if=[ + ['state', 'copied', ['new_name']], + ['unlimited_hosts', False, ['max_hosts']], + ], + ) + + with module.api_connection(): + entity = module.lookup_entity('entity') + scope = module.scope_for('organization') + + if module.state == 'copied': + new_entity = module.find_resource_by_name('activation_keys', name=module.foreman_params['new_name'], params=scope, failsafe=True) + if new_entity is not None: + module.warn("Activation Key '{0}' already exists.".format(module.foreman_params['new_name'])) + module.exit_json() + + subscriptions = module.foreman_params.pop('subscriptions', None) + content_overrides = module.foreman_params.pop('content_overrides', None) + if not module.desired_absent: + module.lookup_entity('host_collections') + host_collections = module.foreman_params.pop('host_collections', None) + activation_key = module.run() + + # only update subscriptions of newly created or updated AKs + # copied keys inherit the subscriptions of the origin, so one would not have to specify them again + # deleted keys don't need subscriptions anymore either + if module.state == 'present' or (module.state == 'present_with_defaults' and module.changed): + # the auto_attach, release_version and service_level parameters can only be set on an existing AK with an update, + # not during create, so let's force an update. see https://projects.theforeman.org/issues/27632 for details + if any(key in module.foreman_params for key in ['auto_attach', 'release_version', 'service_level']) and module.changed: + activation_key = module.ensure_entity('activation_keys', module.foreman_params, activation_key, params=scope) + + ak_scope = {'activation_key_id': activation_key['id']} + if subscriptions is not None: + desired_subscriptions = [] + for subscription in subscriptions: + if subscription.get('name') is not None and subscription.get('pool_id') is None: + desired_subscriptions.append(module.find_resource_by_name('subscriptions', subscription['name'], params=scope, thin=True)) + if subscription.get('pool_id') is not None: + desired_subscriptions.append(module.find_resource_by_id('subscriptions', subscription['pool_id'], params=scope, thin=True)) + desired_subscription_ids = set(item['id'] for item in desired_subscriptions) + current_subscriptions = module.list_resource('subscriptions', params=ak_scope) if entity else [] + current_subscription_ids = set(item['id'] for item in current_subscriptions) + + if desired_subscription_ids != current_subscription_ids: + module.record_before('activation_keys/subscriptions', {'id': activation_key['id'], 'subscriptions': current_subscription_ids}) + module.record_after('activation_keys/subscriptions', {'id': activation_key['id'], 'subscriptions': desired_subscription_ids}) + module.record_after_full('activation_keys/subscriptions', {'id': activation_key['id'], 'subscriptions': desired_subscription_ids}) + + ids_to_remove = current_subscription_ids - desired_subscription_ids + if ids_to_remove: + payload = { + 'id': activation_key['id'], + 'subscriptions': [{'id': item} for item in ids_to_remove], + } + payload.update(scope) + module.resource_action('activation_keys', 'remove_subscriptions', payload) + + ids_to_add = desired_subscription_ids - current_subscription_ids + if ids_to_add: + payload = { + 'id': activation_key['id'], + 'subscriptions': [{'id': item, 'quantity': 1} for item in ids_to_add], + } + payload.update(scope) + module.resource_action('activation_keys', 'add_subscriptions', payload) + + if content_overrides is not None: + if entity: + product_content = module.resource_action( + 'activation_keys', + 'product_content', + params={'id': activation_key['id'], + 'content_access_mode_all': True}, + ignore_check_mode=True, + ) + else: + product_content = {'results': []} + current_content_overrides = { + product['content']['label']: product['enabled_content_override'] + for product in product_content['results'] + if product['enabled_content_override'] is not None + } + desired_content_overrides = { + product['label']: override_to_boolnone(product['override']) for product in content_overrides + } + changed_content_overrides = [] + + module.record_before('activation_keys/content_overrides', {'id': activation_key['id'], 'content_overrides': current_content_overrides.copy()}) + module.record_after('activation_keys/content_overrides', {'id': activation_key['id'], 'content_overrides': desired_content_overrides}) + module.record_after_full('activation_keys/content_overrides', {'id': activation_key['id'], 'content_overrides': desired_content_overrides}) + + for label, override in desired_content_overrides.items(): + if override is not None and override != current_content_overrides.pop(label, None): + changed_content_overrides.append({'content_label': label, 'value': override}) + for label in current_content_overrides.keys(): + changed_content_overrides.append({'content_label': label, 'remove': True}) + + if changed_content_overrides: + payload = { + 'id': activation_key['id'], + 'content_overrides': changed_content_overrides, + } + module.resource_action('activation_keys', 'content_override', payload) + + if host_collections is not None: + if not entity: + current_host_collection_ids = set() + elif 'host_collection_ids' in activation_key: + current_host_collection_ids = set(activation_key['host_collection_ids']) + else: + current_host_collection_ids = set(item['id'] for item in activation_key['host_collections']) + desired_host_collections = host_collections + desired_host_collection_ids = set(item['id'] for item in desired_host_collections) + + if desired_host_collection_ids != current_host_collection_ids: + module.record_before('activation_keys/host_collections', {'id': activation_key['id'], 'host_collections': current_host_collection_ids}) + module.record_after('activation_keys/host_collections', {'id': activation_key['id'], 'host_collections': desired_host_collection_ids}) + module.record_after_full('activation_keys/host_collections', {'id': activation_key['id'], 'host_collections': desired_host_collection_ids}) + + ids_to_remove = current_host_collection_ids - desired_host_collection_ids + if ids_to_remove: + payload = { + 'id': activation_key['id'], + 'host_collection_ids': list(ids_to_remove), + } + module.resource_action('activation_keys', 'remove_host_collections', payload) + + ids_to_add = desired_host_collection_ids - current_host_collection_ids + if ids_to_add: + payload = { + 'id': activation_key['id'], + 'host_collection_ids': list(ids_to_add), + } + module.resource_action('activation_keys', 'add_host_collections', payload) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/architecture.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/architecture.py new file mode 100644 index 00000000..c3fd5b68 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/architecture.py @@ -0,0 +1,122 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Manisha Singhal (ATIX AG) +# +# 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: architecture +version_added: 1.0.0 +short_description: Manage Architectures +description: + - Create, update, and delete Architectures +author: + - "Manisha Singhal (@Manisha15) ATIX AG" +options: + name: + description: Name of architecture + required: true + type: str + updated_name: + description: New architecture name. When this parameter is set, the module will not be idempotent. + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.operatingsystems +''' + +EXAMPLES = ''' +- name: "Create an Architecture" + theforeman.foreman.architecture: + name: "i386" + operatingsystems: + - "TestOS1" + - "TestOS2" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: "Update an Architecture" + theforeman.foreman.architecture: + name: "i386" + operatingsystems: + - "TestOS3" + - "TestOS4" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: "Delete an Architecture" + theforeman.foreman.architecture: + name: "i386" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: absent +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + architectures: + description: List of architectures. + type: list + elements: dict + contains: + id: + description: Database id of the architecture. + type: int + name: + description: Name of the architecture. + type: str + operatinsystem_ids: + description: Database ids of associated operatingsystems. + type: list + elements: int +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule + + +class ForemanArchitectureModule(ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanArchitectureModule( + argument_spec=dict( + updated_name=dict(), + ), + foreman_spec=dict( + name=dict(required=True), + operatingsystems=dict(type='entity_list'), + ), + ) + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/auth_source_ldap.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/auth_source_ldap.py new file mode 100644 index 00000000..5c3511d3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/auth_source_ldap.py @@ -0,0 +1,212 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Christoffer Reijer (Basalt AB) +# +# 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: auth_source_ldap +version_added: 1.0.0 +short_description: Manage LDAP Authentication Sources +description: + - Create, update, and delete LDAP authentication sources +author: + - "Christoffer Reijer (@ephracis) Basalt AB" +options: + name: + description: The name of the LDAP authentication source + required: true + type: str + host: + description: The hostname of the LDAP server + required: true + type: str + port: + description: The port number of the LDAP server + required: false + type: int + default: 389 + account: + description: Account name to use when accessing the LDAP server. + required: false + type: str + account_password: + description: + - Account password to use when accessing the LDAP server. + - Required when using I(onthefly_register). + - When this parameter is set, the module will not be idempotent. + required: false + type: str + base_dn: + description: The base DN to use when searching. + required: false + type: str + attr_login: + description: + - Attribute containing login ID. + - Required when using I(onthefly_register). + required: false + type: str + attr_firstname: + description: + - Attribute containing first name. + - Required when using I(onthefly_register). + required: false + type: str + attr_lastname: + description: + - Attribute containing last name. + - Required when using I(onthefly_register). + required: false + type: str + attr_mail: + description: + - Attribute containing email address. + - Required when using I(onthefly_register). + required: false + type: str + attr_photo: + description: Attribute containing user photo + required: false + type: str + onthefly_register: + description: Whether or not to register users on the fly. + required: false + type: bool + usergroup_sync: + description: Whether or not to sync external user groups on login + required: false + type: bool + tls: + description: Whether or not to use TLS when contacting the LDAP server. + required: false + type: bool + groups_base: + description: Base DN where groups reside. + required: false + type: str + use_netgroups: + description: Whether to use NIS netgroups instead of posix groups, not valid for I(server_type=active_directory) + required: false + type: bool + server_type: + description: Type of the LDAP server + required: false + choices: ["free_ipa", "active_directory", "posix"] + type: str + ldap_filter: + description: Filter to apply to LDAP searches + required: false + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.taxonomy +''' + +EXAMPLES = ''' +- name: LDAP Authentication source + theforeman.foreman.auth_source_ldap: + name: "Example LDAP" + host: "ldap.example.org" + server_url: "https://foreman.example.com" + locations: + - "Uppsala" + organizations: + - "Sweden" + username: "admin" + password: "changeme" + state: present + +- name: LDAP Authentication with automatic registration + theforeman.foreman.auth_source_ldap: + name: "Example LDAP" + host: "ldap.example.org" + onthefly_register: True + account: uid=ansible,cn=sysaccounts,cn=etc,dc=example,dc=com + account_password: secret + base_dn: dc=example,dc=com + groups_base: cn=groups,cn=accounts, dc=example,dc=com + server_type: free_ipa + attr_login: uid + attr_firstname: givenName + attr_lastname: sn + attr_mail: mail + attr_photo: jpegPhoto + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + auth_source_ldaps: + description: List of auth sources for LDAP. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanTaxonomicEntityAnsibleModule + + +class ForemanAuthSourceLdapModule(ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanAuthSourceLdapModule( + foreman_spec=dict( + name=dict(required=True), + host=dict(required=True), + port=dict(type='int', default=389), + account=dict(), + account_password=dict(no_log=True), + base_dn=dict(), + attr_login=dict(), + attr_firstname=dict(), + attr_lastname=dict(), + attr_mail=dict(), + attr_photo=dict(), + onthefly_register=dict(type='bool'), + usergroup_sync=dict(type='bool'), + tls=dict(type='bool'), + groups_base=dict(), + server_type=dict(choices=["free_ipa", "active_directory", "posix"]), + ldap_filter=dict(), + use_netgroups=dict(type='bool'), + ), + required_if=[['onthefly_register', True, ['attr_login', 'attr_firstname', 'attr_lastname', 'attr_mail']]], + ) + + # additional parameter checks + if 'use_netgroups' in module.foreman_params and module.foreman_params['server_type'] == 'active_directory': + module.fail_json(msg='use_netgroups cannot be used when server_type=active_directory') + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/bookmark.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/bookmark.py new file mode 100644 index 00000000..c34b7f55 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/bookmark.py @@ -0,0 +1,157 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Bernhard Hopfenmüller (ATIX AG) +# +# 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: bookmark +version_added: 1.0.0 +short_description: Manage Bookmarks +description: + - "Manage Bookmark Entities" +author: + - "Bernhard Hopfenmueller (@Fobhep) ATIX AG" + - "Christoffer Reijer (@ephracis) Basalt AB" +options: + name: + description: + - Name of the bookmark + required: true + type: str + controller: + description: + - Controller for the bookmark + required: true + type: str + public: + description: + - Make bookmark available for all users + required: false + default: true + type: bool + query: + description: + - Query of the bookmark + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults +''' + +EXAMPLES = ''' +- name: "Create a Bookmark" + theforeman.foreman.bookmark: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "recent" + controller: "job_invocations" + query: "started_at > '24 hours ago'" + state: present_with_defaults + +- name: "Update a Bookmark" + theforeman.foreman.bookmark: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "recent" + controller: "job_invocations" + query: "started_at > '12 hours ago'" + state: present + +- name: "Delete a Bookmark" + theforeman.foreman.bookmark: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "recent" + controller: "job_invocations" + state: absent +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + bookmarks: + description: List of bookmarks. + type: list + elements: dict + contains: + id: + description: Database id of the bookmark. + type: int + name: + description: Name of the bookmark. + type: str + controller: + description: Controller, the query is performed on. + type: str + query: + description: Query to be performed on the controller. + type: str + public: + description: Publicity of the bookmark. + type: bool + owner_type: + description: Class of the owner entity. + type: str + owner_id: + description: Database id of the owner entity. + type: int +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule + + +class ForemanBookmarkModule(ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanBookmarkModule( + foreman_spec=dict( + name=dict(required=True), + controller=dict(required=True), + public=dict(default='true', type='bool'), + query=dict(), + ), + argument_spec=dict( + state=dict(default='present', choices=['present_with_defaults', 'present', 'absent']), + ), + required_if=( + ['state', 'present', ['query']], + ['state', 'present_with_defaults', ['query']], + ), + ) + + with module.api_connection(): + module.set_entity('entity', module.find_resource( + 'bookmarks', + search='name="{0}",controller="{1}"'.format(module.foreman_params['name'], module.foreman_params['controller']), + failsafe=True, + )) + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/compute_attribute.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/compute_attribute.py new file mode 100644 index 00000000..89894d9e --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/compute_attribute.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Manisha Singhal (ATIX AG) +# +# 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: compute_attribute +version_added: 1.0.0 +short_description: Manage Compute Attributes +description: + - "Manage Compute Attributes" + - "This beta version can create, and update compute attributes" +author: + - "Manisha Singhal (@Manisha15) ATIX AG" +options: + compute_resource: + description: + - Name of compute resource + required: true + type: str + compute_profile: + description: + - Name of compute profile + required: true + type: str + vm_attrs: + description: + - Hash containing the data of vm_attrs + required: false + aliases: + - vm_attributes + type: dict +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state +''' + +EXAMPLES = ''' +- name: "Create compute attribute" + theforeman.foreman.compute_attribute: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + compute_profile: "Test Compute Profile" + compute_resource: "Test Compute Resource" + vm_attrs: + memory_mb: '2048' + cpu: '2' + state: present + +- name: "Update compute attribute" + theforeman.foreman.compute_attribute: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + compute_profile: "Test Compute Profile" + compute_resource: "Test Compute Resource" + vm_attrs: + memory_mb: '1024' + cpu: '1' + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + compute_attributes: + description: List of compute attributes. + type: list + elements: dict + contains: + id: + description: Database id of the compute_attribute. + type: int + compute_profile_id: + description: Database id of the associated compute profile. + type: int + compute_profile_name: + description: Name of the associated compute profile. + type: str + compute_resource_id: + description: Database id of the associated compute resource. + type: int + compute_resource_name: + description: Name of the associated compute resource. + type: str + created_at: + description: Creation date of the compute attribute. + type: str + updated_at: + description: Date of last change to the compute attribute. + type: str + name: + description: Generated friendly name for the compute attribute. + type: str + provider_friendly_name: + description: Name of the provider type of the compute resource. + type: str + attributes: + description: Effective attributes for the given combination of compute profile and resource. + type: dict + vm_attrs: + description: Configured attributes. + type: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule + + +class ForemanComputeAttributeModule(ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanComputeAttributeModule( + foreman_spec=dict( + compute_profile=dict(required=True, type='entity'), + compute_resource=dict(required=True, type='entity', thin=False), + vm_attrs=dict(type='dict', aliases=['vm_attributes']), + ), + entity_opts=dict(resolve=False), + ) + + with module.api_connection(): + compute_attributes = module.lookup_entity('compute_resource').get('compute_attributes') + compute_profile_id = module.lookup_entity('compute_profile').get('id') + entity = next((item for item in compute_attributes if item.get('compute_profile_id') == compute_profile_id), None) + module.set_entity('entity', entity) + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/compute_profile.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/compute_profile.py new file mode 100644 index 00000000..2797c671 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/compute_profile.py @@ -0,0 +1,204 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) Philipp Joos 2017 +# (c) Baptiste Agasse 2019 +# +# 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: compute_profile +version_added: 1.0.0 +short_description: Manage Compute Profiles +description: + - Create, update, and delete Compute Profiles +author: + - "Philipp Joos (@philippj)" + - "Baptiste Agasse (@bagasse)" +options: + name: + description: compute profile name + required: true + type: str + updated_name: + description: new compute profile name + required: false + type: str + compute_attributes: + description: Compute attributes related to this compute profile. Some of these attributes are specific to the underlying compute resource type + required: false + type: list + elements: dict + suboptions: + compute_resource: + description: + - Name of the compute resource the attribute should be for + type: str + vm_attrs: + description: + - Hash containing the data of vm_attrs + aliases: + - vm_attributes + type: dict +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state +''' + +EXAMPLES = ''' +- name: compute profile + theforeman.foreman.compute_profile: + name: example_compute_profile + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: another compute profile + theforeman.foreman.compute_profile: + name: another_example_compute_profile + compute_attributes: + - compute_resource: ovirt_compute_resource1 + vm_attrs: + cluster: 'a96d44a4-f14a-1015-82c6-f80354acdf01' + template: 'c88af4b7-a24a-453b-9ac2-bc647ca2ef99' + instance_type: 'cb8927e7-a404-40fb-a6c1-06cbfc92e077' + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: compute profile2 + theforeman.foreman.compute_profile: + name: example_compute_profile2 + compute_attributes: + - compute_resource: ovirt_compute_resource01 + vm_attrs: + cluster: a96d44a4-f14a-1015-82c6-f80354acdf01 + cores: 1 + sockets: 1 + memory: 1073741824 + ha: 0 + interfaces_attributes: + 0: + name: "" + network: 390666e1-dab3-4c99-9f96-006b2e2fd801 + interface: virtio + volumes_attributes: + 0: + size_gb: 16 + storage_domain: 19c50090-1ab4-4023-a63f-75ee1018ed5e + preallocate: '1' + wipe_after_delete: '0' + interface: virtio_scsi + bootable: 'true' + - compute_resource: libvirt_compute_resource03 + vm_attrs: + cpus: 1 + memory: 2147483648 + nics_attributes: + 0: + type: bridge + bridge: "" + model: virtio + volumes_attributes: + 0: + pool_name: default + capacity: 16G + allocation: 16G + format_type: raw + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: Remove compute profile + theforeman.foreman.compute_profile: + name: example_compute_profile2 + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: absent +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + compute_profiles: + description: List of compute profiles. + type: list + elements: dict + contains: + id: + description: Database id of the compute profile. + type: int + name: + description: Name of the compute profile. + type: str + compute_attributes: + description: Attributes for this compute profile. + type: list +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule + + +compute_attribute_foreman_spec = { + 'id': {'invisible': True}, + 'compute_resource': {'type': 'entity'}, + 'vm_attrs': {'type': 'dict', 'aliases': ['vm_attributes']}, +} + + +class ForemanComputeProfileModule(ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanComputeProfileModule( + foreman_spec=dict( + name=dict(required=True), + compute_attributes=dict(type='nested_list', foreman_spec=compute_attribute_foreman_spec), + ), + argument_spec=dict( + updated_name=dict(), + ), + ) + + compute_attributes = module.foreman_params.pop('compute_attributes', None) + + with module.api_connection(): + entity = module.run() + + # Apply changes on underlying compute attributes only when present + if entity and module.state == 'present' and compute_attributes is not None: + # Update or create compute attributes + scope = {'compute_profile_id': entity['id']} + for ca_module_params in compute_attributes: + ca_module_params['compute_resource'] = module.find_resource_by_name( + 'compute_resources', name=ca_module_params['compute_resource'], failsafe=False, thin=False) + ca_entities = ca_module_params['compute_resource'].get('compute_attributes', []) + ca_entity = next((item for item in ca_entities if item.get('compute_profile_id') == entity['id']), None) + module.ensure_entity('compute_attributes', ca_module_params, ca_entity, foreman_spec=compute_attribute_foreman_spec, params=scope) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/compute_resource.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/compute_resource.py new file mode 100644 index 00000000..4a1acb2f --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/compute_resource.py @@ -0,0 +1,406 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) Philipp Joos 2017 +# (c) Baptiste Agasse 2019 +# (c) Mark Hlawatschek 2020 +# +# 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: compute_resource +version_added: 1.0.0 +short_description: Manage Compute Resources +description: + - Create, update, and delete Compute Resources +author: + - "Philipp Joos (@philippj)" + - "Baptiste Agasse (@bagasse)" + - "Manisha Singhal (@Manisha15) ATIX AG" + - "Mark Hlawatschek (@hlawatschek) ATIX AG" +options: + name: + description: compute resource name + required: true + type: str + updated_name: + description: new compute resource name + required: false + type: str + description: + description: compute resource description + required: false + type: str + provider: + description: Compute resource provider. Required if I(state=present_with_defaults). + required: false + choices: ["vmware", "libvirt", "ovirt", "proxmox", "EC2", "AzureRm", "GCE"] + type: str + provider_params: + description: Parameter specific to compute resource provider. Required if I(state=present_with_defaults). + required: false + type: dict + suboptions: + url: + description: + - URL of the compute resource + type: str + user: + description: + - Username for the compute resource connection, not valid for I(provider=libvirt) + type: str + password: + description: + - Password for the compute resource connection, not valid for I(provider=libvirt) + type: str + region: + description: + - AWS region, AZURE region + type: str + tenant: + description: + - AzureRM tenant + type: str + app_ident: + description: + - AzureRM client id + type: str + datacenter: + description: + - Datacenter the compute resource is in, not valid for I(provider=libvirt) + type: str + display_type: + description: + - Display type to use for the remote console, only valid for I(provider=libvirt) + type: str + use_v4: + description: + - Use oVirt API v4, only valid for I(provider=ovirt) + type: bool + ovirt_quota: + description: + - oVirt quota ID, only valid for I(provider=ovirt) + type: str + project: + description: + - Project id for I(provider=GCE) + type: str + email: + description: + - Email for I(provider=GCE) + type: str + key_path: + description: + - Certificate path for I(provider=GCE) + type: str + zone: + description: + - zone for I(provider=GCE) + type: str + ssl_verify_peer: + description: + - verify ssl from provider I(provider=proxmox) + type: bool + caching_enabled: + description: + - enable caching for I(provider=vmware) + type: bool +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults + - theforeman.foreman.foreman.taxonomy +''' + +EXAMPLES = ''' +- name: Create livirt compute resource + theforeman.foreman.compute_resource: + name: example_compute_resource + locations: + - Munich + organizations: + - ACME + provider: libvirt + provider_params: + url: libvirt.example.com + display_type: vnc + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: Update libvirt compute resource + theforeman.foreman.compute_resource: + name: example_compute_resource + description: updated compute resource + locations: + - Munich + organizations: + - ACME + provider: libvirt + provider_params: + url: libvirt.example.com + display_type: vnc + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: Delete libvirt compute resource + theforeman.foreman.compute_resource: + name: example_compute_resource + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: absent + +- name: Create vmware compute resource + theforeman.foreman.compute_resource: + name: example_compute_resource + locations: + - Munich + organizations: + - ACME + provider: vmware + provider_params: + caching_enabled: false + url: vsphere.example.com + user: admin + password: secret + datacenter: ax01 + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: Create ovirt compute resource + theforeman.foreman.compute_resource: + name: ovirt_compute_resource + locations: + - France/Toulouse + organizations: + - Example Org + provider: ovirt + provider_params: + url: ovirt.example.com + user: ovirt-admin@example.com + password: ovirtsecret + datacenter: aa92fb54-0736-4066-8fa8-b8b9e3bd75ac + ovirt_quota: 24868ab9-c2a1-47c3-87e7-706f17d215ac + use_v4: true + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: Create proxmox compute resource + theforeman.foreman.compute_resource: + name: proxmox_compute_resource + locations: + - Munich + organizations: + - ACME + provider: proxmox + provider_params: + url: https://proxmox.example.com:8006/api2/json + user: root@pam + password: secretpassword + ssl_verify_peer: true + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: create EC2 compute resource + theforeman.foreman.compute_resource: + name: EC2_compute_resource + description: EC2 + locations: + - AWS + organizations: + - ACME + provider: EC2 + provider_params: + user: AWS_ACCESS_KEY + password: AWS_SECRET_KEY + region: eu-west-1 + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: create Azure compute resource + theforeman.foreman.compute_resource: + name: AzureRm_compute_resource + description: AzureRm + locations: + - Azure + organizations: + - ACME + provider: AzureRm + provider_params: + user: SUBSCRIPTION_ID + tenant: TENANT_ID + app_ident: CLIENT_ID + password: CLIENT_SECRET + region: westeurope + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: create GCE compute resource + theforeman.foreman.compute_resource: + name: GCE compute resource + description: Google Cloud Engine + locations: + - GCE + organizations: + - ACME + provider: GCE + provider_params: + project: orcharhino + email: myname@atix.de + key_path: "/usr/share/foreman/gce_orcharhino_key.json" + zone: europe-west3-b + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + compute_resources: + description: List of compute resources. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanTaxonomicEntityAnsibleModule + + +def get_provider_info(provider): + provider_name = provider.lower() + + if provider_name == 'libvirt': + return 'Libvirt', ['url', 'display_type'] + + elif provider_name == 'ovirt': + return 'Ovirt', ['url', 'user', 'password', 'datacenter', 'use_v4', 'ovirt_quota'] + + elif provider_name == 'proxmox': + return 'Proxmox', ['url', 'user', 'password', 'ssl_verify_peer'] + + elif provider_name == 'vmware': + return 'Vmware', ['url', 'user', 'password', 'datacenter', 'caching_enabled'] + + elif provider_name == 'ec2': + return 'EC2', ['user', 'password', 'region'] + + elif provider_name == 'azurerm': + return 'AzureRm', ['user', 'password', 'tenant', 'region', 'app_ident'] + + elif provider_name == 'gce': + return 'GCE', ['project', 'email', 'key_path', 'zone'] + + else: + return '', [] + + +class ForemanComputeResourceModule(ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanComputeResourceModule( + foreman_spec=dict( + name=dict(required=True), + updated_name=dict(), + description=dict(), + provider=dict(choices=['vmware', 'libvirt', 'ovirt', 'proxmox', 'EC2', 'AzureRm', 'GCE']), + display_type=dict(invisible=True), + datacenter=dict(invisible=True), + url=dict(invisible=True), + caching_enabled=dict(invisible=True), + user=dict(invisible=True), + password=dict(invisible=True), + region=dict(invisible=True), + tenant=dict(invisible=True), + app_ident=dict(invisible=True), + use_v4=dict(invisible=True), + ovirt_quota=dict(invisible=True), + project=dict(invisible=True), + email=dict(invisible=True), + key_path=dict(invisible=True), + zone=dict(invisible=True), + ssl_verify_peer=dict(invisible=True), + ), + argument_spec=dict( + provider_params=dict(type='dict', options=dict( + url=dict(), + display_type=dict(), + user=dict(), + password=dict(no_log=True), + region=dict(), + tenant=dict(), + app_ident=dict(), + datacenter=dict(), + caching_enabled=dict(type='bool'), + use_v4=dict(type='bool'), + ovirt_quota=dict(), + project=dict(), + email=dict(), + key_path=dict(), + zone=dict(), + ssl_verify_peer=dict(type='bool'), + )), + state=dict(type='str', default='present', choices=['present', 'absent', 'present_with_defaults']), + ), + required_if=( + ['state', 'present_with_defaults', ['provider', 'provider_params']], + ), + ) + + if not module.desired_absent: + if 'provider' in module.foreman_params: + module.foreman_params['provider'], provider_param_keys = get_provider_info(provider=module.foreman_params['provider']) + provider_params = module.foreman_params.pop('provider_params', {}) + + for key in provider_param_keys: + if key in provider_params: + module.foreman_params[key] = provider_params.pop(key) + if provider_params: + module.fail_json(msg="Provider {0} does not support the following given parameters: {1}".format( + module.foreman_params['provider'], list(provider_params.keys()))) + + with module.api_connection(): + entity = module.lookup_entity('entity') + if not module.desired_absent and 'provider' not in module.foreman_params and entity is None: + module.fail_json(msg='To create a compute resource a valid provider must be supplied') + + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/config_group.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/config_group.py new file mode 100644 index 00000000..844b9894 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/config_group.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Baptiste Agasse +# +# 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: config_group +version_added: 1.0.0 +short_description: Manage (Puppet) Config Groups +description: + - Create, update, and delete (Puppet) config groups +author: + - "Baptiste Agasse (@bagasse)" +options: + name: + description: The config group name + required: true + type: str + updated_name: + description: New config group name. When this parameter is set, the module will not be idempotent. + type: str + puppetclasses: + description: List of puppet classes to include in this group + required: false + type: list + elements: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state +''' + +EXAMPLES = ''' +- name: create new config group + theforeman.foreman.config_group: + name: "My config group" + puppetclasses: + - ntp + - mymodule::myclass + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + config_groups: + description: List of config groups. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule + + +class ForemanConfigGroupModule(ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanConfigGroupModule( + argument_spec=dict( + updated_name=dict(), + ), + foreman_spec=dict( + name=dict(required=True), + puppetclasses=dict(type='entity_list'), + ), + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_credential.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_credential.py new file mode 100644 index 00000000..ae2b3230 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_credential.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2018, Baptiste Agasse <baptiste.agagsse@gmail.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: content_credential +version_added: 1.0.0 +short_description: Manage Content Credentials +description: + - Create and manage content credentials +author: "Baptiste Agasse (@bagasse)" +options: + name: + description: + - Name of the content credential + required: true + type: str + content_type: + description: + - Type of credential + choices: + - gpg_key + - cert + required: true + type: str + content: + description: + - Content of the content credential + required: true + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Create katello client GPG key" + theforeman.foreman.content_credential: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "RPM-GPG-KEY-my-repo" + content_type: gpg_key + organization: "Default Organization" + content: "{{ lookup('file', 'RPM-GPG-KEY-my-repo') }}" +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + content_credentials: + description: List of content credentials. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule + + +class KatelloContentCredentialModule(KatelloEntityAnsibleModule): + pass + + +def main(): + module = KatelloContentCredentialModule( + foreman_spec=dict( + name=dict(required=True), + content_type=dict(required=True, choices=['gpg_key', 'cert']), + content=dict(required=True), + ), + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_upload.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_upload.py new file mode 100644 index 00000000..4f012a8c --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_upload.py @@ -0,0 +1,193 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2016, Eric D Helms <ericdhelms@gmail.com> +# (c) 2018, Sean O'Keeffe <seanokeeffe797@gmail.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: content_upload +version_added: 1.0.0 +short_description: Upload content to a repository +description: + - Allows the upload of content to a repository +author: "Eric D Helms (@ehelms)" +requirements: + - python-debian (For deb Package upload) + - rpm (For rpm upload) +options: + src: + description: + - File to upload + required: true + type: path + aliases: + - file + repository: + description: + - Repository to upload file in to + required: true + type: str + product: + description: + - Product to which the repository lives in + required: true + type: str +notes: + - Currently only uploading to deb, RPM & file repositories is supported + - For anything but file repositories, a supporting library must be installed. See Requirements. +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Upload my.rpm" + theforeman.foreman.content_upload: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + src: "my.rpm" + repository: "Build RPMs" + product: "My Product" + organization: "Default Organization" +''' + +RETURN = ''' # ''' + +import os +import traceback + +from ansible.module_utils._text import to_bytes, to_native +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloAnsibleModule, missing_required_lib + +try: + from debian import debfile + HAS_DEBFILE = True +except ImportError: + HAS_DEBFILE = False + DEBFILE_IMP_ERR = traceback.format_exc() + +try: + import rpm + HAS_RPM = True +except ImportError: + HAS_RPM = False + RPM_IMP_ERR = traceback.format_exc() + +CONTENT_CHUNK_SIZE = 2 * 1024 * 1024 + + +def get_deb_info(path): + control = debfile.DebFile(path).debcontrol() + return control['package'], control['version'], control['architecture'] + + +def get_rpm_info(path): + ts = rpm.TransactionSet() + + # disable signature checks, we might not have the key or the file might be unsigned + # pre 4.15 RPM needs to use the old name of the bitmask + try: + vsflags = rpm.RPMVSF_MASK_NOSIGNATURES + except AttributeError: + vsflags = rpm._RPMVSF_NOSIGNATURES + ts.setVSFlags(vsflags) + + with open(path) as rpmfile: + rpmhdr = ts.hdrFromFdno(rpmfile) + + epoch = rpmhdr[rpm.RPMTAG_EPOCHNUM] + name = to_native(rpmhdr[rpm.RPMTAG_NAME]) + version = to_native(rpmhdr[rpm.RPMTAG_VERSION]) + release = to_native(rpmhdr[rpm.RPMTAG_RELEASE]) + arch = to_native(rpmhdr[rpm.RPMTAG_ARCH]) + + return (name, epoch, version, release, arch) + + +def main(): + module = KatelloAnsibleModule( + foreman_spec=dict( + src=dict(required=True, type='path', aliases=['file']), + repository=dict(required=True, type='entity', scope=['product'], thin=False), + product=dict(required=True, type='entity', scope=['organization']), + ), + ) + + with module.api_connection(): + repository_scope = module.scope_for('repository') + + b_src = to_bytes(module.foreman_params['src']) + filename = os.path.basename(module.foreman_params['src']) + + checksum = module.sha256(module.foreman_params['src']) + + content_unit = None + if module.foreman_params['repository']['content_type'] == 'deb': + if not HAS_DEBFILE: + module.fail_json(msg=missing_required_lib("python-debian"), exception=DEBFILE_IMP_ERR) + + name, version, architecture = get_deb_info(b_src) + query = 'name = "{0}" and version = "{1}" and architecture = "{2}"'.format(name, version, architecture) + content_unit = module.find_resource('debs', query, params=repository_scope, failsafe=True) + elif module.foreman_params['repository']['content_type'] == 'yum': + if not HAS_RPM: + module.fail_json(msg=missing_required_lib("rpm"), exception=RPM_IMP_ERR) + + name, epoch, version, release, arch = get_rpm_info(b_src) + query = 'name = "{0}" and epoch = "{1}" and version = "{2}" and release = "{3}" and arch = "{4}"'.format(name, epoch, version, release, arch) + content_unit = module.find_resource('packages', query, params=repository_scope, failsafe=True) + elif module.foreman_params['repository']['content_type'] == 'file': + query = 'name = "{0}" and checksum = "{1}"'.format(filename, checksum) + content_unit = module.find_resource('file_units', query, params=repository_scope, failsafe=True) + else: + # possible types in 3.12: docker, ostree, yum, puppet, file, deb + module.fail_json(msg="Uploading to a {0} repository is not supported yet.".format(module.foreman_params['repository']['content_type'])) + + if not content_unit: + if not module.check_mode: + size = os.stat(module.foreman_params['src']).st_size + content_upload_payload = {'size': size} + content_upload_payload.update(repository_scope) + content_upload = module.resource_action('content_uploads', 'create', content_upload_payload) + content_upload_scope = {'id': content_upload['upload_id']} + content_upload_scope.update(repository_scope) + + offset = 0 + + with open(b_src, 'rb') as contentfile: + for chunk in iter(lambda: contentfile.read(CONTENT_CHUNK_SIZE), b""): + data = {'content': chunk, 'offset': offset, 'size': size} + module.resource_action('content_uploads', 'update', params=content_upload_scope, data=data) + + offset += len(chunk) + + uploads = [{'id': content_upload['upload_id'], 'name': filename, + 'size': offset, 'checksum': checksum}] + import_params = {'id': module.foreman_params['repository']['id'], 'uploads': uploads} + module.resource_action('repositories', 'import_uploads', import_params) + + module.resource_action('content_uploads', 'destroy', content_upload_scope) + else: + module.set_changed() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_view.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_view.py new file mode 100644 index 00000000..8a82127c --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_view.py @@ -0,0 +1,280 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2016, Eric D Helms <ericdhelms@gmail.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: content_view +version_added: 1.0.0 +short_description: Manage Content Views +description: + - Create and manage content views +author: "Eric D Helms (@ehelms)" +options: + name: + description: + - Name of the Content View + required: true + type: str + description: + description: + - Description of the Content View + type: str + repositories: + description: + - List of repositories that include name and product. + - Cannot be combined with I(composite=True). + type: list + elements: dict + suboptions: + name: + description: + - Name of the Repository to be added + type: str + required: true + product: + description: + - Product of the Repository to be added + type: str + required: true + auto_publish: + description: + - Auto publish composite view when a new version of a component content view is created. + - Also note auto publish will only happen when the component is marked "latest". + default: false + type: bool + solve_dependencies: + description: + - Solve RPM dependencies by default on Content View publish + type: bool + composite: + description: + - A composite view contains other content views. + default: false + type: bool + components: + description: + - List of content views to includes content_view and either version or latest. + - Ignored if I(composite=False). + type: list + elements: dict + suboptions: + content_view: + description: + - Content View name to be added to the Composite Content View + type: str + required: true + latest: + description: + - Always use the latest Content View Version + type: bool + default: False + content_view_version: + description: + - Version of the Content View to add + type: str + aliases: + - version +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Create or update Fedora content view" + theforeman.foreman.content_view: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "Fedora CV" + organization: "My Cool new Organization" + repositories: + - name: 'Fedora 26' + product: 'Fedora' + +- name: "Create a composite content view" + theforeman.foreman.content_view: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "Fedora CCV" + organization: "My Cool new Organization" + composite: true + auto_publish: true + components: + - content_view: Fedora CV + content_view_version: 1.0 + - content_view: Internal CV + latest: true +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + content_views: + description: List of content views. + type: list + elements: dict +''' + +import copy +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule + + +cvc_foreman_spec = { + 'id': {'invisible': True}, + 'content_view': {'type': 'entity', 'required': True}, + 'latest': {'type': 'bool', 'default': False}, + 'content_view_version': {'type': 'entity', 'aliases': ['version']}, +} + + +class KatelloContentViewModule(KatelloEntityAnsibleModule): + pass + + +def main(): + module = KatelloContentViewModule( + foreman_spec=dict( + name=dict(required=True), + description=dict(), + composite=dict(type='bool', default=False), + auto_publish=dict(type='bool', default=False), + solve_dependencies=dict(type='bool'), + components=dict(type='nested_list', foreman_spec=cvc_foreman_spec, resolve=False), + repositories=dict(type='entity_list', elements='dict', resolve=False, options=dict( + name=dict(required=True), + product=dict(required=True), + )), + ), + argument_spec=dict( + state=dict(default='present', choices=['present_with_defaults', 'present', 'absent']), + ), + mutually_exclusive=[['repositories', 'components']], + entity_opts=dict(thin=False), + ) + + # components is None when we're managing a CCV but don't want to adjust its components + components = module.foreman_params.pop('components', None) + if components: + for component in components: + if not component['latest'] and component.get('content_view_version') is None: + module.fail_json(msg="Content View Component must either have latest=True or provide a Content View Version.") + + with module.api_connection(): + entity = module.lookup_entity('entity') + scope = module.scope_for('organization') + + if not module.desired_absent: + if 'repositories' in module.foreman_params: + if module.foreman_params['composite']: + module.fail_json(msg="Repositories cannot be parts of a Composite Content View.") + else: + repositories = [] + for repository in module.foreman_params['repositories']: + product = module.find_resource_by_name('products', repository['product'], params=scope, thin=True) + repositories.append(module.find_resource_by_name('repositories', repository['name'], params={'product_id': product['id']}, thin=True)) + module.foreman_params['repositories'] = repositories + + if entity and module.desired_absent: + for lce in entity.get('environments', []): + module.resource_action('content_views', 'remove_from_environment', {'id': entity['id'], 'environment_id': lce['id']}) + + content_view_entity = module.run() + + # only update CVC's of newly created or updated CV's that are composite if components are specified + update_dependent_entities = (module.state == 'present' or (module.state == 'present_with_defaults' and module.changed)) + if update_dependent_entities and content_view_entity['composite'] and components is not None: + if not module.changed: + content_view_entity['content_view_components'] = entity['content_view_components'] + current_cvcs = content_view_entity.get('content_view_components', []) + + # only record a subset of data + current_cvcs_record = [] + for cvc in current_cvcs: + entry = {"id": cvc['id'], "content_view_id": cvc['content_view']['id'], "latest": cvc['latest']} + if 'content_view_version' in cvc and isinstance(cvc['content_view_version'], dict): + entry['content_view_version_id'] = cvc['content_view_version'].get('id') + current_cvcs_record.append(entry) + module.record_before('content_views/components', {'composite_content_view_id': content_view_entity['id'], + 'content_view_components': current_cvcs_record}) + final_cvcs_record = copy.deepcopy(current_cvcs_record) + + components_to_add = [] + ccv_scope = {'composite_content_view_id': content_view_entity['id']} + for component in components: + cvc = { + 'content_view': module.find_resource_by_name('content_views', name=component['content_view'], params=scope), + 'latest': component['latest'], + } + cvc_matched = next((item for item in current_cvcs if item['content_view']['id'] == cvc['content_view']['id']), None) + if not cvc['latest']: + search = "content_view_id={0},version={1}".format(cvc['content_view']['id'], component['content_view_version']) + cvc['content_view_version'] = module.find_resource('content_view_versions', search=search, thin=True) + cvc['latest'] = False + if cvc_matched and cvc_matched['latest']: + # When changing to latest=False & version is the latest we must send 'content_view_version' to the server + # Let's fake, it wasn't there... + cvc_matched.pop('content_view_version', None) + cvc_matched.pop('content_view_version_id', None) + if cvc_matched: + module.ensure_entity( + 'content_view_components', cvc, cvc_matched, state='present', foreman_spec=cvc_foreman_spec, params=ccv_scope) + current_cvcs.remove(cvc_matched) + else: + cvc['content_view_id'] = cvc.pop('content_view')['id'] + if 'content_view_version' in cvc: + cvc['content_view_version_id'] = cvc.pop('content_view_version')['id'] + components_to_add.append(cvc) + + if components_to_add: + payload = { + 'composite_content_view_id': content_view_entity['id'], + 'components': components_to_add, + } + module.resource_action('content_view_components', 'add_components', payload) + + final_cvcs_record.extend(components_to_add) + + # desired cvcs have already been updated and removed from `current_cvcs` + components_to_remove = [item['id'] for item in current_cvcs] + if components_to_remove: + payload = { + 'composite_content_view_id': content_view_entity['id'], + 'component_ids': components_to_remove, + } + module.resource_action('content_view_components', 'remove_components', payload) + + # some entries in "final" don't have an id yet, as it is only assigned on creation of a cv component, + # which didn't happen yet when we record the data + final_cvcs_record = [item for item in final_cvcs_record if item.get('id', 'NEW_ID') not in components_to_remove] + + module.record_after('content_views/components', {'composite_content_view_id': content_view_entity['id'], + 'content_view_components': final_cvcs_record}) + module.record_after_full('content_views/components', {'composite_content_view_id': content_view_entity['id'], + 'content_view_components': final_cvcs_record}) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_view_filter.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_view_filter.py new file mode 100644 index 00000000..face7c28 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_view_filter.py @@ -0,0 +1,329 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2018, Sean O'Keeffe <seanokeeffe797@gmail.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: content_view_filter +version_added: 1.0.0 +short_description: Manage Content View Filters +description: + - Create and manage content View filters +author: "Sean O'Keeffe (@sean797)" +options: + architecture: + description: + - package architecture + type: str + name: + description: + - Name of the Content View Filter + type: str + required: true + description: + description: + - Description of the Content View Filter + type: str + content_view: + description: + - Name of the content view + required: true + type: str + filter_state: + description: + - State of the content view filter + default: present + choices: + - present + - absent + type: str + repositories: + description: + - List of repositories that include name and product + - An empty Array means all current and future repositories + default: [] + type: list + elements: dict + rule_state: + description: + - State of the content view filter rule + default: present + choices: + - present + - absent + type: str + filter_type: + description: + - Content view filter type + required: true + choices: + - rpm + - package_group + - erratum + - docker + type: str + rule_name: + description: + - Content view filter rule name or package name + - If omitted, the value of I(name) will be used if necessary + aliases: + - package_name + - package_group + - tag + type: str + date_type: + description: + - Search using the 'Issued On' or 'Updated On' + - Only valid on I(filter_type=erratum). + default: updated + choices: + - issued + - updated + type: str + end_date: + description: + - erratum end date (YYYY-MM-DD) + type: str + start_date: + description: + - erratum start date (YYYY-MM-DD) + type: str + errata_id: + description: + - erratum id + type: str + max_version: + description: + - package maximum version + type: str + min_version: + description: + - package minimum version + type: str + types: + description: + - erratum types (enhancement, bugfix, security) + default: ["bugfix", "enhancement", "security"] + type: list + elements: str + version: + description: + - package version + type: str + inclusion: + description: + - Create an include filter + default: False + type: bool + original_packages: + description: + - Include all RPMs with no errata + type: bool +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: Exclude csh + theforeman.foreman.content_view_filter: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "package filter 1" + organization: "Default Organization" + content_view: Web Servers + filter_type: "rpm" + package_name: tcsh + +- name: Include newer csh versions + theforeman.foreman.content_view_filter: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "package filter 1" + organization: "Default Organization" + content_view: Web Servers + filter_type: "rpm" + package_name: tcsh + min_version: 6.20.00 + inclusion: True +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + content_view_filters: + description: List of content view filters. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloMixin, ForemanStatelessEntityAnsibleModule + +content_filter_spec = { + 'id': {}, + 'name': {}, + 'description': {}, + 'repositories': {'type': 'entity_list'}, + 'inclusion': {}, + 'content_view': {'type': 'entity'}, + 'filter_type': {'flat_name': 'type'}, + 'original_packages': {}, +} + +content_filter_rule_erratum_spec = { + 'id': {}, + 'date_type': {}, + 'end_date': {}, + 'start_date': {}, + 'types': {'type': 'list'}, +} + +content_filter_rule_erratum_id_spec = { + 'id': {}, + 'errata_id': {}, + 'date_type': {}, +} + +content_filter_rule_rpm_spec = { + 'id': {}, + 'rule_name': {'flat_name': 'name'}, + 'end_date': {}, + 'max_version': {}, + 'min_version': {}, + 'version': {}, + 'architecture': {}, +} + +content_filter_rule_package_group_spec = { + 'id': {}, + 'rule_name': {'flat_name': 'name'}, + 'uuid': {}, +} + +content_filter_rule_docker_spec = { + 'id': {}, + 'rule_name': {'flat_name': 'name'}, +} + + +class KatelloContentViewFilterModule(KatelloMixin, ForemanStatelessEntityAnsibleModule): + pass + + +def main(): + module = KatelloContentViewFilterModule( + foreman_spec=dict( + name=dict(required=True), + description=dict(), + repositories=dict(type='list', default=[], elements='dict'), + inclusion=dict(type='bool', default=False), + original_packages=dict(type='bool'), + content_view=dict(type='entity', scope=['organization'], required=True), + filter_type=dict(required=True, choices=['rpm', 'package_group', 'erratum', 'docker']), + filter_state=dict(default='present', choices=['present', 'absent']), + rule_state=dict(default='present', choices=['present', 'absent']), + rule_name=dict(aliases=['package_name', 'package_group', 'tag']), + date_type=dict(default='updated', choices=['issued', 'updated']), + end_date=dict(), + errata_id=dict(), + max_version=dict(), + min_version=dict(), + start_date=dict(), + types=dict(default=["bugfix", "enhancement", "security"], type='list', elements='str'), + version=dict(), + architecture=dict(), + ), + entity_opts=dict(scope=['content_view']), + ) + + filter_state = module.foreman_params.pop('filter_state') + rule_state = module.foreman_params.pop('rule_state') + + if module.foreman_params['filter_type'] == 'erratum': + module.foreman_params['rule_name'] = None + elif 'rule_name' not in module.foreman_params: + module.foreman_params['rule_name'] = module.foreman_params['name'] + + with module.api_connection(): + scope = module.scope_for('organization') + + cv_scope = module.scope_for('content_view') + if module.foreman_params['repositories']: + repositories = [] + for repo in module.foreman_params['repositories']: + product = module.find_resource_by_name('products', repo['product'], params=scope, thin=True) + product_scope = {'product_id': product['id']} + repositories.append(module.find_resource_by_name('repositories', repo['name'], params=product_scope, thin=True)) + module.foreman_params['repositories'] = repositories + + entity = module.lookup_entity('entity') + content_view_filter = module.ensure_entity( + 'content_view_filters', + module.foreman_params, + entity, + params=cv_scope, + state=filter_state, + foreman_spec=content_filter_spec, + ) + + if content_view_filter is not None: + cv_filter_scope = {'content_view_filter_id': content_view_filter['id']} + if 'errata_id' in module.foreman_params: + # should we try to find the errata the user is asking for? or just pass it blindly? + # errata = module.find_resource('errata', 'id={0}'.format(module.foreman_params['errata_id']), params=scope) + rule_spec = content_filter_rule_erratum_id_spec + search_scope = {'errata_id': module.foreman_params['errata_id']} + search_scope.update(cv_filter_scope) + search = None + else: + rule_spec = globals()['content_filter_rule_%s_spec' % (module.foreman_params['filter_type'])] + search_scope = cv_filter_scope + if module.foreman_params['rule_name'] is not None: + search = 'name="{0}"'.format(module.foreman_params['rule_name']) + else: + search = None + # not using find_resource_by_name here, because not all filters (errata) have names + content_view_filter_rule = module.find_resource('content_view_filter_rules', search, params=search_scope, failsafe=True) if entity else None + + if module.foreman_params['filter_type'] == 'package_group': + package_group = module.find_resource_by_name('package_groups', module.foreman_params['rule_name'], params=scope) + module.foreman_params['uuid'] = package_group['uuid'] + + # drop 'name' from the dict, as otherwise it might override 'rule_name' + rule_dict = module.foreman_params.copy() + rule_dict.pop('name', None) + + module.ensure_entity( + 'content_view_filter_rules', + rule_dict, + content_view_filter_rule, + params=cv_filter_scope, + state=rule_state, + foreman_spec=rule_spec, + ) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_view_version.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_view_version.py new file mode 100644 index 00000000..03739e70 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_view_version.py @@ -0,0 +1,265 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2018, Sean O'Keeffe <seanokeeffe797@gmail.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: content_view_version +version_added: 1.0.0 +short_description: Manage Content View Versions +description: + - Publish, Promote or Remove a Content View Version +author: Sean O'Keeffe (@sean797) +notes: + - You cannot use this to remove a Content View Version from a Lifecycle environment, you should promote another version first. + - For idempotency you must specify either C(version) or C(current_lifecycle_environment). +options: + content_view: + description: + - Name of the content view + required: true + type: str + description: + description: + - Description of the Content View Version + type: str + version: + description: + - The content view version number (i.e. 1.0) + type: str + lifecycle_environments: + description: + - The lifecycle environments the Content View Version should be in. + type: list + elements: str + force_promote: + description: + - Force content view promotion and bypass lifecycle environment restriction + default: false + type: bool + aliases: + - force + force_yum_metadata_regeneration: + description: + - Force metadata regeneration when performing Publish and Promote tasks + type: bool + default: false + current_lifecycle_environment: + description: + - The lifecycle environment that is already associated with the content view version + - Helpful for promoting a content view version + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Ensure content view version 2.0 is in Test & Pre Prod" + theforeman.foreman.content_view_version: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + content_view: "CV 1" + organization: "Default Organization" + version: "2.0" + lifecycle_environments: + - Test + - Pre Prod + +- name: "Ensure content view version in Test is also in Pre Prod" + theforeman.foreman.content_view_version: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + content_view: "CV 1" + organization: "Default Organization" + current_lifecycle_environment: Test + lifecycle_environments: + - Pre Prod + +- name: "Publish a content view, not idempotent" + theforeman.foreman.content_view_version: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + content_view: "CV 1" + organization: "Default Organization" + +- name: "Publish a content view and promote that version to Library & Dev, not idempotent" + theforeman.foreman.content_view_version: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + content_view: "CV 1" + organization: "Default Organization" + lifecycle_environments: + - Library + - Dev + +- name: "Ensure content view version 1.0 doesn't exist" + theforeman.foreman.content_view_version: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + content_view: "Web Servers" + organization: "Default Organization" + version: "1.0" + state: absent + +# Obtain information about a Content View and its versions +- name: find all CVs + theforeman.foreman.resource_info: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + organization: "Default Organization" + resource: content_views + search: 'name="Example Content"' + register: example_content + +# Obtain more details about all versions of a specific Content View +- name: "find content view versions of {{ cv_id }}" + theforeman.foreman.resource_info: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + organization: "Default Organization" + resource: content_view_versions + params: + content_view_id: "{{ example_content.resources[0].id }}" + register: version_information +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + content_view_versions: + description: List of content view versions. + type: list + elements: dict +''' + + +import re +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule + + +def promote_content_view_version(module, content_view_version, environments, force, force_yum_metadata_regeneration): + current_environment_ids = {environment['id'] for environment in content_view_version['environments']} + desired_environment_ids = {environment['id'] for environment in environments} + promote_to_environment_ids = list(desired_environment_ids - current_environment_ids) + + if promote_to_environment_ids: + payload = { + 'id': content_view_version['id'], + 'environment_ids': promote_to_environment_ids, + 'force': force, + 'force_yum_metadata_regeneration': force_yum_metadata_regeneration, + } + + module.record_before('content_view_versions', {'id': content_view_version['id'], 'environments': content_view_version['environments']}) + module.resource_action('content_view_versions', 'promote', params=payload) + module.record_after('content_view_versions', {'id': content_view_version['id'], 'environments': environments}) + module.record_after_full('content_view_versions', {'id': content_view_version['id'], 'environments': environments}) + + +class KatelloContentViewVersionModule(KatelloEntityAnsibleModule): + pass + + +def main(): + module = KatelloContentViewVersionModule( + foreman_spec=dict( + content_view=dict(type='entity', required=True, scope=['organization']), + description=dict(), + version=dict(), + lifecycle_environments=dict(type='entity_list', scope=['organization']), + force_promote=dict(type='bool', aliases=['force'], default=False), + force_yum_metadata_regeneration=dict(type='bool', default=False), + current_lifecycle_environment=dict(type='entity', resource_type='lifecycle_environments', scope=['organization']), + ), + mutually_exclusive=[['current_lifecycle_environment', 'version']], + ) + + module.task_timeout = 60 * 60 + + if 'version' in module.foreman_params and not re.match(r'^\d+\.\d+$', module.foreman_params['version']): + try: + major_version = int(module.foreman_params['version']) + module.foreman_params['version'] = "{0}.0".format(major_version) + except ValueError: + module.fail_json("The 'version' needs to be in the format 'X.Y', not '{0}'".format(module.foreman_params['version'])) + + with module.api_connection(): + scope = module.scope_for('organization') + content_view = module.lookup_entity('content_view') + + if 'current_lifecycle_environment' in module.foreman_params: + search_scope = {'content_view_id': content_view['id'], 'environment_id': module.lookup_entity('current_lifecycle_environment')['id']} + content_view_version = module.find_resource('content_view_versions', search=None, params=search_scope) + elif 'version' in module.foreman_params: + search = "content_view_id={0},version={1}".format(content_view['id'], module.foreman_params['version']) + content_view_version = module.find_resource('content_view_versions', search=search, failsafe=True) + else: + content_view_version = None + module.set_entity('entity', content_view_version) + + if module.desired_absent: + module.ensure_entity('content_view_versions', None, content_view_version, params=scope) + else: + module.auto_lookup_entities() + if content_view_version is None: + payload = { + 'id': content_view['id'], + } + if 'description' in module.foreman_params: + payload['description'] = module.foreman_params['description'] + if 'force_yum_metadata_regeneration' in module.foreman_params: + payload['force_yum_metadata_regeneration'] = module.foreman_params['force_yum_metadata_regeneration'] + if 'version' in module.foreman_params: + split_version = list(map(int, str(module.foreman_params['version']).split('.'))) + payload['major'] = split_version[0] + payload['minor'] = split_version[1] + + response = module.resource_action('content_views', 'publish', params=payload) + # workaround for https://projects.theforeman.org/issues/28138 + if not module.check_mode: + content_view_version_id = response['output'].get('content_view_version_id') or response['input'].get('content_view_version_id') + content_view_version = module.show_resource('content_view_versions', content_view_version_id) + else: + content_view_version = {'id': -1, 'environments': []} + + if 'lifecycle_environments' in module.foreman_params: + promote_content_view_version( + module, + content_view_version, + module.foreman_params['lifecycle_environments'], + force=module.foreman_params['force_promote'], + force_yum_metadata_regeneration=module.foreman_params['force_yum_metadata_regeneration'], + ) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/domain.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/domain.py new file mode 100644 index 00000000..ef05d5f5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/domain.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2018 Markus Bucher (ATIX AG) +# +# 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: domain +version_added: 1.0.0 +short_description: Manage Domains +description: + - Create, update, and delete Domains +author: + - "Markus Bucher (@m-bucher) ATIX AG" +options: + name: + description: The full DNS domain name + required: true + type: str + updated_name: + description: New domain name. When this parameter is set, the module will not be idempotent. + type: str + dns_proxy: + aliases: + - dns + description: DNS proxy to use within this domain for managing A records + required: false + type: str + description: + aliases: + - fullname + description: Full name describing the domain + required: false + type: str + parameters: + description: + - Domain specific host parameters +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.taxonomy + - theforeman.foreman.foreman.nested_parameters +''' + +EXAMPLES = ''' +- name: domain + theforeman.foreman.domain: + name: "example.org" + description: "Example Domain" + locations: + - "Munich" + organizations: + - "ACME" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + domains: + description: List of domains. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanTaxonomicEntityAnsibleModule, ParametersMixin + + +class ForemanDomainModule(ParametersMixin, ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanDomainModule( + argument_spec=dict( + updated_name=dict(), + ), + foreman_spec=dict( + name=dict(required=True), + description=dict(aliases=['fullname'], flat_name='fullname'), + dns_proxy=dict(type='entity', flat_name='dns_id', aliases=['dns'], resource_type='smart_proxies'), + ), + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/external_usergroup.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/external_usergroup.py new file mode 100644 index 00000000..f33f2447 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/external_usergroup.py @@ -0,0 +1,123 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Kirill Shirinkin (kirill@mkdev.me) +# +# 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: external_usergroup +version_added: 1.0.0 +short_description: Manage External User Groups +description: + - Create, update, and delete external user groups +author: + - "Kirill Shirinkin (@Fodoj)" +options: + name: + description: + - Name of the group + required: true + type: str + usergroup: + description: + - Name of the linked usergroup + required: true + type: str + auth_source: + description: + - Name of the authentication source to be used for this group + required: true + type: str + aliases: + - auth_source_ldap +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state +''' + +EXAMPLES = ''' +- name: Create an external user group + theforeman.foreman.external_usergroup: + name: test + auth_source: "My LDAP server" + usergroup: "Internal Usergroup" + state: present +- name: Link a group from FreeIPA + theforeman.foreman.external_usergroup: + name: ipa_users + auth_source: "External" + usergroup: "Internal Usergroup" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + external_usergroups: + description: List of external usergroups. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule + + +class ForemanExternalUsergroupModule(ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanExternalUsergroupModule( + foreman_spec=dict( + name=dict(required=True), + usergroup=dict(required=True), + auth_source=dict(required=True, aliases=['auth_source_ldap'], type='entity', flat_name='auth_source_id', resource_type='auth_sources'), + auth_source_ldap=dict(type='entity', invisible=True, flat_name='auth_source_id'), + auth_source_external=dict(type='entity', invisible=True, flat_name='auth_source_id'), + ), + ) + + params = {"usergroup_id": module.foreman_params.pop('usergroup')} + entity = None + + with module.api_connection(): + # There is no way to find by name via API search, so we need + # to iterate over all external user groups of a given usergroup + for external_usergroup in module.list_resource("external_usergroups", params=params): + if external_usergroup['name'] == module.foreman_params['name']: + entity = external_usergroup + + module.set_entity('entity', entity) + + auth_source = module.lookup_entity('auth_source') + if auth_source.get('type') == 'AuthSourceExternal': + module.set_entity('auth_source_external', auth_source) + elif auth_source.get('type') == 'AuthSourceLdap': + module.set_entity('auth_source_ldap', auth_source) + else: + module.fail_json(msg="Unsupported authentication source type: {0}".format(auth_source.get('type'))) + + module.run(params=params) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/global_parameter.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/global_parameter.py new file mode 100644 index 00000000..0b2b969f --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/global_parameter.py @@ -0,0 +1,164 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017 Matthias Dellweg & Bernhard Hopfenmüller (ATIX AG) +# +# 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/>. + +# pylint: disable=super-with-arguments + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: global_parameter +version_added: 1.0.0 +short_description: Manage Global Parameters +description: + - "Manage Global Parameter Entities" +author: + - "Bernhard Hopfenmueller (@Fobhep) ATIX AG" + - "Matthias Dellweg (@mdellweg) ATIX AG" + - "Manisha Singhal (@manisha15) ATIX AG" +options: + name: + description: + - Name of the Global Parameter + required: true + type: str + updated_name: + description: + - New name of the Global Parameter. When this parameter is set, the module will not be idempotent. + type: str + value: + description: + - Value of the Global Parameter + required: false + type: raw + hidden_value: + description: + - Whether the value should be hidden in the GUI + required: false + type: bool + parameter_type: + description: + - Type of value + default: string + choices: + - string + - boolean + - integer + - real + - array + - hash + - yaml + - json + type: str +notes: + - The I(parameter_type) only has an effect on Foreman >= 1.22 +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults +''' + +EXAMPLES = ''' +- name: "Create a Global Parameter" + theforeman.foreman.global_parameter: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "TheAnswer" + value: "42" + state: present_with_defaults + +- name: "Update a Global Parameter" + theforeman.foreman.global_parameter: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "TheAnswer" + value: "43" + state: present + +- name: "Delete a Global Parameter" + theforeman.foreman.global_parameter: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "TheAnswer" + state: absent +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + global_parameters: + description: List of global parameters. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule, parameter_value_to_str + + +class ForemanCommonParameterModule(ForemanEntityAnsibleModule): + def remove_sensitive_fields(self, entity): + if entity and 'hidden_value?' in entity: + entity['hidden_value'] = entity.pop('hidden_value?') + if entity['hidden_value']: + entity['value'] = None + return super(ForemanCommonParameterModule, self).remove_sensitive_fields(entity) + + +def main(): + module = ForemanCommonParameterModule( + foreman_spec=dict( + name=dict(required=True), + value=dict(type='raw'), + hidden_value=dict(type='bool'), + parameter_type=dict(default='string', choices=['string', 'boolean', 'integer', 'real', 'array', 'hash', 'yaml', 'json']), + ), + argument_spec=dict( + state=dict(default='present', choices=['present_with_defaults', 'present', 'absent']), + updated_name=dict(), + ), + required_if=( + ['state', 'present_with_defaults', ['value']], + ['state', 'present', ['value']], + ), + ) + + with module.api_connection(): + entity = module.lookup_entity('entity', params={'show_hidden': True}) + + if not module.desired_absent: + # Convert values according to their corresponding parameter_type + if entity and 'parameter_type' not in entity: + entity['parameter_type'] = 'string' + module.foreman_params['value'] = parameter_value_to_str(module.foreman_params['value'], module.foreman_params['parameter_type']) + if entity and 'value' in entity: + entity['value'] = parameter_value_to_str(entity['value'], entity.get('parameter_type', 'string')) + if entity and 'hidden_value?' in entity: + entity['hidden_value'] = entity.pop('hidden_value?') + + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/hardware_model.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/hardware_model.py new file mode 100644 index 00000000..27bf2944 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/hardware_model.py @@ -0,0 +1,102 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2020, Evgeni Golov <evgeni@golov.de> +# +# 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: hardware_model +version_added: 1.0.0 +short_description: Manage Hardware Models +description: + - Manage hardware models +author: + - "Evgeni Golov (@evgeni)" +options: + name: + description: + - Name of the hardware model + required: true + type: str + info: + description: + - General description of the hardware model + type: str + vendor_class: + description: + - The class of the machine as reported by the OpenBoot PROM. + - This is primarily used by Solaris SPARC builds and can be left blank for other architectures. + type: str + hardware_model: + description: + - The class of CPU supplied in this machine. + - This is primarily used by Sparc Solaris builds and can be left blank for other architectures. + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state +''' + +EXAMPLES = ''' +- name: "Create ACME Laptop model" + theforeman.foreman.hardware_model: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "acme laptop" + info: "this is the acme laptop" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + hardware_models: + description: List of hardware models. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule + + +class ForemanModelModule(ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanModelModule( + foreman_spec=dict( + name=dict(required=True), + info=dict(), + vendor_class=dict(), + hardware_model=dict(), + ), + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/host.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/host.py new file mode 100644 index 00000000..10ba47cb --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/host.py @@ -0,0 +1,505 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Bernhard Hopfenmüller (ATIX AG) +# +# 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: host +version_added: 1.0.0 +short_description: Manage Hosts +description: + - Create, update, and delete Hosts +author: + - "Bernhard Hopfenmueller (@Fobhep) ATIX AG" +options: + name: + description: + - Fully Qualified Domain Name of host + required: true + type: str + hostgroup: + description: + - Name of related hostgroup. + required: false + type: str + location: + description: + - Name of related location + required: false + type: str + organization: + description: + - Name of related organization + required: false + type: str + build: + description: + - Whether or not to setup build context for the host + type: bool + required: false + enabled: + description: + - Include this host within reporting + type: bool + required: false + managed: + description: + - Whether a host is managed or unmanaged. + - Forced to true when I(build=true) + type: bool + required: false + ip: + description: + - IP address of the primary interface of the host. + type: str + required: false + mac: + description: + - MAC address of the primary interface of the host. + - Please include leading zeros and separate nibbles by colons, otherwise the execution will not be idempotent. + - Example EE:BB:01:02:03:04 + type: str + required: false + comment: + description: + - Comment about the host. + type: str + required: false + owner: + description: + - Owner (user) of the host. + - Mutually exclusive with I(owner_group). + type: str + required: false + owner_group: + description: + - Owner (user group) of the host. + - Mutually excluside with I(owner). + type: str + required: false + provision_method: + description: + - The method used to provision the host. + - I(provision_method=bootdisk) is only available if the bootdisk plugin is installed. + choices: + - 'build' + - 'image' + - 'bootdisk' + type: str + required: false + image: + description: + - The image to use when I(provision_method=image). + - The I(compute_resource) parameter is required to find the correct image. + type: str + required: false + compute_attributes: + description: + - Additional compute resource specific attributes. + - When this parameter is set, the module will not be idempotent. + type: dict + required: false + interfaces_attributes: + description: + - Additional interfaces specific attributes. + version_added: 1.5.0 + required: false + type: list + elements: dict + suboptions: + mac: + description: + - MAC address of interface. Required for managed interfaces on bare metal. + - Please include leading zeros and separate nibbles by colons, otherwise the execution will not be idempotent. + - Example EE:BB:01:02:03:04 + - You need to set one of I(identifier), I(name) or I(mac) to be able to update existing interfaces and make execution idempotent. + type: str + ip: + description: + - IPv4 address of interface + type: str + ip6: + description: + - IPv6 address of interface + type: str + type: + description: + - Interface type. + type: str + choices: + - 'interface' + - 'bmc' + - 'bond' + - 'bridge' + name: + description: + - Interface's DNS name + - You need to set one of I(identifier), I(name) or I(mac) to be able to update existing interfaces and make execution idempotent. + type: str + subnet: + description: + - IPv4 Subnet name + type: str + subnet6: + description: + - IPv6 Subnet name + type: str + domain: + description: + - Domain name + - Required for primary interfaces on managed hosts. + type: str + identifier: + description: + - Device identifier, e.g. eth0 or eth1.1 + - You need to set one of I(identifier), I(name) or I(mac) to be able to update existing interfaces and make execution idempotent. + type: str + managed: + description: + - Should this interface be managed via DHCP and DNS smart proxy and should it be configured during provisioning? + type: bool + primary: + description: + - Should this interface be used for constructing the FQDN of the host? + - Each managed hosts needs to have one primary interface. + type: bool + provision: + description: + - Should this interface be used for TFTP of PXELinux (or SSH for image-based hosts)? + - Each managed hosts needs to have one provision interface. + type: bool + username: + description: + - Username for BMC authentication. + - Only for BMC interfaces. + type: str + password: + description: + - Password for BMC authentication. + - Only for BMC interfaces. + type: str + provider: + description: + - Interface provider, e.g. IPMI. + - Only for BMC interfaces. + type: str + choices: + - 'IPMI' + - 'SSH' + virtual: + description: + - Alias or VLAN device + type: bool + tag: + description: + - VLAN tag, this attribute has precedence over the subnet VLAN ID. + - Only for virtual interfaces. + type: str + mtu: + description: + - MTU, this attribute has precedence over the subnet MTU. + type: int + attached_to: + description: + - Identifier of the interface to which this interface belongs, e.g. eth1. + - Only for virtual interfaces. + type: str + mode: + description: + - Bond mode of the interface. + - Only for bond interfaces. + type: str + choices: + - 'balance-rr' + - 'active-backup' + - 'balance-xor' + - 'broadcast' + - '802.3ad' + - 'balance-tlb' + - 'balance-alb' + attached_devices: + description: + - Identifiers of attached interfaces, e.g. ['eth1', 'eth2']. + - For bond interfaces those are the slaves. + - Only for bond and bridges interfaces. + type: list + elements: str + bond_options: + description: + - Space separated options, e.g. miimon=100. + - Only for bond interfaces. + type: str + compute_attributes: + description: + - Additional compute resource specific attributes for the interface. + - When this parameter is set, the module will not be idempotent. + type: dict +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.host_options + - theforeman.foreman.foreman.nested_parameters + - theforeman.foreman.foreman.operatingsystem +''' + +EXAMPLES = ''' +- name: "Create a host" + theforeman.foreman.host: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "new_host" + hostgroup: my_hostgroup + state: present + +- name: "Create a host with build context" + theforeman.foreman.host: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "new_host" + hostgroup: my_hostgroup + build: true + state: present + +- name: "Create an unmanaged host" + theforeman.foreman.host: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "new_host" + managed: false + state: present + +- name: "Create a VM with 2 CPUs and 4GB RAM" + theforeman.foreman.host: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "new_host" + compute_attributes: + cpus: 2 + memory_mb: 4096 + state: present + +- name: "Create a VM and start it after creation" + theforeman.foreman.host: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "new_host" + compute_attributes: + start: "1" + state: present + +- name: "Create a VM on specific ovirt network" + theforeman.foreman.host: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "new_host" + interfaces_attributes: + - type: "interface" + compute_attributes: + name: "nic1" + network: "969efbe6-f9e0-4383-a19a-a7ee65ad5007" + interface: "virtio" + state: present + +- name: "Create a VM with 2 NICs on specific ovirt networks" + theforeman.foreman.host: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "new_host" + interfaces_attributes: + - type: "interface" + primary: true + compute_attributes: + name: "nic1" + network: "969efbe6-f9e0-4383-a19a-a7ee65ad5007" + interface: "virtio" + - type: "interface" + name: "new_host_nic2" + managed: true + compute_attributes: + name: "nic2" + network: "969efbe6-f9e0-4383-a19a-a7ee65ad5008" + interface: "e1000" + state: present + +- name: "Delete a host" + theforeman.foreman.host: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "new_host" + state: absent +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + hosts: + description: List of hosts. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ( + ensure_puppetclasses, + interfaces_spec, + ForemanEntityAnsibleModule, + HostMixin, +) + + +def ensure_host_interfaces(module, entity, interfaces): + scope = {'host_id': entity['id']} + + current_interfaces = module.list_resource('interfaces', params=scope) + current_interfaces_ids = {x['id'] for x in current_interfaces} + expected_interfaces_ids = set() + + for interface in interfaces: + if 1 == len(current_interfaces) == len(interfaces): + existing_interface = current_interfaces[0] + else: + for possible_identifier in ['identifier', 'name', 'mac']: + if possible_identifier in interface: + unique_identifier = possible_identifier + break + else: + unique_identifier = None + warning_msg = "The provided interface definition has no unique identifier and thus cannot be matched against existing interfaces. " \ + "This will always create a new interface and might not be the desired behaviour." + module.warn(warning_msg) + + existing_interface = next((x for x in current_interfaces if unique_identifier and x.get(unique_identifier) == interface[unique_identifier]), None) + + if 'mac' in interface: + interface['mac'] = interface['mac'].lower() + + # workaround for https://projects.theforeman.org/issues/31390 + if existing_interface is not None and 'attached_devices' in existing_interface: + existing_interface['attached_devices'] = existing_interface['attached_devices'].split(',') + + updated_interface = (existing_interface or {}).copy() + updated_interface.update(interface) + + module.ensure_entity('interfaces', updated_interface, existing_interface, params=scope, state='present', + foreman_spec=module.foreman_spec['interfaces_attributes']['foreman_spec']) + + if 'id' in updated_interface: + expected_interfaces_ids.add(updated_interface['id']) + + for leftover_interface in current_interfaces_ids - expected_interfaces_ids: + module.ensure_entity('interfaces', {}, {'id': leftover_interface}, params=scope, state='absent', + foreman_spec=module.foreman_spec['interfaces_attributes']['foreman_spec']) + + +class ForemanHostModule(HostMixin, ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanHostModule( + foreman_spec=dict( + name=dict(required=True), + hostgroup=dict(type='entity'), + location=dict(type='entity'), + organization=dict(type='entity'), + enabled=dict(type='bool'), + managed=dict(type='bool'), + build=dict(type='bool'), + ip=dict(), + mac=dict(), + comment=dict(), + owner=dict(type='entity', resource_type='users', flat_name='owner_id'), + owner_group=dict(type='entity', resource_type='usergroups', flat_name='owner_id'), + owner_type=dict(invisible=True), + provision_method=dict(choices=['build', 'image', 'bootdisk']), + image=dict(type='entity', scope=['compute_resource']), + compute_attributes=dict(type='dict'), + interfaces_attributes=dict(type='nested_list', foreman_spec=interfaces_spec, ensure=True), + ), + mutually_exclusive=[ + ['owner', 'owner_group'] + ], + required_by=dict( + image=('compute_resource',), + ), + ) + + # additional param validation + if '.' not in module.foreman_params['name']: + module.fail_json(msg="The hostname must be FQDN") + + if not module.desired_absent: + if 'build' in module.foreman_params and module.foreman_params['build']: + # When 'build'=True, 'managed' has to be True. Assuming that user's priority is to build. + if 'managed' in module.foreman_params and not module.foreman_params['managed']: + module.warn("when 'build'=True, 'managed' is ignored and forced to True") + module.foreman_params['managed'] = True + elif 'build' not in module.foreman_params and 'managed' in module.foreman_params and not module.foreman_params['managed']: + # When 'build' is not given and 'managed'=False, have to clear 'build' context that might exist on the server. + module.foreman_params['build'] = False + + if 'mac' in module.foreman_params: + module.foreman_params['mac'] = module.foreman_params['mac'].lower() + + if 'owner' in module.foreman_params: + module.foreman_params['owner_type'] = 'User' + elif 'owner_group' in module.foreman_params: + module.foreman_params['owner_type'] = 'Usergroup' + + if 'interfaces_attributes' in module.foreman_params: + filtered = [nic for nic in ({k: v for k, v in obj.items() if v} for obj in module.foreman_params['interfaces_attributes']) if nic] + module.foreman_params['interfaces_attributes'] = filtered + + with module.api_connection(): + entity = module.lookup_entity('entity') + + if not module.desired_absent: + module.auto_lookup_entities() + + # We use different APIs for creating a host with interfaces + # and updating it, so let's differentiate based on entity being present or not + if entity and 'interfaces_attributes' in module.foreman_params: + interfaces = module.foreman_params.pop('interfaces_attributes') + else: + interfaces = None + + expected_puppetclasses = module.foreman_params.pop('puppetclasses', None) + + entity = module.run() + + if not module.desired_absent: + if 'environment_id' in entity: + ensure_puppetclasses(module, 'host', entity, expected_puppetclasses) + if interfaces is not None: + ensure_host_interfaces(module, entity, interfaces) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/host_collection.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/host_collection.py new file mode 100644 index 00000000..cfd2cae9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/host_collection.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019, Maxim Burgerhout <maxim@wzzrd.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: host_collection +version_added: 1.0.0 +short_description: Manage Host Collections +description: + - Create and Manage host collections +author: + - "Maxim Burgerhout (@wzzrd)" + - "Christoffer Reijer (@ephracis)" +options: + description: + description: + - Description of the host collection + required: false + type: str + name: + description: + - Name of the host collection + required: true + type: str + updated_name: + description: + - New name of the host collection. When this parameter is set, the module will not be idempotent. + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Create Foo host collection" + theforeman.foreman.host_collection: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "Foo" + description: "Foo host collection for Foo servers" + organization: "My Cool new Organization" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + host_collections: + description: List of host collections. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule + + +class KatelloHostCollectionModule(KatelloEntityAnsibleModule): + pass + + +def main(): + module = KatelloHostCollectionModule( + argument_spec=dict( + updated_name=dict(), + ), + foreman_spec=dict( + name=dict(required=True), + description=dict(), + ), + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/host_power.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/host_power.py new file mode 100644 index 00000000..c594f04f --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/host_power.py @@ -0,0 +1,137 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Bernhard Hopfenmüller (ATIX AG) +# +# 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: host_power +version_added: 1.0.0 +short_description: Manage Power State of Hosts +description: + - "Manage power state of a host" + - "This beta version can start and stop an existing foreman host and question the current power state." +author: + - "Bernhard Hopfenmueller (@Fobhep) ATIX AG" + - "Baptiste Agasse (@bagasse)" +options: + name: + description: Name (FQDN) of the host + required: true + aliases: + - hostname + type: str + state: + description: Desired power state + default: state + choices: + - 'on' + - 'start' + - 'off' + - 'stop' + - 'soft' + - 'reboot' + - 'cycle' + - 'reset' + - 'state' + - 'status' + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman +''' + +EXAMPLES = ''' +- name: "Switch a host on" + theforeman.foreman.host_power: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + hostname: "test-host.domain.test" + state: on + +- name: "Switch a host off" + theforeman.foreman.host_power: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + hostname: "test-host.domain.test" + state: off + +- name: "Query host power state" + theforeman.foreman.host_power: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + hostname: "test-host.domain.test" + state: state + register: result +- debug: + msg: "Host power state is {{ result.power_state }}" + + +''' + +RETURN = ''' +power_state: + description: current power state of host + returned: always + type: str + sample: "off" + ''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule + + +def main(): + module = ForemanEntityAnsibleModule( + foreman_spec=dict( + name=dict(aliases=['hostname'], required=True), + ), + argument_spec=dict( + state=dict(default='state', choices=['on', 'start', 'off', 'stop', 'soft', 'reboot', 'cycle', 'reset', 'state', 'status']), + ) + ) + + module_params = module.foreman_params + + with module.api_connection(): + # power_status endpoint was only added in foreman 1.22.0 per https://projects.theforeman.org/issues/25436 + # Delete this piece when versions below 1.22 are off common use + # begin delete + if 'power_status' not in module.foremanapi.resource('hosts').actions: + params = {'id': module_params['name'], 'power_action': 'status'} + power_state = module.resource_action('hosts', 'power', params=params, ignore_check_mode=True) + power_state['state'] = 'on' if power_state['power'] == 'running' else 'off' + else: + # end delete (on delete un-indent the below two lines) + params = {'id': module_params['name']} + power_state = module.resource_action('hosts', 'power_status', params=params, ignore_check_mode=True) + + if module.state in ['state', 'status']: + module.exit_json(power_state=power_state['state']) + elif ((module.state in ['on', 'start'] and power_state['state'] == 'on') + or (module.state in ['off', 'stop'] and power_state['state'] == 'off')): + module.exit_json() + else: + params['power_action'] = module.state + module.resource_action('hosts', 'power', params=params) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/hostgroup.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/hostgroup.py new file mode 100644 index 00000000..d0c37077 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/hostgroup.py @@ -0,0 +1,190 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Manisha Singhal (ATIX AG) +# +# 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: hostgroup +version_added: 1.0.0 +short_description: Manage Hostgroups +description: + - Create, update, and delete Hostgroups +author: + - "Manisha Singhal (@Manisha15) ATIX AG" + - "Baptiste Agasse (@bagasse)" +options: + name: + description: Name of hostgroup + required: true + type: str + updated_name: + description: New name of hostgroup. When this parameter is set, the module will not be idempotent. + type: str + description: + description: Description of hostgroup + required: false + type: str + parent: + description: Hostgroup parent name + required: false + type: str + organization: + description: + - Organization for scoped resources attached to the hostgroup. + - Only used for Katello installations. + - This organization will implicitly be added to the I(organizations) parameter if needed. + required: false + type: str + parameters: + description: + - Hostgroup specific host parameters +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.taxonomy + - theforeman.foreman.foreman.nested_parameters + - theforeman.foreman.foreman.host_options + - theforeman.foreman.foreman.operatingsystem +''' + +EXAMPLES = ''' +- name: "Create a Hostgroup" + theforeman.foreman.hostgroup: + name: "new_hostgroup" + architecture: "architecture_name" + operatingsystem: "operatingsystem_name" + medium: "media_name" + ptable: "Partition_table_name" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: "Update a Hostgroup" + theforeman.foreman.hostgroup: + name: "new_hostgroup" + architecture: "updated_architecture_name" + operatingsystem: "updated_operatingsystem_name" + organizations: + - Org One + - Org Two + locations: + - Loc One + - Loc Two + - Loc One/Nested loc + medium: "updated_media_name" + ptable: "updated_Partition_table_name" + root_pass: "password" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: "My nested hostgroup" + theforeman.foreman.hostgroup: + parent: "new_hostgroup" + name: "my nested hostgroup" + +- name: "My hostgroup with some proxies" + theforeman.foreman.hostgroup: + name: "my hostgroup" + environment: production + puppet_proxy: puppet-proxy.example.com + puppet_ca_proxy: puppet-proxy.example.com + openscap_proxy: openscap-proxy.example.com + +- name: "My katello related hostgroup" + theforeman.foreman.hostgroup: + organization: "My Org" + name: "kt hostgroup" + content_source: capsule.example.com + lifecycle_environment: "Production" + content_view: "My content view" + parameters: + - name: "kt_activation_keys" + value: "my_prod_ak" + +- name: "Delete a Hostgroup" + theforeman.foreman.hostgroup: + name: "new_hostgroup" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: absent +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + hostgroups: + description: List of hostgroups. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ( + ensure_puppetclasses, + HostMixin, + ForemanTaxonomicEntityAnsibleModule, +) + + +class ForemanHostgroupModule(HostMixin, ForemanTaxonomicEntityAnsibleModule): + PARAMETERS_FLAT_NAME = 'group_parameters_attributes' + + +def main(): + module = ForemanHostgroupModule( + foreman_spec=dict( + name=dict(required=True), + description=dict(), + parent=dict(type='entity'), + organization=dict(type='entity', required=False, ensure=False), + ), + argument_spec=dict( + updated_name=dict(), + ), + required_by=dict( + content_source=('organization',), + content_view=('organization',), + lifecycle_environment=('organization',), + ), + ) + + module_params = module.foreman_params + with module.api_connection(): + if not module.desired_absent: + if 'organization' in module_params: + if 'organizations' in module_params: + if module_params['organization'] not in module_params['organizations']: + module_params['organizations'].append(module_params['organization']) + else: + module_params['organizations'] = [module_params['organization']] + expected_puppetclasses = module_params.pop('puppetclasses', None) + entity = module.run() + if not module.desired_absent and 'environment_id' in entity: + ensure_puppetclasses(module, 'hostgroup', entity, expected_puppetclasses) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/http_proxy.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/http_proxy.py new file mode 100644 index 00000000..0c467dfa --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/http_proxy.py @@ -0,0 +1,118 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2020 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 <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: http_proxy +version_added: 1.1.0 +short_description: Manage HTTP Proxies +description: + - Create, update, and delete HTTP Proxies +author: + - "Evgeni Golov (@evgeni)" +options: + name: + description: + - The HTTP Proxy name + required: true + type: str + url: + description: + - URL of the HTTP Proxy + - Required when creating a new HTTP Proxy. + required: False + type: str + proxy_username: + description: + - Username used to authenticate with the HTTP Proxy + required: False + type: str + proxy_password: + description: + - Password used to authenticate with the HTTP Proxy + - When this parameter is set, the module will not be idempotent. + required: False + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.taxonomy +''' + +EXAMPLES = ''' +- name: create example.org proxy + theforeman.foreman.http_proxy: + name: "example.org" + url: "http://example.org:3128" + locations: + - "Munich" + organizations: + - "ACME" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + http_proxies: + description: List of HTTP proxies. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanTaxonomicEntityAnsibleModule + + +class ForemanHttpProxyModule(ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanHttpProxyModule( + foreman_spec=dict( + name=dict(required=True), + url=dict(), + proxy_username=dict(flat_name='username'), + proxy_password=dict(no_log=True, flat_name='password'), + ), + ) + + with module.api_connection(): + entity = module.lookup_entity('entity') + + if not module.desired_absent: + if 'url' not in module.foreman_params: + if not entity: + module.fail_json(msg="The 'url' parameter is required when creating a new HTTP Proxy.") + else: + module.foreman_params['url'] = entity['url'] + + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/image.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/image.py new file mode 100644 index 00000000..1c08f926 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/image.py @@ -0,0 +1,135 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2020 Mark Hlawatschek (ATIX AG) +# +# 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: image +version_added: 1.0.0 +short_description: Manage Images +description: + - Create, update, and delete Images +author: + - "Mark Hlawatschek (@hlawatschek) ATIX AG" +options: + name: + description: Image name + required: true + type: str + compute_resource: + description: Compute resource the image is assigned to + required: true + type: str + uuid: + aliases: + - image_uuid + description: UUID or Marketplace URN of the operatingsystem image + required: true + type: str + image_username: + description: Username that is used to login into the operating system + required: true + type: str + image_password: + description: Password that is used to login into the operating system + required: false + type: str + operatingsystem: + required: true + architecture: + description: architecture of the image + required: true + type: str + user_data: + description: Image supports user_data + required: false + type: bool +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.operatingsystem +''' + +EXAMPLES = ''' + - name: create Image for EC2 + image: + name: CentOS + image_uuid: "ami-0ff760d16d9497662" + image_username: "centos" + operatingsystem: "CentOS 7" + compute_resource: "AWS" + architecture: "x86_64" +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + images: + description: List of images. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule + + +class ForemanImageModule(ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanImageModule( + argument_spec=dict( + image_username=dict(required=True), + image_password=dict(no_log=True), + ), + foreman_spec=dict( + name=dict(required=True), + username=dict(invisible=True), + uuid=dict(required=True, aliases=['image_uuid']), + password=dict(invisible=True, no_log=True), + compute_resource=dict(type='entity', required=True), + architecture=dict(type='entity', required=True), + operatingsystem=dict(type='entity', required=True), + user_data=dict(type='bool') + ), + entity_opts={'scope': ['compute_resource']}, + ) + + module.foreman_params['username'] = module.foreman_params.pop('image_username') + if 'image_password' in module.foreman_params: + module.foreman_params['password'] = module.foreman_params.pop('image_password') + with module.api_connection(): + scope = module.scope_for('compute_resource') + operatingsystem_id = module.lookup_entity('operatingsystem')['id'] + module.set_entity('entity', module.find_resource( + 'images', + search='name="{0}",operatingsystem="{1}"'.format(module.foreman_params['name'], operatingsystem_id), + params=scope, + failsafe=True, + )) + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/installation_medium.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/installation_medium.py new file mode 100644 index 00000000..6a0930e4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/installation_medium.py @@ -0,0 +1,151 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2018 Manuel Bonk (ATIX AG) +# +# 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: installation_medium +version_added: 1.0.0 +short_description: Manage Installation Media +description: + - Create, update, and delete Installation Media +author: + - "Manuel Bonk(@manuelbonk) ATIX AG" +options: + name: + description: + - The full installation medium name. + - The special name "*" (only possible as parameter) is used to perform bulk actions (modify, delete) on all existing partition tables. + required: true + type: str + updated_name: + description: New full installation medium name. When this parameter is set, the module will not be idempotent. + type: str + os_family: + description: + - The OS family the template shall be assigned with. + - If no os_family is set but a operatingsystem, the value will be derived from it. + path: + description: Path to the installation medium + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults + - theforeman.foreman.foreman.taxonomy + - theforeman.foreman.foreman.os_family + - theforeman.foreman.foreman.operatingsystems +''' + +EXAMPLES = ''' +- name: create new debian medium + theforeman.foreman.installation_medium: + name: "wheezy" + locations: + - "Munich" + organizations: + - "ACME" + operatingsystems: + - "Debian" + path: "http://debian.org/mirror/" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + media: + description: List of installation media. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanTaxonomicEntityAnsibleModule, OS_LIST + + +class ForemanInstallationMediumModule(ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanInstallationMediumModule( + argument_spec=dict( + updated_name=dict(), + state=dict(default='present', choices=['present', 'present_with_defaults', 'absent']), + ), + foreman_spec=dict( + name=dict(required=True), + operatingsystems=dict(type='entity_list'), + os_family=dict(choices=OS_LIST), + path=dict(), + ), + entity_opts=dict( + resource_type='media', + ), + entity_name='medium', + ) + + module_params = module.foreman_params + entity = None + + name = module_params['name'] + + affects_multiple = name == '*' + # sanitize user input, filter unuseful configuration combinations with 'name: *' + if affects_multiple: + if module.state == 'present_with_defaults': + module.fail_json(msg="'state: present_with_defaults' and 'name: *' cannot be used together") + if module.params['updated_name']: + module.fail_json(msg="updated_name not allowed if 'name: *'!") + if module.desired_absent: + further_params = set(module_params.keys()) - {'name', 'entity'} + if further_params: + module.fail_json(msg='When deleting all installation media, there is no need to specify further parameters: %s ' % further_params) + + with module.api_connection(): + if affects_multiple: + module.set_entity('entity', None) # prevent lookup + entities = module.list_resource('media') + if not entities: + # Nothing to do shortcut to exit + module.exit_json() + if not module.desired_absent: # not 'thin' + entities = [module.show_resource('media', entity['id']) for entity in entities] + module.auto_lookup_entities() + module_params.pop('name') + for entity in entities: + module.ensure_entity('media', module_params, entity) + else: + entity = module.lookup_entity('entity') + if not module.desired_absent and 'operatingsystems' in module_params: + operatingsystems = module.lookup_entity('operatingsystems') + if len(operatingsystems) == 1 and 'os_family' not in module_params and entity is None: + module_params['os_family'] = module.show_resource('operatingsystems', operatingsystems[0]['id'])['family'] + + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/job_invocation.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/job_invocation.py new file mode 100644 index 00000000..58e296b8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/job_invocation.py @@ -0,0 +1,225 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2020 Peter Ondrejka <pondrejk@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: job_invocation +short_description: Invoke Remote Execution Jobs +version_added: 1.4.0 +description: + - "Invoke and schedule Remote Execution Jobs" +author: + - "Peter Ondrejka (@pondrejk)" +options: + search_query: + description: + - Search query to identify hosts + type: str + bookmark: + description: + - Bookmark to infer the search query from + type: str + job_template: + description: + - Job template to execute + required: true + type: str + targeting_type: + description: + - Dynamic query updates the search results before execution (useful for scheduled jobs) + choices: + - static_query + - dynamic_query + default: static_query + type: str + randomized_ordering: + description: + - Whether to order the selected hosts randomly + type: bool + execution_timeout_interval: + description: + - Override the timeout interval from the template for this invocation only + type: int + ssh: + description: + - ssh related options + type: dict + suboptions: + effective_user: + description: + - What user should be used to run the script (using sudo-like mechanisms) + - Defaults to a template parameter or global setting + type: str + command: + description: + - Command to be executed on host. Required for command templates + type: str + inputs: + description: + - Inputs to use + type: dict + recurrence: + description: + - Schedule a recurring job + type: dict + suboptions: + cron_line: + description: + - How often the job should occur, in the cron format + type: str + max_iteration: + description: + - Repeat a maximum of N times + type: int + end_time: + description: + - Perform no more executions after this time + type: str + scheduling: + description: + - Schedule the job to start at a later time + type: dict + suboptions: + start_at: + description: + - Schedule the job for a future time + type: str + start_before: + description: + - Indicates that the action should be cancelled if it cannot be started before this time. + type: str + concurrency_control: + description: + - Control concurrency level and distribution over time + type: dict + suboptions: + time_span: + description: + - Distribute tasks over given number of seconds + type: int + concurrency_level: + description: + - Maximum jobs to be executed at once + type: int +extends_documentation_fragment: + - theforeman.foreman.foreman +''' + +EXAMPLES = ''' + +- name: "Run remote command on a single host once" + job_invocation: + search_query: "name ^ (foreman.example.com)" + command: 'ls' + job_template: "Run Command - SSH Default" + ssh: + effective_user: "tester" + +- name: "Run ansible command on active hosts once a day" + job_invocation: + bookmark: 'active' + command: 'pwd' + job_template: "Run Command - Ansible Default" + recurrence: + cron_line: "30 2 * * *" + concurrency_control: + concurrency_level: 2 +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + job_invocations: + description: List of job invocations + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ( + ForemanAnsibleModule, +) + +ssh_foreman_spec = { + 'effective_user': dict(), +} + +recurrence_foreman_spec = { + 'cron_line': dict(), + 'max_iteration': dict(type='int'), + 'end_time': dict(), +} + +scheduling_foreman_spec = { + 'start_at': dict(), + 'start_before': dict(), +} + +concurrency_control_foreman_spec = { + 'time_span': dict(type='int'), + 'concurrency_level': dict(type='int'), +} + + +class ForemanJobInvocationModule(ForemanAnsibleModule): + pass + + +def main(): + module = ForemanJobInvocationModule( + foreman_spec=dict( + search_query=dict(), + bookmark=dict(type='entity'), + job_template=dict(required=True, type='entity'), + targeting_type=dict(default='static_query', choices=['static_query', 'dynamic_query']), + randomized_ordering=dict(type='bool'), + command=dict(), + inputs=dict(type='dict'), + execution_timeout_interval=dict(type='int'), + ssh=dict(type='dict', options=ssh_foreman_spec), + recurrence=dict(type='dict', options=recurrence_foreman_spec), + scheduling=dict(type='dict', options=scheduling_foreman_spec), + concurrency_control=dict(type='dict', options=concurrency_control_foreman_spec), + ), + required_one_of=[['search_query', 'bookmark']], + required_if=[ + ['job_template', 'Run Command - SSH Default', ['command']], + ['job_template', 'Run Command - Ansible Default', ['command']], + ], + ) + + # command input required by api + if 'command' in module.foreman_params: + module.foreman_params['inputs'] = {"command": module.foreman_params.pop('command')} + + with module.api_connection(): + if 'bookmark' in module.foreman_params: + module.set_entity('bookmark', module.find_resource('bookmarks', search='name="{0}",controller="hosts"'.format( + module.foreman_params['bookmark']), + failsafe=False, + )) + module.auto_lookup_entities() + module.ensure_entity('job_invocations', module.foreman_params, None, state='present') + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/job_template.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/job_template.py new file mode 100644 index 00000000..c8485203 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/job_template.py @@ -0,0 +1,463 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2018 Manuel Bonk & Matthias Dellweg (ATIX AG) +# +# 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: job_template +version_added: 1.0.0 +short_description: Manage Job Templates +description: + - Manage Remote Execution Job Templates +author: + - "Manuel Bonk (@manuelbonk) ATIX AG" + - "Matthias Dellweg (@mdellweg) ATIX AG" +options: + audit_comment: + description: + - Content of the audit comment field + type: str + description_format: + description: + - description of the job template. Template inputs can be referenced. + type: str + file_name: + description: + - The path of a template file, that shall be imported. + - Either this or I(template) is required as a source for the Job Template "content". + type: path + job_category: + description: + - The category the template should be assigend to + type: str + locked: + description: + - Determines whether the template shall be locked + default: false + type: bool + name: + description: + - The name of the Job Template. + - If omited, will be determined from the C(name) header of the template or the filename (in that order). + - The special value "*" can be used to perform bulk actions (modify, delete) on all existing templates. + type: str + provider_type: + description: + - Determines via which provider the template shall be executed + required: false + type: str + snippet: + description: + - Determines whether the template shall be a snippet + type: bool + template: + description: + - The content of the Job Template. + - Either this or I(file_name) is required as a source for the Job Template "content". + type: str + template_inputs: + description: + - The template inputs used in the Job Template + type: list + elements: dict + suboptions: + advanced: + description: + - Template Input is advanced + type: bool + description: + description: + - description of the Template Input + type: str + fact_name: + description: + - Fact name to use. + - Required when I(input_type=fact). + type: str + input_type: + description: + - input type + required: true + choices: + - user + - fact + - variable + - puppet_parameter + type: str + name: + description: + - name of the Template Input + required: true + type: str + options: + description: + - Template values for user inputs. Must be an array of any type. + type: list + elements: raw + puppet_class_name: + description: + - Puppet class name. + - Required when I(input_type=puppet_parameter). + type: str + puppet_parameter_name: + description: + - Puppet parameter name. + - Required when I(input_type=puppet_parameter). + type: str + required: + description: + - Is the input required + type: bool + variable_name: + description: + - Variable name to use. + - Required when I(input_type=variable). + type: str + value_type: + description: + - Type of the value + choices: + - plain + - search + - date + type: str + resource_type: + description: + - Type of the resource + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults + - theforeman.foreman.foreman.taxonomy +''' + +EXAMPLES = ''' + +- name: "Create a Job Template inline" + theforeman.foreman.job_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: A New Job Template + state: present + template: | + <%# + name: A Job Template + %> + rm -rf <%= input("toDelete") %> + template_inputs: + - name: toDelete + input_type: user + locations: + - Gallifrey + organizations: + - TARDIS INC + +- name: "Create a Job Template from a file" + theforeman.foreman.job_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: a new job template + file_name: timeywimey_template.erb + template_inputs: + - name: a new template input + input_type: user + state: present + locations: + - Gallifrey + organizations: + - TARDIS INC + +- name: "remove a job template's template inputs" + theforeman.foreman.job_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: a new job template + template_inputs: [] + state: present + locations: + - Gallifrey + organizations: + - TARDIS INC + +- name: "Delete a Job Template" + theforeman.foreman.job_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: timeywimey + state: absent + +- name: "Create a Job Template from a file and modify with parameter(s)" + theforeman.foreman.job_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + file_name: timeywimey_template.erb + name: Wibbly Wobbly Template + state: present + locations: + - Gallifrey + organizations: + - TARDIS INC + +# Providing a name in this case wouldn't be very sensible. +# Alternatively make use of with_filetree to parse recursively with filter. +- name: Parsing a directory of Job templates + theforeman.foreman.job_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + file_name: "{{ item }}" + state: present + locations: + - SKARO + organizations: + - DALEK INC + with_fileglob: + - "./arsenal_templates/*.erb" + +# If the templates are stored locally and the ansible module is executed on a remote host +- name: Ensure latest version of all your Job Templates + theforeman.foreman.job_template: + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + template: '{{ lookup("file", item.src) }}' + with_filetree: '/path/to/job/templates' + when: item.state == 'file' + + +# with name set to "*" bulk actions can be performed +- name: "Delete *ALL* Job Templates" + theforeman.foreman.job_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "*" + state: absent + +- name: "Assign all Job Templates to the same organization(s)" + theforeman.foreman.job_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "*" + state: present + organizations: + - DALEK INC + - sky.net + - Doc Brown's garage + +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + job_templates: + description: List of job templates. + type: list + elements: dict + template_inputs: + description: List of template inputs associated with the job template. + type: list + elements: dict +''' + +import os +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ( + ForemanTaxonomicEntityAnsibleModule, + parse_template, + parse_template_from_file, +) + + +template_defaults = { + 'provider_type': 'SSH', + 'job_category': 'unknown', +} + + +template_input_foreman_spec = { + 'id': dict(invisible=True), + 'name': dict(required=True), + 'description': dict(), + 'required': dict(type='bool'), + 'advanced': dict(type='bool'), + 'input_type': dict(required=True, choices=[ + 'user', + 'fact', + 'variable', + 'puppet_parameter', + ]), + 'fact_name': dict(), + 'variable_name': dict(), + 'puppet_class_name': dict(), + 'puppet_parameter_name': dict(), + 'options': dict(type='list', elements='raw'), + 'value_type': dict(choices=[ + 'plain', + 'search', + 'date', + ]), + 'resource_type': dict(), +} + + +class ForemanJobTemplateModule(ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanJobTemplateModule( + foreman_spec=dict( + description_format=dict(), + job_category=dict(), + locked=dict(type='bool', default=False), + name=dict(), + provider_type=dict(), + snippet=dict(type='bool'), + template=dict(), + template_inputs=dict( + type='nested_list', + foreman_spec=template_input_foreman_spec, + required_if=( + ['input_type', 'fact', ('fact_name',)], + ['input_type', 'variable', ('variable_name',)], + ['input_type', 'puppet_parameter', ('puppet_class_name', 'puppet_parameter_name')], + ), + ), + ), + argument_spec=dict( + audit_comment=dict(), + file_name=dict(type='path'), + state=dict(default='present', choices=['absent', 'present_with_defaults', 'present']), + ), + mutually_exclusive=[ + ['file_name', 'template'], + ], + required_one_of=[ + ['name', 'file_name', 'template'], + ], + ) + + # We do not want a layout text for bulk operations + if module.foreman_params.get('name') == '*': + if module.foreman_params.get('file_name') or module.foreman_params.get('template'): + module.fail_json( + msg="Neither file_name nor template allowed if 'name: *'!") + + entity = None + file_name = module.foreman_params.pop('file_name', None) + + if file_name or 'template' in module.foreman_params: + if file_name: + parsed_dict = parse_template_from_file(file_name, module) + else: + parsed_dict = parse_template(module.foreman_params['template'], module) + # sanitize name from template data + # The following condition can actually be hit, when someone is trying to import a + # template with the name set to '*'. + # Besides not being sensible, this would go horribly wrong in this module. + if parsed_dict.get('name') == '*': + module.fail_json(msg="Cannot use '*' as a job template name!") + # module params are priorized + parsed_dict.update(module.foreman_params) + # make sure certain values are set + module.foreman_params = template_defaults.copy() + module.foreman_params.update(parsed_dict) + + # make sure, we have a name + if 'name' not in module.foreman_params: + if file_name: + module.foreman_params['name'] = os.path.splitext( + os.path.basename(file_name))[0] + else: + module.fail_json( + msg='No name specified and no filename to infer it.') + + affects_multiple = module.foreman_params['name'] == '*' + # sanitize user input, filter unuseful configuration combinations with 'name: *' + if affects_multiple: + if module.state == 'present_with_defaults': + module.fail_json(msg="'state: present_with_defaults' and 'name: *' cannot be used together") + if module.desired_absent: + further_params = set(module.foreman_params.keys()) - {'name', 'entity'} + if further_params: + module.fail_json(msg='When deleting all job templates, there is no need to specify further parameters: %s ' % further_params) + + with module.api_connection(): + if 'audit_comment' in module.foreman_params: + extra_params = {'audit_comment': module.foreman_params['audit_comment']} + else: + extra_params = {} + + if affects_multiple: + module.set_entity('entity', None) # prevent lookup + entities = module.list_resource('job_templates') + if not entities: + # Nothing to do; shortcut to exit + module.exit_json() + if not module.desired_absent: # not 'thin' + entities = [module.show_resource('job_templates', entity['id']) for entity in entities] + module.auto_lookup_entities() + module.foreman_params.pop('name') + for entity in entities: + module.ensure_entity('job_templates', module.foreman_params, entity, params=extra_params) + else: + # The name could have been determined to late, so copy it again + module.foreman_params['entity'] = module.foreman_params['name'] + entity = module.lookup_entity('entity') + # TemplateInputs need to be added as separate entities later + template_inputs = module.foreman_params.get('template_inputs') + + job_template = module.run(params=extra_params) + + update_dependent_entities = (module.state == 'present' or (module.state == 'present_with_defaults' and module.changed)) + if update_dependent_entities and template_inputs is not None: + scope = {'template_id': job_template['id']} + + # Manage TemplateInputs here + current_template_input_list = module.list_resource('template_inputs', params=scope) if entity else [] + current_template_inputs = {item['name']: item for item in current_template_input_list} + for template_input_dict in template_inputs: + template_input_entity = current_template_inputs.pop(template_input_dict['name'], None) + + module.ensure_entity( + 'template_inputs', template_input_dict, template_input_entity, + params=scope, foreman_spec=template_input_foreman_spec, + ) + + # At this point, desired template inputs have been removed from the dict. + for template_input_entity in current_template_inputs.values(): + module.ensure_entity( + 'template_inputs', None, template_input_entity, state="absent", + params=scope, foreman_spec=template_input_foreman_spec, + ) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/lifecycle_environment.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/lifecycle_environment.py new file mode 100644 index 00000000..a6ac287d --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/lifecycle_environment.py @@ -0,0 +1,118 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017, Andrew Kofink <ajkofink@gmail.com> +# (c) 2019, Baptiste Agasse <baptiste.agasse@gmail.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: lifecycle_environment +version_added: 1.0.0 +short_description: Manage Lifecycle Environments +description: + - Create and manage lifecycle environments +author: + - "Andrew Kofink (@akofink)" + - "Baptiste Agasse (@bagasse)" +options: + name: + description: + - Name of the lifecycle environment + required: true + type: str + label: + description: + - Label of the lifecycle environment. This field cannot be updated. + type: str + description: + description: + - Description of the lifecycle environment + type: str + prior: + description: + - Name of the parent lifecycle environment + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Add a production lifecycle environment" + theforeman.foreman.lifecycle_environment: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "Production" + label: "production" + organization: "Default Organization" + prior: "Library" + description: "The production environment" + state: "present" +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + lifecycle_environments: + description: List of lifecycle environments. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule + + +class KatelloLifecycleEnvironmentModule(KatelloEntityAnsibleModule): + pass + + +def main(): + module = KatelloLifecycleEnvironmentModule( + foreman_spec=dict( + name=dict(required=True), + label=dict(), + description=dict(), + prior=dict(type='entity', resource_type='lifecycle_environments', scope=['organization']), + ), + ) + + with module.api_connection(): + entity = module.lookup_entity('entity') + + # Default to 'Library' for new env with no 'prior' provided + if 'prior' not in module.foreman_params and not entity: + module.foreman_params['prior'] = 'Library' + + if entity and not module.desired_absent: + if 'label' in module.foreman_params and entity['label'] != module.foreman_params['label']: + module.fail_json(msg="Label cannot be updated on a lifecycle environment.") + + if 'prior' in module.foreman_params and entity['prior']['id'] != module.lookup_entity('prior')['id']: + module.fail_json(msg="Prior cannot be updated on a lifecycle environment.") + + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/location.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/location.py new file mode 100644 index 00000000..567186ca --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/location.py @@ -0,0 +1,127 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017, Matthias M Dellweg <dellweg@atix.de> (ATIX AG) +# +# 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: location +version_added: 1.0.0 +short_description: Manage Locations +description: + - Manage Locations +author: + - "Matthias M Dellweg (@mdellweg) ATIX AG" +options: + name: + description: + - Name of the Location + required: true + type: str + parent: + description: + - Title of a parent Location for nesting + type: str + organizations: + description: + - List of organizations the location should be assigned to + type: list + elements: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.nested_parameters +''' + +EXAMPLES = ''' +# Create a simple location +- name: "Create CI Location" + theforeman.foreman.location: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "My Cool New Location" + organizations: + - "Default Organization" + state: present + +# Create a nested location +- name: "Create Nested CI Location" + theforeman.foreman.location: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "My Nested location" + parent: "My Cool New Location" + state: present + +# Create a new nested location with parent included in name +- name: "Create New Nested Location" + theforeman.foreman.location: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "My Cool New Location/New nested location" + state: present + +# Move a nested location to another parent +- name: "Create Nested CI Location" + theforeman.foreman.location: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "My Cool New Location/New nested location" + parent: "My Cool New Location/My Nested location" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + locations: + description: List of locations. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule, NestedParametersMixin + + +class ForemanLocationModule(NestedParametersMixin, ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanLocationModule( + foreman_spec=dict( + name=dict(required=True), + parent=dict(type='entity'), + organizations=dict(type='entity_list'), + ), + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/operatingsystem.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/operatingsystem.py new file mode 100644 index 00000000..69d70d3d --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/operatingsystem.py @@ -0,0 +1,233 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017 Matthias M Dellweg (ATIX AG) +# (c) 2017 Bernhard Hopfenmüller (ATIX AG) +# +# 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: operatingsystem +version_added: 1.0.0 +short_description: Manage Operating Systems +description: + - Manage Operating Systems +author: + - "Matthias M Dellweg (@mdellweg) ATIX AG" + - "Bernhard Hopfenmüller (@Fobhep) ATIX AG" +options: + name: + description: + - Name of the Operating System + required: true + type: str + updated_name: + description: New operating system name. When this parameter is set, the module will not be idempotent. + type: str + release_name: + description: + - Release name of the operating system (recommended for debian) + type: str + description: + description: + - Description of the Operating System + required: false + type: str + os_family: + description: + - Distribution family of the Operating System + aliases: + - family + major: + description: + - major version of the Operating System + required: false + type: str + minor: + description: + - minor version of the Operating System + required: false + type: str + architectures: + description: + - architectures, the operating system can be installed on + required: false + type: list + elements: str + media: + description: + - list of installation media + required: false + type: list + elements: str + ptables: + description: + - list of partitioning tables + required: false + type: list + elements: str + provisioning_templates: + description: + - List of provisioning templates that are associated with the operating system. + - Specify the full list of template names you want to associate with your OS. + - For example ["Kickstart default", "Kickstart default finish", "Kickstart default iPXE", "custom"]. + - After specifying the template associations, you can set the default association in + - the M(theforeman.foreman.os_default_template) module. + required: false + type: list + elements: str + password_hash: + description: + - hashing algorithm for passwd + required: false + choices: + - MD5 + - SHA256 + - SHA512 + - Base64 + - Base64-Windows + type: str + parameters: + description: + - Operating System specific host parameters +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults + - theforeman.foreman.foreman.nested_parameters + - theforeman.foreman.foreman.os_family +''' + +EXAMPLES = ''' +- name: "Create an Operating System" + theforeman.foreman.operatingsystem: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: Debian + release_name: stretch + family: Debian + major: 9 + parameters: + - name: additional-packages + value: python vim + state: present + +- name: "Ensure existence of an Operating System (provide default values)" + theforeman.foreman.operatingsystem: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: Centos + family: Redhat + major: 7 + password_hash: SHA256 + state: present_with_defaults + +- name: "Delete an Operating System" + theforeman.foreman.operatingsystem: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: Debian + family: Debian + major: 9 + state: absent +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + operatinsystems: + description: List of operatinsystems. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ( + ForemanEntityAnsibleModule, + ParametersMixin, + OS_LIST, +) + + +class ForemanOperatingsystemModule(ParametersMixin, ForemanEntityAnsibleModule): + PARAMETERS_FLAT_NAME = 'os_parameters_attributes' + + +def main(): + module = ForemanOperatingsystemModule( + foreman_spec=dict( + name=dict(required=True), + release_name=dict(), + description=dict(), + os_family=dict(choices=OS_LIST, flat_name='family', aliases=['family']), + major=dict(), + minor=dict(), + architectures=dict(type='entity_list'), + media=dict(type='entity_list', flat_name='medium_ids', resource_type='media'), + ptables=dict(type='entity_list'), + provisioning_templates=dict(type='entity_list'), + password_hash=dict(choices=['MD5', 'SHA256', 'SHA512', 'Base64', 'Base64-Windows'], no_log=False), + ), + argument_spec=dict( + state=dict(default='present', choices=['present', 'present_with_defaults', 'absent']), + updated_name=dict(), + ), + required_if=[ + ['state', 'present', ['name', 'major', 'os_family']], + ['state', 'present_with_defaults', ['name', 'major', 'os_family']], + ], + required_one_of=[ + ['description', 'name'], + ['description', 'major'], + ], + ) + + module_params = module.foreman_params + + with module.api_connection(): + + # Try to find the Operating System to work on + # name is however not unique, but description is, as well as "<name> <major>[.<minor>]" + entity = None + # If we have a description, search for it + if 'description' in module_params and module_params['description'] != '': + search_string = 'description="{0}"'.format(module_params['description']) + entity = module.find_resource('operatingsystems', search_string, failsafe=True) + # If we did not yet find a unique OS, search by name & version + # In case of state == absent, those information might be missing, we assume that we did not find an operatingsytem to delete then + if entity is None and 'name' in module_params and 'major' in module_params: + search_string = ','.join('{0}="{1}"'.format(key, module_params[key]) for key in ('name', 'major', 'minor') if key in module_params) + entity = module.find_resource('operatingsystems', search_string, failsafe=True) + + if not entity and (module.state == 'present' or module.state == 'present_with_defaults'): + # we actually attempt to create a new one... + for param_name in ['major', 'os_family', 'password_hash']: + if param_name not in module_params.keys(): + module.fail_json(msg='{0} is a required parameter to create a new operating system.'.format(param_name)) + + module.set_entity('entity', entity) + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/organization.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/organization.py new file mode 100644 index 00000000..44f9dda6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/organization.py @@ -0,0 +1,98 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2016, Eric D Helms <ericdhelms@gmail.com> +# (c) 2017, Matthias M Dellweg <dellweg@atix.de> (ATIX AG) +# +# 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: organization +version_added: 1.0.0 +short_description: Manage Organizations +description: + - Manage Organizations +author: + - "Eric D Helms (@ehelms)" + - "Matthias M Dellweg (@mdellweg) ATIX AG" +options: + name: + description: + - Name of the Organization + required: true + type: str + description: + description: + - Description of the Organization + required: false + type: str + label: + description: + - Label of the Organization + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.nested_parameters +''' + +EXAMPLES = ''' +- name: "Create CI Organization" + theforeman.foreman.organization: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "My Cool New Organization" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + organizations: + description: List of organizations. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule, NestedParametersMixin + + +class ForemanOrganizationModule(NestedParametersMixin, ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanOrganizationModule( + foreman_spec=dict( + name=dict(required=True), + description=dict(), + label=dict(), + ), + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/os_default_template.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/os_default_template.py new file mode 100644 index 00000000..f35fba61 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/os_default_template.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017 Matthias M Dellweg (ATIX AG) +# +# 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: os_default_template +version_added: 1.0.0 +short_description: Manage Default Template Associations To Operating Systems +description: + - Manage OSDefaultTemplate Entities +author: + - "Matthias M Dellweg (@mdellweg) ATIX AG" +options: + operatingsystem: + required: true + template_kind: + description: + - name of the template kind + required: true + type: str + choices: + - Bootdisk + - cloud-init + - finish + - iPXE + - job_template + - kexec + - POAP + - provision + - ptable + - PXEGrub + - PXEGrub2 + - PXELinux + - registration + - script + - user_data + - ZTP + provisioning_template: + description: + - name of provisioning template + required: false + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults + - theforeman.foreman.foreman.operatingsystem +''' + +EXAMPLES = ''' +- name: "Create an Association" + theforeman.foreman.os_default_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + operatingsystem: "CoolOS" + template_kind: "finish" + provisioning_template: "CoolOS finish" + state: present + +- name: "Delete an Association" + theforeman.foreman.os_default_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + operatingsystem: "CoolOS" + template_kind: "finish" + state: absent +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + os_default_templates: + description: List of operatingsystem default templates. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule, TEMPLATE_KIND_LIST + + +class ForemanOsDefaultTemplateModule(ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanOsDefaultTemplateModule( + argument_spec=dict( + state=dict(default='present', choices=['present', 'present_with_defaults', 'absent']), + ), + foreman_spec=dict( + operatingsystem=dict(required=True, type='entity'), + template_kind=dict(required=True, choices=TEMPLATE_KIND_LIST, type='entity'), + provisioning_template=dict(type='entity', thin=False), + ), + required_if=( + ['state', 'present', ['provisioning_template']], + ['state', 'present_with_defaults', ['provisioning_template']], + ), + entity_opts={'scope': ['operatingsystem']}, + ) + + if 'provisioning_template' in module.foreman_params and module.desired_absent: + module.fail_json(msg='Provisioning template must not be specified for deletion.') + + with module.api_connection(): + template_kind_id = module.lookup_entity('template_kind')['id'] + if not module.desired_absent: + if module.lookup_entity('provisioning_template')['template_kind_id'] != template_kind_id: + module.fail_json(msg='Provisioning template kind mismatching.') + + scope = module.scope_for('operatingsystem') + # Default templates do not support a scoped search + # see: https://projects.theforeman.org/issues/27722 + entities = module.list_resource('os_default_templates', params=scope) + entity = next((item for item in entities if item['template_kind_id'] == template_kind_id), None) + module.set_entity('entity', entity) + + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/partition_table.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/partition_table.py new file mode 100644 index 00000000..1ddccea7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/partition_table.py @@ -0,0 +1,296 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017 Matthias Dellweg & Bernhard Hopfenmüller (ATIX AG) +# +# 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: partition_table +version_added: 1.0.0 +short_description: Manage Partition Table Templates +description: + - Manage Partition Table Templates +author: + - "Bernhard Hopfenmueller (@Fobhep) ATIX AG" + - "Matthias Dellweg (@mdellweg) ATIX AG" +options: + file_name: + description: + - The path of a template file, that shall be imported. + - Either this or I(layout) is required as a source for the Partition Template "content". + required: false + type: path + layout: + description: + - The content of the Partitioning Table Template + - Either this or I(file_name) is required as a source for the Partition Template "content". + required: false + type: str + locked: + description: + - Determines whether the template shall be locked + required: false + type: bool + name: + description: + - The name of the Partition Table. + - If omited, will be determined from the C(name) header of the template or the filename (in that order). + - The special value "*" can be used to perform bulk actions (modify, delete) on all existing Partition Tables. + required: false + type: str + updated_name: + description: New name of the template. When this parameter is set, the module will not be idempotent. + required: false + type: str + os_family: + description: + - The OS family the template shall be assigned with. +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults + - theforeman.foreman.foreman.taxonomy + - theforeman.foreman.foreman.os_family +''' + +EXAMPLES = ''' + +# Keep in mind, that in this case, the inline parameters will be overwritten +- name: "Create a Partition Table inline" + theforeman.foreman.partition_table: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: A New Partition Template + state: present + layout: | + <%# + name: A Partition Template + %> + zerombr + clearpart --all --initlabel + autopart + locations: + - Gallifrey + organizations: + - TARDIS INC + +- name: "Create a Partition Template from a file" + theforeman.foreman.partition_table: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + file_name: timeywimey_template.erb + state: present + locations: + - Gallifrey + organizations: + - TARDIS INC + +- name: "Delete a Partition Template" + theforeman.foreman.partition_table: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: timeywimey + layout: | + <%# + dummy: + %> + state: absent + +- name: "Create a Partition Template from a file and modify with parameter(s)" + theforeman.foreman.partition_table: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + file_name: timeywimey_template.erb + name: Wibbly Wobbly Template + state: present + locations: + - Gallifrey + organizations: + - TARDIS INC + +# Providing a name in this case wouldn't be very sensible. +# Alternatively make use of with_filetree to parse recursively with filter. +- name: "Parsing a directory of partition templates" + theforeman.foreman.partition_table: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + file_name: "{{ item }}" + state: present + locations: + - SKARO + organizations: + - DALEK INC + with_fileglob: + - "./arsenal_templates/*.erb" + +# If the templates are stored locally and the ansible module is executed on a remote host +- name: Ensure latest version of all Ptable Community Templates + theforeman.foreman.partition_table: + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + layout: '{{ lookup("file", item.src) }}' + with_filetree: '/path/to/partition/tables' + when: item.state == 'file' + + +# with name set to "*" bulk actions can be performed +- name: "Delete *ALL* partition tables" + theforeman.foreman.partition_table: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "*" + state: absent + +- name: "Assign all partition tables to the same organization(s)" + theforeman.foreman.partition_table: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "*" + state: present + organizations: + - DALEK INC + - sky.net + - Doc Brown's garage + +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + ptables: + description: List of partition tables. + type: list + elements: dict +''' + + +import os + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ( + ForemanTaxonomicEntityAnsibleModule, + parse_template, + parse_template_from_file, + OS_LIST, +) + + +class ForemanPtableModule(ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanPtableModule( + argument_spec=dict( + file_name=dict(type='path'), + state=dict(default='present', choices=['absent', 'present_with_defaults', 'present']), + updated_name=dict(), + ), + foreman_spec=dict( + layout=dict(), + locked=dict(type='bool'), + name=dict(), + os_family=dict(choices=OS_LIST), + ), + mutually_exclusive=[ + ['file_name', 'layout'], + ], + required_one_of=[ + ['name', 'file_name', 'layout'], + ], + ) + + # We do not want a layout text for bulk operations + if module.foreman_params.get('name') == '*': + if module.foreman_params.get('file_name') or module.foreman_params.get('layout') or module.foreman_params.get('updated_name'): + module.fail_json( + msg="Neither file_name nor layout nor updated_name allowed if 'name: *'!") + + entity = None + file_name = module.foreman_params.pop('file_name', None) + + if file_name or 'layout' in module.foreman_params: + if file_name: + parsed_dict = parse_template_from_file(file_name, module) + else: + parsed_dict = parse_template(module.foreman_params['layout'], module) + parsed_dict['layout'] = parsed_dict.pop('template') + if 'oses' in parsed_dict: + parsed_dict['os_family'] = parsed_dict.pop('oses') + # sanitize name from template data + # The following condition can actually be hit, when someone is trying to import a + # template with the name set to '*'. + # Besides not being sensible, this would go horribly wrong in this module. + if parsed_dict.get('name') == '*': + module.fail_json(msg="Cannot use '*' as a partition table name!") + # module params are priorized + parsed_dict.update(module.foreman_params) + module.foreman_params = parsed_dict + + # make sure, we have a name + if 'name' not in module.foreman_params: + if file_name: + module.foreman_params['name'] = os.path.splitext( + os.path.basename(file_name))[0] + else: + module.fail_json( + msg='No name specified and no filename to infer it.') + + affects_multiple = module.foreman_params['name'] == '*' + # sanitize user input, filter unuseful configuration combinations with 'name: *' + if affects_multiple: + if module.state == 'present_with_defaults': + module.fail_json(msg="'state: present_with_defaults' and 'name: *' cannot be used together") + if module.desired_absent: + further_params = set(module.foreman_params.keys()) - {'name', 'entity'} + if further_params: + module.fail_json(msg='When deleting all partition tables, there is no need to specify further parameters: %s ' % further_params) + + with module.api_connection(): + if affects_multiple: + module.set_entity('entity', None) # prevent lookup + entities = module.list_resource('ptables') + if not entities: + # Nothing to do; shortcut to exit + module.exit_json() + if not module.desired_absent: # not 'thin' + entities = [module.show_resource('ptables', entity['id']) for entity in entities] + module.auto_lookup_entities() + module.foreman_params.pop('name') + for entity in entities: + module.ensure_entity('ptables', module.foreman_params, entity) + else: + # The name could have been determined to late, so copy it again + module.foreman_params['entity'] = module.foreman_params['name'] + + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/product.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/product.py new file mode 100644 index 00000000..61710116 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/product.py @@ -0,0 +1,144 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2016, Eric D Helms <ericdhelms@gmail.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: product +version_added: 1.0.0 +short_description: Manage Products +description: + - Create and manage products +author: + - "Eric D Helms (@ehelms)" + - "Matthias Dellweg (@mdellweg) ATIX AG" +options: + name: + description: + - Name of the product + required: true + type: str + label: + description: + - Label to show the user + required: false + type: str + gpg_key: + description: + - Content GPG key name attached to this product + required: false + type: str + ssl_ca_cert: + description: + - Content SSL CA certificate name attached to this product + required: false + type: str + ssl_client_cert: + description: + - Content SSL client certificate name attached to this product + required: false + type: str + ssl_client_key: + description: + - Content SSL client private key name attached to this product + required: false + type: str + sync_plan: + description: + - Sync plan name attached to this product + required: false + type: str + description: + description: + - Possibly long description to show the user in detail view + required: false + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Create Fedora product with a sync plan" + theforeman.foreman.product: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "Fedora" + organization: "My Cool new Organization" + sync_plan: "Fedora repos sync" + state: present + +- name: "Create CentOS 7 product with content credentials" + theforeman.foreman.product: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "CentOS 7" + gpg_key: "RPM-GPG-KEY-CentOS7" + organization: "My Cool new Organization" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + products: + description: List of products. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule + + +class KatelloProductModule(KatelloEntityAnsibleModule): + pass + + +def main(): + module = KatelloProductModule( + entity_name='product', + foreman_spec=dict( + name=dict(required=True), + label=dict(), + gpg_key=dict(type='entity', resource_type='content_credentials', scope=['organization']), + ssl_ca_cert=dict(type='entity', resource_type='content_credentials', scope=['organization']), + ssl_client_cert=dict(type='entity', resource_type='content_credentials', scope=['organization']), + ssl_client_key=dict(type='entity', resource_type='content_credentials', scope=['organization']), + sync_plan=dict(type='entity', scope=['organization']), + description=dict(), + ), + argument_spec=dict( + state=dict(default='present', choices=['present_with_defaults', 'present', 'absent']), + ), + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/provisioning_template.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/provisioning_template.py new file mode 100644 index 00000000..db8cc4c1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/provisioning_template.py @@ -0,0 +1,344 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017 Matthias Dellweg & Bernhard Hopfenmüller (ATIX AG) +# +# 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: provisioning_template +version_added: 1.0.0 +short_description: Manage Provisioning Templates +description: + - Manage Provisioning Templates +author: + - "Bernhard Hopfenmueller (@Fobhep) ATIX AG" + - "Matthias Dellweg (@mdellweg) ATIX AG" +options: + audit_comment: + description: + - Content of the audit comment field + required: false + type: str + kind: + description: + - The provisioning template kind + required: false + choices: + - Bootdisk + - cloud-init + - finish + - iPXE + - job_template + - kexec + - POAP + - provision + - ptable + - PXEGrub + - PXEGrub2 + - PXELinux + - registration + - script + - snippet + - user_data + - ZTP + type: str + template: + description: + - The content of the provisioning template. + - Either this or I(file_name) is required as a source for the Provisioning Template "content". + required: false + type: str + file_name: + description: + - The path of a template file, that shall be imported. + - Either this or I(template) is required as a source for the Provisioning Template "content". + required: false + type: path + locked: + description: + - Determines whether the template shall be locked + required: false + type: bool + name: + description: + - The name of the Provisioning Template. + - If omited, will be determined from the C(name) header of the template or the filename (in that order). + - The special value "*" can be used to perform bulk actions (modify, delete) on all existing templates. + required: false + type: str + updated_name: + description: New provisioning template name. When this parameter is set, the module will not be idempotent. + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults + - theforeman.foreman.foreman.taxonomy + - theforeman.foreman.foreman.operatingsystems +''' + +EXAMPLES = ''' + +# Keep in mind, that in this case, the inline parameters will be overwritten +- name: "Create a Provisioning Template inline" + theforeman.foreman.provisioning_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: A New Finish Template + kind: finish + state: present + template: | + <%# + name: Finish timetravel + kind: finish + %> + cd / + rm -rf * + locations: + - Gallifrey + organizations: + - TARDIS INC + +- name: "Create a Provisioning Template from a file" + theforeman.foreman.provisioning_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + file_name: timeywimey_template.erb + state: present + locations: + - Gallifrey + organizations: + - TARDIS INC + +# Due to the module logic, deleting requires a template dummy, +# either inline or from a file. +- name: "Delete a Provisioning Template" + theforeman.foreman.provisioning_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: timeywimey_template + template: | + <%# + dummy: + %> + state: absent + +- name: "Create a Provisioning Template from a file and modify with parameter" + theforeman.foreman.provisioning_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + file_name: timeywimey_template.erb + name: Wibbly Wobbly Template + state: present + locations: + - Gallifrey + organizations: + - TARDIS INC + +# Providing a name in this case wouldn't be very sensible. +# Alternatively make use of with_filetree to parse recursively with filter. +- name: "Parsing a directory of provisioning templates" + theforeman.foreman.provisioning_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + file_name: "{{ item }}" + state: present + locations: + - SKARO + organizations: + - DALEK INC + with_fileglob: + - "./arsenal_templates/*.erb" + +# If the templates are stored locally and the ansible module is executed on a remote host +- name: Ensure latest version of all Provisioning Community Templates + theforeman.foreman.provisioning_template: + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + template: '{{ lookup("file", item.src) }}' + with_filetree: '/path/to/provisioning/templates' + when: item.state == 'file' + + +# with name set to "*" bulk actions can be performed +- name: "Delete *ALL* provisioning templates" + theforeman.foreman.provisioning_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "*" + state: absent + +- name: "Assign all provisioning templates to the same organization(s)" + theforeman.foreman.provisioning_template: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "*" + state: present + organizations: + - DALEK INC + - sky.net + - Doc Brown's garage + +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + provisioning_templates: + description: List of provisioning templates. + type: list + elements: dict +''' + + +import os + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ( + ForemanTaxonomicEntityAnsibleModule, + parse_template, + parse_template_from_file, + TEMPLATE_KIND_LIST, +) + + +def find_template_kind(module, module_params): + if 'kind' not in module_params: + return module_params + + module_params['snippet'] = (module_params['kind'] == 'snippet') + if module_params['snippet']: + module_params.pop('kind') + else: + module_params['kind'] = module.find_resource_by_name('template_kinds', module_params['kind'], thin=True) + return module_params + + +class ForemanProvisioningTemplateModule(ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanProvisioningTemplateModule( + argument_spec=dict( + audit_comment=dict(), + file_name=dict(type='path'), + state=dict(default='present', choices=['absent', 'present_with_defaults', 'present']), + updated_name=dict(), + ), + foreman_spec=dict( + kind=dict(choices=TEMPLATE_KIND_LIST + ['snippet'], type='entity', flat_name='template_kind_id', resolve=False), + template=dict(), + locked=dict(type='bool'), + name=dict(), + operatingsystems=dict(type='entity_list'), + snippet=dict(invisible=True), + ), + mutually_exclusive=[ + ['file_name', 'template'], + ], + required_one_of=[ + ['name', 'file_name', 'template'], + ], + ) + + # We do not want a template text for bulk operations + if module.foreman_params.get('name') == '*': + if module.foreman_params.get('file_name') or module.foreman_params.get('template') or module.foreman_params.get('updated_name'): + module.fail_json( + msg="Neither file_name nor template nor updated_name allowed if 'name: *'!") + + entity = None + file_name = module.foreman_params.pop('file_name', None) + + if file_name or 'template' in module.foreman_params: + if file_name: + parsed_dict = parse_template_from_file(file_name, module) + else: + parsed_dict = parse_template(module.foreman_params['template'], module) + # sanitize name from template data + # The following condition can actually be hit, when someone is trying to import a + # template with the name set to '*'. + # Besides not being sensible, this would go horribly wrong in this module. + if parsed_dict.get('name') == '*': + module.fail_json(msg="Cannot use '*' as a template name!") + # module params are priorized + parsed_dict.update(module.foreman_params) + module.foreman_params = parsed_dict + + # make sure, we have a name + if 'name' not in module.foreman_params: + if file_name: + module.foreman_params['name'] = os.path.splitext( + os.path.basename(file_name))[0] + else: + module.fail_json( + msg='No name specified and no filename to infer it.') + + affects_multiple = module.foreman_params['name'] == '*' + # sanitize user input, filter unuseful configuration combinations with 'name: *' + if affects_multiple: + if module.foreman_params.get('updated_name'): + module.fail_json(msg="updated_name not allowed if 'name: *'!") + if module.state == 'present_with_defaults': + module.fail_json(msg="'state: present_with_defaults' and 'name: *' cannot be used together") + if module.desired_absent: + further_params = set(module.foreman_params.keys()) - {'name', 'entity'} + if further_params: + module.fail_json(msg='When deleting all templates, there is no need to specify further parameters: %s ' % further_params) + + with module.api_connection(): + if 'audit_comment' in module.foreman_params: + extra_params = {'audit_comment': module.foreman_params['audit_comment']} + else: + extra_params = {} + + if affects_multiple: + module.set_entity('entity', None) # prevent lookup + entities = module.list_resource('provisioning_templates') + if not entities: + # Nothing to do; shortcut to exit + module.exit_json() + if not module.desired_absent: # not 'thin' + entities = [module.show_resource('provisioning_templates', entity['id']) for entity in entities] + module.auto_lookup_entities() + module.foreman_params.pop('name') + for entity in entities: + module.ensure_entity('provisioning_templates', module.foreman_params, entity, params=extra_params) + else: + # The name could have been determined to late, so copy it again + module.foreman_params['entity'] = module.foreman_params['name'] + + module.foreman_params = find_template_kind(module, module.foreman_params) + + module.run(params=extra_params) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/puppet_environment.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/puppet_environment.py new file mode 100644 index 00000000..40db9243 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/puppet_environment.py @@ -0,0 +1,91 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2018 Bernhard Suttner (ATIX AG) +# (c) 2019 Christoffer Reijer (Basalt AB) +# +# 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: puppet_environment +version_added: 1.0.0 +short_description: Manage Puppet Environments +description: + - Create, update, and delete Puppet Environments +author: + - "Bernhard Suttner (@_sbernhard) ATIX AG" + - "Christoffer Reijer (@ephracis) Basalt AB" +options: + name: + description: The full environment name + required: true + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.taxonomy +''' + +EXAMPLES = ''' +- name: create new environment + theforeman.foreman.puppet_environment: + name: "testing" + locations: + - "Munich" + organizations: + - "ACME" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + puppet_environments: + description: List of puppet environments. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ( + ForemanTaxonomicEntityAnsibleModule, +) + + +class ForemanEnvironmentModule(ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanEnvironmentModule( + foreman_spec=dict( + name=dict(required=True), + ), + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/realm.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/realm.py new file mode 100644 index 00000000..3382cf4c --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/realm.py @@ -0,0 +1,102 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017, Lester R Claudio <claudiol@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: realm +version_added: 1.0.0 +short_description: Manage Realms +description: + - Manage Realms +author: + - "Lester R Claudio (@claudiol1)" +options: + name: + description: + - Name of the realm + required: true + type: str + realm_proxy: + description: + - Proxy to use for this realm + required: true + type: str + realm_type: + description: + - Realm type + choices: + - Red Hat Identity Management + - FreeIPA + - Active Directory + required: true + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.taxonomy +''' + +EXAMPLES = ''' +- name: "Create EXAMPLE.LOCAL Realm" + theforeman.foreman.realm: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "EXAMPLE.COM" + realm_proxy: "foreman.example.com" + realm_type: "Red Hat Identity Management" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + realms: + description: List of realms. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanTaxonomicEntityAnsibleModule + + +class ForemanRealmModule(ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanRealmModule( + foreman_spec=dict( + name=dict(required=True), + realm_proxy=dict(type='entity', required=True, resource_type='smart_proxies'), + realm_type=dict(required=True, choices=['Red Hat Identity Management', 'FreeIPA', 'Active Directory']), + ), + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/redhat_manifest.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/redhat_manifest.py new file mode 100644 index 00000000..ed628f57 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/redhat_manifest.py @@ -0,0 +1,329 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017, Sean O'Keeffe <seanokeeffe797@gmail.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: redhat_manifest +version_added: 1.0.0 +short_description: Interact with a Red Hat Satellite Subscription Manifest +description: + - Download and modify a Red Hat Satellite Subscription Manifest +author: + - "Sean O'Keeffe (@sean797)" +options: + name: + description: + - Manifest Name + type: str + uuid: + description: + - Manifest uuid + type: str + username: + description: + - Red Hat Portal username + required: true + type: str + password: + description: + - Red Hat Portal password + required: true + type: str + pool_id: + description: + - Subscription pool_id + type: str + quantity: + description: + - quantity of pool_id Subscriptions + type: int + pool_state: + description: + - Subscription state + default: present + choices: + - present + - absent + type: str + state: + description: + - Manifest state + default: present + choices: + - present + - absent + type: str + path: + description: + - path to export the manifest + type: path + validate_certs: + description: + - Validate Portal SSL + default: True + type: bool + portal: + description: + - Red Hat Portal subscription access address + default: https://subscription.rhsm.redhat.com + type: str + content_access_mode: + description: + - Content Access Mode of the Subscription Manifest. + - Setting I(content_access_mode=org_enviroment) enables Simple Content Access. + type: str + choices: + - org_environment + - entitlement + default: entitlement +''' + +EXAMPLES = ''' +- name: Create foreman.example.com Manifest and add 7 sub + theforeman.foreman.redhat_manifest: + name: "foreman.example.com" + username: "john-smith" + password: "changeme" + pool_id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + quantity: 7 + +- name: Ensure my manifest has 10 of one subs in it and export + theforeman.foreman.redhat_manifest: + uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + username: john-smith + password: changeme + pool_id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + quantity: 10 + path: /root/manifest.zip + +- name: Remove all of one subs from foreman.example.com + theforeman.foreman.redhat_manifest: + name: foreman.example.com + username: john-smith + password: changeme + pool_id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + pool_state: absent +''' + +RETURN = '''# ''' + +import json +import os + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import fetch_url +from ansible.module_utils._text import to_text, to_native + + +REDHAT_UEP = '/etc/rhsm/ca/redhat-uep.pem' + + +def fetch_portal(module, path, method, data=None, accept_header='application/json'): + if data is None: + data = {} + url = module.params['portal'] + path + headers = {'accept': accept_header, + 'content-type': 'application/json'} + fetch_kwargs = {'timeout': 30} + if os.path.exists(REDHAT_UEP): + fetch_kwargs['ca_path'] = REDHAT_UEP + try: + resp, info = fetch_url(module, url, json.dumps(data), headers, method, **fetch_kwargs) + except TypeError: + # ca_path was added in Ansible 2.9 and backported to 2.8 in 2.8.6 + # older Ansible releases don't support that and we have to omit the CA cert here + if module.params['validate_certs']: + module.warn("Your Ansible version does not support providing custom CA certificates for HTTP requests. " + "Talking to the Red Hat portal might fail without validate_certs=False. Please update.") + del fetch_kwargs['ca_path'] + resp, info = fetch_url(module, url, json.dumps(data), headers, method, **fetch_kwargs) + if resp is None: + try: + error = json.loads(info['body'])['displayMessage'] + except Exception: + error = info['msg'] + module.fail_json(msg="%s to %s failed, got %s" % (method, url, error)) + return resp, info + + +def create_manifest(module): + path = "/subscription/consumers" + data = {'name': module.params['name'], + 'type': "satellite", + 'contentAccessMode': module.params['content_access_mode'], + # TODO: Make these 2 configurable, we need to work out which horribly + # undocumented API to use. + 'facts': {'distributor_version': 'sat-6.3', + 'system.certificate_version': '3.2'}} + resp, info = fetch_portal(module, path, 'POST', data) + return json.loads(to_text(resp.read())) + + +def delete_manifest(module, uuid): + path = "/subscription/consumers/%s" % uuid + resp, info = fetch_portal(module, path, 'DELETE') + if info['status'] != 204: + module.fail_json(msg="Got status %s attempting to delete manifest, expected 204" % (info['status'])) + + +def get_manifest(module): + path = "/subscription/owners/%s/consumers?type=satellite" % (module.params['rhsm_owner']) + resp, info = fetch_portal(module, path, 'GET') + manifests = json.loads(to_text(resp.read())) + if module.params['name']: + attr = 'name' + if module.params['uuid']: + attr = 'uuid' + manifest = [m for m in manifests if m[attr] == module.params[attr]] + if manifest: + if module.params['state'] == 'present': + return manifest[0], False + if module.params['state'] == 'absent': + if not module.check_mode: + return delete_manifest(module, manifest[0]['uuid']), True + return None, True + elif module.params['state'] == 'present': + if not module.check_mode: + return create_manifest(module), True + return None, True + return None, False + + +def get_owner(module): + path = "/subscription/users/%s/owners" % (module.params['username']) + resp, info = fetch_portal(module, path, 'GET') + return json.loads(to_text(resp.read()))[0]['key'] + + +def get_subs(module, manifest): + path = "/subscription/consumers/%s/entitlements" % (manifest['uuid']) + resp, info = fetch_portal(module, path, 'GET') + all_subs = json.loads(to_text(resp.read())) + subs = [s for s in all_subs if s['pool']['id'] == module.params['pool_id']] + return subs + + +def get_remove_or_attach_sub(module, manifest): + changed = False + subs = get_subs(module, manifest) + if subs: + if module.params['pool_state'] == 'present': + sub_quantity = sum(s['quantity'] for s in subs) + while sub_quantity > module.params['quantity']: + if not module.check_mode: + remove_sub(module, manifest, subs[0]) + else: + changed = True + break + changed = True + subs = get_subs(module, manifest) + sub_quantity = sum(s['quantity'] for s in subs) + if sub_quantity < module.params['quantity']: + difference = module.params['quantity'] - sub_quantity + if not module.check_mode: + attach_sub(module, manifest, difference) + changed = True + elif module.params['pool_state'] == 'absent': + if not module.check_mode: + for sub in subs: + remove_sub(module, manifest, sub) + changed = True + elif module.params['pool_state'] == 'present': + if not module.check_mode: + attach_sub(module, manifest, module.params['quantity']) + changed = True + return changed + + +def remove_sub(module, manifest, sub): + path = "/subscription/consumers/%s/entitlements/%s" % (manifest['uuid'], sub['id']) + fetch_portal(module, path, 'DELETE') + + +def attach_sub(module, manifest, quantity): + path = "/subscription/consumers/%s/entitlements?pool=%s&quantity=%s" % (manifest['uuid'], module.params['pool_id'], quantity) + fetch_portal(module, path, 'POST') + + +def export_manifest(module, manifest): + path = "/subscription/consumers/%s/export" % (manifest['uuid']) + try: + resp, info = fetch_portal(module, path, 'GET', accept_header='application/zip') + if not module.check_mode: + with open(module.params['path'], 'wb') as f: + while True: + data = resp.read(65536) # 64K + if not data: + break + f.write(data) + except Exception as e: + module.fail_json(msg="Failure downloading manifest, {0}".format(to_native(e))) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str'), + uuid=dict(type='str'), + username=dict(required=True, no_log=True), + password=dict(required=True, no_log=True), + content_access_mode=dict(choices=['org_environment', 'entitlement'], default='entitlement'), + pool_id=dict(type='str'), + quantity=dict(type='int'), + pool_state=dict(choices=['present', 'absent'], default='present'), + state=dict(choices=['present', 'absent'], default='present'), + path=dict(type='path'), + validate_certs=dict(default=True, type='bool'), + portal=dict(default='https://subscription.rhsm.redhat.com'), + ), + required_one_of=[['name', 'uuid']], + supports_check_mode=True, + ) + + if module.params['validate_certs'] and not os.path.exists(REDHAT_UEP): + module.warn("Couldn't find the Red Hat Entitlement Platform CA certificate ({0}) on your system. " + "It's required to validate the certificate of {1}.".format(REDHAT_UEP, module.params['portal'])) + + username = module.params['username'] + password = module.params['password'] + + # Hack to add options the way fetch_url expects + module.params['url_username'] = username + module.params['url_password'] = password + module.params['force_basic_auth'] = True + + module.params['rhsm_owner'] = get_owner(module) + + manifest, man_changed = get_manifest(module) + if module.params['pool_id'] and manifest: + sub_changed = get_remove_or_attach_sub(module, manifest) + else: + sub_changed = False + + if module.params['path'] and manifest: + export_manifest(module, manifest) + + changed = man_changed or sub_changed + module.exit_json(changed=changed) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/repository.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/repository.py new file mode 100644 index 00000000..fd9f7ab5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/repository.py @@ -0,0 +1,337 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2016, Eric D Helms <ericdhelms@gmail.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: repository +version_added: 1.0.0 +short_description: Manage Repositories +description: + - Crate and manage repositories +author: "Eric D Helms (@ehelms)" +notes: + - You can configure certain aspects of existing Red Hat Repositories (like I(download_policy)) using this module, + but you can't create (enable) or delete (disable) them. + - If you want to enable or disable Red Hat Repositories available through your subscription, + please use the M(theforeman.foreman.repository_set) module instead. +options: + name: + description: + - Name of the repository + required: true + type: str + description: + description: + - Description of the repository + required: false + type: str + product: + description: + - Product to which the repository lives in + required: true + type: str + label: + description: + - label of the repository + type: str + content_type: + description: + - The content type of the repository + required: true + choices: + - deb + - docker + - file + - ostree + - puppet + - yum + - ansible_collection + type: str + url: + description: + - Repository URL to sync from + required: false + type: str + ignore_global_proxy: + description: + - Whether content sync should use or ignore the global http proxy setting + - This is deprecated with Katello 3.13 + - It has been superseeded by I(http_proxy_policy) + required: false + type: bool + http_proxy_policy: + description: + - Which proxy to use for content synching + choices: + - global_default_http_proxy + - none + - use_selected_http_proxy + required: false + type: str + http_proxy: + description: + - Name of the http proxy to use for content synching + - Should be combined with I(http_proxy_policy='use_selected_http_proxy') + required: false + type: str + gpg_key: + description: + - Repository GPG key + required: false + type: str + ssl_ca_cert: + description: + - Repository SSL CA certificate + required: false + type: str + ssl_client_cert: + description: + - Repository SSL client certificate + required: false + type: str + ssl_client_key: + description: + - Repository SSL client private key + required: false + type: str + download_policy: + description: + - download policy for sync from upstream + choices: + - background + - immediate + - on_demand + required: false + type: str + mirror_on_sync: + description: + - toggle "mirror on sync" where the state of the repository mirrors that of the upstream repository at sync time + default: true + type: bool + required: false + verify_ssl_on_sync: + description: + - verify the upstream certifcates are signed by a trusted CA + type: bool + required: false + upstream_username: + description: + - username to access upstream repository + type: str + upstream_password: + description: + - password to access upstream repository + type: str + docker_upstream_name: + description: + - name of the upstream docker repository + - only available for I(content_type=docker) + type: str + docker_tags_whitelist: + description: + - list of tags to sync for Container Image repository + - only available for I(content_type=docker) + type: list + elements: str + deb_releases: + description: + - comma separated list of releases to be synced from deb-archive + - only available for I(content_type=deb) + type: str + deb_components: + description: + - comma separated list of repo components to be synced from deb-archive + - only available for I(content_type=deb) + type: str + deb_architectures: + description: + - comma separated list of architectures to be synced from deb-archive + - only available for I(content_type=deb) + type: str + deb_errata_url: + description: + - URL to sync Debian or Ubuntu errata information from + - only available on Orcharhino + - only available for I(content_type=deb) + type: str + required: false + unprotected: + description: + - publish the repository via HTTP + type: bool + required: false + checksum_type: + description: + - Checksum of the repository + type: str + required: false + choices: + - sha1 + - sha256 + ignorable_content: + description: + - List of content units to ignore while syncing a yum repository. + - Must be subset of rpm,drpm,srpm,distribution,erratum. + type: list + elements: str + required: false + ansible_collection_requirements: + description: + - Contents of requirement yaml file to sync from URL + type: str + required: false + auto_enabled: + description: + - repositories will be automatically enabled on a registered host subscribed to this product + type: bool + required: false +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Create repository" + theforeman.foreman.repository: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "My repository" + state: present + content_type: "yum" + product: "My Product" + organization: "Default Organization" + url: "http://yum.theforeman.org/plugins/latest/el7/x86_64/" + mirror_on_sync: true + download_policy: background + +- name: "Create repository with content credentials" + theforeman.foreman.repository: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "My repository 2" + state: present + content_type: "yum" + product: "My Product" + organization: "Default Organization" + url: "http://yum.theforeman.org/releases/latest/el7/x86_64/" + download_policy: background + mirror_on_sync: true + gpg_key: RPM-GPG-KEY-my-product2 +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + repositories: + description: List of repositories. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule + + +class KatelloRepositoryModule(KatelloEntityAnsibleModule): + pass + + +def main(): + module = KatelloRepositoryModule( + foreman_spec=dict( + product=dict(type='entity', scope=['organization'], required=True), + label=dict(), + name=dict(required=True), + content_type=dict(required=True, choices=['docker', 'ostree', 'yum', 'puppet', 'file', 'deb', 'ansible_collection']), + url=dict(), + ignore_global_proxy=dict(type='bool'), + http_proxy_policy=dict(choices=['global_default_http_proxy', 'none', 'use_selected_http_proxy']), + http_proxy=dict(type='entity'), + gpg_key=dict(type='entity', resource_type='content_credentials', scope=['organization']), + ssl_ca_cert=dict(type='entity', resource_type='content_credentials', scope=['organization']), + ssl_client_cert=dict(type='entity', resource_type='content_credentials', scope=['organization']), + ssl_client_key=dict(type='entity', resource_type='content_credentials', scope=['organization']), + download_policy=dict(choices=['background', 'immediate', 'on_demand']), + mirror_on_sync=dict(type='bool', default=True), + verify_ssl_on_sync=dict(type='bool'), + upstream_username=dict(), + upstream_password=dict(no_log=True), + docker_upstream_name=dict(), + docker_tags_whitelist=dict(type='list', elements='str'), + deb_errata_url=dict(), + deb_releases=dict(), + deb_components=dict(), + deb_architectures=dict(), + description=dict(), + unprotected=dict(type='bool'), + checksum_type=dict(choices=['sha1', 'sha256']), + ignorable_content=dict(type='list', elements='str'), + ansible_collection_requirements=dict(), + auto_enabled=dict(type='bool'), + ), + argument_spec=dict( + state=dict(default='present', choices=['present_with_defaults', 'present', 'absent']), + ), + entity_opts={'scope': ['product']}, + ) + + # KatelloEntityAnsibleModule automatically adds organization to the entity scope + # but repositories are scoped by product (and these are org scoped) + module.foreman_spec['entity']['scope'].remove('organization') + + if module.foreman_params['content_type'] != 'docker': + invalid_list = [key for key in ['docker_upstream_name', 'docker_tags_whitelist'] if key in module.foreman_params] + if invalid_list: + module.fail_json(msg="({0}) can only be used with content_type 'docker'".format(",".join(invalid_list))) + + if module.foreman_params['content_type'] != 'deb': + invalid_list = [key for key in ['deb_errata_url', 'deb_releases', 'deb_components', 'deb_architectures'] if key in module.foreman_params] + if invalid_list: + module.fail_json(msg="({0}) can only be used with content_type 'deb'".format(",".join(invalid_list))) + + if module.foreman_params['content_type'] != 'ansible_collection': + invalid_list = [key for key in ['ansible_collection_requirements'] if key in module.foreman_params] + if invalid_list: + module.fail_json(msg="({0}) can only be used with content_type 'ansible_collection'".format(",".join(invalid_list))) + + if module.foreman_params['content_type'] != 'yum': + invalid_list = [key for key in ['ignorable_content'] if key in module.foreman_params] + if invalid_list: + module.fail_json(msg="({0}) can only be used with content_type 'yum'".format(",".join(invalid_list))) + + if 'ignore_global_proxy' in module.foreman_params and 'http_proxy_policy' not in module.foreman_params: + module.foreman_params['http_proxy_policy'] = 'none' if module.foreman_params['ignore_global_proxy'] else 'global_default_http_proxy' + + with module.api_connection(): + if not module.desired_absent: + module.auto_lookup_entities() + module.foreman_params.pop('organization') + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/repository_set.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/repository_set.py new file mode 100644 index 00000000..b442befe --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/repository_set.py @@ -0,0 +1,338 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017, Andrew Kofink <ajkofink@gmail.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: repository_set +version_added: 1.0.0 +short_description: Enable/disable Red Hat Repositories available through subscriptions +description: + - Enable/disable Red Hat Repositories that are available through subscriptions +author: "Andrew Kofink (@akofink)" +options: + name: + description: + - Name of the repository set + required: false + type: str + product: + description: + - Name of the parent product + required: false + type: str + label: + description: + - Label of the repository set, can be used in place of I(name) & I(product) + required: false + type: str + repositories: + description: + - Release version and base architecture of the repositories to enable. + - Some reposotory sets require only I(basearch) or only I(releasever) to be set. + - See the examples how you can obtain this information using M(theforeman.foreman.resource_info). + - Required when I(all_repositories) is unset or C(false). + required: false + type: list + elements: dict + suboptions: + basearch: + description: + - Basearch of the repository to enable. + type: str + releasever: + description: + - Releasever of the repository to enable. + type: str + all_repositories: + description: + - Affect all available repositories in the repository set instead of listing them in I(repositories). + - Required when I(repositories) is unset or an empty list. + required: false + type: bool + state: + description: + - Whether the repositories are enabled or not + required: false + choices: + - 'enabled' + - 'disabled' + default: enabled + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Enable RHEL 7 RPMs repositories" + theforeman.foreman.repository_set: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "Red Hat Enterprise Linux 7 Server (RPMs)" + organization: "Default Organization" + product: "Red Hat Enterprise Linux Server" + repositories: + - releasever: "7.0" + basearch: "x86_64" + - releasever: "7.1" + basearch: "x86_64" + - releasever: "7.2" + basearch: "x86_64" + - releasever: "7.3" + basearch: "x86_64" + state: enabled + +- name: "Enable RHEL 7 RPMs repositories with label" + theforeman.foreman.repository_set: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + organization: "Default Organization" + label: rhel-7-server-rpms + repositories: + - releasever: "7.0" + basearch: "x86_64" + - releasever: "7.1" + basearch: "x86_64" + - releasever: "7.2" + basearch: "x86_64" + - releasever: "7.3" + basearch: "x86_64" + state: enabled + +- name: "Disable RHEL 7 Extras RPMs repository" + theforeman.foreman.repository_set: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: Red Hat Enterprise Linux 7 Server - Extras (RPMs) + organization: "Default Organization" + product: Red Hat Enterprise Linux Server + state: disabled + repositories: + - basearch: x86_64 + +- name: "Enable RHEL 8 BaseOS RPMs repository with label" + theforeman.foreman.repository_set: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + organization: "Default Organization" + label: rhel-8-for-x86_64-baseos-rpms + repositories: + - releasever: "8" + +- name: "Enable Red Hat Virtualization Manager RPMs repository with label" + theforeman.foreman.repository_set: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + organization: "Default Organization" + label: "rhel-7-server-rhv-4.2-manager-rpms" + repositories: + - basearch: x86_64 + state: enabled + +- name: "Enable Red Hat Virtualization Manager RPMs repository without specifying basearch" + theforeman.foreman.repository_set: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + organization: "Default Organization" + label: "rhel-7-server-rhv-4.2-manager-rpms" + all_repositories: true + state: enabled + +- name: "Search for possible repository sets of a product" + theforeman.foreman.resource_info: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + organization: "Default Organization" + resource: repository_sets + search: product_name="Red Hat Virtualization Manager" + register: data +- name: "Output found repository sets, see the contentUrl section for possible repository substitutions" + debug: + var: data + +- name: "Search for possible repository sets by label" + theforeman.foreman.resource_info: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + organization: "Default Organization" + resource: repository_sets + search: label=rhel-7-server-rhv-4.2-manager-rpms + register: data +- name: "Output found repository sets, see the contentUrl section for possible repository substitutions" + debug: + var: data + +- name: Enable set with and without all_repositories at the same time + theforeman.foreman.repository_set: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + organization: "Default Organization" + label: "{{ item.label }}" + repositories: "{{ item.repositories | default(omit) }}" + all_repositories: "{{ item.repositories is not defined }}" + state: enabled + loop: + - label: rhel-7-server-rpms + repositories: + - releasever: "7Server" + basearch: "x86_64" + - label: rhel-7-server-rhv-4.2-manager-rpms +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + repository_sets: + description: List of repository sets. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule + + +def get_desired_repos(desired_substitutions, available_repos): + desired_repos = [] + for sub in desired_substitutions: + desired_repos += filter(lambda available: available['substitutions'] == sub, available_repos) + return desired_repos + + +def record_repository_set_state(module, record_data, repo, state_before, state_after): + repo_change_data = record_data.copy() + repo_change_data['repo_name'] = repo + repo_change_data['state'] = state_before + repo_change_data_after = repo_change_data.copy() + repo_change_data_after['state'] = state_after + module.record_before('repository_sets', repo_change_data) + module.record_after('repository_sets', repo_change_data_after) + module.record_after_full('repository_sets', repo_change_data_after) + + +class KatelloRepositorySetModule(KatelloEntityAnsibleModule): + pass + + +def main(): + module = KatelloRepositorySetModule( + foreman_spec=dict( + product=dict(type='entity', scope=['organization']), + name=dict(), + label=dict(), + repositories=dict(type='list', elements='dict', options=dict( + basearch=dict(), + releasever=dict(), + )), + all_repositories=dict(type='bool'), + ), + argument_spec=dict( + state=dict(default='enabled', choices=['disabled', 'enabled']), + ), + required_one_of=[ + ['label', 'name'], + ['repositories', 'all_repositories'], + ], + required_if=[ + ['all_repositories', False, ['repositories']], + ['repositories', [], ['all_repositories']], + ], + ) + + repositories = module.foreman_params.get('repositories', []) + + with module.api_connection(): + scope = module.scope_for('organization') + + record_data = {} + if 'product' in module.foreman_params: + record_data['product'] = module.foreman_params['product'] + scope.update(module.scope_for('product')) + + if 'label' in module.foreman_params: + search = 'label="{0}"'.format(module.foreman_params['label']) + repo_set = module.find_resource('repository_sets', search=search, params=scope) + record_data['label'] = module.foreman_params['label'] + else: + repo_set = module.find_resource_by_name('repository_sets', name=module.foreman_params['name'], params=scope) + record_data['name'] = module.foreman_params['name'] + module.set_entity('entity', repo_set) + + repo_set_scope = {'id': repo_set['id'], 'product_id': repo_set['product']['id']} + repo_set_scope.update(scope) + + available_repos = module.resource_action('repository_sets', 'available_repositories', params=repo_set_scope, ignore_check_mode=True) + available_repos = available_repos['results'] + current_repos = repo_set['repositories'] + if not module.foreman_params.get('all_repositories', False): + desired_repos = get_desired_repos(repositories, available_repos) + else: + desired_repos = available_repos[:] + + current_repo_names = set(map(lambda repo: repo['name'], current_repos)) + desired_repo_names = set(map(lambda repo: repo['repo_name'], desired_repos)) + + if not module.foreman_params.get('all_repositories', False) and len(repositories) != len(desired_repo_names): + repo_set_identification = ' '.join(['{0}: {1}'.format(k, v) for (k, v) in record_data.items()]) + + available_repo_details = [{'name': repo['repo_name'], 'repositories': repo['substitutions']} for repo in available_repos] + desired_repo_details = [{'name': repo['repo_name'], 'repositories': repo['substitutions']} for repo in desired_repos] + search_details = record_data.copy() + search_details['repositories'] = repositories + + error_msg = "Desired repositories are not available on the repository set {0}.\nSearched: {1}\nFound: {2}\nAvailable: {3}".format( + repo_set_identification, search_details, desired_repo_details, available_repo_details) + + module.fail_json(msg=error_msg) + + if module.state == 'enabled': + for repo in desired_repo_names - current_repo_names: + repo_to_enable = next((r for r in available_repos if r['repo_name'] == repo)) + repo_change_params = repo_to_enable['substitutions'].copy() + repo_change_params.update(repo_set_scope) + + record_repository_set_state(module, record_data, repo, 'disabled', 'enabled') + + module.resource_action('repository_sets', 'enable', params=repo_change_params) + elif module.state == 'disabled': + for repo in current_repo_names & desired_repo_names: + repo_to_disable = next((r for r in available_repos if r['repo_name'] == repo)) + repo_change_params = repo_to_disable['substitutions'].copy() + repo_change_params.update(repo_set_scope) + + record_repository_set_state(module, record_data, repo, 'enabled', 'disabled') + + module.resource_action('repository_sets', 'disable', params=repo_change_params) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/repository_sync.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/repository_sync.py new file mode 100644 index 00000000..0a1f4fe4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/repository_sync.py @@ -0,0 +1,89 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2016, Eric D Helms <ericdhelms@gmail.com> +# (c) 2019, Matthias M Dellweg <dellweg@atix.de> +# +# 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: repository_sync +version_added: 1.0.0 +short_description: Sync a Repository or Product +description: + - Sync a repository or product +author: + - "Eric D Helms (@ehelms)" + - "Matthias M Dellweg (@mdellweg) ATIX AG" +options: + product: + description: Product to which the I(repository) lives in + required: true + type: str + repository: + description: | + Name of the repository to sync + If omitted, all repositories in I(product) are synched. + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.organization +... +''' + +EXAMPLES = ''' +- name: "Sync repository" + theforeman.foreman.repository_sync: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + repository: "My repository" + product: "My Product" + organization: "Default Organization" +''' + +RETURN = ''' # ''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloAnsibleModule + + +def main(): + module = KatelloAnsibleModule( + foreman_spec=dict( + product=dict(type='entity', scope=['organization'], required=True), + repository=dict(type='entity', scope=['product'], failsafe=True), + # This should be scoped more explicit for better serch performance, but needs rerecording + # repository=dict(type='entity', scope=['organization', 'product'], failsafe=True), + ), + ) + + module.task_timeout = 12 * 60 * 60 + + with module.api_connection(): + product = module.lookup_entity('product') + repository = module.lookup_entity('repository') + if repository: + task = module.resource_action('repositories', 'sync', {'id': repository['id']}) + else: + task = module.resource_action('products', 'sync', {'id': product['id']}) + + module.exit_json(task=task) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/resource_info.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/resource_info.py new file mode 100644 index 00000000..5f69c3f2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/resource_info.py @@ -0,0 +1,169 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2018, Sean O'Keeffe <seanokeeffe797@gmail.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: resource_info +version_added: 1.0.0 +short_description: Gather information about resources +description: + - Gather information about resources +author: + - "Sean O'Keeffe (@sean797)" +options: + resource: + description: + - Resource to search + - Set to an invalid choice like I(foo) see all available options. + required: true + type: str + search: + description: + - Search query to use + - If None, all resources are returned + type: str + params: + description: + - Add parameters to the API call if necessary + - If not specified, no additional parameters are passed + type: dict + organization: + description: + - Scope the searched resource by organization + type: str + full_details: + description: + - If C(True) all details about the found resources are returned + type: bool + default: false + aliases: [ info ] +notes: + - Some resources don't support scoping and will return errors when you pass I(organization) or unknown data in I(params). +extends_documentation_fragment: + - theforeman.foreman.foreman +''' + +EXAMPLES = ''' +- name: "Read a Setting" + theforeman.foreman.resource_info: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + resource: settings + search: name = foreman_url + register: result +- debug: + var: result.resources[0].value + +- name: "Read all Registries" + theforeman.foreman.resource_info: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + resource: registries + register: result +- debug: + var: item.name + with_items: "{{ result.resources }}" + +- name: "Read all Organizations with full details" + theforeman.foreman.resource_info: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + resource: organizations + full_details: true + register: result +- debug: + var: result.resources + +- name: Get all existing subscriptions for organization with id 1 + theforeman.foreman.resource_info: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + resource: subscriptions + params: + organization_id: 1 + register: result +- debug: + var: result + +- name: Get all existing activation keys for organization ACME + theforeman.foreman.resource_info: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + resource: activation_keys + organization: ACME + register: result +- debug: + var: result +''' + +RETURN = ''' +resources: + description: Resource information + returned: always + type: list +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanAnsibleModule + + +def main(): + + module = ForemanAnsibleModule( + foreman_spec=dict( + resource=dict(type='str', required=True), + search=dict(default=""), + full_details=dict(type='bool', aliases=['info'], default='false'), + params=dict(type='dict'), + organization=dict(), + ), + ) + + module_params = module.foreman_params + resource = module_params['resource'] + search = module_params['search'] + params = module_params.get('params', {}) + + with module.api_connection(): + if resource not in module.foremanapi.resources: + msg = "Resource '{0}' does not exist in the API. Existing resources: {1}".format(resource, ', '.join(sorted(module.foremanapi.resources))) + module.fail_json(msg=msg) + if 'organization' in module_params: + params['organization_id'] = module.find_resource_by_name('organizations', module_params['organization'], thin=True)['id'] + + response = module.list_resource(resource, search, params) + + if module_params['full_details']: + resources = [] + for found_resource in response: + resources.append(module.show_resource(resource, found_resource['id'], params)) + else: + resources = response + + module.exit_json(resources=resources) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/role.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/role.py new file mode 100644 index 00000000..94982e8c --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/role.py @@ -0,0 +1,146 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Christoffer Reijer (Basalt AB) +# +# 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: role +version_added: 1.0.0 +short_description: Manage Roles +description: + - Create, update, and delete Roles +author: + - "Christoffer Reijer (@ephracis) Basalt AB" +options: + name: + description: The name of the role + required: true + type: str + description: + description: Description of the role + required: false + type: str + filters: + description: Filters with permissions for this role + required: false + type: list + elements: dict + suboptions: + permissions: + description: List of permissions + required: true + type: list + elements: str + search: + description: Filter condition for the resources + required: false + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.taxonomy +''' + +EXAMPLES = ''' +- name: role + theforeman.foreman.role: + name: "Provisioner" + description: "Only provision on libvirt" + locations: + - "Uppsala" + organizations: + - "ACME" + filters: + - permissions: + - view_hosts + search: "owner_type = Usergroup and owner_id = 4" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + roles: + description: List of roles. + type: list + elements: dict +''' + +import copy + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanTaxonomicEntityAnsibleModule + + +filter_foreman_spec = dict( + id=dict(invisible=True), + permissions=dict(type='entity_list', required=True, resolve=False), + search=dict(), +) + + +class ForemanRoleModule(ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanRoleModule( + foreman_spec=dict( + name=dict(required=True), + description=dict(), + filters=dict(type='nested_list', foreman_spec=filter_foreman_spec), + ), + ) + + with module.api_connection(): + entity = module.lookup_entity('entity') + new_entity = module.run() + + filters = module.foreman_params.get("filters") + if not module.desired_absent and filters is not None: + scope = {'role_id': new_entity['id']} + + if entity: + current_filters = [module.show_resource('filters', filter['id']) for filter in entity['filters']] + else: + current_filters = [] + desired_filters = copy.deepcopy(filters) + + for desired_filter in desired_filters: + # search for an existing filter + for current_filter in current_filters: + if desired_filter.get('search') == current_filter['search']: + if set(desired_filter.get('permissions', [])) == set(perm['name'] for perm in current_filter['permissions']): + current_filters.remove(current_filter) + break + else: + desired_filter['permissions'] = module.find_resources_by_name('permissions', desired_filter['permissions'], thin=True) + module.ensure_entity('filters', desired_filter, None, params=scope, state='present', foreman_spec=filter_foreman_spec) + for current_filter in current_filters: + module.ensure_entity('filters', None, {'id': current_filter['id']}, params=scope, state='absent', foreman_spec=filter_foreman_spec) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scap_content.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scap_content.py new file mode 100644 index 00000000..d43ad99f --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scap_content.py @@ -0,0 +1,126 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Jameer Pathan <jameerpathan111@gmail.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: scap_content +version_added: 1.0.0 +short_description: Manage SCAP content +description: + - Create, update, and delete SCAP content +author: + - "Jameer Pathan (@jameerpathan111)" +options: + title: + description: + - Title of SCAP content. + required: true + type: str + updated_title: + description: + - New SCAP content title. + - When this parameter is set, the module will not be idempotent. + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.taxonomy + - theforeman.foreman.foreman.scap_datastream +''' + +EXAMPLES = ''' +- name: Create SCAP content + theforeman.foreman.scap_content: + title: "Red Hat firefox default content" + scap_file: "/home/user/Downloads/ssg-firefox-ds.xml" + original_filename: "ssg-firefox-ds.xml" + organizations: + - "Default Organization" + locations: + - "Default Location" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: Update SCAP content + theforeman.foreman.scap_content: + title: "Red Hat firefox default content" + updated_title: "Updated scap content title" + scap_file: "/home/user/Downloads/updated-ssg-firefox-ds.xml" + original_filename: "updated-ssg-firefox-ds.xml" + organizations: + - "Org One" + - "Org Two" + locations: + - "Loc One" + - "Loc Two" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: Delete SCAP content + theforeman.foreman.scap_content: + title: "Red Hat firefox default content" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: absent +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + scap_contents: + description: List of scap contents. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanScapDataStreamModule + + +class ForemanScapContentModule(ForemanScapDataStreamModule): + pass + + +def main(): + module = ForemanScapContentModule( + argument_spec=dict( + updated_title=dict(type='str'), + ), + foreman_spec=dict( + title=dict(type='str', required=True), + ), + entity_key='title', + required_plugins=[('openscap', ['*'])], + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scap_tailoring_file.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scap_tailoring_file.py new file mode 100644 index 00000000..c06c7495 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scap_tailoring_file.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2020 Evgeni Golov <evgeni@golov.de> +# +# 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: scap_tailoring_file +version_added: 1.0.0 +short_description: Manage SCAP Tailoring Files +description: + - Create, update, and delete SCAP Tailoring Files +author: + - "Evgeni Golov (@evgeni)" +options: + name: + description: + - Name of the tailoring file. + required: true + type: str + updated_name: + description: + - New name of the tailoring file. + - When this parameter is set, the module will not be idempotent. + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.taxonomy + - theforeman.foreman.foreman.scap_datastream +''' + +EXAMPLES = ''' +- name: Create SCAP tailoring file + theforeman.foreman.scap_tailoring_file: + name: "Red Hat firefox default content" + scap_file: "/home/user/Downloads/ssg-firefox-ds-tailoring.xml" + original_filename: "ssg-firefox-ds-tailoring.xml" + organizations: + - "Default Organization" + locations: + - "Default Location" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: Update SCAP tailoring file + theforeman.foreman.scap_tailoring_file: + name: "Red Hat firefox default content" + updated_name: "Updated tailoring file name" + scap_file: "/home/user/Downloads/updated-ssg-firefox-ds-tailoring.xml" + original_filename: "updated-ssg-firefox-ds-tailoring.xml" + organizations: + - "Org One" + - "Org Two" + locations: + - "Loc One" + - "Loc Two" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: Delete SCAP tailoring file + theforeman.foreman.scap_tailoring_file: + name: "Red Hat firefox default content" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: absent +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + scap_tailoring_files: + description: List of scap tailoring files. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanScapDataStreamModule + + +class ForemanTailoringFileModule(ForemanScapDataStreamModule): + pass + + +def main(): + module = ForemanTailoringFileModule( + argument_spec=dict( + updated_name=dict(type='str'), + ), + foreman_spec=dict( + name=dict(type='str', required=True), + ), + required_plugins=[('openscap', ['*'])], + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scc_account.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scc_account.py new file mode 100644 index 00000000..259e9a7d --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scc_account.py @@ -0,0 +1,180 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Manisha Singhal (ATIX AG) +# +# 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: scc_account +version_added: 1.0.0 +short_description: Manage SUSE Customer Center Accounts +description: + - Manage SUSE Customer Center Accounts + - This module requires the foreman_scc_manager plugin set up in the server + - See U(https://github.com/ATIX-AG/foreman_scc_manager) +author: + - "Manisha Singhal (@manisha15) ATIX AG" +options: + name: + description: Name of the suse customer center account + required: true + type: str + login: + description: Login id of suse customer center account + required: false + type: str + scc_account_password: + description: Password of suse customer center account + required: false + type: str + base_url: + description: URL of SUSE for suse customer center account + required: false + type: str + interval: + description: Interval for syncing suse customer center account + required: false + type: str + choices: ["never", "daily", "weekly", "monthly"] + sync_date: + description: Last Sync time of suse customer center account + required: false + type: str + organization: + description: Name of related organization + type: str + required: true + test_connection: + description: Test suse customer center account credentials that connects to the server + required: false + default: false + type: bool + updated_name: + description: Name to be updated of suse customer center account + type: str + state: + description: State of the suse customer center account + default: present + choices: ["present", "absent", "synced"] + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Create a suse customer center account" + theforeman.foreman.scc_account: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "Test" + login: "abcde" + scc_account_password: "12345" + base_url: "https://scc.suse.com" + state: present + +- name: "Update a suse customer center account" + theforeman.foreman.scc_account: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "Test1" + state: present + +- name: "Delete a suse customer center account" + theforeman.foreman.scc_account: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "Test" + state: absent +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + scc_accounts: + description: List of scc accounts. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule + + +class KatelloSccAccountModule(KatelloEntityAnsibleModule): + pass + + +def main(): + module = KatelloSccAccountModule( + foreman_spec=dict( + name=dict(required=True), + updated_name=dict(), + login=dict(), + scc_account_password=dict(no_log=True, flat_name='password'), + base_url=dict(), + sync_date=dict(), + interval=dict(choices=['never', 'daily', 'weekly', 'monthly']), + ), + argument_spec=dict( + test_connection=dict(type='bool', default=False), + state=dict(default='present', choices=['present', 'absent', 'synced']), + ), + required_plugins=[('scc_manager', ['*'])], + ) + + module.task_timeout = 4 * 60 + + with module.api_connection(): + module.foreman_spec['entity']['failsafe'] = (module.state != 'synced') + entity = module.lookup_entity('entity') + + if not module.desired_absent: + if not entity: + if 'login' not in module.foreman_params: + module.fail_json(msg="scc account login not provided") + if 'scc_account_password' not in module.foreman_params: + module.fail_json(msg="Scc account password not provided") + + if module.foreman_params['test_connection']: + scc_account_credentials = {} + if entity: + scc_account_credentials['id'] = entity['id'] + if 'login' in module.foreman_params: + scc_account_credentials['login'] = module.foreman_params['login'] + if 'scc_account_password' in module.foreman_params: + scc_account_credentials['password'] = module.foreman_params['scc_account_password'] + if 'base_url' in module.foreman_params: + scc_account_credentials['base_url'] = module.foreman_params['base_url'] + module.resource_action('scc_accounts', 'test_connection', scc_account_credentials, ignore_check_mode=True) + + if module.state == 'synced': + module.resource_action('scc_accounts', 'sync', {'id': entity['id']}) + else: + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scc_product.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scc_product.py new file mode 100644 index 00000000..abb935e0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scc_product.py @@ -0,0 +1,84 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Manisha Singhal (ATIX AG) +# +# 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: scc_product +version_added: 1.0.0 +short_description: Subscribe SUSE Customer Center Account Products +description: + - Manage SUSE Customer Center Products + - This module requires the foreman_scc_manager plugin set up in the server + - See U(https://github.com/ATIX-AG/foreman_scc_manager) +author: + - "Manisha Singhal (@manisha15) ATIX AG" +options: + scc_product: + description: Full name of the product of suse customer center account + required: true + type: str + aliases: + - friendly_name + scc_account: + description: Name of the suse customer center account associated with product + required: true + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Subscribe to suse customer center product" + theforeman.foreman.scc_product: + friendly_name: "Product1" + scc_account: "Test" + organization: "Test Organization" +''' + +RETURN = ''' # ''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloAnsibleModule + + +def main(): + module = KatelloAnsibleModule( + foreman_spec=dict( + scc_product=dict(required=True, type='entity', aliases=['friendly_name'], scope=['scc_account'], thin=False), + scc_account=dict(required=True, type='entity', scope=['organization']), + ), + required_plugins=[('scc_manager', ['*'])], + ) + + module.task_timeout = 4 * 60 + + with module.api_connection(): + scc_product = module.lookup_entity('scc_product') + + if not scc_product.get('product_id'): + payload = {'id': scc_product['id']} + payload.update(module.scope_for('scc_account')) + module.resource_action('scc_products', 'subscribe', payload) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/setting.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/setting.py new file mode 100644 index 00000000..f5e0b126 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/setting.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2018 Matthias M Dellweg (ATIX AG) +# +# 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: setting +version_added: 1.0.0 +short_description: Manage Settings +description: + - Manage Settings +author: + - "Matthias M Dellweg (@mdellweg) ATIX AG" +options: + name: + description: + - Name of the Setting + required: true + type: str + value: + description: + - value to set the Setting to + - if missing, reset to default + required: false + type: raw +extends_documentation_fragment: + - theforeman.foreman.foreman +''' + +EXAMPLES = ''' +- name: "Set a Setting" + theforeman.foreman.setting: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "http_proxy" + value: "http://localhost:8088" + +- name: "Reset a Setting" + theforeman.foreman.setting: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "http_proxy" +''' + +RETURN = ''' +foreman_setting: + description: Created / Updated state of the setting + returned: success + type: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanStatelessEntityAnsibleModule, parameter_value_to_str + + +class ForemanSettingModule(ForemanStatelessEntityAnsibleModule): + pass + + +def main(): + module = ForemanSettingModule( + foreman_spec=dict( + name=dict(required=True), + value=dict(type='raw'), + ), + ) + + with module.api_connection(): + entity = module.lookup_entity('entity') + + if 'value' not in module.foreman_params: + module.foreman_params['value'] = entity['default'] or '' + + settings_type = entity['settings_type'] + new_value = module.foreman_params['value'] + # Allow to pass integers as string + if settings_type == 'integer': + new_value = int(new_value) + module.foreman_params['value'] = parameter_value_to_str(new_value, settings_type) + old_value = entity['value'] + entity['value'] = parameter_value_to_str(old_value, settings_type) + + entity = module.ensure_entity('settings', module.foreman_params, entity, state='present') + + if entity: + # Fake the not serialized input value as output + entity['value'] = new_value + + module.exit_json(foreman_setting=entity) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/smart_class_parameter.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/smart_class_parameter.py new file mode 100644 index 00000000..e8f34582 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/smart_class_parameter.py @@ -0,0 +1,273 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2020 Baptiste Agasse (@bagasse) +# +# 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: smart_class_parameter +version_added: 1.0.0 +short_description: Manage Smart Class Parameters +description: + - Update Smart Class Parameters. + - Smart Class Parameters are created/deleted for Puppet classes during import and cannot be created or deleted otherwise. +author: + - "Baptiste Agasse (@bagasse)" +options: + puppetclass_name: + description: Name of the puppetclass that own the parameter + required: true + type: str + parameter: + description: Name of the parameter + required: true + type: str + description: + description: Description of the Smart Class Parameter + type: str + override: + description: Whether the smart class parameter value is managed by Foreman + type: bool + default_value: + description: Value to use by default. + type: raw + hidden_value: + description: When enabled the parameter is hidden in the UI. + type: bool + omit: + description: + - Don't send this parameter in classification output. + - Puppet will use the value defined in the Puppet manifest for this parameter. + type: bool + override_value_order: + description: The order in which values are resolved. + type: list + elements: str + validator_type: + description: Types of validation values. + type: str + choices: + - regexp + - list + validator_rule: + description: Used to enforce certain values for the parameter values. + type: str + parameter_type: + description: Types of variable values. If C(none), set the parameter type to empty value. + type: str + choices: + - string + - boolean + - integer + - real + - array + - hash + - yaml + - json + - none + required: + description: If true, will raise an error if there is no default value and no matcher provide a value. + type: bool + merge_overrides: + description: Merge all matching values (only array/hash type). + type: bool + merge_default: + description: Include default value when merging all matching values. + type: bool + avoid_duplicates: + description: Remove duplicate values (only array type) + type: bool + override_values: + description: Value overrides + required: false + type: list + elements: dict + suboptions: + match: + description: Override match + required: true + type: str + value: + description: Override value, required if omit is false + type: raw + omit: + description: Don't send this parameter in classification output, replaces use_puppet_default. + type: bool + state: + description: State of the entity. + type: str + default: present + choices: + - present + - present_with_defaults +extends_documentation_fragment: + - theforeman.foreman.foreman +''' + +EXAMPLES = ''' +- name: "Update prometheus::server alertmanagers_config param default value" + theforeman.foreman.smart_class_parameter: + puppetclass_name: "prometheus::server" + parameter: alertmanagers_config + override: true + required: true + default_value: /etc/prometheus/alert.yml + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present + +- name: "Update prometheus::server alertmanagers_config param default value" + theforeman.foreman.smart_class_parameter: + puppetclass_name: "prometheus::server" + parameter: alertmanagers_config + override: true + override_value_order: + - fqdn + - hostgroup + - domain + required: true + default_value: /etc/prometheus/alert.yml + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + override_values: + - match: domain=example.com + value: foo + - match: domain=foo.example.com + omit: true + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + smart_class_parameters: + description: List of smart class parameters. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule, parameter_value_to_str + +override_value_foreman_spec = dict( + id=dict(invisible=True), + match=dict(required=True), + value=dict(type='raw'), + omit=dict(type='bool'), +) + + +class ForemanSmartClassParameterModule(ForemanEntityAnsibleModule): + # TODO: greatly similar to how parameters are managed, dry it up ? + def ensure_override_values(self, entity, expected_override_values): + if expected_override_values is not None: + parameter_type = entity.get('parameter_type', 'string') + scope = {'smart_class_parameter_id': entity['id']} + if not self.desired_absent: + current_override_values = {override_value['match']: override_value for override_value in entity.get('override_values', [])} + desired_override_values = {override_value['match']: override_value for override_value in expected_override_values} + + for match in desired_override_values: + desired_override_value = desired_override_values[match] + if 'value' in desired_override_value: + desired_override_value['value'] = parameter_value_to_str(desired_override_value['value'], parameter_type) + current_override_value = current_override_values.pop(match, None) + if current_override_value: + current_override_value['value'] = parameter_value_to_str(current_override_value['value'], parameter_type) + self.ensure_entity( + 'override_values', desired_override_value, current_override_value, + state="present", foreman_spec=override_value_foreman_spec, params=scope) + for current_override_value in current_override_values.values(): + self.ensure_entity( + 'override_values', None, current_override_value, state="absent", foreman_spec=override_value_foreman_spec, params=scope) + + +def main(): + module = ForemanSmartClassParameterModule( + argument_spec=dict( + puppetclass_name=dict(required=True), + parameter=dict(required=True), + state=dict(default='present', choices=['present_with_defaults', 'present']), + ), + foreman_spec=dict( + parameter_type=dict(choices=['string', 'boolean', 'integer', 'real', 'array', 'hash', 'yaml', 'json', 'none']), + validator_type=dict(choices=['list', 'regexp']), + validator_rule=dict(), + description=dict(), + default_value=dict(type='raw'), + omit=dict(type='bool'), + override=dict(type='bool'), + merge_default=dict(type='bool'), + merge_overrides=dict(type='bool'), + avoid_duplicates=dict(type='bool'), + required=dict(type='bool'), + hidden_value=dict(type='bool'), + override_value_order=dict(type='list', elements='str'), + # tried nested_list here but, if using nested_list, override_values are not part of loaded entity. + # override_values=dict(type='nested_list', elements='dict', foreman_spec=override_value_foreman_spec), + override_values=dict(type='list', elements='dict'), + ), + # smart_class_parameters are created on puppetclass import and cannot be created/deleted from API, + # so if we don't find it, it's an error. + entity_opts=dict(failsafe=False), + ) + + module_params = module.foreman_params + if module_params.get('parameter_type', 'string') not in ['array', 'hash']: + if 'merge_default' in module_params or 'merge_overrides' in module_params: + module.fail_json(msg="merge_default or merge_overrides can be used only with array or hash parameter_type") + if module_params.get('parameter_type', 'string') != 'array' and 'avoid_duplicates' in module_params: + module.fail_json(msg="avoid_duplicates can be used only with array parameter_type") + + search = "puppetclass_name={0} and parameter={1}".format(module_params['puppetclass_name'], module_params['parameter']) + override_values = module_params.pop('override_values', None) + + if 'override_value_order' in module_params: + module_params['override_value_order'] = '\n'.join(module_params['override_value_order']) + if 'parameter_type' in module_params and module_params['parameter_type'] == 'none': + module_params['parameter_type'] = '' + + with module.api_connection(): + entity = module.find_resource('smart_class_parameters', search=search) + module.set_entity('entity', entity) + # When override is set to false, foreman API don't accept parameter_type and all 'override options' have to be set to false if present + if not module_params.get('override', False): + module_params['parameter_type'] = '' + for override_option in ['merge_default', 'merge_overrides', 'avoid_duplicates']: + if override_option in entity and entity[override_option]: + module_params[override_option] = False + + # Foreman API returns 'hidden_value?' instead of 'hidden_value' this is a bug ? + if 'hidden_value?' in entity: + entity['hidden_value'] = entity.pop('hidden_value?') + if 'default_value' in module_params: + module_params['default_value'] = parameter_value_to_str(module_params['default_value'], module_params.get('parameter_type', 'string')) + if 'default_value' in entity: + entity['default_value'] = parameter_value_to_str(entity['default_value'], entity.get('parameter_type', 'string')) + + entity = module.run() + module.ensure_override_values(entity, override_values) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/smart_proxy.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/smart_proxy.py new file mode 100644 index 00000000..68672f85 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/smart_proxy.py @@ -0,0 +1,166 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# 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: smart_proxy +version_added: 1.4.0 +short_description: Manage Smart Proxies +description: + - Create, update and delete Smart Proxies +author: + - "James Stuart (@jstuart)" + - "Matthias M Dellweg (@mdellweg)" + - "Jeffrey van Pelt (@Thulium-Drake)" +options: + name: + description: + - Name of the Smart Proxy + required: true + type: str + lifecycle_environments: + description: + - Lifecycle Environments synced to the Smart Proxy. + - Only available for Katello installations. + required: false + elements: str + type: list + url: + description: + - URL of the Smart Proxy + required: true + type: str + download_policy: + description: + - The download policy for the Smart Proxy + - Only available for Katello installations. + choices: + - background + - immediate + - on_demand + required: false + type: str +notes: + - Even with I(state=present) this module does not install a new Smart Proxy. + - It can only associate an existing Smart Proxy listening at the specified I(url). + - Consider using I(foreman-installer) to create Smart Proxies. +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.taxonomy +''' + +EXAMPLES = ''' +# Create a local Smart Proxy +- name: "Create Smart Proxy" + theforeman.foreman.smart_proxy: + username: "admin" + password: "changeme" + server_url: "https://{{ ansible_fqdn }}" + name: "{{ ansible_fqdn }}" + url: "https://{{ ansible_fqdn }}:9090" + download_policy: "immediate" + lifecycle_environments: + - "Development" + organizations: + - "Default Organization" + locations: + - "Default Location" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + smart_proxies: + description: List of smart_proxies. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanTaxonomicEntityAnsibleModule + + +class ForemanSmartProxyModule(ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanSmartProxyModule( + foreman_spec=dict( + name=dict(required=True), + url=dict(required=True), + lifecycle_environments=dict(required=False, type='entity_list'), + download_policy=dict(required=False, choices=['background', 'immediate', 'on_demand']), + ), + required_plugins=[('katello', ['lifecycle_environments', 'download_policy'])], + ) + + with module.api_connection(): + handle_lifecycle_environments = not module.desired_absent and 'lifecycle_environments' in module.foreman_params + if handle_lifecycle_environments: + module.lookup_entity('lifecycle_environments') + lifecycle_environments = module.foreman_params.pop('lifecycle_environments', []) + + smart_proxy = module.lookup_entity('entity') + new_smart_proxy = module.run() + + if handle_lifecycle_environments: + + if smart_proxy: + payload = { + 'id': new_smart_proxy['id'], + } + current_lces = module.resource_action('capsule_content', 'lifecycle_environments', payload, ignore_check_mode=True, record_change=False) + else: + current_lces = {'results': []} + + desired_environment_ids = set(lifecycle_environment['id'] for lifecycle_environment in lifecycle_environments) + current_environment_ids = set(lifecycle_environment['id'] for lifecycle_environment in current_lces['results']) if current_lces else set() + + module.record_before('smart_proxy_content/lifecycle_environment_ids', current_environment_ids) + module.record_after('smart_proxy_content/lifecycle_environment_ids', desired_environment_ids) + module.record_after_full('smart_proxy_content/lifecycle_environment_ids', desired_environment_ids) + + if desired_environment_ids != current_environment_ids: + environment_ids_to_add = desired_environment_ids - current_environment_ids + if environment_ids_to_add: + for environment_id_to_add in environment_ids_to_add: + payload = { + 'id': new_smart_proxy['id'], + 'environment_id': environment_id_to_add, + } + module.resource_action('capsule_content', 'add_lifecycle_environment', payload) + environment_ids_to_remove = current_environment_ids - desired_environment_ids + if environment_ids_to_remove: + for environment_id_to_remove in environment_ids_to_remove: + payload = { + 'id': smart_proxy['id'], + 'environment_id': environment_id_to_remove, + } + module.resource_action('capsule_content', 'remove_lifecycle_environment', payload) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/snapshot.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/snapshot.py new file mode 100644 index 00000000..34043c07 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/snapshot.py @@ -0,0 +1,138 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Manisha Singhal (ATIX AG) +# +# 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: snapshot +version_added: 1.0.0 +short_description: Manage Snapshots +description: + - "Manage Snapshots for Host Entities" + - "This module can create, update, revert and delete snapshots" + - "This module requires the foreman_snapshot_management plugin set up in the server" + - "See: U(https://github.com/ATIX-AG/foreman_snapshot_management)" +author: + - "Manisha Singhal (@Manisha15) ATIX AG" +options: + name: + description: + - Name of Snapshot + required: true + type: str + description: + description: + - Description of Snapshot + required: false + type: str + host: + description: + - Name of related Host + required: true + type: str + state: + description: + - State of Snapshot + default: present + choices: ["present", "reverted", "absent"] + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman +''' + +EXAMPLES = ''' +- name: "Create a Snapshot" + theforeman.foreman.snapshot: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "snapshot_before_software_upgrade" + host: "server.example.com" + state: present + +- name: "Update a Snapshot" + theforeman.foreman.snapshot: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "snapshot_before_software_upgrade" + host: "server.example.com" + description: "description of snapshot" + state: present + +- name: "Revert a Snapshot" + theforeman.foreman.snapshot: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "snapshot_before_software_upgrade" + host: "server.example.com" + state: reverted + +- name: "Delete a Snapshot" + theforeman.foreman.snapshot: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "snapshot_before_software_upgrade" + host: "server.example.com" + state: absent +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + snapshots: + description: List of snapshots. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule + + +class ForemanSnapshotModule(ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanSnapshotModule( + argument_spec=dict( + state=dict(default='present', choices=['present', 'absent', 'reverted']), + ), + foreman_spec=dict( + host=dict(type='entity', required=True, ensure=False), + name=dict(required=True), + description=dict(), + ), + required_plugins=[('snapshot_management', ['*'])], + entity_opts={'scope': ['host']}, + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/status_info.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/status_info.py new file mode 100644 index 00000000..a83e4f98 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/status_info.py @@ -0,0 +1,76 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2020 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 <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: status_info +version_added: 1.3.0 +short_description: Get status info +description: + - Get status information from the server +author: + - "Evgeni Golov (@evgeni)" +extends_documentation_fragment: + - theforeman.foreman.foreman +''' + +EXAMPLES = ''' +- name: status + theforeman.foreman.status_info: + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" +''' + +RETURN = ''' +status: + description: Basic status of the server. + returned: always + type: dict +ping: + description: Detailed service status. + returned: if supported by server + type: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanAnsibleModule + + +def main(): + module = ForemanAnsibleModule() + + with module.api_connection(): + status = module.status() + + if 'ping' in module.foremanapi.resources: + if 'ping' in module.foremanapi.resource('ping').actions: + ping_action = 'ping' + else: + ping_action = 'index' + ping = module.foremanapi.resource('ping').call(ping_action) + else: + ping = None + + module.exit_json(status=status, ping=ping) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/subnet.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/subnet.py new file mode 100644 index 00000000..4936ea55 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/subnet.py @@ -0,0 +1,285 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2018 Baptiste AGASSE (baptiste.agasse@gmail.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: subnet +version_added: 1.0.0 +short_description: Manage Subnets +description: + - Create, update, and delete Subnets +author: + - "Baptiste Agasse (@bagasse)" +requirements: + - ipaddress +options: + name: + description: Subnet name + required: true + type: str + description: + description: Description of the subnet + type: str + updated_name: + description: New subnet name. When this parameter is set, the module will not be idempotent. + type: str + network_type: + description: Subnet type + default: IPv4 + choices: ["IPv4", "IPv6"] + type: str + dns_primary: + description: Primary DNS server for this subnet + required: false + type: str + dns_secondary: + description: Secondary DNS server for this subnet + required: false + type: str + domains: + description: List of DNS domains the subnet should assigned to + required: false + type: list + elements: str + gateway: + description: Subnet gateway IP address + required: false + type: str + network: + description: Subnet IP address + required: true + type: str + cidr: + description: CIDR prefix length; Required if I(network_type=IPv4) and no I(mask) provided + type: int + mask: + description: Subnet netmask. Required if I(network_type=IPv4) and no I(cidr) prefix length provided + type: str + from_ip: + description: First IP address of the host IP allocation pool + required: false + type: str + to_ip: + description: Last IP address of the host IP allocation pool + required: false + type: str + boot_mode: + description: Boot mode used by hosts in this subnet + required: false + default: DHCP + choices: ["DHCP", "Static"] + type: str + ipam: + description: IPAM mode for this subnet + required: false + default: DHCP + choices: + - "DHCP" + - "Internal DB" + - "Random DB" + - "EUI-64" + - "External IPAM" + - "None" + type: str + dhcp_proxy: + description: DHCP Smart proxy for this subnet + required: false + type: str + httpboot_proxy: + description: HTTP Boot Smart proxy for this subnet + required: false + type: str + tftp_proxy: + description: TFTP Smart proxy for this subnet + required: false + type: str + discovery_proxy: + description: + - Discovery Smart proxy for this subnet + - This option is only available if the discovery plugin is installed. + required: false + type: str + dns_proxy: + description: DNS Smart proxy for this subnet + required: false + type: str + template_proxy: + description: Template Smart proxy for this subnet + required: false + type: str + remote_execution_proxies: + description: + - Remote execution Smart proxies for this subnet + - This option is only available if the remote_execution plugin is installed. + - This will always report I(changed=true) when used with I(remote_execution < 4.1.0), due to a bug in the plugin. + required: false + type: list + elements: str + externalipam_proxy: + description: + - External IPAM proxy for this subnet. + - Only relevant if I(ipam=External IPAM). + required: false + type: str + externalipam_group: + description: + - External IPAM group for this subnet. + - Only relevant if I(ipam=External IPAM). + version_added: 1.5.0 + required: false + type: str + vlanid: + description: VLAN ID + required: false + type: int + mtu: + description: MTU + required: false + type: int + parameters: + description: + - Subnet specific host parameters +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.taxonomy + - theforeman.foreman.foreman.nested_parameters +''' + +EXAMPLES = ''' +- name: My subnet + theforeman.foreman.subnet: + name: "My subnet" + description: "My description" + network: "192.168.0.0" + mask: "255.255.255.192" + gateway: "192.168.0.1" + from_ip: "192.168.0.2" + to_ip: "192.168.0.42" + boot_mode: "Static" + dhcp_proxy: "smart-proxy1.foo.example.com" + tftp_proxy: "smart-proxy1.foo.example.com" + dns_proxy: "smart-proxy2.foo.example.com" + template_proxy: "smart-proxy2.foo.example.com" + vlanid: 452 + mtu: 9000 + domains: + - "foo.example.com" + - "bar.example.com" + organizations: + - "Example Org" + locations: + - "Toulouse" + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + subnets: + description: List of subnets. + type: list + elements: dict +''' + +import traceback +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ( + ForemanTaxonomicEntityAnsibleModule, ParametersMixin, missing_required_lib +) +try: + import ipaddress + HAS_IPADDRESS = True +except ImportError: + HAS_IPADDRESS = False + IPADDRESS_IMP_ERR = traceback.format_exc() + + +class ForemanSubnetModule(ParametersMixin, ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanSubnetModule( + argument_spec=dict( + updated_name=dict(), + ), + foreman_spec=dict( + name=dict(required=True), + description=dict(), + network_type=dict(choices=['IPv4', 'IPv6'], default='IPv4'), + dns_primary=dict(), + dns_secondary=dict(), + domains=dict(type='entity_list'), + gateway=dict(), + network=dict(required=True), + cidr=dict(type='int'), + mask=dict(), + from_ip=dict(flat_name='from'), + to_ip=dict(flat_name='to'), + boot_mode=dict(choices=['DHCP', 'Static'], default='DHCP'), + ipam=dict(choices=['DHCP', 'Internal DB', 'Random DB', 'EUI-64', 'External IPAM', 'None'], default='DHCP'), + dhcp_proxy=dict(type='entity', flat_name='dhcp_id', resource_type='smart_proxies'), + httpboot_proxy=dict(type='entity', flat_name='httpboot_id', resource_type='smart_proxies'), + tftp_proxy=dict(type='entity', flat_name='tftp_id', resource_type='smart_proxies'), + discovery_proxy=dict(type='entity', flat_name='discovery_id', resource_type='smart_proxies'), + dns_proxy=dict(type='entity', flat_name='dns_id', resource_type='smart_proxies'), + template_proxy=dict(type='entity', flat_name='template_id', resource_type='smart_proxies'), + remote_execution_proxies=dict(type='entity_list', resource_type='smart_proxies'), + externalipam_proxy=dict(type='entity', flat_name='externalipam_id', resource_type='smart_proxies'), + externalipam_group=dict(), + vlanid=dict(type='int'), + mtu=dict(type='int'), + ), + required_plugins=[ + ('discovery', ['discovery_proxy']), + ('remote_execution', ['remote_execution_proxies']), + ], + ) + + if not HAS_IPADDRESS: + module.fail_json(msg=missing_required_lib("ipaddress"), exception=IPADDRESS_IMP_ERR) + + module_params = module.foreman_params + + if not module.desired_absent: + if module_params['network_type'] == 'IPv4': + if 'mask' not in module_params and 'cidr' not in module_params: + module.fail_json(msg='When specifying IPv4 networks, either "mask" or "cidr" is required.') + IPNetwork = ipaddress.IPv4Network + else: + IPNetwork = ipaddress.IPv6Network + if 'mask' in module_params and 'cidr' not in module_params: + module_params['cidr'] = IPNetwork(u'%s/%s' % (module_params['network'], module_params['mask'])).prefixlen + elif 'mask' not in module_params and 'cidr' in module_params: + module_params['mask'] = str(IPNetwork(u'%s/%s' % (module_params['network'], module_params['cidr'])).netmask) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/subscription_manifest.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/subscription_manifest.py new file mode 100644 index 00000000..3b2c7893 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/subscription_manifest.py @@ -0,0 +1,134 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017, Andrew Kofink <ajkofink@gmail.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: subscription_manifest +version_added: 1.0.0 +short_description: Manage Subscription Manifests +description: + - Upload, refresh and delete Subscription Manifests +author: "Andrew Kofink (@akofink)" +options: + manifest_path: + description: + - Path to the manifest zip file + - This parameter will be ignored if I(state=absent) or I(state=refreshed) + type: path + state: + description: + - The state of the manifest + default: present + choices: + - absent + - present + - refreshed + type: str + repository_url: + description: + - URL to retrieve content from + aliases: [ redhat_repository_url ] + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Upload the RHEL developer edition manifest" + theforeman.foreman.subscription_manifest: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + organization: "Default Organization" + state: present + manifest_path: "/tmp/manifest.zip" +''' + +RETURN = ''' # ''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule + + +def main(): + module = KatelloEntityAnsibleModule( + argument_spec=dict( + manifest_path=dict(type='path'), + state=dict(default='present', choices=['absent', 'present', 'refreshed']), + repository_url=dict(aliases=['redhat_repository_url']), + ), + foreman_spec=dict( + organization=dict(type='entity', required=True, thin=False), + ), + required_if=[ + ['state', 'present', ['manifest_path']], + ], + supports_check_mode=False, + ) + + module.task_timeout = 5 * 60 + + with module.api_connection(): + organization = module.lookup_entity('organization') + scope = module.scope_for('organization') + + try: + existing_manifest = organization['owner_details']['upstreamConsumer'] + except KeyError: + existing_manifest = None + + if module.state == 'present': + if 'repository_url' in module.foreman_params: + payload = {'redhat_repository_url': module.foreman_params['repository_url']} + org_spec = dict(id=dict(), redhat_repository_url=dict()) + organization = module.ensure_entity('organizations', payload, organization, state='present', foreman_spec=org_spec) + + try: + with open(module.foreman_params['manifest_path'], 'rb') as manifest_file: + files = {'content': (module.foreman_params['manifest_path'], manifest_file, 'application/zip')} + params = {} + if 'repository_url' in module.foreman_params: + params['repository_url'] = module.foreman_params['repository_url'] + params.update(scope) + result = module.resource_action('subscriptions', 'upload', params, files=files, record_change=False, ignore_task_errors=True) + for error in result['humanized']['errors']: + if "same as existing data" in error: + # Nothing changed, but everything ok + break + if "older than existing data" in error: + module.fail_json(msg="Manifest is older than existing data.") + else: + module.fail_json(msg="Upload of the manifest failed: %s" % error) + else: + module.set_changed() + except IOError as e: + module.fail_json(msg="Unable to read the manifest file: %s" % e) + elif module.desired_absent and existing_manifest: + module.resource_action('subscriptions', 'delete_manifest', scope) + elif module.state == 'refreshed': + if existing_manifest: + module.resource_action('subscriptions', 'refresh_manifest', scope) + else: + module.fail_json(msg="No manifest found to refresh.") + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/sync_plan.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/sync_plan.py new file mode 100644 index 00000000..ee8ebece --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/sync_plan.py @@ -0,0 +1,180 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2017, Andrew Kofink <ajkofink@gmail.com> +# (c) 2019, Matthias Dellweg <dellweg@atix.de> +# +# 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: sync_plan +version_added: 1.0.0 +short_description: Manage Sync Plans +description: + - Manage sync plans +author: + - "Andrew Kofink (@akofink)" + - "Matthis Dellweg (@mdellweg) ATIX-AG" +options: + name: + description: + - Name of the sync plan + required: true + type: str + description: + description: + - Description of the sync plan + type: str + interval: + description: + - How often synchronization should run + choices: + - hourly + - daily + - weekly + - custom cron + required: true + type: str + enabled: + description: + - Whether the sync plan is active + required: true + type: bool + sync_date: + description: + - Start date and time of the first synchronization + required: true + type: str + cron_expression: + description: + - A cron expression as found in crontab files + - This must be provided together with I(interval='custom cron'). + type: str + products: + description: + - List of products to include in the sync plan + required: false + type: list + elements: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state_with_defaults + - theforeman.foreman.foreman.organization +''' + +EXAMPLES = ''' +- name: "Create or update weekly RHEL sync plan" + theforeman.foreman.sync_plan: + username: "admin" + password: "changeme" + server_url: "https://foreman.example.com" + name: "Weekly RHEL Sync" + organization: "Default Organization" + interval: "weekly" + enabled: false + sync_date: "2017-01-01 00:00:00 UTC" + products: + - 'Red Hat Enterprise Linux Server' + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + sync_plans: + description: List of sync plans. + type: list + elements: dict +''' + + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule + + +class KatelloSyncPlanModule(KatelloEntityAnsibleModule): + pass + + +def main(): + module = KatelloSyncPlanModule( + foreman_spec=dict( + name=dict(required=True), + description=dict(), + interval=dict(choices=['hourly', 'daily', 'weekly', 'custom cron'], required=True), + enabled=dict(type='bool', required=True), + sync_date=dict(required=True), + cron_expression=dict(), + products=dict(type='entity_list', scope=['organization'], resolve=False), + ), + argument_spec=dict( + state=dict(default='present', choices=['present_with_defaults', 'present', 'absent']), + ), + required_if=[ + ['interval', 'custom cron', ['cron_expression']], + ], + ) + + if (module.foreman_params['interval'] != 'custom cron') and ('cron_expression' in module.foreman_params): + module.fail_json(msg='"cron_expression" cannot be combined with "interval"!="custom cron".') + + with module.api_connection(): + entity = module.lookup_entity('entity') + scope = module.scope_for('organization') + + handle_products = not (module.desired_absent or module.state == 'present_with_defaults') and 'products' in module.foreman_params + if handle_products: + module.lookup_entity('products') + + products = module.foreman_params.pop('products', None) + sync_plan = module.run() + + if handle_products: + desired_product_ids = set(product['id'] for product in products) + current_product_ids = set(product['id'] for product in entity['products']) if entity else set() + + module.record_before('sync_plans/products', {'id': sync_plan['id'], 'product_ids': current_product_ids}) + module.record_after('sync_plans/products', {'id': sync_plan['id'], 'product_ids': desired_product_ids}) + module.record_after_full('sync_plans/products', {'id': sync_plan['id'], 'product_ids': desired_product_ids}) + + if desired_product_ids != current_product_ids: + if not module.check_mode: + product_ids_to_add = desired_product_ids - current_product_ids + if product_ids_to_add: + payload = { + 'id': sync_plan['id'], + 'product_ids': list(product_ids_to_add), + } + payload.update(scope) + module.resource_action('sync_plans', 'add_products', payload) + product_ids_to_remove = current_product_ids - desired_product_ids + if product_ids_to_remove: + payload = { + 'id': sync_plan['id'], + 'product_ids': list(product_ids_to_remove), + } + payload.update(scope) + module.resource_action('sync_plans', 'remove_products', payload) + else: + module.set_changed() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/templates_import.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/templates_import.py new file mode 100644 index 00000000..d531039c --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/templates_import.py @@ -0,0 +1,193 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2020 Anton Nesterov (@nesanton) +# +# 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: templates_import +version_added: 1.0.0 +short_description: Sync Templates from a repository +description: + - Sync provisioning templates, report_templates, partition tables and job templates from external git repository or file system. + - Based on foreman_templates plugin U(https://github.com/theforeman/foreman_templates). +author: + - "Anton Nesterov (@nesanton)" +notes: + - Due to a bug in the foreman_templates plugin, this module won't report C(changed=true) + when the only change is the Organization/Location association of the imported templates. + Please see U(https://projects.theforeman.org/issues/29534) for details. + - Default values for all module options can be set using M(theforeman.foreman.setting) for TemplateSync category or on the settings page in WebUI. +options: + prefix: + description: + - Adds specified string to beginning of all imported templates that do not yet have that prefix. + required: false + type: str + associate: + description: + - Associate to Operatingsystems, Locations and Organizations based on metadata. + required: false + type: str + choices: + - always + - new + - never + verbose: + description: + - Add template reports to the output. + required: false + type: bool + force: + description: + - Update templates that are locked. + required: false + type: bool + lock: + description: + - Lock imported templates. + required: false + type: bool + branch: + description: + - Branch of the I(repo). Only for git-based repositories. + required: false + type: str + repo: + description: + - Filesystem path or repo (with protocol), for example /tmp/dir or git://example.com/repo.git or https://example.com/repo.git. + required: false + type: str + filter: + description: + - Sync only templates with name matching this regular expression, after I(prefix) was applied. + - Case-insensitive, snippets are not filtered. + required: false + type: str + negate: + description: + - Negate the filter condition. + required: false + type: bool + dirname: + description: + - The directory within Git repo containing the templates. + required: false + type: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.taxonomy +''' + +EXAMPLES = ''' +- name: Sync templates from git repo + theforeman.foreman.templates_import: + repo: https://github.com/theforeman/community-templates.git + branch: 1.24-stable + associate: new + server_url: "https://foreman.example.com" + username: "admin" + password: "changeme" +''' + +RETURN = ''' +message: + description: Information about the import. + returned: success + type: dict + contains: + repo: + description: Repository, the templates were imported from. + type: str + branch: + description: Branch used in the repository. + type: str +report: + description: Report of the import. + returned: success + type: dict + contains: + changed: + description: List of templates that have been updated. + type: list + new: + description: List of templates that have been created. + type: list +templates: + description: Final state of the templates. + returned: success + type: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanTaxonomicAnsibleModule, _flatten_entity + + +def main(): + module = ForemanTaxonomicAnsibleModule( + foreman_spec=dict( + associate=dict(choices=['always', 'new', 'never']), + prefix=dict(), + branch=dict(), + repo=dict(), + filter=dict(), + dirname=dict(), + verbose=dict(type='bool'), + force=dict(type='bool'), + lock=dict(type='bool'), + negate=dict(type='bool'), + ), + supports_check_mode=False, + required_plugins=[('templates', ['*'])], + ) + + with module.api_connection(): + + module.auto_lookup_entities() + + # Build a list of all existing templates of all supported types to check if we are adding any new + template_report = [] + + template_types = ['provisioning_templates', 'report_templates', 'ptables'] + if 'job_templates' in module.foremanapi.resources: + template_types.append('job_templates') + + for template_type in template_types: + template_report += [(resource['name'], resource['id']) for resource in module.list_resource(template_type)] + + result = module.resource_action('templates', 'import', record_change=False, params=_flatten_entity(module.foreman_params, module.foreman_spec)) + msg_templates = result['message'].pop('templates', []) + + report = {'changed': [], 'new': []} + templates = {} + + for template in msg_templates: + if template['changed']: + report['changed'].append(template['name']) + module.set_changed() + elif template['imported']: + if (template['name'], template['id']) not in template_report: + report['new'].append(template['name']) + module.set_changed() + templates[template.pop('name')] = template + + module.exit_json(templates=templates, message=result['message'], report=report) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/user.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/user.py new file mode 100644 index 00000000..5ef6ee38 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/user.py @@ -0,0 +1,547 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Christoffer Reijer (Basalt AB) +# +# 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: user +version_added: 1.0.0 +short_description: Manage Users +description: + - Create, update, and delete users +author: + - "Christoffer Reijer (@ephracis) Basalt AB" +options: + login: + aliases: + - name + description: + - Name of the user + required: true + type: str + firstname: + description: + - First name of the user + required: false + type: str + lastname: + description: + - Last name of the user + required: false + type: str + mail: + description: + - Email address of the user + - Required when creating a new user + required: false + type: str + description: + description: + - Description of the user + required: false + type: str + admin: + description: + - Whether or not the user is an administrator + required: false + default: false + type: bool + user_password: + description: + - Password for the user + required: false + type: str + default_location: + description: + - The location that the user uses by default + required: false + type: str + default_organization: + description: + - The organizxation that the user uses by default + required: false + type: str + auth_source: + description: + - Authentication source where the user exists + required: false + type: str + timezone: + description: + - Timezone for the user + - If blank it will use the browser timezone. + required: false + type: str + choices: + - 'International Date Line West' + - 'American Samoa' + - 'Midway Island' + - 'Hawaii' + - 'Alaska' + - 'Pacific Time (US & Canada)' + - 'Tijuana' + - 'Arizona' + - 'Chihuahua' + - 'Mazatlan' + - 'Mountain Time (US & Canada)' + - 'Central America' + - 'Central Time (US & Canada)' + - 'Guadalajara' + - 'Mexico City' + - 'Monterrey' + - 'Saskatchewan' + - 'Bogota' + - 'Eastern Time (US & Canada)' + - 'Indiana (East)' + - 'Lima' + - 'Quito' + - 'Atlantic Time (Canada)' + - 'Caracas' + - 'Georgetown' + - 'La Paz' + - 'Puerto Rico' + - 'Santiago' + - 'Newfoundland' + - 'Brasilia' + - 'Buenos Aires' + - 'Greenland' + - 'Montevideo' + - 'Mid-Atlantic' + - 'Azores' + - 'Cape Verde Is.' + - 'Dublin' + - 'Edinburgh' + - 'Lisbon' + - 'London' + - 'Monrovia' + - 'UTC' + - 'Amsterdam' + - 'Belgrade' + - 'Berlin' + - 'Bern' + - 'Bratislava' + - 'Brussels' + - 'Budapest' + - 'Casablanca' + - 'Copenhagen' + - 'Ljubljana' + - 'Madrid' + - 'Paris' + - 'Prague' + - 'Rome' + - 'Sarajevo' + - 'Skopje' + - 'Stockholm' + - 'Vienna' + - 'Warsaw' + - 'West Central Africa' + - 'Zagreb' + - 'Zurich' + - 'Athens' + - 'Bucharest' + - 'Cairo' + - 'Harare' + - 'Helsinki' + - 'Jerusalem' + - 'Kaliningrad' + - 'Kyiv' + - 'Pretoria' + - 'Riga' + - 'Sofia' + - 'Tallinn' + - 'Vilnius' + - 'Baghdad' + - 'Istanbul' + - 'Kuwait' + - 'Minsk' + - 'Moscow' + - 'Nairobi' + - 'Riyadh' + - 'St. Petersburg' + - 'Tehran' + - 'Abu Dhabi' + - 'Baku' + - 'Muscat' + - 'Samara' + - 'Tbilisi' + - 'Volgograd' + - 'Yerevan' + - 'Kabul' + - 'Ekaterinburg' + - 'Islamabad' + - 'Karachi' + - 'Tashkent' + - 'Chennai' + - 'Kolkata' + - 'Mumbai' + - 'New Delhi' + - 'Sri Jayawardenepura' + - 'Kathmandu' + - 'Almaty' + - 'Astana' + - 'Dhaka' + - 'Urumqi' + - 'Rangoon' + - 'Bangkok' + - 'Hanoi' + - 'Jakarta' + - 'Krasnoyarsk' + - 'Novosibirsk' + - 'Beijing' + - 'Chongqing' + - 'Hong Kong' + - 'Irkutsk' + - 'Kuala Lumpur' + - 'Perth' + - 'Singapore' + - 'Taipei' + - 'Ulaanbaatar' + - 'Osaka' + - 'Sapporo' + - 'Seoul' + - 'Tokyo' + - 'Yakutsk' + - 'Adelaide' + - 'Darwin' + - 'Brisbane' + - 'Canberra' + - 'Guam' + - 'Hobart' + - 'Melbourne' + - 'Port Moresby' + - 'Sydney' + - 'Vladivostok' + - 'Magadan' + - 'New Caledonia' + - 'Solomon Is.' + - 'Srednekolymsk' + - 'Auckland' + - 'Fiji' + - 'Kamchatka' + - 'Marshall Is.' + - 'Wellington' + - 'Chatham Is.' + - "Nuku'alofa" + - 'Samoa' + - 'Tokelau Is.' + locale: + description: + - The language locale for the user + required: false + type: str + choices: + - 'ca' + - 'de' + - 'en' + - 'en_GB' + - 'es' + - 'fr' + - 'gl' + - 'it' + - 'ja' + - 'ko' + - 'nl_NL' + - 'pl' + - 'pt_BR' + - 'ru' + - 'sv_SE' + - 'zh_CN' + - 'zh_TW' + roles: + description: + - List of roles assigned to the user + required: false + type: list + elements: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state + - theforeman.foreman.foreman.taxonomy +''' + +EXAMPLES = ''' +- name: Create a user + theforeman.foreman.user: + name: test + firstname: Test + lastname: Userson + mail: test.userson@example.com + description: Dr. Test Userson + admin: no + user_password: s3cret + default_location: Test Location + default_organization: Test Organization + auth_source: Internal + timezone: Stockholm + locale: sv_SE + roles: + - Manager + locations: + - Test Location + organizations: + - Test Organization + state: present + +- name: Update a user + theforeman.foreman.user: + name: test + firstname: Tester + state: present + +- name: Change password + theforeman.foreman.user: + name: test + user_password: newp@ss + +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + users: + description: List of users. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ( + ForemanTaxonomicEntityAnsibleModule, +) + + +# List of allowed timezones +timezone_list = [ + 'International Date Line West', + 'American Samoa', + 'Midway Island', + 'Hawaii', + 'Alaska', + 'Pacific Time (US & Canada)', + 'Tijuana', + 'Arizona', + 'Chihuahua', + 'Mazatlan', + 'Mountain Time (US & Canada)', + 'Central America', + 'Central Time (US & Canada)', + 'Guadalajara', + 'Mexico City', + 'Monterrey', + 'Saskatchewan', + 'Bogota', + 'Eastern Time (US & Canada)', + 'Indiana (East)', + 'Lima', + 'Quito', + 'Atlantic Time (Canada)', + 'Caracas', + 'Georgetown', + 'La Paz', + 'Puerto Rico', + 'Santiago', + 'Newfoundland', + 'Brasilia', + 'Buenos Aires', + 'Greenland', + 'Montevideo', + 'Mid-Atlantic', + 'Azores', + 'Cape Verde Is.', + 'Dublin', + 'Edinburgh', + 'Lisbon', + 'London', + 'Monrovia', + 'UTC', + 'Amsterdam', + 'Belgrade', + 'Berlin', + 'Bern', + 'Bratislava', + 'Brussels', + 'Budapest', + 'Casablanca', + 'Copenhagen', + 'Ljubljana', + 'Madrid', + 'Paris', + 'Prague', + 'Rome', + 'Sarajevo', + 'Skopje', + 'Stockholm', + 'Vienna', + 'Warsaw', + 'West Central Africa', + 'Zagreb', + 'Zurich', + 'Athens', + 'Bucharest', + 'Cairo', + 'Harare', + 'Helsinki', + 'Jerusalem', + 'Kaliningrad', + 'Kyiv', + 'Pretoria', + 'Riga', + 'Sofia', + 'Tallinn', + 'Vilnius', + 'Baghdad', + 'Istanbul', + 'Kuwait', + 'Minsk', + 'Moscow', + 'Nairobi', + 'Riyadh', + 'St. Petersburg', + 'Tehran', + 'Abu Dhabi', + 'Baku', + 'Muscat', + 'Samara', + 'Tbilisi', + 'Volgograd', + 'Yerevan', + 'Kabul', + 'Ekaterinburg', + 'Islamabad', + 'Karachi', + 'Tashkent', + 'Chennai', + 'Kolkata', + 'Mumbai', + 'New Delhi', + 'Sri Jayawardenepura', + 'Kathmandu', + 'Almaty', + 'Astana', + 'Dhaka', + 'Urumqi', + 'Rangoon', + 'Bangkok', + 'Hanoi', + 'Jakarta', + 'Krasnoyarsk', + 'Novosibirsk', + 'Beijing', + 'Chongqing', + 'Hong Kong', + 'Irkutsk', + 'Kuala Lumpur', + 'Perth', + 'Singapore', + 'Taipei', + 'Ulaanbaatar', + 'Osaka', + 'Sapporo', + 'Seoul', + 'Tokyo', + 'Yakutsk', + 'Adelaide', + 'Darwin', + 'Brisbane', + 'Canberra', + 'Guam', + 'Hobart', + 'Melbourne', + 'Port Moresby', + 'Sydney', + 'Vladivostok', + 'Magadan', + 'New Caledonia', + 'Solomon Is.', + 'Srednekolymsk', + 'Auckland', + 'Fiji', + 'Kamchatka', + 'Marshall Is.', + 'Wellington', + 'Chatham Is.', + "Nuku'alofa", + 'Samoa', + 'Tokelau Is.', +] + +# List of allowed locales +locale_list = [ + 'ca', + 'de', + 'en', + 'en_GB', + 'es', + 'fr', + 'gl', + 'it', + 'ja', + 'ko', + 'nl_NL', + 'pl', + 'pt_BR', + 'ru', + 'sv_SE', + 'zh_CN', + 'zh_TW', +] + + +class ForemanUserModule(ForemanTaxonomicEntityAnsibleModule): + pass + + +def main(): + module = ForemanUserModule( + foreman_spec=dict( + login=dict(required=True, aliases=['name']), + firstname=dict(required=False), + lastname=dict(required=False), + mail=dict(required=False), + description=dict(required=False), + admin=dict(required=False, type='bool', default=False), + user_password=dict(required=False, no_log=True, flat_name='password'), + default_location=dict(required=False, type='entity', resource_type='locations'), + default_organization=dict(required=False, type='entity', resource_type='organizations'), + auth_source=dict(required=False, type='entity'), + timezone=dict(required=False, choices=timezone_list), + locale=dict(required=False, choices=locale_list), + roles=dict(required=False, type='entity_list'), + ), + entity_key='login', + ) + + with module.api_connection(): + entity = module.lookup_entity('entity') + + if not module.desired_absent: + if 'mail' not in module.foreman_params: + if not entity: + module.fail_json(msg="The 'mail' parameter is required when creating a new user.") + else: + module.foreman_params['mail'] = entity['mail'] + + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/usergroup.py b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/usergroup.py new file mode 100644 index 00000000..49013ae8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/usergroup.py @@ -0,0 +1,124 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2019 Baptiste AGASSE (baptiste.agasse@gmail.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: usergroup +version_added: 1.0.0 +short_description: Manage User Groups +description: + - Create, update, and delete user groups +author: + - "Baptiste Agasse (@bagasse)" +options: + name: + description: + - Name of the group + required: true + type: str + updated_name: + description: + - New user group name. When this parameter is set, the module will not be idempotent. + required: false + type: str + admin: + description: + - Whether or not the users in this group are administrators + required: false + default: false + type: bool + roles: + description: + - List of roles assigned to the group + required: false + type: list + elements: str + users: + description: + - List of users assigned to the group + required: false + type: list + elements: str + usergroups: + description: + - List of other groups assigned to the group + required: false + type: list + elements: str +extends_documentation_fragment: + - theforeman.foreman.foreman + - theforeman.foreman.foreman.entity_state +''' + +EXAMPLES = ''' +- name: Create a user group + theforeman.foreman.usergroup: + name: test + admin: no + roles: + - Manager + users: + - myuser1 + - myuser2 + usergroups: + - mynestedgroup + state: present +''' + +RETURN = ''' +entity: + description: Final state of the affected entities grouped by their type. + returned: success + type: dict + contains: + usergroups: + description: List of usergroups. + type: list + elements: dict +''' + +from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import ForemanEntityAnsibleModule + + +class ForemanUsergroupModule(ForemanEntityAnsibleModule): + pass + + +def main(): + module = ForemanUsergroupModule( + argument_spec=dict( + updated_name=dict(), + ), + foreman_spec=dict( + name=dict(required=True), + admin=dict(required=False, type='bool', default=False), + users=dict(required=False, type='entity_list'), + usergroups=dict(required=False, type='entity_list'), + roles=dict(required=False, type='entity_list'), + ), + ) + + with module.api_connection(): + module.run() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/requirements.txt b/collections-debian-merged/ansible_collections/theforeman/foreman/requirements.txt new file mode 100644 index 00000000..bcdcbab8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/requirements.txt @@ -0,0 +1,3 @@ +requests>=2.4.2 +ipaddress; python_version < '3.3' +PyYAML diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/README.md b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/README.md new file mode 100644 index 00000000..94c806c1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/README.md @@ -0,0 +1,32 @@ +theforeman.foreman.content_view_version_cleanup +=============================================== + +Clean up unused Content View Versions. + +Role Variables +-------------- + +This role supports the [Common Role Variables](https://github.com/theforeman/foreman-ansible-modules/blob/develop/README.md#common-role-variables). + +### Required + +- `content_view_version_cleanup_keep`: How many unused versions to keep. + +### Optional + +- `content_view_version_cleanup_search`: Limit the cleaned content views using a search string (example: `name ~ SOE`). + +Example Playbook +---------------- + +```yaml +- hosts: localhost + roles: + - role: theforeman.foreman.content_view_version_cleanup + vars: + server_url: https://foreman.example.com + username: "admin" + password: "changeme" + organization: "Default Organization" + content_view_version_cleanup_keep: 10 +``` diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/tasks/delete_cv_versions.yml b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/tasks/delete_cv_versions.yml new file mode 100644 index 00000000..9dcac260 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/tasks/delete_cv_versions.yml @@ -0,0 +1,14 @@ +--- +- name: "delete content view versions of {{ cv_name }}" + theforeman.foreman.content_view_version: + server_url: "{{ server_url | default(omit) }}" + username: "{{ username | default(omit) }}" + password: "{{ password | default(omit) }}" + validate_certs: "{{ validate_certs | default(omit) }}" + organization: "{{ organization }}" + content_view: "{{ cv_name }}" + version: "{{ cv_version }}" + state: absent + loop: "{{ cv_versions }}" + loop_control: + loop_var: "cv_version" diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/tasks/find_and_delete_unused_cv_versions.yml b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/tasks/find_and_delete_unused_cv_versions.yml new file mode 100644 index 00000000..f92dcf09 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/tasks/find_and_delete_unused_cv_versions.yml @@ -0,0 +1,20 @@ +--- +- name: "find content view versions of {{ cv.name }}" + theforeman.foreman.resource_info: + server_url: "{{ server_url | default(omit) }}" + username: "{{ username | default(omit) }}" + password: "{{ password | default(omit) }}" + validate_certs: "{{ validate_certs | default(omit) }}" + organization: "{{ organization }}" + resource: content_view_versions + params: + content_view_id: "{{ cv.id }}" + register: versions + +- name: "delete unused content view versions of {{ cv.name }}" + include_tasks: delete_cv_versions.yml + vars: + cv_name: "{{ cv.name }}" + cv_versions: "{{ (versions.resources | rejectattr('environments') | rejectattr('composite_content_view_ids') | + rejectattr('published_in_composite_content_view_ids') | map(attribute='version') | map('float') | sort | + map('string') | list )[:-content_view_version_cleanup_keep] }}" diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/tasks/main.yml b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/tasks/main.yml new file mode 100644 index 00000000..f00faed7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/tasks/main.yml @@ -0,0 +1,30 @@ +--- +- name: "find all content views" + theforeman.foreman.resource_info: + server_url: "{{ server_url | default(omit) }}" + username: "{{ username | default(omit) }}" + password: "{{ password | default(omit) }}" + validate_certs: "{{ validate_certs | default(omit) }}" + organization: "{{ organization }}" + resource: content_views + search: "{{ content_view_version_cleanup_search | default(omit) }}" + register: all_cvs + +- name: "delete unused composite content view versions" + include_tasks: delete_cv_versions.yml + vars: + cv_name: "{{ ccv.name }}" + cv_versions: "{{ (ccv.versions | rejectattr('environment_ids') | map(attribute='version') | list)[:-content_view_version_cleanup_keep] }}" + loop: "{{ all_cvs.resources | selectattr('composite') | list }}" + loop_control: + label: "{{ ccv.label }}" + loop_var: "ccv" + when: (ccv.versions | rejectattr('environment_ids') | map(attribute='version') | list)[:-content_view_version_cleanup_keep] + +- name: "find and delete unused content view versions" + include_tasks: find_and_delete_unused_cv_versions.yml + loop: "{{ all_cvs.resources | rejectattr('composite') | list }}" + loop_control: + label: "{{ cv.label }}" + loop_var: "cv" + when: (cv.versions | rejectattr('environment_ids') | map(attribute='version') | list)[:-content_view_version_cleanup_keep] diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/roles/manifest/README.md b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/manifest/README.md new file mode 100644 index 00000000..047e10f3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/manifest/README.md @@ -0,0 +1,50 @@ +theforeman.foreman.manifest +=========================== + +Upload Subscription Manifest + +Role Variables +-------------- + +This role supports the [Common Role Variables](https://github.com/theforeman/foreman-ansible-modules/blob/develop/README.md#common-role-variables). + +- `manifest_path`: Path to subscription Manifest file on Ansible target host. When using `manifest_download`, it is first downloaded to this location from the Red Hat Customer Portal before being uploaded to the Foreman server. +- `manifest_download`: Whether to first download the Manifest from the Red Hat Customer Portal. Defaults to `False`. +- `manifest_uuid`: UUID of the Manifest to download, corresponding to a [Subscription Allocation](https://access.redhat.com/management/subscription_allocations) defined on your Red Hat account. Required when `manifest_download` is `True`. +- `rhsm_username`: Your username for the Red Hat Customer Portal. Required when `manifest_download` is `True`. +- `rhsm_password`: Your password for the Red Hat Customer Portal. Required when `manifest_download` is `True`. + +Example Playbooks +----------------- + +Use a Subscription Manifest which has already been downloaded on localhost at `~/manifest.zip`: + +```yaml +- hosts: localhost + roles: + - role: theforeman.foreman.manifest + vars: + server_url: https://foreman.example.com + username: "admin" + password: "changeme" + organization: "Default Organization" + manifest_path: "~/manifest.zip" +``` + +Download the Subscription Manifest from the Red Hat Customer Portal to localhost before uploading to Foreman server: + +```yaml +- hosts: localhost + roles: + - role: theforeman.foreman.manifest + vars: + server_url: https://foreman.example.com + username: "admin" + password: "changeme" + organization: "Default Organization" + manifest_path: "~/manifest.zip" + manifest_download: True + rhsm_username: "happycustomer" + rhsm_password: "$ecur3p4$$w0rd" + manifest_uuid: "01234567-89ab-cdef-0123-456789abcdef" +``` diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/roles/manifest/defaults/main.yml b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/manifest/defaults/main.yml new file mode 100644 index 00000000..60f13f55 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/manifest/defaults/main.yml @@ -0,0 +1,2 @@ +--- +manifest_download: false diff --git a/collections-debian-merged/ansible_collections/theforeman/foreman/roles/manifest/tasks/main.yml b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/manifest/tasks/main.yml new file mode 100644 index 00000000..ce176244 --- /dev/null +++ b/collections-debian-merged/ansible_collections/theforeman/foreman/roles/manifest/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Download Subscription Manifest from Red Hat Customer Portal + theforeman.foreman.redhat_manifest: + uuid: "{{ manifest_uuid }}" + username: "{{ rhsm_username }}" + password: "{{ rhsm_password }}" + path: "{{ manifest_path }}" + when: manifest_download + +- name: Upload Subscription Manifest to Foreman + theforeman.foreman.subscription_manifest: + username: "{{ username }}" + password: "{{ password }}" + server_url: "{{ server_url }}" + validate_certs: "{{ validate_certs | default(omit) }}" + organization: "{{ organization }}" + manifest_path: "{{ manifest_path }}" + state: present |