summaryrefslogtreecommitdiffstats
path: root/collections-debian-merged/ansible_collections/theforeman
diff options
context:
space:
mode:
Diffstat (limited to 'collections-debian-merged/ansible_collections/theforeman')
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/CHANGELOG.rst220
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/FILES.json649
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/LICENSE674
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/MANIFEST.json92
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/README.md93
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/changelogs/changelog.yaml340
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/meta/runtime.yml108
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/callback/foreman.py258
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/doc_fragments/foreman.py320
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/filter/foreman.py24
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/inventory/foreman.py643
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/module_utils/_apypie.py907
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/module_utils/foreman_helper.py1704
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/activation_key.py380
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/architecture.py122
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/auth_source_ldap.py212
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/bookmark.py157
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/compute_attribute.py153
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/compute_profile.py204
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/compute_resource.py406
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/config_group.py97
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_credential.py100
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_upload.py193
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_view.py280
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_view_filter.py329
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/content_view_version.py265
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/domain.py112
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/external_usergroup.py123
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/global_parameter.py164
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/hardware_model.py102
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/host.py505
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/host_collection.py100
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/host_power.py137
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/hostgroup.py190
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/http_proxy.py118
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/image.py135
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/installation_medium.py151
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/job_invocation.py225
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/job_template.py463
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/lifecycle_environment.py118
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/location.py127
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/operatingsystem.py233
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/organization.py98
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/os_default_template.py145
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/partition_table.py296
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/product.py144
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/provisioning_template.py344
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/puppet_environment.py91
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/realm.py102
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/redhat_manifest.py329
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/repository.py337
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/repository_set.py338
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/repository_sync.py89
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/resource_info.py169
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/role.py146
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scap_content.py126
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scap_tailoring_file.py125
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scc_account.py180
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/scc_product.py84
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/setting.py112
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/smart_class_parameter.py273
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/smart_proxy.py166
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/snapshot.py138
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/status_info.py76
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/subnet.py285
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/subscription_manifest.py134
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/sync_plan.py180
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/templates_import.py193
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/user.py547
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/plugins/modules/usergroup.py124
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/requirements.txt3
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/README.md32
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/tasks/delete_cv_versions.yml14
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/tasks/find_and_delete_unused_cv_versions.yml20
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/roles/content_view_version_cleanup/tasks/main.yml30
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/roles/manifest/README.md50
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/roles/manifest/defaults/main.yml2
-rw-r--r--collections-debian-merged/ansible_collections/theforeman/foreman/roles/manifest/tasks/main.yml18
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