summaryrefslogtreecommitdiffstats
path: root/collections-debian-merged/ansible_collections/awx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-14 20:03:01 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-14 20:03:01 +0000
commita453ac31f3428614cceb99027f8efbdb9258a40b (patch)
treef61f87408f32a8511cbd91799f9cececb53e0374 /collections-debian-merged/ansible_collections/awx
parentInitial commit. (diff)
downloadansible-a453ac31f3428614cceb99027f8efbdb9258a40b.tar.xz
ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.zip
Adding upstream version 2.10.7+merged+base+2.10.8+dfsg.upstream/2.10.7+merged+base+2.10.8+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'collections-debian-merged/ansible_collections/awx')
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/COPYING674
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/FILES.json1419
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/MANIFEST.json36
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/README.md135
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/meta/runtime.yml15
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/doc_fragments/auth.py61
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/doc_fragments/auth_legacy.py52
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/doc_fragments/auth_plugin.py48
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/inventory/tower.py174
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/lookup/tower_api.py196
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/lookup/tower_schedule_rrule.py248
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_api.py583
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_awxkit.py53
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_legacy.py117
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_module.py239
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/__init__.py0
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_credential.py427
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_credential_input_source.py129
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_credential_type.py146
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_export.py166
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_group.py156
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_host.py134
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_import.py105
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_inventory.py138
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_inventory_source.py277
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_cancel.py99
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_launch.py227
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_list.py126
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_template.py526
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_wait.py186
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_label.py105
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_license.py81
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_meta.py84
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_notification.py427
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_organization.py174
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_project.py313
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_receive.py199
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_role.py186
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_schedule.py243
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_send.py174
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_settings.py176
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_team.py114
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_token.py201
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_user.py169
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_job_template.py273
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_job_template_node.py271
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_launch.py206
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_template.py230
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/requirements.txt3
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/setup.cfg3
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/conftest.py289
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_credential.py178
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_credential_input_source.py333
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_credential_type.py49
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_group.py117
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_inventory.py60
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_inventory_source.py256
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_job.py55
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_job_template.py217
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_label.py47
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_module_utils.py104
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_notification.py111
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_organization.py60
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_project.py33
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_role.py64
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_schedule.py107
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_send_receive.py76
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_settings.py67
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_team.py66
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_token.py29
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_user.py46
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_workflow_job_template.py146
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_workflow_job_template_node.py128
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/test/awx/test_workflow_template.py130
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/demo_data/tasks/main.yml33
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_credential/tasks/main.yml754
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_credential_input_source/tasks/main.yml105
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_credential_type/tasks/main.yml27
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_export/aliases1
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_export/tasks/main.yml77
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_group/tasks/main.yml98
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_host/tasks/main.yml49
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_import/aliases1
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_import/tasks/main.yml108
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_inventory/tasks/main.yml101
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_inventory_source/tasks/main.yml82
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_cancel/tasks/main.yml40
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_launch/tasks/main.yml136
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_list/tasks/main.yml38
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_template/tasks/main.yml378
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_wait/tasks/main.yml137
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_label/tasks/main.yml24
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml238
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_notification/tasks/main.yml230
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_organization/tasks/main.yml98
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_project/tasks/main.yml225
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml58
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_project_manual/tasks/main.yml37
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_role/tasks/main.yml74
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_schedule/tasks/main.yml88
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_schedule_rrule/tasks/main.yml50
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_settings/tasks/main.yml87
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_team/tasks/main.yml53
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_token/tasks/main.yml110
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_user/tasks/main.yml120
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_workflow_job_template/tasks/main.yml276
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_workflow_launch/tasks/main.yml91
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/sanity/ignore-2.10.txt6
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tests/sanity/ignore-2.9.txt6
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tools/generate.yml21
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tools/roles/generate/tasks/main.yml64
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tools/roles/generate/templates/tower_module.j2222
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tools/roles/template_galaxy/tasks/main.yml56
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tools/roles/template_galaxy/templates/README.md.j2146
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tools/roles/template_galaxy/templates/galaxy.yml.j238
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tools/template_galaxy.yml40
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tools/vars/aliases.yml29
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tools/vars/associations.yml13
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tools/vars/examples.yml52
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tools/vars/generate_for.yml16
-rw-r--r--collections-debian-merged/ansible_collections/awx/awx/tools/vars/resolution.yml6
121 files changed, 18031 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/awx/awx/COPYING b/collections-debian-merged/ansible_collections/awx/awx/COPYING
new file mode 100644
index 00000000..b743e04e
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/COPYING
@@ -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/awx/awx/FILES.json b/collections-debian-merged/ansible_collections/awx/awx/FILES.json
new file mode 100644
index 00000000..6156d893
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/FILES.json
@@ -0,0 +1,1419 @@
+{
+ "files": [
+ {
+ "format": 1,
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": ".",
+ "chksum_type": null
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "7c50cd9b85e2b7eebaea2b5618b402862b01d5a66befff8e41401ef3f14e471a",
+ "name": "COPYING",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "971ddb2ef90aabfb6d49519e15688181f41a8abd580f4fe9d6ac430d1039a9b2",
+ "name": "README.md",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "meta",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "ddf9dd01549307a457f256c839eb76b271472362466c29c9dcf3776a863591ab",
+ "name": "meta/runtime.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "plugins",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "plugins/doc_fragments",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "aaaf77e111937fb586f689d18b41f8c5a3d656390645d7617e798e031d31b0e6",
+ "name": "plugins/doc_fragments/auth.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "740c0c506c28ee8693546638aca964ccc3ed332446a5574a8a213716d6acebcb",
+ "name": "plugins/doc_fragments/auth_legacy.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "526100f565401e2811e049e966d0c9afcc0102c9b0b8c62ba39cded962bc5222",
+ "name": "plugins/doc_fragments/auth_plugin.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "plugins/inventory",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "7fb770eacf91c4fdc0ac775992177ee5b4a5c06aaec433498b5714ae2e440729",
+ "name": "plugins/inventory/tower.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "plugins/lookup",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "b00fe585d53b403723975974ab2ef744ea9104bd72df1b62b814a96d38d9148d",
+ "name": "plugins/lookup/tower_api.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "9b23ead83ef4a9e98c0c47b4491dbec8677902ef39452f184cc695e6d71886f1",
+ "name": "plugins/lookup/tower_schedule_rrule.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "plugins/module_utils",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "9fce90ba980270326df6c28d269f8bdf36bb3a9d23412c808c05bc76efe181e4",
+ "name": "plugins/module_utils/tower_api.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "bc4c977bb08d5a815d72f1dfe3bba2e8ec6d322233c638e919bfb4f340379ee2",
+ "name": "plugins/module_utils/tower_awxkit.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "162ff0173de655277cc6eeb02ae0481fcab98a5da1705d6d2963b822be44f0fd",
+ "name": "plugins/module_utils/tower_legacy.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "201f0e767a1c03a5ed68b582a4a2b5d72181e3adb96ddf18dade169dbd052920",
+ "name": "plugins/module_utils/tower_module.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "plugins/modules",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "name": "plugins/modules/__init__.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "645f3d07b54820a9471dace5a04e1e376c3c9fc30424531253e5d9b992ce1d12",
+ "name": "plugins/modules/tower_credential.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "3d198d9ed6a981137e40f6fd8f88fe7f51e666e59588ed7b2eeda8302c92d7a3",
+ "name": "plugins/modules/tower_credential_input_source.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "97fe4bc8a2dba7699751c68219bbaa7f49d92f222ebbebe6d9a3a6c0e66a86b7",
+ "name": "plugins/modules/tower_credential_type.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "368613ada4bb4a4a06fa8041e18ae8d99caea041b4aace9dc5cb447a61e1f6f0",
+ "name": "plugins/modules/tower_export.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "54b67799c8eeb585d7c354add356144846276d8993541f8a3b372767300b306a",
+ "name": "plugins/modules/tower_group.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "f3b83b4c793fe1251a0178e0c9fc54227c08ff191fdeca21ffc80f6cb961e688",
+ "name": "plugins/modules/tower_host.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "b1bebd0fe86f8ad9fc371aca1feec6d3949f0c1c6fff72c9ecb742744e5748e4",
+ "name": "plugins/modules/tower_import.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "3ce630f169816d027bc091219cb9e5108fd5ec54e6da5927f0306b5f4ab09f34",
+ "name": "plugins/modules/tower_inventory.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "f5e4f89c29f7f72716486c69db63862a9e6244ffef3508c1fc1fa3242eff0093",
+ "name": "plugins/modules/tower_inventory_source.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "9b333d5dda7aa132e3c487ea79208094859972055c7e6ce6c643f8ddc595cd16",
+ "name": "plugins/modules/tower_job_cancel.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "7798b0f881a292558e7a7d64b2de6556fce146b4f4a354a33387ed0873d25971",
+ "name": "plugins/modules/tower_job_launch.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "2afc1b226b9a3a6fcb58e25b539518dc16a658c235235464457a104832ba8618",
+ "name": "plugins/modules/tower_job_list.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "7afd00e7fdd160402b10ca9e4c8ebe9bd031c54a69a915109dfca90eafb3134c",
+ "name": "plugins/modules/tower_job_template.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "652955a7e41800e45337f458c687e3a5f5dd881e2b502e000644752068d74e77",
+ "name": "plugins/modules/tower_job_wait.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "3f1d8e4ed4d3e029164e59cb8ffda74d240312523c249a1dd3ede149e10b90c3",
+ "name": "plugins/modules/tower_label.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "6ec7cfb6eed6ce2913f2d4dac15e71fe4e0e93aae336c602e64f6ac314bbcd75",
+ "name": "plugins/modules/tower_license.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "da33b9b93cc056cee3d6e4fead16278745a8c968bc707aff7ee2fbf4b89ab075",
+ "name": "plugins/modules/tower_meta.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "b527e9eafc9e87bcc2dfcf88ada303af1723ff72f3c745f596eec590b3764658",
+ "name": "plugins/modules/tower_notification.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "086dcc99240d80d4defc30801469add9ff2344843cc27422a461f8ff544e2110",
+ "name": "plugins/modules/tower_organization.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "692aa22bc72618dcb5b4f630ab1c255b42d8faec8dd983535d53d73a76dc70b2",
+ "name": "plugins/modules/tower_project.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "8468afc15da7465cbf3c72d87e6e1aedbf4268b9bcade28facea7272f0623965",
+ "name": "plugins/modules/tower_receive.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "8193893c71e05ee65f26a0c8f0ed121efe0e554b4525f88b0bdb867518169f47",
+ "name": "plugins/modules/tower_role.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "39890d17bdc03d53cb246fef306e914389b81794c95ebfc06323da3529b5732d",
+ "name": "plugins/modules/tower_schedule.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "14b7227afaadcc2d89bdeed1bc82790bda08eb50ea8084f47088542d965d239d",
+ "name": "plugins/modules/tower_send.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "570dc79d3ce4ba6b60cfd8d6b9edaf3ebfc905701cebe628a018dc64433e4aba",
+ "name": "plugins/modules/tower_settings.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "ff79eadbabd993e0f5d210944045333aed26fba301c61fef2e130e48092fe3d5",
+ "name": "plugins/modules/tower_team.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "232ae8b52e33d38e72327d78892dca561a8c68d7660377f2593b615b51f20f52",
+ "name": "plugins/modules/tower_token.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "a585763e351494021f979b702d1a7e32be7031592e1c360627d88fb1491b1788",
+ "name": "plugins/modules/tower_user.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "c190b0dcfdad7ad31f38707178218804f42576ab2a50dff4b370bcdb38cbaf7d",
+ "name": "plugins/modules/tower_workflow_job_template.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "9a7d6e8bae3a9f0abe264061abb6a58cd7f3e497e7ba61b7e555f2d99af5a7b4",
+ "name": "plugins/modules/tower_workflow_job_template_node.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "94a9c31486a20093e745c2c875872a0b6afe2699b2b954fefb5280c87ce2371a",
+ "name": "plugins/modules/tower_workflow_launch.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "502923a998cfc50d5389279c078ff3a03d55463d095aa2588c2aa481d3fcd7eb",
+ "name": "plugins/modules/tower_workflow_template.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "b4e0a72acdb5b80917bc3af3e9872869d11265e98a6867f0a46664305008bd7d",
+ "name": "requirements.txt",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "05955e13ceb24a7db106aff7d1242bc4ed62f832b13e183731f1fddc0dbf6449",
+ "name": "setup.cfg",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "test",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "test/awx",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "95f3534ff1ec6a31c83f1cff777510f571246d71ed5a2eda85e39ca2088b7ed1",
+ "name": "test/awx/conftest.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "60308ed4fe0f146de32fac0e28d82b259575f9a99feea2f3e7c8e4f3aafef8cf",
+ "name": "test/awx/test_credential.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "e05f99a513e8b869183f37c55030812d11b32a694cf41c1fa17a7f7c554bffbe",
+ "name": "test/awx/test_credential_input_source.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "6a22345c0ac27828fcdd314ab78f8c2e1c05dd59e5aedeee8ab00a149dffc424",
+ "name": "test/awx/test_credential_type.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "5884ef919d19ace125306e02afd9c3e6a7bc2154430027a8fbc31e69b3f15a1b",
+ "name": "test/awx/test_group.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "5c5d11da5c45343357c88274c6431e8cc05caea00e50939f9eb4ebf70486f728",
+ "name": "test/awx/test_inventory.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "7aa431446502d53fd21180cf090341091c8752012b14758a274855794479560f",
+ "name": "test/awx/test_inventory_source.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "92e5a2742224842fbae2314b353a7974cd611d683a304a46b8b67bbb9eb0191d",
+ "name": "test/awx/test_job.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "f9f1456d56ceba5485fb422a5b014ad360cda53a4d4e2316db8f8886c1066208",
+ "name": "test/awx/test_job_template.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "cf6c2eb53bb9813a67c88d03a9c69fe1418e30ec1b4f65e5b97d91020b92b32d",
+ "name": "test/awx/test_label.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "7d856f69a0da54b5882293f3d78a0f38c4ac0a0c93a324d47e21138e7dd9cfaf",
+ "name": "test/awx/test_module_utils.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "08ee57748e7c99b4337731c9573124ad701c8143ab9a145efb4b9c0b405222e0",
+ "name": "test/awx/test_notification.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "8ba6c8395909413b6638583c4f5bc6e927269336e8ef1fb376b3f12be71fee88",
+ "name": "test/awx/test_organization.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "9815c0c98a474118b60ef21a3640111d957e823084686c7a3817351cd625d9fd",
+ "name": "test/awx/test_project.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "65dcfea204f2544de698a5edbc0e41eb260fd460cb256ef6c9f3068c333f6a22",
+ "name": "test/awx/test_role.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "f6bef79884ffe9db7ae581e9392c6b4bd794760fdfc4a4c23e1290994d1ddf12",
+ "name": "test/awx/test_schedule.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "d8e5dd41ca1221ee30ec5e6468a838e90fe041be7bc4740b9228c3ba3c6b0657",
+ "name": "test/awx/test_send_receive.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "10004dff541cd01f1d9f6c2e5ef89d716cd8775b51f97cb9ed40dc839958c823",
+ "name": "test/awx/test_settings.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "9768fd13b29b7c7f79ff98fa48cd9b765d05cda2f39de9a91810a31534aa5198",
+ "name": "test/awx/test_team.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "8d19762c0c8f84b31cb58c43ef699acd4799d57594a3e23dc067ce6373f89e61",
+ "name": "test/awx/test_token.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "067e1e8bc996911aade13805ccd0edbfead9c56bcaf3042c47ef0b5ce9393bc1",
+ "name": "test/awx/test_user.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "baee4c65d178e67a5e36397c947bcbb96fdb15eb655141d6de3757e50cf94245",
+ "name": "test/awx/test_workflow_job_template.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "d7e8d6ecc333aa469ad91174a48fea9f56ea26bb89bc1e876b616a44f4b85d08",
+ "name": "test/awx/test_workflow_job_template_node.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "a4c6141d13ee1cff044e7a4b401faa755a20be98cd85e9a2e4d97af00fb408ae",
+ "name": "test/awx/test_workflow_template.py",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/demo_data",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/demo_data/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "e9266afb6c233a959b22aebb9d644712f5fbd1b821e64f0b636e90e1e9391442",
+ "name": "tests/integration/targets/demo_data/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_credential",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_credential/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "90ce93d3ff55756c991acbed6ef3895665b250a636cabd8395fc6ee681e53d1f",
+ "name": "tests/integration/targets/tower_credential/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_credential_input_source",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_credential_input_source/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "bf92e526888340685b170b2608b2428683596b76d32c12787af023e518ef9024",
+ "name": "tests/integration/targets/tower_credential_input_source/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_credential_type",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_credential_type/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "44d30e6ad90487d741984303474d43d095f494a35f360c1dedd2b67a9068041e",
+ "name": "tests/integration/targets/tower_credential_type/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_export",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "52e1315ef042495cdf2b0ce22d8ba47f726dce15b968e301a795be1f69045f20",
+ "name": "tests/integration/targets/tower_export/aliases",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_export/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "2b09e1a9cc24ce4637d72580f0b70525cb4c9b55b9d1b737a5512b4501558b35",
+ "name": "tests/integration/targets/tower_export/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_group",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_group/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "c95a0700d2196bfede38981422a23328f2ac4718c77c606ae91c5d57f9d92418",
+ "name": "tests/integration/targets/tower_group/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_host",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_host/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "4de178adbcad25f8936c0c65bf082422d7a5e07e718e509b783c003bb2d2f129",
+ "name": "tests/integration/targets/tower_host/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_import",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "52e1315ef042495cdf2b0ce22d8ba47f726dce15b968e301a795be1f69045f20",
+ "name": "tests/integration/targets/tower_import/aliases",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_import/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "a3d018cc57193a4a2d052f244643d05b1905d9f2f926189e0890622a56b603af",
+ "name": "tests/integration/targets/tower_import/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_inventory",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_inventory/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "2351ca1932e4d77756d138bf3424ce4b010c156498184aeb8fbbe154b48866d6",
+ "name": "tests/integration/targets/tower_inventory/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_inventory_source",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_inventory_source/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "3929ccd0ca2946624609370129664d915bbb501eb0f9adba486e608d95dc03cd",
+ "name": "tests/integration/targets/tower_inventory_source/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_job_cancel",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_job_cancel/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "84082b74bb0ae8ddabda3c3e9ff10ecffdd9b7030ae97bab95ae43c39a452a93",
+ "name": "tests/integration/targets/tower_job_cancel/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_job_launch",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_job_launch/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "c94d0f4fe761a75ba34b2a9cb8b63299ccc7a80f9e020e9b2d368a6c378706b5",
+ "name": "tests/integration/targets/tower_job_launch/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_job_list",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_job_list/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "b6ed99f7f196254e088a7c431027f44b550ec8cf4bb0aad6db5ccb80cc0ebeeb",
+ "name": "tests/integration/targets/tower_job_list/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_job_template",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_job_template/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "7c753026216baebc4301276e5569b32f59bcc9a678158dc734fdf8d466c4e1e6",
+ "name": "tests/integration/targets/tower_job_template/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_job_wait",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_job_wait/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "9e496726eec92a1ff679a3bf68ffba4219b76b86e4fe1901b63b323125d5a6a8",
+ "name": "tests/integration/targets/tower_job_wait/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_label",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_label/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "3a27d38eae3669311f69ef9f649780fad79ef27f10114f7383cb9b92f74d1634",
+ "name": "tests/integration/targets/tower_label/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_lookup_api_plugin",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_lookup_api_plugin/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "9e910d52586572d72bf4b2c416df3a7517df31d0ffa1fb1ab5c0bd6da20ac206",
+ "name": "tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_notification",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_notification/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "585c970aa38f77e16abbb18402f2e4357b9ff90d9812f6de4535496afd5c3668",
+ "name": "tests/integration/targets/tower_notification/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_organization",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_organization/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "1abecb4347cfb1b77d8e2b2fe90d64632c7d2e39cefd21318f25d07d91c70e70",
+ "name": "tests/integration/targets/tower_organization/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_project",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_project/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "c456db8e1a6f513a6367c55d11d41d638dd68e99211756cea035c82f8cda7ed1",
+ "name": "tests/integration/targets/tower_project/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_project_manual",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_project_manual/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "218e9fc99f54cb65146cc5c41139165111060e8b721f5567a86409ea2bdcfb51",
+ "name": "tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "190a73a479cd59e17357aa09bc5ca4988c615533d5113560f06d61b64229ae91",
+ "name": "tests/integration/targets/tower_project_manual/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_role",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_role/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "4928fb6ac44c0be4497b1cb3a42b32502b132d92ac739714d1debf49b0349cb2",
+ "name": "tests/integration/targets/tower_role/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_schedule",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_schedule/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "ad0ede2f4a20b061bb8ef3c99fb383fa32113fb4fb363747408393f5ac684bcf",
+ "name": "tests/integration/targets/tower_schedule/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_schedule_rrule",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_schedule_rrule/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "cc0e845e9168ce30e9587cbe702402873f476643fbe5cde3c6fafadcd14a7cf3",
+ "name": "tests/integration/targets/tower_schedule_rrule/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_settings",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_settings/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "8cb8aa8fd34ad4b10fa2a5b10e9feba359c73e414694746e1920ddd8167dc2d3",
+ "name": "tests/integration/targets/tower_settings/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_team",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_team/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "427187eac35d6486853ce358ea3bca7ad5f822c19063d3e593f9c2b03e01fe79",
+ "name": "tests/integration/targets/tower_team/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_token",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_token/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "197e2ec188bd2270b417a8806c98cfa477feecb6c6f9663b10c480f2f3ba91f9",
+ "name": "tests/integration/targets/tower_token/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_user",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_user/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "9b70d3ec85846ced6c006ef90b6eb766313e6762b4f220617cd6655b6194abb0",
+ "name": "tests/integration/targets/tower_user/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_workflow_job_template",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_workflow_job_template/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "22dd3978317b05626e1f2be7410e67043c0c52227f3fa7e321db33016cf088cd",
+ "name": "tests/integration/targets/tower_workflow_job_template/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_workflow_launch",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/integration/targets/tower_workflow_launch/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "f97fddca20e1a459850373b1c4e87fb3da9df4833abb606ba1305b787f6b9b1a",
+ "name": "tests/integration/targets/tower_workflow_launch/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tests/sanity",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "d130191bab94e26ce61f8205a9431946b94235730e5cb8e5c3c0b0728fe965eb",
+ "name": "tests/sanity/ignore-2.10.txt",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "00ee77efbc55acc6500f6052038e79541f6c7c8563d20a7852a5f525f5dcf5d9",
+ "name": "tests/sanity/ignore-2.9.txt",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tools",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "aa0d7cefef5234cf5e143c769fa537c4142d411e815dd2b2599891f137ac2347",
+ "name": "tools/generate.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tools/roles",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tools/roles/generate",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tools/roles/generate/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "3aecc6ba819a7b18e755f80207316a9960c8a8e8ce4eeab7fee798ba91a989fb",
+ "name": "tools/roles/generate/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tools/roles/generate/templates",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "10a53db0de9332b1ba639d9fb7ab5c236df50a57220fcc6f27c44fb2409d49ba",
+ "name": "tools/roles/generate/templates/tower_module.j2",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tools/roles/template_galaxy",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tools/roles/template_galaxy/tasks",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "7a1c0a42110ad8816c45291cd870dfc3c0a8486fa12d0ce11c56b59d465e7328",
+ "name": "tools/roles/template_galaxy/tasks/main.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tools/roles/template_galaxy/templates",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "7caad7492660b716f55b21176b33db1687c34bc6829786400981778db7501a79",
+ "name": "tools/roles/template_galaxy/templates/README.md.j2",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "9d549e115b3fbc22b12cdac959607c73b302183060374bd1be915fd02afd95ec",
+ "name": "tools/roles/template_galaxy/templates/galaxy.yml.j2",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "87328b1102cb42e5c49a439e725eef477d8013fe6a61889b3251be061c9749de",
+ "name": "tools/template_galaxy.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "dir",
+ "chksum_sha256": null,
+ "name": "tools/vars",
+ "chksum_type": null,
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "1b7881b3f397d274bf8758f1df94b82f95dcf2438ae0288c5e59e0a92da0c47a",
+ "name": "tools/vars/aliases.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "47f50cc9015ea57b2217c42d0022476e846acde3c9d3e39745339dc5f08f7ded",
+ "name": "tools/vars/associations.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "c4cfcf4390c7d64356e4ea14521ebda56db71d2a1cb0eebcd83002f4c706d726",
+ "name": "tools/vars/examples.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "2af0e6f768d2c9d05a62eb01b6dc125d1c7d3a7de7f8a7267dfc5bcd307f0773",
+ "name": "tools/vars/generate_for.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ },
+ {
+ "ftype": "file",
+ "chksum_sha256": "17eb9fa13ff5c6d88b48a1ac2dc4c32b58181d4a277cacbb31ad09b25a81d84a",
+ "name": "tools/vars/resolution.yml",
+ "chksum_type": "sha256",
+ "format": 1
+ }
+ ],
+ "format": 1
+} \ No newline at end of file
diff --git a/collections-debian-merged/ansible_collections/awx/awx/MANIFEST.json b/collections-debian-merged/ansible_collections/awx/awx/MANIFEST.json
new file mode 100644
index 00000000..ebf10ad6
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/MANIFEST.json
@@ -0,0 +1,36 @@
+{
+ "collection_info": {
+ "description": "Ansible content that interacts with the AWX or Ansible Tower API.",
+ "repository": "https://github.com/ansible/awx",
+ "tags": [
+ "cloud",
+ "infrastructure",
+ "awx",
+ "ansible",
+ "automation"
+ ],
+ "dependencies": {},
+ "authors": [
+ "AWX Project Contributors <awx-project@googlegroups.com>"
+ ],
+ "issues": "https://github.com/ansible/awx/issues?q=is%3Aissue+label%3Acomponent%3Aawx_collection",
+ "name": "awx",
+ "license": [
+ "GPL-3.0-only"
+ ],
+ "documentation": "https://github.com/ansible/awx/blob/devel/awx_collection/README.md",
+ "namespace": "awx",
+ "version": "14.1.0",
+ "readme": "README.md",
+ "license_file": null,
+ "homepage": "https://www.ansible.com/"
+ },
+ "file_manifest_file": {
+ "format": 1,
+ "ftype": "file",
+ "chksum_sha256": "12c59de0bf1e310de1fec6e5c659b9e4483ba517a7ab02d888ae2027b371f3f9",
+ "name": "FILES.json",
+ "chksum_type": "sha256"
+ },
+ "format": 1
+} \ No newline at end of file
diff --git a/collections-debian-merged/ansible_collections/awx/awx/README.md b/collections-debian-merged/ansible_collections/awx/awx/README.md
new file mode 100644
index 00000000..47b517c6
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/README.md
@@ -0,0 +1,135 @@
+# AWX Ansible Collection
+
+[comment]: # (*******************************************************)
+[comment]: # (* *)
+[comment]: # (* WARNING *)
+[comment]: # (* *)
+[comment]: # (* This file is templated and not to be *)
+[comment]: # (* edited directly! Instead modify: *)
+[comment]: # (* tools/roles/template_galaxy/templates/README.md.j2 *)
+[comment]: # (* *)
+[comment]: # (* Changes to the base README.md file are refreshed *)
+[comment]: # (* upon build of the collection *)
+[comment]: # (*******************************************************)
+
+This Ansible collection allows for easy interaction with an AWX server via Ansible playbooks.
+
+This source for this collection lives in the `awx_collection` folder inside of the
+AWX source.
+The previous home for this collection was inside the folder [lib/ansible/modules/web_infrastructure/ansible_tower](https://github.com/ansible/ansible/tree/stable-2.9/lib/ansible/modules/web_infrastructure/ansible_tower) in the Ansible repo,
+as well as other places for the inventory plugin, module utils, and
+doc fragment.
+
+## Building and Installing
+
+This collection templates the `galaxy.yml` file it uses.
+Run `make build_collection` from the root folder of the AWX source tree.
+This will create the `tar.gz` file inside the `awx_collection` folder
+with the current AWX version, for example: `awx_collection/awx-awx-9.2.0.tar.gz`.
+
+Installing the `tar.gz` involves no special instructions.
+
+## Running
+
+Non-deprecated modules in this collection have no Python requirements, but
+may require the official [AWX CLI](https://docs.ansible.com/ansible-tower/latest/html/towercli/index.html)
+in the future. The `DOCUMENTATION` for each module will report this.
+
+You can specify authentication by a combination of either:
+
+ - host, username, password
+ - host, OAuth2 token
+
+The OAuth2 token is the preferred method. You can obtain a token via the
+AWX CLI [login](https://docs.ansible.com/ansible-tower/latest/html/towercli/reference.html#awx-login)
+command.
+
+These can be specified via (from highest to lowest precedence):
+
+ - direct module parameters
+ - environment variables (most useful when running against localhost)
+ - a config file path specified by the `tower_config_file` parameter
+ - a config file at `~/.tower_cli.cfg`
+ - a config file at `/etc/tower/tower_cli.cfg`
+
+Config file syntax looks like this:
+
+```
+[general]
+host = https://localhost:8043
+verify_ssl = true
+oauth_token = LEdCpKVKc4znzffcpQL5vLG8oyeku6
+```
+
+## Release and Upgrade Notes
+
+Notable releases of the `awx.awx` collection:
+
+ - 7.0.0 is intended to be identical to the content prior to the migration, aside from changes necessary to function as a collection.
+ - 11.0.0 has no non-deprecated modules that depend on the deprecated `tower-cli` [PyPI](https://pypi.org/project/ansible-tower-cli/).
+ - 0.0.1-devel is the version you should see if installing from source, which is intended for development and expected to be unstable.
+
+The following notes are changes that may require changes to playbooks:
+
+ - When a project is created, it will wait for the update/sync to finish by default; this can be turned off with the `wait` parameter, if desired.
+ - Creating a "scan" type job template is no longer supported.
+ - Specifying a custom certificate via the `TOWER_CERTIFICATE` environment variable no longer works.
+ - Type changes of variable fields:
+
+ - `extra_vars` in the `tower_job_launch` module worked with a `list` previously, but now only works with a `dict` type
+ - `extra_vars` in the `tower_workflow_job_template` module worked with a `string` previously but now expects a `dict`
+ - When the `extra_vars` parameter is used with the `tower_job_launch` module, the launch will fail unless `ask_extra_vars` or `survey_enabled` is explicitly set to `True` on the Job Template
+ - The `variables` parameter in the `tower_group`, `tower_host` and `tower_inventory` modules now expects a `dict` type and no longer supports the use of `@` syntax for a file
+
+
+ - Type changes of other types of fields:
+
+ - `inputs` or `injectors` in the `tower_credential_type` module worked with a string previously but now expects a `dict`
+ - `schema` in the `tower_workflow_job_template` module worked with a `string` previously but not expects a `list` of `dict`s
+
+ - `tower_group` used to also service inventory sources, but this functionality has been removed from this module; use `tower_inventory_source` instead.
+ - Specified `tower_config` file used to handle `k=v` pairs on a single line; this is no longer supported. Please use a file formatted as `yaml`, `json` or `ini` only.
+ - Some return values (e.g., `credential_type`) have been removed. Use of `id` is recommended.
+ - `tower_job_template` no longer supports the deprecated `extra_vars_path` parameter, please use `extra_vars` with the lookup plugin to replace this functionality.
+ - The `notification_configuration` parameter of `tower_notification` has changed from a string to a dict. Please use the `lookup` plugin to read an existing file into a dict.
+ - `tower_credential` no longer supports passing a file name to ssh_key_data.
+ - The HipChat `notification_type` has been removed and can no longer be created using the `tower_notification` module.
+
+## Running Unit Tests
+
+Tests to verify compatibility with the most recent AWX code are in `awx_collection/test/awx`.
+These can be ran by `make test_collection` in the development container.
+
+To run outside of the development container, or to run against
+Ansible source, set up a working environment:
+
+```
+mkvirtualenv my_new_venv
+# may need to replace psycopg2 with psycopg2-binary in requirements/requirements.txt
+pip install -r requirements/requirements.txt -r requirements/requirements_dev.txt -r requirements/requirements_git.txt
+make clean-api
+pip install -e <path to your Ansible>
+pip install -e .
+pip install -e awxkit
+py.test awx_collection/test/awx/
+```
+
+## Running Integration Tests
+
+The integration tests require a virtualenv with `ansible` >= 2.9 and `tower_cli`.
+The collection must first be installed, which can be done using `make install_collection`.
+You also need a configuration file, as described in the running section.
+
+Run the tests:
+
+```
+# ansible-test must be run from the directory in which the collection is installed
+cd ~/.ansible/collections/ansible_collections/awx/awx/
+ansible-test integration
+```
+
+## Licensing
+
+All content in this folder is licensed under the same license as Ansible,
+which is the same as license that applied before the split into an
+independent collection.
diff --git a/collections-debian-merged/ansible_collections/awx/awx/meta/runtime.yml b/collections-debian-merged/ansible_collections/awx/awx/meta/runtime.yml
new file mode 100644
index 00000000..3980ccc1
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/meta/runtime.yml
@@ -0,0 +1,15 @@
+---
+plugin_routing:
+ modules:
+ tower_receive:
+ deprecation:
+ removal_date: TBD
+ warning_text: see plugin documentation for details
+ tower_send:
+ deprecation:
+ removal_date: TBD
+ warning_text: see plugin documentation for details
+ tower_workflow_template:
+ deprecation:
+ removal_date: TBD
+ warning_text: see plugin documentation for details
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/doc_fragments/auth.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/doc_fragments/auth.py
new file mode 100644
index 00000000..1e77a63b
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/doc_fragments/auth.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+class ModuleDocFragment(object):
+
+ # Ansible Tower documentation fragment
+ DOCUMENTATION = r'''
+options:
+ tower_host:
+ description:
+ - URL to your Tower or AWX instance.
+ - If value not set, will try environment variable C(TOWER_HOST) and then config files
+ - If value not specified by any means, the value of C(127.0.0.1) will be used
+ type: str
+ tower_username:
+ description:
+ - Username for your Tower or AWX instance.
+ - If value not set, will try environment variable C(TOWER_USERNAME) and then config files
+ type: str
+ tower_password:
+ description:
+ - Password for your Tower or AWX instance.
+ - If value not set, will try environment variable C(TOWER_PASSWORD) and then config files
+ type: str
+ tower_oauthtoken:
+ description:
+ - The Tower OAuth token to use.
+ - This value can be in one of two formats.
+ - A string which is the token itself. (i.e. bqV5txm97wqJqtkxlMkhQz0pKhRMMX)
+ - A dictionary structure as returned by the tower_token module.
+ - If value not set, will try environment variable C(TOWER_OAUTH_TOKEN) and then config files
+ type: raw
+ version_added: "3.7"
+ validate_certs:
+ description:
+ - Whether to allow insecure connections to Tower or AWX.
+ - If C(no), SSL certificates will not be validated.
+ - This should only be used on personally controlled sites using self-signed certificates.
+ - If value not set, will try environment variable C(TOWER_VERIFY_SSL) and then config files
+ type: bool
+ aliases: [ tower_verify_ssl ]
+ tower_config_file:
+ description:
+ - Path to the Tower or AWX config file.
+ - If provided, the other locations for config files will not be considered.
+ type: path
+
+notes:
+- If no I(config_file) is provided we will attempt to use the tower-cli library
+ defaults to find your Tower host information.
+- I(config_file) should contain Tower configuration in the following format
+ host=hostname
+ username=username
+ password=password
+'''
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/doc_fragments/auth_legacy.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/doc_fragments/auth_legacy.py
new file mode 100644
index 00000000..bf0ea288
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/doc_fragments/auth_legacy.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+class ModuleDocFragment(object):
+
+ # Ansible Tower documentation fragment
+ DOCUMENTATION = r'''
+options:
+ tower_host:
+ description:
+ - URL to your Tower or AWX instance.
+ - If value not set, will try environment variable C(TOWER_HOST) and then config files
+ - If value not specified by any means, the value of C(127.0.0.1) will be used
+ type: str
+ tower_username:
+ description:
+ - Username for your Tower or AWX instance.
+ - If value not set, will try environment variable C(TOWER_USERNAME) and then config files
+ type: str
+ tower_password:
+ description:
+ - Password for your Tower or AWX instance.
+ - If value not set, will try environment variable C(TOWER_PASSWORD) and then config files
+ type: str
+ validate_certs:
+ description:
+ - Whether to allow insecure connections to Tower or AWX.
+ - If C(no), SSL certificates will not be validated.
+ - This should only be used on personally controlled sites using self-signed certificates.
+ - If value not set, will try environment variable C(TOWER_VERIFY_SSL) and then config files
+ type: bool
+ aliases: [ tower_verify_ssl ]
+ tower_config_file:
+ description:
+ - Path to the Tower or AWX config file.
+ - If provided, the other locations for config files will not be considered.
+ type: path
+
+notes:
+- If no I(config_file) is provided we will attempt to use the tower-cli library
+ defaults to find your Tower host information.
+- I(config_file) should contain Tower configuration in the following format
+ host=hostname
+ username=username
+ password=password
+'''
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/doc_fragments/auth_plugin.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/doc_fragments/auth_plugin.py
new file mode 100644
index 00000000..527054ed
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/doc_fragments/auth_plugin.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Ansible by Red Hat, Inc
+# 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
+
+
+class ModuleDocFragment(object):
+
+ # Ansible Tower documentation fragment
+ DOCUMENTATION = r'''
+options:
+ host:
+ description: The network address of your Ansible Tower host.
+ env:
+ - name: TOWER_HOST
+ username:
+ description: The user that you plan to use to access inventories on Ansible Tower.
+ env:
+ - name: TOWER_USERNAME
+ password:
+ description: The password for your Ansible Tower user.
+ env:
+ - name: TOWER_PASSWORD
+ oauth_token:
+ description:
+ - The Tower OAuth token to use.
+ env:
+ - name: TOWER_OAUTH_TOKEN
+ verify_ssl:
+ description:
+ - Specify whether Ansible should verify the SSL certificate of Ansible Tower host.
+ - Defaults to True, but this is handled by the shared module_utils code
+ type: bool
+ env:
+ - name: TOWER_VERIFY_SSL
+ aliases: [ validate_certs ]
+
+notes:
+- If no I(config_file) is provided we will attempt to use the tower-cli library
+ defaults to find your Tower host information.
+- I(config_file) should contain Tower configuration in the following format
+ host=hostname
+ username=username
+ password=password
+'''
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/inventory/tower.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/inventory/tower.py
new file mode 100644
index 00000000..7dc4aaa1
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/inventory/tower.py
@@ -0,0 +1,174 @@
+# Copyright (c) 2018 Ansible Project
+# 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
+
+DOCUMENTATION = '''
+name: tower
+plugin_type: inventory
+author:
+ - Matthew Jones (@matburt)
+ - Yunfan Zhang (@YunfanZhang42)
+short_description: Ansible dynamic inventory plugin for Ansible Tower.
+description:
+ - Reads inventories from Ansible Tower.
+ - Supports reading configuration from both YAML config file and environment variables.
+ - If reading from the YAML file, the file name must end with tower.(yml|yaml) or tower_inventory.(yml|yaml),
+ the path in the command would be /path/to/tower_inventory.(yml|yaml). If some arguments in the config file
+ are missing, this plugin will try to fill in missing arguments by reading from environment variables.
+ - If reading configurations from environment variables, the path in the command must be @tower_inventory.
+extends_documentation_fragment: awx.awx.auth_plugin
+options:
+ inventory_id:
+ description:
+ - The ID of the Ansible Tower inventory that you wish to import.
+ - This is allowed to be either the inventory primary key or its named URL slug.
+ - Primary key values will be accepted as strings or integers, and URL slugs must be strings.
+ - Named URL slugs follow the syntax of "inventory_name++organization_name".
+ type: raw
+ env:
+ - name: TOWER_INVENTORY
+ required: True
+ include_metadata:
+ description: Make extra requests to provide all group vars with metadata about the source Ansible Tower host.
+ type: bool
+ default: False
+'''
+
+EXAMPLES = '''
+# Before you execute the following commands, you should make sure this file is in your plugin path,
+# and you enabled this plugin.
+
+# Example for using tower_inventory.yml file
+
+plugin: awx.awx.tower
+host: your_ansible_tower_server_network_address
+username: your_ansible_tower_username
+password: your_ansible_tower_password
+inventory_id: the_ID_of_targeted_ansible_tower_inventory
+# Then you can run the following command.
+# If some of the arguments are missing, Ansible will attempt to read them from environment variables.
+# ansible-inventory -i /path/to/tower_inventory.yml --list
+
+# Example for reading from environment variables:
+
+# Set environment variables:
+# export TOWER_HOST=YOUR_TOWER_HOST_ADDRESS
+# export TOWER_USERNAME=YOUR_TOWER_USERNAME
+# export TOWER_PASSWORD=YOUR_TOWER_PASSWORD
+# export TOWER_INVENTORY=THE_ID_OF_TARGETED_INVENTORY
+# Read the inventory specified in TOWER_INVENTORY from Ansible Tower, and list them.
+# The inventory path must always be @tower_inventory if you are reading all settings from environment variables.
+# ansible-inventory -i @tower_inventory --list
+'''
+
+import os
+
+from ansible.module_utils import six
+from ansible.module_utils._text import to_text, to_native
+from ansible.errors import AnsibleParserError, AnsibleOptionsError
+from ansible.plugins.inventory import BaseInventoryPlugin
+from ansible.config.manager import ensure_type
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def handle_error(**kwargs):
+ raise AnsibleParserError(to_native(kwargs.get('msg')))
+
+
+class InventoryModule(BaseInventoryPlugin):
+ NAME = 'awx.awx.tower' # REPLACE
+ # Stays backward compatible with tower inventory script.
+ # If the user supplies '@tower_inventory' as path, the plugin will read from environment variables.
+ no_config_file_supplied = False
+
+ def verify_file(self, path):
+ if path.endswith('@tower_inventory'):
+ self.no_config_file_supplied = True
+ return True
+ elif super(InventoryModule, self).verify_file(path):
+ return path.endswith(('tower_inventory.yml', 'tower_inventory.yaml', 'tower.yml', 'tower.yaml'))
+ else:
+ return False
+
+ def warn_callback(self, warning):
+ self.display.warning(warning)
+
+ def parse(self, inventory, loader, path, cache=True):
+ super(InventoryModule, self).parse(inventory, loader, path)
+ if not self.no_config_file_supplied and os.path.isfile(path):
+ self._read_config_data(path)
+
+ # Defer processing of params to logic shared with the modules
+ module_params = {}
+ for plugin_param, module_param in TowerAPIModule.short_params.items():
+ opt_val = self.get_option(plugin_param)
+ if opt_val is not None:
+ module_params[module_param] = opt_val
+
+ module = TowerAPIModule(
+ argument_spec={}, direct_params=module_params,
+ error_callback=handle_error, warn_callback=self.warn_callback
+ )
+
+ # validate type of inventory_id because we allow two types as special case
+ inventory_id = self.get_option('inventory_id')
+ if isinstance(inventory_id, int):
+ inventory_id = to_text(inventory_id, nonstring='simplerepr')
+ else:
+ try:
+ inventory_id = ensure_type(inventory_id, 'str')
+ except ValueError as e:
+ raise AnsibleOptionsError(
+ 'Invalid type for configuration option inventory_id, '
+ 'not integer, and cannot convert to string: {err}'.format(err=to_native(e))
+ )
+ inventory_id = inventory_id.replace('/', '')
+ inventory_url = '/api/v2/inventories/{inv_id}/script/'.format(inv_id=inventory_id)
+
+ inventory = module.get_endpoint(
+ inventory_url, data={'hostvars': '1', 'towervars': '1', 'all': '1'}
+ )['json']
+
+ # To start with, create all the groups.
+ for group_name in inventory:
+ if group_name != '_meta':
+ self.inventory.add_group(group_name)
+
+ # Then, create all hosts and add the host vars.
+ all_hosts = inventory['_meta']['hostvars']
+ for host_name, host_vars in six.iteritems(all_hosts):
+ self.inventory.add_host(host_name)
+ for var_name, var_value in six.iteritems(host_vars):
+ self.inventory.set_variable(host_name, var_name, var_value)
+
+ # Lastly, create to group-host and group-group relationships, and set group vars.
+ for group_name, group_content in six.iteritems(inventory):
+ if group_name != 'all' and group_name != '_meta':
+ # First add hosts to groups
+ for host_name in group_content.get('hosts', []):
+ self.inventory.add_host(host_name, group_name)
+ # Then add the parent-children group relationships.
+ for child_group_name in group_content.get('children', []):
+ self.inventory.add_child(group_name, child_group_name)
+ # Set the group vars. Note we should set group var for 'all', but not '_meta'.
+ if group_name != '_meta':
+ for var_name, var_value in six.iteritems(group_content.get('vars', {})):
+ self.inventory.set_variable(group_name, var_name, var_value)
+
+ # Fetch extra variables if told to do so
+ if self.get_option('include_metadata'):
+
+ config_data = module.get_endpoint('/api/v2/config/')['json']
+
+ server_data = {}
+ server_data['license_type'] = config_data.get('license_info', {}).get('license_type', 'unknown')
+ for key in ('version', 'ansible_version'):
+ server_data[key] = config_data.get(key, 'unknown')
+ self.inventory.set_variable('all', 'tower_metadata', server_data)
+
+ # Clean up the inventory.
+ self.inventory.reconcile_inventory()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/lookup/tower_api.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/lookup/tower_api.py
new file mode 100644
index 00000000..76b32be6
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/lookup/tower_api.py
@@ -0,0 +1,196 @@
+# (c) 2020 Ansible Project
+# 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
+
+DOCUMENTATION = """
+lookup: tower_api
+author: John Westcott IV (@john-westcott-iv)
+short_description: Search the API for objects
+requirements:
+ - None
+description:
+ - Returns GET requests from the Ansible Tower API. See
+ U(https://docs.ansible.com/ansible-tower/latest/html/towerapi/index.html) for API usage.
+ - For use that is cross-compatible between the awx.awx and ansible.tower collection
+ see the tower_meta module
+extends_documentation_fragment: awx.awx.auth_plugin
+options:
+ _terms:
+ description:
+ - The endpoint to query, i.e. teams, users, tokens, job_templates, etc.
+ required: True
+ query_params:
+ description:
+ - The query parameters to search for in the form of key/value pairs.
+ type: dict
+ required: False
+ aliases: [query, data, filter, params]
+ expect_objects:
+ description:
+ - Error if the response does not contain either a detail view or a list view.
+ type: boolean
+ default: False
+ aliases: [expect_object]
+ expect_one:
+ description:
+ - Error if the response contains more than one object.
+ type: boolean
+ default: False
+ return_objects:
+ description:
+ - If a list view is returned, promote the list of results to the top-level of list returned.
+ - Allows using this lookup plugin to loop over objects without additional work.
+ type: boolean
+ default: True
+ return_all:
+ description:
+ - If the response is paginated, return all pages.
+ type: boolean
+ default: False
+ return_ids:
+ description:
+ - If response contains objects, promote the id key to the top-level entries in the list.
+ - Allows looking up a related object and passing it as a parameter to another module.
+ - This will convert the return to a string or list of strings depending on the number of selected items.
+ type: boolean
+ aliases: [return_id]
+ default: False
+ max_objects:
+ description:
+ - if C(return_all) is true, this is the maximum of number of objects to return from the list.
+ - If a list view returns more an max_objects an exception will be raised
+ type: integer
+ default: 1000
+
+notes:
+ - If the query is not filtered properly this can cause a performance impact.
+"""
+
+EXAMPLES = """
+- name: Load the UI settings
+ set_fact:
+ tower_settings: "{{ lookup('awx.awx.tower_api', 'settings/ui') }}"
+
+- name: Report the usernames of all users with admin privs
+ debug:
+ msg: "Admin users: {{ query('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true }) | map(attribute='username') | join(', ') }}"
+
+- name: debug all organizations in a loop # use query to return a list
+ debug:
+ msg: "Organization description={{ item['description'] }} id={{ item['id'] }}"
+ loop: "{{ query('awx.awx.tower_api', 'organizations') }}"
+ loop_control:
+ label: "{{ item['name'] }}"
+
+- name: Make sure user 'john' is an org admin of the default org if the user exists
+ tower_role:
+ organization: Default
+ role: admin
+ user: john
+ when: "lookup('awx.awx.tower_api', 'users', query_params={ 'username': 'john' }) | length == 1"
+
+- name: Create an inventory group with all 'foo' hosts
+ tower_group:
+ name: "Foo Group"
+ inventory: "Demo Inventory"
+ hosts: >-
+ {{ query(
+ 'awx.awx.tower_api',
+ 'hosts',
+ query_params={ 'name__startswith' : 'foo', },
+ ) | map(attribute='name') | list }}
+ register: group_creation
+"""
+
+RETURN = """
+_raw:
+ description:
+ - Response from the API
+ type: dict
+ returned: on successful request
+"""
+
+from ansible.plugins.lookup import LookupBase
+from ansible.errors import AnsibleError
+from ansible.module_utils._text import to_native
+from ansible.utils.display import Display
+from ..module_utils.tower_api import TowerAPIModule
+
+
+class LookupModule(LookupBase):
+ display = Display()
+
+ def handle_error(self, **kwargs):
+ raise AnsibleError(to_native(kwargs.get('msg')))
+
+ def warn_callback(self, warning):
+ self.display.warning(warning)
+
+ def run(self, terms, variables=None, **kwargs):
+ if len(terms) != 1:
+ raise AnsibleError('You must pass exactly one endpoint to query')
+
+ # Defer processing of params to logic shared with the modules
+ module_params = {}
+ for plugin_param, module_param in TowerAPIModule.short_params.items():
+ opt_val = self.get_option(plugin_param)
+ if opt_val is not None:
+ module_params[module_param] = opt_val
+
+ # Create our module
+ module = TowerAPIModule(
+ argument_spec={}, direct_params=module_params,
+ error_callback=self.handle_error, warn_callback=self.warn_callback
+ )
+
+ self.set_options(direct=kwargs)
+
+ response = module.get_endpoint(terms[0], data=self.get_option('query_params', {}))
+
+ if 'status_code' not in response:
+ raise AnsibleError("Unclear response from API: {0}".format(response))
+
+ if response['status_code'] != 200:
+ raise AnsibleError("Failed to query the API: {0}".format(response['json'].get('detail', response['json'])))
+
+ return_data = response['json']
+
+ if self.get_option('expect_objects') or self.get_option('expect_one'):
+ if ('id' not in return_data) and ('results' not in return_data):
+ raise AnsibleError(
+ 'Did not obtain a list or detail view at {0}, and '
+ 'expect_objects or expect_one is set to True'.format(terms[0])
+ )
+
+ if self.get_option('expect_one'):
+ if 'results' in return_data and len(return_data['results']) != 1:
+ raise AnsibleError(
+ 'Expected one object from endpoint {0}, '
+ 'but obtained {1} from API'.format(terms[0], len(return_data['results']))
+ )
+
+ if self.get_option('return_all') and 'results' in return_data:
+ if return_data['count'] > self.get_option('max_objects'):
+ raise AnsibleError(
+ 'List view at {0} returned {1} objects, which is more than the maximum allowed '
+ 'by max_objects, {2}'.format(terms[0], return_data['count'], self.get_option('max_objects'))
+ )
+
+ next_page = return_data['next']
+ while next_page is not None:
+ next_response = module.get_endpoint(next_page)
+ return_data['results'] += next_response['json']['results']
+ next_page = next_response['json']['next']
+ return_data['next'] = None
+
+ if self.get_option('return_ids'):
+ if 'results' in return_data:
+ return_data['results'] = [str(item['id']) for item in return_data['results']]
+ elif 'id' in return_data:
+ return_data = str(return_data['id'])
+
+ if self.get_option('return_objects') and 'results' in return_data:
+ return return_data['results']
+ else:
+ return [return_data]
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/lookup/tower_schedule_rrule.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/lookup/tower_schedule_rrule.py
new file mode 100644
index 00000000..918b9fa1
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/lookup/tower_schedule_rrule.py
@@ -0,0 +1,248 @@
+# (c) 2020 Ansible Project
+# 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
+
+DOCUMENTATION = """
+ lookup: tower_schedule_rrule
+ author: John Westcott IV (@john-westcott-iv)
+ short_description: Generate an rrule string which can be used for Tower Schedules
+ requirements:
+ - pytz
+ - python.dateutil >= 2.7.0
+ description:
+ - Returns a string based on criteria which represents an rrule
+ options:
+ _terms:
+ description:
+ - The frequency of the schedule
+ - none - Run this schedule once
+ - minute - Run this schedule every x minutes
+ - hour - Run this schedule every x hours
+ - day - Run this schedule every x days
+ - week - Run this schedule weekly
+ - month - Run this schedule monthly
+ required: True
+ choices: ['none', 'minute', 'hour', 'day', 'week', 'month']
+ start_date:
+ description:
+ - The date to start the rule
+ - Used for all frequencies
+ - Format should be YYYY-MM-DD [HH:MM:SS]
+ type: str
+ timezone:
+ description:
+ - The timezone to use for this rule
+ - Used for all frequencies
+ - Format should be as US/Eastern
+ - Defaults to America/New_York
+ type: str
+ every:
+ description:
+ - The repetition in months, weeks, days hours or minutes
+ - Used for all types except none
+ type: int
+ end_on:
+ description:
+ - How to end this schedule
+ - If this is not defined, this schedule will never end
+ - If this is a positive integer, this schedule will end after this number of occurences
+ - If this is a date in the format YYYY-MM-DD [HH:MM:SS], this schedule ends after this date
+ - Used for all types except none
+ type: str
+ on_days:
+ description:
+ - The days to run this schedule on
+ - A comma-separated list which can contain values sunday, monday, tuesday, wednesday, thursday, friday
+ - Used for week type schedules
+ month_day_number:
+ description:
+ - The day of the month this schedule will run on (0-31)
+ - Used for month type schedules
+ - Cannot be used with on_the parameter
+ type: int
+ on_the:
+ description:
+ - A description on when this schedule will run
+ - Two strings separated by a space
+ - First string is one of first, second, third, fourth, last
+ - Second string is one of sunday, monday, tuesday, wednesday, thursday, friday
+ - Used for month type schedules
+ - Cannot be used with month_day_number parameters
+"""
+
+EXAMPLES = """
+ - name: Create a string for a schedule
+ debug:
+ msg: "{{ query('awx.awx.tower_schedule_rrule', 'none', start_date='1979-09-13 03:45:07') }}"
+"""
+
+RETURN = """
+_raw:
+ description:
+ - String in the rrule format
+ type: string
+"""
+
+from ansible.plugins.lookup import LookupBase
+from ansible.errors import AnsibleError
+from datetime import datetime
+import re
+from distutils.version import LooseVersion
+
+missing_modules = []
+try:
+ import pytz
+except ImportError:
+ missing_modules.append('pytz')
+
+try:
+ from dateutil import rrule
+except ImportError:
+ missing_modules.append('python.dateutil')
+
+# Validate the version of python.dateutil
+try:
+ import dateutil
+ if LooseVersion(dateutil.__version__) < LooseVersion("2.7.0"):
+ raise Exception
+except Exception:
+ missing_modules.append('python.dateutil>=2.7.0')
+
+if len(missing_modules) > 0:
+ raise AnsibleError('You are missing the modules {0}'.format(', '.join(missing_modules)))
+
+
+class LookupModule(LookupBase):
+ frequencies = {
+ 'none': rrule.DAILY,
+ 'minute': rrule.MINUTELY,
+ 'hour': rrule.HOURLY,
+ 'day': rrule.DAILY,
+ 'week': rrule.WEEKLY,
+ 'month': rrule.MONTHLY,
+ }
+
+ weekdays = {
+ 'monday': rrule.MO,
+ 'tuesday': rrule.TU,
+ 'wednesday': rrule.WE,
+ 'thursday': rrule.TH,
+ 'friday': rrule.FR,
+ 'saturday': rrule.SA,
+ 'sunday': rrule.SU,
+ }
+
+ set_positions = {
+ 'first': 1,
+ 'second': 2,
+ 'third': 3,
+ 'fourth': 4,
+ 'last': -1,
+ }
+
+ @staticmethod
+ def parse_date_time(date_string):
+ try:
+ return datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S')
+ except ValueError:
+ return datetime.strptime(date_string, '%Y-%m-%d')
+
+ def run(self, terms, variables=None, **kwargs):
+ if len(terms) != 1:
+ raise AnsibleError('You may only pass one schedule type in at a time')
+
+ frequency = terms[0].lower()
+
+ return self.get_rrule(frequency, kwargs)
+
+ @staticmethod
+ def get_rrule(frequency, kwargs):
+
+ if frequency not in LookupModule.frequencies:
+ raise AnsibleError('Frequency of {0} is invalid'.format(frequency))
+
+ rrule_kwargs = {
+ 'freq': LookupModule.frequencies[frequency],
+ 'interval': kwargs.get('every', 1),
+ }
+
+ # All frequencies can use a start date
+ if 'start_date' in kwargs:
+ try:
+ rrule_kwargs['dtstart'] = LookupModule.parse_date_time(kwargs['start_date'])
+ except Exception:
+ raise AnsibleError('Parameter start_date must be in the format YYYY-MM-DD [HH:MM:SS]')
+
+ # If we are a none frequency we don't need anything else
+ if frequency == 'none':
+ rrule_kwargs['count'] = 1
+ else:
+ # All non-none frequencies can have an end_on option
+ if 'end_on' in kwargs:
+ end_on = kwargs['end_on']
+ if re.match(r'^\d+$', end_on):
+ rrule_kwargs['count'] = end_on
+ else:
+ try:
+ rrule_kwargs['until'] = LookupModule.parse_date_time(end_on)
+ except Exception:
+ raise AnsibleError('Parameter end_on must either be an integer or in the format YYYY-MM-DD [HH:MM:SS]')
+
+ # A week-based frequency can also take the on_days parameter
+ if frequency == 'week' and 'on_days' in kwargs:
+ days = []
+ for day in kwargs['on_days'].split(','):
+ day = day.strip()
+ if day not in LookupModule.weekdays:
+ raise AnsibleError('Parameter on_days must only contain values {0}'.format(', '.join(LookupModule.weekdays.keys())))
+ days.append(LookupModule.weekdays[day])
+
+ rrule_kwargs['byweekday'] = days
+
+ # A month-based frequency can also deal with month_day_number and on_the options
+ if frequency == 'month':
+ if 'month_day_number' in kwargs and 'on_the' in kwargs:
+ raise AnsibleError('Month based frequencies can have month_day_number or on_the but not both')
+
+ if 'month_day_number' in kwargs:
+ try:
+ my_month_day = int(kwargs['month_day_number'])
+ if my_month_day < 1 or my_month_day > 31:
+ raise Exception()
+ except Exception:
+ raise AnsibleError('month_day_number must be between 1 and 31')
+
+ rrule_kwargs['bymonthday'] = my_month_day
+
+ if 'on_the' in kwargs:
+ try:
+ (occurance, weekday) = kwargs['on_the'].split(' ')
+ except Exception:
+ raise AnsibleError('on_the parameter must be two words separated by a space')
+
+ if weekday not in LookupModule.weekdays:
+ raise AnsibleError('Weekday portion of on_the parameter is not valid')
+ if occurance not in LookupModule.set_positions:
+ raise AnsibleError('The first string of the on_the parameter is not valid')
+
+ rrule_kwargs['byweekday'] = LookupModule.weekdays[weekday]
+ rrule_kwargs['bysetpos'] = LookupModule.set_positions[occurance]
+
+ my_rule = rrule.rrule(**rrule_kwargs)
+
+ # All frequencies can use a timezone but rrule can't support the format that Tower uses.
+ # So we will do a string manip here if we need to
+ timezone = 'America/New_York'
+ if 'timezone' in kwargs:
+ if kwargs['timezone'] not in pytz.all_timezones:
+ raise AnsibleError('Timezone parameter is not valid')
+ timezone = kwargs['timezone']
+
+ # rrule puts a \n in the rule instad of a space and can't handle timezones
+ return_rrule = str(my_rule).replace('\n', ' ').replace('DTSTART:', 'DTSTART;TZID={0}:'.format(timezone))
+ # Tower requires an interval. rrule will not add interval if it's set to 1
+ if kwargs.get('every', 1) == 1:
+ return_rrule = "{0};INTERVAL=1".format(return_rrule)
+
+ return return_rrule
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_api.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_api.py
new file mode 100644
index 00000000..8ffbd3e5
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_api.py
@@ -0,0 +1,583 @@
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from . tower_module import TowerModule
+from ansible.module_utils.urls import Request, SSLValidationError, ConnectionError
+from ansible.module_utils.six import PY2
+from ansible.module_utils.six.moves.urllib.parse import urlencode
+from ansible.module_utils.six.moves.urllib.error import HTTPError
+from ansible.module_utils.six.moves.http_cookiejar import CookieJar
+import re
+from json import loads, dumps
+
+
+class TowerAPIModule(TowerModule):
+ # TODO: Move the collection version check into tower_module.py
+ # This gets set by the make process so whatever is in here is irrelevant
+ _COLLECTION_VERSION = "14.1.0"
+ _COLLECTION_TYPE = "awx"
+ # This maps the collections type (awx/tower) to the values returned by the API
+ # Those values can be found in awx/api/generics.py line 204
+ collection_to_version = {
+ 'awx': 'AWX',
+ 'tower': 'Red Hat Ansible Tower',
+ }
+ session = None
+ cookie_jar = CookieJar()
+
+ def __init__(self, argument_spec, direct_params=None, error_callback=None, warn_callback=None, **kwargs):
+ kwargs['supports_check_mode'] = True
+
+ super(TowerAPIModule, self).__init__(argument_spec=argument_spec, direct_params=direct_params,
+ error_callback=error_callback, warn_callback=warn_callback, **kwargs)
+ self.session = Request(cookies=CookieJar(), validate_certs=self.verify_ssl)
+
+ @staticmethod
+ def param_to_endpoint(name):
+ exceptions = {
+ 'inventory': 'inventories',
+ 'target_team': 'teams',
+ 'workflow': 'workflow_job_templates'
+ }
+ return exceptions.get(name, '{0}s'.format(name))
+
+ def head_endpoint(self, endpoint, *args, **kwargs):
+ return self.make_request('HEAD', endpoint, **kwargs)
+
+ def get_endpoint(self, endpoint, *args, **kwargs):
+ return self.make_request('GET', endpoint, **kwargs)
+
+ def patch_endpoint(self, endpoint, *args, **kwargs):
+ # Handle check mode
+ if self.check_mode:
+ self.json_output['changed'] = True
+ self.exit_json(**self.json_output)
+
+ return self.make_request('PATCH', endpoint, **kwargs)
+
+ def post_endpoint(self, endpoint, *args, **kwargs):
+ # Handle check mode
+ if self.check_mode:
+ self.json_output['changed'] = True
+ self.exit_json(**self.json_output)
+
+ return self.make_request('POST', endpoint, **kwargs)
+
+ def delete_endpoint(self, endpoint, *args, **kwargs):
+ # Handle check mode
+ if self.check_mode:
+ self.json_output['changed'] = True
+ self.exit_json(**self.json_output)
+
+ return self.make_request('DELETE', endpoint, **kwargs)
+
+ def get_all_endpoint(self, endpoint, *args, **kwargs):
+ response = self.get_endpoint(endpoint, *args, **kwargs)
+ if 'next' not in response['json']:
+ raise RuntimeError('Expected list from API at {0}, got: {1}'.format(endpoint, response))
+ next_page = response['json']['next']
+
+ if response['json']['count'] > 10000:
+ self.fail_json(msg='The number of items being queried for is higher than 10,000.')
+
+ while next_page is not None:
+ next_response = self.get_endpoint(next_page)
+ response['json']['results'] = response['json']['results'] + next_response['json']['results']
+ next_page = next_response['json']['next']
+ response['json']['next'] = next_page
+ return response
+
+ def get_one(self, endpoint, *args, **kwargs):
+ response = self.get_endpoint(endpoint, *args, **kwargs)
+ if response['status_code'] != 200:
+ fail_msg = "Got a {0} response when trying to get one from {1}".format(response['status_code'], endpoint)
+ if 'detail' in response.get('json', {}):
+ fail_msg += ', detail: {0}'.format(response['json']['detail'])
+ self.fail_json(msg=fail_msg)
+
+ if 'count' not in response['json'] or 'results' not in response['json']:
+ self.fail_json(msg="The endpoint did not provide count and results")
+
+ if response['json']['count'] == 0:
+ return None
+ elif response['json']['count'] > 1:
+ self.fail_json(msg="An unexpected number of items was returned from the API ({0})".format(response['json']['count']))
+
+ return response['json']['results'][0]
+
+ def resolve_name_to_id(self, endpoint, name_or_id):
+ # Try to resolve the object by name
+ name_field = 'name'
+ if endpoint == 'users':
+ name_field = 'username'
+
+ response = self.get_endpoint(endpoint, **{'data': {name_field: name_or_id}})
+ if response['status_code'] == 400:
+ self.fail_json(msg="Unable to try and resolve {0} for {1} : {2}".format(endpoint, name_or_id, response['json']['detail']))
+
+ if response['json']['count'] == 1:
+ return response['json']['results'][0]['id']
+ elif response['json']['count'] == 0:
+ try:
+ int(name_or_id)
+ # If we got 0 items by name, maybe they gave us an ID, let's try looking it up by ID
+ response = self.head_endpoint("{0}/{1}".format(endpoint, name_or_id), **{'return_none_on_404': True})
+ if response is not None:
+ return name_or_id
+ except ValueError:
+ # If we got a value error than we didn't have an integer so we can just pass and fall down to the fail
+ pass
+
+ self.fail_json(msg="The {0} {1} was not found on the Tower server".format(endpoint, name_or_id))
+ else:
+ self.fail_json(msg="Found too many names {0} at endpoint {1} try using an ID instead of a name".format(name_or_id, endpoint))
+
+ def make_request(self, method, endpoint, *args, **kwargs):
+ # In case someone is calling us directly; make sure we were given a method, let's not just assume a GET
+ if not method:
+ raise Exception("The HTTP method must be defined")
+
+ # Make sure we start with /api/vX
+ if not endpoint.startswith("/"):
+ endpoint = "/{0}".format(endpoint)
+ if not endpoint.startswith("/api/"):
+ endpoint = "/api/v2{0}".format(endpoint)
+ if not endpoint.endswith('/') and '?' not in endpoint:
+ endpoint = "{0}/".format(endpoint)
+
+ # Extract the headers, this will be used in a couple of places
+ headers = kwargs.get('headers', {})
+
+ # Authenticate to Tower (if we don't have a token and if not already done so)
+ if not self.oauth_token and not self.authenticated:
+ # This method will set a cookie in the cookie jar for us and also an oauth_token
+ self.authenticate(**kwargs)
+ if self.oauth_token:
+ # If we have a oauth token, we just use a bearer header
+ headers['Authorization'] = 'Bearer {0}'.format(self.oauth_token)
+
+ # Update the URL path with the endpoint
+ self.url = self.url._replace(path=endpoint)
+
+ if method in ['POST', 'PUT', 'PATCH']:
+ headers.setdefault('Content-Type', 'application/json')
+ kwargs['headers'] = headers
+ elif kwargs.get('data'):
+ self.url = self.url._replace(query=urlencode(kwargs.get('data')))
+
+ data = None # Important, if content type is not JSON, this should not be dict type
+ if headers.get('Content-Type', '') == 'application/json':
+ data = dumps(kwargs.get('data', {}))
+
+ try:
+ response = self.session.open(method, self.url.geturl(), headers=headers, validate_certs=self.verify_ssl, follow_redirects=True, data=data)
+ except(SSLValidationError) as ssl_err:
+ self.fail_json(msg="Could not establish a secure connection to your host ({1}): {0}.".format(self.url.netloc, ssl_err))
+ except(ConnectionError) as con_err:
+ self.fail_json(msg="There was a network error of some kind trying to connect to your host ({1}): {0}.".format(self.url.netloc, con_err))
+ except(HTTPError) as he:
+ # Sanity check: Did the server send back some kind of internal error?
+ if he.code >= 500:
+ self.fail_json(msg='The host sent back a server error ({1}): {0}. Please check the logs and try again later'.format(self.url.path, he))
+ # Sanity check: Did we fail to authenticate properly? If so, fail out now; this is always a failure.
+ elif he.code == 401:
+ self.fail_json(msg='Invalid Tower authentication credentials for {0} (HTTP 401).'.format(self.url.path))
+ # Sanity check: Did we get a forbidden response, which means that the user isn't allowed to do this? Report that.
+ elif he.code == 403:
+ self.fail_json(msg="You don't have permission to {1} to {0} (HTTP 403).".format(self.url.path, method))
+ # Sanity check: Did we get a 404 response?
+ # Requests with primary keys will return a 404 if there is no response, and we want to consistently trap these.
+ elif he.code == 404:
+ if kwargs.get('return_none_on_404', False):
+ return None
+ self.fail_json(msg='The requested object could not be found at {0}.'.format(self.url.path))
+ # Sanity check: Did we get a 405 response?
+ # A 405 means we used a method that isn't allowed. Usually this is a bad request, but it requires special treatment because the
+ # API sends it as a logic error in a few situations (e.g. trying to cancel a job that isn't running).
+ elif he.code == 405:
+ self.fail_json(msg="The Tower server says you can't make a request with the {0} method to this endpoing {1}".format(method, self.url.path))
+ # Sanity check: Did we get some other kind of error? If so, write an appropriate error message.
+ elif he.code >= 400:
+ # We are going to return a 400 so the module can decide what to do with it
+ page_data = he.read()
+ try:
+ return {'status_code': he.code, 'json': loads(page_data)}
+ # JSONDecodeError only available on Python 3.5+
+ except ValueError:
+ return {'status_code': he.code, 'text': page_data}
+ elif he.code == 204 and method == 'DELETE':
+ # A 204 is a normal response for a delete function
+ pass
+ else:
+ self.fail_json(msg="Unexpected return code when calling {0}: {1}".format(self.url.geturl(), he))
+ except(Exception) as e:
+ self.fail_json(msg="There was an unknown error when trying to connect to {2}: {0} {1}".format(type(e).__name__, e, self.url.geturl()))
+ finally:
+ self.url = self.url._replace(query=None)
+
+ if not self.version_checked:
+ # In PY2 we get back an HTTPResponse object but PY2 is returning an addinfourl
+ # First try to get the headers in PY3 format and then drop down to PY2.
+ try:
+ tower_type = response.getheader('X-API-Product-Name', None)
+ tower_version = response.getheader('X-API-Product-Version', None)
+ except Exception:
+ tower_type = response.info().getheader('X-API-Product-Name', None)
+ tower_version = response.info().getheader('X-API-Product-Version', None)
+
+ if self._COLLECTION_TYPE not in self.collection_to_version or self.collection_to_version[self._COLLECTION_TYPE] != tower_type:
+ self.warn("You are using the {0} version of this collection but connecting to {1}".format(
+ self._COLLECTION_TYPE, tower_type
+ ))
+ elif self._COLLECTION_VERSION != tower_version:
+ self.warn("You are running collection version {0} but connecting to tower version {1}".format(
+ self._COLLECTION_VERSION, tower_version
+ ))
+ self.version_checked = True
+
+ response_body = ''
+ try:
+ response_body = response.read()
+ except(Exception) as e:
+ self.fail_json(msg="Failed to read response body: {0}".format(e))
+
+ response_json = {}
+ if response_body and response_body != '':
+ try:
+ response_json = loads(response_body)
+ except(Exception) as e:
+ self.fail_json(msg="Failed to parse the response json: {0}".format(e))
+
+ if PY2:
+ status_code = response.getcode()
+ else:
+ status_code = response.status
+ return {'status_code': status_code, 'json': response_json}
+
+ def authenticate(self, **kwargs):
+ if self.username and self.password:
+ # Attempt to get a token from /api/v2/tokens/ by giving it our username/password combo
+ # If we have a username and password, we need to get a session cookie
+ login_data = {
+ "description": "Ansible Tower Module Token",
+ "application": None,
+ "scope": "write",
+ }
+ # Post to the tokens endpoint with baisc auth to try and get a token
+ api_token_url = (self.url._replace(path='/api/v2/tokens/')).geturl()
+
+ try:
+ response = self.session.open(
+ 'POST', api_token_url,
+ validate_certs=self.verify_ssl, follow_redirects=True,
+ force_basic_auth=True, url_username=self.username, url_password=self.password,
+ data=dumps(login_data), headers={'Content-Type': 'application/json'}
+ )
+ except HTTPError as he:
+ try:
+ resp = he.read()
+ except Exception as e:
+ resp = 'unknown {0}'.format(e)
+ self.fail_json(msg='Failed to get token: {0}'.format(he), response=resp)
+ except(Exception) as e:
+ # Sanity check: Did the server send back some kind of internal error?
+ self.fail_json(msg='Failed to get token: {0}'.format(e))
+
+ token_response = None
+ try:
+ token_response = response.read()
+ response_json = loads(token_response)
+ self.oauth_token_id = response_json['id']
+ self.oauth_token = response_json['token']
+ except(Exception) as e:
+ self.fail_json(msg="Failed to extract token information from login response: {0}".format(e), **{'response': token_response})
+
+ # If we have neither of these, then we can try un-authenticated access
+ self.authenticated = True
+
+ def delete_if_needed(self, existing_item, on_delete=None):
+ # This will exit from the module on its own.
+ # If the method successfully deletes an item and on_delete param is defined,
+ # the on_delete parameter will be called as a method pasing in this object and the json from the response
+ # This will return one of two things:
+ # 1. None if the existing_item is not defined (so no delete needs to happen)
+ # 2. The response from Tower from calling the delete on the endpont. It's up to you to process the response and exit from the module
+ # Note: common error codes from the Tower API can cause the module to fail
+ if existing_item:
+ # If we have an item, we can try to delete it
+ try:
+ item_url = existing_item['url']
+ item_type = existing_item['type']
+ item_id = existing_item['id']
+ except KeyError as ke:
+ self.fail_json(msg="Unable to process delete of item due to missing data {0}".format(ke))
+
+ if 'name' in existing_item:
+ item_name = existing_item['name']
+ elif 'username' in existing_item:
+ item_name = existing_item['username']
+ elif 'identifier' in existing_item:
+ item_name = existing_item['identifier']
+ elif item_type == 'o_auth2_access_token':
+ # An oauth2 token has no name, instead we will use its id for any of the messages
+ item_name = existing_item['id']
+ elif item_type == 'credential_input_source':
+ # An credential_input_source has no name, instead we will use its id for any of the messages
+ item_name = existing_item['id']
+ else:
+ self.fail_json(msg="Unable to process delete of {0} due to missing name".format(item_type))
+
+ response = self.delete_endpoint(item_url)
+
+ if response['status_code'] in [202, 204]:
+ if on_delete:
+ on_delete(self, response['json'])
+ self.json_output['changed'] = True
+ self.json_output['id'] = item_id
+ self.exit_json(**self.json_output)
+ else:
+ if 'json' in response and '__all__' in response['json']:
+ self.fail_json(msg="Unable to delete {0} {1}: {2}".format(item_type, item_name, response['json']['__all__'][0]))
+ elif 'json' in response:
+ # This is from a project delete (if there is an active job against it)
+ if 'error' in response['json']:
+ self.fail_json(msg="Unable to delete {0} {1}: {2}".format(item_type, item_name, response['json']['error']))
+ else:
+ self.fail_json(msg="Unable to delete {0} {1}: {2}".format(item_type, item_name, response['json']))
+ else:
+ self.fail_json(msg="Unable to delete {0} {1}: {2}".format(item_type, item_name, response['status_code']))
+ else:
+ self.exit_json(**self.json_output)
+
+ def modify_associations(self, association_endpoint, new_association_list):
+ # if we got None instead of [] we are not modifying the association_list
+ if new_association_list is None:
+ return
+
+ # First get the existing associations
+ response = self.get_all_endpoint(association_endpoint)
+ existing_associated_ids = [association['id'] for association in response['json']['results']]
+
+ # Disassociate anything that is in existing_associated_ids but not in new_association_list
+ ids_to_remove = list(set(existing_associated_ids) - set(new_association_list))
+ for an_id in ids_to_remove:
+ response = self.post_endpoint(association_endpoint, **{'data': {'id': int(an_id), 'disassociate': True}})
+ if response['status_code'] == 204:
+ self.json_output['changed'] = True
+ else:
+ self.fail_json(msg="Failed to disassociate item {0}".format(response['json']['detail']))
+
+ # Associate anything that is in new_association_list but not in `association`
+ for an_id in list(set(new_association_list) - set(existing_associated_ids)):
+ response = self.post_endpoint(association_endpoint, **{'data': {'id': int(an_id)}})
+ if response['status_code'] == 204:
+ self.json_output['changed'] = True
+ else:
+ self.fail_json(msg="Failed to associate item {0}".format(response['json']['detail']))
+
+ def create_if_needed(self, existing_item, new_item, endpoint, on_create=None, item_type='unknown', associations=None):
+
+ # This will exit from the module on its own
+ # If the method successfully creates an item and on_create param is defined,
+ # the on_create parameter will be called as a method pasing in this object and the json from the response
+ # This will return one of two things:
+ # 1. None if the existing_item is already defined (so no create needs to happen)
+ # 2. The response from Tower from calling the patch on the endpont. It's up to you to process the response and exit from the module
+ # Note: common error codes from the Tower API can cause the module to fail
+
+ if not endpoint:
+ self.fail_json(msg="Unable to create new {0} due to missing endpoint".format(item_type))
+
+ item_url = None
+ if existing_item:
+ try:
+ item_url = existing_item['url']
+ except KeyError as ke:
+ self.fail_json(msg="Unable to process create of item due to missing data {0}".format(ke))
+ else:
+ # If we don't have an exisitng_item, we can try to create it
+
+ # We have to rely on item_type being passed in since we don't have an existing item that declares its type
+ # We will pull the item_name out from the new_item, if it exists
+ for key in ('name', 'username', 'identifier', 'hostname'):
+ if key in new_item:
+ item_name = new_item[key]
+ break
+ else:
+ item_name = 'unknown'
+
+ response = self.post_endpoint(endpoint, **{'data': new_item})
+ if response['status_code'] == 201:
+ self.json_output['name'] = 'unknown'
+ for key in ('name', 'username', 'identifier', 'hostname'):
+ if key in response['json']:
+ self.json_output['name'] = response['json'][key]
+ self.json_output['id'] = response['json']['id']
+ self.json_output['changed'] = True
+ item_url = response['json']['url']
+ else:
+ if 'json' in response and '__all__' in response['json']:
+ self.fail_json(msg="Unable to create {0} {1}: {2}".format(item_type, item_name, response['json']['__all__'][0]))
+ elif 'json' in response:
+ self.fail_json(msg="Unable to create {0} {1}: {2}".format(item_type, item_name, response['json']))
+ else:
+ self.fail_json(msg="Unable to create {0} {1}: {2}".format(item_type, item_name, response['status_code']))
+
+ # Process any associations with this item
+ if associations is not None:
+ for association_type in associations:
+ sub_endpoint = '{0}{1}/'.format(item_url, association_type)
+ self.modify_associations(sub_endpoint, associations[association_type])
+
+ # If we have an on_create method and we actually changed something we can call on_create
+ if on_create is not None and self.json_output['changed']:
+ on_create(self, response['json'])
+ else:
+ self.exit_json(**self.json_output)
+
+ def _encrypted_changed_warning(self, field, old, warning=False):
+ if not warning:
+ return
+ self.warn(
+ 'The field {0} of {1} {2} has encrypted data and may inaccurately report task is changed.'.format(
+ field, old.get('type', 'unknown'), old.get('id', 'unknown')
+ ))
+
+ @staticmethod
+ def has_encrypted_values(obj):
+ """Returns True if JSON-like python content in obj has $encrypted$
+ anywhere in the data as a value
+ """
+ if isinstance(obj, dict):
+ for val in obj.values():
+ if TowerAPIModule.has_encrypted_values(val):
+ return True
+ elif isinstance(obj, list):
+ for val in obj:
+ if TowerAPIModule.has_encrypted_values(val):
+ return True
+ elif obj == TowerAPIModule.ENCRYPTED_STRING:
+ return True
+ return False
+
+ def objects_could_be_different(self, old, new, field_set=None, warning=False):
+ if field_set is None:
+ field_set = set(fd for fd in new.keys() if fd not in ('modified', 'related', 'summary_fields'))
+ for field in field_set:
+ new_field = new.get(field, None)
+ old_field = old.get(field, None)
+ if old_field != new_field:
+ return True # Something doesn't match
+ elif self.has_encrypted_values(new_field) or field not in new:
+ # case of 'field not in new' - user password write-only field that API will not display
+ self._encrypted_changed_warning(field, old, warning=warning)
+ return True
+ return False
+
+ def update_if_needed(self, existing_item, new_item, on_update=None, associations=None):
+ # This will exit from the module on its own
+ # If the method successfully updates an item and on_update param is defined,
+ # the on_update parameter will be called as a method pasing in this object and the json from the response
+ # This will return one of two things:
+ # 1. None if the existing_item does not need to be updated
+ # 2. The response from Tower from patching to the endpoint. It's up to you to process the response and exit from the module.
+ # Note: common error codes from the Tower API can cause the module to fail
+ response = None
+ if existing_item:
+
+ # If we have an item, we can see if it needs an update
+ try:
+ item_url = existing_item['url']
+ item_type = existing_item['type']
+ if item_type == 'user':
+ item_name = existing_item['username']
+ elif item_type == 'workflow_job_template_node':
+ item_name = existing_item['identifier']
+ elif item_type == 'credential_input_source':
+ item_name = existing_item['id']
+ else:
+ item_name = existing_item['name']
+ item_id = existing_item['id']
+ except KeyError as ke:
+ self.fail_json(msg="Unable to process update of item due to missing data {0}".format(ke))
+
+ # Check to see if anything within the item requires the item to be updated
+ needs_patch = self.objects_could_be_different(existing_item, new_item)
+
+ # If we decided the item needs to be updated, update it
+ self.json_output['id'] = item_id
+ if needs_patch:
+ response = self.patch_endpoint(item_url, **{'data': new_item})
+ if response['status_code'] == 200:
+ # compare apples-to-apples, old API data to new API data
+ # but do so considering the fields given in parameters
+ self.json_output['changed'] = self.objects_could_be_different(
+ existing_item, response['json'], field_set=new_item.keys(), warning=True)
+ elif 'json' in response and '__all__' in response['json']:
+ self.fail_json(msg=response['json']['__all__'])
+ else:
+ self.fail_json(**{'msg': "Unable to update {0} {1}, see response".format(item_type, item_name), 'response': response})
+
+ else:
+ raise RuntimeError('update_if_needed called incorrectly without existing_item')
+
+ # Process any associations with this item
+ if associations is not None:
+ for association_type, id_list in associations.items():
+ endpoint = '{0}{1}/'.format(item_url, association_type)
+ self.modify_associations(endpoint, id_list)
+
+ # If we change something and have an on_change call it
+ if on_update is not None and self.json_output['changed']:
+ if response is None:
+ last_data = existing_item
+ else:
+ last_data = response['json']
+ on_update(self, last_data)
+ else:
+ self.exit_json(**self.json_output)
+
+ def create_or_update_if_needed(self, existing_item, new_item, endpoint=None, item_type='unknown', on_create=None, on_update=None, associations=None):
+ if existing_item:
+ return self.update_if_needed(existing_item, new_item, on_update=on_update, associations=associations)
+ else:
+ return self.create_if_needed(existing_item, new_item, endpoint, on_create=on_create, item_type=item_type, associations=associations)
+
+ def logout(self):
+ if self.authenticated:
+ # Attempt to delete our current token from /api/v2/tokens/
+ # Post to the tokens endpoint with baisc auth to try and get a token
+ api_token_url = (
+ self.url._replace(
+ path='/api/v2/tokens/{0}/'.format(self.oauth_token_id),
+ query=None # in error cases, fail_json exists before exception handling
+ )
+ ).geturl()
+
+ try:
+ self.session.open(
+ 'DELETE',
+ api_token_url,
+ validate_certs=self.verify_ssl,
+ follow_redirects=True,
+ force_basic_auth=True,
+ url_username=self.username,
+ url_password=self.password
+ )
+ self.oauth_token_id = None
+ self.authenticated = False
+ except HTTPError as he:
+ try:
+ resp = he.read()
+ except Exception as e:
+ resp = 'unknown {0}'.format(e)
+ self.warn('Failed to release tower token: {0}, response: {1}'.format(he, resp))
+ except(Exception) as e:
+ # Sanity check: Did the server send back some kind of internal error?
+ self.warn('Failed to release tower token {0}: {1}'.format(self.oauth_token_id, e))
+
+ def is_job_done(self, job_status):
+ if job_status in ['new', 'pending', 'waiting', 'running']:
+ return False
+ else:
+ return True
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_awxkit.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_awxkit.py
new file mode 100644
index 00000000..fc4e232f
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_awxkit.py
@@ -0,0 +1,53 @@
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from . tower_module import TowerModule
+from ansible.module_utils.basic import missing_required_lib
+
+try:
+ from awxkit.api.client import Connection
+ from awxkit.api.pages.api import ApiV2
+ from awxkit.api import get_registered_page
+ HAS_AWX_KIT = True
+except ImportError:
+ HAS_AWX_KIT = False
+
+
+class TowerAWXKitModule(TowerModule):
+ connection = None
+ apiV2Ref = None
+
+ def __init__(self, argument_spec, **kwargs):
+ kwargs['supports_check_mode'] = False
+
+ super(TowerAWXKitModule, self).__init__(argument_spec=argument_spec, **kwargs)
+
+ # Die if we don't have AWX_KIT installed
+ if not HAS_AWX_KIT:
+ self.exit_json(msg=missing_required_lib('awxkit'))
+
+ # Establish our conneciton object
+ self.connection = Connection(self.host, verify=self.verify_ssl)
+
+ def authenticate(self):
+ try:
+ if self.oauth_token:
+ self.connection.login(None, None, token=self.oauth_token)
+ self.authenticated = True
+ elif self.username:
+ self.connection.login(username=self.username, password=self.password)
+ self.authenticated = True
+ except Exception:
+ self.exit_json("Failed to authenticate")
+
+ def get_api_v2_object(self):
+ if not self.apiV2Ref:
+ if not self.authenticated:
+ self.authenticate()
+ v2_index = get_registered_page('/api/v2/')(self.connection).get()
+ self.api_ref = ApiV2(connection=self.connection, **{'json': v2_index})
+ return self.api_ref
+
+ def logout(self):
+ if self.authenticated:
+ self.connection.logout()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_legacy.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_legacy.py
new file mode 100644
index 00000000..3c840861
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_legacy.py
@@ -0,0 +1,117 @@
+# This code is part of Ansible, but is an independent component.
+# This particular file snippet, and this file snippet only, is BSD licensed.
+# Modules you write using this snippet, which is embedded dynamically by Ansible
+# still belong to the author of the module, and may assign their own license
+# to the complete work.
+#
+# Copyright (c), Wayne Witzel III <wayne@riotousliving.com>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import os
+import traceback
+
+TOWER_CLI_IMP_ERR = None
+try:
+ import tower_cli.utils.exceptions as exc
+ from tower_cli.utils import parser
+ from tower_cli.api import client
+
+ HAS_TOWER_CLI = True
+except ImportError:
+ TOWER_CLI_IMP_ERR = traceback.format_exc()
+ HAS_TOWER_CLI = False
+
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib
+
+
+def tower_auth_config(module):
+ '''
+ `tower_auth_config` attempts to load the tower-cli.cfg file
+ specified from the `tower_config_file` parameter. If found,
+ if returns the contents of the file as a dictionary, else
+ it will attempt to fetch values from the module params and
+ only pass those values that have been set.
+ '''
+ config_file = module.params.pop('tower_config_file', None)
+ if config_file:
+ if not os.path.exists(config_file):
+ module.fail_json(msg='file not found: %s' % config_file)
+ if os.path.isdir(config_file):
+ module.fail_json(msg='directory can not be used as config file: %s' % config_file)
+
+ with open(config_file, 'r') as f:
+ return parser.string_to_dict(f.read())
+ else:
+ auth_config = {}
+ host = module.params.pop('tower_host', None)
+ if host:
+ auth_config['host'] = host
+ username = module.params.pop('tower_username', None)
+ if username:
+ auth_config['username'] = username
+ password = module.params.pop('tower_password', None)
+ if password:
+ auth_config['password'] = password
+ module.params.pop('tower_verify_ssl', None) # pop alias if used
+ verify_ssl = module.params.pop('validate_certs', None)
+ if verify_ssl is not None:
+ auth_config['verify_ssl'] = verify_ssl
+ return auth_config
+
+
+def tower_check_mode(module):
+ '''Execute check mode logic for Ansible Tower modules'''
+ if module.check_mode:
+ try:
+ result = client.get('/ping').json()
+ module.exit_json(changed=True, tower_version='{0}'.format(result['version']))
+ except (exc.ServerError, exc.ConnectionError, exc.BadRequest) as excinfo:
+ module.fail_json(changed=False, msg='Failed check mode: {0}'.format(excinfo))
+
+
+class TowerLegacyModule(AnsibleModule):
+ def __init__(self, argument_spec, **kwargs):
+ args = dict(
+ tower_host=dict(),
+ tower_username=dict(),
+ tower_password=dict(no_log=True),
+ validate_certs=dict(type='bool', aliases=['tower_verify_ssl']),
+ tower_config_file=dict(type='path'),
+ )
+ args.update(argument_spec)
+
+ kwargs.setdefault('mutually_exclusive', [])
+ kwargs['mutually_exclusive'].extend((
+ ('tower_config_file', 'tower_host'),
+ ('tower_config_file', 'tower_username'),
+ ('tower_config_file', 'tower_password'),
+ ('tower_config_file', 'validate_certs'),
+ ))
+
+ super(TowerLegacyModule, self).__init__(argument_spec=args, **kwargs)
+
+ if not HAS_TOWER_CLI:
+ self.fail_json(msg=missing_required_lib('ansible-tower-cli'),
+ exception=TOWER_CLI_IMP_ERR)
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_module.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_module.py
new file mode 100644
index 00000000..553a3524
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/module_utils/tower_module.py
@@ -0,0 +1,239 @@
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible.module_utils.basic import AnsibleModule, env_fallback
+from ansible.module_utils.six import PY2, string_types
+from ansible.module_utils.six.moves import StringIO
+from ansible.module_utils.six.moves.urllib.parse import urlparse
+from ansible.module_utils.six.moves.configparser import ConfigParser, NoOptionError
+from socket import gethostbyname
+import re
+from os.path import isfile, expanduser, split, join, exists, isdir
+from os import access, R_OK, getcwd
+from distutils.util import strtobool
+
+try:
+ import yaml
+ HAS_YAML = True
+except ImportError:
+ HAS_YAML = False
+
+
+class ConfigFileException(Exception):
+ pass
+
+
+class ItemNotDefined(Exception):
+ pass
+
+
+class TowerModule(AnsibleModule):
+ url = None
+ AUTH_ARGSPEC = dict(
+ tower_host=dict(required=False, fallback=(env_fallback, ['TOWER_HOST'])),
+ tower_username=dict(required=False, fallback=(env_fallback, ['TOWER_USERNAME'])),
+ tower_password=dict(no_log=True, required=False, fallback=(env_fallback, ['TOWER_PASSWORD'])),
+ validate_certs=dict(type='bool', aliases=['tower_verify_ssl'], required=False, fallback=(env_fallback, ['TOWER_VERIFY_SSL'])),
+ tower_oauthtoken=dict(type='raw', no_log=True, required=False, fallback=(env_fallback, ['TOWER_OAUTH_TOKEN'])),
+ tower_config_file=dict(type='path', required=False, default=None),
+ )
+ short_params = {
+ 'host': 'tower_host',
+ 'username': 'tower_username',
+ 'password': 'tower_password',
+ 'verify_ssl': 'validate_certs',
+ 'oauth_token': 'tower_oauthtoken',
+ }
+ host = '127.0.0.1'
+ username = None
+ password = None
+ verify_ssl = True
+ oauth_token = None
+ oauth_token_id = None
+ authenticated = False
+ config_name = 'tower_cli.cfg'
+ ENCRYPTED_STRING = "$encrypted$"
+ version_checked = False
+ error_callback = None
+ warn_callback = None
+
+ def __init__(self, argument_spec=None, direct_params=None, error_callback=None, warn_callback=None, **kwargs):
+ full_argspec = {}
+ full_argspec.update(TowerModule.AUTH_ARGSPEC)
+ full_argspec.update(argument_spec)
+ kwargs['supports_check_mode'] = True
+
+ self.error_callback = error_callback
+ self.warn_callback = warn_callback
+
+ self.json_output = {'changed': False}
+
+ if direct_params is not None:
+ self.params = direct_params
+ else:
+ super(TowerModule, self).__init__(argument_spec=full_argspec, **kwargs)
+
+ self.load_config_files()
+
+ # Parameters specified on command line will override settings in any config
+ for short_param, long_param in self.short_params.items():
+ direct_value = self.params.get(long_param)
+ if direct_value is not None:
+ setattr(self, short_param, direct_value)
+
+ # Perform magic depending on whether tower_oauthtoken is a string or a dict
+ if self.params.get('tower_oauthtoken'):
+ token_param = self.params.get('tower_oauthtoken')
+ if type(token_param) is dict:
+ if 'token' in token_param:
+ self.oauth_token = self.params.get('tower_oauthtoken')['token']
+ else:
+ self.fail_json(msg="The provided dict in tower_oauthtoken did not properly contain the token entry")
+ elif isinstance(token_param, string_types):
+ self.oauth_token = self.params.get('tower_oauthtoken')
+ else:
+ error_msg = "The provided tower_oauthtoken type was not valid ({0}). Valid options are str or dict.".format(type(token_param).__name__)
+ self.fail_json(msg=error_msg)
+
+ # Perform some basic validation
+ if not re.match('^https{0,1}://', self.host):
+ self.host = "https://{0}".format(self.host)
+
+ # Try to parse the hostname as a url
+ try:
+ self.url = urlparse(self.host)
+ except Exception as e:
+ self.fail_json(msg="Unable to parse tower_host as a URL ({1}): {0}".format(self.host, e))
+
+ # Try to resolve the hostname
+ hostname = self.url.netloc.split(':')[0]
+ try:
+ gethostbyname(hostname)
+ except Exception as e:
+ self.fail_json(msg="Unable to resolve tower_host ({1}): {0}".format(hostname, e))
+
+ def load_config_files(self):
+ # Load configs like TowerCLI would have from least import to most
+ config_files = ['/etc/tower/tower_cli.cfg', join(expanduser("~"), ".{0}".format(self.config_name))]
+ local_dir = getcwd()
+ config_files.append(join(local_dir, self.config_name))
+ while split(local_dir)[1]:
+ local_dir = split(local_dir)[0]
+ config_files.insert(2, join(local_dir, ".{0}".format(self.config_name)))
+
+ # If we have a specified tower config, load it
+ if self.params.get('tower_config_file'):
+ duplicated_params = [
+ fn for fn in self.AUTH_ARGSPEC
+ if fn != 'tower_config_file' and self.params.get(fn) is not None
+ ]
+ if duplicated_params:
+ self.warn((
+ 'The parameter(s) {0} were provided at the same time as tower_config_file. '
+ 'Precedence may be unstable, we suggest either using config file or params.'
+ ).format(', '.join(duplicated_params)))
+ try:
+ # TODO: warn if there are conflicts with other params
+ self.load_config(self.params.get('tower_config_file'))
+ except ConfigFileException as cfe:
+ # Since we were told specifically to load this we want it to fail if we have an error
+ self.fail_json(msg=cfe)
+ else:
+ for config_file in config_files:
+ if exists(config_file) and not isdir(config_file):
+ # Only throw a formatting error if the file exists and is not a directory
+ try:
+ self.load_config(config_file)
+ except ConfigFileException:
+ self.fail_json(msg='The config file {0} is not properly formatted'.format(config_file))
+
+ def load_config(self, config_path):
+ # Validate the config file is an actual file
+ if not isfile(config_path):
+ raise ConfigFileException('The specified config file does not exist')
+
+ if not access(config_path, R_OK):
+ raise ConfigFileException("The specified config file cannot be read")
+
+ # Read in the file contents:
+ with open(config_path, 'r') as f:
+ config_string = f.read()
+
+ # First try to yaml load the content (which will also load json)
+ try:
+ try_config_parsing = True
+ if HAS_YAML:
+ try:
+ config_data = yaml.load(config_string, Loader=yaml.SafeLoader)
+ # If this is an actual ini file, yaml will return the whole thing as a string instead of a dict
+ if type(config_data) is not dict:
+ raise AssertionError("The yaml config file is not properly formatted as a dict.")
+ try_config_parsing = False
+
+ except(AttributeError, yaml.YAMLError, AssertionError):
+ try_config_parsing = True
+
+ if try_config_parsing:
+ # TowerCLI used to support a config file with a missing [general] section by prepending it if missing
+ if '[general]' not in config_string:
+ config_string = '[general]\n{0}'.format(config_string)
+
+ config = ConfigParser()
+
+ try:
+ placeholder_file = StringIO(config_string)
+ # py2 ConfigParser has readfp, that has been deprecated in favor of read_file in py3
+ # This "if" removes the deprecation warning
+ if hasattr(config, 'read_file'):
+ config.read_file(placeholder_file)
+ else:
+ config.readfp(placeholder_file)
+
+ # If we made it here then we have values from reading the ini file, so let's pull them out into a dict
+ config_data = {}
+ for honorred_setting in self.short_params:
+ try:
+ config_data[honorred_setting] = config.get('general', honorred_setting)
+ except NoOptionError:
+ pass
+
+ except Exception as e:
+ raise ConfigFileException("An unknown exception occured trying to ini load config file: {0}".format(e))
+
+ except Exception as e:
+ raise ConfigFileException("An unknown exception occured trying to load config file: {0}".format(e))
+
+ # If we made it here, we have a dict which has values in it from our config, any final settings logic can be performed here
+ for honorred_setting in self.short_params:
+ if honorred_setting in config_data:
+ # Veriffy SSL must be a boolean
+ if honorred_setting == 'verify_ssl':
+ if type(config_data[honorred_setting]) is str:
+ setattr(self, honorred_setting, strtobool(config_data[honorred_setting]))
+ else:
+ setattr(self, honorred_setting, bool(config_data[honorred_setting]))
+ else:
+ setattr(self, honorred_setting, config_data[honorred_setting])
+
+ def logout(self):
+ # This method is intended to be overridden
+ pass
+
+ def fail_json(self, **kwargs):
+ # Try to log out if we are authenticated
+ self.logout()
+ if self.error_callback:
+ self.error_callback(**kwargs)
+ else:
+ super(TowerModule, self).fail_json(**kwargs)
+
+ def exit_json(self, **kwargs):
+ # Try to log out if we are authenticated
+ self.logout()
+ super(TowerModule, self).exit_json(**kwargs)
+
+ def warn(self, warning):
+ if self.warn_callback is not None:
+ self.warn_callback(warning)
+ else:
+ super(TowerModule, self).warn(warning)
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/__init__.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/__init__.py
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_credential.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_credential.py
new file mode 100644
index 00000000..2d4cbd33
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_credential.py
@@ -0,0 +1,427 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# Copyright: (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_credential
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: create, update, or destroy Ansible Tower credential.
+description:
+ - Create, update, or destroy Ansible Tower credentials. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - The name to use for the credential.
+ required: True
+ type: str
+ new_name:
+ description:
+ - Setting this option will change the existing name (looked up via the name field.
+ required: False
+ type: str
+ description:
+ description:
+ - The description to use for the credential.
+ type: str
+ organization:
+ description:
+ - Organization that should own the credential.
+ type: str
+ credential_type:
+ description:
+ - Name of credential type.
+ - Will be preferred over kind
+ type: str
+ inputs:
+ description:
+ - >-
+ Credential inputs where the keys are var names used in templating.
+ Refer to the Ansible Tower documentation for example syntax.
+ - Any fields in this dict will take prescedence over any fields mentioned below (i.e. host, username, etc)
+ type: dict
+ user:
+ description:
+ - User that should own this credential.
+ type: str
+ team:
+ description:
+ - Team that should own this credential.
+ type: str
+
+ kind:
+ description:
+ - Type of credential being added.
+ - The ssh choice refers to a Tower Machine credential.
+ - Deprecated, please use credential_type
+ required: False
+ type: str
+ choices: ["ssh", "vault", "net", "scm", "aws", "vmware", "satellite6", "cloudforms", "gce", "azure_rm", "openstack", "rhv", "insights", "tower"]
+ host:
+ description:
+ - Host for this credential.
+ - Deprecated, will be removed in a future release
+ type: str
+ username:
+ description:
+ - Username for this credential. ``access_key`` for AWS.
+ - Deprecated, please use inputs
+ type: str
+ password:
+ description:
+ - Password for this credential. ``secret_key`` for AWS. ``api_key`` for RAX.
+ - Use "ASK" and launch in Tower to be prompted.
+ - Deprecated, please use inputs
+ type: str
+ project:
+ description:
+ - Project that should use this credential for GCP.
+ - Deprecated, will be removed in a future release
+ type: str
+ ssh_key_data:
+ description:
+ - SSH private key content. To extract the content from a file path, use the lookup function (see examples).
+ - Deprecated, please use inputs
+ type: str
+ ssh_key_unlock:
+ description:
+ - Unlock password for ssh_key.
+ - Use "ASK" and launch in Tower to be prompted.
+ - Deprecated, please use inputs
+ type: str
+ authorize:
+ description:
+ - Should use authorize for net type.
+ - Deprecated, please use inputs
+ type: bool
+ default: 'no'
+ authorize_password:
+ description:
+ - Password for net credentials that require authorize.
+ - Deprecated, please use inputs
+ type: str
+ client:
+ description:
+ - Client or application ID for azure_rm type.
+ - Deprecated, please use inputs
+ type: str
+ security_token:
+ description:
+ - STS token for aws type.
+ - Deprecated, please use inputs
+ type: str
+ secret:
+ description:
+ - Secret token for azure_rm type.
+ - Deprecated, please use inputs
+ type: str
+ subscription:
+ description:
+ - Subscription ID for azure_rm type.
+ - Deprecated, please use inputs
+ type: str
+ tenant:
+ description:
+ - Tenant ID for azure_rm type.
+ - Deprecated, please use inputs
+ type: str
+ domain:
+ description:
+ - Domain for openstack type.
+ - Deprecated, please use inputs
+ type: str
+ become_method:
+ description:
+ - Become method to use for privilege escalation.
+ - Some examples are "None", "sudo", "su", "pbrun"
+ - Due to become plugins, these can be arbitrary
+ - Deprecated, please use inputs
+ type: str
+ become_username:
+ description:
+ - Become username.
+ - Use "ASK" and launch in Tower to be prompted.
+ - Deprecated, please use inputs
+ type: str
+ become_password:
+ description:
+ - Become password.
+ - Use "ASK" and launch in Tower to be prompted.
+ - Deprecated, please use inputs
+ type: str
+ vault_password:
+ description:
+ - Vault password.
+ - Use "ASK" and launch in Tower to be prompted.
+ - Deprecated, please use inputs
+ type: str
+ vault_id:
+ description:
+ - Vault identifier.
+ - This parameter is only valid if C(kind) is specified as C(vault).
+ - Deprecated, please use inputs
+ type: str
+ state:
+ description:
+ - Desired state of the resource.
+ choices: ["present", "absent"]
+ default: "present"
+ type: str
+
+extends_documentation_fragment: awx.awx.auth
+
+notes:
+ - Values `inputs` and the other deprecated fields (such as `tenant`) are replacements of existing values.
+ See the last 4 examples for details.
+'''
+
+
+EXAMPLES = '''
+- name: Add tower machine credential
+ tower_credential:
+ name: Team Name
+ description: Team Description
+ organization: test-org
+ credential_type: Machine
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Create a valid SCM credential from a private_key file
+ tower_credential:
+ name: SCM Credential
+ organization: Default
+ state: present
+ credential_type: Source Control
+ inputs:
+ username: joe
+ password: secret
+ ssh_key_data: "{{ lookup('file', '/tmp/id_rsa') }}"
+ ssh_key_unlock: "passphrase"
+
+- name: Fetch private key
+ slurp:
+ src: '$HOME/.ssh/aws-private.pem'
+ register: aws_ssh_key
+
+- name: Add Credential Into Tower
+ tower_credential:
+ name: Workshop Credential
+ credential_type: Machine
+ organization: Default
+ inputs:
+ ssh_key_data: "{{ aws_ssh_key['content'] | b64decode }}"
+ run_once: true
+ delegate_to: localhost
+
+- name: Add Credential with Custom Credential Type
+ tower_credential:
+ name: Workshop Credential
+ credential_type: MyCloudCredential
+ organization: Default
+ tower_username: admin
+ tower_password: ansible
+ tower_host: https://localhost
+
+- name: Create a Vaiult credential (example for notes)
+ tower_credential:
+ name: Example password
+ credential_type: Vault
+ organization: Default
+ inputs:
+ vault_password: 'hello'
+ vault_id: 'My ID'
+
+- name: Bad password update (will replace vault_id)
+ tower_credential:
+ name: Example password
+ credential_type: Vault
+ organization: Default
+ inputs:
+ vault_password: 'new_password'
+
+- name: Another bad password update (will replace vault_id)
+ tower_credential:
+ name: Example password
+ credential_type: Vault
+ organization: Default
+ vault_password: 'new_password'
+
+- name: A safe way to update a password and keep vault_id
+ tower_credential:
+ name: Example password
+ credential_type: Vault
+ organization: Default
+ inputs:
+ vault_password: 'new_password'
+ vault_id: 'My ID'
+
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+KIND_CHOICES = {
+ 'ssh': 'Machine',
+ 'vault': 'Vault',
+ 'net': 'Network',
+ 'scm': 'Source Control',
+ 'aws': 'Amazon Web Services',
+ 'vmware': 'VMware vCenter',
+ 'satellite6': 'Red Hat Satellite 6',
+ 'cloudforms': 'Red Hat CloudForms',
+ 'gce': 'Google Compute Engine',
+ 'azure_rm': 'Microsoft Azure Resource Manager',
+ 'openstack': 'OpenStack',
+ 'rhv': 'Red Hat Virtualization',
+ 'insights': 'Insights',
+ 'tower': 'Ansible Tower',
+}
+
+
+OLD_INPUT_NAMES = (
+ 'authorize', 'authorize_password', 'client',
+ 'security_token', 'secret', 'tenant', 'subscription',
+ 'domain', 'become_method', 'become_username',
+ 'become_password', 'vault_password', 'project', 'host',
+ 'username', 'password', 'ssh_key_data', 'vault_id',
+ 'ssh_key_unlock'
+)
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ new_name=dict(),
+ description=dict(),
+ organization=dict(),
+ credential_type=dict(),
+ inputs=dict(type='dict', no_log=True),
+ user=dict(),
+ team=dict(),
+ # These are for backwards compatability
+ kind=dict(choices=list(KIND_CHOICES.keys())),
+ host=dict(),
+ username=dict(),
+ password=dict(no_log=True),
+ project=dict(),
+ ssh_key_data=dict(no_log=True),
+ ssh_key_unlock=dict(no_log=True),
+ authorize=dict(type='bool'),
+ authorize_password=dict(no_log=True),
+ client=dict(),
+ security_token=dict(),
+ secret=dict(no_log=True),
+ subscription=dict(),
+ tenant=dict(),
+ domain=dict(),
+ become_method=dict(),
+ become_username=dict(),
+ become_password=dict(no_log=True),
+ vault_password=dict(no_log=True),
+ vault_id=dict(),
+ # End backwards compatability
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec, required_one_of=[['kind', 'credential_type']])
+
+ # Extract our parameters
+ name = module.params.get('name')
+ new_name = module.params.get('new_name')
+ description = module.params.get('description')
+ organization = module.params.get('organization')
+ credential_type = module.params.get('credential_type')
+ inputs = module.params.get('inputs')
+ user = module.params.get('user')
+ team = module.params.get('team')
+ # The legacy arguments are put into a hash down below
+ kind = module.params.get('kind')
+ # End backwards compatability
+ state = module.params.get('state')
+
+ # Deprication warnings
+ for legacy_input in OLD_INPUT_NAMES:
+ if module.params.get(legacy_input) is not None:
+ module.deprecate(msg='{0} parameter has been deprecated, please use inputs instead'.format(legacy_input), version="ansible.tower:4.0.0")
+ if kind:
+ module.deprecate(msg='The kind parameter has been deprecated, please use credential_type instead', version="ansible.tower:4.0.0")
+
+ cred_type_id = module.resolve_name_to_id('credential_types', credential_type if credential_type else KIND_CHOICES[kind])
+ if organization:
+ org_id = module.resolve_name_to_id('organizations', organization)
+
+ # Attempt to look up the object based on the provided name, credential type and optional organization
+ lookup_data = {
+ 'name': name,
+ 'credential_type': cred_type_id,
+ }
+ if organization:
+ lookup_data['organization'] = org_id
+
+ credential = module.get_one('credentials', **{'data': lookup_data})
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(credential)
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ if user:
+ user_id = module.resolve_name_to_id('users', user)
+ if team:
+ team_id = module.resolve_name_to_id('teams', team)
+
+ # Create credential input from legacy inputs
+ has_inputs = False
+ credential_inputs = {}
+ for legacy_input in OLD_INPUT_NAMES:
+ if module.params.get(legacy_input) is not None:
+ has_inputs = True
+ credential_inputs[legacy_input] = module.params.get(legacy_input)
+
+ if inputs:
+ has_inputs = True
+ credential_inputs.update(inputs)
+
+ # Create the data that gets sent for create and update
+ credential_fields = {
+ 'name': new_name if new_name else name,
+ 'credential_type': cred_type_id,
+ }
+ if has_inputs:
+ credential_fields['inputs'] = credential_inputs
+
+ if description:
+ credential_fields['description'] = description
+ if organization:
+ credential_fields['organization'] = org_id
+
+ # If we don't already have a credential (and we are creating one) we can add user/team
+ # The API does not appear to do anything with these after creation anyway
+ # NOTE: We can't just add these on a modification because they are never returned from a GET so it would always cause a changed=True
+ if not credential:
+ if user:
+ credential_fields['user'] = user_id
+ if team:
+ credential_fields['team'] = team_id
+
+ # If the state was present we can let the module build or update the existing group, this will return on its own
+ module.create_or_update_if_needed(
+ credential, credential_fields, endpoint='credentials', item_type='credential'
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_credential_input_source.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_credential_input_source.py
new file mode 100644
index 00000000..cdc55cb1
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_credential_input_source.py
@@ -0,0 +1,129 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# Copyright: (c) 2020, Tom Page <tpage@redhat.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_credential_input_source
+author: "Tom Page (@Tompage1994)"
+version_added: "2.3"
+short_description: create, update, or destroy Ansible Tower credential input sources.
+description:
+ - Create, update, or destroy Ansible Tower credential input sources. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ description:
+ description:
+ - The description to use for the credential input source.
+ type: str
+ input_field_name:
+ description:
+ - The input field the credential source will be used for
+ required: True
+ type: str
+ metadata:
+ description:
+ - A JSON or YAML string
+ required: False
+ type: dict
+ target_credential:
+ description:
+ - The credential which will have its input defined by this source
+ required: true
+ type: str
+ source_credential:
+ description:
+ - The credential which is the source of the credential lookup
+ type: str
+ state:
+ description:
+ - Desired state of the resource.
+ choices: ["present", "absent"]
+ default: "present"
+ type: str
+
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+EXAMPLES = '''
+- name: Use CyberArk Lookup credential as password source
+ tower_credential_input_source:
+ input_field_name: password
+ target_credential: new_cred
+ source_credential: cyberark_lookup
+ metadata:
+ object_query: "Safe=MY_SAFE;Object=awxuser"
+ object_query_format: "Exact"
+ state: present
+
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ description=dict(default=''),
+ input_field_name=dict(required=True),
+ target_credential=dict(required=True),
+ source_credential=dict(default=''),
+ metadata=dict(type="dict"),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ description = module.params.get('description')
+ input_field_name = module.params.get('input_field_name')
+ target_credential = module.params.get('target_credential')
+ source_credential = module.params.get('source_credential')
+ metadata = module.params.get('metadata')
+ state = module.params.get('state')
+
+ target_credential_id = module.resolve_name_to_id('credentials', target_credential)
+
+ # Attempt to look up the object based on the target credential and input field
+ lookup_data = {
+ 'target_credential': target_credential_id,
+ 'input_field_name': input_field_name,
+ }
+ credential_input_source = module.get_one('credential_input_sources', **{'data': lookup_data})
+
+ if state == 'absent':
+ module.delete_if_needed(credential_input_source)
+
+ # Create the data that gets sent for create and update
+ credential_input_source_fields = {
+ 'target_credential': target_credential_id,
+ 'input_field_name': input_field_name,
+ }
+ if source_credential:
+ credential_input_source_fields['source_credential'] = module.resolve_name_to_id('credentials', source_credential)
+ if metadata:
+ credential_input_source_fields['metadata'] = metadata
+ if description:
+ credential_input_source_fields['description'] = description
+
+ # If the state was present we can let the module build or update the existing group, this will return on its own
+ module.create_or_update_if_needed(
+ credential_input_source, credential_input_source_fields, endpoint='credential_input_sources', item_type='credential_input_source'
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_credential_type.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_credential_type.py
new file mode 100644
index 00000000..53f0cc45
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_credential_type.py
@@ -0,0 +1,146 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+#
+# (c) 2018, Adrien Fleury <fleu42@gmail.com>
+# 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
+
+
+ANSIBLE_METADATA = {'status': ['preview'],
+ 'supported_by': 'community',
+ 'metadata_version': '1.1'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_credential_type
+author: "Adrien Fleury (@fleu42)"
+short_description: Create, update, or destroy custom Ansible Tower credential type.
+description:
+ - Create, update, or destroy Ansible Tower credential type. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - The name of the credential type.
+ required: True
+ type: str
+ description:
+ description:
+ - The description of the credential type to give more detail about it.
+ type: str
+ kind:
+ description:
+ - >-
+ The type of credential type being added. Note that only cloud and
+ net can be used for creating credential types. Refer to the Ansible
+ for more information.
+ choices: [ 'ssh', 'vault', 'net', 'scm', 'cloud', 'insights' ]
+ type: str
+ inputs:
+ description:
+ - >-
+ Enter inputs using either JSON or YAML syntax. Refer to the Ansible
+ Tower documentation for example syntax.
+ type: dict
+ injectors:
+ description:
+ - >-
+ Enter injectors using either JSON or YAML syntax. Refer to the
+ Ansible Tower documentation for example syntax.
+ type: dict
+ state:
+ description:
+ - Desired state of the resource.
+ default: "present"
+ choices: ["present", "absent"]
+ type: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+EXAMPLES = '''
+- tower_credential_type:
+ name: Nexus
+ description: Credentials type for Nexus
+ kind: cloud
+ inputs: "{{ lookup('file', 'tower_credential_inputs_nexus.json') }}"
+ injectors: {'extra_vars': {'nexus_credential': 'test' }}
+ state: present
+ validate_certs: false
+
+- tower_credential_type:
+ name: Nexus
+ state: absent
+'''
+
+
+RETURN = ''' # '''
+
+
+from ..module_utils.tower_api import TowerAPIModule
+
+KIND_CHOICES = {
+ 'ssh': 'Machine',
+ 'vault': 'Ansible Vault',
+ 'net': 'Network',
+ 'scm': 'Source Control',
+ 'cloud': 'Lots of others',
+ 'insights': 'Insights'
+}
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ description=dict(),
+ kind=dict(choices=list(KIND_CHOICES.keys())),
+ inputs=dict(type='dict'),
+ injectors=dict(type='dict'),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ name = module.params.get('name')
+ new_name = None
+ kind = module.params.get('kind')
+ state = module.params.get('state')
+
+ # These will be passed into the create/updates
+ credential_type_params = {
+ 'name': new_name if new_name else name,
+ 'managed_by_tower': False,
+ }
+ if kind:
+ credential_type_params['kind'] = kind
+ if module.params.get('description'):
+ credential_type_params['description'] = module.params.get('description')
+ if module.params.get('inputs'):
+ credential_type_params['inputs'] = module.params.get('inputs')
+ if module.params.get('injectors'):
+ credential_type_params['injectors'] = module.params.get('injectors')
+
+ # Attempt to look up credential_type based on the provided name
+ credential_type = module.get_one('credential_types', **{
+ 'data': {
+ 'name': name,
+ }
+ })
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(credential_type)
+
+ # If the state was present and we can let the module build or update the existing credential type, this will return on its own
+ module.create_or_update_if_needed(credential_type, credential_type_params, endpoint='credential_types', item_type='credential type')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_export.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_export.py
new file mode 100644
index 00000000..bd951d17
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_export.py
@@ -0,0 +1,166 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, John Westcott IV <john.westcott.iv@redhat.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_export
+author: "John Westcott IV (@john-westcott-iv)"
+version_added: "3.7"
+short_description: export resources from Ansible Tower.
+description:
+ - Export assets from Ansible Tower.
+options:
+ all:
+ description:
+ - Export all assets
+ type: bool
+ default: 'False'
+ organizations:
+ description:
+ - organization name to export
+ type: str
+ users:
+ description:
+ - user name to export
+ type: str
+ teams:
+ description:
+ - team name to export
+ type: str
+ credential_types:
+ description:
+ - credential type name to export
+ type: str
+ credentials:
+ description:
+ - credential name to export
+ type: str
+ notification_templates:
+ description:
+ - notification template name to export
+ type: str
+ inventory_sources:
+ description:
+ - inventory soruce to export
+ type: str
+ inventory:
+ description:
+ - inventory name to export
+ type: str
+ projects:
+ description:
+ - project name to export
+ type: str
+ job_templates:
+ description:
+ - job template name to export
+ type: str
+ workflow_job_templates:
+ description:
+ - workflow name to export
+ type: str
+requirements:
+ - "awxkit >= 9.3.0"
+notes:
+ - Specifying a name of "all" for any asset type will export all items of that asset type.
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+- name: Export all tower assets
+ tower_export:
+ all: True
+- name: Export all inventories
+ tower_export:
+ inventory: 'all'
+- name: Export a job template named "My Template" and all Credentials
+ tower_export:
+ job_template: "My Template"
+ credential: 'all'
+'''
+
+from os import environ
+import logging
+from ansible.module_utils.six.moves import StringIO
+from ..module_utils.tower_awxkit import TowerAWXKitModule
+
+try:
+ from awxkit.api.pages.api import EXPORTABLE_RESOURCES
+ HAS_EXPORTABLE_RESOURCES = True
+except ImportError:
+ HAS_EXPORTABLE_RESOURCES = False
+
+
+def main():
+ argument_spec = dict(
+ all=dict(type='bool', default=False),
+ )
+
+ # We are not going to raise an error here because the __init__ method of TowerAWXKitModule will do that for us
+ if HAS_EXPORTABLE_RESOURCES:
+ for resource in EXPORTABLE_RESOURCES:
+ argument_spec[resource] = dict(type='str')
+
+ module = TowerAWXKitModule(argument_spec=argument_spec)
+
+ if not HAS_EXPORTABLE_RESOURCES:
+ module.fail_json(msg="Your version of awxkit does not have import/export")
+
+ # The export process will never change a Tower system
+ module.json_output['changed'] = False
+
+ # The exporter code currently works like the following:
+ # Empty string == all assets of that type
+ # Non-Empty string = just one asset of that type (by name or ID)
+ # Asset type not present or None = skip asset type (unless everything is None, then export all)
+ # Here we are going to setup a dict of values to export
+ export_args = {}
+ for resource in EXPORTABLE_RESOURCES:
+ if module.params.get('all') or module.params.get(resource) == 'all':
+ # If we are exporting everything or we got the keyword "all" we pass in an empty string for this asset type
+ export_args[resource] = ''
+ else:
+ # Otherwise we take either the string or None (if the parameter was not passed) to get one or no items
+ export_args[resource] = module.params.get(resource)
+
+ # Currently the import process does not return anything on error
+ # It simply just logs to pythons logger
+ # Setup a log gobbler to get error messages from import_assets
+ log_capture_string = StringIO()
+ ch = logging.StreamHandler(log_capture_string)
+ for logger_name in ['awxkit.api.pages.api', 'awxkit.api.pages.page']:
+ logger = logging.getLogger(logger_name)
+ logger.setLevel(logging.WARNING)
+ ch.setLevel(logging.WARNING)
+
+ logger.addHandler(ch)
+ log_contents = ''
+
+ # Run the import process
+ try:
+ module.json_output['assets'] = module.get_api_v2_object().export_assets(**export_args)
+ module.exit_json(**module.json_output)
+ except Exception as e:
+ module.fail_json(msg="Failed to export assets {0}".format(e))
+ finally:
+ # Finally consume the logs incase there were any errors and die if there were
+ log_contents = log_capture_string.getvalue()
+ log_capture_string.close()
+ if log_contents != '':
+ module.fail_json(msg=log_contents)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_group.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_group.py
new file mode 100644
index 00000000..9e2eaf4e
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_group.py
@@ -0,0 +1,156 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_group
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: create, update, or destroy Ansible Tower group.
+description:
+ - Create, update, or destroy Ansible Tower groups. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - The name to use for the group.
+ required: True
+ type: str
+ description:
+ description:
+ - The description to use for the group.
+ type: str
+ inventory:
+ description:
+ - Inventory the group should be made a member of.
+ required: True
+ type: str
+ variables:
+ description:
+ - Variables to use for the group.
+ type: dict
+ hosts:
+ description:
+ - List of hosts that should be put in this group.
+ type: list
+ elements: str
+ children:
+ description:
+ - List of groups that should be nested inside in this group.
+ type: list
+ elements: str
+ aliases:
+ - groups
+ state:
+ description:
+ - Desired state of the resource.
+ default: "present"
+ choices: ["present", "absent"]
+ type: str
+ new_name:
+ description:
+ - A new name for this group (for renaming)
+ type: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+EXAMPLES = '''
+- name: Add tower group
+ tower_group:
+ name: localhost
+ description: "Local Host Group"
+ inventory: "Local Inventory"
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+import json
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ new_name=dict(),
+ description=dict(),
+ inventory=dict(required=True),
+ variables=dict(type='dict'),
+ hosts=dict(type='list', elements='str'),
+ children=dict(type='list', elements='str', aliases=['groups']),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ name = module.params.get('name')
+ new_name = module.params.get('new_name')
+ inventory = module.params.get('inventory')
+ description = module.params.get('description')
+ state = module.params.pop('state')
+ variables = module.params.get('variables')
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ inventory_id = module.resolve_name_to_id('inventories', inventory)
+
+ # Attempt to look up the object based on the provided name and inventory ID
+ group = module.get_one('groups', **{
+ 'data': {
+ 'name': name,
+ 'inventory': inventory_id
+ }
+ })
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(group)
+
+ # Create the data that gets sent for create and update
+ group_fields = {
+ 'name': new_name if new_name else name,
+ 'inventory': inventory_id,
+ }
+ if description is not None:
+ group_fields['description'] = description
+ if variables is not None:
+ group_fields['variables'] = json.dumps(variables)
+
+ association_fields = {}
+ for resource, relationship in (('hosts', 'hosts'), ('groups', 'children')):
+ name_list = module.params.get(relationship)
+ if name_list is None:
+ continue
+ id_list = []
+ for sub_name in name_list:
+ sub_obj = module.get_one(resource, **{
+ 'data': {'inventory': inventory_id, 'name': sub_name}
+ })
+ if sub_obj is None:
+ module.fail_json(msg='Could not find {0} with name {1}'.format(resource, sub_name))
+ id_list.append(sub_obj['id'])
+ if id_list:
+ association_fields[relationship] = id_list
+
+ # If the state was present we can let the module build or update the existing group, this will return on its own
+ module.create_or_update_if_needed(
+ group, group_fields, endpoint='groups', item_type='group',
+ associations=association_fields
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_host.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_host.py
new file mode 100644
index 00000000..f6bfe554
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_host.py
@@ -0,0 +1,134 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_host
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: create, update, or destroy Ansible Tower host.
+description:
+ - Create, update, or destroy Ansible Tower hosts. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - The name to use for the host.
+ required: True
+ type: str
+ new_name:
+ description:
+ - To use when changing a hosts's name.
+ type: str
+ description:
+ description:
+ - The description to use for the host.
+ type: str
+ inventory:
+ description:
+ - Inventory the host should be made a member of.
+ required: True
+ type: str
+ enabled:
+ description:
+ - If the host should be enabled.
+ type: bool
+ default: 'yes'
+ variables:
+ description:
+ - Variables to use for the host.
+ type: dict
+ state:
+ description:
+ - Desired state of the resource.
+ choices: ["present", "absent"]
+ default: "present"
+ type: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+EXAMPLES = '''
+- name: Add tower host
+ tower_host:
+ name: localhost
+ description: "Local Host Group"
+ inventory: "Local Inventory"
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+ variables:
+ example_var: 123
+'''
+
+
+from ..module_utils.tower_api import TowerAPIModule
+import json
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ new_name=dict(),
+ description=dict(),
+ inventory=dict(required=True),
+ enabled=dict(type='bool', default=True),
+ variables=dict(type='dict'),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ name = module.params.get('name')
+ new_name = module.params.get('new_name')
+ description = module.params.get('description')
+ inventory = module.params.get('inventory')
+ enabled = module.params.get('enabled')
+ state = module.params.get('state')
+ variables = module.params.get('variables')
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ inventory_id = module.resolve_name_to_id('inventories', inventory)
+
+ # Attempt to look up host based on the provided name and inventory ID
+ host = module.get_one('hosts', **{
+ 'data': {
+ 'name': name,
+ 'inventory': inventory_id
+ }
+ })
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(host)
+
+ # Create the data that gets sent for create and update
+ host_fields = {
+ 'name': new_name if new_name else name,
+ 'inventory': inventory_id,
+ 'enabled': enabled,
+ }
+ if description is not None:
+ host_fields['description'] = description
+ if variables is not None:
+ host_fields['variables'] = json.dumps(variables)
+
+ # If the state was present and we can let the module build or update the existing host, this will return on its own
+ module.create_or_update_if_needed(host, host_fields, endpoint='hosts', item_type='host')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_import.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_import.py
new file mode 100644
index 00000000..a39a98a5
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_import.py
@@ -0,0 +1,105 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, John Westcott IV <john.westcott.iv@redhat.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_import
+author: "John Westcott (@john-westcott-iv)"
+version_added: "3.7"
+short_description: import resources into Ansible Tower.
+description:
+ - Import assets into Ansible Tower. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ assets:
+ description:
+ - The assets to import.
+ - This can be the output of tower_export or loaded from a file
+ required: True
+ type: dict
+requirements:
+ - "awxkit >= 9.3.0"
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+- name: Export all assets
+ tower_export:
+ all: True
+ registeR: export_output
+
+- name: Import all tower assets from our export
+ tower_import:
+ assets: "{{ export_output.assets }}"
+
+- name: Load data from a json file created by a command like awx export --organization Default
+ tower_import:
+ assets: "{{ lookup('file', 'org.json') | from_json() }}"
+'''
+
+from ..module_utils.tower_awxkit import TowerAWXKitModule
+
+# These two lines are not needed if awxkit changes to do progamatic notifications on issues
+from ansible.module_utils.six.moves import StringIO
+import logging
+
+# In this module we don't use EXPORTABLE_RESOURCES, we just want to validate that our installed awxkit has import/export
+try:
+ from awxkit.api.pages.api import EXPORTABLE_RESOURCES
+ HAS_EXPORTABLE_RESOURCES = True
+except ImportError:
+ HAS_EXPORTABLE_RESOURCES = False
+
+
+def main():
+ argument_spec = dict(
+ assets=dict(type='dict', required=True)
+ )
+
+ module = TowerAWXKitModule(argument_spec=argument_spec, supports_check_mode=False)
+
+ assets = module.params.get('assets')
+
+ if not HAS_EXPORTABLE_RESOURCES:
+ module.fail_json(msg="Your version of awxkit does not appear to have import/export")
+
+ # Currently the import process does not return anything on error
+ # It simply just logs to pythons logger
+ # Setup a log gobbler to get error messages from import_assets
+ logger = logging.getLogger('awxkit.api.pages.api')
+ logger.setLevel(logging.WARNING)
+ log_capture_string = StringIO()
+ ch = logging.StreamHandler(log_capture_string)
+ ch.setLevel(logging.WARNING)
+ logger.addHandler(ch)
+ log_contents = ''
+
+ # Run the import process
+ try:
+ module.json_output['changed'] = module.get_api_v2_object().import_assets(assets)
+ except Exception as e:
+ module.fail_json(msg="Failed to import assets {0}".format(e))
+ finally:
+ # Finally consume the logs incase there were any errors and die if there were
+ log_contents = log_capture_string.getvalue()
+ log_capture_string.close()
+ if log_contents != '':
+ module.fail_json(msg=log_contents)
+
+ module.exit_json(**module.json_output)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_inventory.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_inventory.py
new file mode 100644
index 00000000..7f03645e
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_inventory.py
@@ -0,0 +1,138 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_inventory
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: create, update, or destroy Ansible Tower inventory.
+description:
+ - Create, update, or destroy Ansible Tower inventories. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - The name to use for the inventory.
+ required: True
+ type: str
+ description:
+ description:
+ - The description to use for the inventory.
+ type: str
+ organization:
+ description:
+ - Organization the inventory belongs to.
+ required: True
+ type: str
+ variables:
+ description:
+ - Inventory variables.
+ type: dict
+ kind:
+ description:
+ - The kind field. Cannot be modified after created.
+ default: ""
+ choices: ["", "smart"]
+ type: str
+ host_filter:
+ description:
+ - The host_filter field. Only useful when C(kind=smart).
+ type: str
+ state:
+ description:
+ - Desired state of the resource.
+ default: "present"
+ choices: ["present", "absent"]
+ type: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+EXAMPLES = '''
+- name: Add tower inventory
+ tower_inventory:
+ name: "Foo Inventory"
+ description: "Our Foo Cloud Servers"
+ organization: "Bar Org"
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+'''
+
+
+from ..module_utils.tower_api import TowerAPIModule
+import json
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ description=dict(),
+ organization=dict(required=True),
+ variables=dict(type='dict'),
+ kind=dict(choices=['', 'smart'], default=''),
+ host_filter=dict(),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ name = module.params.get('name')
+ description = module.params.get('description')
+ organization = module.params.get('organization')
+ variables = module.params.get('variables')
+ state = module.params.get('state')
+ kind = module.params.get('kind')
+ host_filter = module.params.get('host_filter')
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ org_id = module.resolve_name_to_id('organizations', organization)
+
+ # Attempt to look up inventory based on the provided name and org ID
+ inventory = module.get_one('inventories', **{
+ 'data': {
+ 'name': name,
+ 'organization': org_id
+ }
+ })
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(inventory)
+
+ # Create the data that gets sent for create and update
+ inventory_fields = {
+ 'name': name,
+ 'organization': org_id,
+ 'kind': kind,
+ 'host_filter': host_filter,
+ }
+ if description is not None:
+ inventory_fields['description'] = description
+ if variables is not None:
+ inventory_fields['variables'] = json.dumps(variables)
+
+ # We need to perform a check to make sure you are not trying to convert a regular inventory into a smart one.
+ if inventory and inventory['kind'] == '' and inventory_fields['kind'] == 'smart':
+ module.fail_json(msg='You cannot turn a regular inventory into a "smart" inventory.')
+
+ # If the state was present and we can let the module build or update the existing inventory, this will return on its own
+ module.create_or_update_if_needed(inventory, inventory_fields, endpoint='inventories', item_type='inventory')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_inventory_source.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_inventory_source.py
new file mode 100644
index 00000000..5b0e2961
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_inventory_source.py
@@ -0,0 +1,277 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# Copyright: (c) 2018, Adrien Fleury <fleu42@gmail.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_inventory_source
+author: "Adrien Fleury (@fleu42)"
+short_description: create, update, or destroy Ansible Tower inventory source.
+description:
+ - Create, update, or destroy Ansible Tower inventory source. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - The name to use for the inventory source.
+ required: True
+ type: str
+ new_name:
+ description:
+ - A new name for this assets (will rename the asset)
+ type: str
+ description:
+ description:
+ - The description to use for the inventory source.
+ type: str
+ inventory:
+ description:
+ - Inventory the group should be made a member of.
+ required: True
+ type: str
+ source:
+ description:
+ - The source to use for this group.
+ choices: [ "scm", "ec2", "gce", "azure_rm", "vmware", "satellite6", "openstack", "rhv", "tower", "custom" ]
+ type: str
+ source_path:
+ description:
+ - For an SCM based inventory source, the source path points to the file within the repo to use as an inventory.
+ type: str
+ source_script:
+ description:
+ - Inventory script to be used when group type is C(custom).
+ type: str
+ source_vars:
+ description:
+ - The variables or environment fields to apply to this source type.
+ type: dict
+ credential:
+ description:
+ - Credential to use for the source.
+ type: str
+ source_regions:
+ description:
+ - Regions for cloud provider.
+ type: str
+ instance_filters:
+ description:
+ - Comma-separated list of filter expressions for matching hosts.
+ type: str
+ group_by:
+ description:
+ - Limit groups automatically created from inventory source.
+ type: str
+ overwrite:
+ description:
+ - Delete child groups and hosts not found in source.
+ type: bool
+ overwrite_vars:
+ description:
+ - Override vars in child groups and hosts with those from external source.
+ type: bool
+ custom_virtualenv:
+ description:
+ - Local absolute file path containing a custom Python virtualenv to use.
+ type: str
+ timeout:
+ description: The amount of time (in seconds) to run before the task is canceled.
+ type: int
+ verbosity:
+ description: The verbosity level to run this inventory source under.
+ type: int
+ choices: [ 0, 1, 2 ]
+ update_on_launch:
+ description:
+ - Refresh inventory data from its source each time a job is run.
+ type: bool
+ update_cache_timeout:
+ description:
+ - Time in seconds to consider an inventory sync to be current.
+ type: int
+ source_project:
+ description:
+ - Project to use as source with scm option
+ type: str
+ update_on_project_update:
+ description: Update this source when the related project updates if source is C(scm)
+ type: bool
+ state:
+ description:
+ - Desired state of the resource.
+ default: "present"
+ choices: ["present", "absent"]
+ type: str
+ notification_templates_started:
+ description:
+ - list of notifications to send on start
+ type: list
+ elements: str
+ notification_templates_success:
+ description:
+ - list of notifications to send on success
+ type: list
+ elements: str
+ notification_templates_error:
+ description:
+ - list of notifications to send on error
+ type: list
+ elements: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+- name: Add an inventory source
+ tower_inventory_source:
+ name: "source-inventory"
+ description: Source for inventory
+ inventory: previously-created-inventory
+ credential: previously-created-credential
+ overwrite: True
+ update_on_launch: True
+ source_vars:
+ private: false
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+from json import dumps
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ new_name=dict(),
+ description=dict(),
+ inventory=dict(required=True),
+ #
+ # How do we handle manual and file? Tower does not seem to be able to activate them
+ #
+ source=dict(choices=["scm", "ec2", "gce",
+ "azure_rm", "vmware", "satellite6",
+ "openstack", "rhv", "tower", "custom"]),
+ source_path=dict(),
+ source_script=dict(),
+ source_vars=dict(type='dict'),
+ credential=dict(),
+ source_regions=dict(),
+ instance_filters=dict(),
+ group_by=dict(),
+ overwrite=dict(type='bool'),
+ overwrite_vars=dict(type='bool'),
+ custom_virtualenv=dict(),
+ timeout=dict(type='int'),
+ verbosity=dict(type='int', choices=[0, 1, 2]),
+ update_on_launch=dict(type='bool'),
+ update_cache_timeout=dict(type='int'),
+ source_project=dict(),
+ update_on_project_update=dict(type='bool'),
+ notification_templates_started=dict(type="list", elements='str'),
+ notification_templates_success=dict(type="list", elements='str'),
+ notification_templates_error=dict(type="list", elements='str'),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ name = module.params.get('name')
+ new_name = module.params.get('new_name')
+ inventory = module.params.get('inventory')
+ source_script = module.params.get('source_script')
+ credential = module.params.get('credential')
+ source_project = module.params.get('source_project')
+ state = module.params.get('state')
+
+ # Attempt to look up inventory source based on the provided name and inventory ID
+ inventory_id = module.resolve_name_to_id('inventories', inventory)
+ inventory_source = module.get_one('inventory_sources', **{
+ 'data': {
+ 'name': name,
+ 'inventory': inventory_id,
+ }
+ })
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(inventory_source)
+
+ # Attempt to look up associated field items the user specified.
+ association_fields = {}
+
+ notifications_start = module.params.get('notification_templates_started')
+ if notifications_start is not None:
+ association_fields['notification_templates_started'] = []
+ for item in notifications_start:
+ association_fields['notification_templates_started'].append(module.resolve_name_to_id('notification_templates', item))
+
+ notifications_success = module.params.get('notification_templates_success')
+ if notifications_success is not None:
+ association_fields['notification_templates_success'] = []
+ for item in notifications_success:
+ association_fields['notification_templates_success'].append(module.resolve_name_to_id('notification_templates', item))
+
+ notifications_error = module.params.get('notification_templates_error')
+ if notifications_error is not None:
+ association_fields['notification_templates_error'] = []
+ for item in notifications_error:
+ association_fields['notification_templates_error'].append(module.resolve_name_to_id('notification_templates', item))
+
+ # Create the data that gets sent for create and update
+ inventory_source_fields = {
+ 'name': new_name if new_name else name,
+ 'inventory': inventory_id,
+ }
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ if credential is not None:
+ inventory_source_fields['credential'] = module.resolve_name_to_id('credentials', credential)
+ if source_project is not None:
+ inventory_source_fields['source_project'] = module.resolve_name_to_id('projects', source_project)
+ if source_script is not None:
+ inventory_source_fields['source_script'] = module.resolve_name_to_id('inventory_scripts', source_script)
+
+ OPTIONAL_VARS = (
+ 'description', 'source', 'source_path', 'source_vars',
+ 'source_regions', 'instance_filters', 'group_by',
+ 'overwrite', 'overwrite_vars', 'custom_virtualenv',
+ 'timeout', 'verbosity', 'update_on_launch', 'update_cache_timeout',
+ 'update_on_project_update'
+ )
+
+ # Layer in all remaining optional information
+ for field_name in OPTIONAL_VARS:
+ field_val = module.params.get(field_name)
+ if field_val is not None:
+ inventory_source_fields[field_name] = field_val
+
+ # Attempt to JSON encode source vars
+ if inventory_source_fields.get('source_vars', None):
+ inventory_source_fields['source_vars'] = dumps(inventory_source_fields['source_vars'])
+
+ # Sanity check on arguments
+ if state == 'present' and not inventory_source and not inventory_source_fields['source']:
+ module.fail_json(msg="If creating a new inventory source, the source param must be present")
+
+ # If the state was present we can let the module build or update the existing inventory_source, this will return on its own
+ module.create_or_update_if_needed(
+ inventory_source, inventory_source_fields,
+ endpoint='inventory_sources', item_type='inventory source',
+ associations=association_fields
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_cancel.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_cancel.py
new file mode 100644
index 00000000..7404d452
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_cancel.py
@@ -0,0 +1,99 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_job_cancel
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: Cancel an Ansible Tower Job.
+description:
+ - Cancel Ansible Tower jobs. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ job_id:
+ description:
+ - ID of the job to cancel
+ required: True
+ type: int
+ fail_if_not_running:
+ description:
+ - Fail loudly if the I(job_id) can not be canceled
+ default: False
+ type: bool
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+- name: Cancel job
+ tower_job_cancel:
+ job_id: job.id
+'''
+
+RETURN = '''
+id:
+ description: job id requesting to cancel
+ returned: success
+ type: int
+ sample: 94
+'''
+
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ job_id=dict(type='int', required=True),
+ fail_if_not_running=dict(type='bool', default=False),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ job_id = module.params.get('job_id')
+ fail_if_not_running = module.params.get('fail_if_not_running')
+
+ # Attempt to look up the job based on the provided name
+ job = module.get_one('jobs', **{
+ 'data': {
+ 'id': job_id,
+ }
+ })
+
+ if job is None:
+ module.fail_json(msg="Unable to find job with id {0}".format(job_id))
+
+ cancel_page = module.get_endpoint(job['related']['cancel'])
+ if 'json' not in cancel_page or 'can_cancel' not in cancel_page['json']:
+ module.fail_json(msg="Failed to cancel job, got unexpected response from tower", **{'response': cancel_page})
+
+ if not cancel_page['json']['can_cancel']:
+ if fail_if_not_running:
+ module.fail_json(msg="Job is not running")
+ else:
+ module.exit_json(**{'changed': False})
+
+ results = module.post_endpoint(job['related']['cancel'], **{'data': {}})
+
+ if results['status_code'] != 202:
+ module.fail_json(msg="Failed to cancel job, see response for details", **{'response': results})
+
+ module.exit_json(**{'changed': True})
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_launch.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_launch.py
new file mode 100644
index 00000000..25a1c52f
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_launch.py
@@ -0,0 +1,227 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_job_launch
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: Launch an Ansible Job.
+description:
+ - Launch an Ansible Tower jobs. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - Name of the job template to use.
+ required: True
+ type: str
+ aliases: ['job_template']
+ job_type:
+ description:
+ - Job_type to use for the job, only used if prompt for job_type is set.
+ choices: ["run", "check"]
+ type: str
+ inventory:
+ description:
+ - Inventory to use for the job, only used if prompt for inventory is set.
+ type: str
+ credentials:
+ description:
+ - Credential to use for job, only used if prompt for credential is set.
+ type: list
+ aliases: ['credential']
+ elements: str
+ extra_vars:
+ description:
+ - extra_vars to use for the Job Template.
+ - ask_extra_vars needs to be set to True via tower_job_template module
+ when creating the Job Template.
+ type: dict
+ limit:
+ description:
+ - Limit to use for the I(job_template).
+ type: str
+ tags:
+ description:
+ - Specific tags to use for from playbook.
+ type: list
+ elements: str
+ scm_branch:
+ description:
+ - A specific of the SCM project to run the template on.
+ - This is only applicable if your project allows for branch override.
+ type: str
+ skip_tags:
+ description:
+ - Specific tags to skip from the playbook.
+ type: list
+ elements: str
+ verbosity:
+ description:
+ - Verbosity level for this job run
+ type: int
+ choices: [ 0, 1, 2, 3, 4, 5 ]
+ diff_mode:
+ description:
+ - Show the changes made by Ansible tasks where supported
+ type: bool
+ credential_passwords:
+ description:
+ - Passwords for credentials which are set to prompt on launch
+ type: dict
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+- name: Launch a job
+ tower_job_launch:
+ job_template: "My Job Template"
+ register: job
+
+- name: Launch a job template with extra_vars on remote Tower instance
+ tower_job_launch:
+ job_template: "My Job Template"
+ extra_vars:
+ var1: "My First Variable"
+ var2: "My Second Variable"
+ var3: "My Third Variable"
+ job_type: run
+
+- name: Launch a job with inventory and credential
+ tower_job_launch:
+ job_template: "My Job Template"
+ inventory: "My Inventory"
+ credential: "My Credential"
+ register: job
+- name: Wait for job max 120s
+ tower_job_wait:
+ job_id: "{{ job.id }}"
+ timeout: 120
+'''
+
+RETURN = '''
+id:
+ description: job id of the newly launched job
+ returned: success
+ type: int
+ sample: 86
+status:
+ description: status of newly launched job
+ returned: success
+ type: str
+ sample: pending
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True, aliases=['job_template']),
+ job_type=dict(choices=['run', 'check']),
+ inventory=dict(default=None),
+ # Credentials will be a str instead of a list for backwards compatability
+ credentials=dict(type='list', default=None, aliases=['credential'], elements='str'),
+ limit=dict(),
+ tags=dict(type='list', elements='str'),
+ extra_vars=dict(type='dict'),
+ scm_branch=dict(),
+ skip_tags=dict(type='list', elements='str'),
+ verbosity=dict(type='int', choices=[0, 1, 2, 3, 4, 5]),
+ diff_mode=dict(type='bool'),
+ credential_passwords=dict(type='dict'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ optional_args = {}
+ # Extract our parameters
+ name = module.params.get('name')
+ optional_args['job_type'] = module.params.get('job_type')
+ inventory = module.params.get('inventory')
+ credentials = module.params.get('credentials')
+ optional_args['limit'] = module.params.get('limit')
+ optional_args['tags'] = module.params.get('tags')
+ optional_args['extra_vars'] = module.params.get('extra_vars')
+ optional_args['scm_branch'] = module.params.get('scm_branch')
+ optional_args['skip_tags'] = module.params.get('skip_tags')
+ optional_args['verbosity'] = module.params.get('verbosity')
+ optional_args['diff_mode'] = module.params.get('diff_mode')
+ optional_args['credential_passwords'] = module.params.get('credential_passwords')
+
+ # Create a datastructure to pass into our job launch
+ post_data = {}
+ for key in optional_args.keys():
+ if optional_args[key]:
+ post_data[key] = optional_args[key]
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ if inventory:
+ post_data['inventory'] = module.resolve_name_to_id('inventories', inventory)
+
+ if credentials:
+ post_data['credentials'] = []
+ for credential in credentials:
+ post_data['credentials'].append(module.resolve_name_to_id('credentials', credential))
+
+ # Attempt to look up job_template based on the provided name
+ job_template = module.get_one('job_templates', **{
+ 'data': {
+ 'name': name,
+ }
+ })
+
+ if job_template is None:
+ module.fail_json(msg="Unable to find job template by name {0}".format(name))
+
+ # The API will allow you to submit values to a jb launch that are not prompt on launch.
+ # Therefore, we will test to see if anything is set which is not prompt on launch and fail.
+ check_vars_to_prompts = {
+ 'scm_branch': 'ask_scm_branch_on_launch',
+ 'diff_mode': 'ask_diff_mode_on_launch',
+ 'extra_vars': 'ask_variables_on_launch',
+ 'limit': 'ask_limit_on_launch',
+ 'tags': 'ask_tags_on_launch',
+ 'skip_tags': 'ask_skip_tags_on_launch',
+ 'job_type': 'ask_job_type_on_launch',
+ 'verbosity': 'ask_verbosity_on_launch',
+ 'inventory': 'ask_inventory_on_launch',
+ 'credentials': 'ask_credential_on_launch',
+ }
+
+ param_errors = []
+ for variable_name in check_vars_to_prompts:
+ if module.params.get(variable_name) and not job_template[check_vars_to_prompts[variable_name]]:
+ param_errors.append("The field {0} was specified but the job template does not allow for it to be overridden".format(variable_name))
+ if len(param_errors) > 0:
+ module.fail_json(msg="Parameters specified which can not be passed into job template, see errors for details", **{'errors': param_errors})
+
+ # Launch the job
+ results = module.post_endpoint(job_template['related']['launch'], **{'data': post_data})
+
+ if results['status_code'] != 201:
+ module.fail_json(msg="Failed to launch job, see response for details", **{'response': results})
+
+ module.exit_json(**{
+ 'changed': True,
+ 'id': results['json']['id'],
+ 'status': results['json']['status'],
+ })
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_list.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_list.py
new file mode 100644
index 00000000..642a48b0
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_list.py
@@ -0,0 +1,126 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_job_list
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: List Ansible Tower jobs.
+description:
+ - List Ansible Tower jobs. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ status:
+ description:
+ - Only list jobs with this status.
+ choices: ['pending', 'waiting', 'running', 'error', 'failed', 'canceled', 'successful']
+ type: str
+ page:
+ description:
+ - Page number of the results to fetch.
+ type: int
+ all_pages:
+ description:
+ - Fetch all the pages and return a single result.
+ type: bool
+ default: 'no'
+ query:
+ description:
+ - Query used to further filter the list of jobs. C({"foo":"bar"}) will be passed at C(?foo=bar)
+ type: dict
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+EXAMPLES = '''
+- name: List running jobs for the testing.yml playbook
+ tower_job_list:
+ status: running
+ query: {"playbook": "testing.yml"}
+ tower_config_file: "~/tower_cli.cfg"
+ register: testing_jobs
+'''
+
+RETURN = '''
+count:
+ description: Total count of objects return
+ returned: success
+ type: int
+ sample: 51
+next:
+ description: next page available for the listing
+ returned: success
+ type: int
+ sample: 3
+previous:
+ description: previous page available for the listing
+ returned: success
+ type: int
+ sample: 1
+results:
+ description: a list of job objects represented as dictionaries
+ returned: success
+ type: list
+ sample: [{"allow_simultaneous": false, "artifacts": {}, "ask_credential_on_launch": false,
+ "ask_inventory_on_launch": false, "ask_job_type_on_launch": false, "failed": false,
+ "finished": "2017-02-22T15:09:05.633942Z", "force_handlers": false, "forks": 0, "id": 2,
+ "inventory": 1, "job_explanation": "", "job_tags": "", "job_template": 5, "job_type": "run"}, ...]
+'''
+
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ status=dict(choices=['pending', 'waiting', 'running', 'error', 'failed', 'canceled', 'successful']),
+ page=dict(type='int'),
+ all_pages=dict(type='bool', default=False),
+ query=dict(type='dict'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[
+ ('page', 'all_pages'),
+ ]
+ )
+
+ # Extract our parameters
+ query = module.params.get('query')
+ status = module.params.get('status')
+ page = module.params.get('page')
+ all_pages = module.params.get('all_pages')
+
+ job_search_data = {}
+ if page:
+ job_search_data['page'] = page
+ if status:
+ job_search_data['status'] = status
+ if query:
+ job_search_data.update(query)
+ if all_pages:
+ job_list = module.get_all_endpoint('jobs', **{'data': job_search_data})
+ else:
+ job_list = module.get_endpoint('jobs', **{'data': job_search_data})
+
+ # Attempt to look up jobs based on the status
+ module.exit_json(**job_list['json'])
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_template.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_template.py
new file mode 100644
index 00000000..1f12d5aa
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_template.py
@@ -0,0 +1,526 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_job_template
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: create, update, or destroy Ansible Tower job templates.
+description:
+ - Create, update, or destroy Ansible Tower job templates. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - Name to use for the job template.
+ required: True
+ type: str
+ new_name:
+ description:
+ - Setting this option will change the existing name (looed up via the name field.
+ type: str
+ description:
+ description:
+ - Description to use for the job template.
+ type: str
+ job_type:
+ description:
+ - The job type to use for the job template.
+ choices: ["run", "check"]
+ type: str
+ inventory:
+ description:
+ - Name of the inventory to use for the job template.
+ type: str
+ organization:
+ description:
+ - Organization the job template exists in.
+ - Used to help lookup the object, cannot be modified using this module.
+ - The Organization is inferred from the associated project
+ - If not provided, will lookup by name only, which does not work with duplicates.
+ - Requires Tower Version 3.7.0 or AWX 10.0.0 IS NOT backwards compatible with earlier versions.
+ type: str
+ project:
+ description:
+ - Name of the project to use for the job template.
+ type: str
+ playbook:
+ description:
+ - Path to the playbook to use for the job template within the project provided.
+ type: str
+ credential:
+ description:
+ - Name of the credential to use for the job template.
+ - Deprecated, use 'credentials'.
+ type: str
+ credentials:
+ description:
+ - List of credentials to use for the job template.
+ type: list
+ elements: str
+ vault_credential:
+ description:
+ - Name of the vault credential to use for the job template.
+ - Deprecated, use 'credentials'.
+ type: str
+ forks:
+ description:
+ - The number of parallel or simultaneous processes to use while executing the playbook.
+ type: int
+ limit:
+ description:
+ - A host pattern to further constrain the list of hosts managed or affected by the playbook
+ type: str
+ verbosity:
+ description:
+ - Control the output level Ansible produces as the playbook runs. 0 - Normal, 1 - Verbose, 2 - More Verbose, 3 - Debug, 4 - Connection Debug.
+ choices: [0, 1, 2, 3, 4]
+ default: 0
+ type: int
+ extra_vars:
+ description:
+ - Specify C(extra_vars) for the template.
+ type: dict
+ job_tags:
+ description:
+ - Comma separated list of the tags to use for the job template.
+ type: str
+ force_handlers:
+ description:
+ - Enable forcing playbook handlers to run even if a task fails.
+ type: bool
+ default: 'no'
+ aliases:
+ - force_handlers_enabled
+ skip_tags:
+ description:
+ - Comma separated list of the tags to skip for the job template.
+ type: str
+ start_at_task:
+ description:
+ - Start the playbook at the task matching this name.
+ type: str
+ diff_mode:
+ description:
+ - Enable diff mode for the job template.
+ type: bool
+ aliases:
+ - diff_mode_enabled
+ default: 'no'
+ use_fact_cache:
+ description:
+ - Enable use of fact caching for the job template.
+ type: bool
+ default: 'no'
+ aliases:
+ - fact_caching_enabled
+ host_config_key:
+ description:
+ - Allow provisioning callbacks using this host config key.
+ type: str
+ ask_scm_branch_on_launch:
+ description:
+ - Prompt user for (scm branch) on launch.
+ type: bool
+ default: 'False'
+ ask_diff_mode_on_launch:
+ description:
+ - Prompt user to enable diff mode (show changes) to files when supported by modules.
+ type: bool
+ default: 'False'
+ aliases:
+ - ask_diff_mode
+ ask_variables_on_launch:
+ description:
+ - Prompt user for (extra_vars) on launch.
+ type: bool
+ default: 'False'
+ aliases:
+ - ask_extra_vars
+ ask_limit_on_launch:
+ description:
+ - Prompt user for a limit on launch.
+ type: bool
+ default: 'False'
+ aliases:
+ - ask_limit
+ ask_tags_on_launch:
+ description:
+ - Prompt user for job tags on launch.
+ type: bool
+ default: 'False'
+ aliases:
+ - ask_tags
+ ask_skip_tags_on_launch:
+ description:
+ - Prompt user for job tags to skip on launch.
+ type: bool
+ default: 'False'
+ aliases:
+ - ask_skip_tags
+ ask_job_type_on_launch:
+ description:
+ - Prompt user for job type on launch.
+ type: bool
+ default: 'False'
+ aliases:
+ - ask_job_type
+ ask_verbosity_on_launch:
+ description:
+ - Prompt user to choose a verbosity level on launch.
+ type: bool
+ default: 'False'
+ aliases:
+ - ask_verbosity
+ ask_inventory_on_launch:
+ description:
+ - Prompt user for inventory on launch.
+ type: bool
+ default: 'False'
+ aliases:
+ - ask_inventory
+ ask_credential_on_launch:
+ description:
+ - Prompt user for credential on launch.
+ type: bool
+ default: 'False'
+ aliases:
+ - ask_credential
+ survey_enabled:
+ description:
+ - Enable a survey on the job template.
+ type: bool
+ default: 'no'
+ survey_spec:
+ description:
+ - JSON/YAML dict formatted survey definition.
+ type: dict
+ become_enabled:
+ description:
+ - Activate privilege escalation.
+ type: bool
+ default: 'no'
+ allow_simultaneous:
+ description:
+ - Allow simultaneous runs of the job template.
+ type: bool
+ default: 'no'
+ aliases:
+ - concurrent_jobs_enabled
+ timeout:
+ description:
+ - Maximum time in seconds to wait for a job to finish (server-side).
+ type: int
+ custom_virtualenv:
+ description:
+ - Local absolute file path containing a custom Python virtualenv to use.
+ type: str
+ job_slice_count:
+ description:
+ - The number of jobs to slice into at runtime. Will cause the Job Template to launch a workflow if value is greater than 1.
+ type: int
+ default: '1'
+ webhook_service:
+ description:
+ - Service that webhook requests will be accepted from
+ type: str
+ choices:
+ - ''
+ - 'github'
+ - 'gitlab'
+ webhook_credential:
+ description:
+ - Personal Access Token for posting back the status to the service API
+ type: str
+ scm_branch:
+ description:
+ - Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true.
+ type: str
+ default: ''
+ labels:
+ description:
+ - The labels applied to this job template
+ type: list
+ elements: str
+ state:
+ description:
+ - Desired state of the resource.
+ default: "present"
+ choices: ["present", "absent"]
+ type: str
+ notification_templates_started:
+ description:
+ - list of notifications to send on start
+ type: list
+ elements: str
+ notification_templates_success:
+ description:
+ - list of notifications to send on success
+ type: list
+ elements: str
+ notification_templates_error:
+ description:
+ - list of notifications to send on error
+ type: list
+ elements: str
+
+extends_documentation_fragment: awx.awx.auth
+
+notes:
+ - JSON for survey_spec can be found in Tower API Documentation. See
+ U(https://docs.ansible.com/ansible-tower/latest/html/towerapi/api_ref.html#/Job_Templates/Job_Templates_job_templates_survey_spec_create)
+ for POST operation payload example.
+'''
+
+
+EXAMPLES = '''
+- name: Create Tower Ping job template
+ tower_job_template:
+ name: "Ping"
+ job_type: "run"
+ organization: "Default"
+ inventory: "Local"
+ project: "Demo"
+ playbook: "ping.yml"
+ credentials:
+ - "Local"
+ state: "present"
+ tower_config_file: "~/tower_cli.cfg"
+ survey_enabled: yes
+ survey_spec: "{{ lookup('file', 'my_survey.json') }}"
+ custom_virtualenv: "/var/lib/awx/venv/custom-venv/"
+
+- name: Add start notification to Job Template
+ tower_job_template:
+ name: "Ping"
+ notification_templates_started:
+ - Notification1
+ - Notification2
+
+- name: Remove Notification1 start notification from Job Template
+ tower_job_template:
+ name: "Ping"
+ notification_templates_started:
+ - Notification2
+
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+import json
+
+
+def update_survey(module, last_request):
+ spec_endpoint = last_request.get('related', {}).get('survey_spec')
+ if module.params.get('survey_spec') == {}:
+ response = module.delete_endpoint(spec_endpoint)
+ if response['status_code'] != 200:
+ # Not sure how to make this actually return a non 200 to test what to dump in the respinse
+ module.fail_json(msg="Failed to delete survey: {0}".format(response['json']))
+ else:
+ response = module.post_endpoint(spec_endpoint, **{'data': module.params.get('survey_spec')})
+ if response['status_code'] != 200:
+ module.fail_json(msg="Failed to update survey: {0}".format(response['json']['error']))
+ module.exit_json(**module.json_output)
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ new_name=dict(),
+ description=dict(default=''),
+ organization=dict(),
+ job_type=dict(choices=['run', 'check']),
+ inventory=dict(),
+ project=dict(),
+ playbook=dict(),
+ credential=dict(default=''),
+ vault_credential=dict(default=''),
+ custom_virtualenv=dict(),
+ credentials=dict(type='list', elements='str'),
+ forks=dict(type='int'),
+ limit=dict(default=''),
+ verbosity=dict(type='int', choices=[0, 1, 2, 3, 4], default=0),
+ extra_vars=dict(type='dict'),
+ job_tags=dict(default=''),
+ force_handlers=dict(type='bool', default=False, aliases=['force_handlers_enabled']),
+ skip_tags=dict(default=''),
+ start_at_task=dict(default=''),
+ timeout=dict(type='int', default=0),
+ use_fact_cache=dict(type='bool', aliases=['fact_caching_enabled']),
+ host_config_key=dict(),
+ ask_diff_mode_on_launch=dict(type='bool', aliases=['ask_diff_mode']),
+ ask_variables_on_launch=dict(type='bool', aliases=['ask_extra_vars']),
+ ask_limit_on_launch=dict(type='bool', aliases=['ask_limit']),
+ ask_tags_on_launch=dict(type='bool', aliases=['ask_tags']),
+ ask_skip_tags_on_launch=dict(type='bool', aliases=['ask_skip_tags']),
+ ask_job_type_on_launch=dict(type='bool', aliases=['ask_job_type']),
+ ask_verbosity_on_launch=dict(type='bool', aliases=['ask_verbosity']),
+ ask_inventory_on_launch=dict(type='bool', aliases=['ask_inventory']),
+ ask_credential_on_launch=dict(type='bool', aliases=['ask_credential']),
+ survey_enabled=dict(type='bool'),
+ survey_spec=dict(type="dict"),
+ become_enabled=dict(type='bool'),
+ diff_mode=dict(type='bool', aliases=['diff_mode_enabled']),
+ allow_simultaneous=dict(type='bool', aliases=['concurrent_jobs_enabled']),
+ scm_branch=dict(),
+ ask_scm_branch_on_launch=dict(type='bool'),
+ job_slice_count=dict(type='int', default='1'),
+ webhook_service=dict(choices=['github', 'gitlab', '']),
+ webhook_credential=dict(),
+ labels=dict(type="list", elements='str'),
+ notification_templates_started=dict(type="list", elements='str'),
+ notification_templates_success=dict(type="list", elements='str'),
+ notification_templates_error=dict(type="list", elements='str'),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ name = module.params.get('name')
+ new_name = module.params.get("new_name")
+ state = module.params.get('state')
+
+ # Deal with legacy credential and vault_credential
+ credential = module.params.get('credential')
+ vault_credential = module.params.get('vault_credential')
+ credentials = module.params.get('credentials')
+ if vault_credential != '':
+ if credentials is None:
+ credentials = []
+ credentials.append(vault_credential)
+ if credential != '':
+ if credentials is None:
+ credentials = []
+ credentials.append(credential)
+
+ new_fields = {}
+ search_fields = {'name': name}
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ organization_id = None
+ organization = module.params.get('organization')
+ if organization:
+ organization_id = module.resolve_name_to_id('organizations', organization)
+ search_fields['organization'] = new_fields['organization'] = organization_id
+
+ # Attempt to look up an existing item based on the provided data
+ existing_item = module.get_one('job_templates', **{'data': search_fields})
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(existing_item)
+
+ # Create the data that gets sent for create and update
+ new_fields['name'] = new_name if new_name else name
+ for field_name in (
+ 'description', 'job_type', 'playbook', 'scm_branch', 'forks', 'limit', 'verbosity',
+ 'job_tags', 'force_handlers', 'skip_tags', 'start_at_task', 'timeout', 'use_fact_cache',
+ 'host_config_key', 'ask_scm_branch_on_launch', 'ask_diff_mode_on_launch', 'ask_variables_on_launch',
+ 'ask_limit_on_launch', 'ask_tags_on_launch', 'ask_skip_tags_on_launch', 'ask_job_type_on_launch',
+ 'ask_verbosity_on_launch', 'ask_inventory_on_launch', 'ask_credential_on_launch', 'survey_enabled',
+ 'become_enabled', 'diff_mode', 'allow_simultaneous', 'custom_virtualenv', 'job_slice_count', 'webhook_service',
+ ):
+ field_val = module.params.get(field_name)
+ if field_val:
+ new_fields[field_name] = field_val
+
+ # Special treatment of extra_vars parameter
+ extra_vars = module.params.get('extra_vars')
+ if extra_vars is not None:
+ new_fields['extra_vars'] = json.dumps(extra_vars)
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ inventory = module.params.get('inventory')
+ project = module.params.get('project')
+ webhook_credential = module.params.get('webhook_credential')
+
+ if inventory is not None:
+ new_fields['inventory'] = module.resolve_name_to_id('inventories', inventory)
+ if project is not None:
+ if organization_id is not None:
+ project_data = module.get_one('projects', **{
+ 'data': {
+ 'name': project,
+ 'organization': organization_id,
+ }
+ })
+ if project_data is None:
+ module.fail_json(msg="The project {0} in organization {1} was not found on the Tower server".format(
+ project, organization
+ ))
+ new_fields['project'] = project_data['id']
+ else:
+ new_fields['project'] = module.resolve_name_to_id('projects', project)
+ if webhook_credential is not None:
+ new_fields['webhook_credential'] = module.resolve_name_to_id('credentials', webhook_credential)
+
+ association_fields = {}
+
+ if credentials is not None:
+ association_fields['credentials'] = []
+ for item in credentials:
+ association_fields['credentials'].append(module.resolve_name_to_id('credentials', item))
+
+ labels = module.params.get('labels')
+ if labels is not None:
+ association_fields['labels'] = []
+ for item in labels:
+ association_fields['labels'].append(module.resolve_name_to_id('labels', item))
+
+ notifications_start = module.params.get('notification_templates_started')
+ if notifications_start is not None:
+ association_fields['notification_templates_started'] = []
+ for item in notifications_start:
+ association_fields['notification_templates_started'].append(module.resolve_name_to_id('notification_templates', item))
+
+ notifications_success = module.params.get('notification_templates_success')
+ if notifications_success is not None:
+ association_fields['notification_templates_success'] = []
+ for item in notifications_success:
+ association_fields['notification_templates_success'].append(module.resolve_name_to_id('notification_templates', item))
+
+ notifications_error = module.params.get('notification_templates_error')
+ if notifications_error is not None:
+ association_fields['notification_templates_error'] = []
+ for item in notifications_error:
+ association_fields['notification_templates_error'].append(module.resolve_name_to_id('notification_templates', item))
+
+ on_change = None
+ new_spec = module.params.get('survey_spec')
+ if new_spec is not None:
+ existing_spec = None
+ if existing_item:
+ spec_endpoint = existing_item.get('related', {}).get('survey_spec')
+ existing_spec = module.get_endpoint(spec_endpoint)['json']
+ if new_spec != existing_spec:
+ module.json_output['changed'] = True
+ if existing_item and module.has_encrypted_values(existing_spec):
+ module._encrypted_changed_warning('survey_spec', existing_item, warning=True)
+ on_change = update_survey
+
+ # If the state was present and we can let the module build or update the existing item, this will return on its own
+ module.create_or_update_if_needed(
+ existing_item, new_fields,
+ endpoint='job_templates', item_type='job_template',
+ associations=association_fields,
+ on_create=on_change, on_update=on_change,
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_wait.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_wait.py
new file mode 100644
index 00000000..5e5801b2
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_job_wait.py
@@ -0,0 +1,186 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_job_wait
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: Wait for Ansible Tower job to finish.
+description:
+ - Wait for Ansible Tower job to finish and report success or failure. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ job_id:
+ description:
+ - ID of the job to monitor.
+ required: True
+ type: int
+ interval:
+ description:
+ - The interval in sections, to request an update from Tower.
+ - For backwards compatability if unset this will be set to the average of min and max intervals
+ required: False
+ default: 1
+ type: float
+ min_interval:
+ description:
+ - Minimum interval in seconds, to request an update from Tower.
+ - deprecated, use interval instead
+ type: float
+ max_interval:
+ description:
+ - Maximum interval in seconds, to request an update from Tower.
+ - deprecated, use interval instead
+ type: float
+ timeout:
+ description:
+ - Maximum time in seconds to wait for a job to finish.
+ type: int
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+- name: Launch a job
+ tower_job_launch:
+ job_template: "My Job Template"
+ register: job
+
+- name: Wait for job max 120s
+ tower_job_wait:
+ job_id: "{{ job.id }}"
+ timeout: 120
+'''
+
+RETURN = '''
+id:
+ description: job id that is being waited on
+ returned: success
+ type: int
+ sample: 99
+elapsed:
+ description: total time in seconds the job took to run
+ returned: success
+ type: float
+ sample: 10.879
+started:
+ description: timestamp of when the job started running
+ returned: success
+ type: str
+ sample: "2017-03-01T17:03:53.200234Z"
+finished:
+ description: timestamp of when the job finished running
+ returned: success
+ type: str
+ sample: "2017-03-01T17:04:04.078782Z"
+status:
+ description: current status of job
+ returned: success
+ type: str
+ sample: successful
+'''
+
+
+from ..module_utils.tower_api import TowerAPIModule
+import time
+
+
+def check_job(module, job_url):
+ response = module.get_endpoint(job_url)
+ if response['status_code'] != 200:
+ module.fail_json(msg="Unable to read job from Tower {0}: {1}".format(response['status_code'], module.extract_errors_from_response(response)))
+
+ # Since we were successful, extract the fields we want to return
+ for k in ('id', 'status', 'elapsed', 'started', 'finished'):
+ module.json_output[k] = response['json'].get(k)
+
+ # And finally return the payload
+ return response['json']
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ job_id=dict(type='int', required=True),
+ timeout=dict(type='int'),
+ min_interval=dict(type='float'),
+ max_interval=dict(type='float'),
+ interval=dict(type='float', default=1),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ job_id = module.params.get('job_id')
+ timeout = module.params.get('timeout')
+ min_interval = module.params.get('min_interval')
+ max_interval = module.params.get('max_interval')
+ interval = module.params.get('interval')
+
+ if min_interval is not None or max_interval is not None:
+ # We can't tell if we got the default or if someone actually set this to 1.
+ # For now if we find 1 and had a min or max then we will do the average logic.
+ if interval == 1:
+ if not min_interval:
+ min_interval = 1
+ if not max_interval:
+ max_interval = 30
+ interval = abs((min_interval + max_interval) / 2)
+ module.deprecate(
+ msg="Min and max interval have been deprecated, please use interval instead; interval will be set to {0}".format(interval),
+ version="ansible.tower:4.0.0"
+ )
+
+ # Attempt to look up job based on the provided id
+ job = module.get_one('jobs', **{
+ 'data': {
+ 'id': job_id,
+ }
+ })
+
+ if job is None:
+ module.fail_json(msg='Unable to wait on job {0}; that ID does not exist in Tower.'.format(job_id))
+
+ job_url = job['url']
+
+ # Grab our start time to compare against for the timeout
+ start = time.time()
+
+ # Get the initial job status from Tower, this will exit if there are any issues with the HTTP call
+ result = check_job(module, job_url)
+
+ # Loop while the job is not yet completed
+ while not result['finished']:
+ # If we are past our time out fail with a message
+ if timeout and timeout < time.time() - start:
+ module.json_output['msg'] = "Monitoring aborted due to timeout"
+ module.fail_json(**module.json_output)
+
+ # Put the process to sleep for our interval
+ time.sleep(interval)
+
+ # Check the job again
+ result = check_job(module, job_url)
+
+ # If the job has failed, we want to raise an Exception for that so we get a non-zero response.
+ if result['failed']:
+ module.json_output['msg'] = 'Job with id {0} failed'.format(job_id)
+ module.fail_json(**module.json_output)
+
+ module.exit_json(**module.json_output)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_label.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_label.py
new file mode 100644
index 00000000..6a3a8288
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_label.py
@@ -0,0 +1,105 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: tower_label
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: create, update, or destroy Ansible Tower labels.
+description:
+ - Create, update, or destroy Ansible Tower labels. See
+ U(https://www.ansible.com/tower) for an overview.
+ - Note, labels can only be created via the Tower API, they can not be deleted.
+ Once they are fully disassociated the API will clean them up on its own.
+options:
+ name:
+ description:
+ - Name of this label.
+ required: True
+ type: str
+ new_name:
+ description:
+ - Setting this option will change the existing name (looked up via the name field).
+ type: str
+ organization:
+ description:
+ - Organization this label belongs to.
+ required: True
+ type: str
+ state:
+ description:
+ - Desired state of the resource.
+ default: "present"
+ choices: ["present"]
+ type: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+- name: Add label to tower organization
+ tower_label:
+ name: Custom Label
+ organization: My Organization
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ new_name=dict(),
+ organization=dict(required=True),
+ state=dict(choices=['present'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ name = module.params.get('name')
+ new_name = module.params.get("new_name")
+ organization = module.params.get('organization')
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ organization_id = None
+ if organization:
+ organization_id = module.resolve_name_to_id('organizations', organization)
+
+ # Attempt to look up an existing item based on the provided data
+ existing_item = module.get_one('labels', **{
+ 'data': {
+ 'name': name,
+ 'organization': organization_id,
+ }
+ })
+
+ # Create the data that gets sent for create and update
+ new_fields = {}
+ new_fields['name'] = new_name if new_name else name
+ if organization:
+ new_fields['organization'] = organization_id
+
+ module.create_or_update_if_needed(
+ existing_item, new_fields,
+ endpoint='labels', item_type='label',
+ associations={
+ }
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_license.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_license.py
new file mode 100644
index 00000000..25d337cc
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_license.py
@@ -0,0 +1,81 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2019, John Westcott IV <john.westcott.iv@redhat.com>
+# 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
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_license
+author: "John Westcott IV (@john-westcott-iv)"
+short_description: Set the license for Ansible Tower
+description:
+ - Get or Set Ansible Tower license. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ data:
+ description:
+ - The contents of the license file
+ required: True
+ type: dict
+ eula_accepted:
+ description:
+ - Whether or not the EULA is accepted.
+ required: True
+ type: bool
+extends_documentation_fragment: awx.awx.auth
+'''
+
+RETURN = ''' # '''
+
+EXAMPLES = '''
+- name: Set the license using a file
+ license:
+ data: "{{ lookup('file', '/tmp/my_tower.license') }}"
+ eula_accepted: True
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+
+ module = TowerAPIModule(
+ argument_spec=dict(
+ data=dict(type='dict', required=True),
+ eula_accepted=dict(type='bool', required=True),
+ ),
+ )
+
+ json_output = {'changed': False}
+
+ if not module.params.get('eula_accepted'):
+ module.fail_json(msg='You must accept the EULA by passing in the param eula_accepted as True')
+
+ json_output['old_license'] = module.get_endpoint('settings/system/')['json']['LICENSE']
+ new_license = module.params.get('data')
+
+ if json_output['old_license'] != new_license:
+ json_output['changed'] = True
+
+ # Deal with check mode
+ if module.check_mode:
+ module.exit_json(**json_output)
+
+ # We need to add in the EULA
+ new_license['eula_accepted'] = True
+ module.post_endpoint('config', data=new_license)
+
+ module.exit_json(**json_output)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_meta.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_meta.py
new file mode 100644
index 00000000..9455bdf0
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_meta.py
@@ -0,0 +1,84 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2020, Ansible by Red Hat, Inc
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: tower_meta
+author: "Alan Rominger (@alancoding)"
+short_description: Returns metadata about the collection this module lives in.
+description:
+ - Allows a user to find out what collection this module exists in.
+ - This takes common module parameters, but does nothing with them.
+options: {}
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+RETURN = '''
+prefix:
+ description: Collection namespace and name in the namespace.name format
+ returned: success
+ sample: awx.awx
+ type: str
+name:
+ description: Collection name
+ returned: success
+ sample: awx
+ type: str
+namespace:
+ description: Collection namespace
+ returned: success
+ sample: awx
+ type: str
+version:
+ description: Version of the collection
+ returned: success
+ sample: 0.0.1-devel
+ type: str
+'''
+
+
+EXAMPLES = '''
+- tower_meta:
+ register: result
+
+- name: Show details about the collection
+ debug: var=result
+
+- name: Load the UI setting without hard-coding the collection name
+ debug:
+ msg: "{{ lookup(result.prefix + '.tower_api', 'settings/ui') }}"
+'''
+
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+ module = TowerAPIModule(argument_spec={})
+ namespace = {
+ 'awx': 'awx',
+ 'tower': 'ansible'
+ }.get(module._COLLECTION_TYPE, 'unknown')
+ namespace_name = '{0}.{1}'.format(namespace, module._COLLECTION_TYPE)
+ module.exit_json(
+ prefix=namespace_name,
+ name=module._COLLECTION_TYPE,
+ namespace=namespace,
+ version=module._COLLECTION_VERSION
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_notification.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_notification.py
new file mode 100644
index 00000000..12dd28ef
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_notification.py
@@ -0,0 +1,427 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2018, Samuel Carpentier <samuelcarpentier0@gmail.ca>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_notification
+author: "Samuel Carpentier (@samcarpentier)"
+short_description: create, update, or destroy Ansible Tower notification.
+description:
+ - Create, update, or destroy Ansible Tower notifications. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - The name of the notification.
+ type: str
+ required: True
+ new_name:
+ description:
+ - Setting this option will change the existing name (looked up via the name field.
+ type: str
+ description:
+ description:
+ - The description of the notification.
+ type: str
+ organization:
+ description:
+ - The organization the notification belongs to.
+ type: str
+ notification_type:
+ description:
+ - The type of notification to be sent.
+ choices:
+ - 'email'
+ - 'grafana'
+ - 'irc'
+ - 'mattermost'
+ - 'pagerduty'
+ - 'rocketchat'
+ - 'slack'
+ - 'twilio'
+ - 'webhook'
+ type: str
+ notification_configuration:
+ description:
+ - The notification configuration file. Note providing this field would disable all notification-configuration-related fields.
+ type: dict
+ messages:
+ description:
+ - Optional custom messages for notification template.
+ type: dict
+ username:
+ description:
+ - The mail server username.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ sender:
+ description:
+ - The sender email address.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ recipients:
+ description:
+ - The recipients email addresses.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: list
+ elements: str
+ use_tls:
+ description:
+ - The TLS trigger.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: bool
+ host:
+ description:
+ - The mail server host.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ use_ssl:
+ description:
+ - The SSL trigger.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: bool
+ password:
+ description:
+ - The mail server password.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ port:
+ description:
+ - The mail server port.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: int
+ channels:
+ description:
+ - The destination Slack channels.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: list
+ elements: str
+ token:
+ description:
+ - The access token.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ account_token:
+ description:
+ - The Twillio account token.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ from_number:
+ description:
+ - The source phone number.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ to_numbers:
+ description:
+ - The destination phone numbers.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: list
+ elements: str
+ account_sid:
+ description:
+ - The Twillio account SID.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ subdomain:
+ description:
+ - The PagerDuty subdomain.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ service_key:
+ description:
+ - The PagerDuty service/integration API key.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ client_name:
+ description:
+ - The PagerDuty client identifier.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ message_from:
+ description:
+ - The label to be shown with the notification.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ color:
+ description:
+ - The notification color.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ choices: ["yellow", "green", "red", "purple", "gray", "random"]
+ type: str
+ notify:
+ description:
+ - The notify channel trigger.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: bool
+ url:
+ description:
+ - The target URL.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ headers:
+ description:
+ - The HTTP headers as JSON string.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: dict
+ server:
+ description:
+ - The IRC server address.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ nickname:
+ description:
+ - The IRC nickname.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: str
+ targets:
+ description:
+ - The destination channels or users.
+ - This parameter has been deprecated, please use 'notification_configuration' instead.
+ type: list
+ elements: str
+ state:
+ description:
+ - Desired state of the resource.
+ default: "present"
+ choices: ["present", "absent"]
+ type: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+EXAMPLES = '''
+- name: Add Slack notification with custom messages
+ tower_notification:
+ name: slack notification
+ organization: Default
+ notification_type: slack
+ notification_configuration:
+ channels:
+ - general
+ token: cefda9e2be1f21d11cdd9452f5b7f97fda977f42
+ messages:
+ started:
+ message: "{{ '{{ job_friendly_name }}{{ job.id }} started' }}"
+ success:
+ message: "{{ '{{ job_friendly_name }} completed in {{ job.elapsed }} seconds' }}"
+ error:
+ message: "{{ '{{ job_friendly_name }} FAILED! Please look at {{ job.url }}' }}"
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Add webhook notification
+ tower_notification:
+ name: webhook notification
+ notification_type: webhook
+ notification_configuration:
+ url: http://www.example.com/hook
+ headers:
+ X-Custom-Header: value123
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Add email notification
+ tower_notification:
+ name: email notification
+ notification_type: email
+ notification_configuration:
+ username: user
+ password: s3cr3t
+ sender: tower@example.com
+ recipients:
+ - user1@example.com
+ host: smtp.example.com
+ port: 25
+ use_tls: no
+ use_ssl: no
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Add twilio notification
+ tower_notification:
+ name: twilio notification
+ notification_type: twilio
+ notification_configuration:
+ account_token: a_token
+ account_sid: a_sid
+ from_number: '+15551112222'
+ to_numbers:
+ - '+15553334444'
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Add PagerDuty notification
+ tower_notification:
+ name: pagerduty notification
+ notification_type: pagerduty
+ notification_configuration:
+ token: a_token
+ subdomain: sub
+ client_name: client
+ service_key: a_key
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Add IRC notification
+ tower_notification:
+ name: irc notification
+ notification_type: irc
+ notification_configuration:
+ nickname: tower
+ password: s3cr3t
+ targets:
+ - user1
+ port: 8080
+ server: irc.example.com
+ use_ssl: no
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Delete notification
+ tower_notification:
+ name: old notification
+ state: absent
+ tower_config_file: "~/tower_cli.cfg"
+'''
+
+
+RETURN = ''' # '''
+
+
+from ..module_utils.tower_api import TowerAPIModule
+
+OLD_INPUT_NAMES = (
+ 'username', 'sender', 'recipients', 'use_tls',
+ 'host', 'use_ssl', 'password', 'port',
+ 'channels', 'token', 'account_token', 'from_number',
+ 'to_numbers', 'account_sid', 'subdomain', 'service_key',
+ 'client_name', 'message_from', 'color',
+ 'notify', 'url', 'headers', 'server',
+ 'nickname', 'targets',
+)
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ new_name=dict(),
+ description=dict(),
+ organization=dict(),
+ notification_type=dict(choices=[
+ 'email', 'grafana', 'irc', 'mattermost',
+ 'pagerduty', 'rocketchat', 'slack', 'twilio', 'webhook'
+ ]),
+ notification_configuration=dict(type='dict'),
+ messages=dict(type='dict'),
+ username=dict(),
+ sender=dict(),
+ recipients=dict(type='list', elements='str'),
+ use_tls=dict(type='bool'),
+ host=dict(),
+ use_ssl=dict(type='bool'),
+ password=dict(no_log=True),
+ port=dict(type='int'),
+ channels=dict(type='list', elements='str'),
+ token=dict(no_log=True),
+ account_token=dict(no_log=True),
+ from_number=dict(),
+ to_numbers=dict(type='list', elements='str'),
+ account_sid=dict(),
+ subdomain=dict(),
+ service_key=dict(no_log=True),
+ client_name=dict(),
+ message_from=dict(),
+ color=dict(choices=['yellow', 'green', 'red', 'purple', 'gray', 'random']),
+ notify=dict(type='bool'),
+ url=dict(),
+ headers=dict(type='dict'),
+ server=dict(),
+ nickname=dict(),
+ targets=dict(type='list', elements='str'),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ name = module.params.get('name')
+ new_name = module.params.get('new_name')
+ description = module.params.get('description')
+ organization = module.params.get('organization')
+ notification_type = module.params.get('notification_type')
+ notification_configuration = module.params.get('notification_configuration')
+ messages = module.params.get('messages')
+ state = module.params.get('state')
+
+ # Deprecation warnings for all other params
+ for legacy_input in OLD_INPUT_NAMES:
+ if module.params.get(legacy_input) is not None:
+ module.deprecate(
+ msg='{0} parameter has been deprecated, please use notification_configuration instead'.format(legacy_input),
+ version="ansible.tower:4.0.0")
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ organization_id = None
+ if organization:
+ organization_id = module.resolve_name_to_id('organizations', organization)
+
+ # Attempt to look up an existing item based on the provided data
+ existing_item = module.get_one('notification_templates', **{
+ 'data': {
+ 'name': name,
+ 'organization': organization_id,
+ }
+ })
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(existing_item)
+
+ # Create notification_configuration from legacy inputs
+ final_notification_configuration = {}
+ for legacy_input in OLD_INPUT_NAMES:
+ if module.params.get(legacy_input) is not None:
+ final_notification_configuration[legacy_input] = module.params.get(legacy_input)
+ # Give anything in notification_configuration prescedence over the individual inputs
+ if notification_configuration is not None:
+ final_notification_configuration.update(notification_configuration)
+
+ # Create the data that gets sent for create and update
+ new_fields = {}
+ if final_notification_configuration:
+ new_fields['notification_configuration'] = final_notification_configuration
+ new_fields['name'] = new_name if new_name else name
+ if description is not None:
+ new_fields['description'] = description
+ if organization is not None:
+ new_fields['organization'] = organization_id
+ if notification_type is not None:
+ new_fields['notification_type'] = notification_type
+ if messages is not None:
+ new_fields['messages'] = messages
+
+ # If the state was present and we can let the module build or update the existing item, this will return on its own
+ module.create_or_update_if_needed(
+ existing_item, new_fields,
+ endpoint='notification_templates', item_type='notification_template',
+ associations={
+ }
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_organization.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_organization.py
new file mode 100644
index 00000000..16378281
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_organization.py
@@ -0,0 +1,174 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_organization
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: create, update, or destroy Ansible Tower organizations
+description:
+ - Create, update, or destroy Ansible Tower organizations. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - Name to use for the organization.
+ required: True
+ type: str
+ description:
+ description:
+ - The description to use for the organization.
+ type: str
+ custom_virtualenv:
+ description:
+ - Local absolute file path containing a custom Python virtualenv to use.
+ type: str
+ default: ''
+ max_hosts:
+ description:
+ - The max hosts allowed in this organizations
+ default: "0"
+ type: int
+ state:
+ description:
+ - Desired state of the resource.
+ default: "present"
+ choices: ["present", "absent"]
+ type: str
+ notification_templates_started:
+ description:
+ - list of notifications to send on start
+ type: list
+ elements: str
+ notification_templates_success:
+ description:
+ - list of notifications to send on success
+ type: list
+ elements: str
+ notification_templates_error:
+ description:
+ - list of notifications to send on error
+ type: list
+ elements: str
+ notification_templates_approvals:
+ description:
+ - list of notifications to send on start
+ type: list
+ elements: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+EXAMPLES = '''
+- name: Create tower organization
+ tower_organization:
+ name: "Foo"
+ description: "Foo bar organization"
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Create tower organization using 'foo-venv' as default Python virtualenv
+ tower_organization:
+ name: "Foo"
+ description: "Foo bar organization using foo-venv"
+ custom_virtualenv: "/var/lib/awx/venv/foo-venv/"
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ description=dict(),
+ custom_virtualenv=dict(),
+ max_hosts=dict(type='int', default="0"),
+ notification_templates_started=dict(type="list", elements='str'),
+ notification_templates_success=dict(type="list", elements='str'),
+ notification_templates_error=dict(type="list", elements='str'),
+ notification_templates_approvals=dict(type="list", elements='str'),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ name = module.params.get('name')
+ description = module.params.get('description')
+ custom_virtualenv = module.params.get('custom_virtualenv')
+ max_hosts = module.params.get('max_hosts')
+ # instance_group_names = module.params.get('instance_groups')
+ state = module.params.get('state')
+
+ # Attempt to look up organization based on the provided name
+ organization = module.get_one('organizations', **{
+ 'data': {
+ 'name': name,
+ }
+ })
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(organization)
+ # Attempt to look up associated field items the user specified.
+ association_fields = {}
+
+ notifications_start = module.params.get('notification_templates_started')
+ if notifications_start is not None:
+ association_fields['notification_templates_started'] = []
+ for item in notifications_start:
+ association_fields['notification_templates_started'].append(module.resolve_name_to_id('notification_templates', item))
+
+ notifications_success = module.params.get('notification_templates_success')
+ if notifications_success is not None:
+ association_fields['notification_templates_success'] = []
+ for item in notifications_success:
+ association_fields['notification_templates_success'].append(module.resolve_name_to_id('notification_templates', item))
+
+ notifications_error = module.params.get('notification_templates_error')
+ if notifications_error is not None:
+ association_fields['notification_templates_error'] = []
+ for item in notifications_error:
+ association_fields['notification_templates_error'].append(module.resolve_name_to_id('notification_templates', item))
+
+ notifications_approval = module.params.get('notification_templates_approvals')
+ if notifications_approval is not None:
+ association_fields['notification_templates_approvals'] = []
+ for item in notifications_approval:
+ association_fields['notification_templates_approvals'].append(module.resolve_name_to_id('notification_templates', item))
+
+ # Create the data that gets sent for create and update
+ org_fields = {'name': name}
+ if description is not None:
+ org_fields['description'] = description
+ if custom_virtualenv is not None:
+ org_fields['custom_virtualenv'] = custom_virtualenv
+ if max_hosts is not None:
+ org_fields['max_hosts'] = max_hosts
+
+ # If the state was present and we can let the module build or update the existing organization, this will return on its own
+ module.create_or_update_if_needed(
+ organization, org_fields,
+ endpoint='organizations', item_type='organization',
+ associations=association_fields,
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_project.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_project.py
new file mode 100644
index 00000000..36a4f866
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_project.py
@@ -0,0 +1,313 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_project
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: create, update, or destroy Ansible Tower projects
+description:
+ - Create, update, or destroy Ansible Tower projects. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - Name to use for the project.
+ required: True
+ type: str
+ description:
+ description:
+ - Description to use for the project.
+ type: str
+ scm_type:
+ description:
+ - Type of SCM resource.
+ choices: ["manual", "git", "hg", "svn", "insights"]
+ default: "manual"
+ type: str
+ scm_url:
+ description:
+ - URL of SCM resource.
+ type: str
+ local_path:
+ description:
+ - The server playbook directory for manual projects.
+ type: str
+ scm_branch:
+ description:
+ - The branch to use for the SCM resource.
+ type: str
+ default: ''
+ scm_refspec:
+ description:
+ - The refspec to use for the SCM resource.
+ type: str
+ default: ''
+ scm_credential:
+ description:
+ - Name of the credential to use with this SCM resource.
+ type: str
+ scm_clean:
+ description:
+ - Remove local modifications before updating.
+ type: bool
+ default: 'no'
+ scm_delete_on_update:
+ description:
+ - Remove the repository completely before updating.
+ type: bool
+ default: 'no'
+ scm_update_on_launch:
+ description:
+ - Before an update to the local repository before launching a job with this project.
+ type: bool
+ default: 'no'
+ scm_update_cache_timeout:
+ description:
+ - Cache Timeout to cache prior project syncs for a certain number of seconds.
+ Only valid if scm_update_on_launch is to True, otherwise ignored.
+ type: int
+ default: 0
+ allow_override:
+ description:
+ - Allow changing the SCM branch or revision in a job template that uses this project.
+ type: bool
+ aliases:
+ - scm_allow_override
+ job_timeout:
+ description:
+ - The amount of time (in seconds) to run before the SCM Update is canceled. A value of 0 means no timeout.
+ default: 0
+ type: int
+ custom_virtualenv:
+ description:
+ - Local absolute file path containing a custom Python virtualenv to use
+ type: str
+ default: ''
+ organization:
+ description:
+ - Name of organization for project.
+ type: str
+ required: True
+ state:
+ description:
+ - Desired state of the resource.
+ default: "present"
+ choices: ["present", "absent"]
+ type: str
+ wait:
+ description:
+ - Provides option (True by default) to wait for completed project sync
+ before returning
+ - Can assure playbook files are populated so that job templates that rely
+ on the project may be successfully created
+ type: bool
+ default: True
+ notification_templates_started:
+ description:
+ - list of notifications to send on start
+ type: list
+ elements: str
+ notification_templates_success:
+ description:
+ - list of notifications to send on success
+ type: list
+ elements: str
+ notification_templates_error:
+ description:
+ - list of notifications to send on error
+ type: list
+ elements: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+EXAMPLES = '''
+- name: Add tower project
+ tower_project:
+ name: "Foo"
+ description: "Foo bar project"
+ organization: "test"
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Add Tower Project with cache timeout and custom virtualenv
+ tower_project:
+ name: "Foo"
+ description: "Foo bar project"
+ organization: "test"
+ scm_update_on_launch: True
+ scm_update_cache_timeout: 60
+ custom_virtualenv: "/var/lib/awx/venv/ansible-2.2"
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+'''
+
+import time
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def wait_for_project_update(module, last_request):
+ # The current running job for the udpate is in last_request['summary_fields']['current_update']['id']
+
+ if 'current_update' in last_request['summary_fields']:
+ running = True
+ while running:
+ result = module.get_endpoint('/project_updates/{0}/'.format(last_request['summary_fields']['current_update']['id']))['json']
+
+ if module.is_job_done(result['status']):
+ time.sleep(1)
+ running = False
+
+ if result['status'] != 'successful':
+ module.fail_json(msg="Project update failed")
+
+ module.exit_json(**module.json_output)
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ description=dict(),
+ scm_type=dict(choices=['manual', 'git', 'hg', 'svn', 'insights'], default='manual'),
+ scm_url=dict(),
+ local_path=dict(),
+ scm_branch=dict(default=''),
+ scm_refspec=dict(default=''),
+ scm_credential=dict(),
+ scm_clean=dict(type='bool', default=False),
+ scm_delete_on_update=dict(type='bool', default=False),
+ scm_update_on_launch=dict(type='bool', default=False),
+ scm_update_cache_timeout=dict(type='int', default=0),
+ allow_override=dict(type='bool', aliases=['scm_allow_override']),
+ job_timeout=dict(type='int', default=0),
+ custom_virtualenv=dict(),
+ organization=dict(required=True),
+ notification_templates_started=dict(type="list", elements='str'),
+ notification_templates_success=dict(type="list", elements='str'),
+ notification_templates_error=dict(type="list", elements='str'),
+ state=dict(choices=['present', 'absent'], default='present'),
+ wait=dict(type='bool', default=True),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ name = module.params.get('name')
+ description = module.params.get('description')
+ scm_type = module.params.get('scm_type')
+ if scm_type == "manual":
+ scm_type = ""
+ scm_url = module.params.get('scm_url')
+ local_path = module.params.get('local_path')
+ scm_branch = module.params.get('scm_branch')
+ scm_refspec = module.params.get('scm_refspec')
+ scm_credential = module.params.get('scm_credential')
+ scm_clean = module.params.get('scm_clean')
+ scm_delete_on_update = module.params.get('scm_delete_on_update')
+ scm_update_on_launch = module.params.get('scm_update_on_launch')
+ scm_update_cache_timeout = module.params.get('scm_update_cache_timeout')
+ allow_override = module.params.get('allow_override')
+ job_timeout = module.params.get('job_timeout')
+ custom_virtualenv = module.params.get('custom_virtualenv')
+ organization = module.params.get('organization')
+ state = module.params.get('state')
+ wait = module.params.get('wait')
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ org_id = module.resolve_name_to_id('organizations', organization)
+ if scm_credential is not None:
+ scm_credential_id = module.resolve_name_to_id('credentials', scm_credential)
+
+ # Attempt to look up project based on the provided name and org ID
+ project = module.get_one('projects', **{
+ 'data': {
+ 'name': name,
+ 'organization': org_id
+ }
+ })
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(project)
+
+ # Attempt to look up associated field items the user specified.
+ association_fields = {}
+
+ notifications_start = module.params.get('notification_templates_started')
+ if notifications_start is not None:
+ association_fields['notification_templates_started'] = []
+ for item in notifications_start:
+ association_fields['notification_templates_started'].append(module.resolve_name_to_id('notification_templates', item))
+
+ notifications_success = module.params.get('notification_templates_success')
+ if notifications_success is not None:
+ association_fields['notification_templates_success'] = []
+ for item in notifications_success:
+ association_fields['notification_templates_success'].append(module.resolve_name_to_id('notification_templates', item))
+
+ notifications_error = module.params.get('notification_templates_error')
+ if notifications_error is not None:
+ association_fields['notification_templates_error'] = []
+ for item in notifications_error:
+ association_fields['notification_templates_error'].append(module.resolve_name_to_id('notification_templates', item))
+
+ # Create the data that gets sent for create and update
+ project_fields = {
+ 'name': name,
+ 'scm_type': scm_type,
+ 'scm_url': scm_url,
+ 'scm_branch': scm_branch,
+ 'scm_refspec': scm_refspec,
+ 'scm_clean': scm_clean,
+ 'scm_delete_on_update': scm_delete_on_update,
+ 'timeout': job_timeout,
+ 'organization': org_id,
+ 'scm_update_on_launch': scm_update_on_launch,
+ 'scm_update_cache_timeout': scm_update_cache_timeout,
+ 'custom_virtualenv': custom_virtualenv,
+ }
+ if description is not None:
+ project_fields['description'] = description
+ if scm_credential is not None:
+ project_fields['credential'] = scm_credential_id
+ if allow_override is not None:
+ project_fields['allow_override'] = allow_override
+ if scm_type == '':
+ project_fields['local_path'] = local_path
+
+ if scm_update_cache_timeout != 0 and scm_update_on_launch is not True:
+ module.warn('scm_update_cache_timeout will be ignored since scm_update_on_launch was not set to true')
+
+ # If we are doing a not manual project, register our on_change method
+ # An on_change function, if registered, will fire after an post_endpoint or update_if_needed completes successfully
+ on_change = None
+ if wait and scm_type != '':
+ on_change = wait_for_project_update
+
+ # If the state was present and we can let the module build or update the existing project, this will return on its own
+ module.create_or_update_if_needed(
+ project, project_fields,
+ endpoint='projects', item_type='project',
+ associations=association_fields,
+ on_create=on_change, on_update=on_change
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_receive.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_receive.py
new file mode 100644
index 00000000..bd086825
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_receive.py
@@ -0,0 +1,199 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, John Westcott IV <john.westcott.iv@redhat.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['deprecated'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_receive
+deprecated:
+ removed_in: "14.0.0"
+ why: Deprecated in favor of upcoming C(_export) module.
+ alternative: Once published, use M(tower_export) instead.
+author: "John Westcott IV (@john-westcott-iv)"
+short_description: Receive assets from Ansible Tower.
+description:
+ - Receive assets from Ansible Tower. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ all:
+ description:
+ - Export all assets
+ type: bool
+ default: 'False'
+ organization:
+ description:
+ - List of organization names to export
+ default: []
+ type: list
+ elements: str
+ user:
+ description:
+ - List of user names to export
+ default: []
+ type: list
+ elements: str
+ team:
+ description:
+ - List of team names to export
+ default: []
+ type: list
+ elements: str
+ credential_type:
+ description:
+ - List of credential type names to export
+ default: []
+ type: list
+ elements: str
+ credential:
+ description:
+ - List of credential names to export
+ default: []
+ type: list
+ elements: str
+ notification_template:
+ description:
+ - List of notification template names to export
+ default: []
+ type: list
+ elements: str
+ inventory_script:
+ description:
+ - List of inventory script names to export
+ default: []
+ type: list
+ elements: str
+ inventory:
+ description:
+ - List of inventory names to export
+ default: []
+ type: list
+ elements: str
+ project:
+ description:
+ - List of project names to export
+ default: []
+ type: list
+ elements: str
+ job_template:
+ description:
+ - List of job template names to export
+ default: []
+ type: list
+ elements: str
+ workflow:
+ description:
+ - List of workflow names to export
+ default: []
+ type: list
+ elements: str
+
+requirements:
+ - "ansible-tower-cli >= 3.3.0"
+
+notes:
+ - Specifying a name of "all" for any asset type will export all items of that asset type.
+
+extends_documentation_fragment: awx.awx.auth_legacy
+'''
+
+EXAMPLES = '''
+- name: Export all tower assets
+ tower_receive:
+ all: True
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Export all inventories
+ tower_receive:
+ inventory:
+ - all
+
+- name: Export a job template named "My Template" and all Credentials
+ tower_receive:
+ job_template:
+ - "My Template"
+ credential:
+ - all
+'''
+
+RETURN = '''
+assets:
+ description: The exported assets
+ returned: success
+ type: dict
+ sample: [ {}, {} ]
+'''
+
+from ..module_utils.tower_legacy import TowerLegacyModule, tower_auth_config, HAS_TOWER_CLI
+
+try:
+ from tower_cli.cli.transfer.receive import Receiver
+ from tower_cli.cli.transfer.common import SEND_ORDER
+ from tower_cli.utils.exceptions import TowerCLIError
+
+ from tower_cli.conf import settings
+ TOWER_CLI_HAS_EXPORT = True
+except ImportError:
+ TOWER_CLI_HAS_EXPORT = False
+
+
+def main():
+ argument_spec = dict(
+ all=dict(type='bool', default=False),
+ credential=dict(type='list', default=[], elements='str'),
+ credential_type=dict(type='list', default=[], elements='str'),
+ inventory=dict(type='list', default=[], elements='str'),
+ inventory_script=dict(type='list', default=[], elements='str'),
+ job_template=dict(type='list', default=[], elements='str'),
+ notification_template=dict(type='list', default=[], elements='str'),
+ organization=dict(type='list', default=[], elements='str'),
+ project=dict(type='list', default=[], elements='str'),
+ team=dict(type='list', default=[], elements='str'),
+ user=dict(type='list', default=[], elements='str'),
+ workflow=dict(type='list', default=[], elements='str'),
+ )
+
+ module = TowerLegacyModule(argument_spec=argument_spec, supports_check_mode=False)
+
+ module.deprecate(msg="This module is deprecated and will be replaced by the AWX CLI export command.", version="awx.awx:14.0.0")
+
+ if not HAS_TOWER_CLI:
+ module.fail_json(msg='ansible-tower-cli required for this module')
+
+ if not TOWER_CLI_HAS_EXPORT:
+ module.fail_json(msg='ansible-tower-cli version does not support export')
+
+ export_all = module.params.get('all')
+ assets_to_export = {}
+ for asset_type in SEND_ORDER:
+ assets_to_export[asset_type] = module.params.get(asset_type)
+
+ result = dict(
+ assets=None,
+ changed=False,
+ message='',
+ )
+
+ tower_auth = tower_auth_config(module)
+ with settings.runtime_values(**tower_auth):
+ try:
+ receiver = Receiver()
+ result['assets'] = receiver.export_assets(all=export_all, asset_input=assets_to_export)
+ module.exit_json(**result)
+ except TowerCLIError as e:
+ result['message'] = e.message
+ module.fail_json(msg='Receive Failed', **result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_role.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_role.py
new file mode 100644
index 00000000..d0d010a0
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_role.py
@@ -0,0 +1,186 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_role
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: grant or revoke an Ansible Tower role.
+description:
+ - Roles are used for access control, this module is for managing user access to server resources.
+ - Grant or revoke Ansible Tower roles to users. See U(https://www.ansible.com/tower) for an overview.
+options:
+ user:
+ description:
+ - User that receives the permissions specified by the role.
+ type: str
+ team:
+ description:
+ - Team that receives the permissions specified by the role.
+ type: str
+ role:
+ description:
+ - The role type to grant/revoke.
+ required: True
+ choices: ["admin", "read", "member", "execute", "adhoc", "update", "use", "auditor", "project_admin", "inventory_admin", "credential_admin",
+ "workflow_admin", "notification_admin", "job_template_admin"]
+ type: str
+ target_team:
+ description:
+ - Team that the role acts on.
+ - For example, make someone a member or an admin of a team.
+ - Members of a team implicitly receive the permissions that the team has.
+ type: str
+ inventory:
+ description:
+ - Inventory the role acts on.
+ type: str
+ job_template:
+ description:
+ - The job template the role acts on.
+ type: str
+ workflow:
+ description:
+ - The workflow job template the role acts on.
+ type: str
+ credential:
+ description:
+ - Credential the role acts on.
+ type: str
+ organization:
+ description:
+ - Organization the role acts on.
+ type: str
+ project:
+ description:
+ - Project the role acts on.
+ type: str
+ state:
+ description:
+ - Desired state.
+ - State of present indicates the user should have the role.
+ - State of absent indicates the user should have the role taken away, if they have it.
+ default: "present"
+ choices: ["present", "absent"]
+ type: str
+
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+EXAMPLES = '''
+- name: Add jdoe to the member role of My Team
+ tower_role:
+ user: jdoe
+ target_team: "My Team"
+ role: member
+ state: present
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+
+ argument_spec = dict(
+ user=dict(),
+ team=dict(),
+ role=dict(choices=["admin", "read", "member", "execute", "adhoc", "update", "use", "auditor", "project_admin", "inventory_admin", "credential_admin",
+ "workflow_admin", "notification_admin", "job_template_admin"], required=True),
+ target_team=dict(),
+ inventory=dict(),
+ job_template=dict(),
+ workflow=dict(),
+ credential=dict(),
+ organization=dict(),
+ project=dict(),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ role_type = module.params.pop('role')
+ role_field = role_type + '_role'
+ state = module.params.pop('state')
+
+ module.json_output['role'] = role_type
+
+ # Lookup data for all the objects specified in params
+ params = module.params.copy()
+ resource_param_keys = (
+ 'user', 'team',
+ 'target_team', 'inventory', 'job_template', 'workflow', 'credential', 'organization', 'project'
+ )
+ resource_data = {}
+ for param in resource_param_keys:
+ endpoint = module.param_to_endpoint(param)
+ name_field = 'username' if param == 'user' else 'name'
+
+ resource_name = params.get(param)
+ if resource_name:
+ resource = module.get_one(endpoint, **{'data': {name_field: resource_name}})
+ if not resource:
+ module.fail_json(
+ msg='Failed to update role, {0} not found in {1}'.format(param, endpoint),
+ changed=False
+ )
+ resource_data[param] = resource
+
+ # separate actors from resources
+ actor_data = {}
+ for key in ('user', 'team'):
+ if key in resource_data:
+ actor_data[key] = resource_data.pop(key)
+
+ # build association agenda
+ associations = {}
+ for actor_type, actor in actor_data.items():
+ for resource_type, resource in resource_data.items():
+ resource_roles = resource['summary_fields']['object_roles']
+ if role_field not in resource_roles:
+ available_roles = ', '.join(list(resource_roles.keys()))
+ module.fail_json(msg='Resource {0} has no role {1}, available roles: {2}'.format(
+ resource['url'], role_field, available_roles
+ ), changed=False)
+ role_data = resource_roles[role_field]
+ endpoint = '/roles/{0}/{1}/'.format(role_data['id'], module.param_to_endpoint(actor_type))
+ associations.setdefault(endpoint, [])
+ associations[endpoint].append(actor['id'])
+
+ # perform associations
+ for association_endpoint, new_association_list in associations.items():
+ response = module.get_all_endpoint(association_endpoint)
+ existing_associated_ids = [association['id'] for association in response['json']['results']]
+
+ if state == 'present':
+ for an_id in list(set(new_association_list) - set(existing_associated_ids)):
+ response = module.post_endpoint(association_endpoint, **{'data': {'id': int(an_id)}})
+ if response['status_code'] == 204:
+ module.json_output['changed'] = True
+ else:
+ module.fail_json(msg="Failed to grant role {0}".format(response['json']['detail']))
+ else:
+ for an_id in list(set(existing_associated_ids) & set(new_association_list)):
+ response = module.post_endpoint(association_endpoint, **{'data': {'id': int(an_id), 'disassociate': True}})
+ if response['status_code'] == 204:
+ module.json_output['changed'] = True
+ else:
+ module.fail_json(msg="Failed to revoke role {0}".format(response['json']['detail']))
+
+ module.exit_json(**module.json_output)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_schedule.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_schedule.py
new file mode 100644
index 00000000..4922aaa6
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_schedule.py
@@ -0,0 +1,243 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+
+# (c) 2020, John Westcott IV <john.westcott.iv@redhat.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: tower_schedule
+author: "John Westcott IV (@john-westcott-iv)"
+short_description: create, update, or destroy Ansible Tower schedules.
+description:
+ - Create, update, or destroy Ansible Tower schedules. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ rrule:
+ description:
+ - A value representing the schedules iCal recurrence rule.
+ - See rrule plugin for help constructing this value
+ required: False
+ type: str
+ name:
+ description:
+ - Name of this schedule.
+ required: True
+ type: str
+ new_name:
+ description:
+ - Setting this option will change the existing name (looked up via the name field.
+ required: False
+ type: str
+ description:
+ description:
+ - Optional description of this schedule.
+ required: False
+ type: str
+ extra_data:
+ description:
+ - Specify C(extra_vars) for the template.
+ required: False
+ type: dict
+ default: {}
+ inventory:
+ description:
+ - Inventory applied as a prompt, assuming job template prompts for inventory
+ required: False
+ type: str
+ scm_branch:
+ description:
+ - Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true.
+ required: False
+ type: str
+ job_type:
+ description:
+ - The job type to use for the job template.
+ required: False
+ type: str
+ choices:
+ - 'run'
+ - 'check'
+ job_tags:
+ description:
+ - Comma separated list of the tags to use for the job template.
+ required: False
+ type: str
+ skip_tags:
+ description:
+ - Comma separated list of the tags to skip for the job template.
+ required: False
+ type: str
+ limit:
+ description:
+ - A host pattern to further constrain the list of hosts managed or affected by the playbook
+ required: False
+ type: str
+ diff_mode:
+ description:
+ - Enable diff mode for the job template.
+ required: False
+ type: bool
+ verbosity:
+ description:
+ - Control the output level Ansible produces as the playbook runs. 0 - Normal, 1 - Verbose, 2 - More Verbose, 3 - Debug, 4 - Connection Debug.
+ required: False
+ type: int
+ choices:
+ - 0
+ - 1
+ - 2
+ - 3
+ - 4
+ - 5
+ unified_job_template:
+ description:
+ - Name of unified job template to schedule.
+ required: False
+ type: str
+ enabled:
+ description:
+ - Enables processing of this schedule.
+ required: False
+ type: bool
+ state:
+ description:
+ - Desired state of the resource.
+ choices: ["present", "absent"]
+ default: "present"
+ type: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+- name: Build a schedule for Demo Job Template
+ tower_schedule:
+ name: "{{ sched1 }}"
+ state: present
+ unified_job_template: "Demo Job Template"
+ rrule: "DTSTART:20191219T130551Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1"
+ register: result
+
+- name: Build the same schedule using the rrule plugin
+ tower_schedule:
+ name: "{{ sched1 }}"
+ state: present
+ unified_job_template: "Demo Job Template"
+ rrule: "{{ query('awx.awx.tower_schedule_rrule', 'week', start_date='2019-12-19 13:05:51') }}"
+ register: result
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ rrule=dict(),
+ name=dict(required=True),
+ new_name=dict(),
+ description=dict(),
+ extra_data=dict(type='dict'),
+ inventory=dict(),
+ scm_branch=dict(),
+ job_type=dict(choices=['run', 'check']),
+ job_tags=dict(),
+ skip_tags=dict(),
+ limit=dict(),
+ diff_mode=dict(type='bool'),
+ verbosity=dict(type='int', choices=[0, 1, 2, 3, 4, 5]),
+ unified_job_template=dict(),
+ enabled=dict(type='bool'),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ rrule = module.params.get('rrule')
+ name = module.params.get('name')
+ new_name = module.params.get("new_name")
+ description = module.params.get('description')
+ extra_data = module.params.get('extra_data')
+ inventory = module.params.get('inventory')
+ scm_branch = module.params.get('scm_branch')
+ job_type = module.params.get('job_type')
+ job_tags = module.params.get('job_tags')
+ skip_tags = module.params.get('skip_tags')
+ limit = module.params.get('limit')
+ diff_mode = module.params.get('diff_mode')
+ verbosity = module.params.get('verbosity')
+ unified_job_template = module.params.get('unified_job_template')
+ enabled = module.params.get('enabled')
+ state = module.params.get('state')
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ inventory_id = None
+ if inventory:
+ inventory_id = module.resolve_name_to_id('inventories', inventory)
+ unified_job_template_id = None
+ if unified_job_template:
+ unified_job_template_id = module.resolve_name_to_id('unified_job_templates', unified_job_template)
+
+ # Attempt to look up an existing item based on the provided data
+ existing_item = module.get_one('schedules', **{
+ 'data': {
+ 'name': name,
+ }
+ })
+
+ # Create the data that gets sent for create and update
+ new_fields = {}
+ if rrule is not None:
+ new_fields['rrule'] = rrule
+ new_fields['name'] = new_name if new_name else name
+ if description is not None:
+ new_fields['description'] = description
+ if extra_data is not None:
+ new_fields['extra_data'] = extra_data
+ if inventory is not None:
+ new_fields['inventory'] = inventory_id
+ if scm_branch is not None:
+ new_fields['scm_branch'] = scm_branch
+ if job_type is not None:
+ new_fields['job_type'] = job_type
+ if job_tags is not None:
+ new_fields['job_tags'] = job_tags
+ if skip_tags is not None:
+ new_fields['skip_tags'] = skip_tags
+ if limit is not None:
+ new_fields['limit'] = limit
+ if diff_mode is not None:
+ new_fields['diff_mode'] = diff_mode
+ if verbosity is not None:
+ new_fields['verbosity'] = verbosity
+ if unified_job_template is not None:
+ new_fields['unified_job_template'] = unified_job_template_id
+ if enabled is not None:
+ new_fields['enabled'] = enabled
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(existing_item)
+ elif state == 'present':
+ # If the state was present and we can let the module build or update the existing item, this will return on its own
+ module.create_or_update_if_needed(
+ existing_item, new_fields,
+ endpoint='schedules', item_type='schedule',
+ associations={
+ }
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_send.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_send.py
new file mode 100644
index 00000000..772b2b67
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_send.py
@@ -0,0 +1,174 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, John Westcott IV <john.westcott.iv@redhat.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['deprecated'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_send
+deprecated:
+ removed_in: "14.0.0"
+ why: Deprecated in favor of upcoming C(_import) module.
+ alternative: Once published, use M(tower_import) instead.
+author: "John Westcott IV (@john-westcott-iv)"
+short_description: Send assets to Ansible Tower.
+description:
+ - Send assets to Ansible Tower. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ assets:
+ description:
+ - The assets to import.
+ - This can be the output of tower_receive or loaded from a file
+ type: str
+ files:
+ description:
+ - List of files to import.
+ default: []
+ type: list
+ elements: str
+ prevent:
+ description:
+ - A list of asset types to prevent import for
+ default: []
+ type: list
+ elements: str
+ password_management:
+ description:
+ - The password management option to use.
+ - The prompt option is not supported.
+ default: 'default'
+ choices: ["default", "random"]
+ type: str
+
+notes:
+ - One of assets or files needs to be passed in
+
+requirements:
+ - "ansible-tower-cli >= 3.3.0"
+ - six.moves.StringIO
+ - sys
+
+extends_documentation_fragment: awx.awx.auth_legacy
+'''
+
+EXAMPLES = '''
+- name: Import all tower assets
+ tower_send:
+ assets: "{{ export_output.assets }}"
+ tower_config_file: "~/tower_cli.cfg"
+'''
+
+RETURN = '''
+output:
+ description: The import messages
+ returned: success, fail
+ type: list
+ sample: [ 'Message 1', 'Message 2' ]
+'''
+
+import os
+import sys
+
+from ansible.module_utils.six.moves import StringIO
+from ..module_utils.tower_legacy import TowerLegacyModule, tower_auth_config, HAS_TOWER_CLI
+
+from tempfile import mkstemp
+
+try:
+ from tower_cli.cli.transfer.send import Sender
+ from tower_cli.utils.exceptions import TowerCLIError
+
+ from tower_cli.conf import settings
+ TOWER_CLI_HAS_EXPORT = True
+except ImportError:
+ TOWER_CLI_HAS_EXPORT = False
+
+
+def main():
+ argument_spec = dict(
+ assets=dict(),
+ files=dict(default=[], type='list', elements='str'),
+ prevent=dict(default=[], type='list', elements='str'),
+ password_management=dict(default='default', choices=['default', 'random']),
+ )
+
+ module = TowerLegacyModule(argument_spec=argument_spec, supports_check_mode=False)
+
+ module.deprecate(msg="This module is deprecated and will be replaced by the AWX CLI import command", version="awx.awx:14.0.0")
+
+ if not HAS_TOWER_CLI:
+ module.fail_json(msg='ansible-tower-cli required for this module')
+
+ if not TOWER_CLI_HAS_EXPORT:
+ module.fail_json(msg='ansible-tower-cli version does not support export')
+
+ assets = module.params.get('assets')
+ prevent = module.params.get('prevent')
+ password_management = module.params.get('password_management')
+ files = module.params.get('files')
+
+ result = dict(
+ changed=False,
+ msg='',
+ output='',
+ )
+
+ if not assets and not files:
+ result['msg'] = "Assets or files must be specified"
+ module.fail_json(**result)
+
+ path = None
+ if assets:
+ # We got assets so we need to dump this out to a temp file and append that to files
+ handle, path = mkstemp(prefix='', suffix='', dir='')
+ with open(path, 'w') as f:
+ f.write(assets)
+ files.append(path)
+
+ tower_auth = tower_auth_config(module)
+ failed = False
+ with settings.runtime_values(**tower_auth):
+ try:
+ sender = Sender(no_color=False)
+ old_stdout = sys.stdout
+ sys.stdout = captured_stdout = StringIO()
+ try:
+ sender.send(files, prevent, password_management)
+ except TypeError:
+ # Newer versions of TowerCLI require 4 parameters
+ sender.send(files, prevent, [], password_management)
+
+ if sender.error_messages > 0:
+ failed = True
+ result['msg'] = "Transfer Failed with %d errors" % sender.error_messages
+ if sender.changed_messages > 0:
+ result['changed'] = True
+ except TowerCLIError as e:
+ result['msg'] = e.message
+ failed = True
+ finally:
+ if path is not None:
+ os.remove(path)
+ result['output'] = captured_stdout.getvalue().split("\n")
+ sys.stdout = old_stdout
+
+ # Return stdout so that module returns will work
+ if failed:
+ module.fail_json(**result)
+ else:
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_settings.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_settings.py
new file mode 100644
index 00000000..c2e8ed1a
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_settings.py
@@ -0,0 +1,176 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2018, Nikhil Jain <nikjain@redhat.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_settings
+author: "Nikhil Jain (@jainnikhil30)"
+short_description: Modify Ansible Tower settings.
+description:
+ - Modify Ansible Tower settings. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - Name of setting to modify
+ type: str
+ value:
+ description:
+ - Value to be modified for given setting.
+ - If given a non-string type, will make best effort to cast it to type API expects.
+ - For better control over types, use the C(settings) param instead.
+ type: str
+ settings:
+ description:
+ - A data structure to be sent into the settings endpoint
+ type: dict
+requirements:
+ - pyyaml
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+- name: Set the value of AWX_PROOT_BASE_PATH
+ tower_settings:
+ name: AWX_PROOT_BASE_PATH
+ value: "/tmp"
+ register: testing_settings
+
+- name: Set the value of AWX_PROOT_SHOW_PATHS
+ tower_settings:
+ name: "AWX_PROOT_SHOW_PATHS"
+ value: "'/var/lib/awx/projects/', '/tmp'"
+ register: testing_settings
+
+- name: Set the LDAP Auth Bind Password
+ tower_settings:
+ name: "AUTH_LDAP_BIND_PASSWORD"
+ value: "Password"
+ no_log: true
+
+- name: Set all the LDAP Auth Bind Params
+ tower_settings:
+ settings:
+ AUTH_LDAP_BIND_PASSWORD: "password"
+ AUTH_LDAP_USER_ATTR_MAP:
+ email: "mail"
+ first_name: "givenName"
+ last_name: "surname"
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+try:
+ import yaml
+ HAS_YAML = True
+except ImportError:
+ HAS_YAML = False
+
+
+def coerce_type(module, value):
+ # If our value is already None we can just return directly
+ if value is None:
+ return value
+
+ yaml_ish = bool((
+ value.startswith('{') and value.endswith('}')
+ ) or (
+ value.startswith('[') and value.endswith(']'))
+ )
+ if yaml_ish:
+ if not HAS_YAML:
+ module.fail_json(msg="yaml is not installed, try 'pip install pyyaml'")
+ return yaml.safe_load(value)
+ elif value.lower in ('true', 'false', 't', 'f'):
+ return {'t': True, 'f': False}[value[0].lower()]
+ try:
+ return int(value)
+ except ValueError:
+ pass
+ return value
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(),
+ value=dict(),
+ settings=dict(type='dict'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(
+ argument_spec=argument_spec,
+ required_one_of=[['name', 'settings']],
+ mutually_exclusive=[['name', 'settings']],
+ required_if=[['name', 'present', ['value']]]
+ )
+
+ # Extract our parameters
+ name = module.params.get('name')
+ value = module.params.get('value')
+ new_settings = module.params.get('settings')
+
+ # If we were given a name/value pair we will just make settings out of that and proceed normally
+ if new_settings is None:
+ new_value = coerce_type(module, value)
+
+ new_settings = {name: new_value}
+
+ # Load the existing settings
+ existing_settings = module.get_endpoint('settings/all')['json']
+
+ # Begin a json response
+ json_response = {'changed': False, 'old_values': {}}
+
+ # Check any of the settings to see if anything needs to be updated
+ needs_update = False
+ for a_setting in new_settings:
+ if a_setting not in existing_settings or existing_settings[a_setting] != new_settings[a_setting]:
+ # At least one thing is different so we need to patch
+ needs_update = True
+ json_response['old_values'][a_setting] = existing_settings[a_setting]
+
+ # If nothing needs an update we can simply exit with the response (as not changed)
+ if not needs_update:
+ module.exit_json(**json_response)
+
+ # Make the call to update the settings
+ response = module.patch_endpoint('settings/all', **{'data': new_settings})
+
+ if response['status_code'] == 200:
+ # Set the changed response to True
+ json_response['changed'] = True
+
+ # To deal with the old style values we need to return 'value' in the response
+ new_values = {}
+ for a_setting in new_settings:
+ new_values[a_setting] = response['json'][a_setting]
+
+ # If we were using a name we will just add a value of a string, otherwise we will return an array in values
+ if name is not None:
+ json_response['value'] = new_values[name]
+ else:
+ json_response['values'] = new_values
+
+ module.exit_json(**json_response)
+ elif 'json' in response and '__all__' in response['json']:
+ module.fail_json(msg=response['json']['__all__'])
+ else:
+ module.fail_json(**{'msg': "Unable to update settings, see response", 'response': response})
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_team.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_team.py
new file mode 100644
index 00000000..8ed56e48
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_team.py
@@ -0,0 +1,114 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2017, Wayne Witzel III <wayne@riotousliving.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_team
+author: "Wayne Witzel III (@wwitzel3)"
+short_description: create, update, or destroy Ansible Tower team.
+description:
+ - Create, update, or destroy Ansible Tower teams. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - Name to use for the team.
+ required: True
+ type: str
+ new_name:
+ description:
+ - To use when changing a team's name.
+ type: str
+ description:
+ description:
+ - The description to use for the team.
+ type: str
+ organization:
+ description:
+ - Organization the team should be made a member of.
+ required: True
+ type: str
+ state:
+ description:
+ - Desired state of the resource.
+ choices: ["present", "absent"]
+ default: "present"
+ type: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+EXAMPLES = '''
+- name: Create tower team
+ tower_team:
+ name: Team Name
+ description: Team Description
+ organization: test-org
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ new_name=dict(),
+ description=dict(),
+ organization=dict(required=True),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ name = module.params.get('name')
+ new_name = module.params.get('new_name')
+ description = module.params.get('description')
+ organization = module.params.get('organization')
+ state = module.params.get('state')
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ org_id = module.resolve_name_to_id('organizations', organization)
+
+ # Attempt to look up team based on the provided name and org ID
+ team = module.get_one('teams', **{
+ 'data': {
+ 'name': name,
+ 'organization': org_id
+ }
+ })
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(team)
+
+ # Create the data that gets sent for create and update
+ team_fields = {
+ 'name': new_name if new_name else name,
+ 'organization': org_id
+ }
+ if description is not None:
+ team_fields['description'] = description
+
+ # If the state was present and we can let the module build or update the existing team, this will return on its own
+ module.create_or_update_if_needed(team, team_fields, endpoint='teams', item_type='team')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_token.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_token.py
new file mode 100644
index 00000000..ee6fd5c2
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_token.py
@@ -0,0 +1,201 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+
+# (c) 2020, John Westcott IV <john.westcott.iv@redhat.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: tower_token
+author: "John Westcott IV (@john-westcott-iv)"
+version_added: "2.3"
+short_description: create, update, or destroy Ansible Tower tokens.
+description:
+ - Create or destroy Ansible Tower tokens. See
+ U(https://www.ansible.com/tower) for an overview.
+ - In addition, the module sets an Ansible fact which can be passed into other
+ tower_* modules as the parameter tower_oauthtoken. See examples for usage.
+ - Because of the sensitive nature of tokens, the created token value is only available once
+ through the Ansible fact. (See RETURN for details)
+ - Due to the nature of tokens in Tower this module is not idempotent. A second will
+ with the same parameters will create a new token.
+ - If you are creating a temporary token for use with modules you should delete the token
+ when you are done with it. See the example for how to do it.
+options:
+ description:
+ description:
+ - Optional description of this access token.
+ required: False
+ type: str
+ default: ''
+ application:
+ description:
+ - The application tied to this token.
+ required: False
+ type: str
+ scope:
+ description:
+ - Allowed scopes, further restricts user's permissions. Must be a simple space-separated string with allowed scopes ['read', 'write'].
+ required: False
+ type: str
+ default: 'write'
+ choices: ["read", "write"]
+ existing_token:
+ description: The data structure produced from tower_token in create mode to be used with state absent.
+ type: dict
+ existing_token_id:
+ description: A token ID (number) which can be used to delete an arbitrary token with state absent.
+ type: str
+ state:
+ description:
+ - Desired state of the resource.
+ choices: ["present", "absent"]
+ default: "present"
+ type: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+- block:
+ - name: Create a new token using an existing token
+ tower_token:
+ description: '{{ token_description }}'
+ scope: "write"
+ state: present
+ tower_oauthtoken: "{{ my_existing_token }}"
+
+ - name: Delete this token
+ tower_token:
+ existing_token: "{{ tower_token }}"
+ state: absent
+
+ - name: Create a new token using username/password
+ tower_token:
+ description: '{{ token_description }}'
+ scope: "write"
+ state: present
+ tower_username: "{{ my_username }}"
+ tower_password: "{{ my_password }}"
+
+ - name: Use our new token to make another call
+ tower_job_list:
+ tower_oauthtoken: "{{ tower_token }}"
+
+ always:
+ - name: Delete our Token with the token we created
+ tower_token:
+ existing_token: "{{ tower_token }}"
+ state: absent
+ when: tower_token is defined
+
+- name: Delete a token by its id
+ tower_token:
+ existing_token_id: 4
+ state: absent
+'''
+
+RETURN = '''
+tower_token:
+ type: dict
+ description: An Ansible Fact variable representing a Tower token object which can be used for auth in subsequent modules. See examples for usage.
+ contains:
+ token:
+ description: The token that was generated. This token can never be accessed again, make sure this value is noted before it is lost.
+ type: str
+ id:
+ description: The numeric ID of the token created
+ type: str
+ returned: on successful create
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def return_token(module, last_response):
+ # A token is special because you can never get the actual token ID back from the API.
+ # So the default module return would give you an ID but then the token would forever be masked on you.
+ # This method will return the entire token object we got back so that a user has access to the token
+
+ module.json_output['ansible_facts'] = {
+ 'tower_token': last_response,
+ }
+ module.exit_json(**module.json_output)
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ description=dict(),
+ application=dict(),
+ scope=dict(choices=['read', 'write'], default='write'),
+ existing_token=dict(type='dict'),
+ existing_token_id=dict(),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[
+ ('existing_token', 'existing_token_id'),
+ ],
+ # If we are state absent make sure one of existing_token or existing_token_id are present
+ required_if=[
+ ['state', 'absent', ('existing_token', 'existing_token_id'), True, ],
+ ],
+ )
+
+ # Extract our parameters
+ description = module.params.get('description')
+ application = module.params.get('application')
+ scope = module.params.get('scope')
+ existing_token = module.params.get('existing_token')
+ existing_token_id = module.params.get('existing_token_id')
+ state = module.params.get('state')
+
+ if state == 'absent':
+ if not existing_token:
+ existing_token = module.get_one('tokens', **{
+ 'data': {
+ 'id': existing_token_id,
+ }
+ })
+
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(existing_token)
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ application_id = None
+ if application:
+ application_id = module.resolve_name_to_id('applications', application)
+
+ # Create the data that gets sent for create and update
+ new_fields = {}
+ if description is not None:
+ new_fields['description'] = description
+ if application is not None:
+ new_fields['application'] = application_id
+ if scope is not None:
+ new_fields['scope'] = scope
+
+ # If the state was present and we can let the module build or update the existing item, this will return on its own
+ module.create_or_update_if_needed(
+ None, new_fields,
+ endpoint='tokens', item_type='token',
+ associations={
+ },
+ on_create=return_token,
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_user.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_user.py
new file mode 100644
index 00000000..15c41cb0
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_user.py
@@ -0,0 +1,169 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# (c) 2020, John Westcott IV <john.westcott.iv@redhat.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_user
+author: "John Westcott IV (@john-westcott-iv)"
+short_description: create, update, or destroy Ansible Tower users.
+description:
+ - Create, update, or destroy Ansible Tower users. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ username:
+ description:
+ - Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.
+ required: True
+ type: str
+ first_name:
+ description:
+ - First name of the user.
+ type: str
+ last_name:
+ description:
+ - Last name of the user.
+ type: str
+ email:
+ description:
+ - Email address of the user.
+ type: str
+ is_superuser:
+ description:
+ - Designates that this user has all permissions without explicitly assigning them.
+ type: bool
+ default: False
+ aliases: ['superuser']
+ is_system_auditor:
+ description:
+ - User is a system wide auditor.
+ type: bool
+ default: False
+ aliases: ['auditor']
+ password:
+ description:
+ - Write-only field used to change the password.
+ type: str
+ state:
+ description:
+ - Desired state of the resource.
+ choices: ["present", "absent"]
+ default: "present"
+ type: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+
+EXAMPLES = '''
+- name: Add tower user
+ tower_user:
+ username: jdoe
+ password: foobarbaz
+ email: jdoe@example.org
+ first_name: John
+ last_name: Doe
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Add tower user as a system administrator
+ tower_user:
+ username: jdoe
+ password: foobarbaz
+ email: jdoe@example.org
+ superuser: yes
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Add tower user as a system auditor
+ tower_user:
+ username: jdoe
+ password: foobarbaz
+ email: jdoe@example.org
+ auditor: yes
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+- name: Delete tower user
+ tower_user:
+ username: jdoe
+ email: jdoe@example.org
+ state: absent
+ tower_config_file: "~/tower_cli.cfg"
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ username=dict(required=True),
+ first_name=dict(),
+ last_name=dict(),
+ email=dict(),
+ is_superuser=dict(type='bool', default=False, aliases=['superuser']),
+ is_system_auditor=dict(type='bool', default=False, aliases=['auditor']),
+ password=dict(no_log=True),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ username = module.params.get('username')
+ first_name = module.params.get('first_name')
+ last_name = module.params.get('last_name')
+ email = module.params.get('email')
+ is_superuser = module.params.get('is_superuser')
+ is_system_auditor = module.params.get('is_system_auditor')
+ password = module.params.get('password')
+ state = module.params.get('state')
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+
+ # Attempt to look up an existing item based on the provided data
+ existing_item = module.get_one('users', **{
+ 'data': {
+ 'username': username,
+ }
+ })
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(existing_item)
+
+ # Create the data that gets sent for create and update
+ new_fields = {}
+ if username:
+ new_fields['username'] = username
+ if first_name:
+ new_fields['first_name'] = first_name
+ if last_name:
+ new_fields['last_name'] = last_name
+ if email:
+ new_fields['email'] = email
+ if is_superuser:
+ new_fields['is_superuser'] = is_superuser
+ if is_system_auditor:
+ new_fields['is_system_auditor'] = is_system_auditor
+ if password:
+ new_fields['password'] = password
+
+ # If the state was present and we can let the module build or update the existing item, this will return on its own
+ module.create_or_update_if_needed(existing_item, new_fields, endpoint='users', item_type='user')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_job_template.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_job_template.py
new file mode 100644
index 00000000..8fb350b9
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_job_template.py
@@ -0,0 +1,273 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+
+# (c) 2020, John Westcott IV <john.westcott.iv@redhat.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: tower_workflow_job_template
+author: "John Westcott IV (@john-westcott-iv)"
+short_description: create, update, or destroy Ansible Tower workflow job templates.
+description:
+ - Create, update, or destroy Ansible Tower workflow job templates.
+ - Replaces the deprecated tower_workflow_template module.
+ - Use the tower_workflow_job_template_node after this to build the workflow's graph.
+options:
+ name:
+ description:
+ - Name of this workflow job template.
+ required: True
+ type: str
+ new_name:
+ description:
+ - Setting this option will change the existing name.
+ type: str
+ description:
+ description:
+ - Optional description of this workflow job template.
+ type: str
+ extra_vars:
+ description:
+ - Variables which will be made available to jobs ran inside the workflow.
+ type: dict
+ organization:
+ description:
+ - Organization the workflow job template exists in.
+ - Used to help lookup the object, cannot be modified using this module.
+ - If not provided, will lookup by name only, which does not work with duplicates.
+ type: str
+ allow_simultaneous:
+ description:
+ - Allow simultaneous runs of the workflow job template.
+ type: bool
+ ask_variables_on_launch:
+ description:
+ - Prompt user for C(extra_vars) on launch.
+ type: bool
+ inventory:
+ description:
+ - Inventory applied as a prompt, assuming job template prompts for inventory
+ type: str
+ limit:
+ description:
+ - Limit applied as a prompt, assuming job template prompts for limit
+ type: str
+ scm_branch:
+ description:
+ - SCM branch applied as a prompt, assuming job template prompts for SCM branch
+ type: str
+ ask_inventory_on_launch:
+ description:
+ - Prompt user for inventory on launch of this workflow job template
+ type: bool
+ ask_scm_branch_on_launch:
+ description:
+ - Prompt user for SCM branch on launch of this workflow job template
+ type: bool
+ ask_limit_on_launch:
+ description:
+ - Prompt user for limit on launch of this workflow job template
+ type: bool
+ webhook_service:
+ description:
+ - Service that webhook requests will be accepted from
+ type: str
+ choices:
+ - github
+ - gitlab
+ webhook_credential:
+ description:
+ - Personal Access Token for posting back the status to the service API
+ type: str
+ survey_enabled:
+ description:
+ - Setting that variable will prompt the user for job type on the
+ workflow launch.
+ type: bool
+ survey:
+ description:
+ - The definition of the survey associated to the workflow.
+ type: dict
+ state:
+ description:
+ - Desired state of the resource.
+ choices:
+ - present
+ - absent
+ default: "present"
+ type: str
+ notification_templates_started:
+ description:
+ - list of notifications to send on start
+ type: list
+ elements: str
+ notification_templates_success:
+ description:
+ - list of notifications to send on success
+ type: list
+ elements: str
+ notification_templates_error:
+ description:
+ - list of notifications to send on error
+ type: list
+ elements: str
+ notification_templates_approvals:
+ description:
+ - list of notifications to send on start
+ type: list
+ elements: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+- name: Create a workflow job template
+ tower_workflow_job_template:
+ name: example-workflow
+ description: created by Ansible Playbook
+ organization: Default
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+import json
+
+
+def update_survey(module, last_request):
+ spec_endpoint = last_request.get('related', {}).get('survey_spec')
+ module.post_endpoint(spec_endpoint, **{'data': module.params.get('survey')})
+ module.exit_json(**module.json_output)
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True),
+ new_name=dict(),
+ description=dict(),
+ extra_vars=dict(type='dict'),
+ organization=dict(),
+ survey=dict(type='dict'), # special handling
+ survey_enabled=dict(type='bool'),
+ allow_simultaneous=dict(type='bool'),
+ ask_variables_on_launch=dict(type='bool'),
+ inventory=dict(),
+ limit=dict(),
+ scm_branch=dict(),
+ ask_inventory_on_launch=dict(type='bool'),
+ ask_scm_branch_on_launch=dict(type='bool'),
+ ask_limit_on_launch=dict(type='bool'),
+ webhook_service=dict(choices=['github', 'gitlab']),
+ webhook_credential=dict(),
+ notification_templates_started=dict(type="list", elements='str'),
+ notification_templates_success=dict(type="list", elements='str'),
+ notification_templates_error=dict(type="list", elements='str'),
+ notification_templates_approvals=dict(type="list", elements='str'),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ name = module.params.get('name')
+ new_name = module.params.get("new_name")
+ state = module.params.get('state')
+
+ new_fields = {}
+ search_fields = {'name': name}
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ organization = module.params.get('organization')
+ if organization:
+ organization_id = module.resolve_name_to_id('organizations', organization)
+ search_fields['organization'] = new_fields['organization'] = organization_id
+
+ # Attempt to look up an existing item based on the provided data
+ existing_item = module.get_one('workflow_job_templates', **{'data': search_fields})
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(existing_item)
+
+ inventory = module.params.get('inventory')
+ if inventory:
+ new_fields['inventory'] = module.resolve_name_to_id('inventories', inventory)
+
+ webhook_credential = module.params.get('webhook_credential')
+ if webhook_credential:
+ new_fields['webhook_credential'] = module.resolve_name_to_id('webhook_credential', webhook_credential)
+
+ # Create the data that gets sent for create and update
+ new_fields['name'] = new_name if new_name else name
+ for field_name in (
+ 'description', 'survey_enabled', 'allow_simultaneous',
+ 'limit', 'scm_branch', 'extra_vars',
+ 'ask_inventory_on_launch', 'ask_scm_branch_on_launch', 'ask_limit_on_launch', 'ask_variables_on_launch',
+ 'webhook_service',):
+ field_val = module.params.get(field_name)
+ if field_val:
+ new_fields[field_name] = field_val
+
+ if 'extra_vars' in new_fields:
+ new_fields['extra_vars'] = json.dumps(new_fields['extra_vars'])
+
+ association_fields = {}
+
+ notifications_start = module.params.get('notification_templates_started')
+ if notifications_start is not None:
+ association_fields['notification_templates_started'] = []
+ for item in notifications_start:
+ association_fields['notification_templates_started'].append(module.resolve_name_to_id('notification_templates', item))
+
+ notifications_success = module.params.get('notification_templates_success')
+ if notifications_success is not None:
+ association_fields['notification_templates_success'] = []
+ for item in notifications_success:
+ association_fields['notification_templates_success'].append(module.resolve_name_to_id('notification_templates', item))
+
+ notifications_error = module.params.get('notification_templates_error')
+ if notifications_error is not None:
+ association_fields['notification_templates_error'] = []
+ for item in notifications_error:
+ association_fields['notification_templates_error'].append(module.resolve_name_to_id('notification_templates', item))
+
+ notifications_approval = module.params.get('notification_templates_approvals')
+ if notifications_approval is not None:
+ association_fields['notification_templates_approvals'] = []
+ for item in notifications_approval:
+ association_fields['notification_templates_approvals'].append(module.resolve_name_to_id('notification_templates', item))
+
+ on_change = None
+ new_spec = module.params.get('survey')
+ if new_spec:
+ existing_spec = None
+ if existing_item:
+ spec_endpoint = existing_item.get('related', {}).get('survey_spec')
+ existing_spec = module.get_endpoint(spec_endpoint)
+ if new_spec != existing_spec:
+ module.json_output['changed'] = True
+ if existing_item and module.has_encrypted_values(existing_spec):
+ module._encrypted_changed_warning('survey_spec', existing_item, warning=True)
+ on_change = update_survey
+
+ # If the state was present and we can let the module build or update the existing item, this will return on its own
+ module.create_or_update_if_needed(
+ existing_item, new_fields,
+ endpoint='workflow_job_templates', item_type='workflow_job_template',
+ associations=association_fields,
+ on_create=on_change, on_update=on_change
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_job_template_node.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_job_template_node.py
new file mode 100644
index 00000000..7ef9e146
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_job_template_node.py
@@ -0,0 +1,271 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+
+# (c) 2020, John Westcott IV <john.westcott.iv@redhat.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: tower_workflow_job_template_node
+author: "John Westcott IV (@john-westcott-iv)"
+short_description: create, update, or destroy Ansible Tower workflow job template nodes.
+description:
+ - Create, update, or destroy Ansible Tower workflow job template nodes.
+ - Use this to build a graph for a workflow, which dictates what the workflow runs.
+ - Replaces the deprecated tower_workflow_template module schema command.
+ - You can create nodes first, and link them afterwards, and not worry about ordering.
+ For failsafe referencing of a node, specify identifier, WFJT, and organization.
+ With those specified, you can choose to modify or not modify any other parameter.
+options:
+ extra_data:
+ description:
+ - Variables to apply at launch time.
+ - Will only be accepted if job template prompts for vars or has a survey asking for those vars.
+ type: dict
+ default: {}
+ inventory:
+ description:
+ - Inventory applied as a prompt, if job template prompts for inventory
+ type: str
+ scm_branch:
+ description:
+ - SCM branch applied as a prompt, if job template prompts for SCM branch
+ type: str
+ job_type:
+ description:
+ - Job type applied as a prompt, if job template prompts for job type
+ type: str
+ choices:
+ - 'run'
+ - 'check'
+ job_tags:
+ description:
+ - Job tags applied as a prompt, if job template prompts for job tags
+ type: str
+ skip_tags:
+ description:
+ - Tags to skip, applied as a prompt, if job tempalte prompts for job tags
+ type: str
+ limit:
+ description:
+ - Limit to act on, applied as a prompt, if job template prompts for limit
+ type: str
+ diff_mode:
+ description:
+ - Run diff mode, applied as a prompt, if job template prompts for diff mode
+ type: bool
+ verbosity:
+ description:
+ - Verbosity applied as a prompt, if job template prompts for verbosity
+ type: str
+ choices:
+ - '0'
+ - '1'
+ - '2'
+ - '3'
+ - '4'
+ - '5'
+ workflow_job_template:
+ description:
+ - The workflow job template the node exists in.
+ - Used for looking up the node, cannot be modified after creation.
+ required: True
+ type: str
+ aliases:
+ - workflow
+ organization:
+ description:
+ - The organization of the workflow job template the node exists in.
+ - Used for looking up the workflow, not a direct model field.
+ type: str
+ unified_job_template:
+ description:
+ - Name of unified job template to run in the workflow.
+ - Can be a job template, project, inventory source, etc.
+ - Omit if creating an approval node (not yet implemented).
+ type: str
+ all_parents_must_converge:
+ description:
+ - If enabled then the node will only run if all of the parent nodes have met the criteria to reach this node
+ type: bool
+ identifier:
+ description:
+ - An identifier for this node that is unique within its workflow.
+ - It is copied to workflow job nodes corresponding to this node.
+ required: True
+ type: str
+ always_nodes:
+ description:
+ - Nodes that will run after this node completes.
+ - List of node identifiers.
+ type: list
+ elements: str
+ success_nodes:
+ description:
+ - Nodes that will run after this node on success.
+ - List of node identifiers.
+ type: list
+ elements: str
+ failure_nodes:
+ description:
+ - Nodes that will run after this node on failure.
+ - List of node identifiers.
+ type: list
+ elements: str
+ credentials:
+ description:
+ - Credentials to be applied to job as launch-time prompts.
+ - List of credential names.
+ - Uniqueness is not handled rigorously.
+ type: list
+ elements: str
+ state:
+ description:
+ - Desired state of the resource.
+ choices: ["present", "absent"]
+ default: "present"
+ type: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+- name: Create a node, follows tower_workflow_job_template example
+ tower_workflow_job_template_node:
+ identifier: my-first-node
+ workflow: example-workflow
+ unified_job_template: jt-for-node-use
+ organization: Default # organization of workflow job template
+ extra_data:
+ foo_key: bar_value
+
+- name: Create parent node for prior node
+ tower_workflow_job_template_node:
+ identifier: my-root-node
+ workflow: example-workflow
+ unified_job_template: jt-for-node-use
+ organization: Default
+ success_nodes:
+ - my-first-node
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ identifier=dict(required=True),
+ workflow_job_template=dict(required=True, aliases=['workflow']),
+ organization=dict(),
+ extra_data=dict(type='dict'),
+ inventory=dict(),
+ scm_branch=dict(),
+ job_type=dict(choices=['run', 'check']),
+ job_tags=dict(),
+ skip_tags=dict(),
+ limit=dict(),
+ diff_mode=dict(type='bool'),
+ verbosity=dict(choices=['0', '1', '2', '3', '4', '5']),
+ unified_job_template=dict(),
+ all_parents_must_converge=dict(type='bool'),
+ success_nodes=dict(type='list', elements='str'),
+ always_nodes=dict(type='list', elements='str'),
+ failure_nodes=dict(type='list', elements='str'),
+ credentials=dict(type='list', elements='str'),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+ identifier = module.params.get('identifier')
+ state = module.params.get('state')
+
+ new_fields = {}
+ search_fields = {'identifier': identifier}
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ workflow_job_template = module.params.get('workflow_job_template')
+ workflow_job_template_id = None
+ if workflow_job_template:
+ wfjt_search_fields = {'name': workflow_job_template}
+ organization = module.params.get('organization')
+ if organization:
+ organization_id = module.resolve_name_to_id('organizations', organization)
+ wfjt_search_fields['organization'] = organization_id
+ wfjt_data = module.get_one('workflow_job_templates', **{'data': wfjt_search_fields})
+ if wfjt_data is None:
+ module.fail_json(msg="The workflow {0} in organization {1} was not found on the Tower server".format(
+ workflow_job_template, organization
+ ))
+ workflow_job_template_id = wfjt_data['id']
+ search_fields['workflow_job_template'] = new_fields['workflow_job_template'] = workflow_job_template_id
+
+ # Attempt to look up an existing item based on the provided data
+ existing_item = module.get_one('workflow_job_template_nodes', **{'data': search_fields})
+
+ if state == 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(existing_item)
+
+ unified_job_template = module.params.get('unified_job_template')
+ if unified_job_template:
+ new_fields['unified_job_template'] = module.resolve_name_to_id('unified_job_templates', unified_job_template)
+
+ inventory = module.params.get('inventory')
+ if inventory:
+ new_fields['inventory'] = module.resolve_name_to_id('inventories', inventory)
+
+ # Create the data that gets sent for create and update
+ for field_name in (
+ 'identifier', 'extra_data', 'scm_branch', 'job_type', 'job_tags', 'skip_tags',
+ 'limit', 'diff_mode', 'verbosity', 'all_parents_must_converge',):
+ field_val = module.params.get(field_name)
+ if field_val:
+ new_fields[field_name] = field_val
+
+ association_fields = {}
+ for association in ('always_nodes', 'success_nodes', 'failure_nodes', 'credentials'):
+ name_list = module.params.get(association)
+ if name_list is None:
+ continue
+ id_list = []
+ for sub_name in name_list:
+ if association == 'credentials':
+ endpoint = 'credentials'
+ lookup_data = {'name': sub_name}
+ else:
+ endpoint = 'workflow_job_template_nodes'
+ lookup_data = {'identifier': sub_name}
+ if workflow_job_template_id:
+ lookup_data['workflow_job_template'] = workflow_job_template_id
+ sub_obj = module.get_one(endpoint, **{'data': lookup_data})
+ if sub_obj is None:
+ module.fail_json(msg='Could not find {0} entry with name {1}'.format(association, sub_name))
+ id_list.append(sub_obj['id'])
+ if id_list:
+ association_fields[association] = id_list
+
+ # In the case of a new object, the utils need to know it is a node
+ new_fields['type'] = 'workflow_job_template_node'
+
+ # If the state was present and we can let the module build or update the existing item, this will return on its own
+ module.create_or_update_if_needed(
+ existing_item, new_fields,
+ endpoint='workflow_job_template_nodes', item_type='workflow_job_template_node',
+ associations=association_fields
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_launch.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_launch.py
new file mode 100644
index 00000000..249feeed
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_launch.py
@@ -0,0 +1,206 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# 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
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: tower_workflow_launch
+author: "John Westcott IV (@john-westcott-iv)"
+short_description: Run a workflow in Ansible Tower
+description:
+ - Launch an Ansible Tower workflows. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+ name:
+ description:
+ - The name of the workflow template to run.
+ required: True
+ type: str
+ aliases:
+ - workflow_template
+ organization:
+ description:
+ - Organization the workflow job template exists in.
+ - Used to help lookup the object, cannot be modified using this module.
+ - If not provided, will lookup by name only, which does not work with duplicates.
+ type: str
+ inventory:
+ description:
+ - Inventory to use for the job ran with this workflow, only used if prompt for inventory is set.
+ type: str
+ limit:
+ description:
+ - Limit to use for the I(job_template).
+ type: str
+ scm_branch:
+ description:
+ - A specific branch of the SCM project to run the template on.
+ - This is only applicable if your project allows for branch override.
+ type: str
+ extra_vars:
+ description:
+ - Any extra vars required to launch the job.
+ type: dict
+ wait:
+ description:
+ - Wait for the workflow to complete.
+ default: True
+ type: bool
+ interval:
+ description:
+ - The interval to request an update from Tower.
+ required: False
+ default: 1
+ type: float
+ timeout:
+ description:
+ - If waiting for the workflow to complete this will abort after this
+ amount of seconds
+ type: int
+extends_documentation_fragment: awx.awx.auth
+'''
+
+RETURN = '''
+job_info:
+ description: dictionary containing information about the workflow executed
+ returned: If workflow launched
+ type: dict
+'''
+
+
+EXAMPLES = '''
+- name: Launch a workflow with a timeout of 10 seconds
+ tower_workflow_launch:
+ workflow_template: "Test Workflow"
+ timeout: 10
+
+- name: Launch a Workflow with extra_vars without waiting
+ tower_workflow_launch:
+ workflow_template: "Test workflow"
+ extra_vars:
+ var1: My First Variable
+ var2: My Second Variable
+ wait: False
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+import json
+import time
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+ name=dict(required=True, aliases=['workflow_template']),
+ organization=dict(),
+ inventory=dict(),
+ limit=dict(),
+ scm_branch=dict(),
+ extra_vars=dict(type='dict'),
+ wait=dict(required=False, default=True, type='bool'),
+ interval=dict(required=False, default=1.0, type='float'),
+ timeout=dict(required=False, default=None, type='int'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ optional_args = {}
+ # Extract our parameters
+ name = module.params.get('name')
+ organization = module.params.get('organization')
+ inventory = module.params.get('inventory')
+ optional_args['limit'] = module.params.get('limit')
+ wait = module.params.get('wait')
+ interval = module.params.get('interval')
+ timeout = module.params.get('timeout')
+
+ # Special treatment of extra_vars parameter
+ extra_vars = module.params.get('extra_vars')
+ if extra_vars is not None:
+ optional_args['extra_vars'] = json.dumps(extra_vars)
+
+ # Create a datastructure to pass into our job launch
+ post_data = {}
+ for key in optional_args.keys():
+ if optional_args[key]:
+ post_data[key] = optional_args[key]
+
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+ if inventory:
+ post_data['inventory'] = module.resolve_name_to_id('inventories', inventory)
+
+ # Attempt to look up job_template based on the provided name
+ lookup_data = {'name': name}
+ if organization:
+ lookup_data['organization'] = module.resolve_name_to_id('organizations', organization)
+ workflow_job_template = module.get_one('workflow_job_templates', data=lookup_data)
+
+ if workflow_job_template is None:
+ module.fail_json(msg="Unable to find workflow job template")
+
+ # The API will allow you to submit values to a jb launch that are not prompt on launch.
+ # Therefore, we will test to see if anything is set which is not prompt on launch and fail.
+ check_vars_to_prompts = {
+ 'inventory': 'ask_inventory_on_launch',
+ 'limit': 'ask_limit_on_launch',
+ 'scm_branch': 'ask_scm_branch_on_launch',
+ 'extra_vars': 'ask_variables_on_launch',
+ }
+
+ param_errors = []
+ for variable_name in check_vars_to_prompts:
+ if variable_name in post_data and not workflow_job_template[check_vars_to_prompts[variable_name]]:
+ param_errors.append("The field {0} was specified but the workflow job template does not allow for it to be overridden".format(variable_name))
+ if len(param_errors) > 0:
+ module.fail_json(msg="Parameters specified which can not be passed into wotkflow job template, see errors for details", errors=param_errors)
+
+ # Launch the job
+ result = module.post_endpoint(workflow_job_template['related']['launch'], data=post_data)
+
+ if result['status_code'] != 201:
+ module.fail_json(msg="Failed to launch workflow, see response for details", response=result)
+
+ module.json_output['changed'] = True
+ module.json_output['id'] = result['json']['id']
+ module.json_output['status'] = result['json']['status']
+ # This is for backwards compatability
+ module.json_output['job_info'] = {'id': result['json']['id']}
+
+ if not wait:
+ module.exit_json(**module.json_output)
+
+ # Grab our start time to compare against for the timeout
+ start = time.time()
+
+ job_url = result['json']['url']
+ while not result['json']['finished']:
+ # If we are past our time out fail with a message
+ if timeout and timeout < time.time() - start:
+ module.json_output['msg'] = "Monitoring aborted due to timeout"
+ module.fail_json(**module.json_output)
+
+ # Put the process to sleep for our interval
+ time.sleep(interval)
+
+ result = module.get_endpoint(job_url)
+ module.json_output['status'] = result['json']['status']
+
+ # If the job has failed, we want to raise a task failure for that so we get a non-zero response.
+ if result['json']['failed']:
+ module.json_output['msg'] = 'The workflow "{0}" failed'.format(name)
+ module.fail_json(**module.json_output)
+
+ module.exit_json(**module.json_output)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_template.py b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_template.py
new file mode 100644
index 00000000..a8557b2a
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/plugins/modules/tower_workflow_template.py
@@ -0,0 +1,230 @@
+#!/usr/bin/python
+# coding: utf-8 -*-
+
+# Copyright: (c) 2018, Adrien Fleury <fleu42@gmail.com>
+# 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
+
+
+ANSIBLE_METADATA = {'status': ['deprecated'],
+ 'supported_by': 'community',
+ 'metadata_version': '1.1'}
+
+
+DOCUMENTATION = '''
+---
+module: tower_workflow_template
+deprecated:
+ removed_in: "14.0.0"
+ why: Deprecated in favor of C(_workflow_job_template) and C(_workflow_job_template_node) modules.
+ alternative: Use M(tower_workflow_job_template) and M(_workflow_job_template_node) instead.
+author: "Adrien Fleury (@fleu42)"
+short_description: create, update, or destroy Ansible Tower workflow template.
+description:
+ - A tower-cli based module for CRUD actions on workflow job templates.
+ - Enables use of the old schema functionality.
+ - Not updated for new features, convert to the modules for
+ workflow_job_template and workflow_job_template node instead.
+options:
+ allow_simultaneous:
+ description:
+ - If enabled, simultaneous runs of this job template will be allowed.
+ type: bool
+ ask_extra_vars:
+ description:
+ - Prompt user for (extra_vars) on launch.
+ type: bool
+ ask_inventory:
+ description:
+ - Prompt user for inventory on launch.
+ type: bool
+ description:
+ description:
+ - The description to use for the workflow.
+ type: str
+ extra_vars:
+ description:
+ - Extra variables used by Ansible in YAML or key=value format.
+ type: dict
+ inventory:
+ description:
+ - Name of the inventory to use for the job template.
+ type: str
+ name:
+ description:
+ - The name to use for the workflow.
+ required: True
+ type: str
+ organization:
+ description:
+ - The organization the workflow is linked to.
+ type: str
+ schema:
+ description:
+ - >
+ The schema is a JSON- or YAML-formatted string defining the
+ hierarchy structure that connects the nodes. Refer to Tower
+ documentation for more information.
+ type: list
+ elements: dict
+ survey_enabled:
+ description:
+ - Setting that variable will prompt the user for job type on the
+ workflow launch.
+ type: bool
+ survey:
+ description:
+ - The definition of the survey associated to the workflow.
+ type: dict
+ state:
+ description:
+ - Desired state of the resource.
+ default: "present"
+ choices: ["present", "absent"]
+ type: str
+
+requirements:
+- ansible-tower-cli >= 3.0.2
+
+extends_documentation_fragment: awx.awx.auth_legacy
+'''
+
+
+EXAMPLES = '''
+- tower_workflow_template:
+ name: Workflow Template
+ description: My very first Workflow Template
+ organization: My optional Organization
+ schema: "{{ lookup('file', 'my_workflow.json') }}"
+
+- tower_workflow_template:
+ name: Workflow Template
+ state: absent
+'''
+
+
+RETURN = ''' # '''
+
+
+from ..module_utils.tower_legacy import (
+ TowerLegacyModule,
+ tower_auth_config,
+ tower_check_mode
+)
+
+import json
+
+try:
+ import tower_cli
+ import tower_cli.exceptions as exc
+ from tower_cli.conf import settings
+except ImportError:
+ pass
+
+
+def main():
+ argument_spec = dict(
+ name=dict(required=True),
+ description=dict(),
+ extra_vars=dict(type='dict'),
+ organization=dict(),
+ allow_simultaneous=dict(type='bool'),
+ schema=dict(type='list', elements='dict'),
+ survey=dict(type='dict'),
+ survey_enabled=dict(type='bool'),
+ inventory=dict(),
+ ask_inventory=dict(type='bool'),
+ ask_extra_vars=dict(type='bool'),
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ module = TowerLegacyModule(
+ argument_spec=argument_spec,
+ supports_check_mode=False
+ )
+
+ module.deprecate(msg=(
+ "This module is replaced by the combination of tower_workflow_job_template and "
+ "tower_workflow_job_template_node. This uses the old tower-cli and wll be "
+ "removed in 2022."
+ ), version='awx.awx:14.0.0')
+
+ name = module.params.get('name')
+ state = module.params.get('state')
+
+ schema = None
+ if module.params.get('schema'):
+ schema = module.params.get('schema')
+
+ if schema and state == 'absent':
+ module.fail_json(
+ msg='Setting schema when state is absent is not allowed',
+ changed=False
+ )
+
+ json_output = {'workflow_template': name, 'state': state}
+
+ tower_auth = tower_auth_config(module)
+ with settings.runtime_values(**tower_auth):
+ tower_check_mode(module)
+ wfjt_res = tower_cli.get_resource('workflow')
+ params = {}
+ params['name'] = name
+
+ if module.params.get('description'):
+ params['description'] = module.params.get('description')
+
+ if module.params.get('organization'):
+ organization_res = tower_cli.get_resource('organization')
+ try:
+ organization = organization_res.get(
+ name=module.params.get('organization'))
+ params['organization'] = organization['id']
+ except exc.NotFound as excinfo:
+ module.fail_json(
+ msg='Failed to update organization source,'
+ 'organization not found: {0}'.format(excinfo),
+ changed=False
+ )
+
+ if module.params.get('survey'):
+ params['survey_spec'] = module.params.get('survey')
+
+ if module.params.get('ask_extra_vars'):
+ params['ask_variables_on_launch'] = module.params.get('ask_extra_vars')
+
+ if module.params.get('ask_inventory'):
+ params['ask_inventory_on_launch'] = module.params.get('ask_inventory')
+
+ for key in ('allow_simultaneous', 'inventory',
+ 'survey_enabled', 'description'):
+ if module.params.get(key):
+ params[key] = module.params.get(key)
+
+ # Special treatment for tower-cli extra_vars
+ extra_vars = module.params.get('extra_vars')
+ if extra_vars:
+ params['extra_vars'] = [json.dumps(extra_vars)]
+
+ try:
+ if state == 'present':
+ params['create_on_missing'] = True
+ result = wfjt_res.modify(**params)
+ json_output['id'] = result['id']
+ if schema:
+ wfjt_res.schema(result['id'], json.dumps(schema))
+ elif state == 'absent':
+ params['fail_on_missing'] = False
+ result = wfjt_res.delete(**params)
+ except (exc.ConnectionError, exc.BadRequest, exc.AuthError) as excinfo:
+ module.fail_json(msg='Failed to update workflow template: \
+ {0}'.format(excinfo), changed=False)
+
+ json_output['changed'] = result['changed']
+ module.exit_json(**json_output)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/requirements.txt b/collections-debian-merged/ansible_collections/awx/awx/requirements.txt
new file mode 100644
index 00000000..37d1ffa1
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/requirements.txt
@@ -0,0 +1,3 @@
+pytz # for tower_schedule_rrule lookup plugin
+python-dateutil>=2.7.0 # tower_schedule_rrule
+awxkit # For import and export modules \ No newline at end of file
diff --git a/collections-debian-merged/ansible_collections/awx/awx/setup.cfg b/collections-debian-merged/ansible_collections/awx/awx/setup.cfg
new file mode 100644
index 00000000..fdfea44c
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/setup.cfg
@@ -0,0 +1,3 @@
+[flake8]
+max-line-length=160
+ignore=E402 \ No newline at end of file
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/conftest.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/conftest.py
new file mode 100644
index 00000000..5db00d63
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/conftest.py
@@ -0,0 +1,289 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import io
+import os
+import json
+import datetime
+import importlib
+from contextlib import redirect_stdout, suppress
+from unittest import mock
+import logging
+
+from requests.models import Response, PreparedRequest
+
+import pytest
+
+from awx.main.tests.functional.conftest import _request
+from awx.main.models import Organization, Project, Inventory, JobTemplate, Credential, CredentialType
+
+try:
+ import tower_cli # noqa
+ HAS_TOWER_CLI = True
+except ImportError:
+ HAS_TOWER_CLI = False
+
+try:
+ # Because awxkit will be a directory at the root of this makefile and we are using python3, import awxkit will work even if its not installed.
+ # However, awxkit will not contain api whih causes a stack failure down on line 170 when we try to mock it.
+ # So here we are importing awxkit.api to prevent that. Then you only get an error on tests for awxkit functionality.
+ import awxkit.api
+ HAS_AWX_KIT = True
+except ImportError:
+ HAS_AWX_KIT = False
+
+logger = logging.getLogger('awx.main.tests')
+
+
+def sanitize_dict(din):
+ '''Sanitize Django response data to purge it of internal types
+ so it may be used to cast a requests response object
+ '''
+ if isinstance(din, (int, str, type(None), bool)):
+ return din # native JSON types, no problem
+ elif isinstance(din, datetime.datetime):
+ return din.isoformat()
+ elif isinstance(din, list):
+ for i in range(len(din)):
+ din[i] = sanitize_dict(din[i])
+ return din
+ elif isinstance(din, dict):
+ for k in din.copy().keys():
+ din[k] = sanitize_dict(din[k])
+ return din
+ else:
+ return str(din) # translation proxies often not string but stringlike
+
+
+@pytest.fixture(autouse=True)
+def collection_path_set(monkeypatch):
+ """Monkey patch sys.path, insert the root of the collection folder
+ so that content can be imported without being fully packaged
+ """
+ base_folder = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
+ )
+ monkeypatch.syspath_prepend(base_folder)
+
+
+@pytest.fixture
+def collection_import():
+ """These tests run assuming that the awx_collection folder is inserted
+ into the PATH before-hand by collection_path_set.
+ But all imports internally to the collection
+ go through this fixture so that can be changed if needed.
+ For instance, we could switch to fully-qualified import paths.
+ """
+ def rf(path):
+ return importlib.import_module(path)
+ return rf
+
+
+@pytest.fixture
+def run_module(request, collection_import):
+ def rf(module_name, module_params, request_user):
+
+ def new_request(self, method, url, **kwargs):
+ kwargs_copy = kwargs.copy()
+ if 'data' in kwargs:
+ if isinstance(kwargs['data'], dict):
+ kwargs_copy['data'] = kwargs['data']
+ elif kwargs['data'] is None:
+ pass
+ elif isinstance(kwargs['data'], str):
+ kwargs_copy['data'] = json.loads(kwargs['data'])
+ else:
+ raise RuntimeError('Expected data to be dict or str, got {0}, data: {1}'.format(
+ type(kwargs['data']), kwargs['data']))
+ if 'params' in kwargs and method == 'GET':
+ # query params for GET are handled a bit differently by
+ # tower-cli and python requests as opposed to REST framework APIRequestFactory
+ if not kwargs_copy.get('data'):
+ kwargs_copy['data'] = {}
+ if isinstance(kwargs['params'], dict):
+ kwargs_copy['data'].update(kwargs['params'])
+ elif isinstance(kwargs['params'], list):
+ for k, v in kwargs['params']:
+ kwargs_copy['data'][k] = v
+
+ # make request
+ rf = _request(method.lower())
+ django_response = rf(url, user=request_user, expect=None, **kwargs_copy)
+
+ # requests library response object is different from the Django response, but they are the same concept
+ # this converts the Django response object into a requests response object for consumption
+ resp = Response()
+ py_data = django_response.data
+ sanitize_dict(py_data)
+ resp._content = bytes(json.dumps(django_response.data), encoding='utf8')
+ resp.status_code = django_response.status_code
+ resp.headers = {'X-API-Product-Name': 'AWX', 'X-API-Product-Version': '0.0.1-devel'}
+
+ if request.config.getoption('verbose') > 0:
+ logger.info(
+ '%s %s by %s, code:%s',
+ method, '/api/' + url.split('/api/')[1],
+ request_user.username, resp.status_code
+ )
+
+ resp.request = PreparedRequest()
+ resp.request.prepare(method=method, url=url)
+ return resp
+
+ def new_open(self, method, url, **kwargs):
+ r = new_request(self, method, url, **kwargs)
+ m = mock.MagicMock(read=mock.MagicMock(return_value=r._content),
+ status=r.status_code,
+ getheader=mock.MagicMock(side_effect=r.headers.get)
+ )
+ return m
+
+ stdout_buffer = io.StringIO()
+ # Requies specific PYTHONPATH, see docs
+ # Note that a proper Ansiballz explosion of the modules will have an import path like:
+ # ansible_collections.awx.awx.plugins.modules.{}
+ # We should consider supporting that in the future
+ resource_module = collection_import('plugins.modules.{0}'.format(module_name))
+
+ if not isinstance(module_params, dict):
+ raise RuntimeError('Module params must be dict, got {0}'.format(type(module_params)))
+
+ # Ansible params can be passed as an invocation argument or over stdin
+ # this short circuits within the AnsibleModule interface
+ def mock_load_params(self):
+ self.params = module_params
+
+ if getattr(resource_module, 'TowerAWXKitModule', None):
+ resource_class = resource_module.TowerAWXKitModule
+ elif getattr(resource_module, 'TowerAPIModule', None):
+ resource_class = resource_module.TowerAPIModule
+ elif getattr(resource_module, 'TowerLegacyModule', None):
+ resource_class = resource_module.TowerLegacyModule
+ else:
+ raise("The module has neither a TowerLegacyModule, TowerAWXKitModule or a TowerAPIModule")
+
+ with mock.patch.object(resource_class, '_load_params', new=mock_load_params):
+ # Call the test utility (like a mock server) instead of issuing HTTP requests
+ with mock.patch('ansible.module_utils.urls.Request.open', new=new_open):
+ if HAS_TOWER_CLI:
+ tower_cli_mgr = mock.patch('tower_cli.api.Session.request', new=new_request)
+ elif HAS_AWX_KIT:
+ tower_cli_mgr = mock.patch('awxkit.api.client.requests.Session.request', new=new_request)
+ else:
+ tower_cli_mgr = suppress()
+ with tower_cli_mgr:
+ try:
+ # Ansible modules return data to the mothership over stdout
+ with redirect_stdout(stdout_buffer):
+ resource_module.main()
+ except SystemExit:
+ pass # A system exit indicates successful execution
+ except Exception:
+ # dump the stdout back to console for debugging
+ print(stdout_buffer.getvalue())
+ raise
+
+ module_stdout = stdout_buffer.getvalue().strip()
+ try:
+ result = json.loads(module_stdout)
+ except Exception as e:
+ raise Exception('Module did not write valid JSON, error: {0}, stdout:\n{1}'.format(str(e), module_stdout))
+ # A module exception should never be a test expectation
+ if 'exception' in result:
+ if "ModuleNotFoundError: No module named 'tower_cli'" in result['exception']:
+ pytest.skip('The tower-cli library is needed to run this test, module no longer supported.')
+ raise Exception('Module encountered error:\n{0}'.format(result['exception']))
+ return result
+
+ return rf
+
+
+@pytest.fixture
+def survey_spec():
+ return {
+ "spec": [
+ {
+ "index": 0,
+ "question_name": "my question?",
+ "default": "mydef",
+ "variable": "myvar",
+ "type": "text",
+ "required": False
+ }
+ ],
+ "description": "test",
+ "name": "test"
+ }
+
+
+@pytest.fixture
+def organization():
+ return Organization.objects.create(name='Default')
+
+
+@pytest.fixture
+def project(organization):
+ return Project.objects.create(
+ name="test-proj",
+ description="test-proj-desc",
+ organization=organization,
+ playbook_files=['helloworld.yml'],
+ local_path='_92__test_proj',
+ scm_revision='1234567890123456789012345678901234567890',
+ scm_url='localhost',
+ scm_type='git'
+ )
+
+
+@pytest.fixture
+def inventory(organization):
+ return Inventory.objects.create(
+ name='test-inv',
+ organization=organization
+ )
+
+
+@pytest.fixture
+def job_template(project, inventory):
+ return JobTemplate.objects.create(
+ name='test-jt',
+ project=project,
+ inventory=inventory,
+ playbook='helloworld.yml'
+ )
+
+
+@pytest.fixture
+def machine_credential(organization):
+ ssh_type = CredentialType.defaults['ssh']()
+ ssh_type.save()
+ return Credential.objects.create(
+ credential_type=ssh_type, name='machine-cred',
+ inputs={'username': 'test_user', 'password': 'pas4word'}
+ )
+
+
+@pytest.fixture
+def vault_credential(organization):
+ ct = CredentialType.defaults['vault']()
+ ct.save()
+ return Credential.objects.create(
+ credential_type=ct, name='vault-cred',
+ inputs={'vault_id': 'foo', 'vault_password': 'pas4word'}
+ )
+
+
+@pytest.fixture
+def silence_deprecation():
+ """The deprecation warnings are stored in a global variable
+ they will create cross-test interference. Use this to turn them off.
+ """
+ with mock.patch('ansible.module_utils.basic.AnsibleModule.deprecate') as this_mock:
+ yield this_mock
+
+
+@pytest.fixture(autouse=True)
+def silence_warning():
+ """Warnings use global variable, same as deprecations."""
+ with mock.patch('ansible.module_utils.basic.AnsibleModule.warn') as this_mock:
+ yield this_mock
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_credential.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_credential.py
new file mode 100644
index 00000000..754133de
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_credential.py
@@ -0,0 +1,178 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import Credential, CredentialType, Organization
+
+
+@pytest.fixture
+def cred_type():
+ # Make a credential type which will be used by the credential
+ ct = CredentialType.objects.create(
+ name='Ansible Galaxy Token',
+ inputs={
+ "fields": [
+ {
+ "id": "token",
+ "type": "string",
+ "secret": True,
+ "label": "Ansible Galaxy Secret Token Value"
+ }
+ ],
+ "required": ["token"]
+ },
+ injectors={
+ "extra_vars": {
+ "galaxy_token": "{{token}}",
+ }
+ }
+ )
+ return ct
+
+
+@pytest.mark.django_db
+def test_create_machine_credential(run_module, admin_user, organization, silence_deprecation):
+ Organization.objects.create(name='test-org')
+ # create the ssh credential type
+ ct = CredentialType.defaults['ssh']()
+ ct.save()
+ # Example from docs
+ result = run_module('tower_credential', dict(
+ name='Test Machine Credential',
+ organization=organization.name,
+ kind='ssh',
+ state='present'
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ cred = Credential.objects.get(name='Test Machine Credential')
+ assert cred.credential_type == ct
+
+ assert result['name'] == "Test Machine Credential"
+ assert result['id'] == cred.pk
+
+
+@pytest.mark.django_db
+def test_create_vault_credential(run_module, admin_user, organization, silence_deprecation):
+ # https://github.com/ansible/ansible/issues/61324
+ Organization.objects.create(name='test-org')
+ ct = CredentialType.defaults['vault']()
+ ct.save()
+
+ result = run_module('tower_credential', dict(
+ name='Test Vault Credential',
+ organization=organization.name,
+ kind='vault',
+ vault_id='bar',
+ vault_password='foobar',
+ state='present'
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ cred = Credential.objects.get(name='Test Vault Credential')
+ assert cred.credential_type == ct
+ assert 'vault_id' in cred.inputs
+ assert 'vault_password' in cred.inputs
+
+ assert result['name'] == "Test Vault Credential"
+ assert result['id'] == cred.pk
+
+
+@pytest.mark.django_db
+def test_ct_precedence_over_kind(run_module, admin_user, organization, cred_type, silence_deprecation):
+ result = run_module('tower_credential', dict(
+ name='A credential',
+ organization=organization.name,
+ kind='ssh',
+ credential_type=cred_type.name,
+ state='present'
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ cred = Credential.objects.get(name='A credential')
+
+ assert cred.credential_type == cred_type
+
+
+@pytest.mark.django_db
+def test_input_overrides_old_fields(run_module, admin_user, organization, silence_deprecation):
+ # create the vault credential type
+ ct = CredentialType.defaults['vault']()
+ ct.save()
+ result = run_module('tower_credential', dict(
+ name='A Vault credential',
+ organization=organization.name,
+ kind='vault',
+ vault_id='1234',
+ inputs={'vault_id': 'asdf'},
+ state='present',
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ cred = Credential.objects.get(name='A Vault credential')
+
+ assert cred.inputs['vault_id'] == 'asdf'
+
+
+@pytest.mark.django_db
+def test_missing_credential_type(run_module, admin_user, organization):
+ Organization.objects.create(name='test-org')
+ result = run_module('tower_credential', dict(
+ name='A credential',
+ organization=organization.name,
+ credential_type='foobar',
+ state='present'
+ ), admin_user)
+ assert result.get('failed', False), result
+ assert 'foobar was not found on the Tower server' in result['msg']
+
+
+@pytest.mark.django_db
+def test_make_use_of_custom_credential_type(run_module, organization, admin_user, cred_type):
+ result = run_module('tower_credential', dict(
+ name='Galaxy Token for Steve',
+ organization=organization.name,
+ credential_type=cred_type.name,
+ inputs={'token': '7rEZK38DJl58A7RxA6EC7lLvUHbBQ1'}
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', False), result
+
+ cred = Credential.objects.get(name='Galaxy Token for Steve')
+ assert cred.credential_type_id == cred_type.id
+ assert list(cred.inputs.keys()) == ['token']
+ assert cred.inputs['token'].startswith('$encrypted$')
+ assert len(cred.inputs['token']) >= len('$encrypted$') + len('7rEZK38DJl58A7RxA6EC7lLvUHbBQ1')
+
+ assert result['name'] == "Galaxy Token for Steve"
+ assert result['id'] == cred.pk
+
+
+@pytest.mark.django_db
+def test_secret_field_write_twice(run_module, organization, admin_user, cred_type):
+ val1 = '7rEZK38DJl58A7RxA6EC7lLvUHbBQ1'
+ result = run_module('tower_credential', dict(
+ name='Galaxy Token for Steve',
+ organization=organization.name,
+ credential_type=cred_type.name,
+ inputs={'token': val1}
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ Credential.objects.get(id=result['id']).inputs['token'] == val1
+
+ val2 = '7rEZ238DJl5837rxA6xxxlLvUHbBQ1'
+
+ result = run_module('tower_credential', dict(
+ name='Galaxy Token for Steve',
+ organization=organization.name,
+ credential_type=cred_type.name,
+ inputs={'token': val2}
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ Credential.objects.get(id=result['id']).inputs['token'] == val2
+ assert result.get('changed'), result
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_credential_input_source.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_credential_input_source.py
new file mode 100644
index 00000000..a676ab15
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_credential_input_source.py
@@ -0,0 +1,333 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import CredentialInputSource, Credential, CredentialType, Organization
+
+
+@pytest.fixture
+def aim_cred_type():
+ ct = CredentialType.defaults['aim']()
+ ct.save()
+ return ct
+
+
+# Test CyberArk AIM credential source
+@pytest.fixture
+def source_cred_aim(aim_cred_type):
+ return Credential.objects.create(
+ name='CyberArk AIM Cred',
+ credential_type=aim_cred_type,
+ inputs={
+ "url": "https://cyberark.example.com",
+ "app_id": "myAppID",
+ "verify": "false"
+ }
+ )
+
+
+@pytest.mark.django_db
+def test_aim_credential_source(run_module, admin_user, organization, source_cred_aim, silence_deprecation):
+ ct = CredentialType.defaults['ssh']()
+ ct.save()
+ tgt_cred = Credential.objects.create(
+ name='Test Machine Credential',
+ organization=organization,
+ credential_type=ct,
+ inputs={'username': 'bob'}
+ )
+
+ result = run_module('tower_credential_input_source', dict(
+ source_credential=source_cred_aim.name,
+ target_credential=tgt_cred.name,
+ input_field_name='password',
+ metadata={"object_query": "Safe=SUPERSAFE;Object=MyAccount"},
+ state='present'
+ ), admin_user)
+
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ assert CredentialInputSource.objects.count() == 1
+ cis = CredentialInputSource.objects.first()
+
+ assert cis.metadata['object_query'] == "Safe=SUPERSAFE;Object=MyAccount"
+ assert cis.source_credential.name == source_cred_aim.name
+ assert cis.target_credential.name == tgt_cred.name
+ assert cis.input_field_name == 'password'
+ assert result['id'] == cis.pk
+
+
+# Test CyberArk Conjur credential source
+@pytest.fixture
+def source_cred_conjur(organization):
+ # Make a credential type which will be used by the credential
+ ct = CredentialType.defaults['conjur']()
+ ct.save()
+ return Credential.objects.create(
+ name='CyberArk CONJUR Cred',
+ credential_type=ct,
+ inputs={
+ "url": "https://cyberark.example.com",
+ "api_key": "myApiKey",
+ "account": "account",
+ "username": "username"
+ }
+ )
+
+
+@pytest.mark.django_db
+def test_conjur_credential_source(run_module, admin_user, organization, source_cred_conjur, silence_deprecation):
+ ct = CredentialType.defaults['ssh']()
+ ct.save()
+ tgt_cred = Credential.objects.create(
+ name='Test Machine Credential',
+ organization=organization,
+ credential_type=ct,
+ inputs={'username': 'bob'}
+ )
+
+ result = run_module('tower_credential_input_source', dict(
+ source_credential=source_cred_conjur.name,
+ target_credential=tgt_cred.name,
+ input_field_name='password',
+ metadata={"secret_path": "/path/to/secret"},
+ state='present'
+ ), admin_user)
+
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ assert CredentialInputSource.objects.count() == 1
+ cis = CredentialInputSource.objects.first()
+
+ assert cis.metadata['secret_path'] == "/path/to/secret"
+ assert cis.source_credential.name == source_cred_conjur.name
+ assert cis.target_credential.name == tgt_cred.name
+ assert cis.input_field_name == 'password'
+ assert result['id'] == cis.pk
+
+
+# Test Hashicorp Vault secret credential source
+@pytest.fixture
+def source_cred_hashi_secret(organization):
+ # Make a credential type which will be used by the credential
+ ct = CredentialType.defaults['hashivault_kv']()
+ ct.save()
+ return Credential.objects.create(
+ name='HashiCorp secret Cred',
+ credential_type=ct,
+ inputs={
+ "url": "https://secret.hash.example.com",
+ "token": "myApiKey",
+ "role_id": "role",
+ "secret_id": "secret"
+ }
+ )
+
+
+@pytest.mark.django_db
+def test_hashi_secret_credential_source(run_module, admin_user, organization, source_cred_hashi_secret, silence_deprecation):
+ ct = CredentialType.defaults['ssh']()
+ ct.save()
+ tgt_cred = Credential.objects.create(
+ name='Test Machine Credential',
+ organization=organization,
+ credential_type=ct,
+ inputs={'username': 'bob'}
+ )
+
+ result = run_module('tower_credential_input_source', dict(
+ source_credential=source_cred_hashi_secret.name,
+ target_credential=tgt_cred.name,
+ input_field_name='password',
+ metadata={"secret_path": "/path/to/secret", "auth_path": "/path/to/auth", "secret_backend": "backend", "secret_key": "a_key"},
+ state='present'
+ ), admin_user)
+
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ assert CredentialInputSource.objects.count() == 1
+ cis = CredentialInputSource.objects.first()
+
+ assert cis.metadata['secret_path'] == "/path/to/secret"
+ assert cis.metadata['auth_path'] == "/path/to/auth"
+ assert cis.metadata['secret_backend'] == "backend"
+ assert cis.metadata['secret_key'] == "a_key"
+ assert cis.source_credential.name == source_cred_hashi_secret.name
+ assert cis.target_credential.name == tgt_cred.name
+ assert cis.input_field_name == 'password'
+ assert result['id'] == cis.pk
+
+
+# Test Hashicorp Vault signed ssh credential source
+@pytest.fixture
+def source_cred_hashi_ssh(organization):
+ # Make a credential type which will be used by the credential
+ ct = CredentialType.defaults['hashivault_ssh']()
+ ct.save()
+ return Credential.objects.create(
+ name='HashiCorp ssh Cred',
+ credential_type=ct,
+ inputs={
+ "url": "https://ssh.hash.example.com",
+ "token": "myApiKey",
+ "role_id": "role",
+ "secret_id": "secret"
+ }
+ )
+
+
+@pytest.mark.django_db
+def test_hashi_ssh_credential_source(run_module, admin_user, organization, source_cred_hashi_ssh, silence_deprecation):
+ ct = CredentialType.defaults['ssh']()
+ ct.save()
+ tgt_cred = Credential.objects.create(
+ name='Test Machine Credential',
+ organization=organization,
+ credential_type=ct,
+ inputs={'username': 'bob'}
+ )
+
+ result = run_module('tower_credential_input_source', dict(
+ source_credential=source_cred_hashi_ssh.name,
+ target_credential=tgt_cred.name,
+ input_field_name='password',
+ metadata={"secret_path": "/path/to/secret", "auth_path": "/path/to/auth", "role": "role", "public_key": "a_key", "valid_principals": "some_value"},
+ state='present'
+ ), admin_user)
+
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ assert CredentialInputSource.objects.count() == 1
+ cis = CredentialInputSource.objects.first()
+
+ assert cis.metadata['secret_path'] == "/path/to/secret"
+ assert cis.metadata['auth_path'] == "/path/to/auth"
+ assert cis.metadata['role'] == "role"
+ assert cis.metadata['public_key'] == "a_key"
+ assert cis.metadata['valid_principals'] == "some_value"
+ assert cis.source_credential.name == source_cred_hashi_ssh.name
+ assert cis.target_credential.name == tgt_cred.name
+ assert cis.input_field_name == 'password'
+ assert result['id'] == cis.pk
+
+
+# Test Azure Key Vault credential source
+@pytest.fixture
+def source_cred_azure_kv(organization):
+ # Make a credential type which will be used by the credential
+ ct = CredentialType.defaults['azure_kv']()
+ ct.save()
+ return Credential.objects.create(
+ name='Azure KV Cred',
+ credential_type=ct,
+ inputs={
+ "url": "https://key.azure.example.com",
+ "client": "client",
+ "secret": "secret",
+ "tenant": "tenant",
+ "cloud_name": "the_cloud",
+ }
+ )
+
+
+@pytest.mark.django_db
+def test_azure_kv_credential_source(run_module, admin_user, organization, source_cred_azure_kv, silence_deprecation):
+ ct = CredentialType.defaults['ssh']()
+ ct.save()
+ tgt_cred = Credential.objects.create(
+ name='Test Machine Credential',
+ organization=organization,
+ credential_type=ct,
+ inputs={'username': 'bob'}
+ )
+
+ result = run_module('tower_credential_input_source', dict(
+ source_credential=source_cred_azure_kv.name,
+ target_credential=tgt_cred.name,
+ input_field_name='password',
+ metadata={"secret_field": "my_pass"},
+ state='present'
+ ), admin_user)
+
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ assert CredentialInputSource.objects.count() == 1
+ cis = CredentialInputSource.objects.first()
+
+ assert cis.metadata['secret_field'] == "my_pass"
+ assert cis.source_credential.name == source_cred_azure_kv.name
+ assert cis.target_credential.name == tgt_cred.name
+ assert cis.input_field_name == 'password'
+ assert result['id'] == cis.pk
+
+
+# Test Changing Credential Source
+@pytest.fixture
+def source_cred_aim_alt(aim_cred_type):
+ return Credential.objects.create(
+ name='Alternate CyberArk AIM Cred',
+ credential_type=aim_cred_type,
+ inputs={
+ "url": "https://cyberark-alt.example.com",
+ "app_id": "myAltID",
+ "verify": "false"
+ }
+ )
+
+
+@pytest.mark.django_db
+def test_aim_credential_source(run_module, admin_user, organization, source_cred_aim, source_cred_aim_alt, silence_deprecation):
+ ct = CredentialType.defaults['ssh']()
+ ct.save()
+ tgt_cred = Credential.objects.create(
+ name='Test Machine Credential',
+ organization=organization,
+ credential_type=ct,
+ inputs={'username': 'bob'}
+ )
+
+ result = run_module('tower_credential_input_source', dict(
+ source_credential=source_cred_aim.name,
+ target_credential=tgt_cred.name,
+ input_field_name='password',
+ metadata={"object_query": "Safe=SUPERSAFE;Object=MyAccount"},
+ state='present'
+ ), admin_user)
+
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ unchangedResult = run_module('tower_credential_input_source', dict(
+ source_credential=source_cred_aim.name,
+ target_credential=tgt_cred.name,
+ input_field_name='password',
+ metadata={"object_query": "Safe=SUPERSAFE;Object=MyAccount"},
+ state='present'
+ ), admin_user)
+
+ assert not unchangedResult.get('failed', False), result.get('msg', result)
+ assert not unchangedResult.get('changed'), result
+
+ changedResult = run_module('tower_credential_input_source', dict(
+ source_credential=source_cred_aim_alt.name,
+ target_credential=tgt_cred.name,
+ input_field_name='password',
+ state='present'
+ ), admin_user)
+
+ assert not changedResult.get('failed', False), changedResult.get('msg', result)
+ assert changedResult.get('changed'), result
+
+ assert CredentialInputSource.objects.count() == 1
+ cis = CredentialInputSource.objects.first()
+
+ assert cis.metadata['object_query'] == "Safe=SUPERSAFE;Object=MyAccount"
+ assert cis.source_credential.name == source_cred_aim_alt.name
+ assert cis.target_credential.name == tgt_cred.name
+ assert cis.input_field_name == 'password'
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_credential_type.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_credential_type.py
new file mode 100644
index 00000000..29f4869d
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_credential_type.py
@@ -0,0 +1,49 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import CredentialType
+
+
+@pytest.mark.django_db
+def test_create_custom_credential_type(run_module, admin_user, silence_deprecation):
+ # Example from docs
+ result = run_module('tower_credential_type', dict(
+ name='Nexus',
+ description='Credentials type for Nexus',
+ kind='cloud',
+ inputs={"fields": [{"id": "server", "type": "string", "default": "", "label": ""}], "required": []},
+ injectors={'extra_vars': {'nexus_credential': 'test'}},
+ state='present',
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ ct = CredentialType.objects.get(name='Nexus')
+
+ assert result['name'] == 'Nexus'
+ assert result['id'] == ct.pk
+
+ assert ct.inputs == {"fields": [{"id": "server", "type": "string", "default": "", "label": ""}], "required": []}
+ assert ct.injectors == {'extra_vars': {'nexus_credential': 'test'}}
+
+
+@pytest.mark.django_db
+def test_changed_false_with_api_changes(run_module, admin_user):
+ result = run_module('tower_credential_type', dict(
+ name='foo',
+ kind='cloud',
+ inputs={"fields": [{"id": "env_value", "label": "foo", "default": "foo"}]},
+ injectors={'env': {'TEST_ENV_VAR': '{{ env_value }}'}},
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ result = run_module('tower_credential_type', dict(
+ name='foo',
+ inputs={"fields": [{"id": "env_value", "label": "foo", "default": "foo"}]},
+ injectors={'env': {'TEST_ENV_VAR': '{{ env_value }}'}},
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert not result.get('changed'), result
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_group.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_group.py
new file mode 100644
index 00000000..3e5bcc6b
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_group.py
@@ -0,0 +1,117 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import Organization, Inventory, Group, Host
+
+
+@pytest.mark.django_db
+def test_create_group(run_module, admin_user):
+ org = Organization.objects.create(name='test-org')
+ inv = Inventory.objects.create(name='test-inv', organization=org)
+ variables = {"ansible_network_os": "iosxr"}
+
+ result = run_module('tower_group', dict(
+ name='Test Group',
+ inventory='test-inv',
+ variables=variables,
+ state='present'
+ ), admin_user)
+ assert result.get('changed'), result
+
+ group = Group.objects.get(name='Test Group')
+ assert group.inventory == inv
+ assert group.variables == '{"ansible_network_os": "iosxr"}'
+
+ result.pop('invocation')
+ assert result == {
+ 'id': group.id,
+ 'name': 'Test Group',
+ 'changed': True,
+ }
+
+
+@pytest.mark.django_db
+def test_associate_hosts_and_children(run_module, admin_user, organization):
+ inv = Inventory.objects.create(name='test-inv', organization=organization)
+ group = Group.objects.create(name='Test Group', inventory=inv)
+
+ inv_hosts = [Host.objects.create(inventory=inv, name='foo{0}'.format(i)) for i in range(3)]
+ group.hosts.add(inv_hosts[0], inv_hosts[1])
+
+ child = Group.objects.create(inventory=inv, name='child_group')
+
+ result = run_module('tower_group', dict(
+ name='Test Group',
+ inventory='test-inv',
+ hosts=[inv_hosts[1].name, inv_hosts[2].name],
+ children=[child.name],
+ state='present'
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result['changed'] is True
+
+ assert set(group.hosts.all()) == set([inv_hosts[1], inv_hosts[2]])
+ assert set(group.children.all()) == set([child])
+
+
+@pytest.mark.django_db
+def test_associate_on_create(run_module, admin_user, organization):
+ inv = Inventory.objects.create(name='test-inv', organization=organization)
+ child = Group.objects.create(name='test-child', inventory=inv)
+ host = Host.objects.create(name='test-host', inventory=inv)
+
+ result = run_module('tower_group', dict(
+ name='Test Group',
+ inventory='test-inv',
+ hosts=[host.name],
+ groups=[child.name],
+ state='present'
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result['changed'] is True
+
+ group = Group.objects.get(pk=result['id'])
+ assert set(group.hosts.all()) == set([host])
+ assert set(group.children.all()) == set([child])
+
+
+@pytest.mark.django_db
+def test_children_alias_of_groups(run_module, admin_user, organization):
+ inv = Inventory.objects.create(name='test-inv', organization=organization)
+ group = Group.objects.create(name='Test Group', inventory=inv)
+ child = Group.objects.create(inventory=inv, name='child_group')
+ result = run_module('tower_group', dict(
+ name='Test Group',
+ inventory='test-inv',
+ groups=[child.name],
+ state='present'
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result['changed'] is True
+
+ assert set(group.children.all()) == set([child])
+
+
+@pytest.mark.django_db
+def test_tower_group_idempotent(run_module, admin_user):
+ # https://github.com/ansible/ansible/issues/46803
+ org = Organization.objects.create(name='test-org')
+ inv = Inventory.objects.create(name='test-inv', organization=org)
+ group = Group.objects.create(
+ name='Test Group',
+ inventory=inv,
+ )
+
+ result = run_module('tower_group', dict(
+ name='Test Group',
+ inventory='test-inv',
+ state='present'
+ ), admin_user)
+
+ result.pop('invocation')
+ assert result == {
+ 'id': group.id,
+ 'changed': False, # idempotency assertion
+ }
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_inventory.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_inventory.py
new file mode 100644
index 00000000..2ba52ac0
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_inventory.py
@@ -0,0 +1,60 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import Inventory
+
+
+@pytest.mark.django_db
+def test_inventory_create(run_module, admin_user, organization):
+ result = run_module('tower_inventory', {
+ 'name': 'foo-inventory',
+ 'organization': organization.name,
+ 'variables': {'foo': 'bar', 'another-foo': {'barz': 'bar2'}},
+ 'state': 'present'
+ }, admin_user)
+
+ inv = Inventory.objects.get(name='foo-inventory')
+ assert inv.variables == '{"foo": "bar", "another-foo": {"barz": "bar2"}}'
+
+ result.pop('module_args', None)
+ result.pop('invocation', None)
+ assert result == {
+ "name": "foo-inventory",
+ "id": inv.id,
+ "changed": True
+ }
+
+ assert inv.organization_id == organization.id
+
+
+@pytest.mark.django_db
+def test_invalid_smart_inventory_create(run_module, admin_user, organization):
+ result = run_module('tower_inventory', {
+ 'name': 'foo-inventory',
+ 'organization': organization.name,
+ 'kind': 'smart',
+ 'host_filter': 'ansible',
+ 'state': 'present'
+ }, admin_user)
+ assert result.get('failed', False), result
+
+ assert 'Invalid query ansible' in result['msg']
+
+
+@pytest.mark.django_db
+def test_valid_smart_inventory_create(run_module, admin_user, organization):
+ result = run_module('tower_inventory', {
+ 'name': 'foo-inventory',
+ 'organization': organization.name,
+ 'kind': 'smart',
+ 'host_filter': 'name=my_host',
+ 'state': 'present'
+ }, admin_user)
+ assert not result.get('failed', False), result
+
+ inv = Inventory.objects.get(name='foo-inventory')
+ assert inv.host_filter == 'name=my_host'
+ assert inv.kind == 'smart'
+ assert inv.organization_id == organization.id
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_inventory_source.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_inventory_source.py
new file mode 100644
index 00000000..ab029668
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_inventory_source.py
@@ -0,0 +1,256 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import Organization, Inventory, InventorySource, Project
+
+
+@pytest.fixture
+def base_inventory():
+ org = Organization.objects.create(name='test-org')
+ inv = Inventory.objects.create(name='test-inv', organization=org)
+ return inv
+
+
+@pytest.fixture
+def project(base_inventory):
+ return Project.objects.create(
+ name='test-proj',
+ organization=base_inventory.organization,
+ scm_type='git',
+ scm_url='https://github.com/ansible/test-playbooks.git',
+ )
+
+
+@pytest.mark.django_db
+def test_inventory_source_create(run_module, admin_user, base_inventory, project):
+ source_path = '/var/lib/awx/example_source_path/'
+ result = run_module('tower_inventory_source', dict(
+ name='foo',
+ inventory=base_inventory.name,
+ state='present',
+ source='scm',
+ source_path=source_path,
+ source_project=project.name
+ ), admin_user)
+ assert result.pop('changed', None), result
+
+ inv_src = InventorySource.objects.get(name='foo')
+ assert inv_src.inventory == base_inventory
+ result.pop('invocation')
+ assert result == {
+ 'id': inv_src.id,
+ 'name': 'foo',
+ }
+
+
+@pytest.mark.django_db
+def test_create_inventory_source_implied_org(run_module, admin_user):
+ org = Organization.objects.create(name='test-org')
+ inv = Inventory.objects.create(name='test-inv', organization=org)
+
+ # Credential is not required for ec2 source, because of IAM roles
+ result = run_module('tower_inventory_source', dict(
+ name='Test Inventory Source',
+ inventory='test-inv',
+ source='ec2',
+ state='present'
+ ), admin_user)
+ assert result.pop('changed', None), result
+
+ inv_src = InventorySource.objects.get(name='Test Inventory Source')
+ assert inv_src.inventory == inv
+
+ result.pop('invocation')
+ assert result == {
+ "name": "Test Inventory Source",
+ "id": inv_src.id,
+ }
+
+
+@pytest.mark.django_db
+def test_create_inventory_source_multiple_orgs(run_module, admin_user):
+ org = Organization.objects.create(name='test-org')
+ Inventory.objects.create(name='test-inv', organization=org)
+
+ # make another inventory by same name in another org
+ org2 = Organization.objects.create(name='test-org-number-two')
+ inv2 = Inventory.objects.create(name='test-inv', organization=org2)
+
+ result = run_module('tower_inventory_source', dict(
+ name='Test Inventory Source',
+ inventory=inv2.id,
+ source='ec2',
+ state='present'
+ ), admin_user)
+ assert result.pop('changed', None), result
+
+ inv_src = InventorySource.objects.get(name='Test Inventory Source')
+ assert inv_src.inventory == inv2
+
+ result.pop('invocation')
+ assert result == {
+ "name": "Test Inventory Source",
+ "id": inv_src.id,
+ }
+
+
+@pytest.mark.django_db
+def test_create_inventory_source_with_venv(run_module, admin_user, base_inventory, mocker, project):
+ path = '/var/lib/awx/venv/custom-venv/foobar13489435/'
+ source_path = '/var/lib/awx/example_source_path/'
+ with mocker.patch('awx.main.models.mixins.get_custom_venv_choices', return_value=[path]):
+ result = run_module('tower_inventory_source', dict(
+ name='foo',
+ inventory=base_inventory.name,
+ state='present',
+ source='scm',
+ source_project=project.name,
+ custom_virtualenv=path,
+ source_path=source_path
+ ), admin_user)
+ assert result.pop('changed'), result
+
+ inv_src = InventorySource.objects.get(name='foo')
+ assert inv_src.inventory == base_inventory
+ result.pop('invocation')
+
+ assert inv_src.custom_virtualenv == path
+
+
+@pytest.mark.django_db
+def test_custom_venv_no_op(run_module, admin_user, base_inventory, mocker, project):
+ """If the inventory source is modified, then it should not blank fields
+ unrelated to the params that the user passed.
+ This enforces assumptions about the behavior of the AnsibleModule
+ default argument_spec behavior.
+ """
+ source_path = '/var/lib/awx/example_source_path/'
+ inv_src = InventorySource.objects.create(
+ name='foo',
+ inventory=base_inventory,
+ source_project=project,
+ source='scm',
+ custom_virtualenv='/venv/foobar/'
+ )
+ # mock needed due to API behavior, not incorrect client behavior
+ with mocker.patch('awx.main.models.mixins.get_custom_venv_choices', return_value=['/venv/foobar/']):
+ result = run_module('tower_inventory_source', dict(
+ name='foo',
+ description='this is the changed description',
+ inventory=base_inventory.name,
+ source='scm', # is required, but behavior is arguable
+ state='present',
+ source_project=project.name,
+ source_path=source_path
+ ), admin_user)
+ assert result.pop('changed', None), result
+ inv_src.refresh_from_db()
+ assert inv_src.custom_virtualenv == '/venv/foobar/'
+ assert inv_src.description == 'this is the changed description'
+
+
+@pytest.mark.django_db
+def test_falsy_value(run_module, admin_user, base_inventory):
+ result = run_module('tower_inventory_source', dict(
+ name='falsy-test',
+ inventory=base_inventory.name,
+ source='ec2',
+ update_on_launch=True
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', None), result
+
+ inv_src = InventorySource.objects.get(name='falsy-test')
+ assert inv_src.update_on_launch is True
+
+ result = run_module('tower_inventory_source', dict(
+ name='falsy-test',
+ inventory=base_inventory.name,
+ # source='ec2',
+ update_on_launch=False
+ ), admin_user)
+
+ inv_src.refresh_from_db()
+ assert inv_src.update_on_launch is False
+
+
+# Tests related to source-specific parameters
+#
+# We want to let the API return issues with "this doesn't support that", etc.
+#
+# GUI OPTIONS:
+# - - - - - - - manual: file: scm: ec2: gce azure_rm vmware sat cloudforms openstack rhv tower custom
+# credential ? ? o o r r r r r r r r o
+# source_project ? ? r - - - - - - - - - -
+# source_path ? ? r - - - - - - - - - -
+# verbosity ? ? o o o o o o o o o o o
+# overwrite ? ? o o o o o o o o o o o
+# overwrite_vars ? ? o o o o o o o o o o o
+# update_on_launch ? ? o o o o o o o o o o o
+# UoPL ? ? o - - - - - - - - - -
+# source_regions ? ? - o o o - - - - - - -
+# instance_filters ? ? - o - - o - - - - o -
+# group_by ? ? - o - - o - - - - - -
+# source_vars* ? ? - o - o o o o o - - -
+# environmet vars* ? ? o - - - - - - - - - o
+# source_script ? ? - - - - - - - - - - r
+#
+# UoPL - update_on_project_launch
+# * - source_vars are labeled environment_vars on project and custom sources
+
+
+@pytest.mark.django_db
+def test_missing_required_credential(run_module, admin_user, base_inventory):
+ result = run_module('tower_inventory_source', dict(
+ name='Test Azure Source',
+ inventory=base_inventory.name,
+ source='azure_rm',
+ state='present'
+ ), admin_user)
+ assert result.pop('failed', None) is True, result
+
+ assert 'Credential is required for a cloud source' in result.get('msg', '')
+
+
+@pytest.mark.django_db
+def test_source_project_not_for_cloud(run_module, admin_user, base_inventory, project):
+ result = run_module('tower_inventory_source', dict(
+ name='Test ec2 Inventory Source',
+ inventory=base_inventory.name,
+ source='ec2',
+ state='present',
+ source_project=project.name
+ ), admin_user)
+ assert result.pop('failed', None) is True, result
+
+ assert 'Cannot set source_project if not SCM type' in result.get('msg', '')
+
+
+@pytest.mark.django_db
+def test_source_path_not_for_cloud(run_module, admin_user, base_inventory):
+ result = run_module('tower_inventory_source', dict(
+ name='Test ec2 Inventory Source',
+ inventory=base_inventory.name,
+ source='ec2',
+ state='present',
+ source_path='where/am/I'
+ ), admin_user)
+ assert result.pop('failed', None) is True, result
+
+ assert 'Cannot set source_path if not SCM type' in result.get('msg', '')
+
+
+@pytest.mark.django_db
+def test_scm_source_needs_project(run_module, admin_user, base_inventory):
+ result = run_module('tower_inventory_source', dict(
+ name='SCM inventory without project',
+ inventory=base_inventory.name,
+ state='present',
+ source='scm',
+ source_path='/var/lib/awx/example_source_path/'
+ ), admin_user)
+ assert result.pop('failed', None), result
+
+ assert 'Project required for scm type sources' in result.get('msg', '')
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_job.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_job.py
new file mode 100644
index 00000000..5e478d96
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_job.py
@@ -0,0 +1,55 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+from django.utils.timezone import now
+
+from awx.main.models import Job
+
+
+@pytest.mark.django_db
+def test_job_wait_successful(run_module, admin_user):
+ job = Job.objects.create(status='successful', started=now(), finished=now())
+ result = run_module('tower_job_wait', dict(
+ job_id=job.id
+ ), admin_user)
+ result.pop('invocation', None)
+ assert result.pop('finished', '')[:10] == str(job.finished)[:10]
+ assert result.pop('started', '')[:10] == str(job.started)[:10]
+ assert result == {
+ "status": "successful",
+ "changed": False,
+ "elapsed": str(job.elapsed),
+ "id": job.id
+ }
+
+
+@pytest.mark.django_db
+def test_job_wait_failed(run_module, admin_user):
+ job = Job.objects.create(status='failed', started=now(), finished=now())
+ result = run_module('tower_job_wait', dict(
+ job_id=job.id
+ ), admin_user)
+ result.pop('invocation', None)
+ assert result.pop('finished', '')[:10] == str(job.finished)[:10]
+ assert result.pop('started', '')[:10] == str(job.started)[:10]
+ assert result == {
+ "status": "failed",
+ "failed": True,
+ "changed": False,
+ "elapsed": str(job.elapsed),
+ "id": job.id,
+ "msg": "Job with id 1 failed"
+ }
+
+
+@pytest.mark.django_db
+def test_job_wait_not_found(run_module, admin_user):
+ result = run_module('tower_job_wait', dict(
+ job_id=42
+ ), admin_user)
+ result.pop('invocation', None)
+ assert result == {
+ "failed": True,
+ "msg": "Unable to wait on job 42; that ID does not exist in Tower."
+ }
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_job_template.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_job_template.py
new file mode 100644
index 00000000..4b480ed4
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_job_template.py
@@ -0,0 +1,217 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import ActivityStream, JobTemplate, Job, NotificationTemplate
+
+
+@pytest.mark.django_db
+def test_create_job_template(run_module, admin_user, project, inventory):
+
+ module_args = {
+ 'name': 'foo', 'playbook': 'helloworld.yml',
+ 'project': project.name, 'inventory': inventory.name,
+ 'extra_vars': {'foo': 'bar'},
+ 'job_type': 'run',
+ 'state': 'present'
+ }
+
+ result = run_module('tower_job_template', module_args, admin_user)
+
+ jt = JobTemplate.objects.get(name='foo')
+ assert jt.extra_vars == '{"foo": "bar"}'
+
+ assert result == {
+ "name": "foo",
+ "id": jt.id,
+ "changed": True,
+ "invocation": {
+ "module_args": module_args
+ }
+ }
+
+ assert jt.project_id == project.id
+ assert jt.inventory_id == inventory.id
+
+
+@pytest.mark.django_db
+def test_job_launch_with_prompting(run_module, admin_user, project, inventory, machine_credential):
+ JobTemplate.objects.create(
+ name='foo',
+ project=project,
+ playbook='helloworld.yml',
+ ask_variables_on_launch=True,
+ ask_inventory_on_launch=True,
+ ask_credential_on_launch=True
+ )
+ result = run_module('tower_job_launch', dict(
+ job_template='foo',
+ inventory=inventory.name,
+ credential=machine_credential.name,
+ extra_vars={"var1": "My First Variable",
+ "var2": "My Second Variable",
+ "var3": "My Third Variable"
+ }
+ ), admin_user)
+ assert result.pop('changed', None), result
+
+ job = Job.objects.get(id=result['id'])
+ assert job.extra_vars == '{"var1": "My First Variable", "var2": "My Second Variable", "var3": "My Third Variable"}'
+ assert job.inventory == inventory
+ assert [cred.id for cred in job.credentials.all()] == [machine_credential.id]
+
+
+@pytest.mark.django_db
+def test_job_template_with_new_credentials(
+ run_module, admin_user, project, inventory,
+ machine_credential, vault_credential):
+ result = run_module('tower_job_template', dict(
+ name='foo',
+ playbook='helloworld.yml',
+ project=project.name,
+ inventory=inventory.name,
+ credentials=[machine_credential.name, vault_credential.name]
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', False), result
+ jt = JobTemplate.objects.get(pk=result['id'])
+
+ assert set([machine_credential.id, vault_credential.id]) == set([
+ cred.pk for cred in jt.credentials.all()])
+
+ prior_ct = ActivityStream.objects.count()
+ result = run_module('tower_job_template', dict(
+ name='foo',
+ playbook='helloworld.yml',
+ project=project.name,
+ inventory=inventory.name,
+ credentials=[machine_credential.name, vault_credential.name]
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert not result.get('changed', True), result
+ jt.refresh_from_db()
+ assert result['id'] == jt.id
+
+ assert set([machine_credential.id, vault_credential.id]) == set([
+ cred.pk for cred in jt.credentials.all()])
+ assert ActivityStream.objects.count() == prior_ct
+
+
+@pytest.mark.django_db
+def test_job_template_with_survey_spec(run_module, admin_user, project, inventory, survey_spec):
+ result = run_module('tower_job_template', dict(
+ name='foo',
+ playbook='helloworld.yml',
+ project=project.name,
+ inventory=inventory.name,
+ survey_spec=survey_spec,
+ survey_enabled=True
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', False), result
+ jt = JobTemplate.objects.get(pk=result['id'])
+
+ assert jt.survey_spec == survey_spec
+
+ prior_ct = ActivityStream.objects.count()
+ result = run_module('tower_job_template', dict(
+ name='foo',
+ playbook='helloworld.yml',
+ project=project.name,
+ inventory=inventory.name,
+ survey_spec=survey_spec,
+ survey_enabled=True
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert not result.get('changed', True), result
+ jt.refresh_from_db()
+ assert result['id'] == jt.id
+
+ assert jt.survey_spec == survey_spec
+ assert ActivityStream.objects.count() == prior_ct
+
+
+@pytest.mark.django_db
+def test_job_template_with_survey_encrypted_default(run_module, admin_user, project, inventory, silence_warning):
+ spec = {
+ "spec": [
+ {
+ "index": 0,
+ "question_name": "my question?",
+ "default": "very_secret_value",
+ "variable": "myvar",
+ "type": "password",
+ "required": False
+ }
+ ],
+ "description": "test",
+ "name": "test"
+ }
+ for i in range(2):
+ result = run_module('tower_job_template', dict(
+ name='foo',
+ playbook='helloworld.yml',
+ project=project.name,
+ inventory=inventory.name,
+ survey_spec=spec,
+ survey_enabled=True
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ assert result.get('changed', False), result # not actually desired, but assert for sanity
+
+ silence_warning.assert_called_once_with(
+ "The field survey_spec of job_template {0} has encrypted data and "
+ "may inaccurately report task is changed.".format(result['id']))
+
+
+@pytest.mark.django_db
+def test_associate_only_on_success(run_module, admin_user, organization, project):
+ jt = JobTemplate.objects.create(
+ name='foo',
+ project=project,
+ playbook='helloworld.yml',
+ ask_inventory_on_launch=True,
+ )
+ create_kwargs = dict(
+ notification_configuration={
+ 'url': 'http://www.example.com/hook',
+ 'headers': {
+ 'X-Custom-Header': 'value123'
+ },
+ 'password': 'bar'
+ },
+ notification_type='webhook',
+ organization=organization
+ )
+ nt1 = NotificationTemplate.objects.create(name='nt1', **create_kwargs)
+ nt2 = NotificationTemplate.objects.create(name='nt2', **create_kwargs)
+
+ jt.notification_templates_error.add(nt1)
+
+ # test preservation of error NTs when success NTs are added
+ result = run_module('tower_job_template', dict(
+ name='foo',
+ playbook='helloworld.yml',
+ project=project.name,
+ notification_templates_success=['nt2']
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', True), result
+
+ assert list(jt.notification_templates_success.values_list('id', flat=True)) == [nt2.id]
+ assert list(jt.notification_templates_error.values_list('id', flat=True)) == [nt1.id]
+
+ # test removal to empty list
+ result = run_module('tower_job_template', dict(
+ name='foo',
+ playbook='helloworld.yml',
+ project=project.name,
+ notification_templates_success=[]
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', True), result
+
+ assert list(jt.notification_templates_success.values_list('id', flat=True)) == []
+ assert list(jt.notification_templates_error.values_list('id', flat=True)) == [nt1.id]
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_label.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_label.py
new file mode 100644
index 00000000..9ede40f3
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_label.py
@@ -0,0 +1,47 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import Label
+
+
+@pytest.mark.django_db
+def test_create_label(run_module, admin_user, organization):
+ result = run_module('tower_label', dict(
+ name='test-label',
+ organization=organization.name
+ ), admin_user)
+ assert not result.get('failed'), result.get('msg', result)
+ assert result.get('changed', False)
+
+ assert Label.objects.get(name='test-label').organization == organization
+
+
+@pytest.mark.django_db
+def test_create_label_using_org_id(run_module, admin_user, organization):
+ result = run_module('tower_label', dict(
+ name='test-label',
+ organization=organization.id
+ ), admin_user)
+ assert not result.get('failed'), result.get('msg', result)
+ assert result.get('changed', False)
+
+ assert Label.objects.get(name='test-label').organization == organization
+
+
+@pytest.mark.django_db
+def test_modify_label(run_module, admin_user, organization):
+ label = Label.objects.create(name='test-label', organization=organization)
+
+ result = run_module('tower_label', dict(
+ name='test-label',
+ new_name='renamed-label',
+ organization=organization.name
+ ), admin_user)
+ assert not result.get('failed'), result.get('msg', result)
+ assert result.get('changed', False)
+
+ label.refresh_from_db()
+ assert label.organization == organization
+ assert label.name == 'renamed-label'
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_module_utils.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_module_utils.py
new file mode 100644
index 00000000..e93b6ee9
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_module_utils.py
@@ -0,0 +1,104 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import json
+import sys
+
+from requests.models import Response
+from unittest import mock
+
+
+def getheader(self, header_name, default):
+ mock_headers = {'X-API-Product-Name': 'not-junk', 'X-API-Product-Version': '1.2.3'}
+ return mock_headers.get(header_name, default)
+
+
+def read(self):
+ return json.dumps({})
+
+
+def status(self):
+ return 200
+
+
+def mock_ping_response(self, method, url, **kwargs):
+ r = Response()
+ r.getheader = getheader.__get__(r)
+ r.read = read.__get__(r)
+ r.status = status.__get__(r)
+ return r
+
+
+def test_version_warning(collection_import, silence_warning):
+ TowerAPIModule = collection_import('plugins.module_utils.tower_api').TowerAPIModule
+ cli_data = {'ANSIBLE_MODULE_ARGS': {}}
+ testargs = ['module_file2.py', json.dumps(cli_data)]
+ with mock.patch.object(sys, 'argv', testargs):
+ with mock.patch('ansible.module_utils.urls.Request.open', new=mock_ping_response):
+ my_module = TowerAPIModule(argument_spec=dict())
+ my_module._COLLECTION_VERSION = "1.0.0"
+ my_module._COLLECTION_TYPE = "not-junk"
+ my_module.collection_to_version['not-junk'] = 'not-junk'
+ my_module.get_endpoint('ping')
+ silence_warning.assert_called_once_with(
+ 'You are running collection version 1.0.0 but connecting to tower version 1.2.3'
+ )
+
+
+def test_type_warning(collection_import, silence_warning):
+ TowerAPIModule = collection_import('plugins.module_utils.tower_api').TowerAPIModule
+ cli_data = {'ANSIBLE_MODULE_ARGS': {}}
+ testargs = ['module_file2.py', json.dumps(cli_data)]
+ with mock.patch.object(sys, 'argv', testargs):
+ with mock.patch('ansible.module_utils.urls.Request.open', new=mock_ping_response):
+ my_module = TowerAPIModule(argument_spec={})
+ my_module._COLLECTION_VERSION = "1.2.3"
+ my_module._COLLECTION_TYPE = "junk"
+ my_module.collection_to_version['junk'] = 'junk'
+ my_module.get_endpoint('ping')
+ silence_warning.assert_called_once_with(
+ 'You are using the junk version of this collection but connecting to not-junk'
+ )
+
+
+def test_duplicate_config(collection_import, silence_warning):
+ # imports done here because of PATH issues unique to this test suite
+ TowerAPIModule = collection_import('plugins.module_utils.tower_api').TowerAPIModule
+ data = {
+ 'name': 'zigzoom',
+ 'zig': 'zoom',
+ 'tower_username': 'bob',
+ 'tower_config_file': 'my_config'
+ }
+
+ with mock.patch.object(TowerAPIModule, 'load_config') as mock_load:
+ argument_spec = dict(
+ name=dict(required=True),
+ zig=dict(type='str'),
+ )
+ TowerAPIModule(argument_spec=argument_spec, direct_params=data)
+ assert mock_load.mock_calls[-1] == mock.call('my_config')
+
+ silence_warning.assert_called_once_with(
+ 'The parameter(s) tower_username were provided at the same time as '
+ 'tower_config_file. Precedence may be unstable, '
+ 'we suggest either using config file or params.'
+ )
+
+
+def test_no_templated_values(collection_import):
+ """This test corresponds to replacements done by
+ awx_collection/tools/roles/template_galaxy/tasks/main.yml
+ Those replacements should happen at build time, so they should not be
+ checked into source.
+ """
+ TowerAPIModule = collection_import('plugins.module_utils.tower_api').TowerAPIModule
+ assert TowerAPIModule._COLLECTION_VERSION == "0.0.1-devel", (
+ 'The collection version is templated when the collection is built '
+ 'and the code should retain the placeholder of "0.0.1-devel".'
+ )
+ InventoryModule = collection_import('plugins.inventory.tower').InventoryModule
+ assert InventoryModule.NAME == 'awx.awx.tower', (
+ 'The inventory plugin FQCN is templated when the collection is built '
+ 'and the code should retain the default of awx.awx.'
+ )
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_notification.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_notification.py
new file mode 100644
index 00000000..9d916d1d
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_notification.py
@@ -0,0 +1,111 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import NotificationTemplate
+
+
+def compare_with_encrypted(model_config, param_config):
+ '''Given a model_config from the database, assure that this is consistent
+ with the config given in the notification_configuration parameter
+ this requires handling of password fields
+ '''
+ for key, model_val in model_config.items():
+ param_val = param_config.get(key, 'missing')
+ if isinstance(model_val, str) and (model_val.startswith('$encrypted$') or param_val.startswith('$encrypted$')):
+ assert model_val.startswith('$encrypted$') # must be saved as encrypted
+ assert len(model_val) > len('$encrypted$')
+ else:
+ assert model_val == param_val, 'Config key {0} did not match, (model: {1}, input: {2})'.format(
+ key, model_val, param_val
+ )
+
+
+@pytest.mark.django_db
+def test_create_modify_notification_template(run_module, admin_user, organization):
+ nt_config = {
+ 'username': 'user',
+ 'password': 'password',
+ 'sender': 'foo@invalid.com',
+ 'recipients': ['foo2@invalid.com'],
+ 'host': 'smtp.example.com',
+ 'port': 25,
+ 'use_tls': False, 'use_ssl': False,
+ 'timeout': 4
+ }
+ result = run_module('tower_notification', dict(
+ name='foo-notification-template',
+ organization=organization.name,
+ notification_type='email',
+ notification_configuration=nt_config,
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.pop('changed', None), result
+
+ nt = NotificationTemplate.objects.get(id=result['id'])
+ compare_with_encrypted(nt.notification_configuration, nt_config)
+ assert nt.organization == organization
+
+ # Test no-op, this is impossible if the notification_configuration is given
+ # because we cannot determine if password fields changed
+ result = run_module('tower_notification', dict(
+ name='foo-notification-template',
+ organization=organization.name,
+ notification_type='email',
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert not result.pop('changed', None), result
+
+ # Test a change in the configuration
+ nt_config['timeout'] = 12
+ result = run_module('tower_notification', dict(
+ name='foo-notification-template',
+ organization=organization.name,
+ notification_type='email',
+ notification_configuration=nt_config,
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.pop('changed', None), result
+
+ nt.refresh_from_db()
+ compare_with_encrypted(nt.notification_configuration, nt_config)
+
+
+@pytest.mark.django_db
+def test_invalid_notification_configuration(run_module, admin_user, organization):
+ result = run_module('tower_notification', dict(
+ name='foo-notification-template',
+ organization=organization.name,
+ notification_type='email',
+ notification_configuration={},
+ ), admin_user)
+ assert result.get('failed', False), result.get('msg', result)
+ assert 'Missing required fields for Notification Configuration' in result['msg']
+
+
+@pytest.mark.django_db
+def test_deprecated_to_modern_no_op(run_module, admin_user, organization):
+ nt_config = {
+ 'url': 'http://www.example.com/hook',
+ 'headers': {
+ 'X-Custom-Header': 'value123'
+ }
+ }
+ result = run_module('tower_notification', dict(
+ name='foo-notification-template',
+ organization=organization.name,
+ notification_type='webhook',
+ notification_configuration=nt_config,
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.pop('changed', None), result
+
+ result = run_module('tower_notification', dict(
+ name='foo-notification-template',
+ organization=organization.name,
+ notification_type='webhook',
+ notification_configuration=nt_config,
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert not result.pop('changed', None), result
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_organization.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_organization.py
new file mode 100644
index 00000000..8f4872c3
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_organization.py
@@ -0,0 +1,60 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import Organization
+
+
+@pytest.mark.django_db
+def test_create_organization(run_module, admin_user):
+
+ module_args = {
+ 'name': 'foo',
+ 'description': 'barfoo',
+ 'state': 'present',
+ 'max_hosts': '0',
+ 'tower_host': None,
+ 'tower_username': None,
+ 'tower_password': None,
+ 'validate_certs': None,
+ 'tower_oauthtoken': None,
+ 'tower_config_file': None,
+ 'custom_virtualenv': None
+ }
+
+ result = run_module('tower_organization', module_args, admin_user)
+ assert result.get('changed'), result
+
+ org = Organization.objects.get(name='foo')
+ assert result == {
+ "name": "foo",
+ "changed": True,
+ "id": org.id,
+ "invocation": {
+ "module_args": module_args
+ }
+ }
+
+ assert org.description == 'barfoo'
+
+
+@pytest.mark.django_db
+def test_create_organization_with_venv(run_module, admin_user, mocker):
+ path = '/var/lib/awx/venv/custom-venv/foobar13489435/'
+ with mocker.patch('awx.main.models.mixins.get_custom_venv_choices', return_value=[path]):
+ result = run_module('tower_organization', {
+ 'name': 'foo',
+ 'custom_virtualenv': path,
+ 'state': 'present'
+ }, admin_user)
+ assert result.pop('changed'), result
+
+ org = Organization.objects.get(name='foo')
+ result.pop('invocation')
+ assert result == {
+ "name": "foo",
+ "id": org.id
+ }
+
+ assert org.custom_virtualenv == path
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_project.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_project.py
new file mode 100644
index 00000000..9ef1596d
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_project.py
@@ -0,0 +1,33 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import Project
+
+
+@pytest.mark.django_db
+def test_create_project(run_module, admin_user, organization, silence_warning):
+ result = run_module('tower_project', dict(
+ name='foo',
+ organization=organization.name,
+ scm_type='git',
+ scm_url='https://foo.invalid',
+ wait=False,
+ scm_update_cache_timeout=5
+ ), admin_user)
+ silence_warning.assert_called_once_with(
+ 'scm_update_cache_timeout will be ignored since scm_update_on_launch '
+ 'was not set to true')
+
+ assert result.pop('changed', None), result
+
+ proj = Project.objects.get(name='foo')
+ assert proj.scm_url == 'https://foo.invalid'
+ assert proj.organization == organization
+
+ result.pop('invocation')
+ assert result == {
+ 'name': 'foo',
+ 'id': proj.id
+ }
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_role.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_role.py
new file mode 100644
index 00000000..a97bdb76
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_role.py
@@ -0,0 +1,64 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import WorkflowJobTemplate, User
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('state', ('present', 'absent'))
+def test_grant_organization_permission(run_module, admin_user, organization, state):
+ rando = User.objects.create(username='rando')
+ if state == 'absent':
+ organization.admin_role.members.add(rando)
+
+ result = run_module('tower_role', {
+ 'user': rando.username,
+ 'organization': organization.name,
+ 'role': 'admin',
+ 'state': state
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ if state == 'present':
+ assert rando in organization.execute_role
+ else:
+ assert rando not in organization.execute_role
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('state', ('present', 'absent'))
+def test_grant_workflow_permission(run_module, admin_user, organization, state):
+ wfjt = WorkflowJobTemplate.objects.create(organization=organization, name='foo-workflow')
+ rando = User.objects.create(username='rando')
+ if state == 'absent':
+ wfjt.execute_role.members.add(rando)
+
+ result = run_module('tower_role', {
+ 'user': rando.username,
+ 'workflow': wfjt.name,
+ 'role': 'execute',
+ 'state': state
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ if state == 'present':
+ assert rando in wfjt.execute_role
+ else:
+ assert rando not in wfjt.execute_role
+
+
+@pytest.mark.django_db
+def test_invalid_role(run_module, admin_user, project):
+ rando = User.objects.create(username='rando')
+ result = run_module('tower_role', {
+ 'user': rando.username,
+ 'project': project.name,
+ 'role': 'adhoc',
+ 'state': 'present'
+ }, admin_user)
+ assert result.get('failed', False)
+ msg = result.get('msg')
+ assert 'has no role adhoc_role' in msg
+ assert 'available roles: admin_role, use_role, update_role, read_role' in msg
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_schedule.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_schedule.py
new file mode 100644
index 00000000..7a58892d
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_schedule.py
@@ -0,0 +1,107 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from ansible.errors import AnsibleError
+
+from awx.main.models import Schedule
+from awx.api.serializers import SchedulePreviewSerializer
+
+
+@pytest.mark.django_db
+def test_create_schedule(run_module, job_template, admin_user):
+ my_rrule = 'DTSTART;TZID=Zulu:20200416T034507 RRULE:FREQ=MONTHLY;INTERVAL=1'
+ result = run_module('tower_schedule', {
+ 'name': 'foo_schedule',
+ 'unified_job_template': job_template.name,
+ 'rrule': my_rrule
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ schedule = Schedule.objects.filter(name='foo_schedule').first()
+
+ assert result['id'] == schedule.id
+ assert result['changed']
+
+ assert schedule.rrule == my_rrule
+
+
+@pytest.mark.parametrize("freq, kwargs, expect", [
+ # Test with a valid start date (no time) (also tests none frequency and count)
+ ('none', {'start_date': '2020-04-16'}, 'DTSTART;TZID=America/New_York:20200416T000000 RRULE:FREQ=DAILY;COUNT=1;INTERVAL=1'),
+ # Test with a valid start date and time
+ ('none', {'start_date': '2020-04-16 03:45:07'}, 'DTSTART;TZID=America/New_York:20200416T034507 RRULE:FREQ=DAILY;COUNT=1;INTERVAL=1'),
+ # Test end_on as count (also integration test)
+ ('minute', {'start_date': '2020-4-16 03:45:07', 'end_on': '2'}, 'DTSTART;TZID=America/New_York:20200416T034507 RRULE:FREQ=MINUTELY;COUNT=2;INTERVAL=1'),
+ # Test end_on as date
+ ('minute', {'start_date': '2020-4-16 03:45:07', 'end_on': '2020-4-17 03:45:07'},
+ 'DTSTART;TZID=America/New_York:20200416T034507 RRULE:FREQ=MINUTELY;UNTIL=20200417T034507;INTERVAL=1'),
+ # Test on_days as a single day
+ ('week', {'start_date': '2020-4-16 03:45:07', 'on_days': 'saturday'},
+ 'DTSTART;TZID=America/New_York:20200416T034507 RRULE:FREQ=WEEKLY;BYDAY=SA;INTERVAL=1'),
+ # Test on_days as multiple days (with some whitespaces)
+ ('week', {'start_date': '2020-4-16 03:45:07', 'on_days': 'saturday,monday , friday'},
+ 'DTSTART;TZID=America/New_York:20200416T034507 RRULE:FREQ=WEEKLY;BYDAY=MO,FR,SA;INTERVAL=1'),
+ # Test valid month_day_number
+ ('month', {'start_date': '2020-4-16 03:45:07', 'month_day_number': '18'},
+ 'DTSTART;TZID=America/New_York:20200416T034507 RRULE:FREQ=MONTHLY;BYMONTHDAY=18;INTERVAL=1'),
+ # Test a valid on_the
+ ('month', {'start_date': '2020-4-16 03:45:07', 'on_the': 'second sunday'},
+ 'DTSTART;TZID=America/New_York:20200416T034507 RRULE:FREQ=MONTHLY;BYSETPOS=2;BYDAY=SU;INTERVAL=1'),
+ # Test an valid timezone
+ ('month', {'start_date': '2020-4-16 03:45:07', 'timezone': 'Zulu'},
+ 'DTSTART;TZID=Zulu:20200416T034507 RRULE:FREQ=MONTHLY;INTERVAL=1'),
+])
+def test_rrule_lookup_plugin(collection_import, freq, kwargs, expect):
+ LookupModule = collection_import('plugins.lookup.tower_schedule_rrule').LookupModule
+ generated_rule = LookupModule.get_rrule(freq, kwargs)
+ assert generated_rule == expect
+ rrule_checker = SchedulePreviewSerializer()
+ # Try to run our generated rrule through the awx validator
+ # This will raise its own exception on failure
+ rrule_checker.validate_rrule(generated_rule)
+
+
+@pytest.mark.parametrize("freq", ('none', 'minute', 'hour', 'day', 'week', 'month'))
+def test_empty_schedule_rrule(collection_import, freq):
+ LookupModule = collection_import('plugins.lookup.tower_schedule_rrule').LookupModule
+ if freq == 'day':
+ pfreq = 'DAILY'
+ elif freq == 'none':
+ pfreq = 'DAILY;COUNT=1'
+ else:
+ pfreq = freq.upper() + 'LY'
+ assert LookupModule.get_rrule(freq, {}).endswith(' RRULE:FREQ={0};INTERVAL=1'.format(pfreq))
+
+
+@pytest.mark.parametrize("freq, kwargs, msg", [
+ # Test end_on as junk
+ ('minute', {'start_date': '2020-4-16 03:45:07', 'end_on': 'junk'},
+ 'Parameter end_on must either be an integer or in the format YYYY-MM-DD'),
+ # Test on_days as junk
+ ('week', {'start_date': '2020-4-16 03:45:07', 'on_days': 'junk'},
+ 'Parameter on_days must only contain values monday, tuesday, wednesday, thursday, friday, saturday, sunday'),
+ # Test combo of both month_day_number and on_the
+ ('month', dict(start_date='2020-4-16 03:45:07', on_the='something', month_day_number='else'),
+ "Month based frequencies can have month_day_number or on_the but not both"),
+ # Test month_day_number as not an integer
+ ('month', dict(start_date='2020-4-16 03:45:07', month_day_number='junk'), "month_day_number must be between 1 and 31"),
+ # Test month_day_number < 1
+ ('month', dict(start_date='2020-4-16 03:45:07', month_day_number='0'), "month_day_number must be between 1 and 31"),
+ # Test month_day_number > 31
+ ('month', dict(start_date='2020-4-16 03:45:07', month_day_number='32'), "month_day_number must be between 1 and 31"),
+ # Test on_the as junk
+ ('month', dict(start_date='2020-4-16 03:45:07', on_the='junk'), "on_the parameter must be two words separated by a space"),
+ # Test on_the with invalid occurance
+ ('month', dict(start_date='2020-4-16 03:45:07', on_the='junk wednesday'), "The first string of the on_the parameter is not valid"),
+ # Test on_the with invalid weekday
+ ('month', dict(start_date='2020-4-16 03:45:07', on_the='second junk'), "Weekday portion of on_the parameter is not valid"),
+ # Test an invalid timezone
+ ('month', dict(start_date='2020-4-16 03:45:07', timezone='junk'), 'Timezone parameter is not valid'),
+])
+def test_rrule_lookup_plugin_failure(collection_import, freq, kwargs, msg):
+ LookupModule = collection_import('plugins.lookup.tower_schedule_rrule').LookupModule
+ with pytest.raises(AnsibleError) as e:
+ assert LookupModule.get_rrule(freq, kwargs)
+ assert msg in str(e.value)
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_send_receive.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_send_receive.py
new file mode 100644
index 00000000..14f3c894
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_send_receive.py
@@ -0,0 +1,76 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+import json
+
+from awx.main.models import (
+ Organization,
+ Project,
+ Inventory,
+ Host,
+ CredentialType,
+ Credential,
+ JobTemplate
+)
+
+
+# warns based on password_management param, but not security issue
+@pytest.mark.django_db
+def test_receive_send_jt(run_module, admin_user, mocker, silence_deprecation):
+ org = Organization.objects.create(name='SRtest')
+ proj = Project.objects.create(
+ name='SRtest',
+ playbook_files=['debug.yml'],
+ scm_type='git',
+ scm_url='https://github.com/ansible/test-playbooks.git',
+ organization=org,
+ allow_override=True # so we do not require playbooks populated
+ )
+ inv = Inventory.objects.create(name='SRtest', organization=org)
+ Host.objects.create(name='SRtest', inventory=inv)
+ ct = CredentialType.defaults['ssh']()
+ ct.save()
+ cred = Credential.objects.create(
+ name='SRtest',
+ credential_type=ct,
+ organization=org
+ )
+ jt = JobTemplate.objects.create(
+ name='SRtest',
+ project=proj,
+ inventory=inv,
+ playbook='helloworld.yml'
+ )
+ jt.credentials.add(cred)
+ jt.admin_role.members.add(admin_user) # work around send/receive bug
+
+ # receive everything
+ result = run_module('tower_receive', dict(all=True), admin_user)
+
+ assert 'assets' in result, result
+ assets = result['assets']
+ assert not result.get('changed', True)
+ assert set(a['asset_type'] for a in assets) == set((
+ 'organization', 'inventory', 'job_template', 'credential', 'project',
+ 'user'
+ ))
+
+ # delete everything
+ for obj in (jt, inv, proj, cred, org):
+ obj.delete()
+
+ def fake_wait(self, pk, parent_pk=None, **kwargs):
+ return {"changed": True}
+
+ # recreate everything
+ with mocker.patch('sys.stdin.isatty', return_value=True):
+ with mocker.patch('tower_cli.models.base.MonitorableResource.wait'):
+ result = run_module('tower_send', dict(assets=json.dumps(assets)), admin_user)
+
+ assert not result.get('failed'), result
+
+ new = JobTemplate.objects.get(name='SRtest')
+ assert new.project.name == 'SRtest'
+ assert new.inventory.name == 'SRtest'
+ assert [cred.name for cred in new.credentials.all()] == ['SRtest']
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_settings.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_settings.py
new file mode 100644
index 00000000..e39d7eaa
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_settings.py
@@ -0,0 +1,67 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.conf.models import Setting
+
+
+@pytest.mark.django_db
+def test_setting_flat_value(run_module, admin_user):
+ the_value = 'CN=service_account,OU=ServiceAccounts,DC=domain,DC=company,DC=org'
+ result = run_module('tower_settings', dict(
+ name='AUTH_LDAP_BIND_DN',
+ value=the_value
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ assert Setting.objects.get(key='AUTH_LDAP_BIND_DN').value == the_value
+
+
+@pytest.mark.django_db
+def test_setting_dict_value(run_module, admin_user):
+ the_value = {
+ 'email': 'mail',
+ 'first_name': 'givenName',
+ 'last_name': 'surname'
+ }
+ result = run_module('tower_settings', dict(
+ name='AUTH_LDAP_USER_ATTR_MAP',
+ value=the_value
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ assert Setting.objects.get(key='AUTH_LDAP_USER_ATTR_MAP').value == the_value
+
+
+@pytest.mark.django_db
+def test_setting_nested_type(run_module, admin_user):
+ the_value = {
+ 'email': 'mail',
+ 'first_name': 'givenName',
+ 'last_name': 'surname'
+ }
+ result = run_module('tower_settings', dict(
+ settings={
+ 'AUTH_LDAP_USER_ATTR_MAP': the_value
+ }
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ assert Setting.objects.get(key='AUTH_LDAP_USER_ATTR_MAP').value == the_value
+
+
+@pytest.mark.django_db
+def test_setting_bool_value(run_module, admin_user):
+ for the_value in (True, False):
+ result = run_module('tower_settings', dict(
+ name='ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC',
+ value=the_value
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ assert Setting.objects.get(key='ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC').value is the_value
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_team.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_team.py
new file mode 100644
index 00000000..ccc164dc
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_team.py
@@ -0,0 +1,66 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import Organization, Team
+
+
+@pytest.mark.django_db
+def test_create_team(run_module, admin_user):
+ org = Organization.objects.create(name='foo')
+
+ result = run_module('tower_team', {
+ 'name': 'foo_team',
+ 'description': 'fooin around',
+ 'state': 'present',
+ 'organization': 'foo'
+ }, admin_user)
+
+ team = Team.objects.filter(name='foo_team').first()
+
+ result.pop('invocation')
+ assert result == {
+ "changed": True,
+ "name": "foo_team",
+ "id": team.id if team else None,
+ }
+ team = Team.objects.get(name='foo_team')
+ assert team.description == 'fooin around'
+ assert team.organization_id == org.id
+
+
+@pytest.mark.django_db
+def test_modify_team(run_module, admin_user):
+ org = Organization.objects.create(name='foo')
+ team = Team.objects.create(
+ name='foo_team',
+ organization=org,
+ description='flat foo'
+ )
+ assert team.description == 'flat foo'
+
+ result = run_module('tower_team', {
+ 'name': 'foo_team',
+ 'description': 'fooin around',
+ 'organization': 'foo'
+ }, admin_user)
+ team.refresh_from_db()
+ result.pop('invocation')
+ assert result == {
+ "changed": True,
+ "id": team.id,
+ }
+ assert team.description == 'fooin around'
+
+ # 2nd modification, should cause no change
+ result = run_module('tower_team', {
+ 'name': 'foo_team',
+ 'description': 'fooin around',
+ 'organization': 'foo'
+ }, admin_user)
+ result.pop('invocation')
+ assert result == {
+ "id": team.id,
+ "changed": False
+ }
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_token.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_token.py
new file mode 100644
index 00000000..442fa2e9
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_token.py
@@ -0,0 +1,29 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import OAuth2AccessToken
+
+
+@pytest.mark.django_db
+def test_create_token(run_module, admin_user):
+
+ module_args = {
+ 'description': 'barfoo',
+ 'state': 'present',
+ 'scope': 'read',
+ 'tower_host': None,
+ 'tower_username': None,
+ 'tower_password': None,
+ 'validate_certs': None,
+ 'tower_oauthtoken': None,
+ 'tower_config_file': None,
+ }
+
+ result = run_module('tower_token', module_args, admin_user)
+ assert result.get('changed'), result
+
+ tokens = OAuth2AccessToken.objects.filter(description='barfoo')
+ assert len(tokens) == 1, 'Tokens with description of barfoo != 0: {0}'.format(len(tokens))
+ assert tokens[0].scope == 'read', 'Token was not given read access'
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_user.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_user.py
new file mode 100644
index 00000000..db705bd5
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_user.py
@@ -0,0 +1,46 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from unittest import mock
+
+from awx.main.models import User
+
+
+@pytest.fixture
+def mock_auth_stuff():
+ """Some really specific session-related stuff is done for changing or setting
+ passwords, so we will just avoid that here.
+ """
+ with mock.patch('awx.api.serializers.update_session_auth_hash'):
+ yield
+
+
+@pytest.mark.django_db
+def test_create_user(run_module, admin_user, mock_auth_stuff):
+ result = run_module('tower_user', dict(
+ username='Bob',
+ password='pass4word'
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed'), result
+
+ user = User.objects.get(id=result['id'])
+ assert user.username == 'Bob'
+
+
+@pytest.mark.django_db
+def test_password_no_op_warning(run_module, admin_user, mock_auth_stuff, silence_warning):
+ for i in range(2):
+ result = run_module('tower_user', dict(
+ username='Bob',
+ password='pass4word'
+ ), admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ assert result.get('changed') # not actually desired, but assert for sanity
+
+ silence_warning.assert_called_once_with(
+ "The field password of user {0} has encrypted data and "
+ "may inaccurately report task is changed.".format(result['id']))
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_workflow_job_template.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_workflow_job_template.py
new file mode 100644
index 00000000..bc2a44b1
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_workflow_job_template.py
@@ -0,0 +1,146 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import WorkflowJobTemplate, NotificationTemplate
+
+
+@pytest.mark.django_db
+def test_create_workflow_job_template(run_module, admin_user, organization, survey_spec):
+ result = run_module('tower_workflow_job_template', {
+ 'name': 'foo-workflow',
+ 'organization': organization.name,
+ 'extra_vars': {'foo': 'bar', 'another-foo': {'barz': 'bar2'}},
+ 'survey': survey_spec,
+ 'survey_enabled': True,
+ 'state': 'present'
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ wfjt = WorkflowJobTemplate.objects.get(name='foo-workflow')
+ assert wfjt.extra_vars == '{"foo": "bar", "another-foo": {"barz": "bar2"}}'
+
+ result.pop('invocation', None)
+ assert result == {"name": "foo-workflow", "id": wfjt.id, "changed": True}
+
+ assert wfjt.organization_id == organization.id
+ assert wfjt.survey_spec == survey_spec
+
+
+@pytest.mark.django_db
+def test_create_modify_no_survey(run_module, admin_user, organization, survey_spec):
+ result = run_module('tower_workflow_job_template', {
+ 'name': 'foo-workflow',
+ 'organization': organization.name
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', False), result
+
+ wfjt = WorkflowJobTemplate.objects.get(name='foo-workflow')
+ assert wfjt.organization_id == organization.id
+ assert wfjt.survey_spec == {}
+ result.pop('invocation', None)
+ assert result == {"name": "foo-workflow", "id": wfjt.id, "changed": True}
+
+ result = run_module('tower_workflow_job_template', {
+ 'name': 'foo-workflow',
+ 'organization': organization.name
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert not result.get('changed', True), result
+
+
+@pytest.mark.django_db
+def test_survey_spec_only_changed(run_module, admin_user, organization, survey_spec):
+ wfjt = WorkflowJobTemplate.objects.create(
+ organization=organization, name='foo-workflow',
+ survey_enabled=True, survey_spec=survey_spec
+ )
+ result = run_module('tower_workflow_job_template', {
+ 'name': 'foo-workflow',
+ 'organization': organization.name,
+ 'state': 'present'
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert not result.get('changed', True), result
+ wfjt.refresh_from_db()
+ assert wfjt.survey_spec == survey_spec
+
+ survey_spec['description'] = 'changed description'
+
+ result = run_module('tower_workflow_job_template', {
+ 'name': 'foo-workflow',
+ 'organization': organization.name,
+ 'survey': survey_spec,
+ 'state': 'present'
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', True), result
+ wfjt.refresh_from_db()
+ assert wfjt.survey_spec == survey_spec
+
+
+@pytest.mark.django_db
+def test_associate_only_on_success(run_module, admin_user, organization, project):
+ wfjt = WorkflowJobTemplate.objects.create(
+ organization=organization, name='foo-workflow',
+ # survey_enabled=True, survey_spec=survey_spec
+ )
+ create_kwargs = dict(
+ notification_configuration={
+ 'url': 'http://www.example.com/hook',
+ 'headers': {
+ 'X-Custom-Header': 'value123'
+ },
+ 'password': 'bar'
+ },
+ notification_type='webhook',
+ organization=organization
+ )
+ nt1 = NotificationTemplate.objects.create(name='nt1', **create_kwargs)
+ nt2 = NotificationTemplate.objects.create(name='nt2', **create_kwargs)
+
+ wfjt.notification_templates_error.add(nt1)
+
+ # test preservation of error NTs when success NTs are added
+ result = run_module('tower_workflow_job_template', {
+ 'name': 'foo-workflow',
+ 'organization': organization.name,
+ 'notification_templates_success': ['nt2']
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', True), result
+
+ assert list(wfjt.notification_templates_success.values_list('id', flat=True)) == [nt2.id]
+ assert list(wfjt.notification_templates_error.values_list('id', flat=True)) == [nt1.id]
+
+ # test removal to empty list
+ result = run_module('tower_workflow_job_template', {
+ 'name': 'foo-workflow',
+ 'organization': organization.name,
+ 'notification_templates_success': []
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', True), result
+
+ assert list(wfjt.notification_templates_success.values_list('id', flat=True)) == []
+ assert list(wfjt.notification_templates_error.values_list('id', flat=True)) == [nt1.id]
+
+
+@pytest.mark.django_db
+def test_delete_with_spec(run_module, admin_user, organization, survey_spec):
+ WorkflowJobTemplate.objects.create(
+ organization=organization, name='foo-workflow',
+ survey_enabled=True, survey_spec=survey_spec
+ )
+ result = run_module('tower_workflow_job_template', {
+ 'name': 'foo-workflow',
+ 'organization': organization.name,
+ 'state': 'absent'
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', True), result
+
+ assert WorkflowJobTemplate.objects.filter(
+ name='foo-workflow', organization=organization).count() == 0
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_workflow_job_template_node.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_workflow_job_template_node.py
new file mode 100644
index 00000000..935b0154
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_workflow_job_template_node.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import WorkflowJobTemplateNode, WorkflowJobTemplate, JobTemplate
+
+
+@pytest.fixture
+def job_template(project, inventory):
+ return JobTemplate.objects.create(
+ project=project,
+ inventory=inventory,
+ playbook='helloworld.yml',
+ name='foo-jt',
+ ask_variables_on_launch=True,
+ ask_credential_on_launch=True,
+ ask_limit_on_launch=True
+ )
+
+
+@pytest.fixture
+def wfjt(organization):
+ WorkflowJobTemplate.objects.create(organization=None, name='foo-workflow') # to test org scoping
+ return WorkflowJobTemplate.objects.create(organization=organization, name='foo-workflow')
+
+
+@pytest.mark.django_db
+def test_create_workflow_job_template_node(run_module, admin_user, wfjt, job_template):
+ this_identifier = '42🐉'
+ result = run_module('tower_workflow_job_template_node', {
+ 'identifier': this_identifier,
+ 'workflow_job_template': 'foo-workflow',
+ 'organization': wfjt.organization.name,
+ 'unified_job_template': 'foo-jt',
+ 'state': 'present'
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ node = WorkflowJobTemplateNode.objects.get(identifier=this_identifier)
+
+ result.pop('invocation', None)
+ assert result == {
+ "name": this_identifier, # FIXME: should this be identifier instead
+ "id": node.id,
+ "changed": True
+ }
+
+ assert node.identifier == this_identifier
+ assert node.workflow_job_template_id == wfjt.id
+ assert node.unified_job_template_id == job_template.id
+
+
+@pytest.mark.django_db
+def test_create_workflow_job_template_node_no_template(run_module, admin_user, wfjt, job_template):
+ """This is a part of the API contract for creating approval nodes
+ and at some point in the future, tha feature will be supported by the collection
+ """
+ this_identifier = '42🐉'
+ result = run_module('tower_workflow_job_template_node', {
+ 'identifier': this_identifier,
+ 'workflow_job_template': wfjt.name,
+ 'organization': wfjt.organization.name,
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', False), result
+
+ node = WorkflowJobTemplateNode.objects.get(pk=result['id'])
+ # node = WorkflowJobTemplateNode.objects.first()
+
+ assert result['id'] == node.id
+
+ assert node.identifier == this_identifier
+ assert node.workflow_job_template_id == wfjt.id
+ assert node.unified_job_template_id is None
+
+
+@pytest.mark.django_db
+def test_make_use_of_prompts(run_module, admin_user, wfjt, job_template, machine_credential, vault_credential):
+ result = run_module('tower_workflow_job_template_node', {
+ 'identifier': '42',
+ 'workflow_job_template': 'foo-workflow',
+ 'organization': wfjt.organization.name,
+ 'unified_job_template': 'foo-jt',
+ 'extra_data': {'foo': 'bar', 'another-foo': {'barz': 'bar2'}},
+ 'limit': 'foo_hosts',
+ 'credentials': [machine_credential.name, vault_credential.name],
+ 'state': 'present'
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', False)
+
+ node = WorkflowJobTemplateNode.objects.get(identifier='42')
+
+ assert node.limit == 'foo_hosts'
+ assert node.extra_data == {'foo': 'bar', 'another-foo': {'barz': 'bar2'}}
+ assert set(node.credentials.all()) == set([machine_credential, vault_credential])
+
+
+@pytest.mark.django_db
+def test_create_with_edges(run_module, admin_user, wfjt, job_template):
+ next_nodes = [
+ WorkflowJobTemplateNode.objects.create(
+ identifier='foo{0}'.format(i),
+ workflow_job_template=wfjt,
+ unified_job_template=job_template
+ ) for i in range(3)
+ ]
+
+ result = run_module('tower_workflow_job_template_node', {
+ 'identifier': '42',
+ 'workflow_job_template': 'foo-workflow',
+ 'organization': wfjt.organization.name,
+ 'unified_job_template': 'foo-jt',
+ 'success_nodes': ['foo0'],
+ 'always_nodes': ['foo1'],
+ 'failure_nodes': ['foo2'],
+ 'state': 'present'
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+ assert result.get('changed', False)
+
+ node = WorkflowJobTemplateNode.objects.get(identifier='42')
+
+ assert list(node.success_nodes.all()) == [next_nodes[0]]
+ assert list(node.always_nodes.all()) == [next_nodes[1]]
+ assert list(node.failure_nodes.all()) == [next_nodes[2]]
diff --git a/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_workflow_template.py b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_workflow_template.py
new file mode 100644
index 00000000..c8b401ae
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/test/awx/test_workflow_template.py
@@ -0,0 +1,130 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from awx.main.models import (
+ WorkflowJobTemplate, JobTemplate, Project, InventorySource,
+ Inventory, WorkflowJobTemplateNode
+)
+
+
+@pytest.mark.django_db
+def test_create_workflow_job_template(run_module, admin_user, organization, survey_spec, silence_deprecation):
+ result = run_module('tower_workflow_template', {
+ 'name': 'foo-workflow',
+ 'organization': organization.name,
+ 'extra_vars': {'foo': 'bar', 'another-foo': {'barz': 'bar2'}},
+ 'survey': survey_spec,
+ 'survey_enabled': True,
+ 'state': 'present'
+ }, admin_user)
+
+ wfjt = WorkflowJobTemplate.objects.get(name='foo-workflow')
+ assert wfjt.extra_vars == '{"foo": "bar", "another-foo": {"barz": "bar2"}}'
+
+ result.pop('invocation', None)
+ assert result == {
+ "workflow_template": "foo-workflow", # TODO: remove after refactor
+ "state": "present",
+ "id": wfjt.id,
+ "changed": True
+ }
+
+ assert wfjt.organization_id == organization.id
+ assert wfjt.survey_spec == survey_spec
+
+
+@pytest.mark.django_db
+def test_with_nested_workflow(run_module, admin_user, organization, silence_deprecation):
+ wfjt1 = WorkflowJobTemplate.objects.create(name='first', organization=organization)
+
+ result = run_module('tower_workflow_template', {
+ 'name': 'foo-workflow',
+ 'organization': organization.name,
+ 'schema': [
+ {'workflow': wfjt1.name}
+ ],
+ 'state': 'present'
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ wfjt = WorkflowJobTemplate.objects.get(name='foo-workflow')
+ node = wfjt.workflow_nodes.first()
+ assert node is not None
+ assert node.unified_job_template == wfjt1
+
+
+@pytest.mark.django_db
+def test_schema_with_branches(run_module, admin_user, organization, silence_deprecation):
+
+ proj = Project.objects.create(organization=organization, name='Ansible Examples')
+ inv = Inventory.objects.create(organization=organization, name='test-inv')
+ jt = JobTemplate.objects.create(
+ project=proj,
+ playbook='helloworld.yml',
+ inventory=inv,
+ name='Hello world'
+ )
+ inv_src = InventorySource.objects.create(
+ inventory=inv,
+ name='AWS servers',
+ source='ec2'
+ )
+
+ result = run_module('tower_workflow_template', {
+ 'name': 'foo-workflow',
+ 'organization': organization.name,
+ 'schema': [
+ {
+ 'job_template': 'Hello world',
+ 'failure': [
+ {
+ 'inventory_source': 'AWS servers',
+ 'success': [
+ {
+ 'project': 'Ansible Examples',
+ 'always': [
+ {
+ 'job_template': "Hello world"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ 'state': 'present'
+ }, admin_user)
+ assert not result.get('failed', False), result.get('msg', result)
+
+ wfjt = WorkflowJobTemplate.objects.get(name='foo-workflow')
+ root_nodes = wfjt.workflow_nodes.filter(**{
+ '%ss_success__isnull' % WorkflowJobTemplateNode.__name__.lower(): True,
+ '%ss_failure__isnull' % WorkflowJobTemplateNode.__name__.lower(): True,
+ '%ss_always__isnull' % WorkflowJobTemplateNode.__name__.lower(): True,
+ })
+ assert len(root_nodes) == 1
+ node = root_nodes[0]
+ assert node.unified_job_template == jt
+ second = node.failure_nodes.first()
+ assert second.unified_job_template == inv_src
+ third = second.success_nodes.first()
+ assert third.unified_job_template == proj
+ fourth = third.always_nodes.first()
+ assert fourth.unified_job_template == jt
+
+
+@pytest.mark.django_db
+def test_with_missing_ujt(run_module, admin_user, organization, silence_deprecation):
+ result = run_module('tower_workflow_template', {
+ 'name': 'foo-workflow',
+ 'organization': organization.name,
+ 'schema': [
+ {'foo': 'bar'}
+ ],
+ 'state': 'present'
+ }, admin_user)
+ assert result.get('failed', False), result
+ assert 'You should provide exactly one of the attributes job_template,' in result['msg']
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/demo_data/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/demo_data/tasks/main.yml
new file mode 100644
index 00000000..800afda5
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/demo_data/tasks/main.yml
@@ -0,0 +1,33 @@
+---
+- name: Assure that default organization exists
+ tower_organization:
+ name: Default
+
+- name: Assure that demo project exists
+ tower_project:
+ name: "Demo Project"
+ scm_type: 'git'
+ scm_url: 'https://github.com/ansible/ansible-tower-samples'
+ scm_update_on_launch: true
+ organization: Default
+
+- name: Assure that demo inventory exists
+ tower_inventory:
+ name: "Demo Inventory"
+ organization: Default
+
+- name: Create a Host
+ tower_host:
+ name: "localhost"
+ inventory: "Demo Inventory"
+ state: present
+ variables:
+ ansible_connection: local
+ register: result
+
+- name: Assure that demo job template exists
+ tower_job_template:
+ name: "Demo Job Template"
+ project: "Demo Project"
+ inventory: "Demo Inventory"
+ playbook: "hello_world.yml"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_credential/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_credential/tasks/main.yml
new file mode 100644
index 00000000..7c0f4b08
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_credential/tasks/main.yml
@@ -0,0 +1,754 @@
+---
+- name: Generate a random string for test
+ set_fact:
+ test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ when: test_id is not defined
+
+- name: Generate names
+ set_fact:
+ ssh_cred_name1: "AWX-Collection-tests-tower_credential-ssh-cred1-{{ test_id }}"
+ ssh_cred_name2: "AWX-Collection-tests-tower_credential-ssh-cred2-{{ test_id }}"
+ ssh_cred_name3: "AWX-Collection-tests-tower_credential-ssh-cred-lookup-source-{{ test_id }}"
+ ssh_cred_name4: "AWX-Collection-tests-tower_credential-ssh-cred-file-source-{{ test_id }}"
+ vault_cred_name1: "AWX-Collection-tests-tower_credential-vault-cred1-{{ test_id }}"
+ vault_cred_name2: "AWX-Collection-tests-tower_credential-vault-ssh-cred1-{{ test_id }}"
+ net_cred_name1: "AWX-Collection-tests-tower_credential-net-cred1-{{ test_id }}"
+ scm_cred_name1: "AWX-Collection-tests-tower_credential-scm-cred1-{{ test_id }}"
+ aws_cred_name1: "AWX-Collection-tests-tower_credential-aws-cred1-{{ test_id }}"
+ vmware_cred_name1: "AWX-Collection-tests-tower_credential-vmware-cred1-{{ test_id }}"
+ sat6_cred_name1: "AWX-Collection-tests-tower_credential-sat6-cred1-{{ test_id }}"
+ cf_cred_name1: "AWX-Collection-tests-tower_credential-cf-cred1-{{ test_id }}"
+ gce_cred_name1: "AWX-Collection-tests-tower_credential-gce-cred1-{{ test_id }}"
+ azurerm_cred_name1: "AWX-Collection-tests-tower_credential-azurerm-cred1-{{ test_id }}"
+ openstack_cred_name1: "AWX-Collection-tests-tower_credential-openstack-cred1-{{ test_id }}"
+ rhv_cred_name1: "AWX-Collection-tests-tower_credential-rhv-cred1-{{ test_id }}"
+ insights_cred_name1: "AWX-Collection-tests-tower_credential-insights-cred1-{{ test_id }}"
+ tower_cred_name1: "AWX-Collection-tests-tower_credential-tower-cred1-{{ test_id }}"
+
+- name: create a tempdir for an SSH key
+ local_action: shell mktemp -d
+ register: tempdir
+
+- name: Generate a local SSH key
+ local_action: "shell ssh-keygen -b 2048 -t rsa -f {{ tempdir.stdout }}/id_rsa -q -N 'passphrase'"
+
+- name: Read the generated key
+ set_fact:
+ ssh_key_data: "{{ lookup('file', tempdir.stdout + '/id_rsa') }}"
+
+- name: Test deprecation warnings
+ tower_credential:
+ name: "{{ ssh_cred_name1 }}"
+ organization: Default
+ kind: ssh
+ authorize: false
+ authorize_password: 'test'
+ client: 'test'
+ security_token: 'test'
+ secret: 'test'
+ tenant: 'test'
+ subscription: 'test'
+ domain: 'test'
+ become_method: 'test'
+ become_username: 'test'
+ become_password: 'test'
+ vault_password: 'test'
+ project: 'test'
+ host: 'test'
+ username: 'test'
+ password: 'test'
+ ssh_key_data: 'test'
+ vault_id: 'test'
+ ssh_key_unlock: 'test'
+ state: absent
+ ignore_errors: true
+ register: result
+
+- assert:
+ that:
+ - "'deprecations' in result"
+ # The 20 comes from the length of OLD_INPUT_NAMES + 1 for kind
+ - result['deprecations'] | length() == 20
+
+- name: Create an Org-specific credential (old school)
+ tower_credential:
+ name: "{{ ssh_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: ssh
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Re-create the Org-specific credential (new school)
+ tower_credential:
+ name: "{{ ssh_cred_name1 }}"
+ organization: Default
+ credential_type: 'Machine'
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Delete a Org-specific credential
+ tower_credential:
+ name: "{{ ssh_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: ssh
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create the User-specific credential
+ tower_credential:
+ name: "{{ ssh_cred_name1 }}"
+ user: admin
+ credential_type: 'Machine'
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a User-specific credential
+ tower_credential:
+ name: "{{ ssh_cred_name1 }}"
+ user: admin
+ state: absent
+ kind: ssh
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid SSH credential (old school)
+ tower_credential:
+ name: "{{ ssh_cred_name2 }}"
+ organization: Default
+ state: present
+ kind: ssh
+ description: An example SSH credential
+ username: joe
+ password: secret
+ become_method: sudo
+ become_username: superuser
+ become_password: supersecret
+ ssh_key_data: "{{ ssh_key_data }}"
+ ssh_key_unlock: "passphrase"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid SSH credential (new school)
+ tower_credential:
+ name: "{{ ssh_cred_name2 }}"
+ organization: Default
+ state: present
+ credential_type: Machine
+ description: An example SSH credential
+ inputs:
+ username: joe
+ password: secret
+ become_method: sudo
+ become_username: superuser
+ become_password: supersecret
+ ssh_key_data: "{{ ssh_key_data }}"
+ ssh_key_unlock: "passphrase"
+ register: result
+
+# This will be changed because we are setting ssh_key_data and ssh_key_unlock.
+# These will come out as $encrypted$ which will always compare false to the values.
+- assert:
+ that:
+ - result is changed
+
+- name: Create a valid SSH credential (new school)
+ tower_credential:
+ name: "{{ ssh_cred_name2 }}"
+ organization: Default
+ state: present
+ credential_type: Machine
+ description: An example SSH credential
+ inputs:
+ username: joe
+ become_method: sudo
+ become_username: superuser
+ register: result
+
+# This shows as "changed" because these listed inputs replace the existing inputs from the previous task
+- assert:
+ that:
+ - result is changed
+
+- name: Check for inputs idempotency (when "inputs" is blank)
+ tower_credential:
+ name: "{{ ssh_cred_name2 }}"
+ organization: Default
+ state: present
+ credential_type: Machine
+ description: An example SSH credential
+ register: result
+
+- assert:
+ that:
+ - result is not changed
+
+- name: Create a valid SSH credential from lookup source (old school)
+ tower_credential:
+ name: "{{ ssh_cred_name3 }}"
+ organization: Default
+ state: present
+ kind: ssh
+ description: An example SSH credential from lookup source
+ username: joe
+ password: secret
+ become_method: sudo
+ become_username: superuser
+ become_password: supersecret
+ ssh_key_data: "{{ lookup('file', tempdir.stdout + '/id_rsa') }}"
+ ssh_key_unlock: "passphrase"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid SSH credential from lookup source (new school)
+ tower_credential:
+ name: "{{ ssh_cred_name3 }}"
+ organization: Default
+ state: present
+ credential_type: Machine
+ description: An example SSH credential from lookup source
+ inputs:
+ username: joe
+ password: secret
+ become_method: sudo
+ become_username: superuser
+ become_password: supersecret
+ ssh_key_data: "{{ lookup('file', tempdir.stdout + '/id_rsa') }}"
+ ssh_key_unlock: "passphrase"
+ register: result
+
+# This will be changed because we are passing in ssh_key_data and password
+- assert:
+ that:
+ - result is changed
+
+- name: Fail to create an SSH credential from a file source (old school format)
+ tower_credential:
+ name: "{{ ssh_cred_name4 }}"
+ organization: Default
+ state: present
+ kind: ssh
+ description: An example SSH credential from file source
+ username: joe
+ password: secret
+ become_method: sudo
+ become_username: superuser
+ become_password: supersecret
+ ssh_key_data: "{{ tempdir.stdout }}/id_rsa"
+ ssh_key_unlock: "passphrase"
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - result is failed
+ - "'Unable to create credential {{ ssh_cred_name4 }}' in result.msg"
+ - "'Invalid certificate or key' in result.msg"
+
+- name: Create an invalid SSH credential (passphrase required)
+ tower_credential:
+ name: SSH Credential
+ organization: Default
+ state: present
+ kind: ssh
+ username: joe
+ ssh_key_data: "{{ ssh_key_data }}"
+ ignore_errors: true
+ register: result
+
+- assert:
+ that:
+ - "result is failed"
+ - "'must be set when SSH key is encrypted' in result.msg"
+
+- name: Create an invalid SSH credential (Organization not found)
+ tower_credential:
+ name: SSH Credential
+ organization: Missing Organization
+ state: present
+ kind: ssh
+ username: joe
+ ignore_errors: true
+ register: result
+
+- assert:
+ that:
+ - "result is failed"
+ - "'The organizations Missing Organization was not found on the Tower server' in result.msg"
+
+- name: Delete an SSH credential
+ tower_credential:
+ name: "{{ ssh_cred_name2 }}"
+ organization: Default
+ state: absent
+ kind: ssh
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete an SSH credential
+ tower_credential:
+ name: "{{ ssh_cred_name3 }}"
+ organization: Default
+ state: absent
+ kind: ssh
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete an SSH credential
+ tower_credential:
+ name: "{{ ssh_cred_name4 }}"
+ organization: Default
+ state: absent
+ kind: ssh
+ register: result
+
+# This one was never really created so it shouldn't be deleted
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Create a valid Vault credential
+ tower_credential:
+ name: "{{ vault_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: vault
+ description: An example Vault credential
+ vault_password: secret-vault
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+# We should decide when to delete this test
+- name: Create a valid Vault credential w/ kind=ssh (deprecated, will now fail)
+ tower_credential:
+ name: "{{ vault_cred_name2 }}"
+ organization: Default
+ state: present
+ kind: ssh
+ description: An example Vault credential
+ vault_password: secret-vault
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - result is failed
+ - "'Unable to create credential {{ vault_cred_name2 }}' in result.msg"
+ - "'Additional properties are not allowed' in result.msg"
+ - "'\\'vault_password\\' was unexpected' in result.msg"
+
+- name: Delete a Vault credential
+ tower_credential:
+ name: "{{ vault_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: vault
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a Vault credential
+ tower_credential:
+ name: "{{ vault_cred_name2 }}"
+ organization: Default
+ state: absent
+ kind: vault
+ register: result
+
+# The creation of vault_cred_name2 never worked so we shouldn't actually need to delete it
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Create a valid Network credential
+ tower_credential:
+ name: "{{ net_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: net
+ username: joe
+ password: secret
+ authorize: true
+ authorize_password: authorize-me
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a Network credential
+ tower_credential:
+ name: "{{ net_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: net
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid SCM credential
+ tower_credential:
+ name: "{{ scm_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: scm
+ username: joe
+ password: secret
+ ssh_key_data: "{{ ssh_key_data }}"
+ ssh_key_unlock: "passphrase"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete an SCM credential
+ tower_credential:
+ name: "{{ scm_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: scm
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid AWS credential
+ tower_credential:
+ name: "{{ aws_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: aws
+ username: joe
+ password: secret
+ security_token: aws-token
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete an AWS credential
+ tower_credential:
+ name: "{{ aws_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: aws
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid VMWare credential
+ tower_credential:
+ name: "{{ vmware_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: vmware
+ host: https://example.org
+ username: joe
+ password: secret
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete an VMWare credential
+ tower_credential:
+ name: "{{ vmware_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: vmware
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid Satellite6 credential
+ tower_credential:
+ name: "{{ sat6_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: satellite6
+ host: https://example.org
+ username: joe
+ password: secret
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a Satellite6 credential
+ tower_credential:
+ name: "{{ sat6_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: satellite6
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid CloudForms credential
+ tower_credential:
+ name: "{{ cf_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: cloudforms
+ host: https://example.org
+ username: joe
+ password: secret
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a CloudForms credential
+ tower_credential:
+ name: "{{ cf_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: cloudforms
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid GCE credential
+ tower_credential:
+ name: "{{ gce_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: gce
+ username: joe
+ project: ABC123
+ ssh_key_data: "{{ ssh_key_data }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a GCE credential
+ tower_credential:
+ name: "{{ gce_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: gce
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid AzureRM credential
+ tower_credential:
+ name: "{{ azurerm_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: azure_rm
+ username: joe
+ password: secret
+ subscription: some-subscription
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid AzureRM credential with a tenant
+ tower_credential:
+ name: "{{ azurerm_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: azure_rm
+ client: some-client
+ secret: some-secret
+ tenant: some-tenant
+ subscription: some-subscription
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete an AzureRM credential
+ tower_credential:
+ name: "{{ azurerm_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: azure_rm
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid OpenStack credential
+ tower_credential:
+ name: "{{ openstack_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: openstack
+ host: https://keystone.example.org
+ username: joe
+ password: secret
+ project: tenant123
+ domain: some-domain
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a OpenStack credential
+ tower_credential:
+ name: "{{ openstack_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: openstack
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid RHV credential
+ tower_credential:
+ name: "{{ rhv_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: rhv
+ host: https://example.org
+ username: joe
+ password: secret
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete an RHV credential
+ tower_credential:
+ name: "{{ rhv_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: rhv
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid Insights credential
+ tower_credential:
+ name: "{{ insights_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: insights
+ username: joe
+ password: secret
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete an Insights credential
+ tower_credential:
+ name: "{{ insights_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: insights
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a valid Tower-to-Tower credential
+ tower_credential:
+ name: "{{ tower_cred_name1 }}"
+ organization: Default
+ state: present
+ kind: tower
+ host: https://tower.example.org
+ username: joe
+ password: secret
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a Tower-to-Tower credential
+ tower_credential:
+ name: "{{ tower_cred_name1 }}"
+ organization: Default
+ state: absent
+ kind: tower
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Check module fails with correct msg
+ tower_credential:
+ name: test-credential
+ description: Credential Description
+ kind: ssh
+ organization: test-non-existing-org
+ state: present
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - result is failed
+ - "result.msg =='The organizations test-non-existing-org was not found on the Tower server'"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_credential_input_source/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_credential_input_source/tasks/main.yml
new file mode 100644
index 00000000..45be47dd
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_credential_input_source/tasks/main.yml
@@ -0,0 +1,105 @@
+---
+- name: Generate names
+ set_fact:
+ src_cred_name: src_cred
+ target_cred_name: target_cred
+
+- name: Add Tower credential Lookup
+ tower_credential:
+ description: Credential for Testing Source
+ name: "{{ src_cred_name }}"
+ credential_type: CyberArk AIM Central Credential Provider Lookup
+ inputs:
+ url: "https://cyberark.example.com"
+ app_id: "My-App-ID"
+ organization: Default
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Add Tower credential Target
+ tower_credential:
+ description: Credential for Testing Target
+ name: "{{ target_cred_name }}"
+ credential_type: Machine
+ inputs:
+ username: user
+ organization: Default
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Add credential Input Source
+ tower_credential_input_source:
+ input_field_name: password
+ target_credential: "{{ target_cred_name }}"
+ source_credential: "{{ src_cred_name }}"
+ metadata:
+ object_query: "Safe=MY_SAFE;Object=AWX-user"
+ object_query_format: "Exact"
+ state: present
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Add Second Tower credential Lookup
+ tower_credential:
+ description: Credential for Testing Source Change
+ name: "{{ src_cred_name }}-2"
+ credential_type: CyberArk AIM Central Credential Provider Lookup
+ inputs:
+ url: "https://cyberark-prod.example.com"
+ app_id: "My-App-ID"
+ organization: Default
+ register: result
+
+- name: Change credential Input Source
+ tower_credential_input_source:
+ input_field_name: password
+ target_credential: "{{ target_cred_name }}"
+ source_credential: "{{ src_cred_name }}-2"
+ state: present
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Remove a Tower credential source
+ tower_credential_input_source:
+ input_field_name: password
+ target_credential: "{{ target_cred_name }}"
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Remove Tower credential Lookup
+ tower_credential:
+ name: "{{ src_cred_name }}"
+ organization: Default
+ credential_type: CyberArk AIM Central Credential Provider Lookup
+ state: absent
+ register: result
+
+- name: Remove Alt Tower credential Lookup
+ tower_credential:
+ name: "{{ src_cred_name }}-2"
+ organization: Default
+ credential_type: CyberArk AIM Central Credential Provider Lookup
+ state: absent
+ register: result
+
+- name: Remove Tower credential
+ tower_credential:
+ name: "{{ target_cred_name }}"
+ organization: Default
+ credential_type: Machine
+ state: absent
+ register: result
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_credential_type/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_credential_type/tasks/main.yml
new file mode 100644
index 00000000..a15cd267
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_credential_type/tasks/main.yml
@@ -0,0 +1,27 @@
+---
+- name: Generate names
+ set_fact:
+ cred_type_name: "AWX-Collection-tests-tower_credential_type-cred-type-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Add Tower credential type
+ tower_credential_type:
+ description: Credential type for Test
+ name: "{{ cred_type_name }}"
+ kind: cloud
+ inputs: {"fields": [{"type": "string", "id": "username", "label": "Username"}, {"secret": true, "type": "string", "id": "password", "label": "Password"}], "required": ["username", "password"]}
+ injectors: {"extra_vars": {"test": "foo"}}
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Remove a Tower credential type
+ tower_credential_type:
+ name: "{{ cred_type_name }}"
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_export/aliases b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_export/aliases
new file mode 100644
index 00000000..527d07c3
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_export/aliases
@@ -0,0 +1 @@
+skip/python2
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_export/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_export/tasks/main.yml
new file mode 100644
index 00000000..7ffbc158
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_export/tasks/main.yml
@@ -0,0 +1,77 @@
+---
+- name: Generate a random string for test
+ set_fact:
+ test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ when: test_id is not defined
+
+- name: Generate names
+ set_fact:
+ org_name1: "AWX-Collection-tests-tower_export-organization-{{ test_id }}"
+ org_name2: "AWX-Collection-tests-tower_export-organization2-{{ test_id }}"
+ inventory_name1: "AWX-Collection-tests-tower_export-inv1-{{ test_id }}"
+
+- block:
+ - name: Create some organizations
+ tower_organization:
+ name: "{{ item }}"
+ loop:
+ - "{{ org_name1 }}"
+ - "{{ org_name2 }}"
+
+ - name: Create an inventory
+ tower_inventory:
+ name: "{{ inventory_name1 }}"
+ organization: "{{ org_name1 }}"
+
+ - name: Export all tower assets
+ tower_export:
+ all: true
+ register: all_assets
+
+ - assert:
+ that:
+ - all_assets is not changed
+ - all_assets is successful
+ - all_assets['assets']['organizations'] | length() >= 2
+
+ - name: Export all inventories
+ tower_export:
+ inventory: 'all'
+ register: inventory_export
+
+ - assert:
+ that:
+ - inventory_export is successful
+ - inventory_export is not changed
+ - inventory_export['assets']['inventory'] | length() >= 1
+ - "'organizations' not in inventory_export['assets']"
+
+ # This mimics the example in the module
+ - name: Export an all and a specific
+ tower_export:
+ inventory: 'all'
+ organizations: "{{ org_name1 }}"
+ register: mixed_export
+
+ - assert:
+ that:
+ - mixed_export is successful
+ - mixed_export is not changed
+ - mixed_export['assets']['inventory'] | length() >= 1
+ - mixed_export['assets']['organizations'] | length() == 1
+ - "'workflow_job_templates' not in mixed_export['assets']"
+
+ always:
+ - name: Remove our inventory
+ tower_inventory:
+ name: "{{ inventory_name1 }}"
+ organization: "{{ org_name1 }}"
+ state: absent
+
+ - name: Remove test organizations
+ tower_organization:
+ name: "{{ item }}"
+ state: absent
+ loop:
+ - "{{ org_name1 }}"
+ - "{{ org_name2 }}"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_group/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_group/tasks/main.yml
new file mode 100644
index 00000000..38e76719
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_group/tasks/main.yml
@@ -0,0 +1,98 @@
+---
+- name: Generate names
+ set_fact:
+ group_name1: "AWX-Collection-tests-tower_group-group-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ group_name2: "AWX-Collection-tests-tower_group-group-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ group_name3: "AWX-Collection-tests-tower_group-group-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ inv_name: "AWX-Collection-test-tower_group-inv-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ host_name1: "AWX-Collection-test-tower_group-host-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ host_name2: "AWX-Collection-test-tower_group-host-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ host_name3: "AWX-Collection-test-tower_group-host-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Create an Inventory
+ tower_inventory:
+ name: "{{ inv_name }}"
+ organization: Default
+ state: present
+
+- name: Create a Group
+ tower_group:
+ name: "{{ group_name1 }}"
+ inventory: "{{ inv_name }}"
+ state: present
+ variables:
+ foo: bar
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a Group
+ tower_group:
+ name: "{{ group_name1 }}"
+ inventory: "{{ inv_name }}"
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Check module fails with correct msg
+ tower_group:
+ name: test-group
+ description: Group Description
+ inventory: test-non-existing-inventory
+ state: present
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "result.msg =='Failed to update the group, inventory not found: The requested object could not be found.' or
+ result.msg =='The inventories test-non-existing-inventory was not found on the Tower server'"
+
+- name: add hosts
+ tower_host:
+ name: "{{ item }}"
+ inventory: "{{ inv_name }}"
+ loop:
+ - "{{ host_name1 }}"
+ - "{{ host_name2 }}"
+ - "{{ host_name3 }}"
+
+- name: add mid level group
+ tower_group:
+ name: "{{ group_name2 }}"
+ inventory: "{{ inv_name }}"
+ hosts:
+ - "{{ host_name3 }}"
+
+- name: add top group
+ tower_group:
+ name: "{{ group_name3 }}"
+ inventory: "{{ inv_name }}"
+ hosts:
+ - "{{ host_name1 }}"
+ - "{{ host_name2 }}"
+ children:
+ - "{{ group_name2 }}"
+
+- name: Delete the parent group
+ tower_group:
+ name: "{{ group_name3 }}"
+ inventory: "{{ inv_name }}"
+ state: absent
+
+- name: Delete the child group
+ tower_group:
+ name: "{{ group_name2 }}"
+ inventory: "{{ inv_name }}"
+ state: absent
+
+- name: Delete an Inventory
+ tower_inventory:
+ name: "{{ inv_name }}"
+ organization: Default
+ state: absent
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_host/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_host/tasks/main.yml
new file mode 100644
index 00000000..597d6ef7
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_host/tasks/main.yml
@@ -0,0 +1,49 @@
+---
+- name: Generate names
+ set_fact:
+ host_name: "AWX-Collection-tests-tower_host-host-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ inv_name: "AWX-Collection-tests-tower_host-inv-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Create an Inventory
+ tower_inventory:
+ name: "{{ inv_name }}"
+ organization: Default
+ state: present
+
+- name: Create a Host
+ tower_host:
+ name: "{{ host_name }}"
+ inventory: "{{ inv_name }}"
+ state: present
+ variables:
+ foo: bar
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a Host
+ tower_host:
+ name: "{{ host_name }}"
+ inventory: "{{ inv_name }}"
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Check module fails with correct msg
+ tower_host:
+ name: test-host
+ description: Host Description
+ inventory: test-non-existing-inventory
+ state: present
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "result.msg =='The inventories test-non-existing-inventory was not found on the Tower server' or
+ result.msg =='Failed to update host, inventory not found: The requested object could not be found.'"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_import/aliases b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_import/aliases
new file mode 100644
index 00000000..527d07c3
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_import/aliases
@@ -0,0 +1 @@
+skip/python2
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_import/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_import/tasks/main.yml
new file mode 100644
index 00000000..9835ff89
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_import/tasks/main.yml
@@ -0,0 +1,108 @@
+---
+- name: Generate a random string for test
+ set_fact:
+ test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ when: test_id is not defined
+
+- name: Generate names
+ set_fact:
+ org_name1: "AWX-Collection-tests-tower_import-organization-{{ test_id }}"
+ org_name2: "AWX-Collection-tests-tower_import-organization2-{{ test_id }}"
+
+- block:
+ - name: "Import something"
+ tower_import:
+ assets:
+ organizations:
+ - name: "{{ org_name1 }}"
+ description: ""
+ max_hosts: 0
+ custom_virtualenv: null
+ related:
+ notification_templates: []
+ notification_templates_started: []
+ notification_templates_success: []
+ notification_templates_error: []
+ notification_templates_approvals: []
+ natural_key:
+ name: "Default"
+ type: "organization"
+ register: import_output
+
+ - assert:
+ that:
+ - import_output is changed
+
+ - name: "Import something again (awxkit is not idempotent, this tests a failure)"
+ tower_import:
+ assets:
+ organizations:
+ - name: "{{ org_name1 }}"
+ description: ""
+ max_hosts: 0
+ custom_virtualenv: null
+ related:
+ notification_templates: []
+ notification_templates_started: []
+ notification_templates_success: []
+ notification_templates_error: []
+ notification_templates_approvals: []
+ natural_key:
+ name: "Default"
+ type: "organization"
+ register: import_output
+ ignore_errors: true
+
+ - assert:
+ that:
+ - import_output is failed
+ - "'Organization with this Name already exists' in import_output.msg"
+
+ - name: "Write out a json file"
+ copy:
+ content: |
+ {
+ "organizations": [
+ {
+ "name": "{{ org_name2 }}",
+ "description": "",
+ "max_hosts": 0,
+ "custom_virtualenv": null,
+ "related": {
+ "notification_templates": [],
+ "notification_templates_started": [],
+ "notification_templates_success": [],
+ "notification_templates_error": [],
+ "notification_templates_approvals": []
+ },
+ "natural_key": {
+ "name": "Default",
+ "type": "organization"
+ }
+ }
+ ]
+ }
+ dest: ./org.json
+
+ - name: "Load assets from a file"
+ tower_import:
+ assets: "{{ lookup('file', 'org.json') | from_json() }}"
+ register: import_output
+
+ - assert:
+ that:
+ - import_output is changed
+
+ always:
+ - name: Remove organizations
+ tower_organization:
+ name: "{{ item }}"
+ state: absent
+ loop:
+ - "{{ org_name1 }}"
+ - "{{ org_name2 }}"
+
+ - name: Delete org.json
+ file:
+ path: ./org.json
+ state: absent
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_inventory/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_inventory/tasks/main.yml
new file mode 100644
index 00000000..a4e3424e
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_inventory/tasks/main.yml
@@ -0,0 +1,101 @@
+---
+- name: Generate names
+ set_fact:
+ inv_name1: "AWX-Collection-tests-tower_inventory-inv1-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ inv_name2: "AWX-Collection-tests-tower_inventory-inv2-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Create an Inventory
+ tower_inventory:
+ name: "{{ inv_name1 }}"
+ organization: Default
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Test Inventory module idempotency
+ tower_inventory:
+ name: "{{ inv_name1 }}"
+ organization: Default
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Fail Change Regular to Smart
+ tower_inventory:
+ name: "{{ inv_name1 }}"
+ organization: Default
+ kind: smart
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "result is failed"
+
+- name: Create a smart inventory
+ tower_inventory:
+ name: "{{ inv_name2 }}"
+ organization: Default
+ kind: smart
+ host_filter: name=foo
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a smart inventory
+ tower_inventory:
+ name: "{{ inv_name2 }}"
+ organization: Default
+ kind: smart
+ host_filter: name=foo
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete an Inventory
+ tower_inventory:
+ name: "{{ inv_name1 }}"
+ organization: Default
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a Non-Existent Inventory
+ tower_inventory:
+ name: "{{ inv_name1 }}"
+ organization: Default
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Check module fails with correct msg
+ tower_inventory:
+ name: test-inventory
+ description: Inventory Description
+ organization: test-non-existing-org
+ state: present
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "result is not changed"
+ - "result.msg =='Failed to update inventory, organization not found: The requested object could not be found.'
+ or result.msg =='The organizations test-non-existing-org was not found on the Tower server'"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_inventory_source/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_inventory_source/tasks/main.yml
new file mode 100644
index 00000000..fb5f33c3
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_inventory_source/tasks/main.yml
@@ -0,0 +1,82 @@
+---
+- name: Generate names
+ set_fact:
+ openstack_cred: "AWX-Collection-tests-tower_inventory_source-cred-openstack-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ openstack_inv: "AWX-Collection-tests-tower_inventory_source-inv-openstack-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ openstack_inv_source: "AWX-Collection-tests-tower_inventory_source-inv-source-openstack-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Add a Tower credential
+ tower_credential:
+ description: Credentials for Openstack Test project
+ name: "{{ openstack_cred }}"
+ kind: openstack
+ organization: Default
+ project: Test
+ username: admin
+ host: https://example.org:5000
+ password: passw0rd
+ domain: test
+
+- name: Add a Tower inventory
+ tower_inventory:
+ description: Test inventory
+ organization: Default
+ name: "{{ openstack_inv }}"
+
+- name: Create a source inventory
+ tower_inventory_source:
+ name: "{{ openstack_inv_source }}"
+ description: Source for Test inventory
+ inventory: "{{ openstack_inv }}"
+ credential: "{{ openstack_cred }}"
+ overwrite: true
+ update_on_launch: true
+ source_vars:
+ private: false
+ source: openstack
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete the inventory source with an invalid cred, source_project, sourece_script specified
+ tower_inventory_source:
+ name: "{{ openstack_inv_source }}"
+ inventory: "{{ openstack_inv }}"
+ credential: "Does Not Exit"
+ source_project: "Does Not Exist"
+ source_script: "Does Not Exist"
+ state: absent
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete the credential
+ tower_credential:
+ description: Credentials for Openstack Test project
+ name: "{{ openstack_cred }}"
+ kind: openstack
+ organization: Default
+ project: Test
+ username: admin
+ host: https://example.org:5000
+ password: passw0rd
+ domain: test
+ state: absent
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete the inventory
+ tower_inventory:
+ description: Test inventory
+ organization: Default
+ name: "{{ openstack_inv }}"
+ state: absent
+
+- assert:
+ that:
+ - "result is changed"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_cancel/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_cancel/tasks/main.yml
new file mode 100644
index 00000000..d949610d
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_cancel/tasks/main.yml
@@ -0,0 +1,40 @@
+---
+- name: Launch a Job Template
+ tower_job_launch:
+ job_template: "Demo Job Template"
+ register: job
+
+- assert:
+ that:
+ - "job is changed"
+
+- name: Cancel the job
+ tower_job_cancel:
+ job_id: "{{ job.id }}"
+ register: results
+
+- assert:
+ that:
+ - results is changed
+
+- name: Cancel an already canceled job (assert failure)
+ tower_job_cancel:
+ job_id: "{{ job.id }}"
+ fail_if_not_running: true
+ register: results
+ ignore_errors: true
+
+- assert:
+ that:
+ - results is failed
+
+- name: Check module fails with correct msg
+ tower_job_cancel:
+ job_id: 9999999999
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "result.msg =='Unable to cancel job_id/9999999999: The requested object could not be found.'
+ or result.msg =='Unable to find job with id 9999999999'"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_launch/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_launch/tasks/main.yml
new file mode 100644
index 00000000..86eb196a
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_launch/tasks/main.yml
@@ -0,0 +1,136 @@
+---
+- name: Generate names
+ set_fact:
+ jt_name1: "AWX-Collection-tests-tower_job_launch-jt1-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ jt_name2: "AWX-Collection-tests-tower_job_launch-jt2-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Launch a Job Template
+ tower_job_launch:
+ job_template: "Demo Job Template"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+ - "result.status == 'pending'"
+
+- name: Wait for a job template to complete
+ tower_job_wait:
+ job_id: "{{ result.id }}"
+ max_interval: 10
+ timeout: 120
+ register: result
+
+- assert:
+ that:
+ - "result is not changed"
+ - "result.status == 'successful'"
+
+- name: Check module fails with correct msg
+ tower_job_launch:
+ job_template: "Non Existing Job Template"
+ inventory: "Test Inventory"
+ credential: "Test Credential"
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "result.msg =='Unable to launch job, job_template/Non Existing Job Template was not found: The requested object could not be found.'
+ or result.msg == 'The inventories Test Inventory was not found on the Tower server'"
+
+- name: Create a Job Template for testing prompt on launch
+ tower_job_template:
+ name: "{{ jt_name1 }}"
+ project: Demo Project
+ playbook: hello_world.yml
+ job_type: run
+ ask_credential: true
+ ask_inventory: true
+ state: present
+ register: result
+
+- name: Launch job template with inventory and credential for prompt on launch
+ tower_job_launch:
+ job_template: "{{ jt_name1 }}"
+ inventory: "Demo Inventory"
+ credential: "Demo Credential"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+ - "result.status == 'pending'"
+
+- name: Create a project for testing extra_vars
+ tower_project:
+ name: "{{ proj_name }}"
+ organization: Default
+ scm_type: git
+ scm_url: https://github.com/ansible/test-playbooks
+
+- name: Create a Job Template for testing extra_vars
+ tower_job_template:
+ name: "{{ jt_name2 }}"
+ project: "{{ proj_name }}"
+ playbook: debug.yml
+ job_type: run
+ state: present
+ inventory: "Demo Inventory"
+ extra_vars:
+ foo: bar
+ register: result
+
+- name: Launch job template with inventory and credential for prompt on launch
+ tower_job_launch:
+ job_template: "{{ jt_name2 }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Get the job
+ tower_job_list:
+ query: {"id": "{{result.id}}"}
+ register: result
+
+- assert:
+ that:
+ - '{"foo": "bar"} | to_json in result.results[0].extra_vars'
+
+- name: Delete the first jt
+ tower_job_template:
+ name: "{{ jt_name1 }}"
+ project: Demo Project
+ playbook: hello_world.yml
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete the second jt
+ tower_job_template:
+ name: "{{ jt_name2 }}"
+ project: "{{ proj_name }}"
+ playbook: debug.yml
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete the extra_vars project
+ tower_project:
+ name: "{{ proj_name }}"
+ organization: Default
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_list/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_list/tasks/main.yml
new file mode 100644
index 00000000..a883f28d
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_list/tasks/main.yml
@@ -0,0 +1,38 @@
+---
+- name: Launch a Job Template
+ tower_job_launch:
+ job_template: "Demo Job Template"
+ register: job
+
+- assert:
+ that:
+ - "job is changed"
+ - "job.status == 'pending'"
+
+- name: List jobs w/ a matching primary key
+ tower_job_list:
+ query: {"id": "{{ job.id }}"}
+ register: matching_jobs
+
+- assert:
+ that:
+ - "{{ matching_jobs.count }} == 1"
+
+- name: List failed jobs (which don't exist)
+ tower_job_list:
+ status: failed
+ query: {"id": "{{ job.id }}"}
+ register: successful_jobs
+
+- assert:
+ that:
+ - "{{ successful_jobs.count }} == 0"
+
+- name: Get ALL result pages!
+ tower_job_list:
+ all_pages: true
+ register: all_page_query
+
+- assert:
+ that:
+ - 'not all_page_query.next'
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_template/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_template/tasks/main.yml
new file mode 100644
index 00000000..72711451
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_template/tasks/main.yml
@@ -0,0 +1,378 @@
+---
+- name: Generate a random string for test
+ set_fact:
+ test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: generate random string for project
+ set_fact:
+ cred1: "AWX-Collection-tests-tower_job_template-cred1-{{ test_id }}"
+ cred2: "AWX-Collection-tests-tower_job_template-cred2-{{ test_id }}"
+ cred3: "AWX-Collection-tests-tower_job_template-cred3-{{ test_id }}"
+ proj1: "AWX-Collection-tests-tower_job_template-proj-{{ test_id }}"
+ jt1: "AWX-Collection-tests-tower_job_template-jt1-{{ test_id }}"
+ jt2: "AWX-Collection-tests-tower_job_template-jt2-{{ test_id }}"
+ lab1: "AWX-Collection-tests-tower_job_template-lab1-{{ test_id }}"
+ email_not: "AWX-Collection-tests-tower_job_template-email-not-{{ test_id }}"
+ webhook_not: "AWX-Collection-tests-tower_notification-wehbook-not-{{ test_id }}"
+
+- name: Create a Demo Project
+ tower_project:
+ name: "{{ proj1 }}"
+ organization: Default
+ state: present
+ scm_type: git
+ scm_url: https://github.com/ansible/ansible-tower-samples.git
+
+ register: result
+
+- name: Create Credential1
+ tower_credential:
+ name: "{{ cred1 }}"
+ organization: Default
+ kind: tower
+
+- name: Create Credential2
+ tower_credential:
+ name: "{{ cred2 }}"
+ organization: Default
+ kind: ssh
+
+- name: Create Credential3
+ tower_credential:
+ name: "{{ cred3 }}"
+ organization: Default
+ kind: ssh
+
+- name: Create Label
+ tower_label:
+ name: "{{ lab1 }}"
+ organization: Default
+
+- name: Add email notification
+ tower_notification:
+ name: "{{ email_not }}"
+ organization: Default
+ notification_type: email
+ username: user
+ password: s3cr3t
+ sender: tower@example.com
+ recipients:
+ - user1@example.com
+ host: smtp.example.com
+ port: 25
+ use_tls: false
+ use_ssl: false
+ state: present
+
+- name: Add webhook notification
+ tower_notification:
+ name: "{{ webhook_not }}"
+ organization: Default
+ notification_type: webhook
+ url: http://www.example.com/hook
+ headers:
+ X-Custom-Header: value123
+ state: present
+ register: result
+
+- name: Create Job Template 1
+ tower_job_template:
+ name: "{{ jt1 }}"
+ project: "{{ proj1 }}"
+ inventory: Demo Inventory
+ playbook: hello_world.yml
+ credentials: ["{{ cred1 }}", "{{ cred2 }}"]
+ job_type: run
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Add a credential to this JT
+ tower_job_template:
+ name: "{{ jt1 }}"
+ project: "{{ proj1 }}"
+ playbook: hello_world.yml
+ credentials:
+ - "{{ cred1 }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Try to add the same credential to this JT
+ tower_job_template:
+ name: "{{ jt1 }}"
+ project: "{{ proj1 }}"
+ playbook: hello_world.yml
+ credentials:
+ - "{{ cred1 }}"
+ register: result
+
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Add another credential to this JT
+ tower_job_template:
+ name: "{{ jt1 }}"
+ project: "{{ proj1 }}"
+ playbook: hello_world.yml
+ credentials:
+ - "{{ cred1 }}"
+ - "{{ cred2 }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Remove a credential for this JT
+ tower_job_template:
+ name: "{{ jt1 }}"
+ project: "{{ proj1 }}"
+ playbook: hello_world.yml
+ credentials:
+ - "{{ cred1 }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Remove all credentials from this JT
+ tower_job_template:
+ name: "{{ jt1 }}"
+ project: "{{ proj1 }}"
+ playbook: hello_world.yml
+ credentials: []
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+# This doesnt work if you include the credentials parameter
+- name: Delete Job Template 1
+ tower_job_template:
+ name: "{{ jt1 }}"
+ playbook: hello_world.yml
+ job_type: run
+ project: "Does Not Exist"
+ inventory: "Does Not Exist"
+ webhook_credential: "Does Not Exist"
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create Job Template 2
+ tower_job_template:
+ name: "{{ jt2 }}"
+ project: "{{ proj1 }}"
+ inventory: Demo Inventory
+ playbook: hello_world.yml
+ credential: "{{ cred3 }}"
+ job_type: run
+ labels:
+ - "{{ lab1 }}"
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Add survey to Job Template 2
+ tower_job_template:
+ name: "{{ jt2 }}"
+ survey_enabled: true
+ survey_spec:
+ name: ""
+ description: ""
+ spec:
+ - question_name: "Q1"
+ question_description: "The first question"
+ required: true
+ type: "text"
+ variable: "q1"
+ min: 5
+ max: 15
+ default: "hello"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Re Add survey to Job Template 2
+ tower_job_template:
+ name: "{{ jt2 }}"
+ survey_enabled: true
+ survey_spec:
+ name: ""
+ description: ""
+ spec:
+ - question_name: "Q1"
+ question_description: "The first question"
+ required: true
+ type: "text"
+ variable: "q1"
+ min: 5
+ max: 15
+ default: "hello"
+ register: result
+
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Add question to survey to Job Template 2
+ tower_job_template:
+ name: "{{ jt2 }}"
+ survey_enabled: true
+ survey_spec:
+ name: ""
+ description: ""
+ spec:
+ - question_name: "Q1"
+ question_description: "The first question"
+ required: true
+ type: "text"
+ variable: "q1"
+ min: 5
+ max: 15
+ default: "hello"
+ choices: ""
+ - question_name: "Q2"
+ type: "text"
+ variable: "q2"
+ required: false
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Remove survey from Job Template 2
+ tower_job_template:
+ name: "{{ jt2 }}"
+ survey_enabled: false
+ survey_spec: {}
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Add started notifications to Job Template 2
+ tower_job_template:
+ name: "{{ jt2 }}"
+ notification_templates_started:
+ - "{{ email_not }}"
+ - "{{ webhook_not }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Re Add started notifications to Job Template 2
+ tower_job_template:
+ name: "{{ jt2 }}"
+ notification_templates_started:
+ - "{{ email_not }}"
+ - "{{ webhook_not }}"
+ register: result
+
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Add success notifications to Job Template 2
+ tower_job_template:
+ name: "{{ jt2 }}"
+ notification_templates_success:
+ - "{{ email_not }}"
+ - "{{ webhook_not }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Remove "on start" webhook notification from Job Template 2
+ tower_job_template:
+ name: "{{ jt2 }}"
+ notification_templates_started:
+ - "{{ email_not }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+
+- name: Delete Job Template 2
+ tower_job_template:
+ name: "{{ jt2 }}"
+ project: "{{ proj1 }}"
+ inventory: Demo Inventory
+ playbook: hello_world.yml
+ credential: "{{ cred3 }}"
+ job_type: run
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete the Demo Project
+ tower_project:
+ name: "{{ proj1 }}"
+ organization: Default
+ state: absent
+ scm_type: git
+ scm_url: https://github.com/ansible/ansible-tower-samples.git
+ register: result
+
+- name: Delete Credential1
+ tower_credential:
+ name: "{{ cred1 }}"
+ organization: Default
+ kind: tower
+ state: absent
+
+- name: Delete Credential2
+ tower_credential:
+ name: "{{ cred2 }}"
+ organization: Default
+ kind: ssh
+ state: absent
+
+- name: Delete Credential3
+ tower_credential:
+ name: "{{ cred3 }}"
+ organization: Default
+ kind: ssh
+ state: absent
+
+# You can't delete a label directly so no cleanup needed
+
+- name: Delete email notification
+ tower_notification:
+ name: "{{ email_not }}"
+ organization: Default
+ state: absent
+
+- name: Delete webhook notification
+ tower_notification:
+ name: "{{ webhook_not }}"
+ organization: Default
+ state: absent
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_wait/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_wait/tasks/main.yml
new file mode 100644
index 00000000..b04fa62f
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_job_wait/tasks/main.yml
@@ -0,0 +1,137 @@
+---
+- name: Generate random string for template and project
+ set_fact:
+ jt_name: "AWX-Collection-tests-tower_job_wait-long_running-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ proj_name: "AWX-Collection-tests-tower_job_wait-long_running-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Assure that the demo project exists
+ tower_project:
+ name: "{{ proj_name }}"
+ scm_type: 'git'
+ scm_url: 'https://github.com/ansible/test-playbooks.git'
+ scm_update_on_launch: true
+ organization: Default
+
+- name: Create a job template
+ tower_job_template:
+ name: "{{ jt_name }}"
+ playbook: "sleep.yml"
+ job_type: run
+ project: "{{ proj_name }}"
+ inventory: "Demo Inventory"
+ extra_vars:
+ sleep_interval: 300
+
+- name: Check deprecation warnings
+ tower_job_wait:
+ min_interval: 10
+ max_interval: 20
+ job_id: "99999999"
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "'Min and max interval have been deprecated, please use interval instead; interval will be set to 15'"
+
+- name: Validate that interval superceeds min/max
+ tower_job_wait:
+ min_interval: 10
+ max_interval: 20
+ interval: 12
+ job_id: "99999999"
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "result.msg =='Unable to wait on job 99999999; that ID does not exist in Tower.' or
+ 'min and max interval have been depricated, please use interval instead, interval will be set to 12'"
+
+- name: Check module fails with correct msg
+ tower_job_wait:
+ job_id: "99999999"
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - result is failed
+ - "result.msg =='Unable to wait, no job_id 99999999 found: The requested object could not be found.' or
+ 'Unable to wait on job 99999999; that ID does not exist in Tower.'"
+
+- name: Launch Demo Job Template (take happy path)
+ tower_job_launch:
+ job_template: "Demo Job Template"
+ register: job
+
+- assert:
+ that:
+ - job is changed
+
+- name: Wait for the Job to finish
+ tower_job_wait:
+ job_id: "{{ job.id }}"
+ register: wait_results
+
+# Make sure it worked and that we have some data in our results
+- assert:
+ that:
+ - wait_results is successful
+ - "'elapsed' in wait_results"
+ - "'id' in wait_results"
+
+- name: Launch a long running job
+ tower_job_launch:
+ job_template: "{{ jt_name }}"
+ register: job
+
+- assert:
+ that:
+ - job is changed
+
+- name: Timeout waiting for the job to complete
+ tower_job_wait:
+ job_id: "{{ job.id }}"
+ timeout: 5
+ ignore_errors: true
+ register: wait_results
+
+# Make sure that we failed and that we have some data in our results
+- assert:
+ that:
+ - "wait_results.msg == 'Monitoring aborted due to timeout' or 'Timeout waiting for job to finish.'"
+ - "'id' in wait_results"
+
+- name: Async cancel the long running job
+ tower_job_cancel:
+ job_id: "{{ job.id }}"
+ async: 3600
+ poll: 0
+
+- name: Wait for the job to exit on cancel
+ tower_job_wait:
+ job_id: "{{ job.id }}"
+ register: wait_results
+ ignore_errors: true
+
+- assert:
+ that:
+ - wait_results is failed
+ - 'wait_results.status == "canceled"'
+ - "wait_results.msg == 'Job with id {{ job.id }} failed' or 'Job with id={{ job.id }} failed, error: Job failed.'"
+
+- name: Delete the job template
+ tower_job_template:
+ name: "{{ jt_name }}"
+ playbook: "sleep.yml"
+ job_type: run
+ project: "{{ proj_name }}"
+ inventory: "Demo Inventory"
+ state: absent
+
+- name: Delete the project
+ tower_project:
+ name: "{{ proj_name }}"
+ organization: Default
+ state: absent
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_label/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_label/tasks/main.yml
new file mode 100644
index 00000000..d354376c
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_label/tasks/main.yml
@@ -0,0 +1,24 @@
+---
+- name: Generate names
+ set_fact:
+ label_name: "AWX-Collection-tests-tower_label-label-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Create a Label
+ tower_label:
+ name: "{{ label_name }}"
+ organization: Default
+ state: present
+
+- name: Check module fails with correct msg
+ tower_label:
+ name: "Test Label"
+ organization: "Non existing org"
+ state: present
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "'Non existing org was not found on the Tower server' in result.msg"
+
+# TODO: Deleting labels doesn't seem to work currently
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml
new file mode 100644
index 00000000..9f29b0d9
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml
@@ -0,0 +1,238 @@
+---
+- name: Generate a random string for test
+ set_fact:
+ test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ when: test_id is not defined
+
+- name: Generate usernames
+ set_fact:
+ usernames:
+ - "AWX-Collection-tests-tower_api_lookup-user1-{{ test_id }}"
+ - "AWX-Collection-tests-tower_api_lookup-user2-{{ test_id }}"
+ - "AWX-Collection-tests-tower_api_lookup-user3-{{ test_id }}"
+ hosts:
+ - "AWX-Collection-tests-tower_api_lookup-host1-{{ test_id }}"
+ - "AWX-Collection-tests-tower_api_lookup-host2-{{ test_id }}"
+ group_name: "AWX-Collection-tests-tower_api_lookup-group1-{{ test_id }}"
+
+- name: Get our collection package
+ tower_meta:
+ register: tower_meta
+
+- name: Generate the name of our plugin
+ set_fact:
+ plugin_name: "{{ tower_meta.prefix }}.tower_api"
+
+- name: Create all of our users
+ tower_user:
+ username: "{{ item }}"
+ is_superuser: true
+ password: "{{ test_id }}"
+ loop: "{{ usernames }}"
+ register: user_creation_results
+
+- block:
+ - name: Create our hosts
+ tower_host:
+ name: "{{ item }}"
+ inventory: "Demo Inventory"
+ loop: "{{ hosts }}"
+
+ - name: Test too many params (failure from validation of terms)
+ set_fact:
+ junk: "{{ query(plugin_name, 'users', 'teams', query_params={}, ) }}"
+ ignore_errors: true
+ register: result
+
+ - assert:
+ that:
+ - result is failed
+ - "'You must pass exactly one endpoint to query' in result.msg"
+
+ - name: Try to load invalid endpoint
+ set_fact:
+ junk: "{{ query(plugin_name, 'john', query_params={}, ) }}"
+ ignore_errors: true
+ register: result
+
+ - assert:
+ that:
+ - result is failed
+ - "'The requested object could not be found at' in result.msg"
+
+ - name: Load user of a specific name without promoting objects
+ set_fact:
+ users_list: "{{ lookup(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_objects=False) }}"
+
+ - assert:
+ that:
+ - users_list['results'] | length() == 1
+ - users_list['count'] == 1
+ - users_list['results'][0]['id'] == user_creation_results['results'][0]['id']
+
+ - name: Load user of a specific name with promoting objects
+ set_fact:
+ user_objects: "{{ query(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_objects=True ) }}"
+
+ - assert:
+ that:
+ - user_objects | length() == 1
+ - users_list['results'][0]['id'] == user_objects[0]['id']
+
+ - name: Loop over one user with the loop syntax
+ assert:
+ that:
+ - item['id'] == user_creation_results['results'][0]['id']
+ loop: "{{ query(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] } ) }}"
+ loop_control:
+ label: "{{ item.id }}"
+
+ - name: Get a page of users as just ids
+ set_fact:
+ users: "{{ query(plugin_name, 'users', query_params={ 'username__endswith': test_id, 'page_size': 2 }, return_ids=True ) }}"
+
+ - name: Assert that user list has 2 ids only and that they are strings, not ints
+ assert:
+ that:
+ - users | length() == 2
+ - user_creation_results['results'][0]['id'] not in users
+ - user_creation_results['results'][0]['id'] | string in users
+
+ - name: Get all users of a system through next attribute
+ set_fact:
+ users: "{{ query(plugin_name, 'users', query_params={ 'username__endswith': test_id, 'page_size': 1 }, return_all=true ) }}"
+
+ - assert:
+ that:
+ - users | length() >= 3
+
+ - name: Get all of the users created with a max_objects of 1
+ set_fact:
+ users: "{{ lookup(plugin_name, 'users', query_params={ 'username__endswith': test_id, 'page_size': 1 }, return_all=true, max_objects=1 ) }}"
+ ignore_errors: true
+ register: max_user_errors
+
+ - assert:
+ that:
+ - max_user_errors is failed
+ - "'List view at users returned 3 objects, which is more than the maximum allowed by max_objects' in max_user_errors.msg"
+
+ - name: Get the ID of the first user created and verify that it is correct
+ assert:
+ that: "{{ query(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_ids=True)[0] }} == {{ user_creation_results['results'][0]['id'] }}"
+
+ - name: Try to get an ID of someone who does not exist
+ set_fact:
+ failed_user_id: "{{ query(plugin_name, 'users', query_params={ 'username': 'john jacob jingleheimer schmidt' }, expect_one=True) }}"
+ register: result
+ ignore_errors: true
+
+ - assert:
+ that:
+ - result is failed
+ - "'Expected one object from endpoint users' in result['msg']"
+
+ - name: Lookup too many users
+ set_fact:
+ too_many_user_ids: " {{ query(plugin_name, 'users', query_params={ 'username__endswith': test_id }, expect_one=True) }}"
+ register: results
+ ignore_errors: true
+
+ - assert:
+ that:
+ - results is failed
+ - "'Expected one object from endpoint users, but obtained 3' in results['msg']"
+
+ - name: Get the ping page
+ set_fact:
+ ping_data: "{{ lookup(plugin_name, 'ping' ) }}"
+ register: results
+
+ - assert:
+ that:
+ - results is succeeded
+ - "'active_node' in ping_data"
+
+ - name: "Make sure that expect_objects fails on an API page"
+ set_fact:
+ my_var: "{{ lookup(plugin_name, 'settings/ui', expect_objects=True) }}"
+ ignore_errors: true
+ register: results
+
+ - assert:
+ that:
+ - results is failed
+ - "'Did not obtain a list or detail view at settings/ui, and expect_objects or expect_one is set to True' in results.msg"
+
+ # DOCS Example Tests
+ - name: Load the UI settings
+ set_fact:
+ tower_settings: "{{ lookup('awx.awx.tower_api', 'settings/ui') }}"
+
+ - assert:
+ that:
+ - "'CUSTOM_LOGO' in tower_settings"
+
+ - name: Display the usernames of all admin users
+ debug:
+ msg: "Admin users: {{ query('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true }) | map(attribute='username') | join(', ') }}"
+ register: results
+
+ - assert:
+ that:
+ - "'admin' in results.msg"
+
+ - name: debug all organizations in a loop # use query to return a list
+ debug:
+ msg: "Organization description={{ item['description'] }} id={{ item['id'] }}"
+ loop: "{{ query('awx.awx.tower_api', 'organizations') }}"
+ loop_control:
+ label: "{{ item['name'] }}"
+
+ - name: Make sure user 'john' is an org admin of the default org if the user exists
+ tower_role:
+ organization: Default
+ role: admin
+ user: "{{ usernames[0] }}"
+ state: absent
+ register: tower_role_revoke
+ when: "query('awx.awx.tower_api', 'users', query_params={ 'username': 'DNE_TESTING' }) | length == 1"
+
+ - assert:
+ that:
+ - tower_role_revoke is skipped
+
+ - name: Create an inventory group with all 'foo' hosts
+ tower_group:
+ name: "{{ group_name }}"
+ inventory: "Demo Inventory"
+ hosts: >-
+ {{ query(
+ 'awx.awx.tower_api',
+ 'hosts',
+ query_params={ 'name__endswith' : test_id, },
+ ) | map(attribute='name') | list }}
+ register: group_creation
+
+ - assert:
+ that: group_creation is changed
+
+ always:
+ - name: Cleanup group
+ tower_group:
+ name: "{{ group_name }}"
+ inventory: "Demo Inventory"
+ state: absent
+
+ - name: Cleanup hosts
+ tower_host:
+ name: "{{ item }}"
+ inventory: "Demo Inventory"
+ state: absent
+ loop: "{{ hosts }}"
+
+ - name: Cleanup users
+ tower_user:
+ username: "{{ item }}"
+ state: absent
+ loop: "{{ usernames }}"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_notification/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_notification/tasks/main.yml
new file mode 100644
index 00000000..ea3e96b9
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_notification/tasks/main.yml
@@ -0,0 +1,230 @@
+---
+- name: Generate names
+ set_fact:
+ slack_not: "AWX-Collection-tests-tower_notification-slack-not-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ webhook_not: "AWX-Collection-tests-tower_notification-wehbook-not-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ email_not: "AWX-Collection-tests-tower_notification-email-not-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ twillo_not: "AWX-Collection-tests-tower_notification-twillo-not-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ pd_not: "AWX-Collection-tests-tower_notification-pd-not-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ irc_not: "AWX-Collection-tests-tower_notification-irc-not-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Test deprecation warnings
+ tower_notification:
+ name: "{{ slack_not }}"
+ organization: Default
+ notification_type: slack
+ username: maw
+ sender: maw
+ recipients:
+ - everyone
+ use_tls: true
+ host: all
+ use_ssl: false
+ password: password
+ port: 12
+ channels:
+ - general
+ token: chunkecheese
+ account_token: asdf1234
+ from_number: "1 (888) 733-4281"
+ to_numbers:
+ - 867-5309
+ account_sid: vicious
+ subdomain: 'redhat.com'
+ service_key: skeleton
+ client_name: Bill
+ message_from: me
+ color: green
+ notify: true
+ url: ansible.com
+ headers:
+ X-Custom-Header: value123
+ server: littimer.somewhere.com
+ nickname: chalk
+ targets:
+ - zombie
+ state: absent
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "'deprecations' in result"
+ # The 25 can be count from the size of the OLD_INPUT_NAMES list in the module
+ - result['deprecations'] | length() == 25
+
+- name: Create Slack notification with custom messages
+ tower_notification:
+ name: "{{ slack_not }}"
+ organization: Default
+ notification_type: slack
+ token: a_token
+ channels:
+ - general
+ messages:
+ started:
+ message: "{{ '{{' }} job_friendly_name {{' }}' }} {{ '{{' }} job.id {{' }}' }} started"
+ success:
+ message: "{{ '{{' }} job_friendly_name {{ '}}' }} completed in {{ '{{' }} job.elapsed {{ '}}' }} seconds"
+ error:
+ message: "{{ '{{' }} job_friendly_name {{ '}}' }} FAILED! Please look at {{ '{{' }} job.url {{ '}}' }}"
+ state: present
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Delete Slack notification
+ tower_notification:
+ name: "{{ slack_not }}"
+ organization: Default
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Add webhook notification
+ tower_notification:
+ name: "{{ webhook_not }}"
+ organization: Default
+ notification_type: webhook
+ url: http://www.example.com/hook
+ headers:
+ X-Custom-Header: value123
+ state: present
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Delete webhook notification
+ tower_notification:
+ name: "{{ webhook_not }}"
+ organization: Default
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Add email notification
+ tower_notification:
+ name: "{{ email_not }}"
+ organization: Default
+ notification_type: email
+ username: user
+ password: s3cr3t
+ sender: tower@example.com
+ recipients:
+ - user1@example.com
+ host: smtp.example.com
+ port: 25
+ use_tls: false
+ use_ssl: false
+ state: present
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Delete email notification
+ tower_notification:
+ name: "{{ email_not }}"
+ organization: Default
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Add twilio notification
+ tower_notification:
+ name: "{{ twillo_not }}"
+ organization: Default
+ notification_type: twilio
+ account_token: a_token
+ account_sid: a_sid
+ from_number: '+15551112222'
+ to_numbers:
+ - '+15553334444'
+ state: present
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Delete twilio notification
+ tower_notification:
+ name: "{{ twillo_not }}"
+ organization: Default
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Add PagerDuty notification
+ tower_notification:
+ name: "{{ pd_not }}"
+ organization: Default
+ notification_type: pagerduty
+ token: a_token
+ subdomain: sub
+ client_name: client
+ service_key: a_key
+ state: present
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Delete PagerDuty notification
+ tower_notification:
+ name: "{{ pd_not }}"
+ organization: Default
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Add IRC notification
+ tower_notification:
+ name: "{{ irc_not }}"
+ organization: Default
+ notification_type: irc
+ nickname: tower
+ password: s3cr3t
+ targets:
+ - user1
+ port: 8080
+ server: irc.example.com
+ use_ssl: false
+ state: present
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Delete IRC notification
+ tower_notification:
+ name: "{{ irc_not }}"
+ organization: Default
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - result is changed
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_organization/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_organization/tasks/main.yml
new file mode 100644
index 00000000..cc40ed59
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_organization/tasks/main.yml
@@ -0,0 +1,98 @@
+---
+- name: Generate an org name
+ set_fact:
+ org_name: "AWX-Collection-tests-tower_organization-org-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Make sure {{ org_name }} is not there
+ tower_organization:
+ name: "{{ org_name }}"
+ state: absent
+ register: result
+
+- name: "Create a new organization"
+ tower_organization:
+ name: "{{ org_name }}"
+ register: result
+
+- assert:
+ that: "result is changed"
+
+- name: "Make sure making the same org is not a change"
+ tower_organization:
+ name: "{{ org_name }}"
+ register: result
+
+- assert:
+ that:
+ - "result is not changed"
+
+- name: "Try adding a bad custom_virtualenv"
+ tower_organization:
+ name: "{{ org_name }}"
+ custom_virtualenv: "/does/not/exit"
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "result is failed"
+
+- name: "Pass in all parameters"
+ tower_organization:
+ name: "{{ org_name }}"
+ description: "A description"
+ custom_virtualenv: ""
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: "Change the description"
+ tower_organization:
+ name: "{{ org_name }}"
+ description: "A new description"
+ custom_virtualenv: ""
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: "Remove the organization"
+ tower_organization:
+ name: "{{ org_name }}"
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: "Remove a missing organization"
+ tower_organization:
+ name: "{{ org_name }}"
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is not changed"
+
+# Test behaviour common to all tower modules
+- name: Check that SSL is available and verify_ssl is enabled (task must fail)
+ tower_organization:
+ name: Default
+ validate_certs: true
+ ignore_errors: true
+ register: check_ssl_is_used
+
+- name: Check that connection failed
+ assert:
+ that:
+ - "'CERTIFICATE_VERIFY_FAILED' in check_ssl_is_used['msg']"
+
+- name: Check that verify_ssl is disabled (task must not fail)
+ tower_organization:
+ name: Default
+ validate_certs: false
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_project/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_project/tasks/main.yml
new file mode 100644
index 00000000..cece78ac
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_project/tasks/main.yml
@@ -0,0 +1,225 @@
+---
+- name: Generate names
+ set_fact:
+ project_name1: "AWX-Collection-tests-tower_project-project1-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ project_name2: "AWX-Collection-tests-tower_project-project2-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ project_name3: "AWX-Collection-tests-tower_project-project3-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ jt1: "AWX-Collection-tests-tower_project-jt1-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ scm_cred_name: "AWX-Collection-tests-tower_project-scm-cred-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ org_name: "AWX-Collection-tests-tower_project-org-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ cred_name: "AWX-Collection-tests-tower_project-cred-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Create an SCM Credential
+ tower_credential:
+ name: "{{ scm_cred_name }}"
+ organization: Default
+ kind: scm
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Create a git project without credentials without waiting
+ tower_project:
+ name: "{{ project_name1 }}"
+ organization: Default
+ scm_type: git
+ scm_url: https://github.com/ansible/test-playbooks
+ wait: false
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Recreate the project to validate not changed
+ tower_project:
+ name: "{{ project_name1 }}"
+ organization: Default
+ scm_type: git
+ scm_url: https://github.com/ansible/test-playbooks
+ wait: true
+ register: result
+
+- assert:
+ that:
+ - result is not changed
+
+- name: Create organizations
+ tower_organization:
+ name: "{{ org_name }}"
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Create credential
+ tower_credential:
+ kind: scm
+ name: "{{ cred_name }}"
+ organization: "{{ org_name }}"
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Create a new test project in check_mode
+ tower_project:
+ name: "{{ project_name2 }}"
+ organization: "{{ org_name }}"
+ scm_type: git
+ scm_url: https://github.com/ansible/test-playbooks
+ scm_credential: "{{ cred_name }}"
+ check_mode: true
+
+- name: Create a new test project
+ tower_project:
+ name: "{{ project_name2 }}"
+ organization: "{{ org_name }}"
+ scm_type: git
+ scm_url: https://github.com/ansible/test-playbooks
+ scm_credential: "{{ cred_name }}"
+ register: result
+
+# If this fails it may be because the check_mode task actually already created
+# the project, or it could be because the module actually failed somehow
+- assert:
+ that:
+ - "result is changed"
+
+- name: Check module fails with correct msg when given non-existing org as param
+ tower_project:
+ name: "{{ project_name2 }}"
+ organization: Non Existing Org
+ scm_type: git
+ scm_url: https://github.com/ansible/test-playbooks
+ scm_credential: "{{ cred_name }}"
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "result.msg == 'The organizations Non Existing Org was not found on the Tower server' or
+ result.msg == 'Failed to update project, organization not found: Non Existing Org'"
+
+- name: Check module fails with correct msg when given non-existing credential as param
+ tower_project:
+ name: "{{ project_name2 }}"
+ organization: "{{ org_name }}"
+ scm_type: git
+ scm_url: https://github.com/ansible/test-playbooks
+ scm_credential: Non Existing Credential
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "result.msg =='The credentials Non Existing Credential was not found on the Tower server' or
+ result.msg =='Failed to update project, credential not found: Non Existing Credential'"
+
+- name: Create a git project without credentials without waiting
+ tower_project:
+ name: "{{ project_name3 }}"
+ organization: Default
+ scm_type: git
+ scm_branch: empty_branch
+ scm_url: https://github.com/ansible/test-playbooks
+ allow_override: true
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Create a job template that overrides the project scm_branch
+ tower_job_template:
+ name: "{{ jt1 }}"
+ project: "{{ project_name3 }}"
+ inventory: "Demo Inventory"
+ scm_branch: master
+ playbook: debug.yml
+
+- name: Launch "{{ jt1 }}"
+ tower_job_launch:
+ job_template: "{{ jt1 }}"
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: "wait for job {{ result.id }}"
+ tower_job_wait:
+ job_id: "{{ result.id }}"
+ register: job
+
+- assert:
+ that:
+ - job is successful
+
+- name: Delete the test job_template
+ tower_job_template:
+ name: "{{ jt1 }}"
+ project: "{{ project_name3 }}"
+ inventory: "Demo Inventory"
+ state: absent
+
+- name: Delete the test project 3
+ tower_project:
+ name: "{{ project_name3 }}"
+ organization: Default
+ state: absent
+
+- name: Delete the test project 2
+ tower_project:
+ name: "{{ project_name2 }}"
+ organization: "{{ org_name }}"
+ state: absent
+
+- name: Delete the SCM Credential
+ tower_credential:
+ name: "{{ scm_cred_name }}"
+ organization: Default
+ kind: scm
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Delete the test project 1
+ tower_project:
+ name: "{{ project_name1 }}"
+ organization: Default
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Delete credential
+ tower_credential:
+ kind: scm
+ name: "{{ cred_name }}"
+ organization: "{{ org_name }}"
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Delete the organization
+ tower_organization:
+ name: "{{ org_name }}"
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - result is changed
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml
new file mode 100644
index 00000000..7f5b3b49
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml
@@ -0,0 +1,58 @@
+---
+- name: get tower host variable
+ shell: tower-cli config host | cut -d ' ' -f2
+ register: host
+
+- name: get tower username variable
+ shell: tower-cli config username | cut -d ' ' -f2
+ register: username
+
+- name: get tower password variable
+ shell: tower-cli config password | cut -d ' ' -f2
+ register: password
+
+- name: Fetch project_base_dir
+ uri:
+ url: "{{ host.stdout }}/api/v2/config/"
+ user: "{{ username.stdout }}"
+ password: "{{ password.stdout }}"
+ validate_certs: false
+ return_content: true
+ force_basic_auth: true
+ register: awx_config
+
+- tower_inventory:
+ name: localhost
+ organization: Default
+
+- tower_host:
+ name: localhost
+ inventory: localhost
+ variables:
+ ansible_connection: local
+
+- name: create an unused SSH / Machine credential
+ tower_credential:
+ name: dummy
+ kind: ssh
+ ssh_key_data: |
+ -----BEGIN EC PRIVATE KEY-----
+ MHcCAQEEIIUl6R1xgzR6siIUArz4XBPtGZ09aetma2eWf1v3uYymoAoGCCqGSM49
+ AwEHoUQDQgAENJNjgeZDAh/+BY860s0yqrLDprXJflY0GvHIr7lX3ieCtrzOMCVU
+ QWzw35pc5tvuP34SSi0ZE1E+7cVMDDOF3w==
+ -----END EC PRIVATE KEY-----
+ organization: Default
+
+- name: Disable bubblewrap
+ command: tower-cli setting modify AWX_PROOT_ENABLED false
+
+- block:
+ - name: Create a directory for manual project
+ vars:
+ project_base_dir: "{{ awx_config.json.project_base_dir }}"
+ command: tower-cli ad_hoc launch --wait --inventory localhost
+ --credential dummy --module-name command
+ --module-args "mkdir -p {{ project_base_dir }}/{{ project_dir_name }}"
+ always:
+ - name: enable bubblewrap
+ command: tower-cli setting modify AWX_PROOT_ENABLED true
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_project_manual/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_project_manual/tasks/main.yml
new file mode 100644
index 00000000..8718219e
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_project_manual/tasks/main.yml
@@ -0,0 +1,37 @@
+---
+- name: generate random string for project
+ set_fact:
+ rand_string: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+- name: Generate manual project dir name
+ set_fact:
+ project_name: "manual project {{ rand_string }}"
+
+- name: Generate manual project dir name
+ set_fact:
+ project_dir_name: "proj_{{ rand_string }}"
+
+- name: create a project directory for manual project
+ import_tasks: create_project_dir.yml
+
+- name: Create a manual project
+ tower_project:
+ name: "{{ project_name }}"
+ organization: Default
+ scm_type: manual
+ local_path: "{{ project_dir_name }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a manual project
+ tower_project:
+ name: "{{ project_name }}"
+ organization: Default
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_role/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_role/tasks/main.yml
new file mode 100644
index 00000000..f0c26a7e
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_role/tasks/main.yml
@@ -0,0 +1,74 @@
+---
+- name: Generate names
+ set_fact:
+ username: "AWX-Collection-tests-tower_role-user-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Create a User
+ tower_user:
+ first_name: Joe
+ last_name: User
+ username: "{{ username }}"
+ password: "{{ 65535 | random | to_uuid }}"
+ email: joe@example.org
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Add Joe to the update role of the default Project
+ tower_role:
+ user: "{{ username }}"
+ role: update
+ project: Demo Project
+ state: "{{ item }}"
+ register: result
+ with_items:
+ - "present"
+ - "absent"
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a workflow
+ tower_workflow_job_template:
+ name: test-role-workflow
+ organization: Default
+ state: present
+
+- name: Add Joe to workflow execute role
+ tower_role:
+ user: "{{ username }}"
+ role: execute
+ workflow: test-role-workflow
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Add Joe to workflow execute role, no-op
+ tower_role:
+ user: "{{ username }}"
+ role: execute
+ workflow: test-role-workflow
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Delete a User
+ tower_user:
+ username: "{{ username }}"
+ email: joe@example.org
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_schedule/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_schedule/tasks/main.yml
new file mode 100644
index 00000000..9a2caa0c
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_schedule/tasks/main.yml
@@ -0,0 +1,88 @@
+---
+- name: Generate a random string for test
+ set_fact:
+ test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: generate random string for project
+ set_fact:
+ sched1: "AWX-Collection-tests-tower_schedule-sched1-{{ test_id }}"
+
+- name: Try to create without an rrule
+ tower_schedule:
+ name: "{{ sched1 }}"
+ state: present
+ unified_job_template: "Demo Job Template"
+ enabled: true
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - result is failed
+ - "'Unable to create schedule {{ sched1 }}' in result.msg"
+
+- name: Create with options that the JT does not support
+ tower_schedule:
+ name: "{{ sched1 }}"
+ state: present
+ unified_job_template: "Demo Job Template"
+ rrule: "DTSTART:20191219T130551Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1"
+ description: "This hopefully will not work"
+ extra_data:
+ some: var
+ inventory: Demo Inventory
+ scm_branch: asdf1234
+ job_type: run
+ job_tags: other_tags
+ skip_tags: some_tags
+ limit: node1
+ diff_mode: true
+ verbosity: 4
+ enabled: true
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - result is failed
+ - "'Unable to create schedule {{ sched1 }}' in result.msg"
+
+- name: Build a real schedule
+ tower_schedule:
+ name: "{{ sched1 }}"
+ state: present
+ unified_job_template: "Demo Job Template"
+ rrule: "DTSTART:20191219T130551Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1"
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Rebuild the same schedule
+ tower_schedule:
+ name: "{{ sched1 }}"
+ state: present
+ unified_job_template: "Demo Job Template"
+ rrule: "DTSTART:20191219T130551Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1"
+ register: result
+
+- assert:
+ that:
+ - result is not changed
+
+- name: Disable a schedule
+ tower_schedule:
+ name: "{{ sched1 }}"
+ state: present
+ enabled: "false"
+ register: result
+
+- assert:
+ that:
+ - result is changed
+
+- name: Delete the schedule
+ tower_schedule:
+ name: "{{ sched1 }}"
+ state: absent
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_schedule_rrule/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_schedule_rrule/tasks/main.yml
new file mode 100644
index 00000000..837821ba
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_schedule_rrule/tasks/main.yml
@@ -0,0 +1,50 @@
+---
+- name: Get our collection package
+ tower_meta:
+ register: tower_meta
+
+- name: Generate the name of our plugin
+ set_fact:
+ plugin_name: "{{ tower_meta.prefix }}.tower_schedule_rrule"
+
+- name: Test too many params (failure from validation of terms)
+ debug:
+ msg: "{{ query(plugin_name, 'none', 'weekly', start_date='2020-4-16 03:45:07') }}"
+ ignore_errors: true
+ register: result
+
+- assert:
+ that:
+ - result is failed
+ - "'You may only pass one schedule type in at a time' in result.msg"
+
+- name: Test invalid frequency (failure from validation of term)
+ debug:
+ msg: "{{ query(plugin_name, 'john', start_date='2020-4-16 03:45:07') }}"
+ ignore_errors: true
+ register: result
+
+- assert:
+ that:
+ - result is failed
+ - "'Frequency of john is invalid' in result.msg"
+
+- name: Test an invalid start date (generic failure case from get_rrule)
+ debug:
+ msg: "{{ query(plugin_name, 'none', start_date='invalid') }}"
+ ignore_errors: true
+ register: result
+
+- assert:
+ that:
+ - result is failed
+ - "'Parameter start_date must be in the format YYYY-MM-DD' in result.msg"
+
+- name: Test end_on as count (generic success case)
+ debug:
+ msg: "{{ query(plugin_name, 'minute', start_date='2020-4-16 03:45:07', end_on='2') }}"
+ register: result
+
+- assert:
+ that:
+ - result.msg == 'DTSTART;TZID=America/New_York:20200416T034507 RRULE:FREQ=MINUTELY;COUNT=2;INTERVAL=1'
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_settings/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_settings/tasks/main.yml
new file mode 100644
index 00000000..8a42f576
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_settings/tasks/main.yml
@@ -0,0 +1,87 @@
+---
+- name: Set the value of AWX_PROOT_SHOW_PATHS to a baseline
+ tower_settings:
+ name: AWX_PROOT_SHOW_PATHS
+ value: '["/var/lib/awx/projects/"]'
+
+- name: Set the value of AWX_PROOT_SHOW_PATHS to get an error back from Tower
+ tower_settings:
+ settings:
+ AWX_PROOT_SHOW_PATHS:
+ 'not': 'a valid'
+ 'tower': 'setting'
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "result is failed"
+
+- name: Set the value of AWX_PROOT_SHOW_PATHS
+ tower_settings:
+ name: AWX_PROOT_SHOW_PATHS
+ value: '["/var/lib/awx/projects/", "/tmp"]'
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Attempt to set the value of AWX_PROOT_BASE_PATH to what it already is
+ tower_settings:
+ name: AWX_PROOT_BASE_PATH
+ value: /tmp
+ register: result
+
+- debug:
+ msg: "{{ result }}"
+
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Apply a single setting via settings
+ tower_settings:
+ name: AWX_PROOT_SHOW_PATHS
+ value: '["/var/lib/awx/projects/", "/var/tmp"]'
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Apply multiple setting via settings with no change
+ tower_settings:
+ settings:
+ AWX_PROOT_BASE_PATH: /tmp
+ AWX_PROOT_SHOW_PATHS: ["/var/lib/awx/projects/", "/var/tmp"]
+ register: result
+
+- debug:
+ msg: "{{ result }}"
+
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Apply multiple setting via settings with change
+ tower_settings:
+ settings:
+ AWX_PROOT_BASE_PATH: /tmp
+ AWX_PROOT_SHOW_PATHS: []
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Handle an omit value
+ tower_settings:
+ name: AWX_PROOT_BASE_PATH
+ value: '{{ junk_var | default(omit) }}'
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "'Unable to update settings' in result.msg"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_team/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_team/tasks/main.yml
new file mode 100644
index 00000000..0bb9047f
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_team/tasks/main.yml
@@ -0,0 +1,53 @@
+---
+- name: Generate names
+ set_fact:
+ team_name: "AWX-Collection-tests-tower_team-team-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Attempt to add a Tower team to a non-existant Organization
+ tower_team:
+ name: Test Team
+ organization: Missing Organization
+ state: present
+ register: result
+ ignore_errors: true
+
+- name: Assert a meaningful error was provided for the failed Tower team creation
+ assert:
+ that:
+ - result is failed
+ - "result.msg =='Failed to update team, organization not found: The requested object could not be found.' or
+ result.msg =='The organizations Missing Organization was not found on the Tower server'"
+
+- name: Create a Tower team
+ tower_team:
+ name: "{{ team_name }}"
+ organization: Default
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a Tower team
+ tower_team:
+ name: "{{ team_name }}"
+ organization: Default
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Check module fails with correct msg
+ tower_team:
+ name: "{{ team_name }}"
+ organization: Non Existing Org
+ state: present
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "result.msg =='Failed to update team, organization not found: The requested object could not be found.' or
+ result.msg =='The organizations Non Existing Org was not found on the Tower server'"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_token/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_token/tasks/main.yml
new file mode 100644
index 00000000..355d5dd0
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_token/tasks/main.yml
@@ -0,0 +1,110 @@
+---
+- name: Generate names
+ set_fact:
+ token_description: "AWX-Collection-tests-tower_token-description-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Try to use a token as a dict which is missing the token parameter
+ tower_job_list:
+ tower_oauthtoken:
+ not_token: "This has no token entry"
+ register: results
+ ignore_errors: true
+
+- assert:
+ that:
+ - results is failed
+ - '"The provided dict in tower_oauthtoken did not properly contain the token entry" == results.msg'
+
+- name: Try to use a token as a list
+ tower_job_list:
+ tower_oauthtoken:
+ - dummy_token
+ register: results
+ ignore_errors: true
+
+- assert:
+ that:
+ - results is failed
+ - '"The provided tower_oauthtoken type was not valid (list). Valid options are str or dict." == results.msg'
+
+- name: Try to delete a token with no existing_token or existing_token_id
+ tower_token:
+ state: absent
+ register: results
+ ignore_errors: true
+
+- assert:
+ that:
+ - results is failed
+ # We don't assert a message here because it handled by ansible
+
+- name: Try to delete a token with both existing_token or existing_token_id
+ tower_token:
+ existing_token:
+ id: 1234
+ existing_token_id: 1234
+ state: absent
+ register: results
+ ignore_errors: true
+
+- assert:
+ that:
+ - results is failed
+ # We don't assert a message here because it handled by ansible
+
+
+- block:
+ - name: Create a Token
+ tower_token:
+ description: '{{ token_description }}'
+ scope: "write"
+ state: present
+ register: new_token
+
+ - name: Validate our token works by token
+ tower_job_list:
+ tower_oauthtoken: "{{ tower_token.token }}"
+ register: job_list
+
+ - name: Validate out token works by object
+ tower_job_list:
+ tower_oauthtoken: "{{ tower_token }}"
+ register: job_list
+
+ always:
+ - name: Delete our Token with our own token
+ tower_token:
+ existing_token: "{{ tower_token }}"
+ tower_oauthtoken: "{{ tower_token }}"
+ state: absent
+ when: tower_token is defined
+ register: results
+
+ - assert:
+ that:
+ - results is changed or results is skipped
+
+- block:
+ - name: Create a second token
+ tower_token:
+ description: '{{ token_description }}'
+ scope: "write"
+ state: present
+ register: results
+
+ - assert:
+ that:
+ - results is changed
+
+ always:
+ - name: Delete the second Token with our own token
+ tower_token:
+ existing_token_id: "{{ tower_token['id'] }}"
+ tower_oauthtoken: "{{ tower_token }}"
+ state: absent
+ when: tower_token is defined
+ register: results
+
+ - assert:
+ that:
+ - results is changed or resuslts is skipped
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_user/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_user/tasks/main.yml
new file mode 100644
index 00000000..f04f1a83
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_user/tasks/main.yml
@@ -0,0 +1,120 @@
+---
+- name: Generate names
+ set_fact:
+ username: "AWX-Collection-tests-tower_user-user-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Create a User
+ tower_user:
+ username: "{{ username }}"
+ first_name: Joe
+ password: "{{ 65535 | random | to_uuid }}"
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Change a User
+ tower_user:
+ username: "{{ username }}"
+ last_name: User
+ email: joe@example.org
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Check idempotency
+ tower_user:
+ username: "{{ username }}"
+ first_name: Joe
+ last_name: User
+ register: result
+
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Delete a User
+ tower_user:
+ username: "{{ username }}"
+ email: joe@example.org
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create an Auditor
+ tower_user:
+ first_name: Joe
+ last_name: Auditor
+ username: "{{ username }}"
+ password: "{{ 65535 | random | to_uuid }}"
+ email: joe@example.org
+ state: present
+ auditor: true
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete an Auditor
+ tower_user:
+ username: "{{ username }}"
+ email: joe@example.org
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a Superuser
+ tower_user:
+ first_name: Joe
+ last_name: Super
+ username: "{{ username }}"
+ password: "{{ 65535 | random | to_uuid }}"
+ email: joe@example.org
+ state: present
+ superuser: true
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a Superuser
+ tower_user:
+ username: "{{ username }}"
+ email: joe@example.org
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Test tower SSL parameter
+ tower_user:
+ first_name: Joe
+ last_name: User
+ username: "{{ username }}"
+ password: "{{ 65535 | random | to_uuid }}"
+ email: joe@example.org
+ state: present
+ validate_certs: true
+ tower_host: http://foo.invalid
+ ignore_errors: true
+ register: result
+
+- assert:
+ that:
+ - "'Unable to resolve tower_host' in result.msg or
+ 'Can not verify ssl with non-https protocol' in result.exception"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_workflow_job_template/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_workflow_job_template/tasks/main.yml
new file mode 100644
index 00000000..8a7a9771
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_workflow_job_template/tasks/main.yml
@@ -0,0 +1,276 @@
+---
+- name: Generate a random string for names
+ set_fact:
+ test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
+- name: Generate random names for test objects
+ set_fact:
+ scm_cred_name: "AWX-Collection-tests-tower_workflow_job_template-scm-cred-{{ test_id }}"
+ demo_project_name: "AWX-Collection-tests-tower_workflow_job_template-proj-{{ test_id }}"
+ jt1_name: "AWX-Collection-tests-tower_workflow_job_template-jt1-{{ test_id }}"
+ jt2_name: "AWX-Collection-tests-tower_workflow_job_template-jt2-{{ test_id }}"
+ wfjt_name: "AWX-Collection-tests-tower_workflow_job_template-wfjt-{{ test_id }}"
+ email_not: "AWX-Collection-tests-tower_job_template-email-not-{{ test_id }}"
+ webhook_not: "AWX-Collection-tests-tower_notification-wehbook-not-{{ test_id }}"
+
+- name: Create an SCM Credential
+ tower_credential:
+ name: "{{ scm_cred_name }}"
+ organization: Default
+ kind: scm
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Add email notification
+ tower_notification:
+ name: "{{ email_not }}"
+ organization: Default
+ notification_type: email
+ username: user
+ password: s3cr3t
+ sender: tower@example.com
+ recipients:
+ - user1@example.com
+ host: smtp.example.com
+ port: 25
+ use_tls: false
+ use_ssl: false
+ state: present
+
+- name: Add webhook notification
+ tower_notification:
+ name: "{{ webhook_not }}"
+ organization: Default
+ notification_type: webhook
+ url: http://www.example.com/hook
+ headers:
+ X-Custom-Header: value123
+ state: present
+ register: result
+
+- name: Create a Demo Project
+ tower_project:
+ name: "{{ demo_project_name }}"
+ organization: Default
+ state: present
+ scm_type: git
+ scm_url: https://github.com/ansible/ansible-tower-samples.git
+ scm_credential: "{{ scm_cred_name }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a Job Template
+ tower_job_template:
+ name: "{{ jt1_name }}"
+ project: "{{ demo_project_name }}"
+ inventory: Demo Inventory
+ playbook: hello_world.yml
+ credential: Demo Credential
+ job_type: run
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a second Job Template
+ tower_job_template:
+ name: "{{ jt2_name }}"
+ project: "{{ demo_project_name }}"
+ inventory: Demo Inventory
+ playbook: hello_world.yml
+ credential: Demo Credential
+ job_type: run
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Add a Survey to second Job Template
+ tower_job_template:
+ name: "{{ jt2_name }}"
+ project: "{{ demo_project_name }}"
+ inventory: Demo Inventory
+ playbook: hello_world.yml
+ credential: Demo Credential
+ job_type: run
+ state: present
+ survey_enabled: true
+ survey_spec: '{"spec": [{"index": 0, "question_name": "my question?", "default": "mydef", "variable": "myvar", "type": "text", "required": false}], "description": "test", "name": "test"}'
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Create a workflow job template
+ tower_workflow_job_template:
+ name: "{{ wfjt_name }}"
+ inventory: Demo Inventory
+ extra_vars: {'foo': 'bar', 'another-foo': {'barz': 'bar2'}}
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+# Node actions do what this schema command used to do
+# schema: [{"success": [{"job_template": "{{ jt1_name }}"}], "job_template": "{{ jt2_name }}"}]
+- name: Create leaf node
+ tower_workflow_job_template_node:
+ identifier: leaf
+ unified_job_template: "{{ jt2_name }}"
+ workflow: "{{ wfjt_name }}"
+
+- name: Create root node
+ tower_workflow_job_template_node:
+ identifier: root
+ unified_job_template: "{{ jt1_name }}"
+ workflow: "{{ wfjt_name }}"
+
+- name: Add started notifications to workflow job template
+ tower_workflow_job_template:
+ name: "{{ wfjt_name }}"
+ notification_templates_started:
+ - "{{ email_not }}"
+ - "{{ webhook_not }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Re Add started notifications to workflow job template
+ tower_workflow_job_template:
+ name: "{{ wfjt_name }}"
+ notification_templates_started:
+ - "{{ email_not }}"
+ - "{{ webhook_not }}"
+ register: result
+
+- assert:
+ that:
+ - "result is not changed"
+
+- name: Add success notifications to workflow job template
+ tower_workflow_job_template:
+ name: "{{ wfjt_name }}"
+ notification_templates_success:
+ - "{{ email_not }}"
+ - "{{ webhook_not }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Remove "on start" webhook notification from workflow job template
+ tower_workflow_job_template:
+ name: "{{ wfjt_name }}"
+ notification_templates_started:
+ - "{{ email_not }}"
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete a workflow job template with an invalid inventory and webook_credential
+ tower_workflow_job_template:
+ name: "{{ wfjt_name }}"
+ inventory: "Does Not Exist"
+ webhook_credential: "Does Not Exist"
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Check module fails with correct msg
+ tower_workflow_job_template:
+ name: "{{ wfjt_name }}"
+ organization: Non Existing Organization
+ register: result
+ ignore_errors: true
+
+- assert:
+ that:
+ - "'The organizations Non Existing Organization was not found' in result.msg"
+
+- name: Delete the Job Template
+ tower_job_template:
+ name: "{{ jt1_name }}"
+ project: "{{ demo_project_name }}"
+ inventory: Demo Inventory
+ playbook: hello_world.yml
+ credential: Demo Credential
+ job_type: run
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete the second Job Template
+ tower_job_template:
+ name: "{{ jt2_name }}"
+ project: "{{ demo_project_name }}"
+ inventory: Demo Inventory
+ playbook: hello_world.yml
+ credential: Demo Credential
+ job_type: run
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete the Demo Project
+ tower_project:
+ name: "{{ demo_project_name }}"
+ organization: Default
+ scm_type: git
+ scm_url: https://github.com/ansible/ansible-tower-samples.git
+ scm_credential: "{{ scm_cred_name }}"
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete the SCM Credential
+ tower_credential:
+ name: "{{ scm_cred_name }}"
+ organization: Default
+ kind: scm
+ state: absent
+ register: result
+
+- assert:
+ that:
+ - "result is changed"
+
+- name: Delete email notification
+ tower_notification:
+ name: "{{ email_not }}"
+ organization: Default
+ state: absent
+
+- name: Delete webhook notification
+ tower_notification:
+ name: "{{ webhook_not }}"
+ organization: Default
+ state: absent
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_workflow_launch/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_workflow_launch/tasks/main.yml
new file mode 100644
index 00000000..bf88aecf
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/integration/targets/tower_workflow_launch/tasks/main.yml
@@ -0,0 +1,91 @@
+---
+- name: Generate a random string for test
+ set_fact:
+ test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+ when: test_id is not defined
+
+- name: Generate names
+ set_fact:
+ wfjt_name1: "AWX-Collection-tests-tower_workflow_launch--wfjt1-{{ test_id }}"
+
+- name: Create our workflow
+ tower_workflow_job_template:
+ name: "{{ wfjt_name1 }}"
+ state: present
+
+- name: Add a node
+ tower_workflow_job_template_node:
+ workflow_job_template: "{{ wfjt_name1 }}"
+ unified_job_template: "Demo Job Template"
+ identifier: leaf
+ register: new_node
+
+- name: Connect to Tower server but request an invalid workflow
+ tower_workflow_launch:
+ workflow_template: "Does Not Exist"
+ ignore_errors: true
+ register: result
+
+- assert:
+ that:
+ - result is failed
+ - "'Unable to find workflow job template' in result.msg"
+
+- name: Run the workflow without waiting (this should just give us back a job ID)
+ tower_workflow_launch:
+ workflow_template: "{{ wfjt_name1 }}"
+ wait: false
+ ignore_errors: true
+ register: result
+
+- assert:
+ that:
+ - result is not failed
+ - "'id' in result['job_info']"
+
+- name: Kick off a workflow and wait for it, but only for a second
+ tower_workflow_launch:
+ workflow_template: "{{ wfjt_name1 }}"
+ timeout: 1
+ ignore_errors: true
+ register: result
+
+- assert:
+ that:
+ - result is failed
+ - "'Monitoring aborted due to timeout' in result.msg"
+
+- name: Kick off a workflow and wait for it
+ tower_workflow_launch:
+ workflow_template: "{{ wfjt_name1 }}"
+ ignore_errors: true
+ register: result
+
+- assert:
+ that:
+ - result is not failed
+ - "'id' in result['job_info']"
+
+- name: Prompt the workflow's extra_vars on launch
+ tower_workflow_job_template:
+ name: "{{ wfjt_name1 }}"
+ state: present
+ ask_variables_on_launch: true
+
+- name: Kick off a workflow with extra_vars
+ tower_workflow_launch:
+ workflow_template: "{{ wfjt_name1 }}"
+ extra_vars:
+ var1: My First Variable
+ var2: My Second Variable
+ ignore_errors: true
+ register: result
+
+- assert:
+ that:
+ - result is not failed
+
+- name: Clean up test workflow
+ tower_workflow_job_template:
+ name: "{{ wfjt_name1 }}"
+ state: absent
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/sanity/ignore-2.10.txt b/collections-debian-merged/ansible_collections/awx/awx/tests/sanity/ignore-2.10.txt
new file mode 100644
index 00000000..76f35a9a
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/sanity/ignore-2.10.txt
@@ -0,0 +1,6 @@
+plugins/modules/tower_receive.py validate-modules:deprecation-mismatch
+plugins/modules/tower_send.py validate-modules:deprecation-mismatch
+plugins/modules/tower_workflow_template.py validate-modules:deprecation-mismatch
+plugins/modules/tower_credential.py pylint:wrong-collection-deprecated-version-tag
+plugins/modules/tower_job_wait.py pylint:wrong-collection-deprecated-version-tag
+plugins/modules/tower_notification.py pylint:wrong-collection-deprecated-version-tag
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tests/sanity/ignore-2.9.txt b/collections-debian-merged/ansible_collections/awx/awx/tests/sanity/ignore-2.9.txt
new file mode 100644
index 00000000..9242eefc
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tests/sanity/ignore-2.9.txt
@@ -0,0 +1,6 @@
+plugins/modules/tower_receive.py validate-modules:deprecation-mismatch
+plugins/modules/tower_receive.py validate-modules:invalid-documentation
+plugins/modules/tower_send.py validate-modules:deprecation-mismatch
+plugins/modules/tower_send.py validate-modules:invalid-documentation
+plugins/modules/tower_workflow_template.py validate-modules:deprecation-mismatch
+plugins/modules/tower_workflow_template.py validate-modules:invalid-documentation
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tools/generate.yml b/collections-debian-merged/ansible_collections/awx/awx/tools/generate.yml
new file mode 100644
index 00000000..4a592ae8
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tools/generate.yml
@@ -0,0 +1,21 @@
+---
+- name: Generate the awx.awx collection
+ hosts: localhost
+ connection: local
+ gather_facts: false
+ vars:
+ api_url: "{{ lookup('env', 'TOWER_HOST') }}"
+ vars_files:
+ - vars/generate_for.yml
+ - vars/aliases.yml
+ - vars/associations.yml
+ - vars/resolution.yml
+ - vars/examples.yml
+ module_defaults:
+ uri:
+ validate_certs: false
+ force_basic_auth: true
+ url_username: "{{ lookup('env', 'TOWER_USERNAME') }}"
+ url_password: "{{ lookup('env', 'TOWER_PASSWORD') }}"
+ roles:
+ - generate
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tools/roles/generate/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tools/roles/generate/tasks/main.yml
new file mode 100644
index 00000000..6a7b4cf6
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tools/roles/generate/tasks/main.yml
@@ -0,0 +1,64 @@
+---
+- name: Get date time data
+ setup:
+ gather_subset: min
+
+- name: Create module directory
+ file:
+ state: directory
+ name: "modules"
+
+- name: Load api/v2
+ uri:
+ method: GET
+ url: "{{ api_url }}/api/v2/"
+ register: endpoints
+
+- name: Load endpoint options
+ uri:
+ method: "OPTIONS"
+ url: "{{ api_url }}{{ item.value }}"
+ loop: "{{ endpoints['json'] | dict2items }}"
+ loop_control:
+ label: "{{ item.key }}"
+ register: end_point_options
+ when: "generate_for is not defined or item.key in generate_for"
+
+- name: Scan POST options for different things
+ set_fact:
+ all_options: "{{ all_options | default({}) | combine(options[0]) }}"
+ loop: "{{ end_point_options.results }}"
+ vars:
+ options: "{{ item | json_query('json.actions.POST.[*]') }}"
+ loop_control:
+ label: "{{ item['item']['key'] }}"
+ when:
+ - item is not skipped
+ - options is defined
+
+- name: Process endpoint
+ template:
+ src: "templates/tower_module.j2"
+ dest: "{{ playbook_dir | dirname }}/plugins/modules/{{ file_name }}"
+ loop: "{{ end_point_options['results'] }}"
+ loop_control:
+ label: "{{ item['item']['key'] }}"
+ when: "'json' in item and 'actions' in item['json'] and 'POST' in item['json']['actions']"
+ vars:
+ item_type: "{{ item['item']['key'] }}"
+ human_readable: "{{ item_type | replace('_', ' ') }}"
+ singular_item_type: "{{ item['item']['key'] | regex_replace('ies$', 'y') | regex_replace('s$', '') }}"
+ file_name: "tower_{% if item['item']['key'] in ['settings'] %}{{ item['item']['key'] }}{% else %}{{ singular_item_type }}{% endif %}.py"
+ type_map:
+ bool: 'bool'
+ boolean: 'bool'
+ choice: 'str'
+ datetime: 'str'
+ id: 'str'
+ int: 'int'
+ integer: 'int'
+ json: 'dict'
+ list: 'list'
+ object: 'dict'
+ password: 'str'
+ string: 'str'
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tools/roles/generate/templates/tower_module.j2 b/collections-debian-merged/ansible_collections/awx/awx/tools/roles/generate/templates/tower_module.j2
new file mode 100644
index 00000000..3606cff5
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tools/roles/generate/templates/tower_module.j2
@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+# coding: utf-8 -*-
+
+{# The following is set by the generate.yml file:
+ # item_type: the type of item i.e. 'teams'
+ # human_readable: the type with _ replaced with spaces i.e. worflow job template
+ # singular_item_type: the type of an item replace singularized i.e. team
+ # type_map: a mapping of things like string to str
+ #}
+{% set name_option = 'username' if item_type == 'users' else 'name' %}
+
+# (c) {{ ansible_date_time['year'] }}, John Westcott IV <john.westcott.iv@redhat.com>
+# 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
+
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: tower_{{ singular_item_type }}
+author: "John Westcott IV (@john-westcott-iv)"
+version_added: "4.0"
+short_description: create, update, or destroy Ansible Tower {{ human_readable }}.
+description:
+ - Create, update, or destroy Ansible Tower {{ human_readable }}. See
+ U(https://www.ansible.com/tower) for an overview.
+options:
+{% for option in item['json']['actions']['POST'] %}
+{# to do: sort documentation options #}
+ {{ option }}:
+ description:
+{% if 'help_text' in item['json']['actions']['POST'][option] %}
+ - {{ item['json']['actions']['POST'][option]['help_text'] }}
+{% else %}
+ - NO DESCRIPTION GIVEN IN THE TOWER API
+{% endif %}
+ required: {{ item['json']['actions']['POST'][option]['required'] }}
+ type: {{ type_map[ item['json']['actions']['POST'][option]['type'] ] }}
+{% if 'default' in item['json']['actions']['POST'][option] %}
+{# for tower_job_template/extra vars, its type is dict but its default is '', so we want to make that {} #}
+{% if item['json']['actions']['POST'][option]['default'] == '' and type_map[ item['json']['actions']['POST'][option]['type'] ] == 'dict' %}
+ default: {}
+{% else %}
+ default: '{{ item['json']['actions']['POST'][option]['default'] }}'
+{% endif %}
+{% endif %}
+{% if 'choices' in item['json']['actions']['POST'][option] %}
+ choices:
+{% for choice in item['json']['actions']['POST'][option]['choices'] %}
+ - '{{ choice[0] }}'
+{% endfor %}
+{%endif %}
+{% if aliases[item_type][option] | default(False) %}
+ aliases:
+{% for alias_name in aliases[item_type][option] %}
+ - {{ alias_name }}
+{% endfor %}
+{% endif %}
+{% if option == name_option %}
+ new_{{ name_option }}:
+ description:
+ - Setting this option will change the existing name (looked up via the {{ name_option }} field.
+ required: True
+ type: str
+{% endif %}
+{% endfor %}
+{% for association in associations[item_type] | default([]) %}
+ {{ association['related_item'] }}:
+ description:
+ - {{ association['description'] }}
+ required: {{ association['required'] }}
+ type: list
+{% endfor %}
+ state:
+ description:
+ - Desired state of the resource.
+ choices: ["present", "absent"]
+ default: "present"
+ type: str
+ tower_oauthtoken:
+ description:
+ - The Tower OAuth token to use.
+ required: False
+ type: str
+extends_documentation_fragment: awx.awx.auth
+'''
+
+EXAMPLES = '''
+{% if examples[item_type] | default(False) %}
+{{ examples[item_type] }}
+{% endif %}
+'''
+
+from ..module_utils.tower_api import TowerAPIModule
+
+
+def main():
+ # Any additional arguments that are not fields of the item can be added here
+ argument_spec = dict(
+{% for option in item['json']['actions']['POST'] %}
+{% set option_data = [] %}
+{{ option_data.append('required={}'.format(item['json']['actions']['POST'][option]['required'])) -}}
+{{ option_data.append('type=\'{}\''.format(type_map[item['json']['actions']['POST'][option]['type']])) -}}
+{% if item['json']['actions']['POST'][option]['type'] == 'password' %}
+{{ option_data.append('no_log=True') -}}
+{% endif %}
+{% if 'choices' in item['json']['actions']['POST'][option] %}
+{% set all_choices = [] %}
+{% for choice in item['json']['actions']['POST'][option]['choices'] %}
+{{ all_choices.append("'{}'".format(choice[0])) -}}
+{% endfor %}
+{{ option_data.append('choices=[{}]'.format(all_choices | join(', '))) -}}
+{% endif %}
+{% if item['json']['actions']['POST'][option].get('default', '') != '' %}
+{% set default_value = item['json']['actions']['POST'][option]['default'] %}
+{% if item['json']['actions']['POST'][option]['default'] == '' and type_map[ item['json']['actions']['POST'][option]['type'] ] == 'dict' %}
+{% set default_value = '{}' %}
+{% endif %}
+{{ option_data.append("default='{}'".format(default_value)) -}}
+{% endif %}
+{% if aliases[item_type][option] | default(False) %}
+{% set alias_list = [] %}
+{% for alias_name in aliases[item_type][option] %}
+{{ alias_list.append("'{}'".format(alias_name)) -}}
+{% endfor %}
+{{ option_data.append('aliases=[{}]'.format(alias_list | join(', '))) -}}
+{% endif %}
+ {{ option }}=dict({{ option_data | join(', ') }}),
+{% if option == name_option %}
+ new_{{ name_option }}=dict(required=False, type='str'),
+{% endif %}
+{% endfor %}
+{% for association_option in associations[item_type] | default([]) %}
+ {{ association_option['related_item'] }}=dict(required={{ association_option['required'] }}, type="list", default=None),
+{% endfor %}
+ state=dict(choices=['present', 'absent'], default='present'),
+ )
+
+ # Create a module for ourselves
+ module = TowerAPIModule(argument_spec=argument_spec)
+
+ # Extract our parameters
+{% for option in item['json']['actions']['POST'] %}
+ {{ option }} = module.params.get('{{ option }}')
+{% if option == name_option %}
+ new_{{ name_option }} = module.params.get("new_{{ name_option }}")
+{% endif %}
+{% endfor %}
+{% for association_option in associations[item_type] | default([]) %}
+ {{ association_option['related_item'] }} = module.params.get('{{ association_option['related_item'] }}')
+{% endfor %}
+ state = module.params.get('state')
+
+{% if item['json']['actions']['POST'] | length() > 0 %}
+ # Attempt to look up the related items the user specified (these will fail the module if not found)
+{% for option in item['json']['actions']['POST'] %}
+{% if item['json']['actions']['POST'][option]['type'] == 'id' %}
+ {{ option }}_id = None
+ if {{ option }}:
+ {{ option }}_id = module.resolve_name_to_id('{{ name_to_id_endpoint_resolution[option] }}', {{ option }})
+{% endif %}
+{% endfor %}
+{% endif %}
+{% for association in associations[item_type] | default([]) %}
+ {{ association['related_item'] }}_ids = None
+ if {{ association['related_item'] }} is not None:
+ {{ association['related_item'] }}_ids = []
+ for item in {{ association['related_item'] }}:
+ {{ association['related_item'] }}_ids.append( module.resolve_name_to_id('{{ association['related_item'] }}', item) )
+{% endfor %}
+
+ # Attempt to look up an existing item based on the provided data
+ existing_item = module.get_one('{{ item_type }}', **{
+ 'data': {
+ '{{ name_option }}': {{ name_option }},
+{% if 'organization' in item['json']['actions']['POST'] and item['json']['actions']['POST']['organization']['type'] == 'id' %}
+ 'organization': org_id,
+{% endif %}
+{% if item_type in ['hosts', 'groups', 'inventory_sources'] %}
+ 'inventory': inventory_id,
+{% endif %}
+ }
+ })
+
+ if state is 'absent':
+ # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
+ module.delete_if_needed(existing_item)
+
+ # Create the data that gets sent for create and update
+ new_fields = {}
+{% for option in item['json']['actions']['POST'] %}
+{% if option == name_option %}
+ new_fields['{{ name_option }}'] = new_{{ name_option }} if new_{{ name_option }} else {{ name_option }}
+{% else %}
+ if {{ option }} is not None:
+{% if item['json']['actions']['POST'][option]['type'] == 'id' %}
+ new_fields['{{ option }}'] = {{ option }}_id
+{% else %}
+ new_fields['{{ option }}'] = {{ option }}
+{% endif %}
+{% endif %}
+{% endfor %}
+
+ # If the state was present and we can let the module build or update the existing item, this will return on its own
+ module.create_or_update_if_needed(
+ existing_item, new_fields,
+ endpoint='{{ item_type }}', item_type='{{ singular_item_type }}',
+ associations={
+{% for association in associations[item_type] | default([]) %}
+ '{{ association['endpoint'] }}': {{ association['related_item'] }}_ids,
+{% endfor %}
+ }
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tools/roles/template_galaxy/tasks/main.yml b/collections-debian-merged/ansible_collections/awx/awx/tools/roles/template_galaxy/tasks/main.yml
new file mode 100644
index 00000000..96eb2641
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tools/roles/template_galaxy/tasks/main.yml
@@ -0,0 +1,56 @@
+---
+- name: Set the collection version in the tower_api.py file
+ replace:
+ path: "{{ collection_path }}/plugins/module_utils/tower_api.py"
+ regexp: '^ _COLLECTION_VERSION = "0.0.1-devel"'
+ replace: ' _COLLECTION_VERSION = "{{ collection_version }}"'
+ when:
+ - "awx_template_version | default(True)"
+
+- name: Set the collection type in the tower_api.py file
+ replace:
+ path: "{{ collection_path }}/plugins/module_utils/tower_api.py"
+ regexp: '^ _COLLECTION_TYPE = "awx"'
+ replace: ' _COLLECTION_TYPE = "{{ collection_package }}"'
+
+- name: Do file content replacements for non-default namespace or package name
+ block:
+
+ - name: Change module doc_fragments to support desired namespace and package names
+ replace:
+ path: "{{ item }}"
+ regexp: '^extends_documentation_fragment: awx.awx.auth([a-zA-Z0-9_]*)$'
+ replace: 'extends_documentation_fragment: {{ collection_namespace }}.{{ collection_package }}.auth\1'
+ with_fileglob:
+ - "{{ collection_path }}/plugins/inventory/*.py"
+ - "{{ collection_path }}/plugins/lookup/*.py"
+ - "{{ collection_path }}/plugins/modules/tower_*.py"
+ loop_control:
+ label: "{{ item | basename }}"
+
+ - name: Change inventory file to support desired namespace and package names
+ replace:
+ path: "{{ collection_path }}/plugins/inventory/tower.py"
+ regexp: "^ NAME = 'awx.awx.tower' # REPLACE$"
+ replace: " NAME = '{{ collection_namespace }}.{{ collection_package }}.tower' # REPLACE"
+
+ - name: Get sanity tests to work with non-default name
+ lineinfile:
+ path: "{{ collection_path }}/tests/sanity/ignore-2.10.txt"
+ state: absent
+ regexp: ' pylint:wrong-collection-deprecated-version-tag$'
+
+ when:
+ - (collection_package != 'awx') or (collection_namespace != 'awx')
+
+- name: Template the galaxy.yml file
+ template:
+ src: "{{ collection_path }}/tools/roles/template_galaxy/templates/galaxy.yml.j2"
+ dest: "{{ collection_path }}/galaxy.yml"
+ force: true
+
+- name: Template the README.md file
+ template:
+ src: "{{ collection_path }}/tools/roles/template_galaxy/templates/README.md.j2"
+ dest: "{{ collection_path }}/README.md"
+ force: true
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tools/roles/template_galaxy/templates/README.md.j2 b/collections-debian-merged/ansible_collections/awx/awx/tools/roles/template_galaxy/templates/README.md.j2
new file mode 100644
index 00000000..53cc8bd0
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tools/roles/template_galaxy/templates/README.md.j2
@@ -0,0 +1,146 @@
+# {% if collection_package | lower() == 'awx' %}AWX{% else %}Tower{% endif %} Ansible Collection
+
+[comment]: # (*******************************************************)
+[comment]: # (* *)
+[comment]: # (* WARNING *)
+[comment]: # (* *)
+[comment]: # (* This file is templated and not to be *)
+[comment]: # (* edited directly! Instead modify: *)
+[comment]: # (* tools/roles/template_galaxy/templates/README.md.j2 *)
+[comment]: # (* *)
+[comment]: # (* Changes to the base README.md file are refreshed *)
+[comment]: # (* upon build of the collection *)
+[comment]: # (*******************************************************)
+
+This Ansible collection allows for easy interaction with an {% if collection_package | lower() == 'awx' %}AWX{% else %}Ansible Tower{% endif %} server via Ansible playbooks.
+
+This source for this collection lives in the `awx_collection` folder inside of the
+AWX source.
+The previous home for this collection was inside the folder [lib/ansible/modules/web_infrastructure/ansible_tower](https://github.com/ansible/ansible/tree/stable-2.9/lib/ansible/modules/web_infrastructure/ansible_tower) in the Ansible repo,
+as well as other places for the inventory plugin, module utils, and
+doc fragment.
+
+## Building and Installing
+
+{% if collection_package | lower() == 'awx' %}
+This collection templates the `galaxy.yml` file it uses.
+Run `make build_collection` from the root folder of the AWX source tree.
+This will create the `tar.gz` file inside the `awx_collection` folder
+with the current AWX version, for example: `awx_collection/awx-awx-9.2.0.tar.gz`.
+
+Installing the `tar.gz` involves no special instructions.
+
+{% else %}
+This collection should be installed from [Content Hub][https://cloud.redhat.com/ansible/automation-hub/ansible/tower/]
+
+{% endif %}
+## Running
+
+Non-deprecated modules in this collection have no Python requirements, but
+may require the official [AWX CLI](https://docs.ansible.com/ansible-tower/latest/html/towercli/index.html)
+in the future. The `DOCUMENTATION` for each module will report this.
+
+You can specify authentication by a combination of either:
+
+ - host, username, password
+ - host, OAuth2 token
+
+The OAuth2 token is the preferred method. You can obtain a token via the
+AWX CLI [login](https://docs.ansible.com/ansible-tower/latest/html/towercli/reference.html#awx-login)
+command.
+
+These can be specified via (from highest to lowest precedence):
+
+ - direct module parameters
+ - environment variables (most useful when running against localhost)
+ - a config file path specified by the `tower_config_file` parameter
+ - a config file at `~/.tower_cli.cfg`
+ - a config file at `/etc/tower/tower_cli.cfg`
+
+Config file syntax looks like this:
+
+```
+[general]
+host = https://localhost:8043
+verify_ssl = true
+oauth_token = LEdCpKVKc4znzffcpQL5vLG8oyeku6
+```
+
+## Release and Upgrade Notes
+
+Notable releases of the `{{ collection_namespace }}.{{ collection_package }}` collection:
+
+{% if collection_package | lower() == "awx" %}
+ - 7.0.0 is intended to be identical to the content prior to the migration, aside from changes necessary to function as a collection.
+ - 11.0.0 has no non-deprecated modules that depend on the deprecated `tower-cli` [PyPI](https://pypi.org/project/ansible-tower-cli/).
+ - 0.0.1-devel is the version you should see if installing from source, which is intended for development and expected to be unstable.
+{% else %}
+ - 3.7.0 initial release
+{% endif %}
+
+The following notes are changes that may require changes to playbooks:
+
+ - When a project is created, it will wait for the update/sync to finish by default; this can be turned off with the `wait` parameter, if desired.
+ - Creating a "scan" type job template is no longer supported.
+ - Specifying a custom certificate via the `TOWER_CERTIFICATE` environment variable no longer works.
+ - Type changes of variable fields:
+
+ - `extra_vars` in the `tower_job_launch` module worked with a `list` previously, but now only works with a `dict` type
+ - `extra_vars` in the `tower_workflow_job_template` module worked with a `string` previously but now expects a `dict`
+ - When the `extra_vars` parameter is used with the `tower_job_launch` module, the launch will fail unless `ask_extra_vars` or `survey_enabled` is explicitly set to `True` on the Job Template
+ - The `variables` parameter in the `tower_group`, `tower_host` and `tower_inventory` modules now expects a `dict` type and no longer supports the use of `@` syntax for a file
+
+
+ - Type changes of other types of fields:
+
+ - `inputs` or `injectors` in the `tower_credential_type` module worked with a string previously but now expects a `dict`
+ - `schema` in the `tower_workflow_job_template` module worked with a `string` previously but not expects a `list` of `dict`s
+
+ - `tower_group` used to also service inventory sources, but this functionality has been removed from this module; use `tower_inventory_source` instead.
+ - Specified `tower_config` file used to handle `k=v` pairs on a single line; this is no longer supported. Please use a file formatted as `yaml`, `json` or `ini` only.
+ - Some return values (e.g., `credential_type`) have been removed. Use of `id` is recommended.
+ - `tower_job_template` no longer supports the deprecated `extra_vars_path` parameter, please use `extra_vars` with the lookup plugin to replace this functionality.
+ - The `notification_configuration` parameter of `tower_notification` has changed from a string to a dict. Please use the `lookup` plugin to read an existing file into a dict.
+ - `tower_credential` no longer supports passing a file name to ssh_key_data.
+ - The HipChat `notification_type` has been removed and can no longer be created using the `tower_notification` module.
+
+{% if collection_package | lower() == "awx" %}
+## Running Unit Tests
+
+Tests to verify compatibility with the most recent AWX code are in `awx_collection/test/awx`.
+These can be ran by `make test_collection` in the development container.
+
+To run outside of the development container, or to run against
+Ansible source, set up a working environment:
+
+```
+mkvirtualenv my_new_venv
+# may need to replace psycopg2 with psycopg2-binary in requirements/requirements.txt
+pip install -r requirements/requirements.txt -r requirements/requirements_dev.txt -r requirements/requirements_git.txt
+make clean-api
+pip install -e <path to your Ansible>
+pip install -e .
+pip install -e awxkit
+py.test awx_collection/test/awx/
+```
+
+## Running Integration Tests
+
+The integration tests require a virtualenv with `ansible` >= 2.9 and `tower_cli`.
+The collection must first be installed, which can be done using `make install_collection`.
+You also need a configuration file, as described in the running section.
+
+Run the tests:
+
+```
+# ansible-test must be run from the directory in which the collection is installed
+cd ~/.ansible/collections/ansible_collections/awx/awx/
+ansible-test integration
+```
+{% endif %}
+
+## Licensing
+
+All content in this folder is licensed under the same license as Ansible,
+which is the same as license that applied before the split into an
+independent collection.
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tools/roles/template_galaxy/templates/galaxy.yml.j2 b/collections-debian-merged/ansible_collections/awx/awx/tools/roles/template_galaxy/templates/galaxy.yml.j2
new file mode 100644
index 00000000..d6e6a272
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tools/roles/template_galaxy/templates/galaxy.yml.j2
@@ -0,0 +1,38 @@
+# (********************************************************)
+# (* *)
+# (* WARNING *)
+# (* *)
+# (* This file is managed by Ansible and not to be *)
+# (* edited directly! Instead modify: *)
+# (* tools/roles/template_galaxy/templates/galaxy.yml.j2 *)
+# (* *)
+# (* Changes to the base galaxy.yml file are refreshed *)
+# (* upon build of the collection *)
+# (********************************************************)
+---
+authors:
+ - AWX Project Contributors <awx-project@googlegroups.com>
+dependencies: {}
+description: Ansible content that interacts with the AWX or Ansible Tower API.
+documentation: https://github.com/ansible/awx/blob/devel/awx_collection/README.md
+homepage: https://www.ansible.com/
+issues: https://github.com/ansible/awx/issues?q=is%3Aissue+label%3Acomponent%3Aawx_collection
+license:
+ - GPL-3.0-only
+name: {{ collection_package }}
+namespace: {{ collection_namespace }}
+readme: README.md
+repository: https://github.com/ansible/awx
+tags:
+ - cloud
+ - infrastructure
+ - awx
+ - ansible
+ - automation
+version: {{ collection_version_override | default(collection_version) }}
+build_ignore:
+ - tools
+ - setup.cfg
+ - galaxy.yml.j2
+ - template_galaxy.yml
+ - '*.tar.gz'
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tools/template_galaxy.yml b/collections-debian-merged/ansible_collections/awx/awx/tools/template_galaxy.yml
new file mode 100644
index 00000000..d2e88fac
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tools/template_galaxy.yml
@@ -0,0 +1,40 @@
+---
+- name: Template the collection galaxy.yml
+ hosts: localhost
+ gather_facts: false
+ connection: local
+ vars:
+ collection_package: awx
+ collection_namespace: awx
+ collection_version: 0.0.1 # not for updating, pass in extra_vars
+ collection_source: "{{ playbook_dir }}/../"
+ collection_path: "{{ playbook_dir }}/../../awx_collection_build"
+ pre_tasks:
+ - file:
+ path: "{{ collection_path }}"
+ state: absent
+
+ - copy:
+ src: "{{ collection_source }}"
+ dest: "{{ collection_path }}"
+ remote_src: true
+
+ roles:
+ - template_galaxy
+
+ tasks:
+ - name: Make substitutions in source to sync with templates
+ set_fact:
+ collection_version_override: 0.0.1-devel
+
+ - name: Template the galaxy.yml source file (should be commited with your changes)
+ template:
+ src: "{{ collection_source }}/tools/roles/template_galaxy/templates/galaxy.yml.j2"
+ dest: "{{ collection_source }}/galaxy.yml"
+ force: true
+
+ - name: Template the README.md source file (should be commited with your changes)
+ template:
+ src: "{{ collection_source }}/tools/roles/template_galaxy/templates/README.md.j2"
+ dest: "{{ collection_source }}/README.md"
+ force: true
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tools/vars/aliases.yml b/collections-debian-merged/ansible_collections/awx/awx/tools/vars/aliases.yml
new file mode 100644
index 00000000..03c7735c
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tools/vars/aliases.yml
@@ -0,0 +1,29 @@
+---
+aliases:
+ job_templates:
+ ask_tags_on_launch:
+ - ask_tags
+ ask_verbosity_on_launch:
+ - ask_verbosity
+ ask_diff_mode_on_launch:
+ - ask_diff_mode
+ allow_simultaneous:
+ - concurrent_jobs_enabled
+ diff_mode:
+ - diff_mode_enabled
+ ask_inventory_on_launch:
+ - ask_inventory
+ limit:
+ - ask_limit
+ force_handlers:
+ - force_handlers_enabled
+ ask_job_type_on_launch:
+ - ask_job_type
+ ask_skip_tags_on_launch:
+ - ask_skip_tags
+ use_fact_cache:
+ - fact_caching_enabled
+ extra_vars:
+ - ask_extra_vars
+ ask_credential_on_launch:
+ - ask_credential
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tools/vars/associations.yml b/collections-debian-merged/ansible_collections/awx/awx/tools/vars/associations.yml
new file mode 100644
index 00000000..9d95bd66
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tools/vars/associations.yml
@@ -0,0 +1,13 @@
+---
+associations:
+ job_templates:
+ - related_item: credentials
+ endpoint: credentials
+ description: "The credentials used by this job template"
+ groups:
+ - related_item: hosts
+ endpoint: hosts
+ description: "The hosts associated with this group"
+ - related_item: groups
+ endpoint: children
+ description: "The hosts associated with this group"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tools/vars/examples.yml b/collections-debian-merged/ansible_collections/awx/awx/tools/vars/examples.yml
new file mode 100644
index 00000000..8a8d1328
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tools/vars/examples.yml
@@ -0,0 +1,52 @@
+---
+examples:
+ users: |
+ - name: Add tower user
+ tower_user:
+ username: jdoe
+ password: foobarbaz
+ email: jdoe@example.org
+ first_name: John
+ last_name: Doe
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+ - name: Add tower user as a system administrator
+ tower_user:
+ username: jdoe
+ password: foobarbaz
+ email: jdoe@example.org
+ superuser: yes
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+ - name: Add tower user as a system auditor
+ tower_user:
+ username: jdoe
+ password: foobarbaz
+ email: jdoe@example.org
+ auditor: yes
+ state: present
+ tower_config_file: "~/tower_cli.cfg"
+
+ - name: Delete tower user
+ tower_user:
+ username: jdoe
+ email: jdoe@example.org
+ state: absent
+ tower_config_file: "~/tower_cli.cfg"
+
+ job_templates: |
+ - name: Create tower Ping job template
+ tower_job_template:
+ name: "Ping"
+ job_type: "run"
+ inventory: "Local"
+ project: "Demo"
+ playbook: "ping.yml"
+ credential: "Local"
+ state: "present"
+ tower_config_file: "~/tower_cli.cfg"
+ survey_enabled: yes
+ survey_spec: "{{ '{{' }} lookup('file', 'my_survey.json') {{ '}}' }}"
+ custom_virtualenv: "/var/lib/awx/venv/custom-venv/"
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tools/vars/generate_for.yml b/collections-debian-merged/ansible_collections/awx/awx/tools/vars/generate_for.yml
new file mode 100644
index 00000000..029024d8
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tools/vars/generate_for.yml
@@ -0,0 +1,16 @@
+---
+generate_for:
+ # - credentials
+ # - credential_types
+ # - groups
+ # - hosts
+ # - inventorues
+ # - inventory_sources
+ # - job_templates
+ # - labels
+ # - notification_templates
+ # - organizations
+ # - projects
+ - schedules
+ # - teams
+ # - users
diff --git a/collections-debian-merged/ansible_collections/awx/awx/tools/vars/resolution.yml b/collections-debian-merged/ansible_collections/awx/awx/tools/vars/resolution.yml
new file mode 100644
index 00000000..fd6ec096
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/awx/awx/tools/vars/resolution.yml
@@ -0,0 +1,6 @@
+---
+name_to_id_endpoint_resolution:
+ webhook_credential: credentials
+ project: projects
+ inventory: inventories
+ organization: organizations