diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
commit | a453ac31f3428614cceb99027f8efbdb9258a40b (patch) | |
tree | f61f87408f32a8511cbd91799f9cececb53e0374 /collections-debian-merged/ansible_collections/openstack/cloud | |
parent | Initial commit. (diff) | |
download | ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.tar.xz ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.zip |
Adding upstream version 2.10.7+merged+base+2.10.8+dfsg.upstream/2.10.7+merged+base+2.10.8+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'collections-debian-merged/ansible_collections/openstack/cloud')
152 files changed, 34153 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/CHANGELOG.rst b/collections-debian-merged/ansible_collections/openstack/cloud/CHANGELOG.rst new file mode 100644 index 00000000..935170e8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/CHANGELOG.rst @@ -0,0 +1,144 @@ +============================================= +Openstack Cloud Ansilbe modules Release Notes +============================================= + +.. contents:: Topics + + +v1.2.1 +====== + +Release Summary +--------------- + +Porting modules to new OpenstackModule class and fixes. + +Minor Changes +------------- + +- dns_zone - Migrating dns_zone from AnsibleModule to OpenStackModule +- dns_zone, recordset - Enable update for recordset and add tests for dns and recordset module +- endpoint - Do not fail when endpoint state is absent +- ironic - Refactor ironic authentication into a new module_utils module +- loadbalancer - Refactor loadbalancer module +- network - Migrating network from AnsibleModule to OpenStackModule +- networks_info - Migrating networks_info from AnsibleModule to OpenStackModule +- openstack - Add galaxy.yml to support install from git +- openstack - Fix docs-args mismatch in modules +- openstack - OpenStackModule Support defining a minimum version of the SDK +- router - Migrating routers from AnsibleModule to OpenStackModule +- routers_info - Added deprecated_names for router_info module +- routers_info - Migrating routers_info from AnsibleModule to OpenStackModule +- security_group.py - Migrating security_group from AnsibleModule to OpenStackModule +- security_group_rule - Refactor TCP/UDP port check +- server.py - Improve "server" module with OpenstackModule class +- server_volume - Migrating server_volume from AnsibleModule to OpenStackModule +- subnet - Fix subnets update and idempotency +- subnet - Migrating subnet module from AnsibleModule to OpenStackModule +- subnets_info - Migrating subnets_info from AnsibleModule to OpenStackModule +- volume.py - Migrating volume from AnsibleModule to OpenStackModule +- volume_info - Fix volume_info arguments for SDK 0.19 + +v1.2.0 +====== + +Release Summary +--------------- + +New volume backup modules. + +Minor Changes +------------- + +- lb_health_monitor - Make it possible to create a health monitor to a pool + +New Modules +----------- + +- openstack.cloud.volume_backup module - Add/Delete Openstack volumes backup. +- openstack.cloud.volume_backup_info module - Retrieve information about Openstack volume backups. +- openstack.cloud.volume_snapshot_info module - Retrieve information about Openstack volume snapshots. + +v1.1.0 +====== + +Release Summary +--------------- + +Starting redesign modules and bugfixes. + +Minor Changes +------------- + +- A basic module subclass was introduced and a few modules moved to inherit from it. +- Add more useful information from exception +- Added pip installation option for collection. +- Added template for generation of artibtrary module. +- baremetal modules - Do not require ironic_url if cloud or auth.endpoint is provided +- inventory_openstack - Add openstack logger and Ansible display utility +- loadbalancer - Add support for setting the Flavor when creating a load balancer + +Bugfixes +-------- + +- Fix non existing attribuites in SDK exception +- security_group_rule - Don't pass tenant_id for remote group + +New Modules +----------- + +- openstack.cloud.volume_info - Retrieve information about Openstack volumes. + +v1.0.1 +====== + +Release Summary +--------------- + +Bugfix for server_info + +Bugfixes +-------- + +- server_info - Fix broken server_info module and add tests + +v1.0.0 +====== + +Release Summary +--------------- + +Initial release of collection. + +Minor Changes +------------- + +- Renaming all modules and removing "os" prefix from names. +- baremetal_node_action - Support json type for the ironic_node config_drive parameter +- config - Update os_client_config to use openstacksdk +- host_aggregate - Add support for not 'purging' missing hosts +- project - Add properties for os_project +- server_action - pass imageRef to rebuild +- subnet - Updated allocation pool checks + +Bugfixes +-------- + +- baremetal_node - Correct parameter name +- coe_cluster - Retrive id/uuid correctly +- federation_mapping - Fixup some minor nits found in followup reviews +- inventory_openstack - Fix constructed compose +- network - Bump minimum openstacksdk version when using os_network/dns_domain +- role_assignment - Fix os_user_role for groups in multidomain context +- role_assignment - Fix os_user_role issue to grant a role in a domain + +New Modules +----------- + +- openstack.cloud.federation_idp - Add support for Keystone Identity Providers +- openstack.cloud.federation_idp_info - Add support for fetching the information about federation IDPs +- openstack.cloud.federation_mapping - Add support for Keystone mappings +- openstack.cloud.federation_mapping_info - Add support for fetching the information about Keystone mappings +- openstack.cloud.keystone_federation_protocol - Add support for Keystone federation Protocols +- openstack.cloud.keystone_federation_protocol_info - Add support for getting information about Keystone federation Protocols +- openstack.cloud.routers_info - Retrieve information about one or more OpenStack routers. diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/CONTRIBUTING.rst b/collections-debian-merged/ansible_collections/openstack/cloud/CONTRIBUTING.rst new file mode 100644 index 00000000..cf632ce3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/CONTRIBUTING.rst @@ -0,0 +1,40 @@ +.. _contributing: + +============================================= +Contributing to ansible-collections-openstack +============================================= + +If you're interested in contributing to the ansible-collections-openstack project, +the following will help get you started. + +Developer Workflow +------------------ + +OpenStack uses OpenDev for it's development, and patches are submitted to +`OpenDev Gerrit`_. Please read `DeveloperWorkflow`_ before sending your +first patch for review. + +Pull requests submitted through GitHub will be ignored. + +.. seealso:: + + * https://wiki.openstack.org/wiki/How_To_Contribute + * https://wiki.openstack.org/wiki/CLA + +.. _OpenDev Gerrit: https://review.opendev.org/ +.. _DeveloperWorkflow: https://docs.openstack.org/infra/manual/developers.html#development-workflow + +Project Hosting Details +----------------------- + +Bug tracker + https://storyboard.openstack.org/#!/project/openstack/ansible-collections-openstack + +Mailing list (prefix subjects with ``[ansible]`` for faster responses) + http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss + +Code Hosting + https://opendev.org/openstack/ansible-collections-openstack + +Code Review + https://review.opendev.org/#/q/status:open+project:openstack/ansible-collections-openstack,n,z diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/COPYING b/collections-debian-merged/ansible_collections/openstack/cloud/COPYING new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/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/openstack/cloud/FILES.json b/collections-debian-merged/ansible_collections/openstack/cloud/FILES.json new file mode 100644 index 00000000..8e3589f2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/FILES.json @@ -0,0 +1,1132 @@ +{ + "files": [ + { + "name": ".", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/config.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "00d7e614bce1059924964290c1f8fc83e418ec8965d252d031dbcd0490754265", + "format": 1 + }, + { + "name": "changelogs/changelog.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cc51ba4c61025a171bd2159c04c8880898b40fc7f52acfa706dd37bbd7e36037", + "format": 1 + }, + { + "name": "docs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "docs/openstack_guidelines.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "44698431a9e28a61bdd0b6642e12546b2f5a69cef1c1aba0c24f1cbbf4870e49", + "format": 1 + }, + { + "name": "meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "meta/runtime.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b46dc46581b96bf1443f9e5ec0df77bb07b328c59d3033160e0edc4b0a3b0e64", + "format": 1 + }, + { + "name": "plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/doc_fragments", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/doc_fragments/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/doc_fragments/openstack.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7f836c14d0a8b23b368ac246350d691aad48986d2d1e0a2b44724df471ffeaa7", + "format": 1 + }, + { + "name": "plugins/inventory", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/inventory/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/inventory/openstack.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "340a0931232d74ab4bc91a710117fb9a3a18e51833adf6c4d1537021bb06653f", + "format": 1 + }, + { + "name": "plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/module_utils/ironic.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c639b9bcd7ceb1f0b0fb8116cb7d7db33ceb2ee957f3b56d173d9146c845b38f", + "format": 1 + }, + { + "name": "plugins/module_utils/openstack.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b726eeb221c5ce9644440bd7e53292a752fe1f78492c5145b86d159bea31f7b4", + "format": 1 + }, + { + "name": "plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/modules/os_auth.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ea07d88b52d583b18da34686fb8d84c447463d8df676ccefff3225cc2c04fe13", + "format": 1 + }, + { + "name": "plugins/modules/os_client_config.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7e0e3782be88d4734a2207cb3047a97ccbb9d1cd8e21d9eae845a667e7731ec8", + "format": 1 + }, + { + "name": "plugins/modules/os_coe_cluster.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bd7f8299bd1503fc6e825c7de0e0dc277269acbd3376b6dedb21032b7d2c78ad", + "format": 1 + }, + { + "name": "plugins/modules/os_coe_cluster_template.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40f9e2b0ff0c97f46a94f40e1427b9bcbc82cd7764a0eb56ea89ada022de162e", + "format": 1 + }, + { + "name": "plugins/modules/os_flavor_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4c49228909164fe4e5c64c085fdb399809c4bf5f4984c1e4d00f49571fc31c30", + "format": 1 + }, + { + "name": "plugins/modules/os_floating_ip.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d3c016cab4add8068308f1377fc47d7a130a9fd933a568b49a46da1e541dc6e3", + "format": 1 + }, + { + "name": "plugins/modules/os_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b0cda597fb26a15388439881a4ac6b54db85a49b8d8ab88492bbb7a60fdbbb57", + "format": 1 + }, + { + "name": "plugins/modules/os_group_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fde4b7e62a9a313f2ab0d3a2ce2c713d05f556b86672532b5f75b5f509c9c825", + "format": 1 + }, + { + "name": "plugins/modules/os_image.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "da1fb0dc25bf693d23b010592e51d401c638646600cadce60b94c814758abe57", + "format": 1 + }, + { + "name": "plugins/modules/os_image_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d85e77db1c0dccc05e6a93aa48a02b8e86693b5b7b4e2e2daa45ed474f9f2cf9", + "format": 1 + }, + { + "name": "plugins/modules/os_ironic.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b452b7262fc5a69f2e5b25ce7888210485bb443e2595dfedca362067fabae73c", + "format": 1 + }, + { + "name": "plugins/modules/os_ironic_inspect.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ff83c3b479131331f3af9366d00ef47328faf1050fb7be44ccb18f9dd02e9f1b", + "format": 1 + }, + { + "name": "plugins/modules/os_ironic_node.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af77101be82c61538b12f94e8ec21b55945fd0a68fa23db280a4bb5afba26c18", + "format": 1 + }, + { + "name": "plugins/modules/os_keypair.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "63b99a4c529d6e1304f804412b64bf75145c133964f6847d3a513dd32db4a31a", + "format": 1 + }, + { + "name": "plugins/modules/os_keystone_domain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fa526366b9f821169b16903bb2174d4fb8d7847d10ccc7ed1c2537dfec456632", + "format": 1 + }, + { + "name": "plugins/modules/os_keystone_domain_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8fc73f4ec14a1335b3b268feea33fa7f74289df653a49b362851620a47c01313", + "format": 1 + }, + { + "name": "plugins/modules/os_keystone_endpoint.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1063555e12d0b850d42114408410f92ec092938fa76d41c3cc988c687986705a", + "format": 1 + }, + { + "name": "plugins/modules/os_keystone_federation_protocol.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "64b0e9056b7e343c72fe83199e687241431b745a934a65269bba61ef3caf173e", + "format": 1 + }, + { + "name": "plugins/modules/os_keystone_federation_protocol_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6f40bb015260482dcdb87dd11d19e3a172a97571fde5686555e939c871507e00", + "format": 1 + }, + { + "name": "plugins/modules/os_keystone_identity_provider.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "06b0d164d719e7f51cf61c925af3d911f16e6ffe274c2e8331157817c87d74f4", + "format": 1 + }, + { + "name": "plugins/modules/os_keystone_identity_provider_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9fe3ccba379c701cdaa9f38ceddf02c639d368eed440b4c25f2027f5657741cb", + "format": 1 + }, + { + "name": "plugins/modules/os_keystone_mapping.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e7812e85195896285799b7d39ce1dde2a580002217e387715f8826a23e57e928", + "format": 1 + }, + { + "name": "plugins/modules/os_keystone_mapping_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "128d60b202e8a482b7bdc522d68cacf7bfa15b193ef0ade7c6d9e33cfa23485d", + "format": 1 + }, + { + "name": "plugins/modules/os_keystone_role.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1051ce847568ed84ff122a03465fada873f1d737412584ffed6eeacc155a4d89", + "format": 1 + }, + { + "name": "plugins/modules/os_keystone_service.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "523c77ebca8f46ad93225113d4b49a1c170d56d654de74ca5a8052c41d97aa70", + "format": 1 + }, + { + "name": "plugins/modules/os_listener.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "92e6a1c4ca971b74cd5da1e94e3968ea2c25e0a18148b54dd8873bf2d5234f1f", + "format": 1 + }, + { + "name": "plugins/modules/os_loadbalancer.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5fd41532921909493d6a432005c461f5c4dcb99a3b1337e04a1f0e2ccd491929", + "format": 1 + }, + { + "name": "plugins/modules/os_member.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "872ea4714b15db66f80662015a0fb22d1d6af2d81f841b07d9e647224fcb7c69", + "format": 1 + }, + { + "name": "plugins/modules/os_network.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "60e4f2066700eb7070c73a6d53cf0adf62d0f81d2214e319d1796f63d03cd576", + "format": 1 + }, + { + "name": "plugins/modules/os_networks_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fff659e1fd85dd5599c60832b31a6904ff6850285f7398b8ac30ea8dc0cb8050", + "format": 1 + }, + { + "name": "plugins/modules/os_nova_flavor.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a04941150f3ba6ec9a4eaf0a60581f1b367297dbbbf48f2699c9dcb4054b47ba", + "format": 1 + }, + { + "name": "plugins/modules/os_nova_host_aggregate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "662c6a6166861b54ac5ef16fb44f8ad16f5ebae23b1136e8fc9330344bcd614d", + "format": 1 + }, + { + "name": "plugins/modules/os_object.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d5d4e061db7042c8fd323b5d8aeb908b6f9968f0ce67c05d8a5d58bd268e59c0", + "format": 1 + }, + { + "name": "plugins/modules/os_pool.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1ef9327adfc017656f9ffaba92586fe258812384dc4bdfad3e02e4c523498863", + "format": 1 + }, + { + "name": "plugins/modules/os_port.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "13ee6e9347cb4b42e96199e2af42462996c112c57df4aea518e0807cba55c1bc", + "format": 1 + }, + { + "name": "plugins/modules/os_port_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ed3e5f04cc2c3353e45a3b6f6c835d64db47bbfc7338173e040e36aab6e5119", + "format": 1 + }, + { + "name": "plugins/modules/os_project.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7168102f5cfc7e9430a1e9643458825e78037afbabe808f79211cd1201c2aa1f", + "format": 1 + }, + { + "name": "plugins/modules/os_project_access.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "efebc1dbcb701754462ab8525322235260f8259c95d738d76d381cd187da5f05", + "format": 1 + }, + { + "name": "plugins/modules/os_project_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "98e7d05452ec48c6189c83c6b9ef7832ecabdf1faf9250c7b6cdbd046446e3f0", + "format": 1 + }, + { + "name": "plugins/modules/os_quota.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "857dd7a5fa3d85f4dd9d024fe9a625a624966eab4657cd784eb460603b2554ec", + "format": 1 + }, + { + "name": "plugins/modules/os_recordset.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1bf710b1e0fb86b2a060ef7aeae734404df9f2851fd47064adc907dab941216e", + "format": 1 + }, + { + "name": "plugins/modules/os_router.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af887cd5aabc05271e3b293d86a595a25a2d1134ab3157bd16c2bf508d513ee6", + "format": 1 + }, + { + "name": "plugins/modules/os_routers_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d74bb1e96a0ee1d637fd443538c371d644e9a9161b988e91188b3439bb6f01b5", + "format": 1 + }, + { + "name": "plugins/modules/os_security_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "754dbf89abfcad4a7a7b9487a19123910f8ea5fde2677d1c209f0a90b0a418b7", + "format": 1 + }, + { + "name": "plugins/modules/os_security_group_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "094585c361b1e8ef843504c9d8391c07432d7c51133e1c434cce9b7d0682831e", + "format": 1 + }, + { + "name": "plugins/modules/os_server.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1d9a1bd4b637f62496e61214c576a2dacff74755f76378f9df733c57c67b7c59", + "format": 1 + }, + { + "name": "plugins/modules/os_server_action.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "033b4c541403ebf63e99ef7634bd449dc0e9bddf122faf369e9140aebe1b3aba", + "format": 1 + }, + { + "name": "plugins/modules/os_server_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c67801f503554b5c0ac3a7dd648640c53b933d77db239db6e280d7c98551c177", + "format": 1 + }, + { + "name": "plugins/modules/os_server_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e4bb24695d2fd27faf4bc379924b35ca1bcfd7eee1b17ebbf4ef8512153df384", + "format": 1 + }, + { + "name": "plugins/modules/os_server_metadata.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bd8caf7915ac64d985f96a47dfe7b68727396cb25ea73e7e0a0710aab1a0207c", + "format": 1 + }, + { + "name": "plugins/modules/os_server_volume.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "da07eb01544ecd9aa6e632c945bb3d9a33ed85a37ac05cbfdea899e40ec8edc9", + "format": 1 + }, + { + "name": "plugins/modules/os_stack.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ce7a1fc037d76c8ae0d2ee545df97aa177c67766d176fd07ec7c47097c0a8d87", + "format": 1 + }, + { + "name": "plugins/modules/os_subnet.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "082102e530a2769b6ff2a223fe0515996f90d87bda3ae51ca035d5445c08efee", + "format": 1 + }, + { + "name": "plugins/modules/os_subnets_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b97cee57bcc3ee8e06efda592e4e8da55a2805249d4b43444f58b41560eacb62", + "format": 1 + }, + { + "name": "plugins/modules/os_user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6104537cbda3cc85d89ddb8bc09a1dd2c294ad8b63975abb6f7138c37e0cbd5b", + "format": 1 + }, + { + "name": "plugins/modules/os_user_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "24c5f0004868028077b5b94af18b901122cab8f1bec07289e5a96749d3e2fe4a", + "format": 1 + }, + { + "name": "plugins/modules/os_user_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3fc7c14b0b4108950ca945a63ddc38d24c7a25db9a7fbd93c006d066016e25b4", + "format": 1 + }, + { + "name": "plugins/modules/os_user_role.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7386128c6564847e746ae2470d7b0191c01bfad2d9d333d01e33fa038ba3bfb4", + "format": 1 + }, + { + "name": "plugins/modules/os_volume.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0d0e34a9979e32c43f4ecc708a0b4743e34d585e2ee1f541429c157f6d26366a", + "format": 1 + }, + { + "name": "plugins/modules/os_volume_snapshot.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1e44ec81b860e41ff143a7e43aaf3c26fc2b795a393a42e929b59b252a41cbd0", + "format": 1 + }, + { + "name": "plugins/modules/os_zone.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9dc86ee13a42726ead899f2ca54decfc9323ae4f2507df2b78c175e3c166d68f", + "format": 1 + }, + { + "name": "plugins/modules/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "plugins/modules/auth.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ea07d88b52d583b18da34686fb8d84c447463d8df676ccefff3225cc2c04fe13", + "format": 1 + }, + { + "name": "plugins/modules/baremetal_inspect.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ff83c3b479131331f3af9366d00ef47328faf1050fb7be44ccb18f9dd02e9f1b", + "format": 1 + }, + { + "name": "plugins/modules/baremetal_node_action.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af77101be82c61538b12f94e8ec21b55945fd0a68fa23db280a4bb5afba26c18", + "format": 1 + }, + { + "name": "plugins/modules/baremetal_node_introspection.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "aedd3aa38df94ba97192dd98b5177709e83d2d8b9ecc137ba0b175f19587cddb", + "format": 1 + }, + { + "name": "plugins/modules/catalog_service.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "523c77ebca8f46ad93225113d4b49a1c170d56d654de74ca5a8052c41d97aa70", + "format": 1 + }, + { + "name": "plugins/modules/coe_cluster.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bd7f8299bd1503fc6e825c7de0e0dc277269acbd3376b6dedb21032b7d2c78ad", + "format": 1 + }, + { + "name": "plugins/modules/coe_cluster_template.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40f9e2b0ff0c97f46a94f40e1427b9bcbc82cd7764a0eb56ea89ada022de162e", + "format": 1 + }, + { + "name": "plugins/modules/compute_flavor.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a04941150f3ba6ec9a4eaf0a60581f1b367297dbbbf48f2699c9dcb4054b47ba", + "format": 1 + }, + { + "name": "plugins/modules/compute_flavor_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4c49228909164fe4e5c64c085fdb399809c4bf5f4984c1e4d00f49571fc31c30", + "format": 1 + }, + { + "name": "plugins/modules/config.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7e0e3782be88d4734a2207cb3047a97ccbb9d1cd8e21d9eae845a667e7731ec8", + "format": 1 + }, + { + "name": "plugins/modules/endpoint.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1063555e12d0b850d42114408410f92ec092938fa76d41c3cc988c687986705a", + "format": 1 + }, + { + "name": "plugins/modules/federation_idp.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "06b0d164d719e7f51cf61c925af3d911f16e6ffe274c2e8331157817c87d74f4", + "format": 1 + }, + { + "name": "plugins/modules/federation_idp_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9fe3ccba379c701cdaa9f38ceddf02c639d368eed440b4c25f2027f5657741cb", + "format": 1 + }, + { + "name": "plugins/modules/federation_mapping.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e7812e85195896285799b7d39ce1dde2a580002217e387715f8826a23e57e928", + "format": 1 + }, + { + "name": "plugins/modules/federation_mapping_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "128d60b202e8a482b7bdc522d68cacf7bfa15b193ef0ade7c6d9e33cfa23485d", + "format": 1 + }, + { + "name": "plugins/modules/floating_ip.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d3c016cab4add8068308f1377fc47d7a130a9fd933a568b49a46da1e541dc6e3", + "format": 1 + }, + { + "name": "plugins/modules/group_assignment.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "24c5f0004868028077b5b94af18b901122cab8f1bec07289e5a96749d3e2fe4a", + "format": 1 + }, + { + "name": "plugins/modules/host_aggregate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "662c6a6166861b54ac5ef16fb44f8ad16f5ebae23b1136e8fc9330344bcd614d", + "format": 1 + }, + { + "name": "plugins/modules/identity_domain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fa526366b9f821169b16903bb2174d4fb8d7847d10ccc7ed1c2537dfec456632", + "format": 1 + }, + { + "name": "plugins/modules/identity_domain_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8fc73f4ec14a1335b3b268feea33fa7f74289df653a49b362851620a47c01313", + "format": 1 + }, + { + "name": "plugins/modules/identity_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b0cda597fb26a15388439881a4ac6b54db85a49b8d8ab88492bbb7a60fdbbb57", + "format": 1 + }, + { + "name": "plugins/modules/identity_group_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fde4b7e62a9a313f2ab0d3a2ce2c713d05f556b86672532b5f75b5f509c9c825", + "format": 1 + }, + { + "name": "plugins/modules/identity_role.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1051ce847568ed84ff122a03465fada873f1d737412584ffed6eeacc155a4d89", + "format": 1 + }, + { + "name": "plugins/modules/identity_user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6104537cbda3cc85d89ddb8bc09a1dd2c294ad8b63975abb6f7138c37e0cbd5b", + "format": 1 + }, + { + "name": "plugins/modules/identity_user_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3fc7c14b0b4108950ca945a63ddc38d24c7a25db9a7fbd93c006d066016e25b4", + "format": 1 + }, + { + "name": "plugins/modules/image.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "da1fb0dc25bf693d23b010592e51d401c638646600cadce60b94c814758abe57", + "format": 1 + }, + { + "name": "plugins/modules/image_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d85e77db1c0dccc05e6a93aa48a02b8e86693b5b7b4e2e2daa45ed474f9f2cf9", + "format": 1 + }, + { + "name": "plugins/modules/keypair.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "63b99a4c529d6e1304f804412b64bf75145c133964f6847d3a513dd32db4a31a", + "format": 1 + }, + { + "name": "plugins/modules/keystone_federation_protocol.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "64b0e9056b7e343c72fe83199e687241431b745a934a65269bba61ef3caf173e", + "format": 1 + }, + { + "name": "plugins/modules/keystone_federation_protocol_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6f40bb015260482dcdb87dd11d19e3a172a97571fde5686555e939c871507e00", + "format": 1 + }, + { + "name": "plugins/modules/lb_health_monitor.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dffb0550c0664f0a63fa853cc327fca37c2c533959dd66535c6e02e1b4b8ad62", + "format": 1 + }, + { + "name": "plugins/modules/lb_listener.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "92e6a1c4ca971b74cd5da1e94e3968ea2c25e0a18148b54dd8873bf2d5234f1f", + "format": 1 + }, + { + "name": "plugins/modules/lb_member.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "872ea4714b15db66f80662015a0fb22d1d6af2d81f841b07d9e647224fcb7c69", + "format": 1 + }, + { + "name": "plugins/modules/lb_pool.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1ef9327adfc017656f9ffaba92586fe258812384dc4bdfad3e02e4c523498863", + "format": 1 + }, + { + "name": "plugins/modules/object.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d5d4e061db7042c8fd323b5d8aeb908b6f9968f0ce67c05d8a5d58bd268e59c0", + "format": 1 + }, + { + "name": "plugins/modules/port.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "13ee6e9347cb4b42e96199e2af42462996c112c57df4aea518e0807cba55c1bc", + "format": 1 + }, + { + "name": "plugins/modules/port_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ed3e5f04cc2c3353e45a3b6f6c835d64db47bbfc7338173e040e36aab6e5119", + "format": 1 + }, + { + "name": "plugins/modules/project.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7168102f5cfc7e9430a1e9643458825e78037afbabe808f79211cd1201c2aa1f", + "format": 1 + }, + { + "name": "plugins/modules/project_access.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "efebc1dbcb701754462ab8525322235260f8259c95d738d76d381cd187da5f05", + "format": 1 + }, + { + "name": "plugins/modules/project_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "98e7d05452ec48c6189c83c6b9ef7832ecabdf1faf9250c7b6cdbd046446e3f0", + "format": 1 + }, + { + "name": "plugins/modules/quota.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "857dd7a5fa3d85f4dd9d024fe9a625a624966eab4657cd784eb460603b2554ec", + "format": 1 + }, + { + "name": "plugins/modules/role_assignment.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7386128c6564847e746ae2470d7b0191c01bfad2d9d333d01e33fa038ba3bfb4", + "format": 1 + }, + { + "name": "plugins/modules/routers_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d74bb1e96a0ee1d637fd443538c371d644e9a9161b988e91188b3439bb6f01b5", + "format": 1 + }, + { + "name": "plugins/modules/server_action.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "033b4c541403ebf63e99ef7634bd449dc0e9bddf122faf369e9140aebe1b3aba", + "format": 1 + }, + { + "name": "plugins/modules/server_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c67801f503554b5c0ac3a7dd648640c53b933d77db239db6e280d7c98551c177", + "format": 1 + }, + { + "name": "plugins/modules/server_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e4bb24695d2fd27faf4bc379924b35ca1bcfd7eee1b17ebbf4ef8512153df384", + "format": 1 + }, + { + "name": "plugins/modules/server_metadata.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bd8caf7915ac64d985f96a47dfe7b68727396cb25ea73e7e0a0710aab1a0207c", + "format": 1 + }, + { + "name": "plugins/modules/stack.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ce7a1fc037d76c8ae0d2ee545df97aa177c67766d176fd07ec7c47097c0a8d87", + "format": 1 + }, + { + "name": "plugins/modules/volume_snapshot.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1e44ec81b860e41ff143a7e43aaf3c26fc2b795a393a42e929b59b252a41cbd0", + "format": 1 + }, + { + "name": "plugins/modules/loadbalancer.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5fd41532921909493d6a432005c461f5c4dcb99a3b1337e04a1f0e2ccd491929", + "format": 1 + }, + { + "name": "plugins/modules/security_group_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "094585c361b1e8ef843504c9d8391c07432d7c51133e1c434cce9b7d0682831e", + "format": 1 + }, + { + "name": "plugins/modules/networks_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fff659e1fd85dd5599c60832b31a6904ff6850285f7398b8ac30ea8dc0cb8050", + "format": 1 + }, + { + "name": "plugins/modules/subnets_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b97cee57bcc3ee8e06efda592e4e8da55a2805249d4b43444f58b41560eacb62", + "format": 1 + }, + { + "name": "plugins/modules/server.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1d9a1bd4b637f62496e61214c576a2dacff74755f76378f9df733c57c67b7c59", + "format": 1 + }, + { + "name": "plugins/modules/baremetal_node.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b452b7262fc5a69f2e5b25ce7888210485bb443e2595dfedca362067fabae73c", + "format": 1 + }, + { + "name": "plugins/modules/volume_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eda8044807ee873641236a1b7f0408127640b28598d24641fe2206dc7eace374", + "format": 1 + }, + { + "name": "plugins/modules/volume.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0d0e34a9979e32c43f4ecc708a0b4743e34d585e2ee1f541429c157f6d26366a", + "format": 1 + }, + { + "name": "plugins/modules/volume_backup.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "01f6678f0c0abebf952b14891074b9f043970bd88e17d984847a0a19ae94d7b8", + "format": 1 + }, + { + "name": "plugins/modules/volume_backup_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "27b7af9d6b95a28a338eb3e886ef9c33ff829f5d2bb0519ba2c260a6144e5ac7", + "format": 1 + }, + { + "name": "plugins/modules/volume_snapshot_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ff1f7d9819ae00bfedb584ae1198d779305b879285d371afcd7ad75a922f59d7", + "format": 1 + }, + { + "name": "plugins/modules/router.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af887cd5aabc05271e3b293d86a595a25a2d1134ab3157bd16c2bf508d513ee6", + "format": 1 + }, + { + "name": "plugins/modules/security_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "754dbf89abfcad4a7a7b9487a19123910f8ea5fde2677d1c209f0a90b0a418b7", + "format": 1 + }, + { + "name": "plugins/modules/server_volume.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "da07eb01544ecd9aa6e632c945bb3d9a33ed85a37ac05cbfdea899e40ec8edc9", + "format": 1 + }, + { + "name": "plugins/modules/network.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "60e4f2066700eb7070c73a6d53cf0adf62d0f81d2214e319d1796f63d03cd576", + "format": 1 + }, + { + "name": "plugins/modules/subnet.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "082102e530a2769b6ff2a223fe0515996f90d87bda3ae51ca035d5445c08efee", + "format": 1 + }, + { + "name": "plugins/modules/dns_zone.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9dc86ee13a42726ead899f2ca54decfc9323ae4f2507df2b78c175e3c166d68f", + "format": 1 + }, + { + "name": "plugins/modules/recordset.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1bf710b1e0fb86b2a060ef7aeae734404df9f2851fd47064adc907dab941216e", + "format": 1 + }, + { + "name": "scripts", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "scripts/inventory", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "scripts/inventory/openstack.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9d2659ec793a078ba38a521ea59eb2c281193c9bde5002da230c76381f71e95d", + "format": 1 + }, + { + "name": "scripts/inventory/openstack_inventory.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "61b541da6c34c659b730b479a4bba2e897da2e1a582c6a42a6073b5dcfca9139", + "format": 1 + }, + { + "name": "CONTRIBUTING.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c5017ba9aff6506564036e816e8bbb7d8d7c9e8acd4a94ffce3c269c51b96ee1", + "format": 1 + }, + { + "name": "COPYING", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ceb4b9ee5adedde47b31e975c1d90c73ad27b6b165a1dcd80c7c545eb65b903", + "format": 1 + }, + { + "name": "setup.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7478e980356fe29366647e7c7d6687b7da68d1ea763d4ca865a75ca9498db1c2", + "format": 1 + }, + { + "name": "test-requirements-2.10.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c3f23178b0b98c1bfaa49c88c8bac6c1122d5910aefafa278e04e0780399d041", + "format": 1 + }, + { + "name": "test-requirements-2.11.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40c8b493f8954dc8e442c2d7df1498c4d28cea01905432233c99d2e806044389", + "format": 1 + }, + { + "name": "test-requirements-2.9.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "117b483f615cc531d99c01d09508c7910a38c733abc0d33fba04a25be0d33378", + "format": 1 + }, + { + "name": "README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "16bd6d87a045fcc8f7b74c3ac4a5cec65b4d00c69918a55f46394aff7eacb5bb", + "format": 1 + }, + { + "name": "CHANGELOG.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "96f6435cb434104aa942f39ddd4cb53b27b28fd86bb2c37c3c74f36922fee5cb", + "format": 1 + } + ], + "format": 1 +}
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/MANIFEST.json b/collections-debian-merged/ansible_collections/openstack/cloud/MANIFEST.json new file mode 100644 index 00000000..56924eae --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/MANIFEST.json @@ -0,0 +1,33 @@ +{ + "collection_info": { + "namespace": "openstack", + "name": "cloud", + "version": "1.2.1", + "authors": [ + "Openstack" + ], + "readme": "README.md", + "tags": [ + "cloud", + "openstack" + ], + "description": "Openstack Ansible modules", + "license": [ + "GPL-3.0-or-later" + ], + "license_file": null, + "dependencies": {}, + "repository": "https://opendev.org/openstack/ansible-collections-openstack", + "documentation": "https://docs.ansible.com/ansible/latest/collections/openstack/cloud/index.html", + "homepage": "https://opendev.org/openstack/ansible-collections-openstack", + "issues": "https://storyboard.openstack.org/#!/project/openstack/ansible-collections-openstack" + }, + "file_manifest_file": { + "name": "FILES.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "972b5df477abfc074531f019a275c0d77f73840c6cadce0d7ef9db39459d2e6b", + "format": 1 + }, + "format": 1 +}
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/README.md b/collections-debian-merged/ansible_collections/openstack/cloud/README.md new file mode 100644 index 00000000..fc9f0b32 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/README.md @@ -0,0 +1,123 @@ +[![OpenDev Zuul Builds - Ansible Collection OpenStack](https://zuul-ci.org/gated.svg)](http://zuul.opendev.org/t/openstack/builds?project=openstack%2Fansible-collections-openstack#) + +# Ansible Collection: openstack.cloud + + +This repo hosts the `openstack.cloud` Ansible Collection. + +The collection includes the Openstack modules and plugins supported by Openstack community to help the management of Openstack infrastructure. + +## Installation and Usage + +### Installing dependencies + +For using Openstack Cloud collection firstly need to install `ansible` and `openstacksdk` Python modules on Ansible controller. +For example with pip: + +```bash +pip install ansible openstacksdk +``` + +OpenStackSDK has to be available to Ansible and to the Python interpreter on the host, where Ansible executes the module (target host). +Please note, that under some circumstances Ansible might invoke not standard Python interpreter on the target host. +Using Python verison 3 is highly recommended for OpenstackSDK and strongly required from OpenstackSDK version 0.39.0. + +--- + +#### NOTE + +OpenstackSDK is better to be the last stable version. It should NOT be installed on Openstack nodes, +but rather on operators host (aka "Ansible controller"). OpenstackSDK from last version supports +operations on all Openstack cloud versions. Therefore OpenstackSDK module version doesn't have to match +Openstack cloud version usually. + +--- + +### Installing the Collection from Ansible Galaxy + +Before using the Openstack Cloud collection, you need to install the collection with the `ansible-galaxy` CLI: + +`ansible-galaxy collection install openstack.cloud` + +You can also include it in a `requirements.yml` file and install it via `ansible-galaxy collection install -r requirements.yml` using the format: + +```yaml +collections: +- name: openstack.cloud +``` + +### Playbooks + +To use a module from Openstack Cloud collection, please reference the full namespace, collection name, and modules name that you want to use: + +```yaml +--- +- name: Using Openstack Cloud collection + hosts: localhost + tasks: + - openstack.cloud.server: + name: vm + state: present + cloud: openstack + region_name: ams01 + image: Ubuntu Server 14.04 + flavor_ram: 4096 + boot_from_volume: True + volume_size: 75 +``` + +Or you can add full namepsace and collecton name in the `collections` element: + +```yaml +--- +- name: Using Openstack Cloud collection + hosts: localhost + collections: + - openstack.cloud + tasks: + - server_volume: + state: present + cloud: openstack + server: Mysql-server + volume: mysql-data + device: /dev/vdb +``` + +## Contributing + +For information on contributing, please see [CONTRIBUTING](https://opendev.org/openstack/ansible-collections-openstack/src/branch/master/CONTRIBUTING.rst) + +There are many ways in which you can participate in the project, for example: + +- Submit [bugs and feature requests](https://storyboard.openstack.org/#!/project/openstack/ansible-collections-openstack), and help us verify them +- Submit and review source code changes in [Openstack Gerrit](https://review.opendev.org/#/q/project:openstack/ansible-collections-openstack) +- Add new modules for Openstack Cloud + +We work with [OpenDev Gerrit](https://review.opendev.org/), pull requests submitted through GitHub will be ignored. + +## Testing and Development + +If you want to develop new content for this collection or improve what is already here, the easiest way to work on the collection is to clone it into one of the configured [`COLLECTIONS_PATHS`](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths), and work on it there. + +### Testing with `ansible-test` + +We use `ansible-test` for sanity: + +```bash +tox -e linters +``` + +## More Information + +TBD + +## Communication + +We have a dedicated Interest Group for Openstack Ansible modules. +You can find other people interested in this in `#openstack-ansible-sig` on Freenode IRC. + +## License + +GNU General Public License v3.0 or later + +See [LICENCE](https://opendev.org/openstack/ansible-collections-openstack/src/branch/master/COPYING) to see the full text. diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/changelogs/changelog.yaml b/collections-debian-merged/ansible_collections/openstack/cloud/changelogs/changelog.yaml new file mode 100644 index 00000000..209317b0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/changelogs/changelog.yaml @@ -0,0 +1,111 @@ +releases: + 1.2.1: + release_date: '2021-01-03' + changes: + release_summary: Porting modules to new OpenstackModule class and fixes. + minor_changes: + - dns_zone, recordset - Enable update for recordset and add tests for dns and recordset module + - loadbalancer - Refactor loadbalancer module + - openstack - OpenStackModule Support defining a minimum version of the SDK + - ironic - Refactor ironic authentication into a new module_utils module + - endpoint - Do not fail when endpoint state is absent + - subnet - Fix subnets update and idempotency + - volume_info - Fix volume_info arguments for SDK 0.19 + - routers_info - Added deprecated_names for router_info module + - security_group_rule - Refactor TCP/UDP port check + - openstack - Fix docs-args mismatch in modules + - security_group.py - Migrating security_group from AnsibleModule to OpenStackModule + - router - Migrating routers from AnsibleModule to OpenStackModule + - server_volume - Migrating server_volume from AnsibleModule to OpenStackModule + - dns_zone - Migrating dns_zone from AnsibleModule to OpenStackModule + - network - Migrating network from AnsibleModule to OpenStackModule + - routers_info - Migrating routers_info from AnsibleModule to OpenStackModule + - networks_info - Migrating networks_info from AnsibleModule to OpenStackModule + - subnets_info - Migrating subnets_info from AnsibleModule to OpenStackModule + - server.py - Improve "server" module with OpenstackModule class + - volume.py - Migrating volume from AnsibleModule to OpenStackModule + - subnet - Migrating subnet module from AnsibleModule to OpenStackModule + - openstack - Add galaxy.yml to support install from git + 1.2.0: + release_date: '2020-10-13' + changes: + release_summary: New volume backup modules. + minor_changes: + - lb_health_monitor - Make it possible to create a health monitor to a pool + modules: + - name: volume_snapshot_info module + description: Retrieve information about Openstack volume snapshots. + namespace: '' + - name: volume_backup_info module + description: Retrieve information about Openstack volume backups. + namespace: '' + - name: volume_backup module + description: Add/Delete Openstack volumes backup. + namespace: '' + 1.1.0: + release_date: '2020-08-17' + changes: + release_summary: Starting redesign modules and bugfixes. + minor_changes: + - A basic module subclass was introduced and a few modules moved to inherit from it. + - Added pip installation option for collection. + - Added template for generation of artibtrary module. + - Add more useful information from exception + - inventory_openstack - Add openstack logger and Ansible display utility + - loadbalancer - Add support for setting the Flavor when creating a load balancer + - baremetal modules - Do not require ironic_url if cloud or auth.endpoint is provided + bugfixes: + - security_group_rule - Don't pass tenant_id for remote group + - Fix non existing attribuites in SDK exception + modules: + - name: volume_info + description: Retrieve information about Openstack volumes. + namespace: '' + 1.0.1: + release_date: '2020-05-22' + changes: + release_summary: Bugfix for server_info + bugfixes: + - server_info - Fix broken server_info module and add tests + 1.0.0: + release_date: '2020-05-19' + changes: + release_summary: Initial release of collection. + minor_changes: + - Renaming all modules and removing "os" prefix from names. + - server_action - pass imageRef to rebuild + - subnet - Updated allocation pool checks + - project - Add properties for os_project + - config - Update os_client_config to use openstacksdk + - host_aggregate - Add support for not 'purging' missing hosts + - baremetal_node_action - Support json type for the ironic_node config_drive parameter + bugfixes: + - federation_mapping - Fixup some minor nits found in followup reviews + - coe_cluster - Retrive id/uuid correctly + - baremetal_node - Correct parameter name + - network - Bump minimum openstacksdk version when using os_network/dns_domain + - inventory_openstack - Fix constructed compose + - role_assignment - Fix os_user_role for groups in multidomain context + - role_assignment - Fix os_user_role issue to grant a role in a domain + modules: + - name: keystone_federation_protocol_info + description: Add support for getting information about Keystone federation Protocols + namespace: '' + - name: keystone_federation_protocol + description: Add support for Keystone federation Protocols + namespace: '' + - name: federation_idp_info + description: Add support for fetching the information about federation IDPs + namespace: '' + - name: federation_idp + description: Add support for Keystone Identity Providers + namespace: '' + - name: federation_mapping_info + description: Add support for fetching the information about Keystone mappings + namespace: '' + - name: federation_mapping + description: Add support for Keystone mappings + namespace: '' + - name: routers_info + description: Retrieve information about one or more OpenStack routers. + namespace: '' diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/changelogs/config.yaml b/collections-debian-merged/ansible_collections/openstack/cloud/changelogs/config.yaml new file mode 100644 index 00000000..d57bf838 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/changelogs/config.yaml @@ -0,0 +1,31 @@ +changelog_filename_template: ../CHANGELOG.rst +changelog_filename_version_depth: 0 +changes_file: changelog.yaml +changes_format: combined +ignore_other_fragment_extensions: true +keep_fragments: false +mention_ancestor: true +new_plugins_after_name: removed_features +notesdir: fragments +prelude_section_name: release_summary +prelude_section_title: Release Summary +sections: +- - major_changes + - Major Changes +- - minor_changes + - Minor Changes +- - breaking_changes + - Breaking Changes / Porting Guide +- - deprecated_features + - Deprecated Features +- - removed_features + - Removed Features (previously deprecated) +- - security_fixes + - Security Fixes +- - bugfixes + - Bugfixes +- - known_issues + - Known Issues +title: Openstack Cloud Ansilbe modules +trivial_section_name: trivial +use_fqcn: true diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/docs/openstack_guidelines.rst b/collections-debian-merged/ansible_collections/openstack/cloud/docs/openstack_guidelines.rst new file mode 100644 index 00000000..ac90eb0f --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/docs/openstack_guidelines.rst @@ -0,0 +1,67 @@ +.. _OpenStack_module_development: + +OpenStack Ansible Modules +========================= + +These are a set of modules for interacting with the OpenStack API as either an admin +or an end user. + +.. contents:: + :local: + +Naming +------ + +* This is a collection named ``openstack.cloud``. There is no need for further namespace prefixing. +* Name any module that a cloud consumer would expect to use after the logical resource it manages: + ``server`` not ``nova``. This naming convention acknowledges that the end user does not care + which service manages the resource - that is a deployment detail. For example cloud consumers may + not know whether their floating IPs are managed by Nova or Neutron. + +Interface +--------- + +* If the resource being managed has an id, it should be returned. +* If the resource being managed has an associated object more complex than + an id, it should also be returned. + +Interoperability +---------------- + +* It should be assumed that the cloud consumer does not know + details about the deployment choices their cloud provider made. A best + effort should be made to present one sane interface to the Ansible user + regardless of deployer choices. +* It should be assumed that a user may have more than one cloud account that + they wish to combine as part of a single Ansible-managed infrastructure. +* All modules should work appropriately against all existing versions of + OpenStack regardless of upstream EOL status. The reason for this is that + the Ansible modules are for consumers of cloud APIs who are not in a + position to impact what version of OpenStack their cloud provider is + running. It is known that there are OpenStack Public Clouds running rather + old versions of OpenStack, but from a user point of view the Ansible + modules can still support these users without impacting use of more + modern versions. + +Libraries +--------- + +* All modules should use ``OpenStackModule`` from + ``ansible_collections.openstack.cloud.plugins.module_utils.openstack`` + as their base class. +* All modules should include ``extends_documentation_fragment: openstack``. +* All complex cloud interaction or interoperability code should be housed in + the `openstacksdk <https://opendev.org/openstack/openstacksdk>`_ + library. +* All OpenStack API interactions should happen via the openstacksdk and not via + OpenStack Client libraries. The OpenStack Client libraries do no have end + users as a primary audience, they are for intra-server communication. +* All modules should be registered in ``meta/action_groups.yml`` for enabling the + variables to be set in `group level + <https://docs.ansible.com/ansible/latest/user_guide/playbooks_module_defaults.html>`. + +Testing +------- + +* Integration testing is currently done in `OpenStack's CI system + <https://opendev.org/openstack/ansible-collections-openstack/src/branch/master/zuul.yaml>` diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/meta/runtime.yml b/collections-debian-merged/ansible_collections/openstack/cloud/meta/runtime.yml new file mode 100644 index 00000000..496a335d --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/meta/runtime.yml @@ -0,0 +1,596 @@ +action_groups: + openstack: + - auth + - baremetal_inspect + - baremetal_inspect + - baremetal_node + - baremetal_node + - baremetal_node_action + - baremetal_node_action + - catalog_endpoint + - catalog_service + - catalog_service + - coe_cluster + - coe_cluster_template + - compute_flavor + - compute_flavor + - compute_flavor + - compute_flavor_info + - compute_flavor_info + - config + - config + - dns_zone + - dns_zone + - endpoint + - endpoint + - federation_idp + - federation_idp + - federation_idp_info + - federation_idp_info + - federation_mapping + - federation_mapping + - federation_mapping_info + - federation_mapping_info + - floating_ip + - group_assignment + - group_assignment + - host_aggregate + - host_aggregate + - identity_domain + - identity_domain + - identity_domain_info + - identity_domain_info + - identity_group + - identity_group + - identity_group_info + - identity_group_info + - identity_role + - identity_role + - identity_user + - identity_user + - identity_user_info + - identity_user_info + - image + - image_info + - keypair + - keystone_federation_protocol + - keystone_federation_protocol_info + - lb_listener + - lb_listener + - lb_member + - lb_member + - lb_pool + - lb_pool + - loadbalancer + - network + - networks_info + - object + - port + - port_info + - project + - project_access + - project_info + - quota + - recordset + - role_assignment + - role_assignment + - router + - routers_info + - security_group + - security_group_rule + - server + - server_action + - server_group + - server_info + - server_metadata + - server_volume + - stack + - subnet + - subnets_info + - volume + - volume_backup + - volume_backup_info + - volume_info + - volume_snapshot + - volume_snapshot_info + os: + - auth + - baremetal_inspect + - baremetal_inspect + - baremetal_node + - baremetal_node + - baremetal_node_action + - baremetal_node_action + - catalog_endpoint + - catalog_service + - catalog_service + - coe_cluster + - coe_cluster_template + - compute_flavor + - compute_flavor + - compute_flavor + - compute_flavor_info + - compute_flavor_info + - config + - config + - dns_zone + - dns_zone + - endpoint + - endpoint + - federation_idp + - federation_idp + - federation_idp_info + - federation_idp_info + - federation_mapping + - federation_mapping + - federation_mapping_info + - federation_mapping_info + - floating_ip + - group_assignment + - group_assignment + - host_aggregate + - host_aggregate + - identity_domain + - identity_domain + - identity_domain_info + - identity_domain_info + - identity_group + - identity_group + - identity_group_info + - identity_group_info + - identity_role + - identity_role + - identity_user + - identity_user + - identity_user_info + - identity_user_info + - image + - image_info + - keypair + - keystone_federation_protocol + - keystone_federation_protocol_info + - lb_listener + - lb_listener + - lb_member + - lb_member + - lb_pool + - lb_pool + - loadbalancer + - network + - networks_info + - object + - port + - port_info + - project + - project_access + - project_info + - quota + - recordset + - role_assignment + - role_assignment + - router + - routers_info + - security_group + - security_group_rule + - server + - server_action + - server_group + - server_info + - server_metadata + - server_volume + - stack + - subnet + - subnets_info + - volume + - volume_backup + - volume_backup_info + - volume_info + - volume_snapshot + - volume_snapshot_info + - os_auth + - os_client_config + - os_client_config + - os_coe_cluster + - os_coe_cluster_template + - os_endpoint + - os_flavor + - os_flavor_info + - os_flavor_info + - os_floating_ip + - os_group + - os_group + - os_group_info + - os_group_info + - os_image + - os_image_info + - os_ironic + - os_ironic + - os_ironic_inspect + - os_ironic_inspect + - os_ironic_node + - os_ironic_node + - os_keypair + - os_keystone_domain + - os_keystone_domain + - os_keystone_domain_info + - os_keystone_domain_info + - os_keystone_endpoint + - os_keystone_endpoint + - os_keystone_federation_protocol + - os_keystone_federation_protocol_info + - os_keystone_identity_provider + - os_keystone_identity_provider + - os_keystone_identity_provider_info + - os_keystone_identity_provider_info + - os_keystone_mapping + - os_keystone_mapping + - os_keystone_mapping_info + - os_keystone_mapping_info + - os_keystone_role + - os_keystone_role + - os_keystone_service + - os_keystone_service + - os_listener + - os_listener + - os_loadbalancer + - os_member + - os_member + - os_network + - os_networks_info + - os_nova_flavor + - os_nova_flavor + - os_nova_host_aggregate + - os_nova_host_aggregate + - os_object + - os_pool + - os_pool + - os_port + - os_port_info + - os_project + - os_project_access + - os_project_info + - os_quota + - os_recordset + - os_router + - os_routers_info + - os_security_group + - os_security_group_rule + - os_server + - os_server_action + - os_server_group + - os_server_info + - os_server_metadata + - os_server_volume + - os_stack + - os_subnet + - os_subnets_info + - os_user + - os_user + - os_user_group + - os_user_group + - os_user_info + - os_user_info + - os_user_role + - os_user_role + - os_volume + - os_volume_snapshot + - os_zone + - os_zone + +plugin_routing: + modules: + os_auth: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.auth + redirect: openstack.cloud.auth + os_client_config: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.config + redirect: openstack.cloud.config + os_coe_cluster: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.coe_cluster + redirect: openstack.cloud.coe_cluster + os_coe_cluster_template: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.coe_cluster_template + redirect: openstack.cloud.coe_cluster_template + os_endpoint: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.catalog_endpoint + redirect: openstack.cloud.catalog_endpoint + os_flavor: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.compute_flavor + redirect: openstack.cloud.compute_flavor + os_flavor_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.compute_flavor_info + redirect: openstack.cloud.compute_flavor_info + os_floating_ip: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.floating_ip + redirect: openstack.cloud.floating_ip + os_group: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.identity_group + redirect: openstack.cloud.identity_group + os_group_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.identity_group_info + redirect: openstack.cloud.identity_group_info + os_image: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.image + redirect: openstack.cloud.image + os_image_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.image_info + redirect: openstack.cloud.image_info + os_ironic: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.baremetal_node + redirect: openstack.cloud.baremetal_node + os_ironic_inspect: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.baremetal_inspect + redirect: openstack.cloud.baremetal_inspect + os_ironic_node: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.baremetal_node_action + redirect: openstack.cloud.baremetal_node_action + os_keypair: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.keypair + redirect: openstack.cloud.keypair + os_keystone_domain: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.identity_domain + redirect: openstack.cloud.identity_domain + os_keystone_domain_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.identity_domain_info + redirect: openstack.cloud.identity_domain_info + os_keystone_endpoint: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.endpoint + redirect: openstack.cloud.endpoint + os_keystone_federation_protocol: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.keystone_federation_protocol + redirect: openstack.cloud.keystone_federation_protocol + os_keystone_federation_protocol_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.keystone_federation_protocol_info + redirect: openstack.cloud.keystone_federation_protocol_info + os_keystone_identity_provider: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.federation_idp + redirect: openstack.cloud.federation_idp + os_keystone_identity_provider_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.federation_idp_info + redirect: openstack.cloud.federation_idp_info + os_keystone_mapping: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.federation_mapping + redirect: openstack.cloud.federation_mapping + os_keystone_mapping_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.federation_mapping_info + redirect: openstack.cloud.federation_mapping_info + os_keystone_role: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.identity_role + redirect: openstack.cloud.identity_role + os_keystone_service: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.catalog_service + redirect: openstack.cloud.catalog_service + os_listener: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.lb_listener + redirect: openstack.cloud.lb_listener + os_loadbalancer: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.loadbalancer + redirect: openstack.cloud.loadbalancer + os_member: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.lb_member + redirect: openstack.cloud.lb_member + os_network: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.network + redirect: openstack.cloud.network + os_networks_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.networks_info + redirect: openstack.cloud.networks_info + os_nova_flavor: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.compute_flavor + redirect: openstack.cloud.compute_flavor + os_nova_host_aggregate: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.host_aggregate + redirect: openstack.cloud.host_aggregate + os_object: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.object + redirect: openstack.cloud.object + os_pool: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.lb_pool + redirect: openstack.cloud.lb_pool + os_port: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.port + redirect: openstack.cloud.port + os_port_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.port_info + redirect: openstack.cloud.port_info + os_project: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.project + redirect: openstack.cloud.project + os_project_access: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.project_access + redirect: openstack.cloud.project_access + os_project_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.project_info + redirect: openstack.cloud.project_info + os_quota: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.quota + redirect: openstack.cloud.quota + os_recordset: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.recordset + redirect: openstack.cloud.recordset + os_router: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.router + redirect: openstack.cloud.router + os_routers_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.routers_info + redirect: openstack.cloud.routers_info + os_security_group: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.security_group + redirect: openstack.cloud.security_group + os_security_group_rule: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.security_group_rule + redirect: openstack.cloud.security_group_rule + os_server: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.server + redirect: openstack.cloud.server + os_server_action: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.server_action + redirect: openstack.cloud.server_action + os_server_group: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.server_group + redirect: openstack.cloud.server_group + os_server_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.server_info + redirect: openstack.cloud.server_info + os_server_metadata: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.server_metadata + redirect: openstack.cloud.server_metadata + os_server_volume: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.server_volume + redirect: openstack.cloud.server_volume + os_stack: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.stack + redirect: openstack.cloud.stack + os_subnet: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.subnet + redirect: openstack.cloud.subnet + os_subnets_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.subnets_info + redirect: openstack.cloud.subnets_info + os_user: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.identity_user + redirect: openstack.cloud.identity_user + os_user_group: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.group_assignment + redirect: openstack.cloud.group_assignment + os_user_info: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.identity_user_info + redirect: openstack.cloud.identity_user_info + os_user_role: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.role_assignment + redirect: openstack.cloud.role_assignment + os_volume: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.volume + redirect: openstack.cloud.volume + os_volume_snapshot: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.volume_snapshot + redirect: openstack.cloud.volume_snapshot + os_zone: + deprecation: + removal_date: 2021-12-12 + warning_text: os_ prefixed module names are deprecated, use openstack.cloud.dns_zone + redirect: openstack.cloud.dns_zone diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/doc_fragments/__init__.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/doc_fragments/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/doc_fragments/__init__.py diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/doc_fragments/openstack.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/doc_fragments/openstack.py new file mode 100644 index 00000000..680059b9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/doc_fragments/openstack.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2014, Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +class ModuleDocFragment(object): + + # Standard openstack documentation fragment + DOCUMENTATION = r''' +options: + cloud: + description: + - Named cloud or cloud config to operate against. + If I(cloud) is a string, it references a named cloud config as defined + in an OpenStack clouds.yaml file. Provides default values for I(auth) + and I(auth_type). This parameter is not needed if I(auth) is provided + or if OpenStack OS_* environment variables are present. + If I(cloud) is a dict, it contains a complete cloud configuration like + would be in a section of clouds.yaml. + type: raw + auth: + description: + - Dictionary containing auth information as needed by the cloud's auth + plugin strategy. For the default I(password) plugin, this would contain + I(auth_url), I(username), I(password), I(project_name) and any + information about domains (for example, I(user_domain_name) or + I(project_domain_name)) if the cloud supports them. + For other plugins, + this param will need to contain whatever parameters that auth plugin + requires. This parameter is not needed if a named cloud is provided or + OpenStack OS_* environment variables are present. + type: dict + auth_type: + description: + - Name of the auth plugin to use. If the cloud uses something other than + password authentication, the name of the plugin should be indicated here + and the contents of the I(auth) parameter should be updated accordingly. + type: str + region_name: + description: + - Name of the region. + type: str + wait: + description: + - Should ansible wait until the requested resource is complete. + type: bool + default: yes + timeout: + description: + - How long should ansible wait for the requested resource. + type: int + default: 180 + api_timeout: + description: + - How long should the socket layer wait before timing out for API calls. + If this is omitted, nothing will be passed to the requests library. + type: int + validate_certs: + description: + - Whether or not SSL API requests should be verified. + - Before Ansible 2.3 this defaulted to C(yes). + type: bool + default: False + aliases: [ verify ] + ca_cert: + description: + - A path to a CA Cert bundle that can be used as part of verifying + SSL API requests. + type: str + aliases: [ cacert ] + client_cert: + description: + - A path to a client certificate to use as part of the SSL transaction. + type: str + aliases: [ cert ] + client_key: + description: + - A path to a client key to use as part of the SSL transaction. + type: str + aliases: [ key ] + interface: + description: + - Endpoint URL type to fetch from the service catalog. + type: str + choices: [ admin, internal, public ] + default: public + aliases: [ endpoint_type ] + availability_zone: + description: + - Ignored. Present for backwards compatibility + type: str +requirements: + - python >= 3.6 + - openstacksdk >= 0.12.0 +notes: + - The standard OpenStack environment variables, such as C(OS_USERNAME) + may be used instead of providing explicit values. + - Auth information is driven by openstacksdk, which means that values + can come from a yaml config file in /etc/ansible/openstack.yaml, + /etc/openstack/clouds.yaml or ~/.config/openstack/clouds.yaml, then from + standard environment variables, then finally by explicit parameters in + plays. More information can be found at + U(https://docs.openstack.org/openstacksdk/) +''' diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/inventory/__init__.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/inventory/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/inventory/__init__.py diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/inventory/openstack.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/inventory/openstack.py new file mode 100644 index 00000000..183e898c --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/inventory/openstack.py @@ -0,0 +1,376 @@ +# Copyright (c) 2012, Marco Vito Moscaritolo <marco@agavee.com> +# Copyright (c) 2013, Jesse Keating <jesse.keating@rackspace.com> +# Copyright (c) 2015, Hewlett-Packard Development Company, L.P. +# Copyright (c) 2016, Rackspace Australia +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +DOCUMENTATION = ''' +--- +name: openstack +plugin_type: inventory +author: OpenStack Ansible SIG +short_description: OpenStack inventory source +requirements: + - "openstacksdk >= 0.28" +description: + - Get inventory hosts from OpenStack clouds + - Uses openstack.(yml|yaml) YAML configuration file to configure the inventory plugin + - Uses standard clouds.yaml YAML configuration file to configure cloud credentials +options: + plugin: + description: token that ensures this is a source file for the 'openstack' plugin. + required: True + choices: ['openstack'] + show_all: + description: toggles showing all vms vs only those with a working IP + type: bool + default: 'no' + inventory_hostname: + description: | + What to register as the inventory hostname. + If set to 'uuid' the uuid of the server will be used and a + group will be created for the server name. + If set to 'name' the name of the server will be used unless + there are more than one server with the same name in which + case the 'uuid' logic will be used. + Default is to do 'name', which is the opposite of the old + openstack.py inventory script's option use_hostnames) + type: string + choices: + - name + - uuid + default: "name" + expand_hostvars: + description: | + Run extra commands on each host to fill in additional + information about the host. May interrogate cinder and + neutron and can be expensive for people with many hosts. + (Note, the default value of this is opposite from the default + old openstack.py inventory script's option expand_hostvars) + type: bool + default: 'no' + private: + description: | + Use the private interface of each server, if it has one, as + the host's IP in the inventory. This can be useful if you are + running ansible inside a server in the cloud and would rather + communicate to your servers over the private network. + type: bool + default: 'no' + only_clouds: + description: | + List of clouds from clouds.yaml to use, instead of using + the whole list. + type: list + default: [] + fail_on_errors: + description: | + Causes the inventory to fail and return no hosts if one cloud + has failed (for example, bad credentials or being offline). + When set to False, the inventory will return as many hosts as + it can from as many clouds as it can contact. (Note, the + default value of this is opposite from the old openstack.py + inventory script's option fail_on_errors) + type: bool + default: 'no' + all_projects: + description: | + Lists servers from all projects + type: bool + default: 'no' + clouds_yaml_path: + description: | + Override path to clouds.yaml file. If this value is given it + will be searched first. The default path for the + ansible inventory adds /etc/ansible/openstack.yaml and + /etc/ansible/openstack.yml to the regular locations documented + at https://docs.openstack.org/os-client-config/latest/user/configuration.html#config-files + type: list + env: + - name: OS_CLIENT_CONFIG_FILE + compose: + description: Create vars from jinja2 expressions. + type: dictionary + default: {} + groups: + description: Add hosts to group based on Jinja2 conditionals. + type: dictionary + default: {} + +extends_documentation_fragment: +- inventory_cache +- constructed + +''' + +EXAMPLES = ''' +# file must be named openstack.yaml or openstack.yml +# Make the plugin behave like the default behavior of the old script +plugin: openstack +expand_hostvars: yes +fail_on_errors: yes +all_projects: yes +''' + +import collections +import sys +import logging + +from ansible.errors import AnsibleParserError +from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable +from ansible.utils.display import Display + +display = Display() +os_logger = logging.getLogger("openstack") + +try: + # Due to the name shadowing we should import other way + import importlib + sdk = importlib.import_module('openstack') + sdk_inventory = importlib.import_module('openstack.cloud.inventory') + client_config = importlib.import_module('openstack.config.loader') + sdk_exceptions = importlib.import_module("openstack.exceptions") + HAS_SDK = True +except ImportError: + display.vvvv("Couldn't import Openstack SDK modules") + HAS_SDK = False + + +class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): + ''' Host inventory provider for ansible using OpenStack clouds. ''' + + NAME = 'openstack.cloud.openstack' + + def parse(self, inventory, loader, path, cache=True): + + super(InventoryModule, self).parse(inventory, loader, path) + + cache_key = self._get_cache_prefix(path) + + # file is config file + self._config_data = self._read_config_data(path) + + msg = '' + if not self._config_data: + msg = 'File empty. this is not my config file' + elif 'plugin' in self._config_data and self._config_data['plugin'] != self.NAME: + msg = 'plugin config file, but not for us: %s' % self._config_data['plugin'] + elif 'plugin' not in self._config_data and 'clouds' not in self._config_data: + msg = "it's not a plugin configuration nor a clouds.yaml file" + elif not HAS_SDK: + msg = "openstacksdk is required for the OpenStack inventory plugin. OpenStack inventory sources will be skipped." + + if msg: + display.vvvv(msg) + raise AnsibleParserError(msg) + + if 'clouds' in self._config_data: + self.display.vvvv( + "Found clouds config file instead of plugin config. " + "Using default configuration." + ) + self._config_data = {} + + # update cache if the user has caching enabled and the cache is being refreshed + # will update variable below in the case of an expired cache + cache_needs_update = not cache and self.get_option('cache') + + if cache: + cache = self.get_option('cache') + source_data = None + if cache: + self.display.vvvv("Reading inventory data from cache: %s" % cache_key) + try: + source_data = self._cache[cache_key] + except KeyError: + # cache expired or doesn't exist yet + display.vvvv("Inventory data cache not found") + cache_needs_update = True + + if not source_data: + self.display.vvvv("Getting hosts from Openstack clouds") + clouds_yaml_path = self._config_data.get('clouds_yaml_path') + if clouds_yaml_path: + config_files = ( + clouds_yaml_path + + client_config.CONFIG_FILES + ) + else: + config_files = None + + # Redict logging to stderr so it does not mix with output + # particular ansible-inventory JSON output + # TODO(mordred) Integrate openstack's logging with ansible's logging + if self.display.verbosity > 3: + sdk.enable_logging(debug=True, stream=sys.stderr) + else: + sdk.enable_logging(stream=sys.stderr) + + cloud_inventory = sdk_inventory.OpenStackInventory( + config_files=config_files, + private=self._config_data.get('private', False)) + self.display.vvvv("Found %d cloud(s) in Openstack" % + len(cloud_inventory.clouds)) + only_clouds = self._config_data.get('only_clouds', []) + if only_clouds and not isinstance(only_clouds, list): + raise ValueError( + 'OpenStack Inventory Config Error: only_clouds must be' + ' a list') + if only_clouds: + new_clouds = [] + for cloud in cloud_inventory.clouds: + self.display.vvvv("Looking at cloud : %s" % cloud.name) + if cloud.name in only_clouds: + self.display.vvvv("Selecting cloud : %s" % cloud.name) + new_clouds.append(cloud) + cloud_inventory.clouds = new_clouds + + self.display.vvvv("Selected %d cloud(s)" % + len(cloud_inventory.clouds)) + + expand_hostvars = self._config_data.get('expand_hostvars', False) + fail_on_errors = self._config_data.get('fail_on_errors', False) + all_projects = self._config_data.get('all_projects', False) + + source_data = [] + try: + source_data = cloud_inventory.list_hosts( + expand=expand_hostvars, fail_on_cloud_config=fail_on_errors, + all_projects=all_projects) + except Exception as e: + self.display.warning("Couldn't list Openstack hosts. " + "See logs for details") + os_logger.error(e.message) + finally: + if cache_needs_update: + self._cache[cache_key] = source_data + + self._populate_from_source(source_data) + + def _populate_from_source(self, source_data): + groups = collections.defaultdict(list) + firstpass = collections.defaultdict(list) + hostvars = {} + + use_server_id = ( + self._config_data.get('inventory_hostname', 'name') != 'name') + show_all = self._config_data.get('show_all', False) + + for server in source_data: + if 'interface_ip' not in server and not show_all: + continue + firstpass[server['name']].append(server) + + for name, servers in firstpass.items(): + if len(servers) == 1 and not use_server_id: + self._append_hostvars(hostvars, groups, name, servers[0]) + else: + server_ids = set() + # Trap for duplicate results + for server in servers: + server_ids.add(server['id']) + if len(server_ids) == 1 and not use_server_id: + self._append_hostvars(hostvars, groups, name, servers[0]) + else: + for server in servers: + self._append_hostvars( + hostvars, groups, server['id'], server, + namegroup=True) + + self._set_variables(hostvars, groups) + + def _set_variables(self, hostvars, groups): + + strict = self.get_option('strict') + + # set vars in inventory from hostvars + for host in hostvars: + + # actually update inventory + for key in hostvars[host]: + self.inventory.set_variable(host, key, hostvars[host][key]) + + # create composite vars + self._set_composite_vars( + self._config_data.get('compose'), self.inventory.get_host(host).get_vars(), host, strict) + + # constructed groups based on conditionals + self._add_host_to_composed_groups( + self._config_data.get('groups'), hostvars[host], host, strict) + + # constructed groups based on jinja expressions + self._add_host_to_keyed_groups( + self._config_data.get('keyed_groups'), hostvars[host], host, strict) + + for group_name, group_hosts in groups.items(): + gname = self.inventory.add_group(group_name) + for host in group_hosts: + self.inventory.add_child(gname, host) + + def _get_groups_from_server(self, server_vars, namegroup=True): + groups = [] + + region = server_vars['region'] + cloud = server_vars['cloud'] + metadata = server_vars.get('metadata', {}) + + # Create a group for the cloud + groups.append(cloud) + + # Create a group on region + if region: + groups.append(region) + + # And one by cloud_region + groups.append("%s_%s" % (cloud, region)) + + # Check if group metadata key in servers' metadata + if 'group' in metadata: + groups.append(metadata['group']) + + for extra_group in metadata.get('groups', '').split(','): + if extra_group: + groups.append(extra_group.strip()) + + groups.append('instance-%s' % server_vars['id']) + if namegroup: + groups.append(server_vars['name']) + + for key in ('flavor', 'image'): + if 'name' in server_vars[key]: + groups.append('%s-%s' % (key, server_vars[key]['name'])) + + for key, value in iter(metadata.items()): + groups.append('meta-%s_%s' % (key, value)) + + az = server_vars.get('az', None) + if az: + # Make groups for az, region_az and cloud_region_az + groups.append(az) + groups.append('%s_%s' % (region, az)) + groups.append('%s_%s_%s' % (cloud, region, az)) + return groups + + def _append_hostvars(self, hostvars, groups, current_host, + server, namegroup=False): + hostvars[current_host] = dict( + ansible_ssh_host=server['interface_ip'], + ansible_host=server['interface_ip'], + openstack=server) + self.inventory.add_host(current_host) + + for group in self._get_groups_from_server(server, namegroup=namegroup): + groups[group].append(current_host) + + def verify_file(self, path): + + if super(InventoryModule, self).verify_file(path): + for fn in ('openstack', 'clouds'): + for suffix in ('yaml', 'yml'): + maybe = '{fn}.{suffix}'.format(fn=fn, suffix=suffix) + if path.endswith(maybe): + self.display.vvvv("Valid plugin config file found") + return True + return False diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/module_utils/__init__.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/module_utils/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/module_utils/__init__.py diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/module_utils/ironic.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/module_utils/ironic.py new file mode 100644 index 00000000..a7ab19ef --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/module_utils/ironic.py @@ -0,0 +1,68 @@ +# 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. +# +# 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 ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec + + +def ironic_argument_spec(**kwargs): + spec = dict( + auth_type=dict(required=False), + ironic_url=dict(required=False), + ) + spec.update(kwargs) + return openstack_full_argument_spec(**spec) + + +# TODO(dtantsur): inherit the collection's base module +class IronicModule(AnsibleModule): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._update_ironic_auth() + + def _update_ironic_auth(self): + """Validate and update authentication parameters for ironic.""" + if ( + self.params['auth_type'] in [None, 'None', 'none'] + and self.params['ironic_url'] is None + and not self.params['cloud'] + and not (self.params['auth'] + and self.params['auth'].get('endpoint')) + ): + self.fail_json(msg=("Authentication appears to be disabled, " + "Please define either ironic_url, or cloud, " + "or auth.endpoint")) + + if ( + self.params['ironic_url'] + and self.params['auth_type'] in [None, 'None', 'none'] + and not (self.params['auth'] + and self.params['auth'].get('endpoint')) + ): + self.params['auth'] = dict( + endpoint=self.params['ironic_url'] + ) diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/module_utils/openstack.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/module_utils/openstack.py new file mode 100644 index 00000000..8d0e41ad --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/module_utils/openstack.py @@ -0,0 +1,420 @@ +# 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 2019 Red Hat, Inc. +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# 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. + +import abc +import copy +from distutils.version import StrictVersion +import importlib +import os + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + +OVERRIDES = {'os_client_config': 'config', + 'os_endpoint': 'catalog_endpoint', + 'os_flavor': 'compute_flavor', + 'os_flavor_info': 'compute_flavor_info', + 'os_group': 'identity_group', + 'os_group_info': 'identity_group_info', + 'os_ironic': 'baremetal_node', + 'os_ironic_inspect': 'baremetal_inspect', + 'os_ironic_node': 'baremetal_node_action', + 'os_keystone_domain': 'identity_domain', + 'os_keystone_domain_info': 'identity_domain_info', + 'os_keystone_endpoint': 'endpoint', + 'os_keystone_identity_provider': 'federation_idp', + 'os_keystone_identity_provider_info': 'federation_idp_info', + 'os_keystone_mapping': 'federation_mapping', + 'os_keystone_mapping_info': 'federation_mapping_info', + 'os_keystone_role': 'identity_role', + 'os_keystone_service': 'catalog_service', + 'os_listener': 'lb_listener', + 'os_member': 'lb_member', + 'os_nova_flavor': 'compute_flavor', + 'os_nova_host_aggregate': 'host_aggregate', + 'os_pool': 'lb_pool', + 'os_user': 'identity_user', + 'os_user_group': 'group_assignment', + 'os_user_info': 'identity_user_info', + 'os_user_role': 'role_assignment', + 'os_zone': 'dns_zone'} + +CUSTOM_VAR_PARAMS = ['min_ver', 'max_ver'] + +MINIMUM_SDK_VERSION = '0.12.0' + + +def openstack_argument_spec(): + # DEPRECATED: This argument spec is only used for the deprecated old + # OpenStack modules. It turns out that modern OpenStack auth is WAY + # more complex than this. + # Consume standard OpenStack environment variables. + # This is mainly only useful for ad-hoc command line operation as + # in playbooks one would assume variables would be used appropriately + OS_AUTH_URL = os.environ.get('OS_AUTH_URL', 'http://127.0.0.1:35357/v2.0/') + OS_PASSWORD = os.environ.get('OS_PASSWORD', None) + OS_REGION_NAME = os.environ.get('OS_REGION_NAME', None) + OS_USERNAME = os.environ.get('OS_USERNAME', 'admin') + OS_TENANT_NAME = os.environ.get('OS_TENANT_NAME', OS_USERNAME) + + spec = dict( + login_username=dict(default=OS_USERNAME), + auth_url=dict(default=OS_AUTH_URL), + region_name=dict(default=OS_REGION_NAME), + availability_zone=dict(), + ) + if OS_PASSWORD: + spec['login_password'] = dict(default=OS_PASSWORD) + else: + spec['login_password'] = dict(required=True) + if OS_TENANT_NAME: + spec['login_tenant_name'] = dict(default=OS_TENANT_NAME) + else: + spec['login_tenant_name'] = dict(required=True) + return spec + + +def openstack_find_nova_addresses(addresses, ext_tag, key_name=None): + + ret = [] + for (k, v) in iteritems(addresses): + if key_name and k == key_name: + ret.extend([addrs['addr'] for addrs in v]) + else: + for interface_spec in v: + if 'OS-EXT-IPS:type' in interface_spec and interface_spec['OS-EXT-IPS:type'] == ext_tag: + ret.append(interface_spec['addr']) + return ret + + +def openstack_full_argument_spec(**kwargs): + spec = dict( + cloud=dict(default=None, type='raw'), + auth_type=dict(default=None), + auth=dict(default=None, type='dict', no_log=True), + region_name=dict(default=None), + availability_zone=dict(default=None), + validate_certs=dict(default=False, type='bool', aliases=['verify']), + ca_cert=dict(default=None, aliases=['cacert']), + client_cert=dict(default=None, aliases=['cert']), + client_key=dict(default=None, no_log=True, aliases=['key']), + wait=dict(default=True, type='bool'), + timeout=dict(default=180, type='int'), + api_timeout=dict(default=None, type='int'), + interface=dict( + default='public', choices=['public', 'internal', 'admin'], + aliases=['endpoint_type']), + ) + # Filter out all our custom parameters before passing to AnsibleModule + kwargs_copy = copy.deepcopy(kwargs) + for v in kwargs_copy.values(): + for c in CUSTOM_VAR_PARAMS: + v.pop(c, None) + spec.update(kwargs_copy) + return spec + + +def openstack_module_kwargs(**kwargs): + ret = {} + for key in ('mutually_exclusive', 'required_together', 'required_one_of'): + if key in kwargs: + if key in ret: + ret[key].extend(kwargs[key]) + else: + ret[key] = kwargs[key] + return ret + + +# for compatibility with old versions +def openstack_cloud_from_module(module, min_version=None): + try: + # Due to the name shadowing we should import other way + sdk = importlib.import_module('openstack') + sdk_version = importlib.import_module('openstack.version') + except ImportError: + module.fail_json(msg='openstacksdk is required for this module') + + if min_version: + min_version = max(StrictVersion(MINIMUM_SDK_VERSION), + StrictVersion(min_version)) + else: + min_version = StrictVersion(MINIMUM_SDK_VERSION) + + if StrictVersion(sdk_version.__version__) < min_version: + module.fail_json( + msg="To utilize this module, the installed version of " + "the openstacksdk library MUST be >={min_version}.".format( + min_version=min_version)) + + cloud_config = module.params.pop('cloud', None) + try: + if isinstance(cloud_config, dict): + fail_message = ( + "A cloud config dict was provided to the cloud parameter" + " but also a value was provided for {param}. If a cloud" + " config dict is provided, {param} should be" + " excluded.") + for param in ( + 'auth', 'region_name', 'validate_certs', + 'ca_cert', 'client_key', 'api_timeout', 'auth_type'): + if module.params[param] is not None: + module.fail_json(msg=fail_message.format(param=param)) + # For 'interface' parameter, fail if we receive a non-default value + if module.params['interface'] != 'public': + module.fail_json(msg=fail_message.format(param='interface')) + return sdk, sdk.connect(**cloud_config) + else: + return sdk, sdk.connect( + cloud=cloud_config, + auth_type=module.params['auth_type'], + auth=module.params['auth'], + region_name=module.params['region_name'], + verify=module.params['validate_certs'], + cacert=module.params['ca_cert'], + key=module.params['client_key'], + api_timeout=module.params['api_timeout'], + interface=module.params['interface'], + ) + except sdk.exceptions.SDKException as e: + # Probably a cloud configuration/login error + module.fail_json(msg=str(e)) + + +class OpenStackModule: + """Openstack Module is a base class for all Openstack Module classes. + + The class has `run` function that should be overriden in child classes, + the provided methods include: + + Methods: + params: Dictionary of Ansible module parameters. + module_name: Module name (i.e. server_action) + sdk_version: Version of used OpenstackSDK. + results: Dictionary for return of Ansible module, + must include `changed` keyword. + exit, exit_json: Exit module and return data inside, must include + changed` keyword in a data. + fail, fail_json: Exit module with failure, has `msg` keyword to + specify a reason of failure. + conn: Connection to SDK object. + log: Print message to system log. + debug: Print debug message to system log, prints if Ansible Debug is + enabled or verbosity is more than 2. + check_deprecated_names: Function that checks if module was called with + a deprecated name and prints the correct name + with deprecation warning. + check_versioned: helper function to check that all arguments are known + in the current SDK version. + run: method that executes and shall be overriden in inherited classes. + + Args: + deprecated_names: Should specify deprecated modules names for current + module. + argument_spec: Used for construction of Openstack common arguments. + module_kwargs: Additional arguments for Ansible Module. + """ + + deprecated_names = () + argument_spec = {} + module_kwargs = {} + module_min_sdk_version = None + + def __init__(self): + """Initialize Openstack base class. + + Set up variables, connection to SDK and check if there are + deprecated names. + """ + self.ansible = AnsibleModule( + openstack_full_argument_spec(**self.argument_spec), + **self.module_kwargs) + self.params = self.ansible.params + self.module_name = self.ansible._name + self.sdk_version = None + self.results = {'changed': False} + self.exit = self.exit_json = self.ansible.exit_json + self.fail = self.fail_json = self.ansible.fail_json + self.sdk, self.conn = self.openstack_cloud_from_module() + self.check_deprecated_names() + + def log(self, msg): + """Prints log message to system log. + + Arguments: + msg {str} -- Log message + """ + self.ansible.log(msg) + + def debug(self, msg): + """Prints debug message to system log + + Arguments: + msg {str} -- Debug message. + """ + if self.ansible._debug or self.ansible._verbosity > 2: + self.ansible.log( + " ".join(['[DEBUG]', msg])) + + def check_deprecated_names(self): + """Check deprecated module names if `deprecated_names` variable is set. + """ + new_module_name = OVERRIDES.get(self.module_name) + if self.module_name in self.deprecated_names and new_module_name: + self.ansible.deprecate( + "The '%s' module has been renamed to '%s' in openstack " + "collection: openstack.cloud.%s" % ( + self.module_name, new_module_name, new_module_name), + version='2.10') + + def openstack_cloud_from_module(self): + """Sets up connection to cloud using provided options. Checks if all + provided variables are supported for the used SDK version. + """ + try: + # Due to the name shadowing we should import other way + sdk = importlib.import_module('openstack') + sdk_version_lib = importlib.import_module('openstack.version') + self.sdk_version = sdk_version_lib.__version__ + except ImportError: + self.fail_json(msg='openstacksdk is required for this module') + + # Fail if the available SDK version doesn't meet the minimum version + # requirements + if self.module_min_sdk_version: + min_version = max(StrictVersion(MINIMUM_SDK_VERSION), + StrictVersion(self.module_min_sdk_version)) + else: + min_version = StrictVersion(MINIMUM_SDK_VERSION) + if StrictVersion(self.sdk_version) < min_version: + self.fail( + msg="To utilize this module, the installed version of " + "the openstacksdk library MUST be >={min_version}.".format( + min_version=min_version)) + + # Fail if there are set unsupported for this version parameters + # New parameters should NOT use 'default' but rely on SDK defaults + for param in self.argument_spec: + if (self.params[param] is not None + and 'min_ver' in self.argument_spec[param] + and StrictVersion(self.sdk_version) < self.argument_spec[param]['min_ver']): + self.fail_json( + msg="To use parameter '{param}' with module '{module}', the installed version of " + "the openstacksdk library MUST be >={min_version}.".format( + min_version=self.argument_spec[param]['min_ver'], + param=param, + module=self.module_name)) + if (self.params[param] is not None + and 'max_ver' in self.argument_spec[param] + and StrictVersion(self.sdk_version) > self.argument_spec[param]['max_ver']): + self.fail_json( + msg="To use parameter '{param}' with module '{module}', the installed version of " + "the openstacksdk library MUST be <={max_version}.".format( + max_version=self.argument_spec[param]['max_ver'], + param=param, + module=self.module_name)) + + cloud_config = self.params.pop('cloud', None) + if isinstance(cloud_config, dict): + fail_message = ( + "A cloud config dict was provided to the cloud parameter" + " but also a value was provided for {param}. If a cloud" + " config dict is provided, {param} should be" + " excluded.") + for param in ( + 'auth', 'region_name', 'validate_certs', + 'ca_cert', 'client_key', 'api_timeout', 'auth_type'): + if self.params[param] is not None: + self.fail_json(msg=fail_message.format(param=param)) + # For 'interface' parameter, fail if we receive a non-default value + if self.params['interface'] != 'public': + self.fail_json(msg=fail_message.format(param='interface')) + else: + cloud_config = dict( + cloud=cloud_config, + auth_type=self.params['auth_type'], + auth=self.params['auth'], + region_name=self.params['region_name'], + verify=self.params['validate_certs'], + cacert=self.params['ca_cert'], + key=self.params['client_key'], + api_timeout=self.params['api_timeout'], + interface=self.params['interface'], + ) + try: + return sdk, sdk.connect(**cloud_config) + except sdk.exceptions.SDKException as e: + # Probably a cloud configuration/login error + self.fail_json(msg=str(e)) + + # Filter out all arguments that are not from current SDK version + def check_versioned(self, **kwargs): + """Check that provided arguments are supported by current SDK version + + Returns: + versioned_result {dict} dictionary of only arguments that are + supported by current SDK version. All others + are dropped. + """ + versioned_result = {} + for var_name in kwargs: + if ('min_ver' in self.argument_spec[var_name] + and StrictVersion(self.sdk_version) < self.argument_spec[var_name]['min_ver']): + continue + if ('max_ver' in self.argument_spec[var_name] + and StrictVersion(self.sdk_version) > self.argument_spec[var_name]['max_ver']): + continue + versioned_result.update({var_name: kwargs[var_name]}) + return versioned_result + + @abc.abstractmethod + def run(self): + """Function for overriding in inhetired classes, it's executed by default. + """ + pass + + def __call__(self): + """Execute `run` function when calling the class. + """ + try: + results = self.run() + if results and isinstance(results, dict): + self.ansible.exit_json(**results) + except self.sdk.exceptions.OpenStackCloudException as e: + params = { + 'msg': str(e), + 'extra_data': { + 'data': getattr(e, 'extra_data', 'None'), + 'details': getattr(e, 'details', 'None'), + 'response': getattr(getattr(e, 'response', ''), + 'text', 'None') + } + } + self.ansible.fail_json(**params) + # if we got to this place, modules didn't exit + self.ansible.exit_json(**self.results) diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/__init__.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/__init__.py diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/auth.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/auth.py new file mode 100644 index 00000000..0b356217 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/auth.py @@ -0,0 +1,67 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: auth +short_description: Retrieve an auth token +author: OpenStack Ansible SIG +description: + - Retrieve an auth token from an OpenStack Cloud +requirements: + - "python >= 3.6" + - "openstacksdk" +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Authenticate to the cloud and retrieve the service catalog + openstack.cloud.auth: + cloud: rax-dfw + +- name: Show service catalog + debug: + var: service_catalog +''' + +RETURN = ''' +auth_token: + description: Openstack API Auth Token + returned: success + type: str +service_catalog: + description: A dictionary of available API endpoints + returned: success + type: dict +''' + +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + + argument_spec = openstack_full_argument_spec() + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + sdk, cloud = openstack_cloud_from_module(module) + try: + module.exit_json( + changed=False, + ansible_facts=dict( + auth_token=cloud.auth_token, + service_catalog=cloud.service_catalog)) + except Exception as e: + module.fail_json(msg=str(e), exception=traceback.format_exc()) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/baremetal_inspect.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/baremetal_inspect.py new file mode 100644 index 00000000..f7d90d1c --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/baremetal_inspect.py @@ -0,0 +1,133 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (c) 2015-2016, Hewlett Packard Enterprise Development Company LP +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: baremetal_inspect +short_description: Explicitly triggers baremetal node introspection in ironic. +author: OpenStack Ansible SIG +description: + - Requests Ironic to set a node into inspect state in order to collect metadata regarding the node. + This command may be out of band or in-band depending on the ironic driver configuration. + This is only possible on nodes in 'manageable' and 'available' state. +options: + mac: + description: + - unique mac address that is used to attempt to identify the host. + type: str + uuid: + description: + - globally unique identifier (UUID) to identify the host. + type: str + name: + description: + - unique name identifier to identify the host in Ironic. + type: str + ironic_url: + description: + - If noauth mode is utilized, this is required to be set to the endpoint URL for the Ironic API. + Use with "auth" and "auth_type" settings set to None. + type: str + timeout: + description: + - A timeout in seconds to tell the role to wait for the node to complete introspection if wait is set to True. + default: 1200 + type: int + +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +ansible_facts: + description: Dictionary of new facts representing discovered properties of the node.. + returned: changed + type: complex + contains: + memory_mb: + description: Amount of node memory as updated in the node properties + type: str + sample: "1024" + cpu_arch: + description: Detected CPU architecture type + type: str + sample: "x86_64" + local_gb: + description: Total size of local disk storage as updated in node properties. + type: str + sample: "10" + cpus: + description: Count of cpu cores defined in the updated node properties. + type: str + sample: "1" +''' + +EXAMPLES = ''' +# Invoke node inspection +- openstack.cloud.baremetal_inspect: + name: "testnode1" +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.ironic import ( + IronicModule, + ironic_argument_spec, +) +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_module_kwargs, + openstack_cloud_from_module +) + + +def _choose_id_value(module): + if module.params['uuid']: + return module.params['uuid'] + if module.params['name']: + return module.params['name'] + return None + + +def main(): + argument_spec = ironic_argument_spec( + uuid=dict(required=False), + name=dict(required=False), + mac=dict(required=False), + timeout=dict(default=1200, type='int', required=False), + ) + module_kwargs = openstack_module_kwargs() + module = IronicModule(argument_spec, **module_kwargs) + + sdk, cloud = openstack_cloud_from_module(module) + try: + if module.params['name'] or module.params['uuid']: + server = cloud.get_machine(_choose_id_value(module)) + elif module.params['mac']: + server = cloud.get_machine_by_mac(module.params['mac']) + else: + module.fail_json(msg="The worlds did not align, " + "the host was not found as " + "no name, uuid, or mac was " + "defined.") + if server: + cloud.inspect_machine(server['uuid'], module.params['wait']) + # TODO(TheJulia): diff properties, ?and ports? and determine + # if a change occurred. In theory, the node is always changed + # if introspection is able to update the record. + module.exit_json(changed=True, + ansible_facts=server['properties']) + + else: + module.fail_json(msg="node not found.") + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/baremetal_node.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/baremetal_node.py new file mode 100644 index 00000000..9145d54e --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/baremetal_node.py @@ -0,0 +1,364 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (c) 2014, Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: baremetal_node +short_description: Create/Delete Bare Metal Resources from OpenStack +author: OpenStack Ansible SIG +description: + - Create or Remove Ironic nodes from OpenStack. +options: + state: + description: + - Indicates desired state of the resource + choices: ['present', 'absent'] + default: present + type: str + uuid: + description: + - globally unique identifier (UUID) to be given to the resource. Will + be auto-generated if not specified, and name is specified. + - Definition of a UUID will always take precedence to a name value. + type: str + name: + description: + - unique name identifier to be given to the resource. + type: str + driver: + description: + - The name of the Ironic Driver to use with this node. + - Required when I(state=present) + type: str + chassis_uuid: + description: + - Associate the node with a pre-defined chassis. + type: str + ironic_url: + description: + - If noauth mode is utilized, this is required to be set to the + endpoint URL for the Ironic API. Use with "auth" and "auth_type" + settings set to None. + type: str + driver_info: + description: + - Information for this server's driver. Will vary based on which + driver is in use. Any sub-field which is populated will be validated + during creation. + required: true + type: dict + suboptions: + power: + description: + - Information necessary to turn this server on / off. + This often includes such things as IPMI username, password, and IP address. + required: true + deploy: + description: + - Information necessary to deploy this server directly, without using Nova. THIS IS NOT RECOMMENDED. + console: + description: + - Information necessary to connect to this server's serial console. Not all drivers support this. + management: + description: + - Information necessary to interact with this server's management interface. May be shared by power_info in some cases. + required: true + nics: + description: + - 'A list of network interface cards, eg, " - mac: aa:bb:cc:aa:bb:cc"' + required: true + type: list + elements: dict + suboptions: + mac: + description: The MAC address of the network interface card. + type: str + required: true + properties: + description: + - Definition of the physical characteristics of this server, used for scheduling purposes + type: dict + suboptions: + cpu_arch: + description: + - CPU architecture (x86_64, i686, ...) + default: x86_64 + cpus: + description: + - Number of CPU cores this machine has + default: 1 + ram: + description: + - amount of RAM this machine has, in MB + default: 1 + disk_size: + description: + - size of first storage device in this machine (typically /dev/sda), in GB + default: 1 + capabilities: + description: + - special capabilities for the node, such as boot_option, node_role etc + (see U(https://docs.openstack.org/ironic/latest/install/advanced.html) + for more information) + default: "" + root_device: + description: + - Root disk device hints for deployment. + - See U(https://docs.openstack.org/ironic/latest/install/advanced.html#specifying-the-disk-for-deployment-root-device-hints) + for allowed hints. + default: "" + skip_update_of_masked_password: + description: + - Allows the code that would assert changes to nodes to skip the + update if the change is a single line consisting of the password + field. + - As of Kilo, by default, passwords are always masked to API + requests, which means the logic as a result always attempts to + re-assert the password field. + - C(skip_update_of_driver_password) is deprecated alias and will be removed in openstack.cloud 2.0.0. + type: bool + aliases: + - skip_update_of_driver_password +requirements: + - "python >= 3.6" + - "openstacksdk" + - "jsonpatch" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Enroll a node with some basic properties and driver info +- openstack.cloud.baremetal_node: + cloud: "devstack" + driver: "pxe_ipmitool" + uuid: "00000000-0000-0000-0000-000000000002" + properties: + cpus: 2 + cpu_arch: "x86_64" + ram: 8192 + disk_size: 64 + capabilities: "boot_option:local" + root_device: + wwn: "0x4000cca77fc4dba1" + nics: + - mac: "aa:bb:cc:aa:bb:cc" + - mac: "dd:ee:ff:dd:ee:ff" + driver_info: + power: + ipmi_address: "1.2.3.4" + ipmi_username: "admin" + ipmi_password: "adminpass" + chassis_uuid: "00000000-0000-0000-0000-000000000001" + +''' + +try: + import jsonpatch + HAS_JSONPATCH = True +except ImportError: + HAS_JSONPATCH = False + + +from ansible_collections.openstack.cloud.plugins.module_utils.ironic import ( + IronicModule, + ironic_argument_spec, +) +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_module_kwargs, + openstack_cloud_from_module +) + + +def _parse_properties(module): + p = module.params['properties'] + props = dict( + cpu_arch=p.get('cpu_arch') if p.get('cpu_arch') else 'x86_64', + cpus=p.get('cpus') if p.get('cpus') else 1, + memory_mb=p.get('ram') if p.get('ram') else 1, + local_gb=p.get('disk_size') if p.get('disk_size') else 1, + capabilities=p.get('capabilities') if p.get('capabilities') else '', + root_device=p.get('root_device') if p.get('root_device') else '', + ) + return props + + +def _parse_driver_info(sdk, module): + p = module.params['driver_info'] + info = p.get('power') + if not info: + raise sdk.exceptions.OpenStackCloudException( + "driver_info['power'] is required") + if p.get('console'): + info.update(p.get('console')) + if p.get('management'): + info.update(p.get('management')) + if p.get('deploy'): + info.update(p.get('deploy')) + return info + + +def _choose_id_value(module): + if module.params['uuid']: + return module.params['uuid'] + if module.params['name']: + return module.params['name'] + return None + + +def _choose_if_password_only(module, patch): + if len(patch) == 1: + if 'password' in patch[0]['path'] and module.params['skip_update_of_masked_password']: + # Return false to abort update as the password appears + # to be the only element in the patch. + return False + return True + + +def _exit_node_not_updated(module, server): + module.exit_json( + changed=False, + result="Node not updated", + uuid=server['uuid'], + provision_state=server['provision_state'] + ) + + +def main(): + argument_spec = ironic_argument_spec( + uuid=dict(required=False), + name=dict(required=False), + driver=dict(required=False), + driver_info=dict(type='dict', required=True), + nics=dict(type='list', required=True, elements="dict"), + properties=dict(type='dict', default={}), + chassis_uuid=dict(required=False), + skip_update_of_masked_password=dict( + required=False, + type='bool', + aliases=['skip_update_of_driver_password'], + deprecated_aliases=[dict(name='skip_update_of_driver_password', version='2.0.0')] + ), + state=dict(required=False, default='present', choices=['present', 'absent']) + ) + module_kwargs = openstack_module_kwargs() + module = IronicModule(argument_spec, **module_kwargs) + + if not HAS_JSONPATCH: + module.fail_json(msg='jsonpatch is required for this module') + + node_id = _choose_id_value(module) + + sdk, cloud = openstack_cloud_from_module(module) + try: + server = cloud.get_machine(node_id) + if module.params['state'] == 'present': + if module.params['driver'] is None: + module.fail_json(msg="A driver must be defined in order " + "to set a node to present.") + + properties = _parse_properties(module) + driver_info = _parse_driver_info(sdk, module) + kwargs = dict( + driver=module.params['driver'], + properties=properties, + driver_info=driver_info, + name=module.params['name'], + ) + + if module.params['chassis_uuid']: + kwargs['chassis_uuid'] = module.params['chassis_uuid'] + + if server is None: + # Note(TheJulia): Add a specific UUID to the request if + # present in order to be able to re-use kwargs for if + # the node already exists logic, since uuid cannot be + # updated. + if module.params['uuid']: + kwargs['uuid'] = module.params['uuid'] + + server = cloud.register_machine(module.params['nics'], + **kwargs) + module.exit_json(changed=True, uuid=server['uuid'], + provision_state=server['provision_state']) + else: + # TODO(TheJulia): Presently this does not support updating + # nics. Support needs to be added. + # + # Note(TheJulia): This message should never get logged + # however we cannot realistically proceed if neither a + # name or uuid was supplied to begin with. + if not node_id: + module.fail_json(msg="A uuid or name value " + "must be defined") + + # Note(TheJulia): Constructing the configuration to compare + # against. The items listed in the server_config block can + # be updated via the API. + + server_config = dict( + driver=server['driver'], + properties=server['properties'], + driver_info=server['driver_info'], + name=server['name'], + ) + + # Add the pre-existing chassis_uuid only if + # it is present in the server configuration. + if hasattr(server, 'chassis_uuid'): + server_config['chassis_uuid'] = server['chassis_uuid'] + + # Note(TheJulia): If a password is defined and concealed, a + # patch will always be generated and re-asserted. + patch = jsonpatch.JsonPatch.from_diff(server_config, kwargs) + + if not patch: + _exit_node_not_updated(module, server) + elif _choose_if_password_only(module, list(patch)): + # Note(TheJulia): Normally we would allow the general + # exception catch below, however this allows a specific + # message. + try: + server = cloud.patch_machine( + server['uuid'], + list(patch)) + except Exception as e: + module.fail_json(msg="Failed to update node, " + "Error: %s" % e.message) + + # Enumerate out a list of changed paths. + change_list = [] + for change in list(patch): + change_list.append(change['path']) + module.exit_json(changed=True, + result="Node Updated", + changes=change_list, + uuid=server['uuid'], + provision_state=server['provision_state']) + + # Return not updated by default as the conditions were not met + # to update. + _exit_node_not_updated(module, server) + + if module.params['state'] == 'absent': + if not node_id: + module.fail_json(msg="A uuid or name value must be defined " + "in order to remove a node.") + + if server is not None: + cloud.unregister_machine(module.params['nics'], + server['uuid']) + module.exit_json(changed=True, result="deleted") + else: + module.exit_json(changed=False, result="Server not found") + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/baremetal_node_action.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/baremetal_node_action.py new file mode 100644 index 00000000..267e4308 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/baremetal_node_action.py @@ -0,0 +1,362 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (c) 2015, Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: baremetal_node_action +short_description: Activate/Deactivate Bare Metal Resources from OpenStack +author: OpenStack Ansible SIG +description: + - Deploy to nodes controlled by Ironic. +options: + name: + description: + - Name of the node to create. + type: str + state: + description: + - Indicates desired state of the resource. + - I(state) can be C('present'), C('absent'), C('maintenance') or C('off'). + default: present + type: str + deploy: + description: + - Indicates if the resource should be deployed. Allows for deployment + logic to be disengaged and control of the node power or maintenance + state to be changed. + type: str + default: 'yes' + uuid: + description: + - globally unique identifier (UUID) to be given to the resource. + type: str + ironic_url: + description: + - If noauth mode is utilized, this is required to be set to the + endpoint URL for the Ironic API. Use with "auth" and "auth_type" + settings set to None. + type: str + config_drive: + description: + - A configdrive file or HTTP(S) URL that will be passed along to the + node. + type: raw + instance_info: + description: + - Definition of the instance information which is used to deploy + the node. This information is only required when an instance is + set to present. + type: dict + suboptions: + image_source: + description: + - An HTTP(S) URL where the image can be retrieved from. + image_checksum: + description: + - The checksum of image_source. + image_disk_format: + description: + - The type of image that has been requested to be deployed. + power: + description: + - A setting to allow power state to be asserted allowing nodes + that are not yet deployed to be powered on, and nodes that + are deployed to be powered off. + - I(power) can be C('present'), C('absent'), C('maintenance') or C('off'). + default: present + type: str + maintenance: + description: + - A setting to allow the direct control if a node is in + maintenance mode. + - I(maintenance) can be C('yes'), C('no'), C('True'), or C('False'). + type: str + maintenance_reason: + description: + - A string expression regarding the reason a node is in a + maintenance mode. + type: str + wait: + description: + - A boolean value instructing the module to wait for node + activation or deactivation to complete before returning. + type: bool + default: 'no' + timeout: + description: + - An integer value representing the number of seconds to + wait for the node activation or deactivation to complete. + default: 1800 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Activate a node by booting an image with a configdrive attached +- openstack.cloud.baremetal_node_action: + cloud: "openstack" + uuid: "d44666e1-35b3-4f6b-acb0-88ab7052da69" + state: present + power: present + deploy: True + maintenance: False + config_drive: "http://192.168.1.1/host-configdrive.iso" + instance_info: + image_source: "http://192.168.1.1/deploy_image.img" + image_checksum: "356a6b55ecc511a20c33c946c4e678af" + image_disk_format: "qcow" + delegate_to: localhost + +# Activate a node by booting an image with a configdrive json object +- openstack.cloud.baremetal_node_action: + uuid: "d44666e1-35b3-4f6b-acb0-88ab7052da69" + auth_type: None + ironic_url: "http://192.168.1.1:6385/" + config_drive: + meta_data: + hostname: node1 + public_keys: + default: ssh-rsa AAA...BBB== + instance_info: + image_source: "http://192.168.1.1/deploy_image.img" + image_checksum: "356a6b55ecc511a20c33c946c4e678af" + image_disk_format: "qcow" + delegate_to: localhost +''' + + +from ansible_collections.openstack.cloud.plugins.module_utils.ironic import ( + IronicModule, + ironic_argument_spec, +) +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_module_kwargs, + openstack_cloud_from_module +) + + +def _choose_id_value(module): + if module.params['uuid']: + return module.params['uuid'] + if module.params['name']: + return module.params['name'] + return None + + +def _is_true(value): + true_values = [True, 'yes', 'Yes', 'True', 'true', 'present', 'on'] + if value in true_values: + return True + return False + + +def _is_false(value): + false_values = [False, None, 'no', 'No', 'False', 'false', 'absent', 'off'] + if value in false_values: + return True + return False + + +def _check_set_maintenance(module, cloud, node): + if _is_true(module.params['maintenance']): + if _is_false(node['maintenance']): + cloud.set_machine_maintenance_state( + node['uuid'], + True, + reason=module.params['maintenance_reason']) + module.exit_json(changed=True, msg="Node has been set into " + "maintenance mode") + else: + # User has requested maintenance state, node is already in the + # desired state, checking to see if the reason has changed. + if (str(node['maintenance_reason']) not in + str(module.params['maintenance_reason'])): + cloud.set_machine_maintenance_state( + node['uuid'], + True, + reason=module.params['maintenance_reason']) + module.exit_json(changed=True, msg="Node maintenance reason " + "updated, cannot take any " + "additional action.") + elif _is_false(module.params['maintenance']): + if node['maintenance'] is True: + cloud.remove_machine_from_maintenance(node['uuid']) + return True + else: + module.fail_json(msg="maintenance parameter was set but a valid " + "the value was not recognized.") + return False + + +def _check_set_power_state(module, cloud, node): + if 'power on' in str(node['power_state']): + if _is_false(module.params['power']): + # User has requested the node be powered off. + cloud.set_machine_power_off(node['uuid']) + module.exit_json(changed=True, msg="Power requested off") + if 'power off' in str(node['power_state']): + if ( + _is_false(module.params['power']) + and _is_false(module.params['state']) + ): + return False + if ( + _is_false(module.params['power']) + and _is_false(module.params['state']) + ): + module.exit_json( + changed=False, + msg="Power for node is %s, node must be reactivated " + "OR set to state absent" + ) + # In the event the power has been toggled on and + # deployment has been requested, we need to skip this + # step. + if ( + _is_true(module.params['power']) + and _is_false(module.params['deploy']) + ): + # Node is powered down when it is not awaiting to be provisioned + cloud.set_machine_power_on(node['uuid']) + return True + # Default False if no action has been taken. + return False + + +def main(): + argument_spec = ironic_argument_spec( + uuid=dict(required=False), + name=dict(required=False), + instance_info=dict(type='dict', required=False), + config_drive=dict(type='raw', required=False), + state=dict(required=False, default='present'), + maintenance=dict(required=False), + maintenance_reason=dict(required=False), + power=dict(required=False, default='present'), + deploy=dict(required=False, default='yes'), + wait=dict(type='bool', required=False, default=False), + timeout=dict(required=False, type='int', default=1800), + ) + module_kwargs = openstack_module_kwargs() + module = IronicModule(argument_spec, **module_kwargs) + + if ( + module.params['config_drive'] + and not isinstance(module.params['config_drive'], (str, dict)) + ): + config_drive_type = type(module.params['config_drive']) + msg = ('argument config_drive is of type %s and we expected' + ' str or dict') % config_drive_type + module.fail_json(msg=msg) + + node_id = _choose_id_value(module) + + if not node_id: + module.fail_json(msg="A uuid or name value must be defined " + "to use this module.") + sdk, cloud = openstack_cloud_from_module(module) + try: + node = cloud.get_machine(node_id) + + if node is None: + module.fail_json(msg="node not found") + + uuid = node['uuid'] + instance_info = module.params['instance_info'] + changed = False + wait = module.params['wait'] + timeout = module.params['timeout'] + + # User has requested desired state to be in maintenance state. + if module.params['state'] == 'maintenance': + module.params['maintenance'] = True + + if node['provision_state'] in [ + 'cleaning', + 'deleting', + 'wait call-back']: + module.fail_json(msg="Node is in %s state, cannot act upon the " + "request as the node is in a transition " + "state" % node['provision_state']) + # TODO(TheJulia) This is in-development code, that requires + # code in the shade library that is still in development. + if _check_set_maintenance(module, cloud, node): + if node['provision_state'] in 'active': + module.exit_json(changed=True, + result="Maintenance state changed") + changed = True + node = cloud.get_machine(node_id) + + if _check_set_power_state(module, cloud, node): + changed = True + node = cloud.get_machine(node_id) + + if _is_true(module.params['state']): + if _is_false(module.params['deploy']): + module.exit_json( + changed=changed, + result="User request has explicitly disabled " + "deployment logic" + ) + + if 'active' in node['provision_state']: + module.exit_json( + changed=changed, + result="Node already in an active state." + ) + + if instance_info is None: + module.fail_json( + changed=changed, + msg="When setting an instance to present, " + "instance_info is a required variable.") + + # TODO(TheJulia): Update instance info, however info is + # deployment specific. Perhaps consider adding rebuild + # support, although there is a known desire to remove + # rebuild support from Ironic at some point in the future. + cloud.update_machine(uuid, instance_info=instance_info) + cloud.validate_node(uuid) + if not wait: + cloud.activate_node(uuid, module.params['config_drive']) + else: + cloud.activate_node( + uuid, + configdrive=module.params['config_drive'], + wait=wait, + timeout=timeout) + # TODO(TheJulia): Add more error checking.. + module.exit_json(changed=changed, result="node activated") + + elif _is_false(module.params['state']): + if node['provision_state'] not in "deleted": + cloud.update_machine(uuid, instance_info={}) + if not wait: + cloud.deactivate_node(uuid) + else: + cloud.deactivate_node( + uuid, + wait=wait, + timeout=timeout) + + module.exit_json(changed=True, result="deleted") + else: + module.exit_json(changed=False, result="node not found") + else: + module.fail_json(msg="State must be present, absent, " + "maintenance, off") + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/baremetal_node_introspection.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/baremetal_node_introspection.py new file mode 100644 index 00000000..c9503275 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/baremetal_node_introspection.py @@ -0,0 +1,376 @@ +#!/usr/bin/python +# Copyright (c) 2019 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: baremetal_node_introspection +short_description: Introspect Ironic nodes +extends_documentation_fragment: openstack +author: +version_added: "2.10" +description: + - Requests Ironic for nodes info. +options: + ironic_url: + description: + - If noauth mode is utilized, this is required to be set to the + endpoint URL for the Ironic API. + Use with "auth" and "auth_type" settings set to None. + type: str + required: False + node_uuids: + description: + - node_uuids + type: list + required: True + concurrency: + description: + - concurrency + type: int + default: 20 + max_retries: + description: + - max_retries + type: int + default: 2 + node_timeout: + description: + - node_timeout + type: int + default: 1200 + retry_timeout: + description: + - How much time to wait for node to be unlocked before introspection + retry + type: int + default: 120 + quiet: + description: + - Don't provide instrospection info in output of the module + type: bool + default: False +''' + +RETURN = ''' +introspection_data: + description: Dictionary of new facts representing introspection data of + nodes. + returned: changed + type: dict + sample: { + "400b3cd0-d134-417b-8f0e-63e273e01e5a": { + "failed": false, + "retries": 0, + "status": { + "error": null, + "finished_at": "2019-11-22T01:09:07", + "id": "400b3cd0-d134-417b-8f0e-63e273e01e5a", + "is_finished": true, + "location": { + "cloud": "undercloud", + "project": { + "domain_id": null, + "domain_name": "Default", + "id": "......", + "name": "admin" + }, + "region_name": "regionOne", + "zone": null + }, + "name": null, + "started_at": "2019-11-22T01:07:32", + "state": "finished" + } + } + } +''' + +EXAMPLES = ''' +# Invoke node introspection + +- baremetal_node_introspection: + cloud: undercloud + auth: password + node_uuids: + - uuid1 + - uuid2 + concurrency: 10 + max_retries: 1 + node_timeout: 1000 + +''' + +import time +import yaml + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.openstack import openstack_full_argument_spec +from ansible.module_utils.openstack import openstack_module_kwargs +from ansible.module_utils.openstack import openstack_cloud_from_module + + +class IntrospectionManagement(object): + def __init__(self, + cloud, + module, + concurrency, + max_retries, + node_timeout, + retry_timeout): + self.client = cloud.baremetal_introspection + self.cloud = cloud + self.module = module + self.concurrency = concurrency + self.max_retries = max_retries + self.node_timeout = node_timeout + self.retry_timeout = retry_timeout + + def log(self, msg): + self.module.log("baremetal_node_introspection: %s" % msg) + + def push_next(self, pool, queue): + try: + next_introspection = next(queue) + pool.append(next_introspection) + except StopIteration: + pass + return pool + + def introspect(self, node_uuids): + + result = {} + queue = (NodeIntrospection( + uuid, + self.client, + self.cloud, + self.node_timeout, + self.max_retries, + self.retry_timeout, + self.log) for uuid in node_uuids) + pool = [] + + for i in range(self.concurrency): + pool = self.push_next(pool, queue) + + while len(pool) > 0: + finished = [] + for intro in pool: + if not intro.started: + try: + intro.start_introspection() + continue + except Exception as e: + self.log("ERROR Node %s can't start introspection" + " because: %s" % (intro.node_id, str(e))) + result[intro.node_id] = { + "error": "Error for introspection node %s: %s " % ( + intro.node_id, str(e)), + "failed": True, + "status": '' + } + finished.append(intro) + continue + status = intro.get_introspection() + if (not status.is_finished and intro.timeouted()) or ( + status.is_finished and status.error is not None + ): + if status.is_finished: + self.log("ERROR Introspection of node %s " + "failed: %s" % ( + status.id, str(status.error)) + ) + if intro.last_retry(): + result[status.id] = (intro.error_msg() + if status.is_finished + else intro.timeout_msg()) + finished.append(intro) + else: + intro.restart_introspection() + if status.is_finished and status.error is None: + result[status.id] = { + 'status': intro.get_introspection_data(), + 'failed': False, + 'error': None} + finished.append(intro) + for i in finished: + pool.remove(i) + pool = self.push_next(pool, queue) + # Let's not DDOS Ironic service + if pool: + time.sleep(min(10, self.node_timeout)) + + return result + + +class NodeIntrospection: + started = False + + def __init__( + self, + node_id, + os_client, + os_cloud, + timeout, + max_retries, + retry_timeout, + log): + self.node_id = node_id + self.os_client = os_client + self.os_cloud = os_cloud + self.timeout = timeout + self.max_retries = max_retries + self.log = log + self.start = int(time.time()) + self.retries = 0 + self.retry_timeout = retry_timeout + self.last_status = None + + def restart_introspection(self): + self.retries += 1 + try: + self.os_client.abort_introspection(self.node_id) + except Exception as e: + # Node is locked + self.log("ERROR Node %s can't abort introspection: %s" % ( + self.node_id, str(e))) + return + # need to wait before restarting introspection till it's aborted + # to prevent hanging let's use introspect timeout for that + try: + self.os_client.wait_for_introspection( + self.node_id, timeout=self.timeout, ignore_error=True) + # Wait until node is unlocked + self.os_cloud.baremetal.wait_for_node_reservation( + self.node_id, timeout=self.retry_timeout) + except Exception as e: + self.log("ERROR Node %s can't restart introspection because can't " + "abort and unlock it: %s" % (self.node_id, str(e))) + return + self.start = int(time.time()) + return self.start_introspection(restart=True) + + def start_introspection(self, restart=False): + self.started = True + if restart: + self.log("INFO Restarting (try %s of %s) introspection of " + "node %s" % ( + self.retries, self.max_retries, self.node_id)) + else: + self.log("INFO Starting introspection of node %s" % (self.node_id)) + return self.os_client.start_introspection(self.node_id) + + def get_introspection(self): + self.last_status = self.os_client.get_introspection(self.node_id) + return self.last_status + + def get_introspection_data(self): + self.log( + "Instrospection of node %s finished successfully!" % self.node_id) + return self.os_client.get_introspection_data(self.node_id) + + def time_elapsed(self): + return int(time.time()) - self.start + + def timeouted(self): + return self.time_elapsed() > self.timeout + + def last_retry(self): + return self.retries >= self.max_retries + + def timeout_msg(self): + self.log( + "ERROR Retry limit %s reached for introspection " + "node %s: exceeded timeout" % ( + self.max_retries, self.node_id)) + return {"error": "Timeout error for introspection node %s: %s " + "sec exceeded max timeout of %s sec" % ( + self.node_id, self.time_elapsed(), self.timeout), + "failed": True, + "status": self.last_status + } + + def error_msg(self): + self.log( + "ERROR Retry limit %s reached for introspection " + "node %s: %s" % ( + self.max_retries, self.node_id, self.last_status.error)) + return {"error": "Error for introspection node %s: %s " % ( + self.node_id, self.last_status.error), + "failed": True, + "status": self.last_status + } + + +def main(): + argument_spec = openstack_full_argument_spec( + **yaml.safe_load(DOCUMENTATION)['options'] + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule( + argument_spec, + supports_check_mode=False, + **module_kwargs + ) + auth_type = module.params.get('auth_type') + ironic_url = module.params.get('ironic_url') + if auth_type in (None, 'None'): + if not ironic_url: + module.fail_json( + msg="Authentication appears to be disabled," + " Please define an ironic_url parameter" + ) + else: + module.params['auth'] = {'endpoint': ironic_url} + + _, cloud = openstack_cloud_from_module(module) + + introspector = IntrospectionManagement( + cloud, + module, + module.params["concurrency"], + module.params["max_retries"], + module.params["node_timeout"], + module.params["retry_timeout"] + ) + module_results = {"changed": True} + result = introspector.introspect(module.params["node_uuids"]) + failed_nodes = [k for k, v in result.items() if v['failed']] + passed_nodes = [k for k, v in result.items() if not v['failed']] + failed = len(failed_nodes) + if failed > 0: + message = ("Introspection completed with failures. %s node(s) failed." + % failed) + module.log("baremetal_node_introspection ERROR %s" % + message) + module_results.update({'failed': True}) + else: + message = "Introspection completed successfully: %s nodes" % len( + module.params["node_uuids"]) + module.log("baremetal_node_introspection INFO %s" % + message) + + module_results.update({ + "introspection_data": result if not module.params['quiet'] else {}, + "failed_nodes": failed_nodes, + "passed_nodes": passed_nodes, + "msg": message + }) + module.exit_json(**module_results) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/catalog_service.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/catalog_service.py new file mode 100644 index 00000000..ca3903f9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/catalog_service.py @@ -0,0 +1,189 @@ +#!/usr/bin/python +# Copyright 2016 Sam Yaple +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: catalog_service +short_description: Manage OpenStack Identity services +author: OpenStack Ansible SIG +description: + - Create, update, or delete OpenStack Identity service. If a service + with the supplied name already exists, it will be updated with the + new description and enabled attributes. +options: + name: + description: + - Name of the service + required: true + type: str + description: + description: + - Description of the service + type: str + enabled: + description: + - Is the service enabled + type: bool + default: 'yes' + service_type: + description: + - The type of service + required: true + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a service for glance +- openstack.cloud.catalog_service: + cloud: mycloud + state: present + name: glance + service_type: image + description: OpenStack Image Service +# Delete a service +- openstack.cloud.catalog_service: + cloud: mycloud + state: absent + name: glance + service_type: image +''' + +RETURN = ''' +service: + description: Dictionary describing the service. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Service ID. + type: str + sample: "3292f020780b4d5baf27ff7e1d224c44" + name: + description: Service name. + type: str + sample: "glance" + service_type: + description: Service type. + type: str + sample: "image" + description: + description: Service description. + type: str + sample: "OpenStack Image Service" + enabled: + description: Service status. + type: bool + sample: True +id: + description: The service ID. + returned: On success when I(state) is 'present' + type: str + sample: "3292f020780b4d5baf27ff7e1d224c44" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _needs_update(module, service): + if service.enabled != module.params['enabled']: + return True + if service.description is not None and \ + service.description != module.params['description']: + return True + return False + + +def _system_state_change(module, service): + state = module.params['state'] + if state == 'absent' and service: + return True + + if state == 'present': + if service is None: + return True + return _needs_update(module, service) + + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + description=dict(default=None), + enabled=dict(default=True, type='bool'), + name=dict(required=True), + service_type=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + description = module.params['description'] + enabled = module.params['enabled'] + name = module.params['name'] + state = module.params['state'] + service_type = module.params['service_type'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + services = cloud.search_services(name_or_id=name, + filters=dict(type=service_type)) + + if len(services) > 1: + module.fail_json(msg='Service name %s and type %s are not unique' % + (name, service_type)) + elif len(services) == 1: + service = services[0] + else: + service = None + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, service)) + + if state == 'present': + if service is None: + service = cloud.create_service(name=name, description=description, + type=service_type, enabled=True) + changed = True + else: + if _needs_update(module, service): + service = cloud.update_service( + service.id, name=name, type=service_type, enabled=enabled, + description=description) + changed = True + else: + changed = False + module.exit_json(changed=changed, service=service, id=service.id) + + elif state == 'absent': + if service is None: + changed = False + else: + cloud.delete_service(service.id) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/coe_cluster.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/coe_cluster.py new file mode 100644 index 00000000..65042a98 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/coe_cluster.py @@ -0,0 +1,293 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Catalyst IT Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: coe_cluster +short_description: Add/Remove COE cluster from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove COE cluster from the OpenStack Container Infra service. +options: + cluster_template_id: + description: + - The template ID of cluster template. + required: true + type: str + discovery_url: + description: + - Url used for cluster node discovery + type: str + docker_volume_size: + description: + - The size in GB of the docker volume + type: int + flavor_id: + description: + - The flavor of the minion node for this ClusterTemplate + type: str + keypair: + description: + - Name of the keypair to use. + type: str + labels: + description: + - One or more key/value pairs + type: raw + master_flavor_id: + description: + - The flavor of the master node for this ClusterTemplate + type: str + master_count: + description: + - The number of master nodes for this cluster + default: 1 + type: int + name: + description: + - Name that has to be given to the cluster template + required: true + type: str + node_count: + description: + - The number of nodes for this cluster + default: 1 + type: int + state: + description: + - Indicate desired state of the resource. + choices: [present, absent] + default: present + type: str + timeout: + description: + - Timeout for creating the cluster in minutes. Default to 60 mins + if not set + default: 60 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +id: + description: The cluster UUID. + returned: On success when I(state) is 'present' + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +cluster: + description: Dictionary describing the cluster. + returned: On success when I(state) is 'present' + type: complex + contains: + api_address: + description: + - Api address of cluster master node + type: str + sample: https://172.24.4.30:6443 + cluster_template_id: + description: The cluster_template UUID + type: str + sample: '7b1418c8-cea8-48fc-995d-52b66af9a9aa' + coe_version: + description: + - Version of the COE software currently running in this cluster + type: str + sample: v1.11.1 + container_version: + description: + - "Version of the container software. Example: docker version." + type: str + sample: 1.12.6 + created_at: + description: + - The time in UTC at which the cluster is created + type: str + sample: "2018-08-16T10:29:45+00:00" + create_timeout: + description: + - Timeout for creating the cluster in minutes. Default to 60 if + not set. + type: int + sample: 60 + discovery_url: + description: + - Url used for cluster node discovery + type: str + sample: https://discovery.etcd.io/a42ee38e7113f31f4d6324f24367aae5 + faults: + description: + - Fault info collected from the Heat resources of this cluster + type: dict + sample: {'0': 'ResourceInError: resources[0].resources...'} + flavor_id: + description: + - The flavor of the minion node for this cluster + type: str + sample: c1.c1r1 + keypair: + description: + - Name of the keypair to use. + type: str + sample: mykey + labels: + description: One or more key/value pairs + type: dict + sample: {'key1': 'value1', 'key2': 'value2'} + master_addresses: + description: + - IP addresses of cluster master nodes + type: list + sample: ['172.24.4.5'] + master_count: + description: + - The number of master nodes for this cluster. + type: int + sample: 1 + master_flavor_id: + description: + - The flavor of the master node for this cluster + type: str + sample: c1.c1r1 + name: + description: + - Name that has to be given to the cluster + type: str + sample: k8scluster + node_addresses: + description: + - IP addresses of cluster slave nodes + type: list + sample: ['172.24.4.8'] + node_count: + description: + - The number of master nodes for this cluster. + type: int + sample: 1 + stack_id: + description: + - Stack id of the Heat stack + type: str + sample: '07767ec6-85f5-44cb-bd63-242a8e7f0d9d' + status: + description: Status of the cluster from the heat stack + type: str + sample: 'CREATE_COMLETE' + status_reason: + description: + - Status reason of the cluster from the heat stack + type: str + sample: 'Stack CREATE completed successfully' + updated_at: + description: + - The time in UTC at which the cluster is updated + type: str + sample: '2018-08-16T10:39:25+00:00' + id: + description: + - Unique UUID for this cluster + type: str + sample: '86246a4d-a16c-4a58-9e96ad7719fe0f9d' +''' + +EXAMPLES = ''' +# Create a new Kubernetes cluster +- openstack.cloud.coe_cluster: + name: k8s + cluster_template_id: k8s-ha + keypair: mykey + master_count: 3 + node_count: 5 +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _parse_labels(labels): + if isinstance(labels, str): + labels_dict = {} + for kv_str in labels.split(","): + k, v = kv_str.split("=") + labels_dict[k] = v + return labels_dict + if not labels: + return {} + return labels + + +def main(): + argument_spec = openstack_full_argument_spec( + cluster_template_id=dict(required=True), + discovery_url=dict(default=None), + docker_volume_size=dict(type='int'), + flavor_id=dict(default=None), + keypair=dict(default=None), + labels=dict(default=None, type='raw'), + master_count=dict(type='int', default=1), + master_flavor_id=dict(default=None), + name=dict(required=True), + node_count=dict(type='int', default=1), + state=dict(default='present', choices=['absent', 'present']), + timeout=dict(type='int', default=60), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + params = module.params.copy() + + state = module.params['state'] + name = module.params['name'] + cluster_template_id = module.params['cluster_template_id'] + + kwargs = dict( + discovery_url=module.params['discovery_url'], + docker_volume_size=module.params['docker_volume_size'], + flavor_id=module.params['flavor_id'], + keypair=module.params['keypair'], + labels=_parse_labels(params['labels']), + master_count=module.params['master_count'], + master_flavor_id=module.params['master_flavor_id'], + node_count=module.params['node_count'], + create_timeout=module.params['timeout'], + ) + + sdk, cloud = openstack_cloud_from_module(module) + try: + changed = False + cluster = cloud.get_coe_cluster(name_or_id=name, filters={'cluster_template_id': cluster_template_id}) + + if state == 'present': + if not cluster: + cluster = cloud.create_coe_cluster(name, cluster_template_id=cluster_template_id, **kwargs) + changed = True + else: + changed = False + + # NOTE (brtknr): At present, create_coe_cluster request returns + # cluster_id as `uuid` whereas get_coe_cluster request returns the + # same field as `id`. This behaviour may change in the future + # therefore try `id` first then `uuid`. + cluster_id = cluster.get('id', cluster.get('uuid')) + cluster['id'] = cluster['uuid'] = cluster_id + module.exit_json(changed=changed, cluster=cluster, id=cluster_id) + elif state == 'absent': + if not cluster: + module.exit_json(changed=False) + else: + cloud.delete_coe_cluster(name) + module.exit_json(changed=True) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/coe_cluster_template.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/coe_cluster_template.py new file mode 100644 index 00000000..1fcca8d2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/coe_cluster_template.py @@ -0,0 +1,388 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Catalyst IT Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: coe_cluster_template +short_description: Add/Remove COE cluster template from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove COE cluster template from the OpenStack Container Infra + service. +options: + coe: + description: + - The Container Orchestration Engine for this clustertemplate + choices: [kubernetes, swarm, mesos] + type: str + required: true + dns_nameserver: + description: + - The DNS nameserver address + default: '8.8.8.8' + type: str + docker_storage_driver: + description: + - Docker storage driver + choices: [devicemapper, overlay, overlay2] + type: str + docker_volume_size: + description: + - The size in GB of the docker volume + type: int + external_network_id: + description: + - The external network to attach to the Cluster + type: str + fixed_network: + description: + - The fixed network name to attach to the Cluster + type: str + fixed_subnet: + description: + - The fixed subnet name to attach to the Cluster + type: str + flavor_id: + description: + - The flavor of the minion node for this ClusterTemplate + type: str + floating_ip_enabled: + description: + - Indicates whether created clusters should have a floating ip or not + type: bool + default: true + keypair_id: + description: + - Name or ID of the keypair to use. + type: str + image_id: + description: + - Image id the cluster will be based on + type: str + required: true + labels: + description: + - One or more key/value pairs + type: raw + http_proxy: + description: + - Address of a proxy that will receive all HTTP requests and relay them + The format is a URL including a port number + type: str + https_proxy: + description: + - Address of a proxy that will receive all HTTPS requests and relay + them. The format is a URL including a port number + type: str + master_flavor_id: + description: + - The flavor of the master node for this ClusterTemplate + type: str + master_lb_enabled: + description: + - Indicates whether created clusters should have a load balancer + for master nodes or not + type: bool + default: 'no' + name: + description: + - Name that has to be given to the cluster template + required: true + type: str + network_driver: + description: + - The name of the driver used for instantiating container networks + choices: [flannel, calico, docker] + type: str + no_proxy: + description: + - A comma separated list of IPs for which proxies should not be + used in the cluster + type: str + public: + description: + - Indicates whether the ClusterTemplate is public or not + type: bool + default: 'no' + registry_enabled: + description: + - Indicates whether the docker registry is enabled + type: bool + default: 'no' + server_type: + description: + - Server type for this ClusterTemplate + choices: [vm, bm] + default: vm + type: str + state: + description: + - Indicate desired state of the resource. + choices: [present, absent] + default: present + type: str + tls_disabled: + description: + - Indicates whether the TLS should be disabled + type: bool + default: 'no' + volume_driver: + description: + - The name of the driver used for instantiating container volumes + choices: [cinder, rexray] + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +id: + description: The cluster UUID. + returned: On success when I(state) is 'present' + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +cluster_template: + description: Dictionary describing the template. + returned: On success when I(state) is 'present' + type: complex + contains: + coe: + description: The Container Orchestration Engine for this clustertemplate + type: str + sample: kubernetes + dns_nameserver: + description: The DNS nameserver address + type: str + sample: '8.8.8.8' + docker_storage_driver: + description: Docker storage driver + type: str + sample: devicemapper + docker_volume_size: + description: The size in GB of the docker volume + type: int + sample: 5 + external_network_id: + description: The external network to attach to the Cluster + type: str + sample: public + fixed_network: + description: The fixed network name to attach to the Cluster + type: str + sample: 07767ec6-85f5-44cb-bd63-242a8e7f0d9d + fixed_subnet: + description: + - The fixed subnet name to attach to the Cluster + type: str + sample: 05567ec6-85f5-44cb-bd63-242a8e7f0d9d + flavor_id: + description: + - The flavor of the minion node for this ClusterTemplate + type: str + sample: c1.c1r1 + floating_ip_enabled: + description: + - Indicates whether created clusters should have a floating ip or not + type: bool + sample: true + keypair_id: + description: + - Name or ID of the keypair to use. + type: str + sample: mykey + image_id: + description: + - Image id the cluster will be based on + type: str + sample: 05567ec6-85f5-44cb-bd63-242a8e7f0e9d + labels: + description: One or more key/value pairs + type: dict + sample: {'key1': 'value1', 'key2': 'value2'} + http_proxy: + description: + - Address of a proxy that will receive all HTTP requests and relay them + The format is a URL including a port number + type: str + sample: http://10.0.0.11:9090 + https_proxy: + description: + - Address of a proxy that will receive all HTTPS requests and relay + them. The format is a URL including a port number + type: str + sample: https://10.0.0.10:8443 + master_flavor_id: + description: + - The flavor of the master node for this ClusterTemplate + type: str + sample: c1.c1r1 + master_lb_enabled: + description: + - Indicates whether created clusters should have a load balancer + for master nodes or not + type: bool + sample: true + name: + description: + - Name that has to be given to the cluster template + type: str + sample: k8scluster + network_driver: + description: + - The name of the driver used for instantiating container networks + type: str + sample: calico + no_proxy: + description: + - A comma separated list of IPs for which proxies should not be + used in the cluster + type: str + sample: 10.0.0.4,10.0.0.5 + public: + description: + - Indicates whether the ClusterTemplate is public or not + type: bool + sample: false + registry_enabled: + description: + - Indicates whether the docker registry is enabled + type: bool + sample: false + server_type: + description: + - Server type for this ClusterTemplate + type: str + sample: vm + tls_disabled: + description: + - Indicates whether the TLS should be disabled + type: bool + sample: false + volume_driver: + description: + - The name of the driver used for instantiating container volumes + type: str + sample: cinder +''' + +EXAMPLES = ''' +# Create a new Kubernetes cluster template +- openstack.cloud.coe_cluster_template: + name: k8s + coe: kubernetes + keypair_id: mykey + image_id: 2a8c9888-9054-4b06-a1ca-2bb61f9adb72 + public: no +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _parse_labels(labels): + if isinstance(labels, str): + labels_dict = {} + for kv_str in labels.split(","): + k, v = kv_str.split("=") + labels_dict[k] = v + return labels_dict + if not labels: + return {} + return labels + + +def main(): + argument_spec = openstack_full_argument_spec( + coe=dict(required=True, choices=['kubernetes', 'swarm', 'mesos']), + dns_nameserver=dict(default='8.8.8.8'), + docker_storage_driver=dict(choices=['devicemapper', 'overlay', 'overlay2']), + docker_volume_size=dict(type='int'), + external_network_id=dict(default=None), + fixed_network=dict(default=None), + fixed_subnet=dict(default=None), + flavor_id=dict(default=None), + floating_ip_enabled=dict(type='bool', default=True), + keypair_id=dict(default=None), + image_id=dict(required=True), + labels=dict(default=None, type='raw'), + http_proxy=dict(default=None), + https_proxy=dict(default=None), + master_lb_enabled=dict(type='bool', default=False), + master_flavor_id=dict(default=None), + name=dict(required=True), + network_driver=dict(choices=['flannel', 'calico', 'docker']), + no_proxy=dict(default=None), + public=dict(type='bool', default=False), + registry_enabled=dict(type='bool', default=False), + server_type=dict(default="vm", choices=['vm', 'bm']), + state=dict(default='present', choices=['absent', 'present']), + tls_disabled=dict(type='bool', default=False), + volume_driver=dict(choices=['cinder', 'rexray']), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + params = module.params.copy() + + state = module.params['state'] + name = module.params['name'] + coe = module.params['coe'] + image_id = module.params['image_id'] + + kwargs = dict( + dns_nameserver=module.params['dns_nameserver'], + docker_storage_driver=module.params['docker_storage_driver'], + docker_volume_size=module.params['docker_volume_size'], + external_network_id=module.params['external_network_id'], + fixed_network=module.params['fixed_network'], + fixed_subnet=module.params['fixed_subnet'], + flavor_id=module.params['flavor_id'], + floating_ip_enabled=module.params['floating_ip_enabled'], + keypair_id=module.params['keypair_id'], + labels=_parse_labels(params['labels']), + http_proxy=module.params['http_proxy'], + https_proxy=module.params['https_proxy'], + master_lb_enabled=module.params['master_lb_enabled'], + master_flavor_id=module.params['master_flavor_id'], + network_driver=module.params['network_driver'], + no_proxy=module.params['no_proxy'], + public=module.params['public'], + registry_enabled=module.params['registry_enabled'], + server_type=module.params['server_type'], + tls_disabled=module.params['tls_disabled'], + volume_driver=module.params['volume_driver'], + ) + + sdk, cloud = openstack_cloud_from_module(module) + try: + changed = False + template = cloud.get_coe_cluster_template(name_or_id=name, filters={'coe': coe, 'image_id': image_id}) + + if state == 'present': + if not template: + template = cloud.create_coe_cluster_template(name, coe=coe, image_id=image_id, **kwargs) + changed = True + else: + changed = False + + module.exit_json(changed=changed, cluster_template=template, id=template['uuid']) + elif state == 'absent': + if not template: + module.exit_json(changed=False) + else: + cloud.delete_coe_cluster_template(name) + module.exit_json(changed=True) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/compute_flavor.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/compute_flavor.py new file mode 100644 index 00000000..85092b44 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/compute_flavor.py @@ -0,0 +1,274 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: compute_flavor +short_description: Manage OpenStack compute flavors +author: OpenStack Ansible SIG +description: + - Add or remove flavors from OpenStack. +options: + state: + description: + - Indicate desired state of the resource. When I(state) is 'present', + then I(ram), I(vcpus), and I(disk) are all required. There are no + default values for those parameters. + choices: ['present', 'absent'] + default: present + type: str + name: + description: + - Flavor name. + required: true + type: str + ram: + description: + - Amount of memory, in MB. + type: int + vcpus: + description: + - Number of virtual CPUs. + type: int + disk: + description: + - Size of local disk, in GB. + default: 0 + type: int + ephemeral: + description: + - Ephemeral space size, in GB. + default: 0 + type: int + swap: + description: + - Swap space size, in MB. + default: 0 + type: int + rxtx_factor: + description: + - RX/TX factor. + default: 1.0 + type: float + is_public: + description: + - Make flavor accessible to the public. + type: bool + default: 'yes' + flavorid: + description: + - ID for the flavor. This is optional as a unique UUID will be + assigned if a value is not specified. + default: "auto" + type: str + extra_specs: + description: + - Metadata dictionary + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: "Create 'tiny' flavor with 1024MB of RAM, 1 virtual CPU, and 10GB of local disk, and 10GB of ephemeral." + openstack.cloud.compute_flavor: + cloud: mycloud + state: present + name: tiny + ram: 1024 + vcpus: 1 + disk: 10 + ephemeral: 10 + +- name: "Delete 'tiny' flavor" + openstack.cloud.compute_flavor: + cloud: mycloud + state: absent + name: tiny + +- name: Create flavor with metadata + openstack.cloud.compute_flavor: + cloud: mycloud + state: present + name: tiny + ram: 1024 + vcpus: 1 + disk: 10 + extra_specs: + "quota:disk_read_iops_sec": 5000 + "aggregate_instance_extra_specs:pinned": false +''' + +RETURN = ''' +flavor: + description: Dictionary describing the flavor. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Flavor ID. + returned: success + type: str + sample: "515256b8-7027-4d73-aa54-4e30a4a4a339" + name: + description: Flavor name. + returned: success + type: str + sample: "tiny" + disk: + description: Size of local disk, in GB. + returned: success + type: int + sample: 10 + ephemeral: + description: Ephemeral space size, in GB. + returned: success + type: int + sample: 10 + ram: + description: Amount of memory, in MB. + returned: success + type: int + sample: 1024 + swap: + description: Swap space size, in MB. + returned: success + type: int + sample: 100 + vcpus: + description: Number of virtual CPUs. + returned: success + type: int + sample: 2 + is_public: + description: Make flavor accessible to the public. + returned: success + type: bool + sample: true + extra_specs: + description: Flavor metadata + returned: success + type: dict + sample: + "quota:disk_read_iops_sec": 5000 + "aggregate_instance_extra_specs:pinned": false +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(module, flavor): + state = module.params['state'] + if state == 'present' and not flavor: + return True + if state == 'absent' and flavor: + return True + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + state=dict(required=False, default='present', + choices=['absent', 'present']), + name=dict(required=True), + + # required when state is 'present' + ram=dict(required=False, type='int'), + vcpus=dict(required=False, type='int'), + + disk=dict(required=False, default=0, type='int'), + ephemeral=dict(required=False, default=0, type='int'), + swap=dict(required=False, default=0, type='int'), + rxtx_factor=dict(required=False, default=1.0, type='float'), + is_public=dict(required=False, default=True, type='bool'), + flavorid=dict(required=False, default="auto"), + extra_specs=dict(required=False, default=None, type='dict'), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + required_if=[ + ('state', 'present', ['ram', 'vcpus', 'disk']) + ], + **module_kwargs) + + state = module.params['state'] + name = module.params['name'] + extra_specs = module.params['extra_specs'] or {} + + sdk, cloud = openstack_cloud_from_module(module) + try: + flavor = cloud.get_flavor(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, flavor)) + + if state == 'present': + old_extra_specs = {} + require_update = False + + if flavor: + old_extra_specs = flavor['extra_specs'] + for param_key in ['ram', 'vcpus', 'disk', 'ephemeral', 'swap', 'rxtx_factor', 'is_public']: + if module.params[param_key] != flavor[param_key]: + require_update = True + break + + if flavor and require_update: + cloud.delete_flavor(name) + flavor = None + + if not flavor: + flavor = cloud.create_flavor( + name=name, + ram=module.params['ram'], + vcpus=module.params['vcpus'], + disk=module.params['disk'], + flavorid=module.params['flavorid'], + ephemeral=module.params['ephemeral'], + swap=module.params['swap'], + rxtx_factor=module.params['rxtx_factor'], + is_public=module.params['is_public'] + ) + changed = True + else: + changed = False + + new_extra_specs = dict([(k, str(v)) for k, v in extra_specs.items()]) + unset_keys = set(old_extra_specs.keys()) - set(extra_specs.keys()) + + if unset_keys and not require_update: + cloud.unset_flavor_specs(flavor['id'], unset_keys) + + if old_extra_specs != new_extra_specs: + cloud.set_flavor_specs(flavor['id'], extra_specs) + + changed = (changed or old_extra_specs != new_extra_specs) + + module.exit_json(changed=changed, + flavor=flavor, + id=flavor['id']) + + elif state == 'absent': + if flavor: + cloud.delete_flavor(name) + module.exit_json(changed=True) + module.exit_json(changed=False) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/compute_flavor_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/compute_flavor_info.py new file mode 100644 index 00000000..48e78bf8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/compute_flavor_info.py @@ -0,0 +1,228 @@ +#!/usr/bin/python + +# Copyright (c) 2015 IBM +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: compute_flavor_info +short_description: Retrieve information about one or more flavors +author: OpenStack Ansible SIG +description: + - Retrieve information about available OpenStack instance flavors. By default, + information about ALL flavors are retrieved. Filters can be applied to get + information for only matching flavors. For example, you can filter on the + amount of RAM available to the flavor, or the number of virtual CPUs + available to the flavor, or both. When specifying multiple filters, + *ALL* filters must match on a flavor before that flavor is returned as + a fact. + - This module was called C(openstack.cloud.compute_flavor_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.compute_flavor_info) module no longer returns C(ansible_facts)! +notes: + - The result contains a list of unsorted flavors. +options: + name: + description: + - A flavor name. Cannot be used with I(ram) or I(vcpus) or I(ephemeral). + type: str + ram: + description: + - "A string used for filtering flavors based on the amount of RAM + (in MB) desired. This string accepts the following special values: + 'MIN' (return flavors with the minimum amount of RAM), and 'MAX' + (return flavors with the maximum amount of RAM)." + + - "A specific amount of RAM may also be specified. Any flavors with this + exact amount of RAM will be returned." + + - "A range of acceptable RAM may be given using a special syntax. Simply + prefix the amount of RAM with one of these acceptable range values: + '<', '>', '<=', '>='. These values represent less than, greater than, + less than or equal to, and greater than or equal to, respectively." + type: str + vcpus: + description: + - A string used for filtering flavors based on the number of virtual + CPUs desired. Format is the same as the I(ram) parameter. + type: str + limit: + description: + - Limits the number of flavors returned. All matching flavors are + returned by default. + type: int + ephemeral: + description: + - A string used for filtering flavors based on the amount of ephemeral + storage. Format is the same as the I(ram) parameter + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather information about all available flavors +- openstack.cloud.compute_flavor_info: + cloud: mycloud + register: result + +- debug: + msg: "{{ result.openstack_flavors }}" + +# Gather information for the flavor named "xlarge-flavor" +- openstack.cloud.compute_flavor_info: + cloud: mycloud + name: "xlarge-flavor" + +# Get all flavors that have exactly 512 MB of RAM. +- openstack.cloud.compute_flavor_info: + cloud: mycloud + ram: "512" + +# Get all flavors that have 1024 MB or more of RAM. +- openstack.cloud.compute_flavor_info: + cloud: mycloud + ram: ">=1024" + +# Get a single flavor that has the minimum amount of RAM. Using the 'limit' +# option will guarantee only a single flavor is returned. +- openstack.cloud.compute_flavor_info: + cloud: mycloud + ram: "MIN" + limit: 1 + +# Get all flavors with 1024 MB of RAM or more, AND exactly 2 virtual CPUs. +- openstack.cloud.compute_flavor_info: + cloud: mycloud + ram: ">=1024" + vcpus: "2" + +# Get all flavors with 1024 MB of RAM or more, exactly 2 virtual CPUs, and +# less than 30gb of ephemeral storage. +- openstack.cloud.compute_flavor_info: + cloud: mycloud + ram: ">=1024" + vcpus: "2" + ephemeral: "<30" +''' + + +RETURN = ''' +openstack_flavors: + description: Dictionary describing the flavors. + returned: On success. + type: complex + contains: + id: + description: Flavor ID. + returned: success + type: str + sample: "515256b8-7027-4d73-aa54-4e30a4a4a339" + name: + description: Flavor name. + returned: success + type: str + sample: "tiny" + disk: + description: Size of local disk, in GB. + returned: success + type: int + sample: 10 + ephemeral: + description: Ephemeral space size, in GB. + returned: success + type: int + sample: 10 + ram: + description: Amount of memory, in MB. + returned: success + type: int + sample: 1024 + swap: + description: Swap space size, in MB. + returned: success + type: int + sample: 100 + vcpus: + description: Number of virtual CPUs. + returned: success + type: int + sample: 2 + is_public: + description: Make flavor accessible to the public. + returned: success + type: bool + sample: true +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + ram=dict(required=False, default=None), + vcpus=dict(required=False, default=None), + limit=dict(required=False, default=None, type='int'), + ephemeral=dict(required=False, default=None), + ) + module_kwargs = openstack_module_kwargs( + mutually_exclusive=[ + ['name', 'ram'], + ['name', 'vcpus'], + ['name', 'ephemeral'] + ] + ) + module = AnsibleModule(argument_spec, **module_kwargs) + is_old_facts = module._name == 'openstack.cloud.compute_flavor_facts' + if is_old_facts: + module.deprecate("The 'openstack.cloud.compute_flavor_facts' module has been renamed to 'openstack.cloud.compute_flavor_info', " + "and the renamed one no longer returns ansible_facts", version='2.13') + + name = module.params['name'] + vcpus = module.params['vcpus'] + ram = module.params['ram'] + ephemeral = module.params['ephemeral'] + limit = module.params['limit'] + + filters = {} + if vcpus: + filters['vcpus'] = vcpus + if ram: + filters['ram'] = ram + if ephemeral: + filters['ephemeral'] = ephemeral + + sdk, cloud = openstack_cloud_from_module(module) + try: + if name: + flavors = cloud.search_flavors(filters={'name': name}) + + else: + flavors = cloud.list_flavors() + if filters: + flavors = cloud.range_search(flavors, filters) + + if limit is not None: + flavors = flavors[:limit] + + if is_old_facts: + module.exit_json(changed=False, + ansible_facts=dict(openstack_flavors=flavors)) + else: + module.exit_json(changed=False, + openstack_flavors=flavors) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/config.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/config.py new file mode 100644 index 00000000..94036e49 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/config.py @@ -0,0 +1,76 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: config +short_description: Get OpenStack Client config +description: + - Get I(openstack) client config data from clouds.yaml or environment +notes: + - Facts are placed in the C(openstack.clouds) variable. +options: + clouds: + description: + - List of clouds to limit the return list to. No value means return + information on all configured clouds + required: false + default: [] + type: list + elements: str +requirements: + - "python >= 3.6" + - "openstacksdk" +author: OpenStack Ansible SIG +''' + +EXAMPLES = ''' +- name: Get list of clouds that do not support security groups + openstack.cloud.config: + +- debug: + var: "{{ item }}" + with_items: "{{ openstack.clouds | rejectattr('secgroup_source', 'none') | list }}" + +- name: Get the information back just about the mordred cloud + openstack.cloud.config: + clouds: + - mordred +''' + +try: + import openstack.config + from openstack import exceptions + HAS_OPENSTACKSDK = True +except ImportError: + HAS_OPENSTACKSDK = False + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule(argument_spec=dict( + clouds=dict(required=False, type='list', default=[], elements='str'), + )) + + if not HAS_OPENSTACKSDK: + module.fail_json(msg='openstacksdk is required for this module') + + p = module.params + + try: + config = openstack.config.OpenStackConfig() + clouds = [] + for cloud in config.get_all_clouds(): + if not p['clouds'] or cloud.name in p['clouds']: + cloud.config['name'] = cloud.name + clouds.append(cloud.config) + module.exit_json(ansible_facts=dict(openstack=dict(clouds=clouds))) + except exceptions.ConfigException as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/dns_zone.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/dns_zone.py new file mode 100644 index 00000000..98cf655e --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/dns_zone.py @@ -0,0 +1,244 @@ +#!/usr/bin/python +# Copyright (c) 2016 Hewlett-Packard Enterprise +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: dns_zone +short_description: Manage OpenStack DNS zones +author: OpenStack Ansible SIG +description: + - Manage OpenStack DNS zones. Zones can be created, deleted or + updated. Only the I(email), I(description), I(ttl) and I(masters) values + can be updated. +options: + name: + description: + - Zone name + required: true + type: str + zone_type: + description: + - Zone type + choices: [primary, secondary] + type: str + email: + description: + - Email of the zone owner (only applies if zone_type is primary) + type: str + description: + description: + - Zone description + type: str + ttl: + description: + - TTL (Time To Live) value in seconds + type: int + masters: + description: + - Master nameservers (only applies if zone_type is secondary) + type: list + elements: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a zone named "example.net" +- openstack.cloud.dns_zone: + cloud: mycloud + state: present + name: example.net. + zone_type: primary + email: test@example.net + description: Test zone + ttl: 3600 + +# Update the TTL on existing "example.net." zone +- openstack.cloud.dns_zone: + cloud: mycloud + state: present + name: example.net. + ttl: 7200 + +# Delete zone named "example.net." +- openstack.cloud.dns_zone: + cloud: mycloud + state: absent + name: example.net. +''' + +RETURN = ''' +zone: + description: Dictionary describing the zone. + returned: On success when I(state) is 'present'. + type: complex + contains: + id: + description: Unique zone ID + type: str + sample: "c1c530a3-3619-46f3-b0f6-236927b2618c" + name: + description: Zone name + type: str + sample: "example.net." + type: + description: Zone type + type: str + sample: "PRIMARY" + email: + description: Zone owner email + type: str + sample: "test@example.net" + description: + description: Zone description + type: str + sample: "Test description" + ttl: + description: Zone TTL value + type: int + sample: 3600 + masters: + description: Zone master nameservers + type: list + sample: [] +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class DnsZoneModule(OpenStackModule): + + argument_spec = dict( + name=dict(required=True, type='str'), + zone_type=dict(required=False, choices=['primary', 'secondary'], type='str'), + email=dict(required=False, type='str'), + description=dict(required=False, type='str'), + ttl=dict(required=False, type='int'), + masters=dict(required=False, type='list', elements='str'), + state=dict(default='present', choices=['absent', 'present'], type='str'), + ) + + def _system_state_change(self, state, email, description, ttl, masters, zone): + if state == 'present': + if not zone: + return True + if email is not None and zone.email != email: + return True + if description is not None and zone.description != description: + return True + if ttl is not None and zone.ttl != ttl: + return True + if masters is not None and zone.masters != masters: + return True + if state == 'absent' and zone: + return True + return False + + def _wait(self, timeout, zone, state): + """Wait for a zone to reach the desired state for the given state.""" + + for count in self.sdk.utils.iterate_timeout( + timeout, + "Timeout waiting for zone to be %s" % state): + + if (state == 'absent' and zone is None) or (state == 'present' and zone and zone.status == 'ACTIVE'): + return + + try: + zone = self.conn.get_zone(zone.id) + except Exception: + continue + + if zone and zone.status == 'ERROR': + self.fail_json(msg="Zone reached ERROR state while waiting for it to be %s" % state) + + def run(self): + + name = self.params['name'] + state = self.params['state'] + wait = self.params['wait'] + timeout = self.params['timeout'] + + zone = self.conn.get_zone(name) + + if state == 'present': + + zone_type = self.params['zone_type'] + email = self.params['email'] + description = self.params['description'] + ttl = self.params['ttl'] + masters = self.params['masters'] + + kwargs = {} + + if email: + kwargs['email'] = email + if description: + kwargs['description'] = description + if ttl: + kwargs['ttl'] = ttl + if masters: + kwargs['masters'] = masters + + if self.ansible.check_mode: + self.exit_json(changed=self._system_state_change(state, email, + description, ttl, + masters, zone)) + + if zone is None: + zone = self.conn.create_zone( + name=name, zone_type=zone_type, **kwargs) + changed = True + else: + if masters is None: + masters = [] + + pre_update_zone = zone + changed = self._system_state_change(state, email, + description, ttl, + masters, pre_update_zone) + if changed: + zone = self.conn.update_zone( + name, **kwargs) + + if wait: + self._wait(timeout, zone, state) + + self.exit_json(changed=changed, zone=zone) + + elif state == 'absent': + if self.ansible.check_mode: + self.exit_json(changed=self._system_state_change(state, None, + None, None, + None, zone)) + + if zone is None: + changed = False + else: + self.conn.delete_zone(name) + changed = True + + if wait: + self._wait(timeout, zone, state) + + self.exit_json(changed=changed) + + +def main(): + module = DnsZoneModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/endpoint.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/endpoint.py new file mode 100644 index 00000000..a570dc76 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/endpoint.py @@ -0,0 +1,211 @@ +#!/usr/bin/python + +# Copyright: (c) 2017, VEXXHOST, Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: endpoint +short_description: Manage OpenStack Identity service endpoints +author: OpenStack Ansible SIG +description: + - Create, update, or delete OpenStack Identity service endpoints. If a + service with the same combination of I(service), I(interface) and I(region) + exist, the I(url) and I(state) (C(present) or C(absent)) will be updated. +options: + service: + description: + - Name or id of the service. + required: true + type: str + endpoint_interface: + description: + - Interface of the service. + choices: [admin, public, internal] + required: true + type: str + url: + description: + - URL of the service. + required: true + type: str + region: + description: + - Region that the service belongs to. Note that I(region_name) is used for authentication. + type: str + enabled: + description: + - Is the service enabled. + default: True + type: bool + state: + description: + - Should the resource be C(present) or C(absent). + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.13.0" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Create a service for glance + openstack.cloud.endpoint: + cloud: mycloud + service: glance + endpoint_interface: public + url: http://controller:9292 + region: RegionOne + state: present + +- name: Delete a service for nova + openstack.cloud.endpoint: + cloud: mycloud + service: nova + endpoint_interface: public + region: RegionOne + state: absent +''' + +RETURN = ''' +endpoint: + description: Dictionary describing the endpoint. + returned: On success when I(state) is C(present) + type: complex + contains: + id: + description: Endpoint ID. + type: str + sample: 3292f020780b4d5baf27ff7e1d224c44 + region: + description: Region Name. + type: str + sample: RegionOne + service_id: + description: Service ID. + type: str + sample: b91f1318f735494a825a55388ee118f3 + interface: + description: Endpoint Interface. + type: str + sample: public + url: + description: Service URL. + type: str + sample: http://controller:9292 + enabled: + description: Service status. + type: bool + sample: True +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _needs_update(module, endpoint): + if endpoint.enabled != module.params['enabled']: + return True + if endpoint.url != module.params['url']: + return True + return False + + +def _system_state_change(module, endpoint): + state = module.params['state'] + if state == 'absent' and endpoint: + return True + + if state == 'present': + if endpoint is None: + return True + return _needs_update(module, endpoint) + + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + service=dict(type='str', required=True), + endpoint_interface=dict(type='str', required=True, choices=['admin', 'public', 'internal']), + url=dict(type='str', required=True), + region=dict(type='str'), + enabled=dict(type='bool', default=True), + state=dict(type='str', default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + service_name_or_id = module.params['service'] + interface = module.params['endpoint_interface'] + url = module.params['url'] + region = module.params['region'] + enabled = module.params['enabled'] + state = module.params['state'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + + service = cloud.get_service(service_name_or_id) + if service is None and state == 'absent': + module.exit_json(changed=False) + + elif service is None and state == 'present': + module.fail_json(msg='Service %s does not exist' % service_name_or_id) + + filters = dict(service_id=service.id, interface=interface) + if region is not None: + filters['region'] = region + endpoints = cloud.search_endpoints(filters=filters) + + if len(endpoints) > 1: + module.fail_json(msg='Service %s, interface %s and region %s are ' + 'not unique' % + (service_name_or_id, interface, region)) + elif len(endpoints) == 1: + endpoint = endpoints[0] + else: + endpoint = None + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, endpoint)) + + if state == 'present': + if endpoint is None: + result = cloud.create_endpoint(service_name_or_id=service, + url=url, interface=interface, + region=region, enabled=enabled) + endpoint = result[0] + changed = True + else: + if _needs_update(module, endpoint): + endpoint = cloud.update_endpoint( + endpoint.id, url=url, enabled=enabled) + changed = True + else: + changed = False + module.exit_json(changed=changed, endpoint=endpoint) + + elif state == 'absent': + if endpoint is None: + changed = False + else: + cloud.delete_endpoint(endpoint.id) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/federation_idp.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/federation_idp.py new file mode 100644 index 00000000..1a616860 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/federation_idp.py @@ -0,0 +1,242 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: federation_idp +short_description: manage a federation Identity Provider +author: OpenStack Ansible SIG +description: + - Manage a federation Identity Provider. +options: + name: + description: + - The name of the Identity Provider. + type: str + required: true + aliases: ['id'] + state: + description: + - Whether the Identity Provider should be C(present) or C(absent). + choices: ['present', 'absent'] + default: present + type: str + description: + description: + - The description of the Identity Provider. + type: str + domain_id: + description: + - The ID of a domain that is associated with the Identity Provider. + Federated users that authenticate with the Identity Provider will be + created under the domain specified. + - Required when creating a new Identity Provider. + type: str + enabled: + description: + - Whether the Identity Provider is enabled or not. + - Will default to C(true) when creating a new Identity Provider. + type: bool + aliases: ['is_enabled'] + remote_ids: + description: + - "List of the unique Identity Provider's remote IDs." + - Will default to an empty list when creating a new Identity Provider. + type: list + elements: str +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.44" +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Create an identity provider + openstack.cloud.federation_idp: + cloud: example_cloud + name: example_provider + domain_id: 0123456789abcdef0123456789abcdef + description: 'My example IDP' + remote_ids: + - 'https://auth.example.com/auth/realms/ExampleRealm' + +- name: Delete an identity provider + openstack.cloud.federation_idp: + cloud: example_cloud + name: example_provider + state: absent +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_module_kwargs +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_cloud_from_module + + +def normalize_idp(idp): + """ + Normalizes the IDP definitions so that the outputs are consistent with the + parameters + + - "enabled" (parameter) == "is_enabled" (SDK) + - "name" (parameter) == "id" (SDK) + """ + if idp is None: + return None + + _idp = idp.to_dict() + _idp['enabled'] = idp['is_enabled'] + _idp['name'] = idp['id'] + return _idp + + +def delete_identity_provider(module, sdk, cloud, idp): + """ + Delete an existing Identity Provider + + returns: the "Changed" state + """ + + if idp is None: + return False + + if module.check_mode: + return True + + try: + cloud.identity.delete_identity_provider(idp) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to delete identity provider: {0}'.format(str(ex))) + return True + + +def create_identity_provider(module, sdk, cloud, name): + """ + Create a new Identity Provider + + returns: the "Changed" state and the new identity provider + """ + + if module.check_mode: + return True, None + + description = module.params.get('description') + enabled = module.params.get('enabled') + domain_id = module.params.get('domain_id') + remote_ids = module.params.get('remote_ids') + + if enabled is None: + enabled = True + if remote_ids is None: + remote_ids = [] + + attributes = { + 'domain_id': domain_id, + 'enabled': enabled, + 'remote_ids': remote_ids, + } + if description is not None: + attributes['description'] = description + + try: + idp = cloud.identity.create_identity_provider(id=name, **attributes) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to create identity provider: {0}'.format(str(ex))) + return (True, idp) + + +def update_identity_provider(module, sdk, cloud, idp): + """ + Update an existing Identity Provider + + returns: the "Changed" state and the new identity provider + """ + + description = module.params.get('description') + enabled = module.params.get('enabled') + domain_id = module.params.get('domain_id') + remote_ids = module.params.get('remote_ids') + + attributes = {} + + if (description is not None) and (description != idp.description): + attributes['description'] = description + if (enabled is not None) and (enabled != idp.is_enabled): + attributes['enabled'] = enabled + if (domain_id is not None) and (domain_id != idp.domain_id): + attributes['domain_id'] = domain_id + if (remote_ids is not None) and (remote_ids != idp.remote_ids): + attributes['remote_ids'] = remote_ids + + if not attributes: + return False, idp + + if module.check_mode: + return True, None + + try: + new_idp = cloud.identity.update_identity_provider(idp, **attributes) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to update identity provider: {0}'.format(str(ex))) + return (True, new_idp) + + +def main(): + """ Module entry point """ + + argument_spec = openstack_full_argument_spec( + name=dict(required=True, aliases=['id']), + state=dict(default='present', choices=['absent', 'present']), + description=dict(), + domain_id=dict(), + enabled=dict(type='bool', aliases=['is_enabled']), + remote_ids=dict(type='list', elements='str'), + ) + module_kwargs = openstack_module_kwargs( + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params.get('name') + state = module.params.get('state') + changed = False + + sdk, cloud = openstack_cloud_from_module(module, min_version="0.44") + + try: + idp = cloud.identity.get_identity_provider(name) + except sdk.exceptions.ResourceNotFound: + idp = None + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to get identity provider: {0}'.format(str(ex))) + + if state == 'absent': + if idp is not None: + changed = delete_identity_provider(module, sdk, cloud, idp) + module.exit_json(changed=changed) + + # state == 'present' + else: + if idp is None: + if module.params.get('domain_id') is None: + module.fail_json(msg='A domain_id must be passed when creating' + ' an identity provider') + (changed, idp) = create_identity_provider(module, sdk, cloud, name) + idp = normalize_idp(idp) + module.exit_json(changed=changed, identity_provider=idp) + + (changed, new_idp) = update_identity_provider(module, sdk, cloud, idp) + new_idp = normalize_idp(new_idp) + module.exit_json(changed=changed, identity_provider=new_idp) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/federation_idp_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/federation_idp_info.py new file mode 100644 index 00000000..ced8a7a6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/federation_idp_info.py @@ -0,0 +1,101 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: federation_idp_info +short_description: Get the information about the available federation identity + providers +author: OpenStack Ansible SIG +description: + - Fetch a federation identity provider. +options: + name: + description: + - The name of the identity provider to fetch. + - If I(name) is specified, the module will return failed if the identity + provider doesn't exist. + type: str + aliases: ['id'] +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.44" +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Fetch a specific identity provider + openstack.cloud.federation_idp_info: + cloud: example_cloud + name: example_provider + +- name: Fetch all providers + openstack.cloud.federation_idp_info: + cloud: example_cloud +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_module_kwargs +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_cloud_from_module + + +def normalize_idp(idp): + """ + Normalizes the IDP definitions so that the outputs are consistent with the + parameters + + - "enabled" (parameter) == "is_enabled" (SDK) + - "name" (parameter) == "id" (SDK) + """ + if idp is None: + return + + _idp = idp.to_dict() + _idp['enabled'] = idp['is_enabled'] + _idp['name'] = idp['id'] + return _idp + + +def main(): + """ Module entry point """ + + argument_spec = openstack_full_argument_spec( + name=dict(aliases=['id']), + ) + module_kwargs = openstack_module_kwargs( + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params.get('name') + + sdk, cloud = openstack_cloud_from_module(module, min_version="0.44") + + if name: + try: + idp = normalize_idp(cloud.identity.get_identity_provider(name)) + except sdk.exceptions.ResourceNotFound: + module.fail_json(msg='Failed to find identity provider') + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to get identity provider: {0}'.format(str(ex))) + module.exit_json(changed=False, identity_providers=[idp]) + + else: + try: + providers = list(map(normalize_idp, cloud.identity.identity_providers())) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to list identity providers: {0}'.format(str(ex))) + module.exit_json(changed=False, identity_providers=providers) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/federation_mapping.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/federation_mapping.py new file mode 100644 index 00000000..ad1461ab --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/federation_mapping.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: federation_mapping +short_description: Manage a federation mapping +author: OpenStack Ansible SIG +description: + - Manage a federation mapping. +options: + name: + description: + - The name of the mapping to manage. + required: true + type: str + aliases: ['id'] + state: + description: + - Whether the mapping should be C(present) or C(absent). + choices: ['present', 'absent'] + default: present + type: str + rules: + description: + - The rules that comprise the mapping. These are pairs of I(local) and + I(remote) definitions. For more details on how these work please see + the OpenStack documentation + U(https://docs.openstack.org/keystone/latest/admin/federation/mapping_combinations.html). + - Required if I(state=present) + type: list + elements: dict + suboptions: + local: + description: + - Information on what local attributes will be mapped. + required: true + type: list + elements: dict + remote: + description: + - Information on what remote attributes will be mapped. + required: true + type: list + elements: dict +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.44" +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Create a new mapping + openstack.cloud.federation_mapping: + cloud: example_cloud + name: example_mapping + rules: + - local: + - user: + name: '{0}' + - group: + id: '0cd5e9' + remote: + - type: UserName + - type: orgPersonType + any_one_of: + - Contractor + - SubContractor + +- name: Delete a mapping + openstack.cloud.federation_mapping: + name: example_mapping + state: absent +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_module_kwargs +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_cloud_from_module + + +def normalize_mapping(mapping): + """ + Normalizes the mapping definitions so that the outputs are consistent with + the parameters + + - "name" (parameter) == "id" (SDK) + """ + if mapping is None: + return None + + _mapping = mapping.to_dict() + _mapping['name'] = mapping['id'] + return _mapping + + +def create_mapping(module, sdk, cloud, name): + """ + Attempt to create a Mapping + + returns: A tuple containing the "Changed" state and the created mapping + """ + + if module.check_mode: + return (True, None) + + rules = module.params.get('rules') + + try: + mapping = cloud.identity.create_mapping(id=name, rules=rules) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to create mapping: {0}'.format(str(ex))) + return (True, mapping) + + +def delete_mapping(module, sdk, cloud, mapping): + """ + Attempt to delete a Mapping + + returns: the "Changed" state + """ + if mapping is None: + return False + + if module.check_mode: + return True + + try: + cloud.identity.delete_mapping(mapping) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to delete mapping: {0}'.format(str(ex))) + return True + + +def update_mapping(module, sdk, cloud, mapping): + """ + Attempt to delete a Mapping + + returns: The "Changed" state and the the new mapping + """ + + current_rules = mapping.rules + new_rules = module.params.get('rules') + + # Nothing to do + if current_rules == new_rules: + return (False, mapping) + + if module.check_mode: + return (True, None) + + try: + new_mapping = cloud.identity.update_mapping(mapping, rules=new_rules) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to update mapping: {0}'.format(str(ex))) + return (True, new_mapping) + + +def main(): + """ Module entry point """ + + argument_spec = openstack_full_argument_spec( + name=dict(required=True, aliases=['id']), + state=dict(default='present', choices=['absent', 'present']), + rules=dict(type='list', elements='dict', options=dict( + local=dict(required=True, type='list', elements='dict'), + remote=dict(required=True, type='list', elements='dict') + )), + ) + module_kwargs = openstack_module_kwargs( + required_if=[('state', 'present', ['rules'])] + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params.get('name') + state = module.params.get('state') + changed = False + + sdk, cloud = openstack_cloud_from_module(module, min_version="0.44") + + try: + mapping = cloud.identity.get_mapping(name) + except sdk.exceptions.ResourceNotFound: + mapping = None + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to fetch mapping: {0}'.format(str(ex))) + + if state == 'absent': + if mapping is not None: + changed = delete_mapping(module, sdk, cloud, mapping) + module.exit_json(changed=changed) + + # state == 'present' + else: + if len(module.params.get('rules')) < 1: + module.fail_json(msg='At least one rule must be passed') + + if mapping is None: + (changed, mapping) = create_mapping(module, sdk, cloud, name) + mapping = normalize_mapping(mapping) + module.exit_json(changed=changed, mapping=mapping) + else: + (changed, new_mapping) = update_mapping(module, sdk, cloud, mapping) + new_mapping = normalize_mapping(new_mapping) + module.exit_json(mapping=new_mapping, changed=changed) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/federation_mapping_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/federation_mapping_info.py new file mode 100644 index 00000000..c98f273b --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/federation_mapping_info.py @@ -0,0 +1,98 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: federation_mapping_info +short_description: Get the information about the available federation mappings +author: OpenStack Ansible SIG +description: + - Fetch a federation mapping. +options: + name: + description: + - The name of the mapping to fetch. + - If I(name) is specified, the module will return failed if the mapping + doesn't exist. + type: str + aliases: ['id'] +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.44" +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Fetch a specific mapping + openstack.cloud.federation_mapping_info: + cloud: example_cloud + name: example_mapping + +- name: Fetch all mappings + openstack.cloud.federation_mapping_info: + cloud: example_cloud +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_module_kwargs +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_cloud_from_module + + +def normalize_mapping(mapping): + """ + Normalizes the mapping definitions so that the outputs are consistent with the + parameters + + - "name" (parameter) == "id" (SDK) + """ + if mapping is None: + return None + + _mapping = mapping.to_dict() + _mapping['name'] = mapping['id'] + return _mapping + + +def main(): + """ Module entry point """ + + argument_spec = openstack_full_argument_spec( + name=dict(aliases=['id']), + ) + module_kwargs = openstack_module_kwargs( + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params.get('name') + + sdk, cloud = openstack_cloud_from_module(module, min_version="0.44") + + if name: + try: + mapping = normalize_mapping(cloud.identity.get_mapping(name)) + except sdk.exceptions.ResourceNotFound: + module.fail_json(msg='Failed to find mapping') + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to get mapping: {0}'.format(str(ex))) + module.exit_json(changed=False, mappings=[mapping]) + + else: + try: + mappings = list(map(normalize_mapping, cloud.identity.mappings())) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to list mappings: {0}'.format(str(ex))) + module.exit_json(changed=False, mappings=mappings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/floating_ip.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/floating_ip.py new file mode 100644 index 00000000..dbf6ee0d --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/floating_ip.py @@ -0,0 +1,255 @@ +#!/usr/bin/python + +# Copyright: (c) 2015, Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: floating_ip +author: OpenStack Ansible SIG +short_description: Add/Remove floating IP from an instance +description: + - Add or Remove a floating IP to an instance. + - Returns the floating IP when attaching only if I(wait=true). +options: + server: + description: + - The name or ID of the instance to which the IP address + should be assigned. + required: true + type: str + network: + description: + - The name or ID of a neutron external network or a nova pool name. + type: str + floating_ip_address: + description: + - A floating IP address to attach or to detach. Required only if I(state) + is absent. When I(state) is present can be used to specify a IP address + to attach. + type: str + reuse: + description: + - When I(state) is present, and I(floating_ip_address) is not present, + this parameter can be used to specify whether we should try to reuse + a floating IP address already allocated to the project. + type: bool + default: 'no' + fixed_address: + description: + - To which fixed IP of server the floating IP address should be + attached to. + type: str + nat_destination: + description: + - The name or id of a neutron private network that the fixed IP to + attach floating IP is on + aliases: ["fixed_network", "internal_network"] + type: str + wait: + description: + - When attaching a floating IP address, specify whether to wait for it to appear as attached. + - Must be set to C(yes) for the module to return the value of the floating IP. + type: bool + default: 'no' + timeout: + description: + - Time to wait for an IP address to appear as attached. See wait. + required: false + default: 60 + type: int + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + purge: + description: + - When I(state) is absent, indicates whether or not to delete the floating + IP completely, or only detach it from the server. Default is to detach only. + type: bool + default: 'no' +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Assign a floating IP to the first interface of `cattle001` from an existing +# external network or nova pool. A new floating IP from the first available +# external network is allocated to the project. +- openstack.cloud.floating_ip: + cloud: dguerri + server: cattle001 + +# Assign a new floating IP to the instance fixed ip `192.0.2.3` of +# `cattle001`. If a free floating IP is already allocated to the project, it is +# reused; if not, a new one is created. +- openstack.cloud.floating_ip: + cloud: dguerri + state: present + reuse: yes + server: cattle001 + network: ext_net + fixed_address: 192.0.2.3 + wait: true + timeout: 180 + +# Assign a new floating IP from the network `ext_net` to the instance fixed +# ip in network `private_net` of `cattle001`. +- openstack.cloud.floating_ip: + cloud: dguerri + state: present + server: cattle001 + network: ext_net + nat_destination: private_net + wait: true + timeout: 180 + +# Detach a floating IP address from a server +- openstack.cloud.floating_ip: + cloud: dguerri + state: absent + floating_ip_address: 203.0.113.2 + server: cattle001 +''' + +from ansible.module_utils.basic import AnsibleModule, remove_values +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _get_floating_ip(cloud, floating_ip_address): + f_ips = cloud.search_floating_ips( + filters={'floating_ip_address': floating_ip_address}) + if not f_ips: + return None + + return f_ips[0] + + +def main(): + argument_spec = openstack_full_argument_spec( + server=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + network=dict(required=False, default=None), + floating_ip_address=dict(required=False, default=None), + reuse=dict(required=False, type='bool', default=False), + fixed_address=dict(required=False, default=None), + nat_destination=dict(required=False, default=None, + aliases=['fixed_network', 'internal_network']), + wait=dict(required=False, type='bool', default=False), + timeout=dict(required=False, type='int', default=60), + purge=dict(required=False, type='bool', default=False), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + server_name_or_id = module.params['server'] + state = module.params['state'] + network = module.params['network'] + floating_ip_address = module.params['floating_ip_address'] + reuse = module.params['reuse'] + fixed_address = module.params['fixed_address'] + nat_destination = module.params['nat_destination'] + wait = module.params['wait'] + timeout = module.params['timeout'] + purge = module.params['purge'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + + server = cloud.get_server(server_name_or_id) + if server is None: + module.fail_json( + msg="server {0} not found".format(server_name_or_id)) + + if state == 'present': + # If f_ip already assigned to server, check that it matches + # requirements. + public_ip = cloud.get_server_public_ip(server) + f_ip = _get_floating_ip(cloud, public_ip) if public_ip else public_ip + if f_ip: + if network: + network_id = cloud.get_network(name_or_id=network)["id"] + else: + network_id = None + # check if we have floating ip on given nat_destination network + if nat_destination: + nat_floating_addrs = [ + addr for addr in server.addresses.get( + cloud.get_network(nat_destination)['name'], []) + if addr['addr'] == public_ip + and addr['OS-EXT-IPS:type'] == 'floating' + ] + + if len(nat_floating_addrs) == 0: + module.fail_json(msg="server {server} already has a " + "floating-ip on a different " + "nat-destination than '{nat_destination}'" + .format(server=server_name_or_id, + nat_destination=nat_destination)) + + if all([fixed_address, f_ip.fixed_ip_address == fixed_address, + network, f_ip.network != network_id]): + # Current state definitely conflicts with requirements + module.fail_json(msg="server {server} already has a " + "floating-ip on requested " + "interface but it doesn't match " + "requested network {network}: {fip}" + .format(server=server_name_or_id, + network=network, + fip=remove_values(f_ip, + module.no_log_values))) + if not network or f_ip.network == network_id: + # Requirements are met + module.exit_json(changed=False, floating_ip=f_ip) + + # Requirements are vague enough to ignore existing f_ip and try + # to create a new f_ip to the server. + + server = cloud.add_ips_to_server( + server=server, ips=floating_ip_address, ip_pool=network, + reuse=reuse, fixed_address=fixed_address, wait=wait, + timeout=timeout, nat_destination=nat_destination) + fip_address = cloud.get_server_public_ip(server) + # Update the floating IP status + f_ip = _get_floating_ip(cloud, fip_address) + module.exit_json(changed=True, floating_ip=f_ip) + + elif state == 'absent': + if floating_ip_address is None: + if not server_name_or_id: + module.fail_json(msg="either server or floating_ip_address are required") + server = cloud.get_server(server_name_or_id) + floating_ip_address = cloud.get_server_public_ip(server) + + f_ip = _get_floating_ip(cloud, floating_ip_address) + + if not f_ip: + # Nothing to detach + module.exit_json(changed=False) + changed = False + if f_ip["fixed_ip_address"]: + cloud.detach_ip_from_server( + server_id=server['id'], floating_ip_id=f_ip['id']) + # Update the floating IP status + f_ip = cloud.get_floating_ip(id=f_ip['id']) + changed = True + if purge: + cloud.delete_floating_ip(f_ip['id']) + module.exit_json(changed=True) + module.exit_json(changed=changed, floating_ip=f_ip) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/group_assignment.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/group_assignment.py new file mode 100644 index 00000000..59760acf --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/group_assignment.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: group_assignment +short_description: Associate OpenStack Identity users and groups +author: OpenStack Ansible SIG +description: + - Add and remove users from groups +options: + user: + description: + - Name or id for the user + required: true + type: str + group: + description: + - Name or id for the group. + required: true + type: str + state: + description: + - Should the user be present or absent in the group + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Add the demo user to the demo group +- openstack.cloud.group_assignment: + cloud: mycloud + user: demo + group: demo +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(state, in_group): + if state == 'present' and not in_group: + return True + if state == 'absent' and in_group: + return True + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + user=dict(required=True), + group=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + user = module.params['user'] + group = module.params['group'] + state = module.params['state'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + in_group = cloud.is_user_in_group(user, group) + + if module.check_mode: + module.exit_json(changed=_system_state_change(state, in_group)) + + changed = False + if state == 'present': + if not in_group: + cloud.add_user_to_group(user, group) + changed = True + + elif state == 'absent': + if in_group: + cloud.remove_user_from_group(user, group) + changed = True + + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/host_aggregate.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/host_aggregate.py new file mode 100644 index 00000000..303a3d8a --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/host_aggregate.py @@ -0,0 +1,219 @@ +#!/usr/bin/python +# Copyright 2016 Jakub Jursa <jakub.jursa1@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: host_aggregate +short_description: Manage OpenStack host aggregates +author: OpenStack Ansible SIG +description: + - Create, update, or delete OpenStack host aggregates. If a aggregate + with the supplied name already exists, it will be updated with the + new name, new availability zone, new metadata and new list of hosts. +options: + name: + description: Name of the aggregate. + required: true + type: str + metadata: + description: Metadata dict. + type: dict + availability_zone: + description: Availability zone to create aggregate into. + type: str + hosts: + description: List of hosts to set for an aggregate. + type: list + elements: str + purge_hosts: + description: Whether hosts not in I(hosts) should be removed from the aggregate + type: bool + default: true + state: + description: Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a host aggregate +- openstack.cloud.host_aggregate: + cloud: mycloud + state: present + name: db_aggregate + hosts: + - host1 + - host2 + metadata: + type: dbcluster + +# Add an additional host to the aggregate +- openstack.cloud.host_aggregate: + cloud: mycloud + state: present + name: db_aggregate + hosts: + - host3 + purge_hosts: false + metadata: + type: dbcluster + +# Delete an aggregate +- openstack.cloud.host_aggregate: + cloud: mycloud + state: absent + name: db_aggregate +''' + +RETURN = ''' + +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _needs_update(module, aggregate): + new_metadata = (module.params['metadata'] or {}) + + if module.params['availability_zone'] is not None: + new_metadata['availability_zone'] = module.params['availability_zone'] + + if module.params['name'] != aggregate.name: + return True + if module.params['hosts'] is not None: + if module.params['purge_hosts']: + if set(module.params['hosts']) != set(aggregate.hosts): + return True + else: + intersection = set(module.params['hosts']).intersection(set(aggregate.hosts)) + if set(module.params['hosts']) != intersection: + return True + if module.params['availability_zone'] is not None: + if module.params['availability_zone'] != aggregate.availability_zone: + return True + if module.params['metadata'] is not None: + if new_metadata != aggregate.metadata: + return True + + return False + + +def _system_state_change(module, aggregate): + state = module.params['state'] + if state == 'absent' and aggregate: + return True + + if state == 'present': + if aggregate is None: + return True + return _needs_update(module, aggregate) + + return False + + +def _update_hosts(cloud, aggregate, hosts, purge_hosts): + if hosts is None: + return + + hosts_to_add = set(hosts) - set(aggregate.hosts) + for i in hosts_to_add: + cloud.add_host_to_aggregate(aggregate.id, i) + + if not purge_hosts: + return + + hosts_to_remove = set(aggregate.hosts) - set(hosts) + for i in hosts_to_remove: + cloud.remove_host_from_aggregate(aggregate.id, i) + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + metadata=dict(required=False, default=None, type='dict'), + availability_zone=dict(required=False, default=None), + hosts=dict(required=False, default=None, type='list', elements='str'), + purge_hosts=dict(default=True, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + name = module.params['name'] + metadata = module.params['metadata'] + availability_zone = module.params['availability_zone'] + hosts = module.params['hosts'] + purge_hosts = module.params['purge_hosts'] + state = module.params['state'] + + if metadata is not None: + metadata.pop('availability_zone', None) + + sdk, cloud = openstack_cloud_from_module(module) + try: + aggregates = cloud.search_aggregates(name_or_id=name) + + if len(aggregates) == 1: + aggregate = aggregates[0] + elif len(aggregates) == 0: + aggregate = None + else: + raise Exception("Should not happen") + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, aggregate)) + + if state == 'present': + if aggregate is None: + aggregate = cloud.create_aggregate(name=name, + availability_zone=availability_zone) + _update_hosts(cloud, aggregate, hosts, False) + if metadata: + cloud.set_aggregate_metadata(aggregate.id, metadata) + changed = True + else: + if _needs_update(module, aggregate): + if availability_zone is not None: + aggregate = cloud.update_aggregate(aggregate.id, name=name, + availability_zone=availability_zone) + if metadata is not None: + metas = metadata + for i in (set(aggregate.metadata.keys()) - set(metadata.keys())): + if i != 'availability_zone': + metas[i] = None + cloud.set_aggregate_metadata(aggregate.id, metas) + _update_hosts(cloud, aggregate, hosts, purge_hosts) + changed = True + else: + changed = False + module.exit_json(changed=changed) + + elif state == 'absent': + if aggregate is None: + changed = False + else: + _update_hosts(cloud, aggregate, [], True) + cloud.delete_aggregate(aggregate.id) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_domain.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_domain.py new file mode 100644 index 00000000..6c4ef600 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_domain.py @@ -0,0 +1,177 @@ +#!/usr/bin/python +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_domain +short_description: Manage OpenStack Identity Domains +author: OpenStack Ansible SIG +description: + - Create, update, or delete OpenStack Identity domains. If a domain + with the supplied name already exists, it will be updated with the + new description and enabled attributes. +options: + name: + description: + - Name that has to be given to the instance + required: true + type: str + description: + description: + - Description of the domain + type: str + enabled: + description: + - Is the domain enabled + type: bool + default: 'yes' + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a domain +- openstack.cloud.identity_domain: + cloud: mycloud + state: present + name: demo + description: Demo Domain + +# Delete a domain +- openstack.cloud.identity_domain: + cloud: mycloud + state: absent + name: demo +''' + +RETURN = ''' +domain: + description: Dictionary describing the domain. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Domain ID. + type: str + sample: "474acfe5-be34-494c-b339-50f06aa143e4" + name: + description: Domain name. + type: str + sample: "demo" + description: + description: Domain description. + type: str + sample: "Demo Domain" + enabled: + description: Domain description. + type: bool + sample: True + +id: + description: The domain ID. + returned: On success when I(state) is 'present' + type: str + sample: "474acfe5-be34-494c-b339-50f06aa143e4" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _needs_update(module, domain): + if module.params['description'] is not None and \ + domain.description != module.params['description']: + return True + if domain.enabled != module.params['enabled']: + return True + return False + + +def _system_state_change(module, domain): + state = module.params['state'] + if state == 'absent' and domain: + return True + + if state == 'present': + if domain is None: + return True + return _needs_update(module, domain) + + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + description=dict(default=None), + enabled=dict(default=True, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + name = module.params['name'] + description = module.params['description'] + enabled = module.params['enabled'] + state = module.params['state'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + + domains = cloud.search_domains(filters=dict(name=name)) + + if len(domains) > 1: + module.fail_json(msg='Domain name %s is not unique' % name) + elif len(domains) == 1: + domain = domains[0] + else: + domain = None + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, domain)) + + if state == 'present': + if domain is None: + domain = cloud.create_domain( + name=name, description=description, enabled=enabled) + changed = True + else: + if _needs_update(module, domain): + domain = cloud.update_domain( + domain.id, name=name, description=description, + enabled=enabled) + changed = True + else: + changed = False + module.exit_json(changed=changed, domain=domain, id=domain.id) + + elif state == 'absent': + if domain is None: + changed = False + else: + cloud.delete_domain(domain.id) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_domain_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_domain_info.py new file mode 100644 index 00000000..05f42127 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_domain_info.py @@ -0,0 +1,133 @@ +#!/usr/bin/python +# Copyright (c) 2016 Hewlett-Packard Enterprise Corporation +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_domain_info +short_description: Retrieve information about one or more OpenStack domains +author: OpenStack Ansible SIG +description: + - Retrieve information about a one or more OpenStack domains + - This module was called C(openstack.cloud.identity_domain_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.identity_domain_info) module no longer returns C(ansible_facts)! +options: + name: + description: + - Name or ID of the domain + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather information about previously created domain +- openstack.cloud.identity_domain_info: + cloud: awesomecloud + register: result +- debug: + msg: "{{ result.openstack_domains }}" + +# Gather information about a previously created domain by name +- openstack.cloud.identity_domain_info: + cloud: awesomecloud + name: demodomain + register: result +- debug: + msg: "{{ result.openstack_domains }}" + +# Gather information about a previously created domain with filter +- openstack.cloud.identity_domain_info: + cloud: awesomecloud + name: demodomain + filters: + enabled: false + register: result +- debug: + msg: "{{ result.openstack_domains }}" +''' + + +RETURN = ''' +openstack_domains: + description: has all the OpenStack information about domains + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the domain. + returned: success + type: str + description: + description: Description of the domain. + returned: success + type: str + enabled: + description: Flag to indicate if the domain is enabled. + returned: success + type: bool +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + filters=dict(required=False, type='dict', default=None), + ) + module_kwargs = openstack_module_kwargs( + mutually_exclusive=[ + ['name', 'filters'], + ] + ) + module = AnsibleModule(argument_spec, **module_kwargs) + is_old_facts = module._name == 'openstack.cloud.identity_domain_facts' + if is_old_facts: + module.deprecate("The 'openstack.cloud.identity_domain_facts' module has been renamed to 'openstack.cloud.identity_domain_info', " + "and the renamed one no longer returns ansible_facts", version='2.13') + + sdk, opcloud = openstack_cloud_from_module(module) + try: + name = module.params['name'] + filters = module.params['filters'] + + if name: + # Let's suppose user is passing domain ID + try: + domains = opcloud.get_domain(name) + except Exception: + domains = opcloud.search_domains(filters={'name': name}) + + else: + domains = opcloud.search_domains(filters) + + if is_old_facts: + module.exit_json(changed=False, ansible_facts=dict( + openstack_domains=domains)) + else: + module.exit_json(changed=False, openstack_domains=domains) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_group.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_group.py new file mode 100644 index 00000000..116650e5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_group.py @@ -0,0 +1,161 @@ +#!/usr/bin/python +# Copyright (c) 2016 IBM +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_group +short_description: Manage OpenStack Identity Groups +author: OpenStack Ansible SIG +description: + - Manage OpenStack Identity Groups. Groups can be created, deleted or + updated. Only the I(description) value can be updated. +options: + name: + description: + - Group name + required: true + type: str + description: + description: + - Group description + type: str + domain_id: + description: + - Domain id to create the group in if the cloud supports domains. + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a group named "demo" +- openstack.cloud.identity_group: + cloud: mycloud + state: present + name: demo + description: "Demo Group" + domain_id: demoid + +# Update the description on existing "demo" group +- openstack.cloud.identity_group: + cloud: mycloud + state: present + name: demo + description: "Something else" + domain_id: demoid + +# Delete group named "demo" +- openstack.cloud.identity_group: + cloud: mycloud + state: absent + name: demo +''' + +RETURN = ''' +group: + description: Dictionary describing the group. + returned: On success when I(state) is 'present'. + type: complex + contains: + id: + description: Unique group ID + type: str + sample: "ee6156ff04c645f481a6738311aea0b0" + name: + description: Group name + type: str + sample: "demo" + description: + description: Group description + type: str + sample: "Demo Group" + domain_id: + description: Domain for the group + type: str + sample: "default" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(state, description, group): + if state == 'present' and not group: + return True + if state == 'present' and description is not None and group.description != description: + return True + if state == 'absent' and group: + return True + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + description=dict(required=False, default=None), + domain_id=dict(required=False, default=None), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + name = module.params.get('name') + description = module.params.get('description') + state = module.params.get('state') + + domain_id = module.params.pop('domain_id') + + sdk, cloud = openstack_cloud_from_module(module) + try: + if domain_id: + group = cloud.get_group(name, filters={'domain_id': domain_id}) + else: + group = cloud.get_group(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(state, description, group)) + + if state == 'present': + if group is None: + group = cloud.create_group( + name=name, description=description, domain=domain_id) + changed = True + else: + if description is not None and group.description != description: + group = cloud.update_group( + group.id, description=description) + changed = True + else: + changed = False + module.exit_json(changed=changed, group=group) + + elif state == 'absent': + if group is None: + changed = False + else: + cloud.delete_group(group.id) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_group_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_group_info.py new file mode 100644 index 00000000..c3e78080 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_group_info.py @@ -0,0 +1,158 @@ +#!/usr/bin/python + +# Copyright (c) 2019, Phillipe Smith <phillipelnx@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_group_info +short_description: Retrieve info about one or more OpenStack groups +author: OpenStack Ansible SIG +description: + - Retrieve info about a one or more OpenStack groups. +options: + name: + description: + - Name or ID of the group. + type: str + domain: + description: + - Name or ID of the domain containing the group if the cloud supports domains + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather info about previously created groups +- name: gather info + hosts: localhost + tasks: + - name: Gather info about previously created groups + openstack.cloud.identity_group_info: + cloud: awesomecloud + register: openstack_groups + - debug: + var: openstack_groups + +# Gather info about a previously created group by name +- name: gather info + hosts: localhost + tasks: + - name: Gather info about a previously created group by name + openstack.cloud.identity_group_info: + cloud: awesomecloud + name: demogroup + register: openstack_groups + - debug: + var: openstack_groups + +# Gather info about a previously created group in a specific domain +- name: gather info + hosts: localhost + tasks: + - name: Gather info about a previously created group in a specific domain + openstack.cloud.identity_group_info: + cloud: awesomecloud + name: demogroup + domain: admindomain + register: openstack_groups + - debug: + var: openstack_groups + +# Gather info about a previously created group in a specific domain with filter +- name: gather info + hosts: localhost + tasks: + - name: Gather info about a previously created group in a specific domain with filter + openstack.cloud.identity_group_info: + cloud: awesomecloud + name: demogroup + domain: admindomain + filters: + enabled: False + register: openstack_groups + - debug: + var: openstack_groups +''' + + +RETURN = ''' +openstack_groups: + description: Dictionary describing all the matching groups. + returned: always, but can be null + type: complex + contains: + name: + description: Name given to the group. + returned: success + type: str + description: + description: Description of the group. + returned: success + type: str + id: + description: Unique UUID. + returned: success + type: str + domain_id: + description: Domain ID containing the group (keystone v3 clouds only) + returned: success + type: bool +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec, openstack_cloud_from_module + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + domain=dict(required=False, default=None), + filters=dict(required=False, type='dict', default=None), + ) + + module = AnsibleModule(argument_spec) + + sdk, opcloud = openstack_cloud_from_module(module) + try: + name = module.params['name'] + domain = module.params['domain'] + filters = module.params['filters'] + + if domain: + try: + # We assume admin is passing domain id + dom = opcloud.get_domain(domain)['id'] + domain = dom + except Exception: + # If we fail, maybe admin is passing a domain name. + # Note that domains have unique names, just like id. + dom = opcloud.search_domains(filters={'name': domain}) + if dom: + domain = dom[0]['id'] + else: + module.fail_json(msg='Domain name or ID does not exist') + + if not filters: + filters = {} + + groups = opcloud.search_groups(name, filters, domain_id=domain) + module.exit_json(changed=False, groups=groups) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_role.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_role.py new file mode 100644 index 00000000..3d850637 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_role.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# Copyright (c) 2016 IBM +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_role +short_description: Manage OpenStack Identity Roles +author: OpenStack Ansible SIG +description: + - Manage OpenStack Identity Roles. +options: + name: + description: + - Role Name + required: true + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a role named "demo" +- openstack.cloud.identity_role: + cloud: mycloud + state: present + name: demo + +# Delete the role named "demo" +- openstack.cloud.identity_role: + cloud: mycloud + state: absent + name: demo +''' + +RETURN = ''' +role: + description: Dictionary describing the role. + returned: On success when I(state) is 'present'. + type: complex + contains: + id: + description: Unique role ID. + type: str + sample: "677bfab34c844a01b88a217aa12ec4c2" + name: + description: Role name. + type: str + sample: "demo" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(state, role): + if state == 'present' and not role: + return True + if state == 'absent' and role: + return True + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + name = module.params.get('name') + state = module.params.get('state') + + sdk, cloud = openstack_cloud_from_module(module) + try: + role = cloud.get_role(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(state, role)) + + if state == 'present': + if role is None: + role = cloud.create_role(name) + changed = True + else: + changed = False + module.exit_json(changed=changed, role=role) + elif state == 'absent': + if role is None: + changed = False + else: + cloud.delete_role(name) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_user.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_user.py new file mode 100644 index 00000000..79ee5f23 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_user.py @@ -0,0 +1,293 @@ +#!/usr/bin/python +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_user +short_description: Manage OpenStack Identity Users +author: OpenStack Ansible SIG +description: + - Manage OpenStack Identity users. Users can be created, + updated or deleted using this module. A user will be updated + if I(name) matches an existing user and I(state) is present. + The value for I(name) cannot be updated without deleting and + re-creating the user. +options: + name: + description: + - Username for the user + required: true + type: str + password: + description: + - Password for the user + type: str + update_password: + required: false + choices: ['always', 'on_create'] + description: + - C(always) will attempt to update password. C(on_create) will only + set the password for newly created users. + type: str + email: + description: + - Email address for the user + type: str + description: + description: + - Description about the user + type: str + default_project: + description: + - Project name or ID that the user should be associated with by default + type: str + domain: + description: + - Domain to create the user in if the cloud supports domains + type: str + enabled: + description: + - Is the user enabled + type: bool + default: 'yes' + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a user +- openstack.cloud.identity_user: + cloud: mycloud + state: present + name: demouser + password: secret + email: demo@example.com + domain: default + default_project: demo + +# Delete a user +- openstack.cloud.identity_user: + cloud: mycloud + state: absent + name: demouser + +# Create a user but don't update password if user exists +- openstack.cloud.identity_user: + cloud: mycloud + state: present + name: demouser + password: secret + update_password: on_create + email: demo@example.com + domain: default + default_project: demo + +# Create a user without password +- openstack.cloud.identity_user: + cloud: mycloud + state: present + name: demouser + email: demo@example.com + domain: default + default_project: demo +''' + + +RETURN = ''' +user: + description: Dictionary describing the user. + returned: On success when I(state) is 'present' + type: complex + contains: + default_project_id: + description: User default project ID. Only present with Keystone >= v3. + type: str + sample: "4427115787be45f08f0ec22a03bfc735" + domain_id: + description: User domain ID. Only present with Keystone >= v3. + type: str + sample: "default" + email: + description: User email address + type: str + sample: "demo@example.com" + id: + description: User ID + type: str + sample: "f59382db809c43139982ca4189404650" + name: + description: User name + type: str + sample: "demouser" +''' +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _needs_update(params_dict, user): + for k in params_dict: + if k not in ('password', 'update_password') and user[k] != params_dict[k]: + return True + + # We don't get password back in the user object, so assume any supplied + # password is a change. + if ( + params_dict['password'] is not None + and params_dict['update_password'] == 'always' + ): + return True + + return False + + +def _get_domain_id(cloud, domain): + try: + # We assume admin is passing domain id + domain_id = cloud.get_domain(domain)['id'] + except Exception: + # If we fail, maybe admin is passing a domain name. + # Note that domains have unique names, just like id. + try: + domain_id = cloud.search_domains(filters={'name': domain})[0]['id'] + except Exception: + # Ok, let's hope the user is non-admin and passing a sane id + domain_id = domain + + return domain_id + + +def _get_default_project_id(cloud, default_project, domain_id, module): + project = cloud.get_project(default_project, domain_id=domain_id) + if not project: + module.fail_json(msg='Default project %s is not valid' % default_project) + + return project['id'] + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + password=dict(required=False, default=None, no_log=True), + email=dict(required=False, default=None), + default_project=dict(required=False, default=None), + description=dict(type='str'), + domain=dict(required=False, default=None), + enabled=dict(default=True, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + update_password=dict(default=None, choices=['always', 'on_create']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule( + argument_spec, + **module_kwargs) + + name = module.params['name'] + password = module.params.get('password') + email = module.params['email'] + default_project = module.params['default_project'] + domain = module.params['domain'] + enabled = module.params['enabled'] + state = module.params['state'] + update_password = module.params['update_password'] + description = module.params['description'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + domain_id = None + if domain: + domain_id = _get_domain_id(cloud, domain) + user = cloud.get_user(name, domain_id=domain_id) + else: + user = cloud.get_user(name) + + if state == 'present': + if update_password in ('always', 'on_create'): + if not password: + msg = "update_password is %s but a password value is missing" % update_password + module.fail_json(msg=msg) + default_project_id = None + if default_project: + default_project_id = _get_default_project_id(cloud, default_project, domain_id, module) + + if user is None: + if description is not None: + user = cloud.create_user( + name=name, password=password, email=email, + default_project=default_project_id, domain_id=domain_id, + enabled=enabled, description=description) + else: + user = cloud.create_user( + name=name, password=password, email=email, + default_project=default_project_id, domain_id=domain_id, + enabled=enabled) + changed = True + else: + params_dict = {'email': email, 'enabled': enabled, + 'password': password, + 'update_password': update_password} + if description is not None: + params_dict['description'] = description + if domain_id is not None: + params_dict['domain_id'] = domain_id + if default_project_id is not None: + params_dict['default_project_id'] = default_project_id + + if _needs_update(params_dict, user): + if update_password == 'always': + if description is not None: + user = cloud.update_user( + user['id'], password=password, email=email, + default_project=default_project_id, + domain_id=domain_id, enabled=enabled, description=description) + else: + user = cloud.update_user( + user['id'], password=password, email=email, + default_project=default_project_id, + domain_id=domain_id, enabled=enabled) + else: + if description is not None: + user = cloud.update_user( + user['id'], email=email, + default_project=default_project_id, + domain_id=domain_id, enabled=enabled, description=description) + else: + user = cloud.update_user( + user['id'], email=email, + default_project=default_project_id, + domain_id=domain_id, enabled=enabled) + changed = True + else: + changed = False + module.exit_json(changed=changed, user=user) + + elif state == 'absent': + if user is None: + changed = False + else: + if domain: + cloud.delete_user(user['id'], domain_id=domain_id) + else: + cloud.delete_user(user['id']) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_user_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_user_info.py new file mode 100644 index 00000000..9538f701 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/identity_user_info.py @@ -0,0 +1,168 @@ +#!/usr/bin/python +# Copyright (c) 2016 Hewlett-Packard Enterprise Corporation +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_user_info +short_description: Retrieve information about one or more OpenStack users +author: OpenStack Ansible SIG +description: + - Retrieve information about a one or more OpenStack users + - This module was called C(openstack.cloud.identity_user_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.identity_user_info) module no longer returns C(ansible_facts)! +options: + name: + description: + - Name or ID of the user + type: str + domain: + description: + - Name or ID of the domain containing the user if the cloud supports domains + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather information about previously created users +- openstack.cloud.identity_user_info: + cloud: awesomecloud + register: result +- debug: + msg: "{{ result.openstack_users }}" + +# Gather information about a previously created user by name +- openstack.cloud.identity_user_info: + cloud: awesomecloud + name: demouser + register: result +- debug: + msg: "{{ result.openstack_users }}" + +# Gather information about a previously created user in a specific domain +- openstack.cloud.identity_user_info: + cloud: awesomecloud + name: demouser + domain: admindomain + register: result +- debug: + msg: "{{ result.openstack_users }}" + +# Gather information about a previously created user in a specific domain with filter +- openstack.cloud.identity_user_info: + cloud: awesomecloud + name: demouser + domain: admindomain + filters: + enabled: False + register: result +- debug: + msg: "{{ result.openstack_users }}" +''' + + +RETURN = ''' +openstack_users: + description: has all the OpenStack information about users + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the user. + returned: success + type: str + enabled: + description: Flag to indicate if the user is enabled + returned: success + type: bool + domain_id: + description: Domain ID containing the user + returned: success + type: str + default_project_id: + description: Default project ID of the user + returned: success + type: str + email: + description: Email of the user + returned: success + type: str + username: + description: Username of the user + returned: success + type: str +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_full_argument_spec, + openstack_cloud_from_module, +) + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + domain=dict(required=False, default=None), + filters=dict(required=False, type='dict', default=None), + ) + + module = AnsibleModule(argument_spec) + is_old_facts = module._name == 'openstack.cloud.identity_user_facts' + if is_old_facts: + module.deprecate("The 'openstack.cloud.identity_user_facts' module has been renamed to 'openstack.cloud.identity_user_info', " + "and the renamed one no longer returns ansible_facts", version='2.13') + + sdk, opcloud = openstack_cloud_from_module(module) + try: + name = module.params['name'] + domain = module.params['domain'] + filters = module.params['filters'] + + if domain: + try: + # We assume admin is passing domain id + dom = opcloud.get_domain(domain)['id'] + domain = dom + except Exception: + # If we fail, maybe admin is passing a domain name. + # Note that domains have unique names, just like id. + dom = opcloud.search_domains(filters={'name': domain}) + if dom: + domain = dom[0]['id'] + else: + module.fail_json(msg='Domain name or ID does not exist') + + if not filters: + filters = {} + + filters['domain_id'] = domain + + users = opcloud.search_users(name, filters) + if is_old_facts: + module.exit_json(changed=False, ansible_facts=dict( + openstack_users=users)) + else: + module.exit_json(changed=False, openstack_users=users) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/image.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/image.py new file mode 100644 index 00000000..f6109c80 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/image.py @@ -0,0 +1,241 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +# TODO(mordred): we need to support "location"(v1) and "locations"(v2) + +DOCUMENTATION = ''' +--- +module: image +short_description: Add/Delete images from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove images from the OpenStack Image Repository +options: + name: + description: + - The name of the image when uploading - or the name/ID of the image if deleting + required: true + type: str + id: + description: + - The ID of the image when uploading an image + type: str + checksum: + description: + - The checksum of the image + type: str + disk_format: + description: + - The format of the disk that is getting uploaded + default: qcow2 + choices: ['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso', 'vhdx', 'ploop'] + type: str + container_format: + description: + - The format of the container + default: bare + choices: ['ami', 'aki', 'ari', 'bare', 'ovf', 'ova', 'docker'] + type: str + owner: + description: + - The owner of the image + type: str + min_disk: + description: + - The minimum disk space (in GB) required to boot this image + type: int + min_ram: + description: + - The minimum ram (in MB) required to boot this image + type: int + is_public: + description: + - Whether the image can be accessed publicly. Note that publicizing an image requires admin role by default. + type: bool + default: false + protected: + description: + - Prevent image from being deleted + type: bool + default: 'no' + filename: + description: + - The path to the file which has to be uploaded + type: str + ramdisk: + description: + - The name of an existing ramdisk image that will be associated with this image + type: str + kernel: + description: + - The name of an existing kernel image that will be associated with this image + type: str + properties: + description: + - Additional properties to be associated with this image + default: {} + type: dict + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + volume: + description: + - ID of a volume to create an image from. + - The volume must be in AVAILABLE state. + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Upload an image from a local file named cirros-0.3.0-x86_64-disk.img +- openstack.cloud.image: + auth: + auth_url: https://identity.example.com + username: admin + password: passme + project_name: admin + openstack.cloud.identity_user_domain_name: Default + openstack.cloud.project_domain_name: Default + name: cirros + container_format: bare + disk_format: qcow2 + state: present + filename: cirros-0.3.0-x86_64-disk.img + kernel: cirros-vmlinuz + ramdisk: cirros-initrd + properties: + cpu_arch: x86_64 + distro: ubuntu + +# Create image from volume attached to an instance +- name: create volume snapshot + openstack.cloud.volume_snapshot: + auth: + "{{ auth }}" + display_name: myvol_snapshot + volume: myvol + force: yes + register: myvol_snapshot + +- name: create volume from snapshot + openstack.cloud.volume: + auth: + "{{ auth }}" + size: "{{ myvol_snapshot.snapshot.size }}" + snapshot_id: "{{ myvol_snapshot.snapshot.id }}" + display_name: myvol_snapshot_volume + wait: yes + register: myvol_snapshot_volume + +- name: create image from volume snapshot + openstack.cloud.image: + auth: + "{{ auth }}" + volume: "{{ myvol_snapshot_volume.volume.id }}" + name: myvol_image +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + id=dict(default=None), + checksum=dict(default=None), + disk_format=dict(default='qcow2', choices=['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso', 'vhdx', 'ploop']), + container_format=dict(default='bare', choices=['ami', 'aki', 'ari', 'bare', 'ovf', 'ova', 'docker']), + owner=dict(default=None), + min_disk=dict(type='int', default=0), + min_ram=dict(type='int', default=0), + is_public=dict(type='bool', default=False), + protected=dict(type='bool', default=False), + filename=dict(default=None), + ramdisk=dict(default=None), + kernel=dict(default=None), + properties=dict(type='dict', default={}), + volume=dict(default=None), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs( + mutually_exclusive=[['filename', 'volume']], + ) + module = AnsibleModule(argument_spec, **module_kwargs) + + sdk, cloud = openstack_cloud_from_module(module) + try: + + changed = False + if module.params['id']: + image = cloud.get_image(name_or_id=module.params['id']) + elif module.params['checksum']: + image = cloud.get_image(name_or_id=module.params['name'], filters={'checksum': module.params['checksum']}) + else: + image = cloud.get_image(name_or_id=module.params['name']) + + if module.params['state'] == 'present': + if not image: + kwargs = {} + if module.params['id'] is not None: + kwargs['id'] = module.params['id'] + image = cloud.create_image( + name=module.params['name'], + filename=module.params['filename'], + disk_format=module.params['disk_format'], + container_format=module.params['container_format'], + wait=module.params['wait'], + timeout=module.params['timeout'], + is_public=module.params['is_public'], + protected=module.params['protected'], + min_disk=module.params['min_disk'], + min_ram=module.params['min_ram'], + volume=module.params['volume'], + **kwargs + ) + changed = True + if not module.params['wait']: + module.exit_json(changed=changed, image=image, id=image.id) + + cloud.update_image_properties( + image=image, + kernel=module.params['kernel'], + ramdisk=module.params['ramdisk'], + protected=module.params['protected'], + **module.params['properties']) + image = cloud.get_image(name_or_id=image.id) + module.exit_json(changed=changed, image=image, id=image.id) + + elif module.params['state'] == 'absent': + if not image: + changed = False + else: + cloud.delete_image( + name_or_id=module.params['name'], + wait=module.params['wait'], + timeout=module.params['timeout']) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/image_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/image_info.py new file mode 100644 index 00000000..611de89b --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/image_info.py @@ -0,0 +1,186 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +module: image_info +short_description: Retrieve information about an image within OpenStack. +author: OpenStack Ansible SIG +description: + - Retrieve information about a image image from OpenStack. + - This module was called C(openstack.cloud.image_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.image_info) module no longer returns C(ansible_facts)! +options: + image: + description: + - Name or ID of the image + required: false + type: str + properties: + description: + - Dict of properties of the images used for query + type: dict + required: false +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Gather information about a previously created image named image1 + openstack.cloud.image_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + image: image1 + register: result + +- name: Show openstack information + debug: + msg: "{{ result.openstack_image }}" + +# Show all available Openstack images +- name: Retrieve all available Openstack images + openstack.cloud.image_info: + register: result + +- name: Show images + debug: + msg: "{{ result.openstack_image }}" + +# Show images matching requested properties +- name: Retrieve images having properties with desired values + openstack.cloud.image_facts: + properties: + some_property: some_value + OtherProp: OtherVal + +- name: Show images + debug: + msg: "{{ result.openstack_image }}" +''' + +RETURN = ''' +openstack_image: + description: has all the openstack information about the image + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the image. + returned: success + type: str + status: + description: Image status. + returned: success + type: str + created_at: + description: Image created at timestamp. + returned: success + type: str + deleted: + description: Image deleted flag. + returned: success + type: bool + container_format: + description: Container format of the image. + returned: success + type: str + min_ram: + description: Min amount of RAM required for this image. + returned: success + type: int + disk_format: + description: Disk format of the image. + returned: success + type: str + updated_at: + description: Image updated at timestamp. + returned: success + type: str + properties: + description: Additional properties associated with the image. + returned: success + type: dict + min_disk: + description: Min amount of disk space required for this image. + returned: success + type: int + protected: + description: Image protected flag. + returned: success + type: bool + checksum: + description: Checksum for the image. + returned: success + type: str + owner: + description: Owner for the image. + returned: success + type: str + is_public: + description: Is public flag of the image. + returned: success + type: bool + deleted_at: + description: Image deleted at timestamp. + returned: success + type: str + size: + description: Size of the image. + returned: success + type: int +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + + argument_spec = openstack_full_argument_spec( + image=dict(required=False), + properties=dict(default=None, type='dict'), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + is_old_facts = module._name == 'openstack.cloud.image_facts' + if is_old_facts: + module.deprecate("The 'openstack.cloud.image_facts' module has been renamed to 'openstack.cloud.image_info', " + "and the renamed one no longer returns ansible_facts", version='2.13') + + sdk, cloud = openstack_cloud_from_module(module) + try: + if module.params['image']: + image = cloud.get_image(module.params['image']) + if is_old_facts: + module.exit_json(changed=False, ansible_facts=dict( + openstack_image=image)) + else: + module.exit_json(changed=False, openstack_image=image) + else: + images = cloud.search_images(filters=module.params['properties']) + if is_old_facts: + module.exit_json(changed=False, ansible_facts=dict( + openstack_image=images)) + else: + module.exit_json(changed=False, openstack_image=images) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/keypair.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/keypair.py new file mode 100644 index 00000000..df6bb5d1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/keypair.py @@ -0,0 +1,156 @@ +#!/usr/bin/python + +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# Copyright (c) 2013, John Dewey <john@dewey.ws> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: keypair +short_description: Add/Delete a keypair from OpenStack +author: OpenStack Ansible SIG +description: + - Add or Remove key pair from OpenStack +options: + name: + description: + - Name that has to be given to the key pair + required: true + type: str + public_key: + description: + - The public key that would be uploaded to nova and injected into VMs + upon creation. + type: str + public_key_file: + description: + - Path to local file containing ssh public key. Mutually exclusive + with public_key. + type: str + state: + description: + - Should the resource be present or absent. If state is replace and + the key exists but has different content, delete it and recreate it + with the new content. + choices: [present, absent, replace] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Creates a key pair with the running users public key +- openstack.cloud.keypair: + cloud: mordred + state: present + name: ansible_key + public_key_file: /home/me/.ssh/id_rsa.pub + +# Creates a new key pair and the private key returned after the run. +- openstack.cloud.keypair: + cloud: rax-dfw + state: present + name: ansible_key +''' + +RETURN = ''' +id: + description: Unique UUID. + returned: success + type: str +name: + description: Name given to the keypair. + returned: success + type: str +public_key: + description: The public key value for the keypair. + returned: success + type: str +private_key: + description: The private key value for the keypair. + returned: Only when a keypair is generated for the user (e.g., when creating one + and a public key is not specified). + type: str +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + OpenStackModule) + + +class KeyPairModule(OpenStackModule): + deprecated_names = ('os_keypair', 'openstack.cloud.os_keypair') + + argument_spec = dict( + name=dict(required=True), + public_key=dict(default=None), + public_key_file=dict(default=None), + state=dict(default='present', + choices=['absent', 'present', 'replace']), + ) + + module_kwargs = dict( + mutually_exclusive=[['public_key', 'public_key_file']]) + + def _system_state_change(self, keypair): + state = self.params['state'] + if state == 'present' and not keypair: + return True + if state == 'absent' and keypair: + return True + return False + + def run(self): + + state = self.params['state'] + name = self.params['name'] + public_key = self.params['public_key'] + + if self.params['public_key_file']: + with open(self.params['public_key_file']) as public_key_fh: + public_key = public_key_fh.read().rstrip() + + keypair = self.conn.get_keypair(name) + + if self.ansible.check_mode: + self.exit_json(changed=self._system_state_change(keypair)) + + if state in ('present', 'replace'): + if keypair and keypair['name'] == name: + if public_key and (public_key != keypair['public_key']): + if state == 'present': + self.fail_json( + msg="Key name %s present but key hash not the same" + " as offered. Delete key first." % name + ) + else: + self.conn.delete_keypair(name) + keypair = self.conn.create_keypair(name, public_key) + changed = True + else: + changed = False + else: + keypair = self.conn.create_keypair(name, public_key) + changed = True + + self.exit_json(changed=changed, key=keypair, id=keypair['id']) + + elif state == 'absent': + if keypair: + self.conn.delete_keypair(name) + self.exit_json(changed=True) + self.exit_json(changed=False) + + +def main(): + module = KeyPairModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/keystone_federation_protocol.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/keystone_federation_protocol.py new file mode 100644 index 00000000..f125d7f7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/keystone_federation_protocol.py @@ -0,0 +1,211 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: keystone_federation_protocol +short_description: manage a federation Protocol +author: OpenStack Ansible SIG +description: + - Manage a federation Protocol. +options: + name: + description: + - The name of the Protocol. + type: str + required: true + aliases: ['id'] + state: + description: + - Whether the protocol should be C(present) or C(absent). + choices: ['present', 'absent'] + default: present + type: str + idp_id: + description: + - The name of the Identity Provider this Protocol is associated with. + aliases: ['idp_name'] + required: true + type: str + mapping_id: + description: + - The name of the Mapping to use for this Protocol.' + - Required when creating a new Protocol. + type: str + aliases: ['mapping_name'] +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.44" +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Create a protocol + openstack.cloud.keystone_federation_protocol: + cloud: example_cloud + name: example_protocol + idp_id: example_idp + mapping_id: example_mapping + +- name: Delete a protocol + openstack.cloud.keystone_federation_protocol: + cloud: example_cloud + name: example_protocol + idp_id: example_idp + state: absent +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_module_kwargs +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_cloud_from_module + + +def normalize_protocol(protocol): + """ + Normalizes the protocol definitions so that the outputs are consistent with the + parameters + + - "name" (parameter) == "id" (SDK) + """ + if protocol is None: + return None + + _protocol = protocol.to_dict() + _protocol['name'] = protocol['id'] + # As of 0.44 SDK doesn't copy the URI parameters over, so let's add them + _protocol['idp_id'] = protocol['idp_id'] + return _protocol + + +def delete_protocol(module, sdk, cloud, protocol): + """ + Delete an existing Protocol + + returns: the "Changed" state + """ + + if protocol is None: + return False + + if module.check_mode: + return True + + try: + cloud.identity.delete_federation_protocol(None, protocol) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to delete protocol: {0}'.format(str(ex))) + return True + + +def create_protocol(module, sdk, cloud, name): + """ + Create a new Protocol + + returns: the "Changed" state and the new protocol + """ + + if module.check_mode: + return True, None + + idp_name = module.params.get('idp_id') + mapping_id = module.params.get('mapping_id') + + attributes = { + 'idp_id': idp_name, + 'mapping_id': mapping_id, + } + + try: + protocol = cloud.identity.create_federation_protocol(id=name, **attributes) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to create protocol: {0}'.format(str(ex))) + return (True, protocol) + + +def update_protocol(module, sdk, cloud, protocol): + """ + Update an existing Protocol + + returns: the "Changed" state and the new protocol + """ + + mapping_id = module.params.get('mapping_id') + + attributes = {} + + if (mapping_id is not None) and (mapping_id != protocol.mapping_id): + attributes['mapping_id'] = mapping_id + + if not attributes: + return False, protocol + + if module.check_mode: + return True, None + + try: + new_protocol = cloud.identity.update_federation_protocol(None, protocol, **attributes) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to update protocol: {0}'.format(str(ex))) + return (True, new_protocol) + + +def main(): + """ Module entry point """ + + argument_spec = openstack_full_argument_spec( + name=dict(required=True, aliases=['id']), + state=dict(default='present', choices=['absent', 'present']), + idp_id=dict(required=True, aliases=['idp_name']), + mapping_id=dict(aliases=['mapping_name']), + ) + module_kwargs = openstack_module_kwargs( + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params.get('name') + state = module.params.get('state') + idp = module.params.get('idp_id') + changed = False + + sdk, cloud = openstack_cloud_from_module(module, min_version="0.44") + + try: + protocol = cloud.identity.get_federation_protocol(idp, name) + except sdk.exceptions.ResourceNotFound: + protocol = None + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to get protocol: {0}'.format(str(ex))) + + if state == 'absent': + if protocol is not None: + changed = delete_protocol(module, sdk, cloud, protocol) + module.exit_json(changed=changed) + + # state == 'present' + else: + if protocol is None: + if module.params.get('mapping_id') is None: + module.fail_json(msg='A mapping_id must be passed when creating' + ' a protocol') + (changed, protocol) = create_protocol(module, sdk, cloud, name) + protocol = normalize_protocol(protocol) + module.exit_json(changed=changed, protocol=protocol) + + else: + (changed, new_protocol) = update_protocol(module, sdk, cloud, protocol) + new_protocol = normalize_protocol(new_protocol) + module.exit_json(changed=changed, protocol=new_protocol) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/keystone_federation_protocol_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/keystone_federation_protocol_info.py new file mode 100644 index 00000000..bb8a3333 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/keystone_federation_protocol_info.py @@ -0,0 +1,110 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: keystone_federation_protocol_info +short_description: get information about federation Protocols +author: OpenStack Ansible SIG +description: + - Get information about federation Protocols. +options: + name: + description: + - The name of the Protocol. + type: str + aliases: ['id'] + idp_id: + description: + - The name of the Identity Provider this Protocol is associated with. + aliases: ['idp_name'] + required: true + type: str +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.44" +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Describe a protocol + openstack.cloud.keystone_federation_protocol_info: + cloud: example_cloud + name: example_protocol + idp_id: example_idp + mapping_name: example_mapping + +- name: Describe all protocols attached to an IDP + openstack.cloud.keystone_federation_protocol_info: + cloud: example_cloud + idp_id: example_idp +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_module_kwargs +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_cloud_from_module + + +def normalize_protocol(protocol): + """ + Normalizes the protocol definitions so that the outputs are consistent with the + parameters + + - "name" (parameter) == "id" (SDK) + """ + if protocol is None: + return None + + _protocol = protocol.to_dict() + _protocol['name'] = protocol['id'] + # As of 0.44 SDK doesn't copy the URI parameters over, so let's add them + _protocol['idp_id'] = protocol['idp_id'] + return _protocol + + +def main(): + """ Module entry point """ + + argument_spec = openstack_full_argument_spec( + name=dict(aliases=['id']), + idp_id=dict(required=True, aliases=['idp_name']), + ) + module_kwargs = openstack_module_kwargs( + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params.get('name') + idp = module.params.get('idp_id') + + sdk, cloud = openstack_cloud_from_module(module, min_version="0.44") + + if name: + try: + protocol = cloud.identity.get_federation_protocol(idp, name) + protocol = normalize_protocol(protocol) + except sdk.exceptions.ResourceNotFound: + module.fail_json(msg='Failed to find protocol') + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to get protocol: {0}'.format(str(ex))) + module.exit_json(changed=False, protocols=[protocol]) + + else: + try: + protocols = list(map(normalize_protocol, cloud.identity.federation_protocols(idp))) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to list protocols: {0}'.format(str(ex))) + module.exit_json(changed=False, protocols=protocols) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/lb_health_monitor.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/lb_health_monitor.py new file mode 100644 index 00000000..4c534d66 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/lb_health_monitor.py @@ -0,0 +1,291 @@ +#!/usr/bin/python + +# Copyright (c) 2020 Jesper Schmitz Mouridsen. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: lb_health_monitor +author: OpenStack Ansible SIG +short_description: Add/Delete a health m nonitor to a pool in the load balancing service from OpenStack Cloud +description: + - Add or Remove a health monitor to/from a pool in the OpenStack load-balancer service. +options: + name: + type: 'str' + description: + - Name that has to be given to the health monitor + required: true + state: + type: 'str' + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + pool: + required: true + type: 'str' + description: + - The pool name or id to monitor by the health monitor. + type: + type: 'str' + default: HTTP + description: + - One of HTTP, HTTPS, PING, SCTP, TCP, TLS-HELLO, or UDP-CONNECT. + choices: [HTTP, HTTPS, PING, SCTP, TCP, TLS-HELLO, UDP-CONNECT] + delay: + type: 'str' + required: true + description: + - the interval, in seconds, between health checks. + max_retries: + required: true + type: 'str' + description: + - The number of successful checks before changing the operating status of the member to ONLINE. + max_retries_down: + type: 'str' + default: 3 + description: + - The number of allowed check failures before changing the operating status of the member to ERROR. A valid value is from 1 to 10. The default is 3. + resp_timeout: + required: true + description: + - The time, in seconds, after which a health check times out. Must be less than delay + type: int + admin_state_up: + default: True + description: + - The admin state of the helath monitor true for up or false for down + type: bool + expected_codes: + type: 'str' + default: 200 + description: + - The list of HTTP status codes expected in response from the member to declare it healthy. Specify one of the following values + A single value, such as 200 + A list, such as 200, 202 + A range, such as 200-204 + http_method: + type: 'str' + default: GET + choices: ['GET', 'CONNECT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE'] + description: + - The HTTP method that the health monitor uses for requests. One of CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, or TRACE. The default is GET. + url_path: + type: 'str' + default: '/' + description: + - The HTTP URL path of the request sent by the monitor to test the health of a backend member. + Must be a string that begins with a forward slash (/). The default URL path is /. +requirements: ["openstacksdk"] +extends_documentation_fragment: +- openstack.cloud.openstack +''' +EXAMPLES = ''' +#Create a healtmonitor named healthmonitor01 with method HEAD url_path /status and expect code 200 +- openstack.cloud.lb_health_monitor: + auth: + auth_url: "{{keystone_url}}" + username: "{{username}}" + password: "{{password}}" + project_domain_name: "{{domain_name}}" + user_domain_name: "{{domain_name}}" + project_name: "{{project_name}}" + wait: true + admin_state_up: True + expected_codes: '200' + max_retries_down: '4' + http_method: GET + url_path: "/status" + pool: '{{pool_id}}' + name: 'healthmonitor01' + delay: '10' + max_retries: '3' + resp_timeout: '5' + state: present +''' +RETURN = ''' +health_monitor: + description: Dictionary describing the health monitor. + returned: On success when C(state=present) + type: complex + contains: + id: + description: The health monitor UUID. + returned: On success when C(state=present) + type: str + admin_state_up: + returned: On success when C(state=present) + description: The administrative state of the resource. + type: bool + created_at: + returned: On success when C(state=present) + description: The UTC date and timestamp when the resource was created. + type: str + delay: + returned: On success when C(state=present) + description: The time, in seconds, between sending probes to members. + type: int + expected_codes: + returned: On success when C(state=present) + description: The list of HTTP status codes expected in response from the member to declare it healthy. + type: str + http_method: + returned: On success when C(state=present) + description: The HTTP method that the health monitor uses for requests. + type: str + max_retries: + returned: On success when C(state=present) + description: The number of successful checks before changing the operating status of the member to ONLINE. + type: str + max_retries_down: + returned: On success when C(state=present) + description: The number of allowed check failures before changing the operating status of the member to ERROR. + type: str + name: + returned: On success when C(state=present) + description: Human-readable name of the resource. + type: str + operating_status: + returned: On success when C(state=present) + description: The operating status of the resource. + type: str + pool_id: + returned: On success when C(state=present) + description: The id of the pool. + type: str + project_id: + returned: On success when C(state=present) + description: The ID of the project owning this resource. + type: str + provisioning_status: + returned: On success when C(state=present) + description: The provisioning status of the resource. + type: str + timeout: + returned: On success when C(state=present) + description: The maximum time, in seconds, that a monitor waits to connect before it times out. + type: int + type: + returned: On success when C(state=present) + description: The type of health monitor. + type: str + updated_at: + returned: On success when C(state=present) + description: The UTC date and timestamp when the resource was last updated. + type: str + url_path: + returned: On success when C(state=present) + description: The HTTP URL path of the request sent by the monitor to test the health of a backend member. + type: str +''' +import time + + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class HealthMonitorModule(OpenStackModule): + + def _wait_for_health_monitor_status(self, health_monitor_id, status, failures, interval=5): + timeout = self.params['timeout'] + + total_sleep = 0 + if failures is None: + failures = [] + + while total_sleep < timeout: + health_monitor = self.conn.load_balancer.get_health_monitor(health_monitor_id) + provisioning_status = health_monitor.provisioning_status + if provisioning_status == status: + return health_monitor + if provisioning_status in failures: + self._fail_json( + msg="health monitor %s transitioned to failure state %s" % + (health_monitor, provisioning_status) + ) + + time.sleep(interval) + total_sleep += interval + + self._fail_json(msg="timeout waiting for health monitor %s to transition to %s" % + (health_monitor_id, status) + ) + + argument_spec = dict( + name=dict(required=True), + delay=dict(required=True), + max_retries=dict(required=True), + max_retries_down=dict(required=False, default="3"), + resp_timeout=dict(required=True, type='int'), + pool=dict(required=True), + expected_codes=dict(required=False, default="200"), + admin_state_up=dict(required=False, default=True, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + http_method=dict(default="GET", requried=False, choices=["GET", "CONNECT", "DELETE", + "HEAD", "OPTIONS", "PATCH", + "POST", "PUT", "TRACE"]), + url_path=dict(default="/", requires=False), + type=dict(default='HTTP', + choices=['HTTP', 'HTTPS', 'PING', 'SCTP', 'TCP', 'TLS-HELLO', 'UDP-CONNECT'])) + + module_kwargs = dict(supports_check_mode=True) + + def run(self): + + try: + changed = False + health_monitor = self.conn.load_balancer.find_health_monitor(name_or_id=self.params['name']) + pool = self.conn.load_balancer.find_pool(name_or_id=self.params['pool']) + if self.params['state'] == 'present': + if not health_monitor: + changed = True + health_attrs = {"pool_id": pool.id, + "type": self.params["type"], + "delay": self.params['delay'], + "max_retries": self.params['max_retries'], + "max_retries_down": self.params['max_retries_down'], + "timeout": self.params['resp_timeout'], + "name": self.params['name'], + "admin_state_up": self.params["admin_state_up"], + } + if self.params["type"] in ["HTTP", "HTTPS"]: + health_attrs["expected_codes"] = self.params["expected_codes"] + health_attrs["http_method"] = self.params["http_method"] + health_attrs["url_path"] = self.params["url_path"] + + if self.ansible.check_mode: + self.exit_json(changed=True) + + health_monitor = self.conn.load_balancer.create_health_monitor(**health_attrs) + if not self.params['wait']: + self.exit_json(changed=changed, id=health_monitor.id, + health_monitor=health_monitor.to_dict()) + else: + health_monitor = self._wait_for_health_monitor_status(health_monitor.id, "ACTIVE", ["ERROR"]) + self.exit_json(changed=changed, id=health_monitor.id, + health_monitor=health_monitor.to_dict()) + else: + self.exit_json(changed=changed, id=health_monitor.id, + health_monitor=health_monitor.to_dict() + ) + elif self.params['state'] == 'absent': + if health_monitor: + if self.ansible.check_mode: + self.exit_json(changed=True) + self.conn.load_balancer.delete_health_monitor(health_monitor) + changed = True + + self.exit_json(changed=changed) + except Exception as e: + self.fail(msg=str(e)) + + +def main(): + module = HealthMonitorModule() + module() + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/lb_listener.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/lb_listener.py new file mode 100644 index 00000000..4ea4cf80 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/lb_listener.py @@ -0,0 +1,255 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Catalyst Cloud Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: lb_listener +short_description: Add/Delete a listener for a load balancer from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove a listener for a load balancer from the OpenStack load-balancer service. +options: + name: + description: + - Name that has to be given to the listener + required: true + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + loadbalancer: + description: + - The name or id of the load balancer that this listener belongs to. + required: true + type: str + protocol: + description: + - The protocol for the listener. + choices: [HTTP, HTTPS, TCP, TERMINATED_HTTPS] + default: HTTP + type: str + protocol_port: + description: + - The protocol port number for the listener. + default: 80 + type: int + wait: + description: + - If the module should wait for the load balancer to be ACTIVE. + type: bool + default: 'yes' + timeout: + description: + - The amount of time the module should wait for the load balancer to get + into ACTIVE state. + default: 180 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +id: + description: The listener UUID. + returned: On success when I(state) is 'present' + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +listener: + description: Dictionary describing the listener. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Unique UUID. + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" + name: + description: Name given to the listener. + type: str + sample: "test" + description: + description: The listener description. + type: str + sample: "description" + load_balancer_id: + description: The load balancer UUID this listener belongs to. + type: str + sample: "b32eef7e-d2a6-4ea4-a301-60a873f89b3b" + loadbalancers: + description: A list of load balancer IDs.. + type: list + sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}] + provisioning_status: + description: The provisioning status of the listener. + type: str + sample: "ACTIVE" + operating_status: + description: The operating status of the listener. + type: str + sample: "ONLINE" + is_admin_state_up: + description: The administrative state of the listener. + type: bool + sample: true + protocol: + description: The protocol for the listener. + type: str + sample: "HTTP" + protocol_port: + description: The protocol port number for the listener. + type: int + sample: 80 +''' + +EXAMPLES = ''' +# Create a listener, wait for the loadbalancer to be active. +- openstack.cloud.lb_listener: + cloud: mycloud + endpoint_type: admin + state: present + name: test-listener + loadbalancer: test-loadbalancer + protocol: HTTP + protocol_port: 8080 + +# Create a listener, do not wait for the loadbalancer to be active. +- openstack.cloud.lb_listener: + cloud: mycloud + endpoint_type: admin + state: present + name: test-listener + loadbalancer: test-loadbalancer + protocol: HTTP + protocol_port: 8080 + wait: no + +# Delete a listener +- openstack.cloud.lb_listener: + cloud: mycloud + endpoint_type: admin + state: absent + name: test-listener + loadbalancer: test-loadbalancer +''' + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _lb_wait_for_status(module, cloud, lb, status, failures, interval=5): + """Wait for load balancer to be in a particular provisioning status.""" + timeout = module.params['timeout'] + + total_sleep = 0 + if failures is None: + failures = [] + + while total_sleep < timeout: + lb = cloud.load_balancer.get_load_balancer(lb.id) + if lb.provisioning_status == status: + return None + if lb.provisioning_status in failures: + module.fail_json( + msg="Load Balancer %s transitioned to failure state %s" % + (lb.id, lb.provisioning_status) + ) + + time.sleep(interval) + total_sleep += interval + + module.fail_json( + msg="Timeout waiting for Load Balancer %s to transition to %s" % + (lb.id, status) + ) + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + loadbalancer=dict(required=True), + protocol=dict(default='HTTP', + choices=['HTTP', 'HTTPS', 'TCP', 'TERMINATED_HTTPS']), + protocol_port=dict(default=80, type='int', required=False), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + sdk, cloud = openstack_cloud_from_module(module) + loadbalancer = module.params['loadbalancer'] + loadbalancer_id = None + + try: + changed = False + listener = cloud.load_balancer.find_listener( + name_or_id=module.params['name']) + + if module.params['state'] == 'present': + if not listener: + lb = cloud.load_balancer.find_load_balancer(loadbalancer) + if not lb: + module.fail_json( + msg='load balancer %s is not found' % loadbalancer + ) + loadbalancer_id = lb.id + + listener = cloud.load_balancer.create_listener( + name=module.params['name'], + loadbalancer_id=loadbalancer_id, + protocol=module.params['protocol'], + protocol_port=module.params['protocol_port'], + ) + changed = True + + if not module.params['wait']: + module.exit_json(changed=changed, + listener=listener.to_dict(), + id=listener.id) + + if module.params['wait']: + # Check in case the listener already exists. + lb = cloud.load_balancer.find_load_balancer(loadbalancer) + if not lb: + module.fail_json( + msg='load balancer %s is not found' % loadbalancer + ) + _lb_wait_for_status(module, cloud, lb, "ACTIVE", ["ERROR"]) + + module.exit_json(changed=changed, listener=listener.to_dict(), + id=listener.id) + elif module.params['state'] == 'absent': + if not listener: + changed = False + else: + cloud.load_balancer.delete_listener(listener) + changed = True + + if module.params['wait']: + # Wait for the load balancer to be active after deleting + # the listener. + lb = cloud.load_balancer.find_load_balancer(loadbalancer) + if not lb: + module.fail_json( + msg='load balancer %s is not found' % loadbalancer + ) + _lb_wait_for_status(module, cloud, lb, "ACTIVE", ["ERROR"]) + + module.exit_json(changed=changed) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/lb_member.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/lb_member.py new file mode 100644 index 00000000..2eab39cb --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/lb_member.py @@ -0,0 +1,227 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Catalyst Cloud Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: lb_member +short_description: Add/Delete a member for a pool in load balancer from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove a member for a pool from the OpenStack load-balancer service. +options: + name: + description: + - Name that has to be given to the member + required: true + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + pool: + description: + - The name or id of the pool that this member belongs to. + required: true + type: str + protocol_port: + description: + - The protocol port number for the member. + default: 80 + type: int + address: + description: + - The IP address of the member. + type: str + subnet_id: + description: + - The subnet ID the member service is accessible from. + type: str + wait: + description: + - If the module should wait for the load balancer to be ACTIVE. + type: bool + default: 'yes' + timeout: + description: + - The amount of time the module should wait for the load balancer to get + into ACTIVE state. + default: 180 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +id: + description: The member UUID. + returned: On success when I(state) is 'present' + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +member: + description: Dictionary describing the member. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Unique UUID. + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" + name: + description: Name given to the member. + type: str + sample: "test" + description: + description: The member description. + type: str + sample: "description" + provisioning_status: + description: The provisioning status of the member. + type: str + sample: "ACTIVE" + operating_status: + description: The operating status of the member. + type: str + sample: "ONLINE" + is_admin_state_up: + description: The administrative state of the member. + type: bool + sample: true + protocol_port: + description: The protocol port number for the member. + type: int + sample: 80 + subnet_id: + description: The subnet ID the member service is accessible from. + type: str + sample: "489247fa-9c25-11e8-9679-00224d6b7bc1" + address: + description: The IP address of the backend member server. + type: str + sample: "192.168.2.10" +''' + +EXAMPLES = ''' +# Create a member, wait for the member to be created. +- openstack.cloud.lb_member: + cloud: mycloud + endpoint_type: admin + state: present + name: test-member + pool: test-pool + address: 192.168.10.3 + protocol_port: 8080 + +# Delete a listener +- openstack.cloud.lb_member: + cloud: mycloud + endpoint_type: admin + state: absent + name: test-member + pool: test-pool +''' + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _wait_for_member_status(module, cloud, pool_id, member_id, status, + failures, interval=5): + timeout = module.params['timeout'] + + total_sleep = 0 + if failures is None: + failures = [] + + while total_sleep < timeout: + member = cloud.load_balancer.get_member(member_id, pool_id) + provisioning_status = member.provisioning_status + if provisioning_status == status: + return member + if provisioning_status in failures: + module.fail_json( + msg="Member %s transitioned to failure state %s" % + (member_id, provisioning_status) + ) + + time.sleep(interval) + total_sleep += interval + + module.fail_json( + msg="Timeout waiting for member %s to transition to %s" % + (member_id, status) + ) + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + pool=dict(required=True), + address=dict(default=None), + protocol_port=dict(default=80, type='int'), + subnet_id=dict(default=None), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + sdk, cloud = openstack_cloud_from_module(module) + name = module.params['name'] + pool = module.params['pool'] + + try: + changed = False + + pool_ret = cloud.load_balancer.find_pool(name_or_id=pool) + if not pool_ret: + module.fail_json(msg='pool %s is not found' % pool) + + pool_id = pool_ret.id + member = cloud.load_balancer.find_member(name, pool_id) + + if module.params['state'] == 'present': + if not member: + member = cloud.load_balancer.create_member( + pool_ret, + address=module.params['address'], + name=name, + protocol_port=module.params['protocol_port'], + subnet_id=module.params['subnet_id'] + ) + changed = True + + if not module.params['wait']: + module.exit_json(changed=changed, + member=member.to_dict(), + id=member.id) + + if module.params['wait']: + member = _wait_for_member_status(module, cloud, pool_id, + member.id, "ACTIVE", + ["ERROR"]) + + module.exit_json(changed=changed, member=member.to_dict(), + id=member.id) + + elif module.params['state'] == 'absent': + if member: + cloud.load_balancer.delete_member(member, pool_ret) + changed = True + + module.exit_json(changed=changed) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/lb_pool.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/lb_pool.py new file mode 100644 index 00000000..9d3e7e6b --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/lb_pool.py @@ -0,0 +1,266 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Catalyst Cloud Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: lb_pool +short_description: Add/Delete a pool in the load balancing service from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove a pool from the OpenStack load-balancer service. +options: + name: + description: + - Name that has to be given to the pool + required: true + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + loadbalancer: + description: + - The name or id of the load balancer that this pool belongs to. + Either loadbalancer or listener must be specified for pool creation. + type: str + listener: + description: + - The name or id of the listener that this pool belongs to. + Either loadbalancer or listener must be specified for pool creation. + type: str + protocol: + description: + - The protocol for the pool. + choices: [HTTP, HTTPS, PROXY, TCP, UDP] + default: HTTP + type: str + lb_algorithm: + description: + - The load balancing algorithm for the pool. + choices: [LEAST_CONNECTIONS, ROUND_ROBIN, SOURCE_IP] + default: ROUND_ROBIN + type: str + wait: + description: + - If the module should wait for the pool to be ACTIVE. + type: bool + default: 'yes' + timeout: + description: + - The amount of time the module should wait for the pool to get + into ACTIVE state. + default: 180 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +id: + description: The pool UUID. + returned: On success when I(state) is 'present' + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +listener: + description: Dictionary describing the pool. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Unique UUID. + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" + name: + description: Name given to the pool. + type: str + sample: "test" + description: + description: The pool description. + type: str + sample: "description" + loadbalancers: + description: A list of load balancer IDs. + type: list + sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}] + listeners: + description: A list of listener IDs. + type: list + sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}] + members: + description: A list of member IDs. + type: list + sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}] + loadbalancer_id: + description: The load balancer ID the pool belongs to. This field is set when the pool doesn't belong to any listener in the load balancer. + type: str + sample: "7c4be3f8-9c2f-11e8-83b3-44a8422643a4" + listener_id: + description: The listener ID the pool belongs to. + type: str + sample: "956aa716-9c2f-11e8-83b3-44a8422643a4" + provisioning_status: + description: The provisioning status of the pool. + type: str + sample: "ACTIVE" + operating_status: + description: The operating status of the pool. + type: str + sample: "ONLINE" + is_admin_state_up: + description: The administrative state of the pool. + type: bool + sample: true + protocol: + description: The protocol for the pool. + type: str + sample: "HTTP" + lb_algorithm: + description: The load balancing algorithm for the pool. + type: str + sample: "ROUND_ROBIN" +''' + +EXAMPLES = ''' +# Create a pool, wait for the pool to be active. +- openstack.cloud.lb_pool: + cloud: mycloud + endpoint_type: admin + state: present + name: test-pool + loadbalancer: test-loadbalancer + protocol: HTTP + lb_algorithm: ROUND_ROBIN + +# Delete a pool +- openstack.cloud.lb_pool: + cloud: mycloud + endpoint_type: admin + state: absent + name: test-pool +''' + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec, \ + openstack_module_kwargs, openstack_cloud_from_module + + +def _wait_for_pool_status(module, cloud, pool_id, status, failures, + interval=5): + timeout = module.params['timeout'] + + total_sleep = 0 + if failures is None: + failures = [] + + while total_sleep < timeout: + pool = cloud.load_balancer.get_pool(pool_id) + provisioning_status = pool.provisioning_status + if provisioning_status == status: + return pool + if provisioning_status in failures: + module.fail_json( + msg="pool %s transitioned to failure state %s" % + (pool_id, provisioning_status) + ) + + time.sleep(interval) + total_sleep += interval + + module.fail_json( + msg="timeout waiting for pool %s to transition to %s" % + (pool_id, status) + ) + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + loadbalancer=dict(default=None), + listener=dict(default=None), + protocol=dict(default='HTTP', + choices=['HTTP', 'HTTPS', 'TCP', 'UDP', 'PROXY']), + lb_algorithm=dict( + default='ROUND_ROBIN', + choices=['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP'] + ) + ) + module_kwargs = openstack_module_kwargs( + mutually_exclusive=[['loadbalancer', 'listener']] + ) + module = AnsibleModule(argument_spec, **module_kwargs) + sdk, cloud = openstack_cloud_from_module(module) + + loadbalancer = module.params['loadbalancer'] + listener = module.params['listener'] + + try: + changed = False + pool = cloud.load_balancer.find_pool(name_or_id=module.params['name']) + + if module.params['state'] == 'present': + if not pool: + loadbalancer_id = None + if not (loadbalancer or listener): + module.fail_json( + msg="either loadbalancer or listener must be provided" + ) + + if loadbalancer: + lb = cloud.load_balancer.find_load_balancer(loadbalancer) + if not lb: + module.fail_json(msg='load balancer %s is not ' + 'found' % loadbalancer) + loadbalancer_id = lb.id + + listener_id = None + if listener: + listener_ret = cloud.load_balancer.find_listener(listener) + if not listener_ret: + module.fail_json(msg='listener %s is not found' + % listener) + listener_id = listener_ret.id + + pool = cloud.load_balancer.create_pool( + name=module.params['name'], + loadbalancer_id=loadbalancer_id, + listener_id=listener_id, + protocol=module.params['protocol'], + lb_algorithm=module.params['lb_algorithm'] + ) + changed = True + + if not module.params['wait']: + module.exit_json(changed=changed, + pool=pool.to_dict(), + id=pool.id) + + if module.params['wait']: + pool = _wait_for_pool_status(module, cloud, pool.id, "ACTIVE", + ["ERROR"]) + + module.exit_json(changed=changed, pool=pool.to_dict(), + id=pool.id) + + elif module.params['state'] == 'absent': + if pool: + cloud.load_balancer.delete_pool(pool) + changed = True + + module.exit_json(changed=changed) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/loadbalancer.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/loadbalancer.py new file mode 100644 index 00000000..9836a61c --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/loadbalancer.py @@ -0,0 +1,660 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Catalyst Cloud Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: loadbalancer +short_description: Add/Delete load balancer from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove load balancer from the OpenStack load-balancer + service(Octavia). Load balancer update is not supported for now. +options: + name: + description: + - The name of the load balancer. + required: true + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + flavor: + description: + - The flavor of the load balancer. + type: str + vip_network: + description: + - The name or id of the network for the virtual IP of the load balancer. + One of I(vip_network), I(vip_subnet), or I(vip_port) must be specified + for creation. + type: str + vip_subnet: + description: + - The name or id of the subnet for the virtual IP of the load balancer. + One of I(vip_network), I(vip_subnet), or I(vip_port) must be specified + for creation. + type: str + vip_port: + description: + - The name or id of the load balancer virtual IP port. One of + I(vip_network), I(vip_subnet), or I(vip_port) must be specified for + creation. + type: str + vip_address: + description: + - IP address of the load balancer virtual IP. + type: str + public_ip_address: + description: + - Public IP address associated with the VIP. + type: str + auto_public_ip: + description: + - Allocate a public IP address and associate with the VIP automatically. + type: bool + default: 'no' + public_network: + description: + - The name or ID of a Neutron external network. + type: str + delete_public_ip: + description: + - When C(state=absent) and this option is true, any public IP address + associated with the VIP will be deleted along with the load balancer. + type: bool + default: 'no' + listeners: + description: + - A list of listeners that attached to the load balancer. + suboptions: + name: + description: + - The listener name or ID. + protocol: + description: + - The protocol for the listener. + default: HTTP + protocol_port: + description: + - The protocol port number for the listener. + default: 80 + allowed_cidrs: + description: + - A list of IPv4, IPv6 or mix of both CIDRs to be allowed access to the listener. The default is all allowed. + When a list of CIDRs is provided, the default switches to deny all. + Ignored on unsupported Octavia versions (less than 2.12) + default: [] + pool: + description: + - The pool attached to the listener. + suboptions: + name: + description: + - The pool name or ID. + protocol: + description: + - The protocol for the pool. + default: HTTP + lb_algorithm: + description: + - The load balancing algorithm for the pool. + default: ROUND_ROBIN + members: + description: + - A list of members that added to the pool. + suboptions: + name: + description: + - The member name or ID. + address: + description: + - The IP address of the member. + protocol_port: + description: + - The protocol port number for the member. + default: 80 + subnet: + description: + - The name or ID of the subnet the member service is + accessible from. + elements: dict + type: list + wait: + description: + - If the module should wait for the load balancer to be created or + deleted. + type: bool + default: 'yes' + timeout: + description: + - The amount of time the module should wait. + default: 180 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +id: + description: The load balancer UUID. + returned: On success when C(state=present) + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +loadbalancer: + description: Dictionary describing the load balancer. + returned: On success when C(state=present) + type: complex + contains: + id: + description: Unique UUID. + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" + name: + description: Name given to the load balancer. + type: str + sample: "lingxian_test" + vip_network_id: + description: Network ID the load balancer virtual IP port belongs in. + type: str + sample: "f171db43-56fd-41cf-82d7-4e91d741762e" + vip_subnet_id: + description: Subnet ID the load balancer virtual IP port belongs in. + type: str + sample: "c53e3c70-9d62-409a-9f71-db148e7aa853" + vip_port_id: + description: The load balancer virtual IP port ID. + type: str + sample: "2061395c-1c01-47ab-b925-c91b93df9c1d" + vip_address: + description: The load balancer virtual IP address. + type: str + sample: "192.168.2.88" + public_vip_address: + description: The load balancer public VIP address. + type: str + sample: "10.17.8.254" + provisioning_status: + description: The provisioning status of the load balancer. + type: str + sample: "ACTIVE" + operating_status: + description: The operating status of the load balancer. + type: str + sample: "ONLINE" + is_admin_state_up: + description: The administrative state of the load balancer. + type: bool + sample: true + listeners: + description: The associated listener IDs, if any. + type: list + sample: [{"id": "7aa1b380-beec-459c-a8a7-3a4fb6d30645"}, {"id": "692d06b8-c4f8-4bdb-b2a3-5a263cc23ba6"}] + pools: + description: The associated pool IDs, if any. + type: list + sample: [{"id": "27b78d92-cee1-4646-b831-e3b90a7fa714"}, {"id": "befc1fb5-1992-4697-bdb9-eee330989344"}] +''' + +EXAMPLES = ''' +# Create a load balancer by specifying the VIP subnet. +- openstack.cloud.loadbalancer: + auth: + auth_url: https://identity.example.com + username: admin + password: passme + project_name: admin + state: present + name: my_lb + vip_subnet: my_subnet + timeout: 150 + +# Create a load balancer by specifying the VIP network and the IP address. +- openstack.cloud.loadbalancer: + auth: + auth_url: https://identity.example.com + username: admin + password: passme + project_name: admin + state: present + name: my_lb + vip_network: my_network + vip_address: 192.168.0.11 + +# Create a load balancer together with its sub-resources in the 'all in one' +# way. A public IP address is also allocated to the load balancer VIP. +- openstack.cloud.loadbalancer: + auth: + auth_url: https://identity.example.com + username: admin + password: passme + project_name: admin + name: lingxian_test + state: present + vip_subnet: kong_subnet + auto_public_ip: yes + public_network: public + listeners: + - name: lingxian_80 + protocol: TCP + protocol_port: 80 + pool: + name: lingxian_80_pool + protocol: TCP + members: + - name: mywebserver1 + address: 192.168.2.81 + protocol_port: 80 + subnet: webserver_subnet + - name: lingxian_8080 + protocol: TCP + protocol_port: 8080 + pool: + name: lingxian_8080-pool + protocol: TCP + members: + - name: mywebserver2 + address: 192.168.2.82 + protocol_port: 8080 + wait: yes + timeout: 600 + +# Delete a load balancer(and all its related resources) +- openstack.cloud.loadbalancer: + auth: + auth_url: https://identity.example.com + username: admin + password: passme + project_name: admin + state: absent + name: my_lb + +# Delete a load balancer(and all its related resources) together with the +# public IP address(if any) attached to it. +- openstack.cloud.loadbalancer: + auth: + auth_url: https://identity.example.com + username: admin + password: passme + project_name: admin + state: absent + name: my_lb + delete_public_ip: yes +''' + +import time +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class LoadBalancerModule(OpenStackModule): + + def _wait_for_lb(self, lb, status, failures, interval=5): + """Wait for load balancer to be in a particular provisioning status.""" + timeout = self.params['timeout'] + + total_sleep = 0 + if failures is None: + failures = [] + + while total_sleep < timeout: + lb = self.conn.load_balancer.find_load_balancer(lb.id) + + if lb: + if lb.provisioning_status == status: + return None + if lb.provisioning_status in failures: + self.fail_json( + msg="Load Balancer %s transitioned to failure state %s" % + (lb.id, lb.provisioning_status) + ) + else: + if status == "DELETED": + return None + else: + self.fail_json( + msg="Load Balancer %s transitioned to DELETED" % lb.id + ) + + time.sleep(interval) + total_sleep += interval + + self.fail_json( + msg="Timeout waiting for Load Balancer %s to transition to %s" % + (lb.id, status) + ) + + argument_spec = dict( + name=dict(required=True), + flavor=dict(required=False), + state=dict(default='present', choices=['absent', 'present']), + vip_network=dict(required=False), + vip_subnet=dict(required=False), + vip_port=dict(required=False), + vip_address=dict(required=False), + listeners=dict(type='list', default=[], elements='dict'), + public_ip_address=dict(required=False, default=None), + auto_public_ip=dict(required=False, default=False, type='bool'), + public_network=dict(required=False), + delete_public_ip=dict(required=False, default=False, type='bool'), + ) + module_kwargs = dict(supports_check_mode=True) + + def run(self): + flavor = self.params['flavor'] + vip_network = self.params['vip_network'] + vip_subnet = self.params['vip_subnet'] + vip_port = self.params['vip_port'] + listeners = self.params['listeners'] + public_vip_address = self.params['public_ip_address'] + allocate_fip = self.params['auto_public_ip'] + delete_fip = self.params['delete_public_ip'] + public_network = self.params['public_network'] + + vip_network_id = None + vip_subnet_id = None + vip_port_id = None + flavor_id = None + + try: + max_microversion = 1 + max_majorversion = 2 + changed = False + lb = self.conn.load_balancer.find_load_balancer( + name_or_id=self.params['name']) + + if self.params['state'] == 'present': + if lb and self.ansible.check_mode: + self.exit_json(changed=False) + if lb: + self.exit_json(changed=False) + ver_data = self.conn.load_balancer.get_all_version_data() + region = list(ver_data.keys())[0] + interface_type = list(ver_data[region].keys())[0] + versions = ver_data[region][interface_type]['load-balancer'] + for ver in versions: + if ver['status'] == 'CURRENT': + curversion = ver['version'].split(".") + max_majorversion = int(curversion[0]) + max_microversion = int(curversion[1]) + + if not lb: + if self.ansible.check_mode: + self.exit_json(changed=True) + + if not (vip_network or vip_subnet or vip_port): + self.fail_json( + msg="One of vip_network, vip_subnet, or vip_port must " + "be specified for load balancer creation" + ) + + if flavor: + _flavor = self.conn.load_balancer.find_flavor(flavor) + if not _flavor: + self.fail_json( + msg='flavor %s not found' % flavor + ) + flavor_id = _flavor.id + + if vip_network: + network = self.conn.get_network(vip_network) + if not network: + self.fail_json( + msg='network %s is not found' % vip_network + ) + vip_network_id = network.id + if vip_subnet: + subnet = self.conn.get_subnet(vip_subnet) + if not subnet: + self.fail_json( + msg='subnet %s is not found' % vip_subnet + ) + vip_subnet_id = subnet.id + if vip_port: + port = self.conn.get_port(vip_port) + + if not port: + self.fail_json( + msg='port %s is not found' % vip_port + ) + vip_port_id = port.id + lbargs = {"name": self.params['name'], + "vip_network_id": vip_network_id, + "vip_subnet_id": vip_subnet_id, + "vip_port_id": vip_port_id, + "vip_address": self.params['vip_address'] + } + if flavor_id is not None: + lbargs["flavor_id"] = flavor_id + + lb = self.conn.load_balancer.create_load_balancer(**lbargs) + + changed = True + + if not listeners and not self.params['wait']: + self.exit_json( + changed=changed, + loadbalancer=lb.to_dict(), + id=lb.id + ) + + self._wait_for_lb(lb, "ACTIVE", ["ERROR"]) + + for listener_def in listeners: + listener_name = listener_def.get("name") + pool_def = listener_def.get("pool") + + if not listener_name: + self.fail_json(msg='listener name is required') + + listener = self.conn.load_balancer.find_listener( + name_or_id=listener_name + ) + + if not listener: + self._wait_for_lb(lb, "ACTIVE", ["ERROR"]) + + protocol = listener_def.get("protocol", "HTTP") + protocol_port = listener_def.get("protocol_port", 80) + allowed_cidrs = listener_def.get("allowed_cidrs", []) + listenerargs = {"name": listener_name, + "loadbalancer_id": lb.id, + "protocol": protocol, + "protocol_port": protocol_port + } + if max_microversion >= 12 and max_majorversion >= 2: + listenerargs['allowed_cidrs'] = allowed_cidrs + listener = self.conn.load_balancer.create_listener(**listenerargs) + changed = True + + # Ensure pool in the listener. + if pool_def: + pool_name = pool_def.get("name") + members = pool_def.get('members', []) + + if not pool_name: + self.fail_json(msg='pool name is required') + + pool = self.conn.load_balancer.find_pool(name_or_id=pool_name) + + if not pool: + self._wait_for_lb(lb, "ACTIVE", ["ERROR"]) + + protocol = pool_def.get("protocol", "HTTP") + lb_algorithm = pool_def.get("lb_algorithm", + "ROUND_ROBIN") + + pool = self.conn.load_balancer.create_pool( + name=pool_name, + listener_id=listener.id, + protocol=protocol, + lb_algorithm=lb_algorithm + ) + changed = True + + # Ensure members in the pool + for member_def in members: + member_name = member_def.get("name") + if not member_name: + self.fail_json(msg='member name is required') + + member = self.conn.load_balancer.find_member(member_name, + pool.id + ) + + if not member: + self._wait_for_lb(lb, "ACTIVE", ["ERROR"]) + + address = member_def.get("address") + if not address: + self.fail_json( + msg='member address for member %s is ' + 'required' % member_name + ) + + subnet_id = member_def.get("subnet") + if subnet_id: + subnet = self.conn.get_subnet(subnet_id) + if not subnet: + self.fail_json( + msg='subnet %s for member %s is not ' + 'found' % (subnet_id, member_name) + ) + subnet_id = subnet.id + + protocol_port = member_def.get("protocol_port", 80) + + member = self.conn.load_balancer.create_member( + pool, + name=member_name, + address=address, + protocol_port=protocol_port, + subnet_id=subnet_id + ) + changed = True + + # Associate public ip to the load balancer VIP. If + # public_vip_address is provided, use that IP, otherwise, either + # find an available public ip or create a new one. + fip = None + orig_public_ip = None + new_public_ip = None + if public_vip_address or allocate_fip: + ips = self.conn.network.ips( + port_id=lb.vip_port_id, + fixed_ip_address=lb.vip_address + ) + ips = list(ips) + if ips: + orig_public_ip = ips[0] + new_public_ip = orig_public_ip.floating_ip_address + + if public_vip_address and public_vip_address != orig_public_ip: + fip = self.conn.network.find_ip(public_vip_address) + + if not fip: + self.fail_json( + msg='Public IP %s is unavailable' % public_vip_address + ) + + # Release origin public ip first + self.conn.network.update_ip( + orig_public_ip, + fixed_ip_address=None, + port_id=None + ) + + # Associate new public ip + self.conn.network.update_ip( + fip, + fixed_ip_address=lb.vip_address, + port_id=lb.vip_port_id + ) + + new_public_ip = public_vip_address + changed = True + elif allocate_fip and not orig_public_ip: + fip = self.conn.network.find_available_ip() + if not fip: + if not public_network: + self.fail_json(msg="Public network is not provided") + + pub_net = self.conn.network.find_network(public_network) + if not pub_net: + self.fail_json( + msg='Public network %s not found' % + public_network + ) + fip = self.conn.network.create_ip( + floating_network_id=pub_net.id + ) + + self.conn.network.update_ip( + fip, + fixed_ip_address=lb.vip_address, + port_id=lb.vip_port_id + ) + + new_public_ip = fip.floating_ip_address + changed = True + + # Include public_vip_address in the result. + lb = self.conn.load_balancer.find_load_balancer(name_or_id=lb.id) + lb_dict = lb.to_dict() + lb_dict.update({"public_vip_address": new_public_ip}) + + self.exit_json( + changed=changed, + loadbalancer=lb_dict, + id=lb.id + ) + elif self.params['state'] == 'absent': + changed = False + public_vip_address = None + + if lb: + if self.ansible.check_mode: + self.exit_json(changed=True) + if delete_fip: + ips = self.conn.network.ips( + port_id=lb.vip_port_id, + fixed_ip_address=lb.vip_address + ) + ips = list(ips) + if ips: + public_vip_address = ips[0] + + # Deleting load balancer with `cascade=False` does not make + # sense because the deletion will always fail if there are + # sub-resources. + self.conn.load_balancer.delete_load_balancer(lb, cascade=True) + changed = True + + if self.params['wait']: + self._wait_for_lb(lb, "DELETED", ["ERROR"]) + + if delete_fip and public_vip_address: + self.conn.network.delete_ip(public_vip_address) + changed = True + elif self.ansible.check_mode: + self.exit_json(changed=False) + + self.exit_json(changed=changed) + except Exception as e: + self.fail_json(msg=str(e)) + + +def main(): + module = LoadBalancerModule() + module() + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/network.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/network.py new file mode 100644 index 00000000..780d49ba --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/network.py @@ -0,0 +1,245 @@ +#!/usr/bin/python + +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: network +short_description: Creates/removes networks from OpenStack +author: OpenStack Ansible SIG +description: + - Add or remove network from OpenStack. +options: + name: + description: + - Name to be assigned to the network. + required: true + type: str + shared: + description: + - Whether this network is shared or not. + type: bool + default: 'no' + admin_state_up: + description: + - Whether the state should be marked as up or down. + type: bool + default: 'yes' + external: + description: + - Whether this network is externally accessible. + type: bool + default: 'no' + state: + description: + - Indicate desired state of the resource. + choices: ['present', 'absent'] + default: present + type: str + provider_physical_network: + description: + - The physical network where this network object is implemented. + type: str + provider_network_type: + description: + - The type of physical network that maps to this network resource. + type: str + provider_segmentation_id: + description: + - An isolated segment on the physical network. The I(network_type) + attribute defines the segmentation model. For example, if the + I(network_type) value is vlan, this ID is a vlan identifier. If + the I(network_type) value is gre, this ID is a gre key. + type: int + project: + description: + - Project name or ID containing the network (name admin-only) + type: str + port_security_enabled: + description: + - Whether port security is enabled on the network or not. + Network will use OpenStack defaults if this option is + not utilised. Requires openstacksdk>=0.18. + type: bool + mtu_size: + description: + - The maximum transmission unit (MTU) value to address fragmentation. + Network will use OpenStack defaults if this option is + not provided. Requires openstacksdk>=0.18. + type: int + aliases: ['mtu'] + dns_domain: + description: + - The DNS domain value to set. Requires openstacksdk>=0.29. + Network will use Openstack defaults if this option is + not provided. + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create an externally accessible network named 'ext_network'. +- openstack.cloud.network: + cloud: mycloud + state: present + name: ext_network + external: true +''' + +RETURN = ''' +network: + description: Dictionary describing the network. + returned: On success when I(state) is 'present'. + type: complex + contains: + id: + description: Network ID. + type: str + sample: "4bb4f9a5-3bd2-4562-bf6a-d17a6341bb56" + name: + description: Network name. + type: str + sample: "ext_network" + shared: + description: Indicates whether this network is shared across all tenants. + type: bool + sample: false + status: + description: Network status. + type: str + sample: "ACTIVE" + mtu: + description: The MTU of a network resource. + type: int + sample: 0 + dns_domain: + description: The DNS domain of a network resource. + type: str + sample: "sample.openstack.org." + admin_state_up: + description: The administrative state of the network. + type: bool + sample: true + port_security_enabled: + description: The port security status + type: bool + sample: true + router:external: + description: Indicates whether this network is externally accessible. + type: bool + sample: true + tenant_id: + description: The tenant ID. + type: str + sample: "06820f94b9f54b119636be2728d216fc" + subnets: + description: The associated subnets. + type: list + sample: [] + "provider:physical_network": + description: The physical network where this network object is implemented. + type: str + sample: my_vlan_net + "provider:network_type": + description: The type of physical network that maps to this network resource. + type: str + sample: vlan + "provider:segmentation_id": + description: An isolated segment on the physical network. + type: str + sample: 101 +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class NetworkModule(OpenStackModule): + + argument_spec = dict( + name=dict(required=True), + shared=dict(default=False, type='bool'), + admin_state_up=dict(default=True, type='bool'), + external=dict(default=False, type='bool'), + provider_physical_network=dict(required=False), + provider_network_type=dict(required=False), + provider_segmentation_id=dict(required=False, type='int'), + state=dict(default='present', choices=['absent', 'present']), + project=dict(default=None), + port_security_enabled=dict(type='bool', min_ver='0.18.0'), + mtu_size=dict(required=False, type='int', min_ver='0.18.0', aliases=['mtu']), + dns_domain=dict(required=False, min_ver='0.29.0') + ) + + def run(self): + + state = self.params['state'] + name = self.params['name'] + shared = self.params['shared'] + admin_state_up = self.params['admin_state_up'] + external = self.params['external'] + provider_physical_network = self.params['provider_physical_network'] + provider_network_type = self.params['provider_network_type'] + provider_segmentation_id = self.params['provider_segmentation_id'] + project = self.params['project'] + + kwargs = self.check_versioned( + mtu_size=self.params['mtu_size'], port_security_enabled=self.params['port_security_enabled'], + dns_domain=self.params['dns_domain'] + ) + + if project is not None: + proj = self.conn.get_project(project) + if proj is None: + self.fail_json(msg='Project %s could not be found' % project) + project_id = proj['id'] + filters = {'tenant_id': project_id} + else: + project_id = None + filters = None + net = self.conn.get_network(name, filters=filters) + + if state == 'present': + if not net: + provider = {} + if provider_physical_network: + provider['physical_network'] = provider_physical_network + if provider_network_type: + provider['network_type'] = provider_network_type + if provider_segmentation_id: + provider['segmentation_id'] = provider_segmentation_id + + if project_id is not None: + net = self.conn.create_network(name, shared, admin_state_up, + external, provider, project_id, + **kwargs) + else: + net = self.conn.create_network(name, shared, admin_state_up, + external, provider, + **kwargs) + changed = True + else: + changed = False + self.exit(changed=changed, network=net, id=net['id']) + + elif state == 'absent': + if not net: + self.exit(changed=False) + else: + self.conn.delete_network(name) + self.exit(changed=True) + + +def main(): + module = NetworkModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/networks_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/networks_info.py new file mode 100644 index 00000000..a8d098c7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/networks_info.py @@ -0,0 +1,146 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: networks_info +short_description: Retrieve information about one or more OpenStack networks. +author: OpenStack Ansible SIG +description: + - Retrieve information about one or more networks from OpenStack. + - This module was called C(openstack.cloud.networks_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.networks_info) module no longer returns C(ansible_facts)! +options: + name: + description: + - Name or ID of the Network + required: false + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + required: false + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Gather information about previously created networks + openstack.cloud.networks_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + register: result + +- name: Show openstack networks + debug: + msg: "{{ result.openstack_networks }}" + +- name: Gather information about a previously created network by name + openstack.cloud.networks_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + name: network1 + register: result + +- name: Show openstack networks + debug: + msg: "{{ result.openstack_networks }}" + +- name: Gather information about a previously created network with filter + # Note: name and filters parameters are Not mutually exclusive + openstack.cloud.networks_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + filters: + tenant_id: 55e2ce24b2a245b09f181bf025724cbe + subnets: + - 057d4bdf-6d4d-4728-bb0f-5ac45a6f7400 + - 443d4dc0-91d4-4998-b21c-357d10433483 + register: result + +- name: Show openstack networks + debug: + msg: "{{ result.openstack_networks }}" +''' + +RETURN = ''' +openstack_networks: + description: has all the openstack information about the networks + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the network. + returned: success + type: str + status: + description: Network status. + returned: success + type: str + subnets: + description: Subnet(s) included in this network. + returned: success + type: list + elements: str + tenant_id: + description: Tenant id associated with this network. + returned: success + type: str + shared: + description: Network shared flag. + returned: success + type: bool +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class NetworkInfoModule(OpenStackModule): + + deprecated_names = ('networks_facts', 'openstack.cloud.networks_facts') + + argument_spec = dict( + name=dict(required=False, default=None), + filters=dict(required=False, type='dict', default=None) + ) + + def run(self): + + kwargs = self.check_versioned( + filters=self.params['filters'] + ) + if self.params['name']: + kwargs['name_or_id'] = self.params['name'] + networks = self.conn.search_networks(**kwargs) + + self.exit(changed=False, openstack_networks=networks) + + +def main(): + module = NetworkInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/object.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/object.py new file mode 100644 index 00000000..5d0afcf7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/object.py @@ -0,0 +1,123 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: object +short_description: Create or Delete objects and containers from OpenStack +author: OpenStack Ansible SIG +description: + - Create or Delete objects and containers from OpenStack +options: + container: + description: + - The name of the container in which to create the object + required: true + type: str + name: + description: + - Name to be give to the object. If omitted, operations will be on + the entire container + required: false + type: str + filename: + description: + - Path to local file to be uploaded. + required: false + type: str + container_access: + description: + - desired container access level. + required: false + choices: ['private', 'public'] + default: private + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: "Create a object named 'fstab' in the 'config' container" + openstack.cloud.object: + cloud: mordred + state: present + name: fstab + container: config + filename: /etc/fstab + +- name: Delete a container called config and all of its contents + openstack.cloud.object: + cloud: rax-iad + state: absent + container: config +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def process_object( + cloud_obj, container, name, filename, container_access, **kwargs): + + changed = False + container_obj = cloud_obj.get_container(container) + if kwargs['state'] == 'present': + if not container_obj: + container_obj = cloud_obj.create_container(container) + changed = True + if cloud_obj.get_container_access(container) != container_access: + cloud_obj.set_container_access(container, container_access) + changed = True + if name: + if cloud_obj.is_object_stale(container, name, filename): + cloud_obj.create_object(container, name, filename) + changed = True + else: + if container_obj: + if name: + if cloud_obj.get_object_metadata(container, name): + cloud_obj.delete_object(container, name) + changed = True + else: + cloud_obj.delete_container(container) + changed = True + return changed + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + container=dict(required=True), + filename=dict(required=False, default=None), + container_access=dict(default='private', choices=['private', 'public']), + state=dict(default='present', choices=['absent', 'present']), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + sdk, cloud = openstack_cloud_from_module(module) + try: + changed = process_object(cloud, **module.params) + + module.exit_json(changed=changed) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_auth.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_auth.py new file mode 100644 index 00000000..0b356217 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_auth.py @@ -0,0 +1,67 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: auth +short_description: Retrieve an auth token +author: OpenStack Ansible SIG +description: + - Retrieve an auth token from an OpenStack Cloud +requirements: + - "python >= 3.6" + - "openstacksdk" +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Authenticate to the cloud and retrieve the service catalog + openstack.cloud.auth: + cloud: rax-dfw + +- name: Show service catalog + debug: + var: service_catalog +''' + +RETURN = ''' +auth_token: + description: Openstack API Auth Token + returned: success + type: str +service_catalog: + description: A dictionary of available API endpoints + returned: success + type: dict +''' + +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + + argument_spec = openstack_full_argument_spec() + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + sdk, cloud = openstack_cloud_from_module(module) + try: + module.exit_json( + changed=False, + ansible_facts=dict( + auth_token=cloud.auth_token, + service_catalog=cloud.service_catalog)) + except Exception as e: + module.fail_json(msg=str(e), exception=traceback.format_exc()) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_client_config.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_client_config.py new file mode 100644 index 00000000..94036e49 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_client_config.py @@ -0,0 +1,76 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: config +short_description: Get OpenStack Client config +description: + - Get I(openstack) client config data from clouds.yaml or environment +notes: + - Facts are placed in the C(openstack.clouds) variable. +options: + clouds: + description: + - List of clouds to limit the return list to. No value means return + information on all configured clouds + required: false + default: [] + type: list + elements: str +requirements: + - "python >= 3.6" + - "openstacksdk" +author: OpenStack Ansible SIG +''' + +EXAMPLES = ''' +- name: Get list of clouds that do not support security groups + openstack.cloud.config: + +- debug: + var: "{{ item }}" + with_items: "{{ openstack.clouds | rejectattr('secgroup_source', 'none') | list }}" + +- name: Get the information back just about the mordred cloud + openstack.cloud.config: + clouds: + - mordred +''' + +try: + import openstack.config + from openstack import exceptions + HAS_OPENSTACKSDK = True +except ImportError: + HAS_OPENSTACKSDK = False + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule(argument_spec=dict( + clouds=dict(required=False, type='list', default=[], elements='str'), + )) + + if not HAS_OPENSTACKSDK: + module.fail_json(msg='openstacksdk is required for this module') + + p = module.params + + try: + config = openstack.config.OpenStackConfig() + clouds = [] + for cloud in config.get_all_clouds(): + if not p['clouds'] or cloud.name in p['clouds']: + cloud.config['name'] = cloud.name + clouds.append(cloud.config) + module.exit_json(ansible_facts=dict(openstack=dict(clouds=clouds))) + except exceptions.ConfigException as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_coe_cluster.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_coe_cluster.py new file mode 100644 index 00000000..65042a98 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_coe_cluster.py @@ -0,0 +1,293 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Catalyst IT Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: coe_cluster +short_description: Add/Remove COE cluster from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove COE cluster from the OpenStack Container Infra service. +options: + cluster_template_id: + description: + - The template ID of cluster template. + required: true + type: str + discovery_url: + description: + - Url used for cluster node discovery + type: str + docker_volume_size: + description: + - The size in GB of the docker volume + type: int + flavor_id: + description: + - The flavor of the minion node for this ClusterTemplate + type: str + keypair: + description: + - Name of the keypair to use. + type: str + labels: + description: + - One or more key/value pairs + type: raw + master_flavor_id: + description: + - The flavor of the master node for this ClusterTemplate + type: str + master_count: + description: + - The number of master nodes for this cluster + default: 1 + type: int + name: + description: + - Name that has to be given to the cluster template + required: true + type: str + node_count: + description: + - The number of nodes for this cluster + default: 1 + type: int + state: + description: + - Indicate desired state of the resource. + choices: [present, absent] + default: present + type: str + timeout: + description: + - Timeout for creating the cluster in minutes. Default to 60 mins + if not set + default: 60 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +id: + description: The cluster UUID. + returned: On success when I(state) is 'present' + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +cluster: + description: Dictionary describing the cluster. + returned: On success when I(state) is 'present' + type: complex + contains: + api_address: + description: + - Api address of cluster master node + type: str + sample: https://172.24.4.30:6443 + cluster_template_id: + description: The cluster_template UUID + type: str + sample: '7b1418c8-cea8-48fc-995d-52b66af9a9aa' + coe_version: + description: + - Version of the COE software currently running in this cluster + type: str + sample: v1.11.1 + container_version: + description: + - "Version of the container software. Example: docker version." + type: str + sample: 1.12.6 + created_at: + description: + - The time in UTC at which the cluster is created + type: str + sample: "2018-08-16T10:29:45+00:00" + create_timeout: + description: + - Timeout for creating the cluster in minutes. Default to 60 if + not set. + type: int + sample: 60 + discovery_url: + description: + - Url used for cluster node discovery + type: str + sample: https://discovery.etcd.io/a42ee38e7113f31f4d6324f24367aae5 + faults: + description: + - Fault info collected from the Heat resources of this cluster + type: dict + sample: {'0': 'ResourceInError: resources[0].resources...'} + flavor_id: + description: + - The flavor of the minion node for this cluster + type: str + sample: c1.c1r1 + keypair: + description: + - Name of the keypair to use. + type: str + sample: mykey + labels: + description: One or more key/value pairs + type: dict + sample: {'key1': 'value1', 'key2': 'value2'} + master_addresses: + description: + - IP addresses of cluster master nodes + type: list + sample: ['172.24.4.5'] + master_count: + description: + - The number of master nodes for this cluster. + type: int + sample: 1 + master_flavor_id: + description: + - The flavor of the master node for this cluster + type: str + sample: c1.c1r1 + name: + description: + - Name that has to be given to the cluster + type: str + sample: k8scluster + node_addresses: + description: + - IP addresses of cluster slave nodes + type: list + sample: ['172.24.4.8'] + node_count: + description: + - The number of master nodes for this cluster. + type: int + sample: 1 + stack_id: + description: + - Stack id of the Heat stack + type: str + sample: '07767ec6-85f5-44cb-bd63-242a8e7f0d9d' + status: + description: Status of the cluster from the heat stack + type: str + sample: 'CREATE_COMLETE' + status_reason: + description: + - Status reason of the cluster from the heat stack + type: str + sample: 'Stack CREATE completed successfully' + updated_at: + description: + - The time in UTC at which the cluster is updated + type: str + sample: '2018-08-16T10:39:25+00:00' + id: + description: + - Unique UUID for this cluster + type: str + sample: '86246a4d-a16c-4a58-9e96ad7719fe0f9d' +''' + +EXAMPLES = ''' +# Create a new Kubernetes cluster +- openstack.cloud.coe_cluster: + name: k8s + cluster_template_id: k8s-ha + keypair: mykey + master_count: 3 + node_count: 5 +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _parse_labels(labels): + if isinstance(labels, str): + labels_dict = {} + for kv_str in labels.split(","): + k, v = kv_str.split("=") + labels_dict[k] = v + return labels_dict + if not labels: + return {} + return labels + + +def main(): + argument_spec = openstack_full_argument_spec( + cluster_template_id=dict(required=True), + discovery_url=dict(default=None), + docker_volume_size=dict(type='int'), + flavor_id=dict(default=None), + keypair=dict(default=None), + labels=dict(default=None, type='raw'), + master_count=dict(type='int', default=1), + master_flavor_id=dict(default=None), + name=dict(required=True), + node_count=dict(type='int', default=1), + state=dict(default='present', choices=['absent', 'present']), + timeout=dict(type='int', default=60), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + params = module.params.copy() + + state = module.params['state'] + name = module.params['name'] + cluster_template_id = module.params['cluster_template_id'] + + kwargs = dict( + discovery_url=module.params['discovery_url'], + docker_volume_size=module.params['docker_volume_size'], + flavor_id=module.params['flavor_id'], + keypair=module.params['keypair'], + labels=_parse_labels(params['labels']), + master_count=module.params['master_count'], + master_flavor_id=module.params['master_flavor_id'], + node_count=module.params['node_count'], + create_timeout=module.params['timeout'], + ) + + sdk, cloud = openstack_cloud_from_module(module) + try: + changed = False + cluster = cloud.get_coe_cluster(name_or_id=name, filters={'cluster_template_id': cluster_template_id}) + + if state == 'present': + if not cluster: + cluster = cloud.create_coe_cluster(name, cluster_template_id=cluster_template_id, **kwargs) + changed = True + else: + changed = False + + # NOTE (brtknr): At present, create_coe_cluster request returns + # cluster_id as `uuid` whereas get_coe_cluster request returns the + # same field as `id`. This behaviour may change in the future + # therefore try `id` first then `uuid`. + cluster_id = cluster.get('id', cluster.get('uuid')) + cluster['id'] = cluster['uuid'] = cluster_id + module.exit_json(changed=changed, cluster=cluster, id=cluster_id) + elif state == 'absent': + if not cluster: + module.exit_json(changed=False) + else: + cloud.delete_coe_cluster(name) + module.exit_json(changed=True) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_coe_cluster_template.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_coe_cluster_template.py new file mode 100644 index 00000000..1fcca8d2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_coe_cluster_template.py @@ -0,0 +1,388 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Catalyst IT Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: coe_cluster_template +short_description: Add/Remove COE cluster template from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove COE cluster template from the OpenStack Container Infra + service. +options: + coe: + description: + - The Container Orchestration Engine for this clustertemplate + choices: [kubernetes, swarm, mesos] + type: str + required: true + dns_nameserver: + description: + - The DNS nameserver address + default: '8.8.8.8' + type: str + docker_storage_driver: + description: + - Docker storage driver + choices: [devicemapper, overlay, overlay2] + type: str + docker_volume_size: + description: + - The size in GB of the docker volume + type: int + external_network_id: + description: + - The external network to attach to the Cluster + type: str + fixed_network: + description: + - The fixed network name to attach to the Cluster + type: str + fixed_subnet: + description: + - The fixed subnet name to attach to the Cluster + type: str + flavor_id: + description: + - The flavor of the minion node for this ClusterTemplate + type: str + floating_ip_enabled: + description: + - Indicates whether created clusters should have a floating ip or not + type: bool + default: true + keypair_id: + description: + - Name or ID of the keypair to use. + type: str + image_id: + description: + - Image id the cluster will be based on + type: str + required: true + labels: + description: + - One or more key/value pairs + type: raw + http_proxy: + description: + - Address of a proxy that will receive all HTTP requests and relay them + The format is a URL including a port number + type: str + https_proxy: + description: + - Address of a proxy that will receive all HTTPS requests and relay + them. The format is a URL including a port number + type: str + master_flavor_id: + description: + - The flavor of the master node for this ClusterTemplate + type: str + master_lb_enabled: + description: + - Indicates whether created clusters should have a load balancer + for master nodes or not + type: bool + default: 'no' + name: + description: + - Name that has to be given to the cluster template + required: true + type: str + network_driver: + description: + - The name of the driver used for instantiating container networks + choices: [flannel, calico, docker] + type: str + no_proxy: + description: + - A comma separated list of IPs for which proxies should not be + used in the cluster + type: str + public: + description: + - Indicates whether the ClusterTemplate is public or not + type: bool + default: 'no' + registry_enabled: + description: + - Indicates whether the docker registry is enabled + type: bool + default: 'no' + server_type: + description: + - Server type for this ClusterTemplate + choices: [vm, bm] + default: vm + type: str + state: + description: + - Indicate desired state of the resource. + choices: [present, absent] + default: present + type: str + tls_disabled: + description: + - Indicates whether the TLS should be disabled + type: bool + default: 'no' + volume_driver: + description: + - The name of the driver used for instantiating container volumes + choices: [cinder, rexray] + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +id: + description: The cluster UUID. + returned: On success when I(state) is 'present' + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +cluster_template: + description: Dictionary describing the template. + returned: On success when I(state) is 'present' + type: complex + contains: + coe: + description: The Container Orchestration Engine for this clustertemplate + type: str + sample: kubernetes + dns_nameserver: + description: The DNS nameserver address + type: str + sample: '8.8.8.8' + docker_storage_driver: + description: Docker storage driver + type: str + sample: devicemapper + docker_volume_size: + description: The size in GB of the docker volume + type: int + sample: 5 + external_network_id: + description: The external network to attach to the Cluster + type: str + sample: public + fixed_network: + description: The fixed network name to attach to the Cluster + type: str + sample: 07767ec6-85f5-44cb-bd63-242a8e7f0d9d + fixed_subnet: + description: + - The fixed subnet name to attach to the Cluster + type: str + sample: 05567ec6-85f5-44cb-bd63-242a8e7f0d9d + flavor_id: + description: + - The flavor of the minion node for this ClusterTemplate + type: str + sample: c1.c1r1 + floating_ip_enabled: + description: + - Indicates whether created clusters should have a floating ip or not + type: bool + sample: true + keypair_id: + description: + - Name or ID of the keypair to use. + type: str + sample: mykey + image_id: + description: + - Image id the cluster will be based on + type: str + sample: 05567ec6-85f5-44cb-bd63-242a8e7f0e9d + labels: + description: One or more key/value pairs + type: dict + sample: {'key1': 'value1', 'key2': 'value2'} + http_proxy: + description: + - Address of a proxy that will receive all HTTP requests and relay them + The format is a URL including a port number + type: str + sample: http://10.0.0.11:9090 + https_proxy: + description: + - Address of a proxy that will receive all HTTPS requests and relay + them. The format is a URL including a port number + type: str + sample: https://10.0.0.10:8443 + master_flavor_id: + description: + - The flavor of the master node for this ClusterTemplate + type: str + sample: c1.c1r1 + master_lb_enabled: + description: + - Indicates whether created clusters should have a load balancer + for master nodes or not + type: bool + sample: true + name: + description: + - Name that has to be given to the cluster template + type: str + sample: k8scluster + network_driver: + description: + - The name of the driver used for instantiating container networks + type: str + sample: calico + no_proxy: + description: + - A comma separated list of IPs for which proxies should not be + used in the cluster + type: str + sample: 10.0.0.4,10.0.0.5 + public: + description: + - Indicates whether the ClusterTemplate is public or not + type: bool + sample: false + registry_enabled: + description: + - Indicates whether the docker registry is enabled + type: bool + sample: false + server_type: + description: + - Server type for this ClusterTemplate + type: str + sample: vm + tls_disabled: + description: + - Indicates whether the TLS should be disabled + type: bool + sample: false + volume_driver: + description: + - The name of the driver used for instantiating container volumes + type: str + sample: cinder +''' + +EXAMPLES = ''' +# Create a new Kubernetes cluster template +- openstack.cloud.coe_cluster_template: + name: k8s + coe: kubernetes + keypair_id: mykey + image_id: 2a8c9888-9054-4b06-a1ca-2bb61f9adb72 + public: no +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _parse_labels(labels): + if isinstance(labels, str): + labels_dict = {} + for kv_str in labels.split(","): + k, v = kv_str.split("=") + labels_dict[k] = v + return labels_dict + if not labels: + return {} + return labels + + +def main(): + argument_spec = openstack_full_argument_spec( + coe=dict(required=True, choices=['kubernetes', 'swarm', 'mesos']), + dns_nameserver=dict(default='8.8.8.8'), + docker_storage_driver=dict(choices=['devicemapper', 'overlay', 'overlay2']), + docker_volume_size=dict(type='int'), + external_network_id=dict(default=None), + fixed_network=dict(default=None), + fixed_subnet=dict(default=None), + flavor_id=dict(default=None), + floating_ip_enabled=dict(type='bool', default=True), + keypair_id=dict(default=None), + image_id=dict(required=True), + labels=dict(default=None, type='raw'), + http_proxy=dict(default=None), + https_proxy=dict(default=None), + master_lb_enabled=dict(type='bool', default=False), + master_flavor_id=dict(default=None), + name=dict(required=True), + network_driver=dict(choices=['flannel', 'calico', 'docker']), + no_proxy=dict(default=None), + public=dict(type='bool', default=False), + registry_enabled=dict(type='bool', default=False), + server_type=dict(default="vm", choices=['vm', 'bm']), + state=dict(default='present', choices=['absent', 'present']), + tls_disabled=dict(type='bool', default=False), + volume_driver=dict(choices=['cinder', 'rexray']), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + params = module.params.copy() + + state = module.params['state'] + name = module.params['name'] + coe = module.params['coe'] + image_id = module.params['image_id'] + + kwargs = dict( + dns_nameserver=module.params['dns_nameserver'], + docker_storage_driver=module.params['docker_storage_driver'], + docker_volume_size=module.params['docker_volume_size'], + external_network_id=module.params['external_network_id'], + fixed_network=module.params['fixed_network'], + fixed_subnet=module.params['fixed_subnet'], + flavor_id=module.params['flavor_id'], + floating_ip_enabled=module.params['floating_ip_enabled'], + keypair_id=module.params['keypair_id'], + labels=_parse_labels(params['labels']), + http_proxy=module.params['http_proxy'], + https_proxy=module.params['https_proxy'], + master_lb_enabled=module.params['master_lb_enabled'], + master_flavor_id=module.params['master_flavor_id'], + network_driver=module.params['network_driver'], + no_proxy=module.params['no_proxy'], + public=module.params['public'], + registry_enabled=module.params['registry_enabled'], + server_type=module.params['server_type'], + tls_disabled=module.params['tls_disabled'], + volume_driver=module.params['volume_driver'], + ) + + sdk, cloud = openstack_cloud_from_module(module) + try: + changed = False + template = cloud.get_coe_cluster_template(name_or_id=name, filters={'coe': coe, 'image_id': image_id}) + + if state == 'present': + if not template: + template = cloud.create_coe_cluster_template(name, coe=coe, image_id=image_id, **kwargs) + changed = True + else: + changed = False + + module.exit_json(changed=changed, cluster_template=template, id=template['uuid']) + elif state == 'absent': + if not template: + module.exit_json(changed=False) + else: + cloud.delete_coe_cluster_template(name) + module.exit_json(changed=True) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_flavor_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_flavor_info.py new file mode 100644 index 00000000..48e78bf8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_flavor_info.py @@ -0,0 +1,228 @@ +#!/usr/bin/python + +# Copyright (c) 2015 IBM +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: compute_flavor_info +short_description: Retrieve information about one or more flavors +author: OpenStack Ansible SIG +description: + - Retrieve information about available OpenStack instance flavors. By default, + information about ALL flavors are retrieved. Filters can be applied to get + information for only matching flavors. For example, you can filter on the + amount of RAM available to the flavor, or the number of virtual CPUs + available to the flavor, or both. When specifying multiple filters, + *ALL* filters must match on a flavor before that flavor is returned as + a fact. + - This module was called C(openstack.cloud.compute_flavor_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.compute_flavor_info) module no longer returns C(ansible_facts)! +notes: + - The result contains a list of unsorted flavors. +options: + name: + description: + - A flavor name. Cannot be used with I(ram) or I(vcpus) or I(ephemeral). + type: str + ram: + description: + - "A string used for filtering flavors based on the amount of RAM + (in MB) desired. This string accepts the following special values: + 'MIN' (return flavors with the minimum amount of RAM), and 'MAX' + (return flavors with the maximum amount of RAM)." + + - "A specific amount of RAM may also be specified. Any flavors with this + exact amount of RAM will be returned." + + - "A range of acceptable RAM may be given using a special syntax. Simply + prefix the amount of RAM with one of these acceptable range values: + '<', '>', '<=', '>='. These values represent less than, greater than, + less than or equal to, and greater than or equal to, respectively." + type: str + vcpus: + description: + - A string used for filtering flavors based on the number of virtual + CPUs desired. Format is the same as the I(ram) parameter. + type: str + limit: + description: + - Limits the number of flavors returned. All matching flavors are + returned by default. + type: int + ephemeral: + description: + - A string used for filtering flavors based on the amount of ephemeral + storage. Format is the same as the I(ram) parameter + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather information about all available flavors +- openstack.cloud.compute_flavor_info: + cloud: mycloud + register: result + +- debug: + msg: "{{ result.openstack_flavors }}" + +# Gather information for the flavor named "xlarge-flavor" +- openstack.cloud.compute_flavor_info: + cloud: mycloud + name: "xlarge-flavor" + +# Get all flavors that have exactly 512 MB of RAM. +- openstack.cloud.compute_flavor_info: + cloud: mycloud + ram: "512" + +# Get all flavors that have 1024 MB or more of RAM. +- openstack.cloud.compute_flavor_info: + cloud: mycloud + ram: ">=1024" + +# Get a single flavor that has the minimum amount of RAM. Using the 'limit' +# option will guarantee only a single flavor is returned. +- openstack.cloud.compute_flavor_info: + cloud: mycloud + ram: "MIN" + limit: 1 + +# Get all flavors with 1024 MB of RAM or more, AND exactly 2 virtual CPUs. +- openstack.cloud.compute_flavor_info: + cloud: mycloud + ram: ">=1024" + vcpus: "2" + +# Get all flavors with 1024 MB of RAM or more, exactly 2 virtual CPUs, and +# less than 30gb of ephemeral storage. +- openstack.cloud.compute_flavor_info: + cloud: mycloud + ram: ">=1024" + vcpus: "2" + ephemeral: "<30" +''' + + +RETURN = ''' +openstack_flavors: + description: Dictionary describing the flavors. + returned: On success. + type: complex + contains: + id: + description: Flavor ID. + returned: success + type: str + sample: "515256b8-7027-4d73-aa54-4e30a4a4a339" + name: + description: Flavor name. + returned: success + type: str + sample: "tiny" + disk: + description: Size of local disk, in GB. + returned: success + type: int + sample: 10 + ephemeral: + description: Ephemeral space size, in GB. + returned: success + type: int + sample: 10 + ram: + description: Amount of memory, in MB. + returned: success + type: int + sample: 1024 + swap: + description: Swap space size, in MB. + returned: success + type: int + sample: 100 + vcpus: + description: Number of virtual CPUs. + returned: success + type: int + sample: 2 + is_public: + description: Make flavor accessible to the public. + returned: success + type: bool + sample: true +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + ram=dict(required=False, default=None), + vcpus=dict(required=False, default=None), + limit=dict(required=False, default=None, type='int'), + ephemeral=dict(required=False, default=None), + ) + module_kwargs = openstack_module_kwargs( + mutually_exclusive=[ + ['name', 'ram'], + ['name', 'vcpus'], + ['name', 'ephemeral'] + ] + ) + module = AnsibleModule(argument_spec, **module_kwargs) + is_old_facts = module._name == 'openstack.cloud.compute_flavor_facts' + if is_old_facts: + module.deprecate("The 'openstack.cloud.compute_flavor_facts' module has been renamed to 'openstack.cloud.compute_flavor_info', " + "and the renamed one no longer returns ansible_facts", version='2.13') + + name = module.params['name'] + vcpus = module.params['vcpus'] + ram = module.params['ram'] + ephemeral = module.params['ephemeral'] + limit = module.params['limit'] + + filters = {} + if vcpus: + filters['vcpus'] = vcpus + if ram: + filters['ram'] = ram + if ephemeral: + filters['ephemeral'] = ephemeral + + sdk, cloud = openstack_cloud_from_module(module) + try: + if name: + flavors = cloud.search_flavors(filters={'name': name}) + + else: + flavors = cloud.list_flavors() + if filters: + flavors = cloud.range_search(flavors, filters) + + if limit is not None: + flavors = flavors[:limit] + + if is_old_facts: + module.exit_json(changed=False, + ansible_facts=dict(openstack_flavors=flavors)) + else: + module.exit_json(changed=False, + openstack_flavors=flavors) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_floating_ip.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_floating_ip.py new file mode 100644 index 00000000..dbf6ee0d --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_floating_ip.py @@ -0,0 +1,255 @@ +#!/usr/bin/python + +# Copyright: (c) 2015, Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: floating_ip +author: OpenStack Ansible SIG +short_description: Add/Remove floating IP from an instance +description: + - Add or Remove a floating IP to an instance. + - Returns the floating IP when attaching only if I(wait=true). +options: + server: + description: + - The name or ID of the instance to which the IP address + should be assigned. + required: true + type: str + network: + description: + - The name or ID of a neutron external network or a nova pool name. + type: str + floating_ip_address: + description: + - A floating IP address to attach or to detach. Required only if I(state) + is absent. When I(state) is present can be used to specify a IP address + to attach. + type: str + reuse: + description: + - When I(state) is present, and I(floating_ip_address) is not present, + this parameter can be used to specify whether we should try to reuse + a floating IP address already allocated to the project. + type: bool + default: 'no' + fixed_address: + description: + - To which fixed IP of server the floating IP address should be + attached to. + type: str + nat_destination: + description: + - The name or id of a neutron private network that the fixed IP to + attach floating IP is on + aliases: ["fixed_network", "internal_network"] + type: str + wait: + description: + - When attaching a floating IP address, specify whether to wait for it to appear as attached. + - Must be set to C(yes) for the module to return the value of the floating IP. + type: bool + default: 'no' + timeout: + description: + - Time to wait for an IP address to appear as attached. See wait. + required: false + default: 60 + type: int + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + purge: + description: + - When I(state) is absent, indicates whether or not to delete the floating + IP completely, or only detach it from the server. Default is to detach only. + type: bool + default: 'no' +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Assign a floating IP to the first interface of `cattle001` from an existing +# external network or nova pool. A new floating IP from the first available +# external network is allocated to the project. +- openstack.cloud.floating_ip: + cloud: dguerri + server: cattle001 + +# Assign a new floating IP to the instance fixed ip `192.0.2.3` of +# `cattle001`. If a free floating IP is already allocated to the project, it is +# reused; if not, a new one is created. +- openstack.cloud.floating_ip: + cloud: dguerri + state: present + reuse: yes + server: cattle001 + network: ext_net + fixed_address: 192.0.2.3 + wait: true + timeout: 180 + +# Assign a new floating IP from the network `ext_net` to the instance fixed +# ip in network `private_net` of `cattle001`. +- openstack.cloud.floating_ip: + cloud: dguerri + state: present + server: cattle001 + network: ext_net + nat_destination: private_net + wait: true + timeout: 180 + +# Detach a floating IP address from a server +- openstack.cloud.floating_ip: + cloud: dguerri + state: absent + floating_ip_address: 203.0.113.2 + server: cattle001 +''' + +from ansible.module_utils.basic import AnsibleModule, remove_values +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _get_floating_ip(cloud, floating_ip_address): + f_ips = cloud.search_floating_ips( + filters={'floating_ip_address': floating_ip_address}) + if not f_ips: + return None + + return f_ips[0] + + +def main(): + argument_spec = openstack_full_argument_spec( + server=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + network=dict(required=False, default=None), + floating_ip_address=dict(required=False, default=None), + reuse=dict(required=False, type='bool', default=False), + fixed_address=dict(required=False, default=None), + nat_destination=dict(required=False, default=None, + aliases=['fixed_network', 'internal_network']), + wait=dict(required=False, type='bool', default=False), + timeout=dict(required=False, type='int', default=60), + purge=dict(required=False, type='bool', default=False), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + server_name_or_id = module.params['server'] + state = module.params['state'] + network = module.params['network'] + floating_ip_address = module.params['floating_ip_address'] + reuse = module.params['reuse'] + fixed_address = module.params['fixed_address'] + nat_destination = module.params['nat_destination'] + wait = module.params['wait'] + timeout = module.params['timeout'] + purge = module.params['purge'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + + server = cloud.get_server(server_name_or_id) + if server is None: + module.fail_json( + msg="server {0} not found".format(server_name_or_id)) + + if state == 'present': + # If f_ip already assigned to server, check that it matches + # requirements. + public_ip = cloud.get_server_public_ip(server) + f_ip = _get_floating_ip(cloud, public_ip) if public_ip else public_ip + if f_ip: + if network: + network_id = cloud.get_network(name_or_id=network)["id"] + else: + network_id = None + # check if we have floating ip on given nat_destination network + if nat_destination: + nat_floating_addrs = [ + addr for addr in server.addresses.get( + cloud.get_network(nat_destination)['name'], []) + if addr['addr'] == public_ip + and addr['OS-EXT-IPS:type'] == 'floating' + ] + + if len(nat_floating_addrs) == 0: + module.fail_json(msg="server {server} already has a " + "floating-ip on a different " + "nat-destination than '{nat_destination}'" + .format(server=server_name_or_id, + nat_destination=nat_destination)) + + if all([fixed_address, f_ip.fixed_ip_address == fixed_address, + network, f_ip.network != network_id]): + # Current state definitely conflicts with requirements + module.fail_json(msg="server {server} already has a " + "floating-ip on requested " + "interface but it doesn't match " + "requested network {network}: {fip}" + .format(server=server_name_or_id, + network=network, + fip=remove_values(f_ip, + module.no_log_values))) + if not network or f_ip.network == network_id: + # Requirements are met + module.exit_json(changed=False, floating_ip=f_ip) + + # Requirements are vague enough to ignore existing f_ip and try + # to create a new f_ip to the server. + + server = cloud.add_ips_to_server( + server=server, ips=floating_ip_address, ip_pool=network, + reuse=reuse, fixed_address=fixed_address, wait=wait, + timeout=timeout, nat_destination=nat_destination) + fip_address = cloud.get_server_public_ip(server) + # Update the floating IP status + f_ip = _get_floating_ip(cloud, fip_address) + module.exit_json(changed=True, floating_ip=f_ip) + + elif state == 'absent': + if floating_ip_address is None: + if not server_name_or_id: + module.fail_json(msg="either server or floating_ip_address are required") + server = cloud.get_server(server_name_or_id) + floating_ip_address = cloud.get_server_public_ip(server) + + f_ip = _get_floating_ip(cloud, floating_ip_address) + + if not f_ip: + # Nothing to detach + module.exit_json(changed=False) + changed = False + if f_ip["fixed_ip_address"]: + cloud.detach_ip_from_server( + server_id=server['id'], floating_ip_id=f_ip['id']) + # Update the floating IP status + f_ip = cloud.get_floating_ip(id=f_ip['id']) + changed = True + if purge: + cloud.delete_floating_ip(f_ip['id']) + module.exit_json(changed=True) + module.exit_json(changed=changed, floating_ip=f_ip) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_group.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_group.py new file mode 100644 index 00000000..116650e5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_group.py @@ -0,0 +1,161 @@ +#!/usr/bin/python +# Copyright (c) 2016 IBM +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_group +short_description: Manage OpenStack Identity Groups +author: OpenStack Ansible SIG +description: + - Manage OpenStack Identity Groups. Groups can be created, deleted or + updated. Only the I(description) value can be updated. +options: + name: + description: + - Group name + required: true + type: str + description: + description: + - Group description + type: str + domain_id: + description: + - Domain id to create the group in if the cloud supports domains. + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a group named "demo" +- openstack.cloud.identity_group: + cloud: mycloud + state: present + name: demo + description: "Demo Group" + domain_id: demoid + +# Update the description on existing "demo" group +- openstack.cloud.identity_group: + cloud: mycloud + state: present + name: demo + description: "Something else" + domain_id: demoid + +# Delete group named "demo" +- openstack.cloud.identity_group: + cloud: mycloud + state: absent + name: demo +''' + +RETURN = ''' +group: + description: Dictionary describing the group. + returned: On success when I(state) is 'present'. + type: complex + contains: + id: + description: Unique group ID + type: str + sample: "ee6156ff04c645f481a6738311aea0b0" + name: + description: Group name + type: str + sample: "demo" + description: + description: Group description + type: str + sample: "Demo Group" + domain_id: + description: Domain for the group + type: str + sample: "default" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(state, description, group): + if state == 'present' and not group: + return True + if state == 'present' and description is not None and group.description != description: + return True + if state == 'absent' and group: + return True + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + description=dict(required=False, default=None), + domain_id=dict(required=False, default=None), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + name = module.params.get('name') + description = module.params.get('description') + state = module.params.get('state') + + domain_id = module.params.pop('domain_id') + + sdk, cloud = openstack_cloud_from_module(module) + try: + if domain_id: + group = cloud.get_group(name, filters={'domain_id': domain_id}) + else: + group = cloud.get_group(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(state, description, group)) + + if state == 'present': + if group is None: + group = cloud.create_group( + name=name, description=description, domain=domain_id) + changed = True + else: + if description is not None and group.description != description: + group = cloud.update_group( + group.id, description=description) + changed = True + else: + changed = False + module.exit_json(changed=changed, group=group) + + elif state == 'absent': + if group is None: + changed = False + else: + cloud.delete_group(group.id) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_group_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_group_info.py new file mode 100644 index 00000000..c3e78080 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_group_info.py @@ -0,0 +1,158 @@ +#!/usr/bin/python + +# Copyright (c) 2019, Phillipe Smith <phillipelnx@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_group_info +short_description: Retrieve info about one or more OpenStack groups +author: OpenStack Ansible SIG +description: + - Retrieve info about a one or more OpenStack groups. +options: + name: + description: + - Name or ID of the group. + type: str + domain: + description: + - Name or ID of the domain containing the group if the cloud supports domains + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather info about previously created groups +- name: gather info + hosts: localhost + tasks: + - name: Gather info about previously created groups + openstack.cloud.identity_group_info: + cloud: awesomecloud + register: openstack_groups + - debug: + var: openstack_groups + +# Gather info about a previously created group by name +- name: gather info + hosts: localhost + tasks: + - name: Gather info about a previously created group by name + openstack.cloud.identity_group_info: + cloud: awesomecloud + name: demogroup + register: openstack_groups + - debug: + var: openstack_groups + +# Gather info about a previously created group in a specific domain +- name: gather info + hosts: localhost + tasks: + - name: Gather info about a previously created group in a specific domain + openstack.cloud.identity_group_info: + cloud: awesomecloud + name: demogroup + domain: admindomain + register: openstack_groups + - debug: + var: openstack_groups + +# Gather info about a previously created group in a specific domain with filter +- name: gather info + hosts: localhost + tasks: + - name: Gather info about a previously created group in a specific domain with filter + openstack.cloud.identity_group_info: + cloud: awesomecloud + name: demogroup + domain: admindomain + filters: + enabled: False + register: openstack_groups + - debug: + var: openstack_groups +''' + + +RETURN = ''' +openstack_groups: + description: Dictionary describing all the matching groups. + returned: always, but can be null + type: complex + contains: + name: + description: Name given to the group. + returned: success + type: str + description: + description: Description of the group. + returned: success + type: str + id: + description: Unique UUID. + returned: success + type: str + domain_id: + description: Domain ID containing the group (keystone v3 clouds only) + returned: success + type: bool +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec, openstack_cloud_from_module + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + domain=dict(required=False, default=None), + filters=dict(required=False, type='dict', default=None), + ) + + module = AnsibleModule(argument_spec) + + sdk, opcloud = openstack_cloud_from_module(module) + try: + name = module.params['name'] + domain = module.params['domain'] + filters = module.params['filters'] + + if domain: + try: + # We assume admin is passing domain id + dom = opcloud.get_domain(domain)['id'] + domain = dom + except Exception: + # If we fail, maybe admin is passing a domain name. + # Note that domains have unique names, just like id. + dom = opcloud.search_domains(filters={'name': domain}) + if dom: + domain = dom[0]['id'] + else: + module.fail_json(msg='Domain name or ID does not exist') + + if not filters: + filters = {} + + groups = opcloud.search_groups(name, filters, domain_id=domain) + module.exit_json(changed=False, groups=groups) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_image.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_image.py new file mode 100644 index 00000000..f6109c80 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_image.py @@ -0,0 +1,241 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +# TODO(mordred): we need to support "location"(v1) and "locations"(v2) + +DOCUMENTATION = ''' +--- +module: image +short_description: Add/Delete images from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove images from the OpenStack Image Repository +options: + name: + description: + - The name of the image when uploading - or the name/ID of the image if deleting + required: true + type: str + id: + description: + - The ID of the image when uploading an image + type: str + checksum: + description: + - The checksum of the image + type: str + disk_format: + description: + - The format of the disk that is getting uploaded + default: qcow2 + choices: ['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso', 'vhdx', 'ploop'] + type: str + container_format: + description: + - The format of the container + default: bare + choices: ['ami', 'aki', 'ari', 'bare', 'ovf', 'ova', 'docker'] + type: str + owner: + description: + - The owner of the image + type: str + min_disk: + description: + - The minimum disk space (in GB) required to boot this image + type: int + min_ram: + description: + - The minimum ram (in MB) required to boot this image + type: int + is_public: + description: + - Whether the image can be accessed publicly. Note that publicizing an image requires admin role by default. + type: bool + default: false + protected: + description: + - Prevent image from being deleted + type: bool + default: 'no' + filename: + description: + - The path to the file which has to be uploaded + type: str + ramdisk: + description: + - The name of an existing ramdisk image that will be associated with this image + type: str + kernel: + description: + - The name of an existing kernel image that will be associated with this image + type: str + properties: + description: + - Additional properties to be associated with this image + default: {} + type: dict + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + volume: + description: + - ID of a volume to create an image from. + - The volume must be in AVAILABLE state. + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Upload an image from a local file named cirros-0.3.0-x86_64-disk.img +- openstack.cloud.image: + auth: + auth_url: https://identity.example.com + username: admin + password: passme + project_name: admin + openstack.cloud.identity_user_domain_name: Default + openstack.cloud.project_domain_name: Default + name: cirros + container_format: bare + disk_format: qcow2 + state: present + filename: cirros-0.3.0-x86_64-disk.img + kernel: cirros-vmlinuz + ramdisk: cirros-initrd + properties: + cpu_arch: x86_64 + distro: ubuntu + +# Create image from volume attached to an instance +- name: create volume snapshot + openstack.cloud.volume_snapshot: + auth: + "{{ auth }}" + display_name: myvol_snapshot + volume: myvol + force: yes + register: myvol_snapshot + +- name: create volume from snapshot + openstack.cloud.volume: + auth: + "{{ auth }}" + size: "{{ myvol_snapshot.snapshot.size }}" + snapshot_id: "{{ myvol_snapshot.snapshot.id }}" + display_name: myvol_snapshot_volume + wait: yes + register: myvol_snapshot_volume + +- name: create image from volume snapshot + openstack.cloud.image: + auth: + "{{ auth }}" + volume: "{{ myvol_snapshot_volume.volume.id }}" + name: myvol_image +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + id=dict(default=None), + checksum=dict(default=None), + disk_format=dict(default='qcow2', choices=['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso', 'vhdx', 'ploop']), + container_format=dict(default='bare', choices=['ami', 'aki', 'ari', 'bare', 'ovf', 'ova', 'docker']), + owner=dict(default=None), + min_disk=dict(type='int', default=0), + min_ram=dict(type='int', default=0), + is_public=dict(type='bool', default=False), + protected=dict(type='bool', default=False), + filename=dict(default=None), + ramdisk=dict(default=None), + kernel=dict(default=None), + properties=dict(type='dict', default={}), + volume=dict(default=None), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs( + mutually_exclusive=[['filename', 'volume']], + ) + module = AnsibleModule(argument_spec, **module_kwargs) + + sdk, cloud = openstack_cloud_from_module(module) + try: + + changed = False + if module.params['id']: + image = cloud.get_image(name_or_id=module.params['id']) + elif module.params['checksum']: + image = cloud.get_image(name_or_id=module.params['name'], filters={'checksum': module.params['checksum']}) + else: + image = cloud.get_image(name_or_id=module.params['name']) + + if module.params['state'] == 'present': + if not image: + kwargs = {} + if module.params['id'] is not None: + kwargs['id'] = module.params['id'] + image = cloud.create_image( + name=module.params['name'], + filename=module.params['filename'], + disk_format=module.params['disk_format'], + container_format=module.params['container_format'], + wait=module.params['wait'], + timeout=module.params['timeout'], + is_public=module.params['is_public'], + protected=module.params['protected'], + min_disk=module.params['min_disk'], + min_ram=module.params['min_ram'], + volume=module.params['volume'], + **kwargs + ) + changed = True + if not module.params['wait']: + module.exit_json(changed=changed, image=image, id=image.id) + + cloud.update_image_properties( + image=image, + kernel=module.params['kernel'], + ramdisk=module.params['ramdisk'], + protected=module.params['protected'], + **module.params['properties']) + image = cloud.get_image(name_or_id=image.id) + module.exit_json(changed=changed, image=image, id=image.id) + + elif module.params['state'] == 'absent': + if not image: + changed = False + else: + cloud.delete_image( + name_or_id=module.params['name'], + wait=module.params['wait'], + timeout=module.params['timeout']) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_image_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_image_info.py new file mode 100644 index 00000000..611de89b --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_image_info.py @@ -0,0 +1,186 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +module: image_info +short_description: Retrieve information about an image within OpenStack. +author: OpenStack Ansible SIG +description: + - Retrieve information about a image image from OpenStack. + - This module was called C(openstack.cloud.image_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.image_info) module no longer returns C(ansible_facts)! +options: + image: + description: + - Name or ID of the image + required: false + type: str + properties: + description: + - Dict of properties of the images used for query + type: dict + required: false +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Gather information about a previously created image named image1 + openstack.cloud.image_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + image: image1 + register: result + +- name: Show openstack information + debug: + msg: "{{ result.openstack_image }}" + +# Show all available Openstack images +- name: Retrieve all available Openstack images + openstack.cloud.image_info: + register: result + +- name: Show images + debug: + msg: "{{ result.openstack_image }}" + +# Show images matching requested properties +- name: Retrieve images having properties with desired values + openstack.cloud.image_facts: + properties: + some_property: some_value + OtherProp: OtherVal + +- name: Show images + debug: + msg: "{{ result.openstack_image }}" +''' + +RETURN = ''' +openstack_image: + description: has all the openstack information about the image + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the image. + returned: success + type: str + status: + description: Image status. + returned: success + type: str + created_at: + description: Image created at timestamp. + returned: success + type: str + deleted: + description: Image deleted flag. + returned: success + type: bool + container_format: + description: Container format of the image. + returned: success + type: str + min_ram: + description: Min amount of RAM required for this image. + returned: success + type: int + disk_format: + description: Disk format of the image. + returned: success + type: str + updated_at: + description: Image updated at timestamp. + returned: success + type: str + properties: + description: Additional properties associated with the image. + returned: success + type: dict + min_disk: + description: Min amount of disk space required for this image. + returned: success + type: int + protected: + description: Image protected flag. + returned: success + type: bool + checksum: + description: Checksum for the image. + returned: success + type: str + owner: + description: Owner for the image. + returned: success + type: str + is_public: + description: Is public flag of the image. + returned: success + type: bool + deleted_at: + description: Image deleted at timestamp. + returned: success + type: str + size: + description: Size of the image. + returned: success + type: int +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + + argument_spec = openstack_full_argument_spec( + image=dict(required=False), + properties=dict(default=None, type='dict'), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + is_old_facts = module._name == 'openstack.cloud.image_facts' + if is_old_facts: + module.deprecate("The 'openstack.cloud.image_facts' module has been renamed to 'openstack.cloud.image_info', " + "and the renamed one no longer returns ansible_facts", version='2.13') + + sdk, cloud = openstack_cloud_from_module(module) + try: + if module.params['image']: + image = cloud.get_image(module.params['image']) + if is_old_facts: + module.exit_json(changed=False, ansible_facts=dict( + openstack_image=image)) + else: + module.exit_json(changed=False, openstack_image=image) + else: + images = cloud.search_images(filters=module.params['properties']) + if is_old_facts: + module.exit_json(changed=False, ansible_facts=dict( + openstack_image=images)) + else: + module.exit_json(changed=False, openstack_image=images) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_ironic.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_ironic.py new file mode 100644 index 00000000..9145d54e --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_ironic.py @@ -0,0 +1,364 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (c) 2014, Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: baremetal_node +short_description: Create/Delete Bare Metal Resources from OpenStack +author: OpenStack Ansible SIG +description: + - Create or Remove Ironic nodes from OpenStack. +options: + state: + description: + - Indicates desired state of the resource + choices: ['present', 'absent'] + default: present + type: str + uuid: + description: + - globally unique identifier (UUID) to be given to the resource. Will + be auto-generated if not specified, and name is specified. + - Definition of a UUID will always take precedence to a name value. + type: str + name: + description: + - unique name identifier to be given to the resource. + type: str + driver: + description: + - The name of the Ironic Driver to use with this node. + - Required when I(state=present) + type: str + chassis_uuid: + description: + - Associate the node with a pre-defined chassis. + type: str + ironic_url: + description: + - If noauth mode is utilized, this is required to be set to the + endpoint URL for the Ironic API. Use with "auth" and "auth_type" + settings set to None. + type: str + driver_info: + description: + - Information for this server's driver. Will vary based on which + driver is in use. Any sub-field which is populated will be validated + during creation. + required: true + type: dict + suboptions: + power: + description: + - Information necessary to turn this server on / off. + This often includes such things as IPMI username, password, and IP address. + required: true + deploy: + description: + - Information necessary to deploy this server directly, without using Nova. THIS IS NOT RECOMMENDED. + console: + description: + - Information necessary to connect to this server's serial console. Not all drivers support this. + management: + description: + - Information necessary to interact with this server's management interface. May be shared by power_info in some cases. + required: true + nics: + description: + - 'A list of network interface cards, eg, " - mac: aa:bb:cc:aa:bb:cc"' + required: true + type: list + elements: dict + suboptions: + mac: + description: The MAC address of the network interface card. + type: str + required: true + properties: + description: + - Definition of the physical characteristics of this server, used for scheduling purposes + type: dict + suboptions: + cpu_arch: + description: + - CPU architecture (x86_64, i686, ...) + default: x86_64 + cpus: + description: + - Number of CPU cores this machine has + default: 1 + ram: + description: + - amount of RAM this machine has, in MB + default: 1 + disk_size: + description: + - size of first storage device in this machine (typically /dev/sda), in GB + default: 1 + capabilities: + description: + - special capabilities for the node, such as boot_option, node_role etc + (see U(https://docs.openstack.org/ironic/latest/install/advanced.html) + for more information) + default: "" + root_device: + description: + - Root disk device hints for deployment. + - See U(https://docs.openstack.org/ironic/latest/install/advanced.html#specifying-the-disk-for-deployment-root-device-hints) + for allowed hints. + default: "" + skip_update_of_masked_password: + description: + - Allows the code that would assert changes to nodes to skip the + update if the change is a single line consisting of the password + field. + - As of Kilo, by default, passwords are always masked to API + requests, which means the logic as a result always attempts to + re-assert the password field. + - C(skip_update_of_driver_password) is deprecated alias and will be removed in openstack.cloud 2.0.0. + type: bool + aliases: + - skip_update_of_driver_password +requirements: + - "python >= 3.6" + - "openstacksdk" + - "jsonpatch" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Enroll a node with some basic properties and driver info +- openstack.cloud.baremetal_node: + cloud: "devstack" + driver: "pxe_ipmitool" + uuid: "00000000-0000-0000-0000-000000000002" + properties: + cpus: 2 + cpu_arch: "x86_64" + ram: 8192 + disk_size: 64 + capabilities: "boot_option:local" + root_device: + wwn: "0x4000cca77fc4dba1" + nics: + - mac: "aa:bb:cc:aa:bb:cc" + - mac: "dd:ee:ff:dd:ee:ff" + driver_info: + power: + ipmi_address: "1.2.3.4" + ipmi_username: "admin" + ipmi_password: "adminpass" + chassis_uuid: "00000000-0000-0000-0000-000000000001" + +''' + +try: + import jsonpatch + HAS_JSONPATCH = True +except ImportError: + HAS_JSONPATCH = False + + +from ansible_collections.openstack.cloud.plugins.module_utils.ironic import ( + IronicModule, + ironic_argument_spec, +) +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_module_kwargs, + openstack_cloud_from_module +) + + +def _parse_properties(module): + p = module.params['properties'] + props = dict( + cpu_arch=p.get('cpu_arch') if p.get('cpu_arch') else 'x86_64', + cpus=p.get('cpus') if p.get('cpus') else 1, + memory_mb=p.get('ram') if p.get('ram') else 1, + local_gb=p.get('disk_size') if p.get('disk_size') else 1, + capabilities=p.get('capabilities') if p.get('capabilities') else '', + root_device=p.get('root_device') if p.get('root_device') else '', + ) + return props + + +def _parse_driver_info(sdk, module): + p = module.params['driver_info'] + info = p.get('power') + if not info: + raise sdk.exceptions.OpenStackCloudException( + "driver_info['power'] is required") + if p.get('console'): + info.update(p.get('console')) + if p.get('management'): + info.update(p.get('management')) + if p.get('deploy'): + info.update(p.get('deploy')) + return info + + +def _choose_id_value(module): + if module.params['uuid']: + return module.params['uuid'] + if module.params['name']: + return module.params['name'] + return None + + +def _choose_if_password_only(module, patch): + if len(patch) == 1: + if 'password' in patch[0]['path'] and module.params['skip_update_of_masked_password']: + # Return false to abort update as the password appears + # to be the only element in the patch. + return False + return True + + +def _exit_node_not_updated(module, server): + module.exit_json( + changed=False, + result="Node not updated", + uuid=server['uuid'], + provision_state=server['provision_state'] + ) + + +def main(): + argument_spec = ironic_argument_spec( + uuid=dict(required=False), + name=dict(required=False), + driver=dict(required=False), + driver_info=dict(type='dict', required=True), + nics=dict(type='list', required=True, elements="dict"), + properties=dict(type='dict', default={}), + chassis_uuid=dict(required=False), + skip_update_of_masked_password=dict( + required=False, + type='bool', + aliases=['skip_update_of_driver_password'], + deprecated_aliases=[dict(name='skip_update_of_driver_password', version='2.0.0')] + ), + state=dict(required=False, default='present', choices=['present', 'absent']) + ) + module_kwargs = openstack_module_kwargs() + module = IronicModule(argument_spec, **module_kwargs) + + if not HAS_JSONPATCH: + module.fail_json(msg='jsonpatch is required for this module') + + node_id = _choose_id_value(module) + + sdk, cloud = openstack_cloud_from_module(module) + try: + server = cloud.get_machine(node_id) + if module.params['state'] == 'present': + if module.params['driver'] is None: + module.fail_json(msg="A driver must be defined in order " + "to set a node to present.") + + properties = _parse_properties(module) + driver_info = _parse_driver_info(sdk, module) + kwargs = dict( + driver=module.params['driver'], + properties=properties, + driver_info=driver_info, + name=module.params['name'], + ) + + if module.params['chassis_uuid']: + kwargs['chassis_uuid'] = module.params['chassis_uuid'] + + if server is None: + # Note(TheJulia): Add a specific UUID to the request if + # present in order to be able to re-use kwargs for if + # the node already exists logic, since uuid cannot be + # updated. + if module.params['uuid']: + kwargs['uuid'] = module.params['uuid'] + + server = cloud.register_machine(module.params['nics'], + **kwargs) + module.exit_json(changed=True, uuid=server['uuid'], + provision_state=server['provision_state']) + else: + # TODO(TheJulia): Presently this does not support updating + # nics. Support needs to be added. + # + # Note(TheJulia): This message should never get logged + # however we cannot realistically proceed if neither a + # name or uuid was supplied to begin with. + if not node_id: + module.fail_json(msg="A uuid or name value " + "must be defined") + + # Note(TheJulia): Constructing the configuration to compare + # against. The items listed in the server_config block can + # be updated via the API. + + server_config = dict( + driver=server['driver'], + properties=server['properties'], + driver_info=server['driver_info'], + name=server['name'], + ) + + # Add the pre-existing chassis_uuid only if + # it is present in the server configuration. + if hasattr(server, 'chassis_uuid'): + server_config['chassis_uuid'] = server['chassis_uuid'] + + # Note(TheJulia): If a password is defined and concealed, a + # patch will always be generated and re-asserted. + patch = jsonpatch.JsonPatch.from_diff(server_config, kwargs) + + if not patch: + _exit_node_not_updated(module, server) + elif _choose_if_password_only(module, list(patch)): + # Note(TheJulia): Normally we would allow the general + # exception catch below, however this allows a specific + # message. + try: + server = cloud.patch_machine( + server['uuid'], + list(patch)) + except Exception as e: + module.fail_json(msg="Failed to update node, " + "Error: %s" % e.message) + + # Enumerate out a list of changed paths. + change_list = [] + for change in list(patch): + change_list.append(change['path']) + module.exit_json(changed=True, + result="Node Updated", + changes=change_list, + uuid=server['uuid'], + provision_state=server['provision_state']) + + # Return not updated by default as the conditions were not met + # to update. + _exit_node_not_updated(module, server) + + if module.params['state'] == 'absent': + if not node_id: + module.fail_json(msg="A uuid or name value must be defined " + "in order to remove a node.") + + if server is not None: + cloud.unregister_machine(module.params['nics'], + server['uuid']) + module.exit_json(changed=True, result="deleted") + else: + module.exit_json(changed=False, result="Server not found") + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_ironic_inspect.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_ironic_inspect.py new file mode 100644 index 00000000..f7d90d1c --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_ironic_inspect.py @@ -0,0 +1,133 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (c) 2015-2016, Hewlett Packard Enterprise Development Company LP +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: baremetal_inspect +short_description: Explicitly triggers baremetal node introspection in ironic. +author: OpenStack Ansible SIG +description: + - Requests Ironic to set a node into inspect state in order to collect metadata regarding the node. + This command may be out of band or in-band depending on the ironic driver configuration. + This is only possible on nodes in 'manageable' and 'available' state. +options: + mac: + description: + - unique mac address that is used to attempt to identify the host. + type: str + uuid: + description: + - globally unique identifier (UUID) to identify the host. + type: str + name: + description: + - unique name identifier to identify the host in Ironic. + type: str + ironic_url: + description: + - If noauth mode is utilized, this is required to be set to the endpoint URL for the Ironic API. + Use with "auth" and "auth_type" settings set to None. + type: str + timeout: + description: + - A timeout in seconds to tell the role to wait for the node to complete introspection if wait is set to True. + default: 1200 + type: int + +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +ansible_facts: + description: Dictionary of new facts representing discovered properties of the node.. + returned: changed + type: complex + contains: + memory_mb: + description: Amount of node memory as updated in the node properties + type: str + sample: "1024" + cpu_arch: + description: Detected CPU architecture type + type: str + sample: "x86_64" + local_gb: + description: Total size of local disk storage as updated in node properties. + type: str + sample: "10" + cpus: + description: Count of cpu cores defined in the updated node properties. + type: str + sample: "1" +''' + +EXAMPLES = ''' +# Invoke node inspection +- openstack.cloud.baremetal_inspect: + name: "testnode1" +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.ironic import ( + IronicModule, + ironic_argument_spec, +) +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_module_kwargs, + openstack_cloud_from_module +) + + +def _choose_id_value(module): + if module.params['uuid']: + return module.params['uuid'] + if module.params['name']: + return module.params['name'] + return None + + +def main(): + argument_spec = ironic_argument_spec( + uuid=dict(required=False), + name=dict(required=False), + mac=dict(required=False), + timeout=dict(default=1200, type='int', required=False), + ) + module_kwargs = openstack_module_kwargs() + module = IronicModule(argument_spec, **module_kwargs) + + sdk, cloud = openstack_cloud_from_module(module) + try: + if module.params['name'] or module.params['uuid']: + server = cloud.get_machine(_choose_id_value(module)) + elif module.params['mac']: + server = cloud.get_machine_by_mac(module.params['mac']) + else: + module.fail_json(msg="The worlds did not align, " + "the host was not found as " + "no name, uuid, or mac was " + "defined.") + if server: + cloud.inspect_machine(server['uuid'], module.params['wait']) + # TODO(TheJulia): diff properties, ?and ports? and determine + # if a change occurred. In theory, the node is always changed + # if introspection is able to update the record. + module.exit_json(changed=True, + ansible_facts=server['properties']) + + else: + module.fail_json(msg="node not found.") + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_ironic_node.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_ironic_node.py new file mode 100644 index 00000000..267e4308 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_ironic_node.py @@ -0,0 +1,362 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (c) 2015, Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: baremetal_node_action +short_description: Activate/Deactivate Bare Metal Resources from OpenStack +author: OpenStack Ansible SIG +description: + - Deploy to nodes controlled by Ironic. +options: + name: + description: + - Name of the node to create. + type: str + state: + description: + - Indicates desired state of the resource. + - I(state) can be C('present'), C('absent'), C('maintenance') or C('off'). + default: present + type: str + deploy: + description: + - Indicates if the resource should be deployed. Allows for deployment + logic to be disengaged and control of the node power or maintenance + state to be changed. + type: str + default: 'yes' + uuid: + description: + - globally unique identifier (UUID) to be given to the resource. + type: str + ironic_url: + description: + - If noauth mode is utilized, this is required to be set to the + endpoint URL for the Ironic API. Use with "auth" and "auth_type" + settings set to None. + type: str + config_drive: + description: + - A configdrive file or HTTP(S) URL that will be passed along to the + node. + type: raw + instance_info: + description: + - Definition of the instance information which is used to deploy + the node. This information is only required when an instance is + set to present. + type: dict + suboptions: + image_source: + description: + - An HTTP(S) URL where the image can be retrieved from. + image_checksum: + description: + - The checksum of image_source. + image_disk_format: + description: + - The type of image that has been requested to be deployed. + power: + description: + - A setting to allow power state to be asserted allowing nodes + that are not yet deployed to be powered on, and nodes that + are deployed to be powered off. + - I(power) can be C('present'), C('absent'), C('maintenance') or C('off'). + default: present + type: str + maintenance: + description: + - A setting to allow the direct control if a node is in + maintenance mode. + - I(maintenance) can be C('yes'), C('no'), C('True'), or C('False'). + type: str + maintenance_reason: + description: + - A string expression regarding the reason a node is in a + maintenance mode. + type: str + wait: + description: + - A boolean value instructing the module to wait for node + activation or deactivation to complete before returning. + type: bool + default: 'no' + timeout: + description: + - An integer value representing the number of seconds to + wait for the node activation or deactivation to complete. + default: 1800 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Activate a node by booting an image with a configdrive attached +- openstack.cloud.baremetal_node_action: + cloud: "openstack" + uuid: "d44666e1-35b3-4f6b-acb0-88ab7052da69" + state: present + power: present + deploy: True + maintenance: False + config_drive: "http://192.168.1.1/host-configdrive.iso" + instance_info: + image_source: "http://192.168.1.1/deploy_image.img" + image_checksum: "356a6b55ecc511a20c33c946c4e678af" + image_disk_format: "qcow" + delegate_to: localhost + +# Activate a node by booting an image with a configdrive json object +- openstack.cloud.baremetal_node_action: + uuid: "d44666e1-35b3-4f6b-acb0-88ab7052da69" + auth_type: None + ironic_url: "http://192.168.1.1:6385/" + config_drive: + meta_data: + hostname: node1 + public_keys: + default: ssh-rsa AAA...BBB== + instance_info: + image_source: "http://192.168.1.1/deploy_image.img" + image_checksum: "356a6b55ecc511a20c33c946c4e678af" + image_disk_format: "qcow" + delegate_to: localhost +''' + + +from ansible_collections.openstack.cloud.plugins.module_utils.ironic import ( + IronicModule, + ironic_argument_spec, +) +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_module_kwargs, + openstack_cloud_from_module +) + + +def _choose_id_value(module): + if module.params['uuid']: + return module.params['uuid'] + if module.params['name']: + return module.params['name'] + return None + + +def _is_true(value): + true_values = [True, 'yes', 'Yes', 'True', 'true', 'present', 'on'] + if value in true_values: + return True + return False + + +def _is_false(value): + false_values = [False, None, 'no', 'No', 'False', 'false', 'absent', 'off'] + if value in false_values: + return True + return False + + +def _check_set_maintenance(module, cloud, node): + if _is_true(module.params['maintenance']): + if _is_false(node['maintenance']): + cloud.set_machine_maintenance_state( + node['uuid'], + True, + reason=module.params['maintenance_reason']) + module.exit_json(changed=True, msg="Node has been set into " + "maintenance mode") + else: + # User has requested maintenance state, node is already in the + # desired state, checking to see if the reason has changed. + if (str(node['maintenance_reason']) not in + str(module.params['maintenance_reason'])): + cloud.set_machine_maintenance_state( + node['uuid'], + True, + reason=module.params['maintenance_reason']) + module.exit_json(changed=True, msg="Node maintenance reason " + "updated, cannot take any " + "additional action.") + elif _is_false(module.params['maintenance']): + if node['maintenance'] is True: + cloud.remove_machine_from_maintenance(node['uuid']) + return True + else: + module.fail_json(msg="maintenance parameter was set but a valid " + "the value was not recognized.") + return False + + +def _check_set_power_state(module, cloud, node): + if 'power on' in str(node['power_state']): + if _is_false(module.params['power']): + # User has requested the node be powered off. + cloud.set_machine_power_off(node['uuid']) + module.exit_json(changed=True, msg="Power requested off") + if 'power off' in str(node['power_state']): + if ( + _is_false(module.params['power']) + and _is_false(module.params['state']) + ): + return False + if ( + _is_false(module.params['power']) + and _is_false(module.params['state']) + ): + module.exit_json( + changed=False, + msg="Power for node is %s, node must be reactivated " + "OR set to state absent" + ) + # In the event the power has been toggled on and + # deployment has been requested, we need to skip this + # step. + if ( + _is_true(module.params['power']) + and _is_false(module.params['deploy']) + ): + # Node is powered down when it is not awaiting to be provisioned + cloud.set_machine_power_on(node['uuid']) + return True + # Default False if no action has been taken. + return False + + +def main(): + argument_spec = ironic_argument_spec( + uuid=dict(required=False), + name=dict(required=False), + instance_info=dict(type='dict', required=False), + config_drive=dict(type='raw', required=False), + state=dict(required=False, default='present'), + maintenance=dict(required=False), + maintenance_reason=dict(required=False), + power=dict(required=False, default='present'), + deploy=dict(required=False, default='yes'), + wait=dict(type='bool', required=False, default=False), + timeout=dict(required=False, type='int', default=1800), + ) + module_kwargs = openstack_module_kwargs() + module = IronicModule(argument_spec, **module_kwargs) + + if ( + module.params['config_drive'] + and not isinstance(module.params['config_drive'], (str, dict)) + ): + config_drive_type = type(module.params['config_drive']) + msg = ('argument config_drive is of type %s and we expected' + ' str or dict') % config_drive_type + module.fail_json(msg=msg) + + node_id = _choose_id_value(module) + + if not node_id: + module.fail_json(msg="A uuid or name value must be defined " + "to use this module.") + sdk, cloud = openstack_cloud_from_module(module) + try: + node = cloud.get_machine(node_id) + + if node is None: + module.fail_json(msg="node not found") + + uuid = node['uuid'] + instance_info = module.params['instance_info'] + changed = False + wait = module.params['wait'] + timeout = module.params['timeout'] + + # User has requested desired state to be in maintenance state. + if module.params['state'] == 'maintenance': + module.params['maintenance'] = True + + if node['provision_state'] in [ + 'cleaning', + 'deleting', + 'wait call-back']: + module.fail_json(msg="Node is in %s state, cannot act upon the " + "request as the node is in a transition " + "state" % node['provision_state']) + # TODO(TheJulia) This is in-development code, that requires + # code in the shade library that is still in development. + if _check_set_maintenance(module, cloud, node): + if node['provision_state'] in 'active': + module.exit_json(changed=True, + result="Maintenance state changed") + changed = True + node = cloud.get_machine(node_id) + + if _check_set_power_state(module, cloud, node): + changed = True + node = cloud.get_machine(node_id) + + if _is_true(module.params['state']): + if _is_false(module.params['deploy']): + module.exit_json( + changed=changed, + result="User request has explicitly disabled " + "deployment logic" + ) + + if 'active' in node['provision_state']: + module.exit_json( + changed=changed, + result="Node already in an active state." + ) + + if instance_info is None: + module.fail_json( + changed=changed, + msg="When setting an instance to present, " + "instance_info is a required variable.") + + # TODO(TheJulia): Update instance info, however info is + # deployment specific. Perhaps consider adding rebuild + # support, although there is a known desire to remove + # rebuild support from Ironic at some point in the future. + cloud.update_machine(uuid, instance_info=instance_info) + cloud.validate_node(uuid) + if not wait: + cloud.activate_node(uuid, module.params['config_drive']) + else: + cloud.activate_node( + uuid, + configdrive=module.params['config_drive'], + wait=wait, + timeout=timeout) + # TODO(TheJulia): Add more error checking.. + module.exit_json(changed=changed, result="node activated") + + elif _is_false(module.params['state']): + if node['provision_state'] not in "deleted": + cloud.update_machine(uuid, instance_info={}) + if not wait: + cloud.deactivate_node(uuid) + else: + cloud.deactivate_node( + uuid, + wait=wait, + timeout=timeout) + + module.exit_json(changed=True, result="deleted") + else: + module.exit_json(changed=False, result="node not found") + else: + module.fail_json(msg="State must be present, absent, " + "maintenance, off") + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keypair.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keypair.py new file mode 100644 index 00000000..df6bb5d1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keypair.py @@ -0,0 +1,156 @@ +#!/usr/bin/python + +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# Copyright (c) 2013, John Dewey <john@dewey.ws> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: keypair +short_description: Add/Delete a keypair from OpenStack +author: OpenStack Ansible SIG +description: + - Add or Remove key pair from OpenStack +options: + name: + description: + - Name that has to be given to the key pair + required: true + type: str + public_key: + description: + - The public key that would be uploaded to nova and injected into VMs + upon creation. + type: str + public_key_file: + description: + - Path to local file containing ssh public key. Mutually exclusive + with public_key. + type: str + state: + description: + - Should the resource be present or absent. If state is replace and + the key exists but has different content, delete it and recreate it + with the new content. + choices: [present, absent, replace] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Creates a key pair with the running users public key +- openstack.cloud.keypair: + cloud: mordred + state: present + name: ansible_key + public_key_file: /home/me/.ssh/id_rsa.pub + +# Creates a new key pair and the private key returned after the run. +- openstack.cloud.keypair: + cloud: rax-dfw + state: present + name: ansible_key +''' + +RETURN = ''' +id: + description: Unique UUID. + returned: success + type: str +name: + description: Name given to the keypair. + returned: success + type: str +public_key: + description: The public key value for the keypair. + returned: success + type: str +private_key: + description: The private key value for the keypair. + returned: Only when a keypair is generated for the user (e.g., when creating one + and a public key is not specified). + type: str +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + OpenStackModule) + + +class KeyPairModule(OpenStackModule): + deprecated_names = ('os_keypair', 'openstack.cloud.os_keypair') + + argument_spec = dict( + name=dict(required=True), + public_key=dict(default=None), + public_key_file=dict(default=None), + state=dict(default='present', + choices=['absent', 'present', 'replace']), + ) + + module_kwargs = dict( + mutually_exclusive=[['public_key', 'public_key_file']]) + + def _system_state_change(self, keypair): + state = self.params['state'] + if state == 'present' and not keypair: + return True + if state == 'absent' and keypair: + return True + return False + + def run(self): + + state = self.params['state'] + name = self.params['name'] + public_key = self.params['public_key'] + + if self.params['public_key_file']: + with open(self.params['public_key_file']) as public_key_fh: + public_key = public_key_fh.read().rstrip() + + keypair = self.conn.get_keypair(name) + + if self.ansible.check_mode: + self.exit_json(changed=self._system_state_change(keypair)) + + if state in ('present', 'replace'): + if keypair and keypair['name'] == name: + if public_key and (public_key != keypair['public_key']): + if state == 'present': + self.fail_json( + msg="Key name %s present but key hash not the same" + " as offered. Delete key first." % name + ) + else: + self.conn.delete_keypair(name) + keypair = self.conn.create_keypair(name, public_key) + changed = True + else: + changed = False + else: + keypair = self.conn.create_keypair(name, public_key) + changed = True + + self.exit_json(changed=changed, key=keypair, id=keypair['id']) + + elif state == 'absent': + if keypair: + self.conn.delete_keypair(name) + self.exit_json(changed=True) + self.exit_json(changed=False) + + +def main(): + module = KeyPairModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_domain.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_domain.py new file mode 100644 index 00000000..6c4ef600 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_domain.py @@ -0,0 +1,177 @@ +#!/usr/bin/python +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_domain +short_description: Manage OpenStack Identity Domains +author: OpenStack Ansible SIG +description: + - Create, update, or delete OpenStack Identity domains. If a domain + with the supplied name already exists, it will be updated with the + new description and enabled attributes. +options: + name: + description: + - Name that has to be given to the instance + required: true + type: str + description: + description: + - Description of the domain + type: str + enabled: + description: + - Is the domain enabled + type: bool + default: 'yes' + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a domain +- openstack.cloud.identity_domain: + cloud: mycloud + state: present + name: demo + description: Demo Domain + +# Delete a domain +- openstack.cloud.identity_domain: + cloud: mycloud + state: absent + name: demo +''' + +RETURN = ''' +domain: + description: Dictionary describing the domain. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Domain ID. + type: str + sample: "474acfe5-be34-494c-b339-50f06aa143e4" + name: + description: Domain name. + type: str + sample: "demo" + description: + description: Domain description. + type: str + sample: "Demo Domain" + enabled: + description: Domain description. + type: bool + sample: True + +id: + description: The domain ID. + returned: On success when I(state) is 'present' + type: str + sample: "474acfe5-be34-494c-b339-50f06aa143e4" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _needs_update(module, domain): + if module.params['description'] is not None and \ + domain.description != module.params['description']: + return True + if domain.enabled != module.params['enabled']: + return True + return False + + +def _system_state_change(module, domain): + state = module.params['state'] + if state == 'absent' and domain: + return True + + if state == 'present': + if domain is None: + return True + return _needs_update(module, domain) + + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + description=dict(default=None), + enabled=dict(default=True, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + name = module.params['name'] + description = module.params['description'] + enabled = module.params['enabled'] + state = module.params['state'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + + domains = cloud.search_domains(filters=dict(name=name)) + + if len(domains) > 1: + module.fail_json(msg='Domain name %s is not unique' % name) + elif len(domains) == 1: + domain = domains[0] + else: + domain = None + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, domain)) + + if state == 'present': + if domain is None: + domain = cloud.create_domain( + name=name, description=description, enabled=enabled) + changed = True + else: + if _needs_update(module, domain): + domain = cloud.update_domain( + domain.id, name=name, description=description, + enabled=enabled) + changed = True + else: + changed = False + module.exit_json(changed=changed, domain=domain, id=domain.id) + + elif state == 'absent': + if domain is None: + changed = False + else: + cloud.delete_domain(domain.id) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_domain_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_domain_info.py new file mode 100644 index 00000000..05f42127 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_domain_info.py @@ -0,0 +1,133 @@ +#!/usr/bin/python +# Copyright (c) 2016 Hewlett-Packard Enterprise Corporation +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_domain_info +short_description: Retrieve information about one or more OpenStack domains +author: OpenStack Ansible SIG +description: + - Retrieve information about a one or more OpenStack domains + - This module was called C(openstack.cloud.identity_domain_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.identity_domain_info) module no longer returns C(ansible_facts)! +options: + name: + description: + - Name or ID of the domain + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather information about previously created domain +- openstack.cloud.identity_domain_info: + cloud: awesomecloud + register: result +- debug: + msg: "{{ result.openstack_domains }}" + +# Gather information about a previously created domain by name +- openstack.cloud.identity_domain_info: + cloud: awesomecloud + name: demodomain + register: result +- debug: + msg: "{{ result.openstack_domains }}" + +# Gather information about a previously created domain with filter +- openstack.cloud.identity_domain_info: + cloud: awesomecloud + name: demodomain + filters: + enabled: false + register: result +- debug: + msg: "{{ result.openstack_domains }}" +''' + + +RETURN = ''' +openstack_domains: + description: has all the OpenStack information about domains + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the domain. + returned: success + type: str + description: + description: Description of the domain. + returned: success + type: str + enabled: + description: Flag to indicate if the domain is enabled. + returned: success + type: bool +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + filters=dict(required=False, type='dict', default=None), + ) + module_kwargs = openstack_module_kwargs( + mutually_exclusive=[ + ['name', 'filters'], + ] + ) + module = AnsibleModule(argument_spec, **module_kwargs) + is_old_facts = module._name == 'openstack.cloud.identity_domain_facts' + if is_old_facts: + module.deprecate("The 'openstack.cloud.identity_domain_facts' module has been renamed to 'openstack.cloud.identity_domain_info', " + "and the renamed one no longer returns ansible_facts", version='2.13') + + sdk, opcloud = openstack_cloud_from_module(module) + try: + name = module.params['name'] + filters = module.params['filters'] + + if name: + # Let's suppose user is passing domain ID + try: + domains = opcloud.get_domain(name) + except Exception: + domains = opcloud.search_domains(filters={'name': name}) + + else: + domains = opcloud.search_domains(filters) + + if is_old_facts: + module.exit_json(changed=False, ansible_facts=dict( + openstack_domains=domains)) + else: + module.exit_json(changed=False, openstack_domains=domains) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_endpoint.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_endpoint.py new file mode 100644 index 00000000..a570dc76 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_endpoint.py @@ -0,0 +1,211 @@ +#!/usr/bin/python + +# Copyright: (c) 2017, VEXXHOST, Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: endpoint +short_description: Manage OpenStack Identity service endpoints +author: OpenStack Ansible SIG +description: + - Create, update, or delete OpenStack Identity service endpoints. If a + service with the same combination of I(service), I(interface) and I(region) + exist, the I(url) and I(state) (C(present) or C(absent)) will be updated. +options: + service: + description: + - Name or id of the service. + required: true + type: str + endpoint_interface: + description: + - Interface of the service. + choices: [admin, public, internal] + required: true + type: str + url: + description: + - URL of the service. + required: true + type: str + region: + description: + - Region that the service belongs to. Note that I(region_name) is used for authentication. + type: str + enabled: + description: + - Is the service enabled. + default: True + type: bool + state: + description: + - Should the resource be C(present) or C(absent). + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.13.0" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Create a service for glance + openstack.cloud.endpoint: + cloud: mycloud + service: glance + endpoint_interface: public + url: http://controller:9292 + region: RegionOne + state: present + +- name: Delete a service for nova + openstack.cloud.endpoint: + cloud: mycloud + service: nova + endpoint_interface: public + region: RegionOne + state: absent +''' + +RETURN = ''' +endpoint: + description: Dictionary describing the endpoint. + returned: On success when I(state) is C(present) + type: complex + contains: + id: + description: Endpoint ID. + type: str + sample: 3292f020780b4d5baf27ff7e1d224c44 + region: + description: Region Name. + type: str + sample: RegionOne + service_id: + description: Service ID. + type: str + sample: b91f1318f735494a825a55388ee118f3 + interface: + description: Endpoint Interface. + type: str + sample: public + url: + description: Service URL. + type: str + sample: http://controller:9292 + enabled: + description: Service status. + type: bool + sample: True +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _needs_update(module, endpoint): + if endpoint.enabled != module.params['enabled']: + return True + if endpoint.url != module.params['url']: + return True + return False + + +def _system_state_change(module, endpoint): + state = module.params['state'] + if state == 'absent' and endpoint: + return True + + if state == 'present': + if endpoint is None: + return True + return _needs_update(module, endpoint) + + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + service=dict(type='str', required=True), + endpoint_interface=dict(type='str', required=True, choices=['admin', 'public', 'internal']), + url=dict(type='str', required=True), + region=dict(type='str'), + enabled=dict(type='bool', default=True), + state=dict(type='str', default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + service_name_or_id = module.params['service'] + interface = module.params['endpoint_interface'] + url = module.params['url'] + region = module.params['region'] + enabled = module.params['enabled'] + state = module.params['state'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + + service = cloud.get_service(service_name_or_id) + if service is None and state == 'absent': + module.exit_json(changed=False) + + elif service is None and state == 'present': + module.fail_json(msg='Service %s does not exist' % service_name_or_id) + + filters = dict(service_id=service.id, interface=interface) + if region is not None: + filters['region'] = region + endpoints = cloud.search_endpoints(filters=filters) + + if len(endpoints) > 1: + module.fail_json(msg='Service %s, interface %s and region %s are ' + 'not unique' % + (service_name_or_id, interface, region)) + elif len(endpoints) == 1: + endpoint = endpoints[0] + else: + endpoint = None + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, endpoint)) + + if state == 'present': + if endpoint is None: + result = cloud.create_endpoint(service_name_or_id=service, + url=url, interface=interface, + region=region, enabled=enabled) + endpoint = result[0] + changed = True + else: + if _needs_update(module, endpoint): + endpoint = cloud.update_endpoint( + endpoint.id, url=url, enabled=enabled) + changed = True + else: + changed = False + module.exit_json(changed=changed, endpoint=endpoint) + + elif state == 'absent': + if endpoint is None: + changed = False + else: + cloud.delete_endpoint(endpoint.id) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_federation_protocol.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_federation_protocol.py new file mode 100644 index 00000000..f125d7f7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_federation_protocol.py @@ -0,0 +1,211 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: keystone_federation_protocol +short_description: manage a federation Protocol +author: OpenStack Ansible SIG +description: + - Manage a federation Protocol. +options: + name: + description: + - The name of the Protocol. + type: str + required: true + aliases: ['id'] + state: + description: + - Whether the protocol should be C(present) or C(absent). + choices: ['present', 'absent'] + default: present + type: str + idp_id: + description: + - The name of the Identity Provider this Protocol is associated with. + aliases: ['idp_name'] + required: true + type: str + mapping_id: + description: + - The name of the Mapping to use for this Protocol.' + - Required when creating a new Protocol. + type: str + aliases: ['mapping_name'] +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.44" +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Create a protocol + openstack.cloud.keystone_federation_protocol: + cloud: example_cloud + name: example_protocol + idp_id: example_idp + mapping_id: example_mapping + +- name: Delete a protocol + openstack.cloud.keystone_federation_protocol: + cloud: example_cloud + name: example_protocol + idp_id: example_idp + state: absent +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_module_kwargs +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_cloud_from_module + + +def normalize_protocol(protocol): + """ + Normalizes the protocol definitions so that the outputs are consistent with the + parameters + + - "name" (parameter) == "id" (SDK) + """ + if protocol is None: + return None + + _protocol = protocol.to_dict() + _protocol['name'] = protocol['id'] + # As of 0.44 SDK doesn't copy the URI parameters over, so let's add them + _protocol['idp_id'] = protocol['idp_id'] + return _protocol + + +def delete_protocol(module, sdk, cloud, protocol): + """ + Delete an existing Protocol + + returns: the "Changed" state + """ + + if protocol is None: + return False + + if module.check_mode: + return True + + try: + cloud.identity.delete_federation_protocol(None, protocol) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to delete protocol: {0}'.format(str(ex))) + return True + + +def create_protocol(module, sdk, cloud, name): + """ + Create a new Protocol + + returns: the "Changed" state and the new protocol + """ + + if module.check_mode: + return True, None + + idp_name = module.params.get('idp_id') + mapping_id = module.params.get('mapping_id') + + attributes = { + 'idp_id': idp_name, + 'mapping_id': mapping_id, + } + + try: + protocol = cloud.identity.create_federation_protocol(id=name, **attributes) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to create protocol: {0}'.format(str(ex))) + return (True, protocol) + + +def update_protocol(module, sdk, cloud, protocol): + """ + Update an existing Protocol + + returns: the "Changed" state and the new protocol + """ + + mapping_id = module.params.get('mapping_id') + + attributes = {} + + if (mapping_id is not None) and (mapping_id != protocol.mapping_id): + attributes['mapping_id'] = mapping_id + + if not attributes: + return False, protocol + + if module.check_mode: + return True, None + + try: + new_protocol = cloud.identity.update_federation_protocol(None, protocol, **attributes) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to update protocol: {0}'.format(str(ex))) + return (True, new_protocol) + + +def main(): + """ Module entry point """ + + argument_spec = openstack_full_argument_spec( + name=dict(required=True, aliases=['id']), + state=dict(default='present', choices=['absent', 'present']), + idp_id=dict(required=True, aliases=['idp_name']), + mapping_id=dict(aliases=['mapping_name']), + ) + module_kwargs = openstack_module_kwargs( + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params.get('name') + state = module.params.get('state') + idp = module.params.get('idp_id') + changed = False + + sdk, cloud = openstack_cloud_from_module(module, min_version="0.44") + + try: + protocol = cloud.identity.get_federation_protocol(idp, name) + except sdk.exceptions.ResourceNotFound: + protocol = None + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to get protocol: {0}'.format(str(ex))) + + if state == 'absent': + if protocol is not None: + changed = delete_protocol(module, sdk, cloud, protocol) + module.exit_json(changed=changed) + + # state == 'present' + else: + if protocol is None: + if module.params.get('mapping_id') is None: + module.fail_json(msg='A mapping_id must be passed when creating' + ' a protocol') + (changed, protocol) = create_protocol(module, sdk, cloud, name) + protocol = normalize_protocol(protocol) + module.exit_json(changed=changed, protocol=protocol) + + else: + (changed, new_protocol) = update_protocol(module, sdk, cloud, protocol) + new_protocol = normalize_protocol(new_protocol) + module.exit_json(changed=changed, protocol=new_protocol) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_federation_protocol_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_federation_protocol_info.py new file mode 100644 index 00000000..bb8a3333 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_federation_protocol_info.py @@ -0,0 +1,110 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: keystone_federation_protocol_info +short_description: get information about federation Protocols +author: OpenStack Ansible SIG +description: + - Get information about federation Protocols. +options: + name: + description: + - The name of the Protocol. + type: str + aliases: ['id'] + idp_id: + description: + - The name of the Identity Provider this Protocol is associated with. + aliases: ['idp_name'] + required: true + type: str +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.44" +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Describe a protocol + openstack.cloud.keystone_federation_protocol_info: + cloud: example_cloud + name: example_protocol + idp_id: example_idp + mapping_name: example_mapping + +- name: Describe all protocols attached to an IDP + openstack.cloud.keystone_federation_protocol_info: + cloud: example_cloud + idp_id: example_idp +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_module_kwargs +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_cloud_from_module + + +def normalize_protocol(protocol): + """ + Normalizes the protocol definitions so that the outputs are consistent with the + parameters + + - "name" (parameter) == "id" (SDK) + """ + if protocol is None: + return None + + _protocol = protocol.to_dict() + _protocol['name'] = protocol['id'] + # As of 0.44 SDK doesn't copy the URI parameters over, so let's add them + _protocol['idp_id'] = protocol['idp_id'] + return _protocol + + +def main(): + """ Module entry point """ + + argument_spec = openstack_full_argument_spec( + name=dict(aliases=['id']), + idp_id=dict(required=True, aliases=['idp_name']), + ) + module_kwargs = openstack_module_kwargs( + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params.get('name') + idp = module.params.get('idp_id') + + sdk, cloud = openstack_cloud_from_module(module, min_version="0.44") + + if name: + try: + protocol = cloud.identity.get_federation_protocol(idp, name) + protocol = normalize_protocol(protocol) + except sdk.exceptions.ResourceNotFound: + module.fail_json(msg='Failed to find protocol') + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to get protocol: {0}'.format(str(ex))) + module.exit_json(changed=False, protocols=[protocol]) + + else: + try: + protocols = list(map(normalize_protocol, cloud.identity.federation_protocols(idp))) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to list protocols: {0}'.format(str(ex))) + module.exit_json(changed=False, protocols=protocols) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_identity_provider.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_identity_provider.py new file mode 100644 index 00000000..1a616860 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_identity_provider.py @@ -0,0 +1,242 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: federation_idp +short_description: manage a federation Identity Provider +author: OpenStack Ansible SIG +description: + - Manage a federation Identity Provider. +options: + name: + description: + - The name of the Identity Provider. + type: str + required: true + aliases: ['id'] + state: + description: + - Whether the Identity Provider should be C(present) or C(absent). + choices: ['present', 'absent'] + default: present + type: str + description: + description: + - The description of the Identity Provider. + type: str + domain_id: + description: + - The ID of a domain that is associated with the Identity Provider. + Federated users that authenticate with the Identity Provider will be + created under the domain specified. + - Required when creating a new Identity Provider. + type: str + enabled: + description: + - Whether the Identity Provider is enabled or not. + - Will default to C(true) when creating a new Identity Provider. + type: bool + aliases: ['is_enabled'] + remote_ids: + description: + - "List of the unique Identity Provider's remote IDs." + - Will default to an empty list when creating a new Identity Provider. + type: list + elements: str +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.44" +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Create an identity provider + openstack.cloud.federation_idp: + cloud: example_cloud + name: example_provider + domain_id: 0123456789abcdef0123456789abcdef + description: 'My example IDP' + remote_ids: + - 'https://auth.example.com/auth/realms/ExampleRealm' + +- name: Delete an identity provider + openstack.cloud.federation_idp: + cloud: example_cloud + name: example_provider + state: absent +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_module_kwargs +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_cloud_from_module + + +def normalize_idp(idp): + """ + Normalizes the IDP definitions so that the outputs are consistent with the + parameters + + - "enabled" (parameter) == "is_enabled" (SDK) + - "name" (parameter) == "id" (SDK) + """ + if idp is None: + return None + + _idp = idp.to_dict() + _idp['enabled'] = idp['is_enabled'] + _idp['name'] = idp['id'] + return _idp + + +def delete_identity_provider(module, sdk, cloud, idp): + """ + Delete an existing Identity Provider + + returns: the "Changed" state + """ + + if idp is None: + return False + + if module.check_mode: + return True + + try: + cloud.identity.delete_identity_provider(idp) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to delete identity provider: {0}'.format(str(ex))) + return True + + +def create_identity_provider(module, sdk, cloud, name): + """ + Create a new Identity Provider + + returns: the "Changed" state and the new identity provider + """ + + if module.check_mode: + return True, None + + description = module.params.get('description') + enabled = module.params.get('enabled') + domain_id = module.params.get('domain_id') + remote_ids = module.params.get('remote_ids') + + if enabled is None: + enabled = True + if remote_ids is None: + remote_ids = [] + + attributes = { + 'domain_id': domain_id, + 'enabled': enabled, + 'remote_ids': remote_ids, + } + if description is not None: + attributes['description'] = description + + try: + idp = cloud.identity.create_identity_provider(id=name, **attributes) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to create identity provider: {0}'.format(str(ex))) + return (True, idp) + + +def update_identity_provider(module, sdk, cloud, idp): + """ + Update an existing Identity Provider + + returns: the "Changed" state and the new identity provider + """ + + description = module.params.get('description') + enabled = module.params.get('enabled') + domain_id = module.params.get('domain_id') + remote_ids = module.params.get('remote_ids') + + attributes = {} + + if (description is not None) and (description != idp.description): + attributes['description'] = description + if (enabled is not None) and (enabled != idp.is_enabled): + attributes['enabled'] = enabled + if (domain_id is not None) and (domain_id != idp.domain_id): + attributes['domain_id'] = domain_id + if (remote_ids is not None) and (remote_ids != idp.remote_ids): + attributes['remote_ids'] = remote_ids + + if not attributes: + return False, idp + + if module.check_mode: + return True, None + + try: + new_idp = cloud.identity.update_identity_provider(idp, **attributes) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to update identity provider: {0}'.format(str(ex))) + return (True, new_idp) + + +def main(): + """ Module entry point """ + + argument_spec = openstack_full_argument_spec( + name=dict(required=True, aliases=['id']), + state=dict(default='present', choices=['absent', 'present']), + description=dict(), + domain_id=dict(), + enabled=dict(type='bool', aliases=['is_enabled']), + remote_ids=dict(type='list', elements='str'), + ) + module_kwargs = openstack_module_kwargs( + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params.get('name') + state = module.params.get('state') + changed = False + + sdk, cloud = openstack_cloud_from_module(module, min_version="0.44") + + try: + idp = cloud.identity.get_identity_provider(name) + except sdk.exceptions.ResourceNotFound: + idp = None + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to get identity provider: {0}'.format(str(ex))) + + if state == 'absent': + if idp is not None: + changed = delete_identity_provider(module, sdk, cloud, idp) + module.exit_json(changed=changed) + + # state == 'present' + else: + if idp is None: + if module.params.get('domain_id') is None: + module.fail_json(msg='A domain_id must be passed when creating' + ' an identity provider') + (changed, idp) = create_identity_provider(module, sdk, cloud, name) + idp = normalize_idp(idp) + module.exit_json(changed=changed, identity_provider=idp) + + (changed, new_idp) = update_identity_provider(module, sdk, cloud, idp) + new_idp = normalize_idp(new_idp) + module.exit_json(changed=changed, identity_provider=new_idp) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_identity_provider_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_identity_provider_info.py new file mode 100644 index 00000000..ced8a7a6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_identity_provider_info.py @@ -0,0 +1,101 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: federation_idp_info +short_description: Get the information about the available federation identity + providers +author: OpenStack Ansible SIG +description: + - Fetch a federation identity provider. +options: + name: + description: + - The name of the identity provider to fetch. + - If I(name) is specified, the module will return failed if the identity + provider doesn't exist. + type: str + aliases: ['id'] +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.44" +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Fetch a specific identity provider + openstack.cloud.federation_idp_info: + cloud: example_cloud + name: example_provider + +- name: Fetch all providers + openstack.cloud.federation_idp_info: + cloud: example_cloud +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_module_kwargs +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_cloud_from_module + + +def normalize_idp(idp): + """ + Normalizes the IDP definitions so that the outputs are consistent with the + parameters + + - "enabled" (parameter) == "is_enabled" (SDK) + - "name" (parameter) == "id" (SDK) + """ + if idp is None: + return + + _idp = idp.to_dict() + _idp['enabled'] = idp['is_enabled'] + _idp['name'] = idp['id'] + return _idp + + +def main(): + """ Module entry point """ + + argument_spec = openstack_full_argument_spec( + name=dict(aliases=['id']), + ) + module_kwargs = openstack_module_kwargs( + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params.get('name') + + sdk, cloud = openstack_cloud_from_module(module, min_version="0.44") + + if name: + try: + idp = normalize_idp(cloud.identity.get_identity_provider(name)) + except sdk.exceptions.ResourceNotFound: + module.fail_json(msg='Failed to find identity provider') + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to get identity provider: {0}'.format(str(ex))) + module.exit_json(changed=False, identity_providers=[idp]) + + else: + try: + providers = list(map(normalize_idp, cloud.identity.identity_providers())) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to list identity providers: {0}'.format(str(ex))) + module.exit_json(changed=False, identity_providers=providers) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_mapping.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_mapping.py new file mode 100644 index 00000000..ad1461ab --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_mapping.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: federation_mapping +short_description: Manage a federation mapping +author: OpenStack Ansible SIG +description: + - Manage a federation mapping. +options: + name: + description: + - The name of the mapping to manage. + required: true + type: str + aliases: ['id'] + state: + description: + - Whether the mapping should be C(present) or C(absent). + choices: ['present', 'absent'] + default: present + type: str + rules: + description: + - The rules that comprise the mapping. These are pairs of I(local) and + I(remote) definitions. For more details on how these work please see + the OpenStack documentation + U(https://docs.openstack.org/keystone/latest/admin/federation/mapping_combinations.html). + - Required if I(state=present) + type: list + elements: dict + suboptions: + local: + description: + - Information on what local attributes will be mapped. + required: true + type: list + elements: dict + remote: + description: + - Information on what remote attributes will be mapped. + required: true + type: list + elements: dict +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.44" +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Create a new mapping + openstack.cloud.federation_mapping: + cloud: example_cloud + name: example_mapping + rules: + - local: + - user: + name: '{0}' + - group: + id: '0cd5e9' + remote: + - type: UserName + - type: orgPersonType + any_one_of: + - Contractor + - SubContractor + +- name: Delete a mapping + openstack.cloud.federation_mapping: + name: example_mapping + state: absent +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_module_kwargs +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_cloud_from_module + + +def normalize_mapping(mapping): + """ + Normalizes the mapping definitions so that the outputs are consistent with + the parameters + + - "name" (parameter) == "id" (SDK) + """ + if mapping is None: + return None + + _mapping = mapping.to_dict() + _mapping['name'] = mapping['id'] + return _mapping + + +def create_mapping(module, sdk, cloud, name): + """ + Attempt to create a Mapping + + returns: A tuple containing the "Changed" state and the created mapping + """ + + if module.check_mode: + return (True, None) + + rules = module.params.get('rules') + + try: + mapping = cloud.identity.create_mapping(id=name, rules=rules) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to create mapping: {0}'.format(str(ex))) + return (True, mapping) + + +def delete_mapping(module, sdk, cloud, mapping): + """ + Attempt to delete a Mapping + + returns: the "Changed" state + """ + if mapping is None: + return False + + if module.check_mode: + return True + + try: + cloud.identity.delete_mapping(mapping) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to delete mapping: {0}'.format(str(ex))) + return True + + +def update_mapping(module, sdk, cloud, mapping): + """ + Attempt to delete a Mapping + + returns: The "Changed" state and the the new mapping + """ + + current_rules = mapping.rules + new_rules = module.params.get('rules') + + # Nothing to do + if current_rules == new_rules: + return (False, mapping) + + if module.check_mode: + return (True, None) + + try: + new_mapping = cloud.identity.update_mapping(mapping, rules=new_rules) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to update mapping: {0}'.format(str(ex))) + return (True, new_mapping) + + +def main(): + """ Module entry point """ + + argument_spec = openstack_full_argument_spec( + name=dict(required=True, aliases=['id']), + state=dict(default='present', choices=['absent', 'present']), + rules=dict(type='list', elements='dict', options=dict( + local=dict(required=True, type='list', elements='dict'), + remote=dict(required=True, type='list', elements='dict') + )), + ) + module_kwargs = openstack_module_kwargs( + required_if=[('state', 'present', ['rules'])] + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params.get('name') + state = module.params.get('state') + changed = False + + sdk, cloud = openstack_cloud_from_module(module, min_version="0.44") + + try: + mapping = cloud.identity.get_mapping(name) + except sdk.exceptions.ResourceNotFound: + mapping = None + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to fetch mapping: {0}'.format(str(ex))) + + if state == 'absent': + if mapping is not None: + changed = delete_mapping(module, sdk, cloud, mapping) + module.exit_json(changed=changed) + + # state == 'present' + else: + if len(module.params.get('rules')) < 1: + module.fail_json(msg='At least one rule must be passed') + + if mapping is None: + (changed, mapping) = create_mapping(module, sdk, cloud, name) + mapping = normalize_mapping(mapping) + module.exit_json(changed=changed, mapping=mapping) + else: + (changed, new_mapping) = update_mapping(module, sdk, cloud, mapping) + new_mapping = normalize_mapping(new_mapping) + module.exit_json(mapping=new_mapping, changed=changed) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_mapping_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_mapping_info.py new file mode 100644 index 00000000..c98f273b --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_mapping_info.py @@ -0,0 +1,98 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: federation_mapping_info +short_description: Get the information about the available federation mappings +author: OpenStack Ansible SIG +description: + - Fetch a federation mapping. +options: + name: + description: + - The name of the mapping to fetch. + - If I(name) is specified, the module will return failed if the mapping + doesn't exist. + type: str + aliases: ['id'] +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.44" +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Fetch a specific mapping + openstack.cloud.federation_mapping_info: + cloud: example_cloud + name: example_mapping + +- name: Fetch all mappings + openstack.cloud.federation_mapping_info: + cloud: example_cloud +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_module_kwargs +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_cloud_from_module + + +def normalize_mapping(mapping): + """ + Normalizes the mapping definitions so that the outputs are consistent with the + parameters + + - "name" (parameter) == "id" (SDK) + """ + if mapping is None: + return None + + _mapping = mapping.to_dict() + _mapping['name'] = mapping['id'] + return _mapping + + +def main(): + """ Module entry point """ + + argument_spec = openstack_full_argument_spec( + name=dict(aliases=['id']), + ) + module_kwargs = openstack_module_kwargs( + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params.get('name') + + sdk, cloud = openstack_cloud_from_module(module, min_version="0.44") + + if name: + try: + mapping = normalize_mapping(cloud.identity.get_mapping(name)) + except sdk.exceptions.ResourceNotFound: + module.fail_json(msg='Failed to find mapping') + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to get mapping: {0}'.format(str(ex))) + module.exit_json(changed=False, mappings=[mapping]) + + else: + try: + mappings = list(map(normalize_mapping, cloud.identity.mappings())) + except sdk.exceptions.OpenStackCloudException as ex: + module.fail_json(msg='Failed to list mappings: {0}'.format(str(ex))) + module.exit_json(changed=False, mappings=mappings) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_role.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_role.py new file mode 100644 index 00000000..3d850637 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_role.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# Copyright (c) 2016 IBM +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_role +short_description: Manage OpenStack Identity Roles +author: OpenStack Ansible SIG +description: + - Manage OpenStack Identity Roles. +options: + name: + description: + - Role Name + required: true + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a role named "demo" +- openstack.cloud.identity_role: + cloud: mycloud + state: present + name: demo + +# Delete the role named "demo" +- openstack.cloud.identity_role: + cloud: mycloud + state: absent + name: demo +''' + +RETURN = ''' +role: + description: Dictionary describing the role. + returned: On success when I(state) is 'present'. + type: complex + contains: + id: + description: Unique role ID. + type: str + sample: "677bfab34c844a01b88a217aa12ec4c2" + name: + description: Role name. + type: str + sample: "demo" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(state, role): + if state == 'present' and not role: + return True + if state == 'absent' and role: + return True + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + name = module.params.get('name') + state = module.params.get('state') + + sdk, cloud = openstack_cloud_from_module(module) + try: + role = cloud.get_role(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(state, role)) + + if state == 'present': + if role is None: + role = cloud.create_role(name) + changed = True + else: + changed = False + module.exit_json(changed=changed, role=role) + elif state == 'absent': + if role is None: + changed = False + else: + cloud.delete_role(name) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_service.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_service.py new file mode 100644 index 00000000..ca3903f9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_keystone_service.py @@ -0,0 +1,189 @@ +#!/usr/bin/python +# Copyright 2016 Sam Yaple +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: catalog_service +short_description: Manage OpenStack Identity services +author: OpenStack Ansible SIG +description: + - Create, update, or delete OpenStack Identity service. If a service + with the supplied name already exists, it will be updated with the + new description and enabled attributes. +options: + name: + description: + - Name of the service + required: true + type: str + description: + description: + - Description of the service + type: str + enabled: + description: + - Is the service enabled + type: bool + default: 'yes' + service_type: + description: + - The type of service + required: true + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a service for glance +- openstack.cloud.catalog_service: + cloud: mycloud + state: present + name: glance + service_type: image + description: OpenStack Image Service +# Delete a service +- openstack.cloud.catalog_service: + cloud: mycloud + state: absent + name: glance + service_type: image +''' + +RETURN = ''' +service: + description: Dictionary describing the service. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Service ID. + type: str + sample: "3292f020780b4d5baf27ff7e1d224c44" + name: + description: Service name. + type: str + sample: "glance" + service_type: + description: Service type. + type: str + sample: "image" + description: + description: Service description. + type: str + sample: "OpenStack Image Service" + enabled: + description: Service status. + type: bool + sample: True +id: + description: The service ID. + returned: On success when I(state) is 'present' + type: str + sample: "3292f020780b4d5baf27ff7e1d224c44" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _needs_update(module, service): + if service.enabled != module.params['enabled']: + return True + if service.description is not None and \ + service.description != module.params['description']: + return True + return False + + +def _system_state_change(module, service): + state = module.params['state'] + if state == 'absent' and service: + return True + + if state == 'present': + if service is None: + return True + return _needs_update(module, service) + + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + description=dict(default=None), + enabled=dict(default=True, type='bool'), + name=dict(required=True), + service_type=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + description = module.params['description'] + enabled = module.params['enabled'] + name = module.params['name'] + state = module.params['state'] + service_type = module.params['service_type'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + services = cloud.search_services(name_or_id=name, + filters=dict(type=service_type)) + + if len(services) > 1: + module.fail_json(msg='Service name %s and type %s are not unique' % + (name, service_type)) + elif len(services) == 1: + service = services[0] + else: + service = None + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, service)) + + if state == 'present': + if service is None: + service = cloud.create_service(name=name, description=description, + type=service_type, enabled=True) + changed = True + else: + if _needs_update(module, service): + service = cloud.update_service( + service.id, name=name, type=service_type, enabled=enabled, + description=description) + changed = True + else: + changed = False + module.exit_json(changed=changed, service=service, id=service.id) + + elif state == 'absent': + if service is None: + changed = False + else: + cloud.delete_service(service.id) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_listener.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_listener.py new file mode 100644 index 00000000..4ea4cf80 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_listener.py @@ -0,0 +1,255 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Catalyst Cloud Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: lb_listener +short_description: Add/Delete a listener for a load balancer from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove a listener for a load balancer from the OpenStack load-balancer service. +options: + name: + description: + - Name that has to be given to the listener + required: true + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + loadbalancer: + description: + - The name or id of the load balancer that this listener belongs to. + required: true + type: str + protocol: + description: + - The protocol for the listener. + choices: [HTTP, HTTPS, TCP, TERMINATED_HTTPS] + default: HTTP + type: str + protocol_port: + description: + - The protocol port number for the listener. + default: 80 + type: int + wait: + description: + - If the module should wait for the load balancer to be ACTIVE. + type: bool + default: 'yes' + timeout: + description: + - The amount of time the module should wait for the load balancer to get + into ACTIVE state. + default: 180 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +id: + description: The listener UUID. + returned: On success when I(state) is 'present' + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +listener: + description: Dictionary describing the listener. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Unique UUID. + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" + name: + description: Name given to the listener. + type: str + sample: "test" + description: + description: The listener description. + type: str + sample: "description" + load_balancer_id: + description: The load balancer UUID this listener belongs to. + type: str + sample: "b32eef7e-d2a6-4ea4-a301-60a873f89b3b" + loadbalancers: + description: A list of load balancer IDs.. + type: list + sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}] + provisioning_status: + description: The provisioning status of the listener. + type: str + sample: "ACTIVE" + operating_status: + description: The operating status of the listener. + type: str + sample: "ONLINE" + is_admin_state_up: + description: The administrative state of the listener. + type: bool + sample: true + protocol: + description: The protocol for the listener. + type: str + sample: "HTTP" + protocol_port: + description: The protocol port number for the listener. + type: int + sample: 80 +''' + +EXAMPLES = ''' +# Create a listener, wait for the loadbalancer to be active. +- openstack.cloud.lb_listener: + cloud: mycloud + endpoint_type: admin + state: present + name: test-listener + loadbalancer: test-loadbalancer + protocol: HTTP + protocol_port: 8080 + +# Create a listener, do not wait for the loadbalancer to be active. +- openstack.cloud.lb_listener: + cloud: mycloud + endpoint_type: admin + state: present + name: test-listener + loadbalancer: test-loadbalancer + protocol: HTTP + protocol_port: 8080 + wait: no + +# Delete a listener +- openstack.cloud.lb_listener: + cloud: mycloud + endpoint_type: admin + state: absent + name: test-listener + loadbalancer: test-loadbalancer +''' + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _lb_wait_for_status(module, cloud, lb, status, failures, interval=5): + """Wait for load balancer to be in a particular provisioning status.""" + timeout = module.params['timeout'] + + total_sleep = 0 + if failures is None: + failures = [] + + while total_sleep < timeout: + lb = cloud.load_balancer.get_load_balancer(lb.id) + if lb.provisioning_status == status: + return None + if lb.provisioning_status in failures: + module.fail_json( + msg="Load Balancer %s transitioned to failure state %s" % + (lb.id, lb.provisioning_status) + ) + + time.sleep(interval) + total_sleep += interval + + module.fail_json( + msg="Timeout waiting for Load Balancer %s to transition to %s" % + (lb.id, status) + ) + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + loadbalancer=dict(required=True), + protocol=dict(default='HTTP', + choices=['HTTP', 'HTTPS', 'TCP', 'TERMINATED_HTTPS']), + protocol_port=dict(default=80, type='int', required=False), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + sdk, cloud = openstack_cloud_from_module(module) + loadbalancer = module.params['loadbalancer'] + loadbalancer_id = None + + try: + changed = False + listener = cloud.load_balancer.find_listener( + name_or_id=module.params['name']) + + if module.params['state'] == 'present': + if not listener: + lb = cloud.load_balancer.find_load_balancer(loadbalancer) + if not lb: + module.fail_json( + msg='load balancer %s is not found' % loadbalancer + ) + loadbalancer_id = lb.id + + listener = cloud.load_balancer.create_listener( + name=module.params['name'], + loadbalancer_id=loadbalancer_id, + protocol=module.params['protocol'], + protocol_port=module.params['protocol_port'], + ) + changed = True + + if not module.params['wait']: + module.exit_json(changed=changed, + listener=listener.to_dict(), + id=listener.id) + + if module.params['wait']: + # Check in case the listener already exists. + lb = cloud.load_balancer.find_load_balancer(loadbalancer) + if not lb: + module.fail_json( + msg='load balancer %s is not found' % loadbalancer + ) + _lb_wait_for_status(module, cloud, lb, "ACTIVE", ["ERROR"]) + + module.exit_json(changed=changed, listener=listener.to_dict(), + id=listener.id) + elif module.params['state'] == 'absent': + if not listener: + changed = False + else: + cloud.load_balancer.delete_listener(listener) + changed = True + + if module.params['wait']: + # Wait for the load balancer to be active after deleting + # the listener. + lb = cloud.load_balancer.find_load_balancer(loadbalancer) + if not lb: + module.fail_json( + msg='load balancer %s is not found' % loadbalancer + ) + _lb_wait_for_status(module, cloud, lb, "ACTIVE", ["ERROR"]) + + module.exit_json(changed=changed) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_loadbalancer.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_loadbalancer.py new file mode 100644 index 00000000..9836a61c --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_loadbalancer.py @@ -0,0 +1,660 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Catalyst Cloud Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: loadbalancer +short_description: Add/Delete load balancer from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove load balancer from the OpenStack load-balancer + service(Octavia). Load balancer update is not supported for now. +options: + name: + description: + - The name of the load balancer. + required: true + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + flavor: + description: + - The flavor of the load balancer. + type: str + vip_network: + description: + - The name or id of the network for the virtual IP of the load balancer. + One of I(vip_network), I(vip_subnet), or I(vip_port) must be specified + for creation. + type: str + vip_subnet: + description: + - The name or id of the subnet for the virtual IP of the load balancer. + One of I(vip_network), I(vip_subnet), or I(vip_port) must be specified + for creation. + type: str + vip_port: + description: + - The name or id of the load balancer virtual IP port. One of + I(vip_network), I(vip_subnet), or I(vip_port) must be specified for + creation. + type: str + vip_address: + description: + - IP address of the load balancer virtual IP. + type: str + public_ip_address: + description: + - Public IP address associated with the VIP. + type: str + auto_public_ip: + description: + - Allocate a public IP address and associate with the VIP automatically. + type: bool + default: 'no' + public_network: + description: + - The name or ID of a Neutron external network. + type: str + delete_public_ip: + description: + - When C(state=absent) and this option is true, any public IP address + associated with the VIP will be deleted along with the load balancer. + type: bool + default: 'no' + listeners: + description: + - A list of listeners that attached to the load balancer. + suboptions: + name: + description: + - The listener name or ID. + protocol: + description: + - The protocol for the listener. + default: HTTP + protocol_port: + description: + - The protocol port number for the listener. + default: 80 + allowed_cidrs: + description: + - A list of IPv4, IPv6 or mix of both CIDRs to be allowed access to the listener. The default is all allowed. + When a list of CIDRs is provided, the default switches to deny all. + Ignored on unsupported Octavia versions (less than 2.12) + default: [] + pool: + description: + - The pool attached to the listener. + suboptions: + name: + description: + - The pool name or ID. + protocol: + description: + - The protocol for the pool. + default: HTTP + lb_algorithm: + description: + - The load balancing algorithm for the pool. + default: ROUND_ROBIN + members: + description: + - A list of members that added to the pool. + suboptions: + name: + description: + - The member name or ID. + address: + description: + - The IP address of the member. + protocol_port: + description: + - The protocol port number for the member. + default: 80 + subnet: + description: + - The name or ID of the subnet the member service is + accessible from. + elements: dict + type: list + wait: + description: + - If the module should wait for the load balancer to be created or + deleted. + type: bool + default: 'yes' + timeout: + description: + - The amount of time the module should wait. + default: 180 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +id: + description: The load balancer UUID. + returned: On success when C(state=present) + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +loadbalancer: + description: Dictionary describing the load balancer. + returned: On success when C(state=present) + type: complex + contains: + id: + description: Unique UUID. + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" + name: + description: Name given to the load balancer. + type: str + sample: "lingxian_test" + vip_network_id: + description: Network ID the load balancer virtual IP port belongs in. + type: str + sample: "f171db43-56fd-41cf-82d7-4e91d741762e" + vip_subnet_id: + description: Subnet ID the load balancer virtual IP port belongs in. + type: str + sample: "c53e3c70-9d62-409a-9f71-db148e7aa853" + vip_port_id: + description: The load balancer virtual IP port ID. + type: str + sample: "2061395c-1c01-47ab-b925-c91b93df9c1d" + vip_address: + description: The load balancer virtual IP address. + type: str + sample: "192.168.2.88" + public_vip_address: + description: The load balancer public VIP address. + type: str + sample: "10.17.8.254" + provisioning_status: + description: The provisioning status of the load balancer. + type: str + sample: "ACTIVE" + operating_status: + description: The operating status of the load balancer. + type: str + sample: "ONLINE" + is_admin_state_up: + description: The administrative state of the load balancer. + type: bool + sample: true + listeners: + description: The associated listener IDs, if any. + type: list + sample: [{"id": "7aa1b380-beec-459c-a8a7-3a4fb6d30645"}, {"id": "692d06b8-c4f8-4bdb-b2a3-5a263cc23ba6"}] + pools: + description: The associated pool IDs, if any. + type: list + sample: [{"id": "27b78d92-cee1-4646-b831-e3b90a7fa714"}, {"id": "befc1fb5-1992-4697-bdb9-eee330989344"}] +''' + +EXAMPLES = ''' +# Create a load balancer by specifying the VIP subnet. +- openstack.cloud.loadbalancer: + auth: + auth_url: https://identity.example.com + username: admin + password: passme + project_name: admin + state: present + name: my_lb + vip_subnet: my_subnet + timeout: 150 + +# Create a load balancer by specifying the VIP network and the IP address. +- openstack.cloud.loadbalancer: + auth: + auth_url: https://identity.example.com + username: admin + password: passme + project_name: admin + state: present + name: my_lb + vip_network: my_network + vip_address: 192.168.0.11 + +# Create a load balancer together with its sub-resources in the 'all in one' +# way. A public IP address is also allocated to the load balancer VIP. +- openstack.cloud.loadbalancer: + auth: + auth_url: https://identity.example.com + username: admin + password: passme + project_name: admin + name: lingxian_test + state: present + vip_subnet: kong_subnet + auto_public_ip: yes + public_network: public + listeners: + - name: lingxian_80 + protocol: TCP + protocol_port: 80 + pool: + name: lingxian_80_pool + protocol: TCP + members: + - name: mywebserver1 + address: 192.168.2.81 + protocol_port: 80 + subnet: webserver_subnet + - name: lingxian_8080 + protocol: TCP + protocol_port: 8080 + pool: + name: lingxian_8080-pool + protocol: TCP + members: + - name: mywebserver2 + address: 192.168.2.82 + protocol_port: 8080 + wait: yes + timeout: 600 + +# Delete a load balancer(and all its related resources) +- openstack.cloud.loadbalancer: + auth: + auth_url: https://identity.example.com + username: admin + password: passme + project_name: admin + state: absent + name: my_lb + +# Delete a load balancer(and all its related resources) together with the +# public IP address(if any) attached to it. +- openstack.cloud.loadbalancer: + auth: + auth_url: https://identity.example.com + username: admin + password: passme + project_name: admin + state: absent + name: my_lb + delete_public_ip: yes +''' + +import time +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class LoadBalancerModule(OpenStackModule): + + def _wait_for_lb(self, lb, status, failures, interval=5): + """Wait for load balancer to be in a particular provisioning status.""" + timeout = self.params['timeout'] + + total_sleep = 0 + if failures is None: + failures = [] + + while total_sleep < timeout: + lb = self.conn.load_balancer.find_load_balancer(lb.id) + + if lb: + if lb.provisioning_status == status: + return None + if lb.provisioning_status in failures: + self.fail_json( + msg="Load Balancer %s transitioned to failure state %s" % + (lb.id, lb.provisioning_status) + ) + else: + if status == "DELETED": + return None + else: + self.fail_json( + msg="Load Balancer %s transitioned to DELETED" % lb.id + ) + + time.sleep(interval) + total_sleep += interval + + self.fail_json( + msg="Timeout waiting for Load Balancer %s to transition to %s" % + (lb.id, status) + ) + + argument_spec = dict( + name=dict(required=True), + flavor=dict(required=False), + state=dict(default='present', choices=['absent', 'present']), + vip_network=dict(required=False), + vip_subnet=dict(required=False), + vip_port=dict(required=False), + vip_address=dict(required=False), + listeners=dict(type='list', default=[], elements='dict'), + public_ip_address=dict(required=False, default=None), + auto_public_ip=dict(required=False, default=False, type='bool'), + public_network=dict(required=False), + delete_public_ip=dict(required=False, default=False, type='bool'), + ) + module_kwargs = dict(supports_check_mode=True) + + def run(self): + flavor = self.params['flavor'] + vip_network = self.params['vip_network'] + vip_subnet = self.params['vip_subnet'] + vip_port = self.params['vip_port'] + listeners = self.params['listeners'] + public_vip_address = self.params['public_ip_address'] + allocate_fip = self.params['auto_public_ip'] + delete_fip = self.params['delete_public_ip'] + public_network = self.params['public_network'] + + vip_network_id = None + vip_subnet_id = None + vip_port_id = None + flavor_id = None + + try: + max_microversion = 1 + max_majorversion = 2 + changed = False + lb = self.conn.load_balancer.find_load_balancer( + name_or_id=self.params['name']) + + if self.params['state'] == 'present': + if lb and self.ansible.check_mode: + self.exit_json(changed=False) + if lb: + self.exit_json(changed=False) + ver_data = self.conn.load_balancer.get_all_version_data() + region = list(ver_data.keys())[0] + interface_type = list(ver_data[region].keys())[0] + versions = ver_data[region][interface_type]['load-balancer'] + for ver in versions: + if ver['status'] == 'CURRENT': + curversion = ver['version'].split(".") + max_majorversion = int(curversion[0]) + max_microversion = int(curversion[1]) + + if not lb: + if self.ansible.check_mode: + self.exit_json(changed=True) + + if not (vip_network or vip_subnet or vip_port): + self.fail_json( + msg="One of vip_network, vip_subnet, or vip_port must " + "be specified for load balancer creation" + ) + + if flavor: + _flavor = self.conn.load_balancer.find_flavor(flavor) + if not _flavor: + self.fail_json( + msg='flavor %s not found' % flavor + ) + flavor_id = _flavor.id + + if vip_network: + network = self.conn.get_network(vip_network) + if not network: + self.fail_json( + msg='network %s is not found' % vip_network + ) + vip_network_id = network.id + if vip_subnet: + subnet = self.conn.get_subnet(vip_subnet) + if not subnet: + self.fail_json( + msg='subnet %s is not found' % vip_subnet + ) + vip_subnet_id = subnet.id + if vip_port: + port = self.conn.get_port(vip_port) + + if not port: + self.fail_json( + msg='port %s is not found' % vip_port + ) + vip_port_id = port.id + lbargs = {"name": self.params['name'], + "vip_network_id": vip_network_id, + "vip_subnet_id": vip_subnet_id, + "vip_port_id": vip_port_id, + "vip_address": self.params['vip_address'] + } + if flavor_id is not None: + lbargs["flavor_id"] = flavor_id + + lb = self.conn.load_balancer.create_load_balancer(**lbargs) + + changed = True + + if not listeners and not self.params['wait']: + self.exit_json( + changed=changed, + loadbalancer=lb.to_dict(), + id=lb.id + ) + + self._wait_for_lb(lb, "ACTIVE", ["ERROR"]) + + for listener_def in listeners: + listener_name = listener_def.get("name") + pool_def = listener_def.get("pool") + + if not listener_name: + self.fail_json(msg='listener name is required') + + listener = self.conn.load_balancer.find_listener( + name_or_id=listener_name + ) + + if not listener: + self._wait_for_lb(lb, "ACTIVE", ["ERROR"]) + + protocol = listener_def.get("protocol", "HTTP") + protocol_port = listener_def.get("protocol_port", 80) + allowed_cidrs = listener_def.get("allowed_cidrs", []) + listenerargs = {"name": listener_name, + "loadbalancer_id": lb.id, + "protocol": protocol, + "protocol_port": protocol_port + } + if max_microversion >= 12 and max_majorversion >= 2: + listenerargs['allowed_cidrs'] = allowed_cidrs + listener = self.conn.load_balancer.create_listener(**listenerargs) + changed = True + + # Ensure pool in the listener. + if pool_def: + pool_name = pool_def.get("name") + members = pool_def.get('members', []) + + if not pool_name: + self.fail_json(msg='pool name is required') + + pool = self.conn.load_balancer.find_pool(name_or_id=pool_name) + + if not pool: + self._wait_for_lb(lb, "ACTIVE", ["ERROR"]) + + protocol = pool_def.get("protocol", "HTTP") + lb_algorithm = pool_def.get("lb_algorithm", + "ROUND_ROBIN") + + pool = self.conn.load_balancer.create_pool( + name=pool_name, + listener_id=listener.id, + protocol=protocol, + lb_algorithm=lb_algorithm + ) + changed = True + + # Ensure members in the pool + for member_def in members: + member_name = member_def.get("name") + if not member_name: + self.fail_json(msg='member name is required') + + member = self.conn.load_balancer.find_member(member_name, + pool.id + ) + + if not member: + self._wait_for_lb(lb, "ACTIVE", ["ERROR"]) + + address = member_def.get("address") + if not address: + self.fail_json( + msg='member address for member %s is ' + 'required' % member_name + ) + + subnet_id = member_def.get("subnet") + if subnet_id: + subnet = self.conn.get_subnet(subnet_id) + if not subnet: + self.fail_json( + msg='subnet %s for member %s is not ' + 'found' % (subnet_id, member_name) + ) + subnet_id = subnet.id + + protocol_port = member_def.get("protocol_port", 80) + + member = self.conn.load_balancer.create_member( + pool, + name=member_name, + address=address, + protocol_port=protocol_port, + subnet_id=subnet_id + ) + changed = True + + # Associate public ip to the load balancer VIP. If + # public_vip_address is provided, use that IP, otherwise, either + # find an available public ip or create a new one. + fip = None + orig_public_ip = None + new_public_ip = None + if public_vip_address or allocate_fip: + ips = self.conn.network.ips( + port_id=lb.vip_port_id, + fixed_ip_address=lb.vip_address + ) + ips = list(ips) + if ips: + orig_public_ip = ips[0] + new_public_ip = orig_public_ip.floating_ip_address + + if public_vip_address and public_vip_address != orig_public_ip: + fip = self.conn.network.find_ip(public_vip_address) + + if not fip: + self.fail_json( + msg='Public IP %s is unavailable' % public_vip_address + ) + + # Release origin public ip first + self.conn.network.update_ip( + orig_public_ip, + fixed_ip_address=None, + port_id=None + ) + + # Associate new public ip + self.conn.network.update_ip( + fip, + fixed_ip_address=lb.vip_address, + port_id=lb.vip_port_id + ) + + new_public_ip = public_vip_address + changed = True + elif allocate_fip and not orig_public_ip: + fip = self.conn.network.find_available_ip() + if not fip: + if not public_network: + self.fail_json(msg="Public network is not provided") + + pub_net = self.conn.network.find_network(public_network) + if not pub_net: + self.fail_json( + msg='Public network %s not found' % + public_network + ) + fip = self.conn.network.create_ip( + floating_network_id=pub_net.id + ) + + self.conn.network.update_ip( + fip, + fixed_ip_address=lb.vip_address, + port_id=lb.vip_port_id + ) + + new_public_ip = fip.floating_ip_address + changed = True + + # Include public_vip_address in the result. + lb = self.conn.load_balancer.find_load_balancer(name_or_id=lb.id) + lb_dict = lb.to_dict() + lb_dict.update({"public_vip_address": new_public_ip}) + + self.exit_json( + changed=changed, + loadbalancer=lb_dict, + id=lb.id + ) + elif self.params['state'] == 'absent': + changed = False + public_vip_address = None + + if lb: + if self.ansible.check_mode: + self.exit_json(changed=True) + if delete_fip: + ips = self.conn.network.ips( + port_id=lb.vip_port_id, + fixed_ip_address=lb.vip_address + ) + ips = list(ips) + if ips: + public_vip_address = ips[0] + + # Deleting load balancer with `cascade=False` does not make + # sense because the deletion will always fail if there are + # sub-resources. + self.conn.load_balancer.delete_load_balancer(lb, cascade=True) + changed = True + + if self.params['wait']: + self._wait_for_lb(lb, "DELETED", ["ERROR"]) + + if delete_fip and public_vip_address: + self.conn.network.delete_ip(public_vip_address) + changed = True + elif self.ansible.check_mode: + self.exit_json(changed=False) + + self.exit_json(changed=changed) + except Exception as e: + self.fail_json(msg=str(e)) + + +def main(): + module = LoadBalancerModule() + module() + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_member.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_member.py new file mode 100644 index 00000000..2eab39cb --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_member.py @@ -0,0 +1,227 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Catalyst Cloud Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: lb_member +short_description: Add/Delete a member for a pool in load balancer from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove a member for a pool from the OpenStack load-balancer service. +options: + name: + description: + - Name that has to be given to the member + required: true + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + pool: + description: + - The name or id of the pool that this member belongs to. + required: true + type: str + protocol_port: + description: + - The protocol port number for the member. + default: 80 + type: int + address: + description: + - The IP address of the member. + type: str + subnet_id: + description: + - The subnet ID the member service is accessible from. + type: str + wait: + description: + - If the module should wait for the load balancer to be ACTIVE. + type: bool + default: 'yes' + timeout: + description: + - The amount of time the module should wait for the load balancer to get + into ACTIVE state. + default: 180 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +id: + description: The member UUID. + returned: On success when I(state) is 'present' + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +member: + description: Dictionary describing the member. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Unique UUID. + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" + name: + description: Name given to the member. + type: str + sample: "test" + description: + description: The member description. + type: str + sample: "description" + provisioning_status: + description: The provisioning status of the member. + type: str + sample: "ACTIVE" + operating_status: + description: The operating status of the member. + type: str + sample: "ONLINE" + is_admin_state_up: + description: The administrative state of the member. + type: bool + sample: true + protocol_port: + description: The protocol port number for the member. + type: int + sample: 80 + subnet_id: + description: The subnet ID the member service is accessible from. + type: str + sample: "489247fa-9c25-11e8-9679-00224d6b7bc1" + address: + description: The IP address of the backend member server. + type: str + sample: "192.168.2.10" +''' + +EXAMPLES = ''' +# Create a member, wait for the member to be created. +- openstack.cloud.lb_member: + cloud: mycloud + endpoint_type: admin + state: present + name: test-member + pool: test-pool + address: 192.168.10.3 + protocol_port: 8080 + +# Delete a listener +- openstack.cloud.lb_member: + cloud: mycloud + endpoint_type: admin + state: absent + name: test-member + pool: test-pool +''' + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _wait_for_member_status(module, cloud, pool_id, member_id, status, + failures, interval=5): + timeout = module.params['timeout'] + + total_sleep = 0 + if failures is None: + failures = [] + + while total_sleep < timeout: + member = cloud.load_balancer.get_member(member_id, pool_id) + provisioning_status = member.provisioning_status + if provisioning_status == status: + return member + if provisioning_status in failures: + module.fail_json( + msg="Member %s transitioned to failure state %s" % + (member_id, provisioning_status) + ) + + time.sleep(interval) + total_sleep += interval + + module.fail_json( + msg="Timeout waiting for member %s to transition to %s" % + (member_id, status) + ) + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + pool=dict(required=True), + address=dict(default=None), + protocol_port=dict(default=80, type='int'), + subnet_id=dict(default=None), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + sdk, cloud = openstack_cloud_from_module(module) + name = module.params['name'] + pool = module.params['pool'] + + try: + changed = False + + pool_ret = cloud.load_balancer.find_pool(name_or_id=pool) + if not pool_ret: + module.fail_json(msg='pool %s is not found' % pool) + + pool_id = pool_ret.id + member = cloud.load_balancer.find_member(name, pool_id) + + if module.params['state'] == 'present': + if not member: + member = cloud.load_balancer.create_member( + pool_ret, + address=module.params['address'], + name=name, + protocol_port=module.params['protocol_port'], + subnet_id=module.params['subnet_id'] + ) + changed = True + + if not module.params['wait']: + module.exit_json(changed=changed, + member=member.to_dict(), + id=member.id) + + if module.params['wait']: + member = _wait_for_member_status(module, cloud, pool_id, + member.id, "ACTIVE", + ["ERROR"]) + + module.exit_json(changed=changed, member=member.to_dict(), + id=member.id) + + elif module.params['state'] == 'absent': + if member: + cloud.load_balancer.delete_member(member, pool_ret) + changed = True + + module.exit_json(changed=changed) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_network.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_network.py new file mode 100644 index 00000000..780d49ba --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_network.py @@ -0,0 +1,245 @@ +#!/usr/bin/python + +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: network +short_description: Creates/removes networks from OpenStack +author: OpenStack Ansible SIG +description: + - Add or remove network from OpenStack. +options: + name: + description: + - Name to be assigned to the network. + required: true + type: str + shared: + description: + - Whether this network is shared or not. + type: bool + default: 'no' + admin_state_up: + description: + - Whether the state should be marked as up or down. + type: bool + default: 'yes' + external: + description: + - Whether this network is externally accessible. + type: bool + default: 'no' + state: + description: + - Indicate desired state of the resource. + choices: ['present', 'absent'] + default: present + type: str + provider_physical_network: + description: + - The physical network where this network object is implemented. + type: str + provider_network_type: + description: + - The type of physical network that maps to this network resource. + type: str + provider_segmentation_id: + description: + - An isolated segment on the physical network. The I(network_type) + attribute defines the segmentation model. For example, if the + I(network_type) value is vlan, this ID is a vlan identifier. If + the I(network_type) value is gre, this ID is a gre key. + type: int + project: + description: + - Project name or ID containing the network (name admin-only) + type: str + port_security_enabled: + description: + - Whether port security is enabled on the network or not. + Network will use OpenStack defaults if this option is + not utilised. Requires openstacksdk>=0.18. + type: bool + mtu_size: + description: + - The maximum transmission unit (MTU) value to address fragmentation. + Network will use OpenStack defaults if this option is + not provided. Requires openstacksdk>=0.18. + type: int + aliases: ['mtu'] + dns_domain: + description: + - The DNS domain value to set. Requires openstacksdk>=0.29. + Network will use Openstack defaults if this option is + not provided. + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create an externally accessible network named 'ext_network'. +- openstack.cloud.network: + cloud: mycloud + state: present + name: ext_network + external: true +''' + +RETURN = ''' +network: + description: Dictionary describing the network. + returned: On success when I(state) is 'present'. + type: complex + contains: + id: + description: Network ID. + type: str + sample: "4bb4f9a5-3bd2-4562-bf6a-d17a6341bb56" + name: + description: Network name. + type: str + sample: "ext_network" + shared: + description: Indicates whether this network is shared across all tenants. + type: bool + sample: false + status: + description: Network status. + type: str + sample: "ACTIVE" + mtu: + description: The MTU of a network resource. + type: int + sample: 0 + dns_domain: + description: The DNS domain of a network resource. + type: str + sample: "sample.openstack.org." + admin_state_up: + description: The administrative state of the network. + type: bool + sample: true + port_security_enabled: + description: The port security status + type: bool + sample: true + router:external: + description: Indicates whether this network is externally accessible. + type: bool + sample: true + tenant_id: + description: The tenant ID. + type: str + sample: "06820f94b9f54b119636be2728d216fc" + subnets: + description: The associated subnets. + type: list + sample: [] + "provider:physical_network": + description: The physical network where this network object is implemented. + type: str + sample: my_vlan_net + "provider:network_type": + description: The type of physical network that maps to this network resource. + type: str + sample: vlan + "provider:segmentation_id": + description: An isolated segment on the physical network. + type: str + sample: 101 +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class NetworkModule(OpenStackModule): + + argument_spec = dict( + name=dict(required=True), + shared=dict(default=False, type='bool'), + admin_state_up=dict(default=True, type='bool'), + external=dict(default=False, type='bool'), + provider_physical_network=dict(required=False), + provider_network_type=dict(required=False), + provider_segmentation_id=dict(required=False, type='int'), + state=dict(default='present', choices=['absent', 'present']), + project=dict(default=None), + port_security_enabled=dict(type='bool', min_ver='0.18.0'), + mtu_size=dict(required=False, type='int', min_ver='0.18.0', aliases=['mtu']), + dns_domain=dict(required=False, min_ver='0.29.0') + ) + + def run(self): + + state = self.params['state'] + name = self.params['name'] + shared = self.params['shared'] + admin_state_up = self.params['admin_state_up'] + external = self.params['external'] + provider_physical_network = self.params['provider_physical_network'] + provider_network_type = self.params['provider_network_type'] + provider_segmentation_id = self.params['provider_segmentation_id'] + project = self.params['project'] + + kwargs = self.check_versioned( + mtu_size=self.params['mtu_size'], port_security_enabled=self.params['port_security_enabled'], + dns_domain=self.params['dns_domain'] + ) + + if project is not None: + proj = self.conn.get_project(project) + if proj is None: + self.fail_json(msg='Project %s could not be found' % project) + project_id = proj['id'] + filters = {'tenant_id': project_id} + else: + project_id = None + filters = None + net = self.conn.get_network(name, filters=filters) + + if state == 'present': + if not net: + provider = {} + if provider_physical_network: + provider['physical_network'] = provider_physical_network + if provider_network_type: + provider['network_type'] = provider_network_type + if provider_segmentation_id: + provider['segmentation_id'] = provider_segmentation_id + + if project_id is not None: + net = self.conn.create_network(name, shared, admin_state_up, + external, provider, project_id, + **kwargs) + else: + net = self.conn.create_network(name, shared, admin_state_up, + external, provider, + **kwargs) + changed = True + else: + changed = False + self.exit(changed=changed, network=net, id=net['id']) + + elif state == 'absent': + if not net: + self.exit(changed=False) + else: + self.conn.delete_network(name) + self.exit(changed=True) + + +def main(): + module = NetworkModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_networks_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_networks_info.py new file mode 100644 index 00000000..a8d098c7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_networks_info.py @@ -0,0 +1,146 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: networks_info +short_description: Retrieve information about one or more OpenStack networks. +author: OpenStack Ansible SIG +description: + - Retrieve information about one or more networks from OpenStack. + - This module was called C(openstack.cloud.networks_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.networks_info) module no longer returns C(ansible_facts)! +options: + name: + description: + - Name or ID of the Network + required: false + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + required: false + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Gather information about previously created networks + openstack.cloud.networks_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + register: result + +- name: Show openstack networks + debug: + msg: "{{ result.openstack_networks }}" + +- name: Gather information about a previously created network by name + openstack.cloud.networks_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + name: network1 + register: result + +- name: Show openstack networks + debug: + msg: "{{ result.openstack_networks }}" + +- name: Gather information about a previously created network with filter + # Note: name and filters parameters are Not mutually exclusive + openstack.cloud.networks_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + filters: + tenant_id: 55e2ce24b2a245b09f181bf025724cbe + subnets: + - 057d4bdf-6d4d-4728-bb0f-5ac45a6f7400 + - 443d4dc0-91d4-4998-b21c-357d10433483 + register: result + +- name: Show openstack networks + debug: + msg: "{{ result.openstack_networks }}" +''' + +RETURN = ''' +openstack_networks: + description: has all the openstack information about the networks + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the network. + returned: success + type: str + status: + description: Network status. + returned: success + type: str + subnets: + description: Subnet(s) included in this network. + returned: success + type: list + elements: str + tenant_id: + description: Tenant id associated with this network. + returned: success + type: str + shared: + description: Network shared flag. + returned: success + type: bool +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class NetworkInfoModule(OpenStackModule): + + deprecated_names = ('networks_facts', 'openstack.cloud.networks_facts') + + argument_spec = dict( + name=dict(required=False, default=None), + filters=dict(required=False, type='dict', default=None) + ) + + def run(self): + + kwargs = self.check_versioned( + filters=self.params['filters'] + ) + if self.params['name']: + kwargs['name_or_id'] = self.params['name'] + networks = self.conn.search_networks(**kwargs) + + self.exit(changed=False, openstack_networks=networks) + + +def main(): + module = NetworkInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_nova_flavor.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_nova_flavor.py new file mode 100644 index 00000000..85092b44 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_nova_flavor.py @@ -0,0 +1,274 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: compute_flavor +short_description: Manage OpenStack compute flavors +author: OpenStack Ansible SIG +description: + - Add or remove flavors from OpenStack. +options: + state: + description: + - Indicate desired state of the resource. When I(state) is 'present', + then I(ram), I(vcpus), and I(disk) are all required. There are no + default values for those parameters. + choices: ['present', 'absent'] + default: present + type: str + name: + description: + - Flavor name. + required: true + type: str + ram: + description: + - Amount of memory, in MB. + type: int + vcpus: + description: + - Number of virtual CPUs. + type: int + disk: + description: + - Size of local disk, in GB. + default: 0 + type: int + ephemeral: + description: + - Ephemeral space size, in GB. + default: 0 + type: int + swap: + description: + - Swap space size, in MB. + default: 0 + type: int + rxtx_factor: + description: + - RX/TX factor. + default: 1.0 + type: float + is_public: + description: + - Make flavor accessible to the public. + type: bool + default: 'yes' + flavorid: + description: + - ID for the flavor. This is optional as a unique UUID will be + assigned if a value is not specified. + default: "auto" + type: str + extra_specs: + description: + - Metadata dictionary + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: "Create 'tiny' flavor with 1024MB of RAM, 1 virtual CPU, and 10GB of local disk, and 10GB of ephemeral." + openstack.cloud.compute_flavor: + cloud: mycloud + state: present + name: tiny + ram: 1024 + vcpus: 1 + disk: 10 + ephemeral: 10 + +- name: "Delete 'tiny' flavor" + openstack.cloud.compute_flavor: + cloud: mycloud + state: absent + name: tiny + +- name: Create flavor with metadata + openstack.cloud.compute_flavor: + cloud: mycloud + state: present + name: tiny + ram: 1024 + vcpus: 1 + disk: 10 + extra_specs: + "quota:disk_read_iops_sec": 5000 + "aggregate_instance_extra_specs:pinned": false +''' + +RETURN = ''' +flavor: + description: Dictionary describing the flavor. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Flavor ID. + returned: success + type: str + sample: "515256b8-7027-4d73-aa54-4e30a4a4a339" + name: + description: Flavor name. + returned: success + type: str + sample: "tiny" + disk: + description: Size of local disk, in GB. + returned: success + type: int + sample: 10 + ephemeral: + description: Ephemeral space size, in GB. + returned: success + type: int + sample: 10 + ram: + description: Amount of memory, in MB. + returned: success + type: int + sample: 1024 + swap: + description: Swap space size, in MB. + returned: success + type: int + sample: 100 + vcpus: + description: Number of virtual CPUs. + returned: success + type: int + sample: 2 + is_public: + description: Make flavor accessible to the public. + returned: success + type: bool + sample: true + extra_specs: + description: Flavor metadata + returned: success + type: dict + sample: + "quota:disk_read_iops_sec": 5000 + "aggregate_instance_extra_specs:pinned": false +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(module, flavor): + state = module.params['state'] + if state == 'present' and not flavor: + return True + if state == 'absent' and flavor: + return True + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + state=dict(required=False, default='present', + choices=['absent', 'present']), + name=dict(required=True), + + # required when state is 'present' + ram=dict(required=False, type='int'), + vcpus=dict(required=False, type='int'), + + disk=dict(required=False, default=0, type='int'), + ephemeral=dict(required=False, default=0, type='int'), + swap=dict(required=False, default=0, type='int'), + rxtx_factor=dict(required=False, default=1.0, type='float'), + is_public=dict(required=False, default=True, type='bool'), + flavorid=dict(required=False, default="auto"), + extra_specs=dict(required=False, default=None, type='dict'), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + required_if=[ + ('state', 'present', ['ram', 'vcpus', 'disk']) + ], + **module_kwargs) + + state = module.params['state'] + name = module.params['name'] + extra_specs = module.params['extra_specs'] or {} + + sdk, cloud = openstack_cloud_from_module(module) + try: + flavor = cloud.get_flavor(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, flavor)) + + if state == 'present': + old_extra_specs = {} + require_update = False + + if flavor: + old_extra_specs = flavor['extra_specs'] + for param_key in ['ram', 'vcpus', 'disk', 'ephemeral', 'swap', 'rxtx_factor', 'is_public']: + if module.params[param_key] != flavor[param_key]: + require_update = True + break + + if flavor and require_update: + cloud.delete_flavor(name) + flavor = None + + if not flavor: + flavor = cloud.create_flavor( + name=name, + ram=module.params['ram'], + vcpus=module.params['vcpus'], + disk=module.params['disk'], + flavorid=module.params['flavorid'], + ephemeral=module.params['ephemeral'], + swap=module.params['swap'], + rxtx_factor=module.params['rxtx_factor'], + is_public=module.params['is_public'] + ) + changed = True + else: + changed = False + + new_extra_specs = dict([(k, str(v)) for k, v in extra_specs.items()]) + unset_keys = set(old_extra_specs.keys()) - set(extra_specs.keys()) + + if unset_keys and not require_update: + cloud.unset_flavor_specs(flavor['id'], unset_keys) + + if old_extra_specs != new_extra_specs: + cloud.set_flavor_specs(flavor['id'], extra_specs) + + changed = (changed or old_extra_specs != new_extra_specs) + + module.exit_json(changed=changed, + flavor=flavor, + id=flavor['id']) + + elif state == 'absent': + if flavor: + cloud.delete_flavor(name) + module.exit_json(changed=True) + module.exit_json(changed=False) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_nova_host_aggregate.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_nova_host_aggregate.py new file mode 100644 index 00000000..303a3d8a --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_nova_host_aggregate.py @@ -0,0 +1,219 @@ +#!/usr/bin/python +# Copyright 2016 Jakub Jursa <jakub.jursa1@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: host_aggregate +short_description: Manage OpenStack host aggregates +author: OpenStack Ansible SIG +description: + - Create, update, or delete OpenStack host aggregates. If a aggregate + with the supplied name already exists, it will be updated with the + new name, new availability zone, new metadata and new list of hosts. +options: + name: + description: Name of the aggregate. + required: true + type: str + metadata: + description: Metadata dict. + type: dict + availability_zone: + description: Availability zone to create aggregate into. + type: str + hosts: + description: List of hosts to set for an aggregate. + type: list + elements: str + purge_hosts: + description: Whether hosts not in I(hosts) should be removed from the aggregate + type: bool + default: true + state: + description: Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a host aggregate +- openstack.cloud.host_aggregate: + cloud: mycloud + state: present + name: db_aggregate + hosts: + - host1 + - host2 + metadata: + type: dbcluster + +# Add an additional host to the aggregate +- openstack.cloud.host_aggregate: + cloud: mycloud + state: present + name: db_aggregate + hosts: + - host3 + purge_hosts: false + metadata: + type: dbcluster + +# Delete an aggregate +- openstack.cloud.host_aggregate: + cloud: mycloud + state: absent + name: db_aggregate +''' + +RETURN = ''' + +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _needs_update(module, aggregate): + new_metadata = (module.params['metadata'] or {}) + + if module.params['availability_zone'] is not None: + new_metadata['availability_zone'] = module.params['availability_zone'] + + if module.params['name'] != aggregate.name: + return True + if module.params['hosts'] is not None: + if module.params['purge_hosts']: + if set(module.params['hosts']) != set(aggregate.hosts): + return True + else: + intersection = set(module.params['hosts']).intersection(set(aggregate.hosts)) + if set(module.params['hosts']) != intersection: + return True + if module.params['availability_zone'] is not None: + if module.params['availability_zone'] != aggregate.availability_zone: + return True + if module.params['metadata'] is not None: + if new_metadata != aggregate.metadata: + return True + + return False + + +def _system_state_change(module, aggregate): + state = module.params['state'] + if state == 'absent' and aggregate: + return True + + if state == 'present': + if aggregate is None: + return True + return _needs_update(module, aggregate) + + return False + + +def _update_hosts(cloud, aggregate, hosts, purge_hosts): + if hosts is None: + return + + hosts_to_add = set(hosts) - set(aggregate.hosts) + for i in hosts_to_add: + cloud.add_host_to_aggregate(aggregate.id, i) + + if not purge_hosts: + return + + hosts_to_remove = set(aggregate.hosts) - set(hosts) + for i in hosts_to_remove: + cloud.remove_host_from_aggregate(aggregate.id, i) + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + metadata=dict(required=False, default=None, type='dict'), + availability_zone=dict(required=False, default=None), + hosts=dict(required=False, default=None, type='list', elements='str'), + purge_hosts=dict(default=True, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + name = module.params['name'] + metadata = module.params['metadata'] + availability_zone = module.params['availability_zone'] + hosts = module.params['hosts'] + purge_hosts = module.params['purge_hosts'] + state = module.params['state'] + + if metadata is not None: + metadata.pop('availability_zone', None) + + sdk, cloud = openstack_cloud_from_module(module) + try: + aggregates = cloud.search_aggregates(name_or_id=name) + + if len(aggregates) == 1: + aggregate = aggregates[0] + elif len(aggregates) == 0: + aggregate = None + else: + raise Exception("Should not happen") + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, aggregate)) + + if state == 'present': + if aggregate is None: + aggregate = cloud.create_aggregate(name=name, + availability_zone=availability_zone) + _update_hosts(cloud, aggregate, hosts, False) + if metadata: + cloud.set_aggregate_metadata(aggregate.id, metadata) + changed = True + else: + if _needs_update(module, aggregate): + if availability_zone is not None: + aggregate = cloud.update_aggregate(aggregate.id, name=name, + availability_zone=availability_zone) + if metadata is not None: + metas = metadata + for i in (set(aggregate.metadata.keys()) - set(metadata.keys())): + if i != 'availability_zone': + metas[i] = None + cloud.set_aggregate_metadata(aggregate.id, metas) + _update_hosts(cloud, aggregate, hosts, purge_hosts) + changed = True + else: + changed = False + module.exit_json(changed=changed) + + elif state == 'absent': + if aggregate is None: + changed = False + else: + _update_hosts(cloud, aggregate, [], True) + cloud.delete_aggregate(aggregate.id) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_object.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_object.py new file mode 100644 index 00000000..5d0afcf7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_object.py @@ -0,0 +1,123 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: object +short_description: Create or Delete objects and containers from OpenStack +author: OpenStack Ansible SIG +description: + - Create or Delete objects and containers from OpenStack +options: + container: + description: + - The name of the container in which to create the object + required: true + type: str + name: + description: + - Name to be give to the object. If omitted, operations will be on + the entire container + required: false + type: str + filename: + description: + - Path to local file to be uploaded. + required: false + type: str + container_access: + description: + - desired container access level. + required: false + choices: ['private', 'public'] + default: private + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: "Create a object named 'fstab' in the 'config' container" + openstack.cloud.object: + cloud: mordred + state: present + name: fstab + container: config + filename: /etc/fstab + +- name: Delete a container called config and all of its contents + openstack.cloud.object: + cloud: rax-iad + state: absent + container: config +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def process_object( + cloud_obj, container, name, filename, container_access, **kwargs): + + changed = False + container_obj = cloud_obj.get_container(container) + if kwargs['state'] == 'present': + if not container_obj: + container_obj = cloud_obj.create_container(container) + changed = True + if cloud_obj.get_container_access(container) != container_access: + cloud_obj.set_container_access(container, container_access) + changed = True + if name: + if cloud_obj.is_object_stale(container, name, filename): + cloud_obj.create_object(container, name, filename) + changed = True + else: + if container_obj: + if name: + if cloud_obj.get_object_metadata(container, name): + cloud_obj.delete_object(container, name) + changed = True + else: + cloud_obj.delete_container(container) + changed = True + return changed + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + container=dict(required=True), + filename=dict(required=False, default=None), + container_access=dict(default='private', choices=['private', 'public']), + state=dict(default='present', choices=['absent', 'present']), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + sdk, cloud = openstack_cloud_from_module(module) + try: + changed = process_object(cloud, **module.params) + + module.exit_json(changed=changed) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_pool.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_pool.py new file mode 100644 index 00000000..9d3e7e6b --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_pool.py @@ -0,0 +1,266 @@ +#!/usr/bin/python + +# Copyright (c) 2018 Catalyst Cloud Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: lb_pool +short_description: Add/Delete a pool in the load balancing service from OpenStack Cloud +author: OpenStack Ansible SIG +description: + - Add or Remove a pool from the OpenStack load-balancer service. +options: + name: + description: + - Name that has to be given to the pool + required: true + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + loadbalancer: + description: + - The name or id of the load balancer that this pool belongs to. + Either loadbalancer or listener must be specified for pool creation. + type: str + listener: + description: + - The name or id of the listener that this pool belongs to. + Either loadbalancer or listener must be specified for pool creation. + type: str + protocol: + description: + - The protocol for the pool. + choices: [HTTP, HTTPS, PROXY, TCP, UDP] + default: HTTP + type: str + lb_algorithm: + description: + - The load balancing algorithm for the pool. + choices: [LEAST_CONNECTIONS, ROUND_ROBIN, SOURCE_IP] + default: ROUND_ROBIN + type: str + wait: + description: + - If the module should wait for the pool to be ACTIVE. + type: bool + default: 'yes' + timeout: + description: + - The amount of time the module should wait for the pool to get + into ACTIVE state. + default: 180 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +id: + description: The pool UUID. + returned: On success when I(state) is 'present' + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +listener: + description: Dictionary describing the pool. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Unique UUID. + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" + name: + description: Name given to the pool. + type: str + sample: "test" + description: + description: The pool description. + type: str + sample: "description" + loadbalancers: + description: A list of load balancer IDs. + type: list + sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}] + listeners: + description: A list of listener IDs. + type: list + sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}] + members: + description: A list of member IDs. + type: list + sample: [{"id": "b32eef7e-d2a6-4ea4-a301-60a873f89b3b"}] + loadbalancer_id: + description: The load balancer ID the pool belongs to. This field is set when the pool doesn't belong to any listener in the load balancer. + type: str + sample: "7c4be3f8-9c2f-11e8-83b3-44a8422643a4" + listener_id: + description: The listener ID the pool belongs to. + type: str + sample: "956aa716-9c2f-11e8-83b3-44a8422643a4" + provisioning_status: + description: The provisioning status of the pool. + type: str + sample: "ACTIVE" + operating_status: + description: The operating status of the pool. + type: str + sample: "ONLINE" + is_admin_state_up: + description: The administrative state of the pool. + type: bool + sample: true + protocol: + description: The protocol for the pool. + type: str + sample: "HTTP" + lb_algorithm: + description: The load balancing algorithm for the pool. + type: str + sample: "ROUND_ROBIN" +''' + +EXAMPLES = ''' +# Create a pool, wait for the pool to be active. +- openstack.cloud.lb_pool: + cloud: mycloud + endpoint_type: admin + state: present + name: test-pool + loadbalancer: test-loadbalancer + protocol: HTTP + lb_algorithm: ROUND_ROBIN + +# Delete a pool +- openstack.cloud.lb_pool: + cloud: mycloud + endpoint_type: admin + state: absent + name: test-pool +''' + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec, \ + openstack_module_kwargs, openstack_cloud_from_module + + +def _wait_for_pool_status(module, cloud, pool_id, status, failures, + interval=5): + timeout = module.params['timeout'] + + total_sleep = 0 + if failures is None: + failures = [] + + while total_sleep < timeout: + pool = cloud.load_balancer.get_pool(pool_id) + provisioning_status = pool.provisioning_status + if provisioning_status == status: + return pool + if provisioning_status in failures: + module.fail_json( + msg="pool %s transitioned to failure state %s" % + (pool_id, provisioning_status) + ) + + time.sleep(interval) + total_sleep += interval + + module.fail_json( + msg="timeout waiting for pool %s to transition to %s" % + (pool_id, status) + ) + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + loadbalancer=dict(default=None), + listener=dict(default=None), + protocol=dict(default='HTTP', + choices=['HTTP', 'HTTPS', 'TCP', 'UDP', 'PROXY']), + lb_algorithm=dict( + default='ROUND_ROBIN', + choices=['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP'] + ) + ) + module_kwargs = openstack_module_kwargs( + mutually_exclusive=[['loadbalancer', 'listener']] + ) + module = AnsibleModule(argument_spec, **module_kwargs) + sdk, cloud = openstack_cloud_from_module(module) + + loadbalancer = module.params['loadbalancer'] + listener = module.params['listener'] + + try: + changed = False + pool = cloud.load_balancer.find_pool(name_or_id=module.params['name']) + + if module.params['state'] == 'present': + if not pool: + loadbalancer_id = None + if not (loadbalancer or listener): + module.fail_json( + msg="either loadbalancer or listener must be provided" + ) + + if loadbalancer: + lb = cloud.load_balancer.find_load_balancer(loadbalancer) + if not lb: + module.fail_json(msg='load balancer %s is not ' + 'found' % loadbalancer) + loadbalancer_id = lb.id + + listener_id = None + if listener: + listener_ret = cloud.load_balancer.find_listener(listener) + if not listener_ret: + module.fail_json(msg='listener %s is not found' + % listener) + listener_id = listener_ret.id + + pool = cloud.load_balancer.create_pool( + name=module.params['name'], + loadbalancer_id=loadbalancer_id, + listener_id=listener_id, + protocol=module.params['protocol'], + lb_algorithm=module.params['lb_algorithm'] + ) + changed = True + + if not module.params['wait']: + module.exit_json(changed=changed, + pool=pool.to_dict(), + id=pool.id) + + if module.params['wait']: + pool = _wait_for_pool_status(module, cloud, pool.id, "ACTIVE", + ["ERROR"]) + + module.exit_json(changed=changed, pool=pool.to_dict(), + id=pool.id) + + elif module.params['state'] == 'absent': + if pool: + cloud.load_balancer.delete_pool(pool) + changed = True + + module.exit_json(changed=changed) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_port.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_port.py new file mode 100644 index 00000000..79a48411 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_port.py @@ -0,0 +1,474 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: port +short_description: Add/Update/Delete ports from an OpenStack cloud. +author: OpenStack Ansible SIG +description: + - Add, Update or Remove ports from an OpenStack cloud. A I(state) of + 'present' will ensure the port is created or updated if required. +options: + network: + description: + - Network ID or name this port belongs to. + - Required when creating a new port. + type: str + name: + description: + - Name that has to be given to the port. + type: str + fixed_ips: + description: + - Desired IP and/or subnet for this port. Subnet is referenced by + subnet_id and IP is referenced by ip_address. + type: list + elements: dict + suboptions: + ip_address: + description: The fixed IP address to attempt to allocate. + required: true + type: str + subnet_id: + description: The subnet to attach the IP address to. + type: str + admin_state_up: + description: + - Sets admin state. + type: bool + mac_address: + description: + - MAC address of this port. + type: str + security_groups: + description: + - Security group(s) ID(s) or name(s) associated with the port (comma + separated string or YAML list) + type: list + elements: str + no_security_groups: + description: + - Do not associate a security group with this port. + type: bool + default: 'no' + allowed_address_pairs: + description: + - "Allowed address pairs list. Allowed address pairs are supported with + dictionary structure. + e.g. allowed_address_pairs: + - ip_address: 10.1.0.12 + mac_address: ab:cd:ef:12:34:56 + - ip_address: ..." + type: list + elements: dict + suboptions: + ip_address: + description: The IP address. + type: str + mac_address: + description: The MAC address. + type: str + extra_dhcp_opts: + description: + - "Extra dhcp options to be assigned to this port. Extra options are + supported with dictionary structure. Note that options cannot be removed + only updated. + e.g. extra_dhcp_opts: + - opt_name: opt name1 + opt_value: value1 + ip_version: 4 + - opt_name: ..." + type: list + elements: dict + suboptions: + opt_name: + description: The name of the DHCP option to set. + type: str + required: true + opt_value: + description: The value of the DHCP option to set. + type: str + required: true + ip_version: + description: The IP version this DHCP option is for. + type: int + required: true + device_owner: + description: + - The ID of the entity that uses this port. + type: str + device_id: + description: + - Device ID of device using this port. + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + vnic_type: + description: + - The type of the port that should be created + choices: [normal, direct, direct-physical, macvtap, baremetal, virtio-forwarder] + type: str + port_security_enabled: + description: + - Whether to enable or disable the port security on the network. + type: bool +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a port +- openstack.cloud.port: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: port1 + network: foo + +# Create a port with a static IP +- openstack.cloud.port: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: port1 + network: foo + fixed_ips: + - ip_address: 10.1.0.21 + +# Create a port with No security groups +- openstack.cloud.port: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: port1 + network: foo + no_security_groups: True + +# Update the existing 'port1' port with multiple security groups (version 1) +- openstack.cloud.port: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: port1 + security_groups: 1496e8c7-4918-482a-9172-f4f00fc4a3a5,057d4bdf-6d4d-472... + +# Update the existing 'port1' port with multiple security groups (version 2) +- openstack.cloud.port: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: port1 + security_groups: + - 1496e8c7-4918-482a-9172-f4f00fc4a3a5 + - 057d4bdf-6d4d-472... + +# Create port of type 'direct' +- openstack.cloud.port: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: port1 + network: foo + vnic_type: direct +''' + +RETURN = ''' +id: + description: Unique UUID. + returned: success + type: str +name: + description: Name given to the port. + returned: success + type: str +network_id: + description: Network ID this port belongs in. + returned: success + type: str +security_groups: + description: Security group(s) associated with this port. + returned: success + type: list +status: + description: Port's status. + returned: success + type: str +fixed_ips: + description: Fixed ip(s) associated with this port. + returned: success + type: list +tenant_id: + description: Tenant id associated with this port. + returned: success + type: str +allowed_address_pairs: + description: Allowed address pairs with this port. + returned: success + type: list +admin_state_up: + description: Admin state up flag for this port. + returned: success + type: bool +vnic_type: + description: Type of the created port + returned: success + type: str +port_security_enabled: + description: Port security state on the network. + returned: success + type: bool +''' + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + +try: + from collections import OrderedDict + HAS_ORDEREDDICT = True +except ImportError: + try: + from ordereddict import OrderedDict + HAS_ORDEREDDICT = True + except ImportError: + HAS_ORDEREDDICT = False + + +def _needs_update(module, port, cloud): + """Check for differences in the updatable values. + + NOTE: We don't currently allow name updates. + """ + compare_simple = ['admin_state_up', + 'mac_address', + 'device_owner', + 'device_id', + 'binding:vnic_type', + 'port_security_enabled'] + compare_list_dict = ['allowed_address_pairs', + 'extra_dhcp_opts'] + compare_list = ['security_groups'] + + for key in compare_simple: + if module.params[key] is not None and module.params[key] != port[key]: + return True + for key in compare_list: + if ( + module.params[key] is not None + and set(module.params[key]) != set(port[key]) + ): + return True + + for key in compare_list_dict: + if not module.params[key]: + if not port[key]: + return True + + # sort dicts in list + port_ordered = [OrderedDict(sorted(d.items())) for d in port[key]] + param_ordered = [OrderedDict(sorted(d.items())) for d in module.params[key]] + + for d in param_ordered: + if d not in port_ordered: + return True + + for d in port_ordered: + if d not in param_ordered: + return True + + # NOTE: if port was created or updated with 'no_security_groups=True', + # subsequent updates without 'no_security_groups' flag or + # 'no_security_groups=False' and no specified 'security_groups', will not + # result in an update to the port where the default security group is + # applied. + if module.params['no_security_groups'] and port['security_groups'] != []: + return True + + if module.params['fixed_ips'] is not None: + for item in module.params['fixed_ips']: + if 'ip_address' in item: + # if ip_address in request does not match any in existing port, + # update is required. + if not any(match['ip_address'] == item['ip_address'] + for match in port['fixed_ips']): + return True + if 'subnet_id' in item: + return True + for item in port['fixed_ips']: + # if ip_address in existing port does not match any in request, + # update is required. + if not any(match.get('ip_address') == item['ip_address'] + for match in module.params['fixed_ips']): + return True + + return False + + +def _system_state_change(module, port, cloud): + state = module.params['state'] + if state == 'present': + if not port: + return True + return _needs_update(module, port, cloud) + if state == 'absent' and port: + return True + return False + + +def _compose_port_args(module, cloud): + port_kwargs = {} + optional_parameters = ['name', + 'fixed_ips', + 'admin_state_up', + 'mac_address', + 'security_groups', + 'allowed_address_pairs', + 'extra_dhcp_opts', + 'device_owner', + 'device_id', + 'binding:vnic_type', + 'port_security_enabled'] + for optional_param in optional_parameters: + if module.params[optional_param] is not None: + port_kwargs[optional_param] = module.params[optional_param] + + if module.params['no_security_groups']: + port_kwargs['security_groups'] = [] + + return port_kwargs + + +def get_security_group_id(module, cloud, security_group_name_or_id): + security_group = cloud.get_security_group(security_group_name_or_id) + if not security_group: + module.fail_json(msg="Security group: %s, was not found" + % security_group_name_or_id) + return security_group['id'] + + +def main(): + argument_spec = openstack_full_argument_spec( + network=dict(required=False), + name=dict(required=False), + fixed_ips=dict(type='list', default=None, elements='dict'), + admin_state_up=dict(type='bool', default=None), + mac_address=dict(default=None), + security_groups=dict(default=None, type='list', elements='str'), + no_security_groups=dict(default=False, type='bool'), + allowed_address_pairs=dict(type='list', default=None, elements='dict'), + extra_dhcp_opts=dict(type='list', default=None, elements='dict'), + device_owner=dict(default=None), + device_id=dict(default=None), + state=dict(default='present', choices=['absent', 'present']), + vnic_type=dict(default=None, + choices=['normal', 'direct', 'direct-physical', + 'macvtap', 'baremetal', 'virtio-forwarder']), + port_security_enabled=dict(default=None, type='bool') + ) + + module_kwargs = openstack_module_kwargs( + mutually_exclusive=[ + ['no_security_groups', 'security_groups'], + ] + ) + + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + if not HAS_ORDEREDDICT: + module.fail_json(msg=missing_required_lib('ordereddict')) + + name = module.params['name'] + state = module.params['state'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + if module.params['security_groups']: + # translate security_groups to UUID's if names where provided + module.params['security_groups'] = [ + get_security_group_id(module, cloud, v) + for v in module.params['security_groups'] + ] + + # Neutron API accept 'binding:vnic_type' as an argument + # for the port type. + module.params['binding:vnic_type'] = module.params.pop('vnic_type') + + port = None + network_id = None + if name: + port = cloud.get_port(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, port, cloud)) + + changed = False + if state == 'present': + if not port: + network = module.params['network'] + if not network: + module.fail_json( + msg="Parameter 'network' is required in Port Create" + ) + port_kwargs = _compose_port_args(module, cloud) + network_object = cloud.get_network(network) + + if network_object: + network_id = network_object['id'] + else: + module.fail_json( + msg="Specified network was not found." + ) + + port = cloud.create_port(network_id, **port_kwargs) + changed = True + else: + if _needs_update(module, port, cloud): + port_kwargs = _compose_port_args(module, cloud) + port = cloud.update_port(port['id'], **port_kwargs) + changed = True + module.exit_json(changed=changed, id=port['id'], port=port) + + if state == 'absent': + if port: + cloud.delete_port(port['id']) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_port_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_port_info.py new file mode 100644 index 00000000..1db90ec8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_port_info.py @@ -0,0 +1,217 @@ +#!/usr/bin/python + +# Copyright (c) 2016 IBM +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +module: port_info +short_description: Retrieve information about ports within OpenStack. +author: OpenStack Ansible SIG +description: + - Retrieve information about ports from OpenStack. + - This module was called C(openstack.cloud.port_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.port_info) module no longer returns C(ansible_facts)! +options: + port: + description: + - Unique name or ID of a port. + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements + of this dictionary will be matched against the returned port + dictionaries. Matching is currently limited to strings within + the port dictionary, or strings within nested dictionaries. + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather information about all ports +- openstack.cloud.port_info: + cloud: mycloud + register: result + +- debug: + msg: "{{ result.openstack_ports }}" + +# Gather information about a single port +- openstack.cloud.port_info: + cloud: mycloud + port: 6140317d-e676-31e1-8a4a-b1913814a471 + +# Gather information about all ports that have device_id set to a specific value +# and with a status of ACTIVE. +- openstack.cloud.port_info: + cloud: mycloud + filters: + device_id: 1038a010-3a37-4a9d-82ea-652f1da36597 + status: ACTIVE +''' + +RETURN = ''' +openstack_ports: + description: List of port dictionaries. A subset of the dictionary keys + listed below may be returned, depending on your cloud provider. + returned: always, but can be null + type: complex + contains: + admin_state_up: + description: The administrative state of the router, which is + up (true) or down (false). + returned: success + type: bool + sample: true + allowed_address_pairs: + description: A set of zero or more allowed address pairs. An + address pair consists of an IP address and MAC address. + returned: success + type: list + sample: [] + "binding:host_id": + description: The UUID of the host where the port is allocated. + returned: success + type: str + sample: "b4bd682d-234a-4091-aa5b-4b025a6a7759" + "binding:profile": + description: A dictionary the enables the application running on + the host to pass and receive VIF port-specific + information to the plug-in. + returned: success + type: dict + sample: {} + "binding:vif_details": + description: A dictionary that enables the application to pass + information about functions that the Networking API + provides. + returned: success + type: dict + sample: {"port_filter": true} + "binding:vif_type": + description: The VIF type for the port. + returned: success + type: dict + sample: "ovs" + "binding:vnic_type": + description: The virtual network interface card (vNIC) type that is + bound to the neutron port. + returned: success + type: str + sample: "normal" + device_id: + description: The UUID of the device that uses this port. + returned: success + type: str + sample: "b4bd682d-234a-4091-aa5b-4b025a6a7759" + device_owner: + description: The UUID of the entity that uses this port. + returned: success + type: str + sample: "network:router_interface" + dns_assignment: + description: DNS assignment information. + returned: success + type: list + dns_name: + description: DNS name + returned: success + type: str + sample: "" + extra_dhcp_opts: + description: A set of zero or more extra DHCP option pairs. + An option pair consists of an option value and name. + returned: success + type: list + sample: [] + fixed_ips: + description: The IP addresses for the port. Includes the IP address + and UUID of the subnet. + returned: success + type: list + id: + description: The UUID of the port. + returned: success + type: str + sample: "3ec25c97-7052-4ab8-a8ba-92faf84148de" + ip_address: + description: The IP address. + returned: success + type: str + sample: "127.0.0.1" + mac_address: + description: The MAC address. + returned: success + type: str + sample: "00:00:5E:00:53:42" + name: + description: The port name. + returned: success + type: str + sample: "port_name" + network_id: + description: The UUID of the attached network. + returned: success + type: str + sample: "dd1ede4f-3952-4131-aab6-3b8902268c7d" + port_security_enabled: + description: The port security status. The status is enabled (true) or disabled (false). + returned: success + type: bool + sample: false + security_groups: + description: The UUIDs of any attached security groups. + returned: success + type: list + status: + description: The port status. + returned: success + type: str + sample: "ACTIVE" + tenant_id: + description: The UUID of the tenant who owns the network. + returned: success + type: str + sample: "51fce036d7984ba6af4f6c849f65ef00" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + argument_spec = openstack_full_argument_spec( + port=dict(required=False), + filters=dict(type='dict', required=False), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + is_old_facts = module._name == 'openstack.cloud.port_facts' + if is_old_facts: + module.deprecate("The 'openstack.cloud.port_facts' module has been renamed to 'openstack.cloud.port_info', " + "and the renamed one no longer returns ansible_facts", version='2.13') + + port = module.params.get('port') + filters = module.params.get('filters') + + sdk, cloud = openstack_cloud_from_module(module) + try: + ports = cloud.search_ports(port, filters) + if is_old_facts: + module.exit_json(changed=False, ansible_facts=dict( + openstack_ports=ports)) + else: + module.exit_json(changed=False, openstack_ports=ports) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_project.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_project.py new file mode 100644 index 00000000..5791a1d7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_project.py @@ -0,0 +1,232 @@ +#!/usr/bin/python +# Copyright (c) 2015 IBM Corporation +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: project +short_description: Manage OpenStack Projects +author: OpenStack Ansible SIG +description: + - Manage OpenStack Projects. Projects can be created, + updated or deleted using this module. A project will be updated + if I(name) matches an existing project and I(state) is present. + The value for I(name) cannot be updated without deleting and + re-creating the project. +options: + name: + description: + - Name for the project + required: true + type: str + description: + description: + - Description for the project + type: str + domain_id: + description: + - Domain id to create the project in if the cloud supports domains. + aliases: ['domain'] + type: str + enabled: + description: + - Is the project enabled + type: bool + default: 'yes' + properties: + description: + - Additional properties to be associated with this project. Requires + openstacksdk>0.45. + type: dict + default: {} + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a project +- openstack.cloud.project: + cloud: mycloud + endpoint_type: admin + state: present + name: demoproject + description: demodescription + domain_id: demoid + enabled: True + properties: + internal_alias: demo_project + +# Delete a project +- openstack.cloud.project: + cloud: mycloud + endpoint_type: admin + state: absent + name: demoproject +''' + + +RETURN = ''' +project: + description: Dictionary describing the project. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Project ID + type: str + sample: "f59382db809c43139982ca4189404650" + name: + description: Project name + type: str + sample: "demoproject" + description: + description: Project description + type: str + sample: "demodescription" + enabled: + description: Boolean to indicate if project is enabled + type: bool + sample: True +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _needs_update(module, project): + keys = ('description', 'enabled') + for key in keys: + if module.params[key] is not None and module.params[key] != project.get(key): + return True + + properties = module.params['properties'] + if properties: + project_properties = project.get('properties') + for k, v in properties.items(): + if v is not None and v != project_properties[k]: + return True + + return False + + +def _system_state_change(module, project): + state = module.params['state'] + if state == 'present': + if project is None: + changed = True + else: + if _needs_update(module, project): + changed = True + else: + changed = False + + elif state == 'absent': + if project is None: + changed = False + else: + changed = True + + return changed + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + description=dict(required=False, default=None), + domain_id=dict(required=False, default=None, aliases=['domain']), + properties=dict(type='dict', default={}), + enabled=dict(default=True, type='bool'), + state=dict(default='present', choices=['absent', 'present']) + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params['name'] + description = module.params['description'] + domain = module.params.get('domain_id') + enabled = module.params['enabled'] + properties = module.params['properties'] + state = module.params['state'] + + min_version = None + + if properties: + min_version = '0.45.1' + + sdk, cloud = openstack_cloud_from_module(module, min_version) + try: + if domain: + try: + # We assume admin is passing domain id + dom = cloud.get_domain(domain)['id'] + domain = dom + except Exception: + # If we fail, maybe admin is passing a domain name. + # Note that domains have unique names, just like id. + try: + dom = cloud.search_domains(filters={'name': domain})[0]['id'] + domain = dom + except Exception: + # Ok, let's hope the user is non-admin and passing a sane id + pass + + if domain: + project = cloud.get_project(name, domain_id=domain) + else: + project = cloud.get_project(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, project)) + + if state == 'present': + if project is None: + project = cloud.create_project( + name=name, description=description, + domain_id=domain, + enabled=enabled) + changed = True + + project = cloud.update_project( + project['id'], description=description, + enabled=enabled, **properties) + else: + if _needs_update(module, project): + project = cloud.update_project( + project['id'], description=description, + enabled=enabled, **properties) + changed = True + else: + changed = False + module.exit_json(changed=changed, project=project) + + elif state == 'absent': + if project is None: + changed = False + else: + cloud.delete_project(project['id']) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=e.message, extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_project_access.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_project_access.py new file mode 100644 index 00000000..09b64e27 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_project_access.py @@ -0,0 +1,198 @@ +#!/usr/bin/python + +# This module 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 software 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 software. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: project_access +short_description: Manage OpenStack compute flavors access +author: OpenStack Ansible SIG +description: + - Add or remove flavor, volume_type or other resources access + from OpenStack. +options: + state: + description: + - Indicate desired state of the resource. + choices: ['present', 'absent'] + required: false + default: present + type: str + target_project_id: + description: + - Project id. + required: true + type: str + resource_type: + description: + - The resource type (eg. nova_flavor, cinder_volume_type). + required: true + type: str + resource_name: + description: + - The resource name (eg. tiny). + required: true + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: "Enable access to tiny flavor to your tenant." + openstack.cloud.project_access: + cloud: mycloud + state: present + target_project_id: f0f1f2f3f4f5f67f8f9e0e1 + resource_name: tiny + resource_type: nova_flavor + + +- name: "Disable access to the given flavor to project" + openstack.cloud.project_access: + cloud: mycloud + state: absent + target_project_id: f0f1f2f3f4f5f67f8f9e0e1 + resource_name: tiny + resource_type: nova_flavor +''' + +RETURN = ''' +flavor: + description: Dictionary describing the flavor. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Flavor ID. + returned: success + type: str + sample: "515256b8-7027-4d73-aa54-4e30a4a4a339" + name: + description: Flavor name. + returned: success + type: str + sample: "tiny" + +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + argument_spec = openstack_full_argument_spec( + state=dict(required=False, default='present', + choices=['absent', 'present']), + + target_project_id=dict(required=True, type='str'), + resource_type=dict(required=True, type='str'), + resource_name=dict(required=True, type='str'), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + required_if=[ + ('state', 'present', ['target_project_id']) + ], + **module_kwargs) + + sdk, cloud = openstack_cloud_from_module(module) + + state = module.params['state'] + resource_name = module.params['resource_name'] + resource_type = module.params['resource_type'] + target_project_id = module.params['target_project_id'] + + try: + if resource_type == 'nova_flavor': + # returns Munch({'NAME_ATTR': 'name', + # 'tenant_id': u'37e55da59ec842649d84230f3a24eed5', + # 'HUMAN_ID': False, + # 'flavor_id': u'6d4d37b9-0480-4a8c-b8c9-f77deaad73f9', + # 'request_ids': [], 'human_id': None}), + _get_resource = cloud.get_flavor + _list_resource_access = cloud.list_flavor_access + _add_resource_access = cloud.add_flavor_access + _remove_resource_access = cloud.remove_flavor_access + elif resource_type == 'cinder_volume_type': + # returns [Munch({ + # 'project_id': u'178cdb9955b047eea7afbe582038dc94', + # 'properties': {'request_ids': [], 'NAME_ATTR': 'name', + # 'human_id': None, + # 'HUMAN_ID': False}, + # 'id': u'd5573023-b290-42c8-b232-7c5ca493667f'}), + _get_resource = cloud.get_volume_type + _list_resource_access = cloud.get_volume_type_access + _add_resource_access = cloud.add_volume_type_access + _remove_resource_access = cloud.remove_volume_type_access + else: + module.exit_json(changed=False, + resource_name=resource_name, + resource_type=resource_type, + error="Not implemented.") + + resource = _get_resource(resource_name) + if not resource: + module.exit_json(changed=False, + resource_name=resource_name, + resource_type=resource_type, + error="Not found.") + resource_id = getattr(resource, 'id', resource['id']) + # _list_resource_access returns a list of dicts containing 'project_id' + acls = _list_resource_access(resource_id) + + if not all(acl.get('project_id') for acl in acls): + module.exit_json(changed=False, + resource_name=resource_name, + resource_type=resource_type, + error="Missing project_id in resource output.") + allowed_tenants = [acl['project_id'] for acl in acls] + + changed_access = any(( + state == 'present' and target_project_id not in allowed_tenants, + state == 'absent' and target_project_id in allowed_tenants + )) + if module.check_mode or not changed_access: + module.exit_json(changed=changed_access, + resource=resource, + id=resource_id) + + if state == 'present': + _add_resource_access( + resource_id, target_project_id + ) + elif state == 'absent': + _remove_resource_access( + resource_id, target_project_id + ) + + module.exit_json(changed=True, + resource=resource, + id=resource_id) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), **module.params) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_project_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_project_info.py new file mode 100644 index 00000000..421dc36e --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_project_info.py @@ -0,0 +1,157 @@ +#!/usr/bin/python +# Copyright (c) 2016 Hewlett-Packard Enterprise Corporation +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: project_info +short_description: Retrieve information about one or more OpenStack projects +author: OpenStack Ansible SIG +description: + - Retrieve information about a one or more OpenStack projects + - This module was called C(openstack.cloud.project_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.project_info) module no longer returns C(ansible_facts)! +options: + name: + description: + - Name or ID of the project + type: str + domain: + description: + - Name or ID of the domain containing the project if the cloud supports domains + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather information about previously created projects +- openstack.cloud.project_info: + cloud: awesomecloud + register: result +- debug: + msg: "{{ result.openstack_projects }}" + +# Gather information about a previously created project by name +- openstack.cloud.project_info: + cloud: awesomecloud + name: demoproject + register: result +- debug: + msg: "{{ result.openstack_projects }}" + +# Gather information about a previously created project in a specific domain +- openstack.cloud.project_info: + cloud: awesomecloud + name: demoproject + domain: admindomain + register: result +- debug: + msg: "{{ result.openstack_projects }}" + +# Gather information about a previously created project in a specific domain with filter +- openstack.cloud.project_info: + cloud: awesomecloud + name: demoproject + domain: admindomain + filters: + enabled: False + register: result +- debug: + msg: "{{ result.openstack_projects }}" +''' + + +RETURN = ''' +openstack_projects: + description: has all the OpenStack information about projects + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the project. + returned: success + type: str + description: + description: Description of the project + returned: success + type: str + enabled: + description: Flag to indicate if the project is enabled + returned: success + type: bool + domain_id: + description: Domain ID containing the project (keystone v3 clouds only) + returned: success + type: bool +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec, openstack_cloud_from_module + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + domain=dict(required=False, default=None), + filters=dict(required=False, type='dict', default=None), + ) + + module = AnsibleModule(argument_spec) + is_old_facts = module._name == 'openstack.cloud.project_facts' + if is_old_facts: + module.deprecate("The 'openstack.cloud.project_facts' module has been renamed to 'openstack.cloud.project_info', " + "and the renamed one no longer returns ansible_facts", version='2.13') + + sdk, opcloud = openstack_cloud_from_module(module) + try: + name = module.params['name'] + domain = module.params['domain'] + filters = module.params['filters'] + + if domain: + try: + # We assume admin is passing domain id + dom = opcloud.get_domain(domain)['id'] + domain = dom + except Exception: + # If we fail, maybe admin is passing a domain name. + # Note that domains have unique names, just like id. + dom = opcloud.search_domains(filters={'name': domain}) + if dom: + domain = dom[0]['id'] + else: + module.fail_json(msg='Domain name or ID does not exist') + + if not filters: + filters = {} + + filters['domain_id'] = domain + + projects = opcloud.search_projects(name, filters) + if is_old_facts: + module.exit_json(changed=False, ansible_facts=dict( + openstack_projects=projects)) + else: + module.exit_json(changed=False, openstack_projects=projects) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_quota.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_quota.py new file mode 100644 index 00000000..25c28977 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_quota.py @@ -0,0 +1,496 @@ +#!/usr/bin/python +# Copyright (c) 2016 Pason System Corporation +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: quota +short_description: Manage OpenStack Quotas +author: OpenStack Ansible SIG +description: + - Manage OpenStack Quotas. Quotas can be created, + updated or deleted using this module. A quota will be updated + if matches an existing project and is present. +options: + name: + description: + - Name of the OpenStack Project to manage. + required: true + type: str + state: + description: + - A value of present sets the quota and a value of absent resets the quota to system defaults. + default: present + type: str + choices: ['absent', 'present'] + backup_gigabytes: + description: Maximum size of backups in GB's. + type: int + backups: + description: Maximum number of backups allowed. + type: int + cores: + description: Maximum number of CPU's per project. + type: int + fixed_ips: + description: Number of fixed IP's to allow. + type: int + floating_ips: + description: Number of floating IP's to allow in Compute. + aliases: ['compute_floating_ips'] + type: int + floatingip: + description: Number of floating IP's to allow in Network. + aliases: ['network_floating_ips'] + type: int + gigabytes: + description: Maximum volume storage allowed for project. + type: int + gigabytes_types: + description: + - Per driver volume storage quotas. Keys should be + prefixed with C(gigabytes_) values should be ints. + type: dict + injected_file_size: + description: Maximum file size in bytes. + type: int + injected_files: + description: Number of injected files to allow. + type: int + injected_path_size: + description: Maximum path size. + type: int + instances: + description: Maximum number of instances allowed. + type: int + key_pairs: + description: Number of key pairs to allow. + type: int + loadbalancer: + description: Number of load balancers to allow. + type: int + network: + description: Number of networks to allow. + type: int + per_volume_gigabytes: + description: Maximum size in GB's of individual volumes. + type: int + pool: + description: Number of load balancer pools to allow. + type: int + port: + description: Number of Network ports to allow, this needs to be greater than the instances limit. + type: int + properties: + description: Number of properties to allow. + type: int + ram: + description: Maximum amount of ram in MB to allow. + type: int + rbac_policy: + description: Number of policies to allow. + type: int + router: + description: Number of routers to allow. + type: int + security_group_rule: + description: Number of rules per security group to allow. + type: int + security_group: + description: Number of security groups to allow. + type: int + server_group_members: + description: Number of server group members to allow. + type: int + server_groups: + description: Number of server groups to allow. + type: int + snapshots: + description: Number of snapshots to allow. + type: int + snapshots_types: + description: + - Per-driver volume snapshot quotas. Keys should be + prefixed with C(snapshots_) values should be ints. + type: dict + subnet: + description: Number of subnets to allow. + type: int + subnetpool: + description: Number of subnet pools to allow. + type: int + volumes: + description: Number of volumes to allow. + type: int + volumes_types: + description: + - Per-driver volume count quotas. Keys should be + prefixed with C(gigabytes_) values should be ints. + type: dict + project: + description: Unused, kept for compatability + type: int + +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.13.0" + - "keystoneauth1 >= 3.4.0" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# List a Project Quota +- openstack.cloud.quota: + cloud: mycloud + name: demoproject + +# Set a Project back to the defaults +- openstack.cloud.quota: + cloud: mycloud + name: demoproject + state: absent + +# Update a Project Quota for cores +- openstack.cloud.quota: + cloud: mycloud + name: demoproject + cores: 100 + +# Update a Project Quota +- openstack.cloud.quota: + name: demoproject + cores: 1000 + volumes: 20 + volumes_type: + - volume_lvm: 10 + +# Complete example based on list of projects +- name: Update quotas + openstack.cloud.quota: + name: "{{ item.name }}" + backup_gigabytes: "{{ item.backup_gigabytes }}" + backups: "{{ item.backups }}" + cores: "{{ item.cores }}" + fixed_ips: "{{ item.fixed_ips }}" + floating_ips: "{{ item.floating_ips }}" + floatingip: "{{ item.floatingip }}" + gigabytes: "{{ item.gigabytes }}" + injected_file_size: "{{ item.injected_file_size }}" + injected_files: "{{ item.injected_files }}" + injected_path_size: "{{ item.injected_path_size }}" + instances: "{{ item.instances }}" + key_pairs: "{{ item.key_pairs }}" + loadbalancer: "{{ item.loadbalancer }}" + per_volume_gigabytes: "{{ item.per_volume_gigabytes }}" + pool: "{{ item.pool }}" + port: "{{ item.port }}" + properties: "{{ item.properties }}" + ram: "{{ item.ram }}" + security_group_rule: "{{ item.security_group_rule }}" + security_group: "{{ item.security_group }}" + server_group_members: "{{ item.server_group_members }}" + server_groups: "{{ item.server_groups }}" + snapshots: "{{ item.snapshots }}" + volumes: "{{ item.volumes }}" + volumes_types: + volumes_lvm: "{{ item.volumes_lvm }}" + snapshots_types: + snapshots_lvm: "{{ item.snapshots_lvm }}" + gigabytes_types: + gigabytes_lvm: "{{ item.gigabytes_lvm }}" + with_items: + - "{{ projects }}" + when: item.state == "present" +''' + +RETURN = ''' +openstack_quotas: + description: Dictionary describing the project quota. + returned: Regardless if changes where made or not + type: dict + sample: + openstack_quotas: { + compute: { + cores: 150, + fixed_ips: -1, + floating_ips: 10, + injected_file_content_bytes: 10240, + injected_file_path_bytes: 255, + injected_files: 5, + instances: 100, + key_pairs: 100, + metadata_items: 128, + ram: 153600, + security_group_rules: 20, + security_groups: 10, + server_group_members: 10, + server_groups: 10 + }, + network: { + floatingip: 50, + loadbalancer: 10, + network: 10, + pool: 10, + port: 160, + rbac_policy: 10, + router: 10, + security_group: 10, + security_group_rule: 100, + subnet: 10, + subnetpool: -1 + }, + volume: { + backup_gigabytes: 1000, + backups: 10, + gigabytes: 1000, + gigabytes_lvm: -1, + per_volume_gigabytes: -1, + snapshots: 10, + snapshots_lvm: -1, + volumes: 10, + volumes_lvm: -1 + } + } + +''' + +import traceback + +KEYSTONEAUTH1_IMP_ERR = None +try: + from keystoneauth1 import exceptions as ksa_exceptions + HAS_KEYSTONEAUTH1 = True +except ImportError: + KEYSTONEAUTH1_IMP_ERR = traceback.format_exc() + HAS_KEYSTONEAUTH1 = False + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_full_argument_spec, + openstack_cloud_from_module, +) + + +def _get_volume_quotas(cloud, project): + + return cloud.get_volume_quotas(project) + + +def _get_network_quotas(cloud, project): + + return cloud.get_network_quotas(project) + + +def _get_compute_quotas(cloud, project): + + return cloud.get_compute_quotas(project) + + +def _get_quotas(sdk, module, cloud, project): + + quota = {} + try: + quota['volume'] = _get_volume_quotas(cloud, project) + except ksa_exceptions.EndpointNotFound: + module.warn("No public endpoint for volumev2 service was found. Ignoring volume quotas.") + + try: + quota['network'] = _get_network_quotas(cloud, project) + except ksa_exceptions.EndpointNotFound: + module.warn("No public endpoint for network service was found. Ignoring network quotas.") + + quota['compute'] = _get_compute_quotas(cloud, project) + + for quota_type in quota.keys(): + quota[quota_type] = _scrub_results(quota[quota_type]) + + return quota + + +def _scrub_results(quota): + + filter_attr = [ + 'HUMAN_ID', + 'NAME_ATTR', + 'human_id', + 'request_ids', + 'x_openstack_request_ids', + ] + + for attr in filter_attr: + if attr in quota: + del quota[attr] + + return quota + + +def _system_state_change_details(module, project_quota_output): + + quota_change_request = {} + changes_required = False + + for quota_type in project_quota_output.keys(): + for quota_option in project_quota_output[quota_type].keys(): + if quota_option in module.params and module.params[quota_option] is not None: + if project_quota_output[quota_type][quota_option] != module.params[quota_option]: + changes_required = True + + if quota_type not in quota_change_request: + quota_change_request[quota_type] = {} + + quota_change_request[quota_type][quota_option] = module.params[quota_option] + + return (changes_required, quota_change_request) + + +def _system_state_change(module, project_quota_output): + """ + Determine if changes are required to the current project quota. + + This is done by comparing the current project_quota_output against + the desired quota settings set on the module params. + """ + + changes_required, quota_change_request = _system_state_change_details( + module, + project_quota_output + ) + + if changes_required: + return True + else: + return False + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + backup_gigabytes=dict(required=False, type='int', default=None), + backups=dict(required=False, type='int', default=None), + cores=dict(required=False, type='int', default=None), + fixed_ips=dict(required=False, type='int', default=None), + floating_ips=dict(required=False, type='int', default=None, aliases=['compute_floating_ips']), + floatingip=dict(required=False, type='int', default=None, aliases=['network_floating_ips']), + gigabytes=dict(required=False, type='int', default=None), + gigabytes_types=dict(required=False, type='dict', default={}), + injected_file_size=dict(required=False, type='int', default=None), + injected_files=dict(required=False, type='int', default=None), + injected_path_size=dict(required=False, type='int', default=None), + instances=dict(required=False, type='int', default=None), + key_pairs=dict(required=False, type='int', default=None), + loadbalancer=dict(required=False, type='int', default=None), + network=dict(required=False, type='int', default=None), + per_volume_gigabytes=dict(required=False, type='int', default=None), + pool=dict(required=False, type='int', default=None), + port=dict(required=False, type='int', default=None), + project=dict(required=False, type='int', default=None), + properties=dict(required=False, type='int', default=None), + ram=dict(required=False, type='int', default=None), + rbac_policy=dict(required=False, type='int', default=None), + router=dict(required=False, type='int', default=None), + security_group_rule=dict(required=False, type='int', default=None), + security_group=dict(required=False, type='int', default=None), + server_group_members=dict(required=False, type='int', default=None), + server_groups=dict(required=False, type='int', default=None), + snapshots=dict(required=False, type='int', default=None), + snapshots_types=dict(required=False, type='dict', default={}), + subnet=dict(required=False, type='int', default=None), + subnetpool=dict(required=False, type='int', default=None), + volumes=dict(required=False, type='int', default=None), + volumes_types=dict(required=False, type='dict', default={}) + ) + + module = AnsibleModule(argument_spec, + supports_check_mode=True + ) + + if not HAS_KEYSTONEAUTH1: + module.fail_json(msg=missing_required_lib("keystoneauth1"), exception=KEYSTONEAUTH1_IMP_ERR) + + sdk, cloud = openstack_cloud_from_module(module) + try: + cloud_params = dict(module.params) + + # In order to handle the different volume types we update module params after. + dynamic_types = [ + 'gigabytes_types', + 'snapshots_types', + 'volumes_types', + ] + + for dynamic_type in dynamic_types: + for k, v in module.params[dynamic_type].items(): + module.params[k] = int(v) + + # Get current quota values + project_quota_output = _get_quotas( + sdk, module, cloud, cloud_params['name']) + changes_required = False + + if module.params['state'] == "absent": + # If a quota state is set to absent we should assume there will be changes. + # The default quota values are not accessible so we can not determine if + # no changes will occur or not. + if module.check_mode: + module.exit_json(changed=True) + + # Calling delete_network_quotas when a quota has not been set results + # in an error, according to the sdk docs it should return the + # current quota. + # The following error string is returned: + # network client call failed: Quota for tenant 69dd91d217e949f1a0b35a4b901741dc could not be found. + neutron_msg1 = "network client call failed: Quota for tenant" + neutron_msg2 = "could not be found" + + for quota_type in project_quota_output.keys(): + quota_call = getattr(cloud, 'delete_%s_quotas' % (quota_type)) + try: + quota_call(cloud_params['name']) + except sdk.exceptions.OpenStackCloudException as e: + error_msg = str(e) + if error_msg.find(neutron_msg1) > -1 and error_msg.find(neutron_msg2) > -1: + pass + else: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + project_quota_output = _get_quotas( + sdk, module, cloud, cloud_params['name']) + changes_required = True + + elif module.params['state'] == "present": + if module.check_mode: + module.exit_json(changed=_system_state_change(module, project_quota_output)) + + changes_required, quota_change_request = _system_state_change_details( + module, + project_quota_output + ) + + if changes_required: + for quota_type in quota_change_request.keys(): + quota_call = getattr(cloud, 'set_%s_quotas' % (quota_type)) + quota_call(cloud_params['name'], **quota_change_request[quota_type]) + + # Get quota state post changes for validation + project_quota_update = _get_quotas( + sdk, module, cloud, cloud_params['name']) + + if project_quota_output == project_quota_update: + module.fail_json(msg='Could not apply quota update') + + project_quota_output = project_quota_update + + module.exit_json(changed=changes_required, + openstack_quotas=project_quota_output + ) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_recordset.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_recordset.py new file mode 100644 index 00000000..5f89a281 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_recordset.py @@ -0,0 +1,237 @@ +#!/usr/bin/python +# Copyright (c) 2016 Hewlett-Packard Enterprise +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: recordset +short_description: Manage OpenStack DNS recordsets +author: OpenStack Ansible SIG +description: + - Manage OpenStack DNS recordsets. Recordsets can be created, deleted or + updated. Only the I(records), I(description), and I(ttl) values + can be updated. +options: + zone: + description: + - Zone managing the recordset + required: true + type: str + name: + description: + - Name of the recordset. It must be ended with name of dns zone. + required: true + type: str + recordset_type: + description: + - Recordset type + - Required when I(state=present). + choices: ['a', 'aaaa', 'mx', 'cname', 'txt', 'ns', 'srv', 'ptr', 'caa'] + type: str + records: + description: + - List of recordset definitions. + - Required when I(state=present). + type: list + elements: str + description: + description: + - Description of the recordset + type: str + ttl: + description: + - TTL (Time To Live) value in seconds + type: int + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a recordset named "www.example.net." +- openstack.cloud.recordset: + cloud: mycloud + state: present + zone: example.net. + name: www.example.net. + recordset_type: "a" + records: ['10.1.1.1'] + description: test recordset + ttl: 3600 + +# Update the TTL on existing "www.example.net." recordset +- openstack.cloud.recordset: + cloud: mycloud + state: present + zone: example.net. + name: www.example.net. + ttl: 7200 + +# Delete recordset named "www.example.net." +- openstack.cloud.recordset: + cloud: mycloud + state: absent + zone: example.net. + name: www.example.net. +''' + +RETURN = ''' +recordset: + description: Dictionary describing the recordset. + returned: On success when I(state) is 'present'. + type: complex + contains: + id: + description: Unique recordset ID + type: str + sample: "c1c530a3-3619-46f3-b0f6-236927b2618c" + name: + description: Recordset name + type: str + sample: "www.example.net." + zone_id: + description: Zone id + type: str + sample: 9508e177-41d8-434e-962c-6fe6ca880af7 + type: + description: Recordset type + type: str + sample: "A" + description: + description: Recordset description + type: str + sample: "Test description" + ttl: + description: Zone TTL value + type: int + sample: 3600 + records: + description: Recordset records + type: list + sample: ['10.0.0.1'] +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(state, records, description, ttl, recordset): + if state == 'present': + if recordset is None: + return True + if records is not None and recordset['records'] != records: + return True + if description is not None and recordset['description'] != description: + return True + if ttl is not None and recordset['ttl'] != ttl: + return True + if state == 'absent' and recordset: + return True + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + zone=dict(required=True), + name=dict(required=True), + recordset_type=dict(required=False, choices=['a', 'aaaa', 'mx', 'cname', 'txt', 'ns', 'srv', 'ptr', 'caa']), + records=dict(required=False, type='list', elements='str'), + description=dict(required=False, default=None), + ttl=dict(required=False, type='int'), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + required_if=[ + ('state', 'present', + ['recordset_type', 'records'])], + supports_check_mode=True, + **module_kwargs) + + module.module_min_sdk_version = '0.28.0' + zone = module.params.get('zone') + name = module.params.get('name') + state = module.params.get('state') + + sdk, cloud = openstack_cloud_from_module(module) + recordsets = cloud.search_recordsets(zone, name_or_id=name) + + if recordsets: + recordset = recordsets[0] + try: + recordset_id = recordset['id'] + except KeyError as e: + module.fail_json(msg=str(e)) + else: + # recordsets is filtered by type and should never be more than 1 return + recordset = None + + if state == 'present': + recordset_type = module.params.get('recordset_type').upper() + records = module.params.get('records') + description = module.params.get('description') + ttl = module.params.get('ttl') + + kwargs = {} + if description: + kwargs['description'] = description + kwargs['records'] = records + + if module.check_mode: + module.exit_json(changed=_system_state_change(state, + records, description, + ttl, recordset)) + + if recordset is None: + if ttl: + kwargs['ttl'] = ttl + else: + kwargs['ttl'] = 300 + + recordset = cloud.create_recordset( + zone=zone, name=name, recordset_type=recordset_type, + **kwargs) + changed = True + else: + + if ttl: + kwargs['ttl'] = ttl + + pre_update_recordset = recordset + changed = _system_state_change(state, records, + description, ttl, + pre_update_recordset) + if changed: + recordset = cloud.update_recordset( + zone=zone, name_or_id=recordset_id, **kwargs) + + module.exit_json(changed=changed, recordset=recordset) + + elif state == 'absent': + if module.check_mode: + module.exit_json(changed=_system_state_change(state, + None, None, + None, recordset)) + + if recordset is None: + changed = False + else: + cloud.delete_recordset(zone, recordset_id) + changed = True + module.exit_json(changed=changed) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_router.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_router.py new file mode 100644 index 00000000..a90a06b3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_router.py @@ -0,0 +1,484 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: router +short_description: Create or delete routers from OpenStack +author: OpenStack Ansible SIG +description: + - Create or Delete routers from OpenStack. Although Neutron allows + routers to share the same name, this module enforces name uniqueness + to be more user friendly. +options: + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + type: str + name: + description: + - Name to be give to the router + required: true + type: str + admin_state_up: + description: + - Desired admin state of the created or existing router. + type: bool + default: 'yes' + enable_snat: + description: + - Enable Source NAT (SNAT) attribute. + type: bool + network: + description: + - Unique name or ID of the external gateway network. + - required I(interfaces) or I(enable_snat) are provided. + type: str + project: + description: + - Unique name or ID of the project. + type: str + external_fixed_ips: + description: + - The IP address parameters for the external gateway network. Each + is a dictionary with the subnet name or ID (subnet) and the IP + address to assign on the subnet (ip). If no IP is specified, + one is automatically assigned from that subnet. + type: list + elements: dict + suboptions: + ip: + description: The fixed IP address to attempt to allocate. + required: true + type: str + subnet: + description: The subnet to attach the IP address to. + type: str + interfaces: + description: + - List of subnets to attach to the router internal interface. Default + gateway associated with the subnet will be automatically attached + with the router's internal interface. + In order to provide an ip address different from the default + gateway,parameters are passed as dictionary with keys as network + name or ID (I(net)), subnet name or ID (I(subnet)) and the IP of + port (I(portip)) from the network. + User defined portip is often required when a multiple router need + to be connected to a single subnet for which the default gateway has + been already used. + type: list + elements: raw +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a simple router, not attached to a gateway or subnets. +- openstack.cloud.router: + cloud: mycloud + state: present + name: simple_router + +# Create a simple router, not attached to a gateway or subnets for a given project. +- openstack.cloud.router: + cloud: mycloud + state: present + name: simple_router + project: myproj + +# Creates a router attached to ext_network1 on an IPv4 subnet and one +# internal subnet interface. +- openstack.cloud.router: + cloud: mycloud + state: present + name: router1 + network: ext_network1 + external_fixed_ips: + - subnet: public-subnet + ip: 172.24.4.2 + interfaces: + - private-subnet + +# Create another router with two internal subnet interfaces.One with user defined port +# ip and another with default gateway. +- openstack.cloud.router: + cloud: mycloud + state: present + name: router2 + network: ext_network1 + interfaces: + - net: private-net + subnet: private-subnet + portip: 10.1.1.10 + - project-subnet + +# Create another router with two internal subnet interface.One with user defined port +# ip and and another with default gateway. +- openstack.cloud.router: + cloud: mycloud + state: present + name: router2 + network: ext_network1 + interfaces: + - net: private-net + subnet: private-subnet + portip: 10.1.1.10 + - project-subnet + +# Create another router with two internal subnet interface. one with user defined port +# ip and and another with default gateway. +- openstack.cloud.router: + cloud: mycloud + state: present + name: router2 + network: ext_network1 + interfaces: + - net: private-net + subnet: private-subnet + portip: 10.1.1.10 + - project-subnet + +# Update existing router1 external gateway to include the IPv6 subnet. +# Note that since 'interfaces' is not provided, any existing internal +# interfaces on an existing router will be left intact. +- openstack.cloud.router: + cloud: mycloud + state: present + name: router1 + network: ext_network1 + external_fixed_ips: + - subnet: public-subnet + ip: 172.24.4.2 + - subnet: ipv6-public-subnet + ip: 2001:db8::3 + +# Delete router1 +- openstack.cloud.router: + cloud: mycloud + state: absent + name: router1 +''' + +RETURN = ''' +router: + description: Dictionary describing the router. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Router ID. + type: str + sample: "474acfe5-be34-494c-b339-50f06aa143e4" + name: + description: Router name. + type: str + sample: "router1" + admin_state_up: + description: Administrative state of the router. + type: bool + sample: true + status: + description: The router status. + type: str + sample: "ACTIVE" + tenant_id: + description: The tenant ID. + type: str + sample: "861174b82b43463c9edc5202aadc60ef" + external_gateway_info: + description: The external gateway parameters. + type: dict + sample: { + "enable_snat": true, + "external_fixed_ips": [ + { + "ip_address": "10.6.6.99", + "subnet_id": "4272cb52-a456-4c20-8f3c-c26024ecfa81" + } + ] + } + routes: + description: The extra routes configuration for L3 router. + type: list +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +ROUTER_INTERFACE_OWNERS = set([ + 'network:router_interface', + 'network:router_interface_distributed', + 'network:ha_router_replicated_interface' +]) + + +class RouterModule(OpenStackModule): + argument_spec = dict( + state=dict(default='present', choices=['absent', 'present']), + name=dict(required=True), + admin_state_up=dict(type='bool', default=True), + enable_snat=dict(type='bool'), + network=dict(default=None), + interfaces=dict(type='list', default=None, elements='raw'), + external_fixed_ips=dict(type='list', default=None, elements='dict'), + project=dict(default=None) + ) + + def _router_internal_interfaces(self, router): + for port in self.conn.list_router_interfaces(router, 'internal'): + if port['device_owner'] in ROUTER_INTERFACE_OWNERS: + yield port + + def _needs_update(self, router, network, internal_subnet_ids, internal_port_ids, filters=None): + """Decide if the given router needs an update. + """ + if router['admin_state_up'] != self.params['admin_state_up']: + return True + if router['external_gateway_info']: + # check if enable_snat is set in module params + if self.params['enable_snat'] is not None: + if router['external_gateway_info'].get('enable_snat', True) != self.params['enable_snat']: + return True + if network: + if not router['external_gateway_info']: + return True + elif router['external_gateway_info']['network_id'] != network['id']: + return True + + # check external interfaces + if self.params['external_fixed_ips']: + for new_iface in self.params['external_fixed_ips']: + subnet = self.conn.get_subnet(new_iface['subnet'], filters) + exists = False + + # compare the requested interface with existing, looking for an existing match + for existing_iface in router['external_gateway_info']['external_fixed_ips']: + if existing_iface['subnet_id'] == subnet['id']: + if 'ip' in new_iface: + if existing_iface['ip_address'] == new_iface['ip']: + # both subnet id and ip address match + exists = True + break + else: + # only the subnet was given, so ip doesn't matter + exists = True + break + + # this interface isn't present on the existing router + if not exists: + return True + + # check internal interfaces + if self.params['interfaces']: + existing_subnet_ids = [] + for port in self._router_internal_interfaces(router): + if 'fixed_ips' in port: + for fixed_ip in port['fixed_ips']: + existing_subnet_ids.append(fixed_ip['subnet_id']) + + for iface in self.params['interfaces']: + if isinstance(iface, dict): + for p_id in internal_port_ids: + p = self.conn.get_port(name_or_id=p_id) + if 'fixed_ips' in p: + for fip in p['fixed_ips']: + internal_subnet_ids.append(fip['subnet_id']) + + if set(internal_subnet_ids) != set(existing_subnet_ids): + return True + + return False + + def _system_state_change(self, router, network, internal_ids, internal_portids, filters=None): + """Check if the system state would be changed.""" + state = self.params['state'] + if state == 'absent' and router: + return True + if state == 'present': + if not router: + return True + return self._needs_update(router, network, internal_ids, internal_portids, filters) + return False + + def _build_kwargs(self, router, network): + kwargs = { + 'admin_state_up': self.params['admin_state_up'], + } + + if router: + kwargs['name_or_id'] = router['id'] + else: + kwargs['name'] = self.params['name'] + + if network: + kwargs['ext_gateway_net_id'] = network['id'] + # can't send enable_snat unless we have a network + if self.params.get('enable_snat') is not None: + kwargs['enable_snat'] = self.params['enable_snat'] + + if self.params['external_fixed_ips']: + kwargs['ext_fixed_ips'] = [] + for iface in self.params['external_fixed_ips']: + subnet = self.conn.get_subnet(iface['subnet']) + d = {'subnet_id': subnet['id']} + if 'ip' in iface: + d['ip_address'] = iface['ip'] + kwargs['ext_fixed_ips'].append(d) + + return kwargs + + def _validate_subnets(self, filters=None): + external_subnet_ids = [] + internal_subnet_ids = [] + internal_port_ids = [] + existing_port_ips = [] + if self.params['external_fixed_ips']: + for iface in self.params['external_fixed_ips']: + subnet = self.conn.get_subnet(iface['subnet']) + if not subnet: + self.fail_json(msg='subnet %s not found' % iface['subnet']) + external_subnet_ids.append(subnet['id']) + + if self.params['interfaces']: + for iface in self.params['interfaces']: + if isinstance(iface, str): + subnet = self.conn.get_subnet(iface, filters) + if not subnet: + self.fail(msg='subnet %s not found' % iface) + internal_subnet_ids.append(subnet['id']) + elif isinstance(iface, dict): + subnet = self.conn.get_subnet(iface['subnet'], filters) + if not subnet: + self.fail(msg='subnet %s not found' % iface['subnet']) + net = self.conn.get_network(iface['net']) + if not net: + self.fail(msg='net %s not found' % iface['net']) + if "portip" not in iface: + internal_subnet_ids.append(subnet['id']) + elif not iface['portip']: + self.fail(msg='put an ip in portip or remove it from list to assign default port to router') + else: + for existing_port in self.conn.list_ports(filters={'network_id': net.id}): + for fixed_ip in existing_port['fixed_ips']: + if iface['portip'] == fixed_ip['ip_address']: + internal_port_ids.append(existing_port.id) + existing_port_ips.append(fixed_ip['ip_address']) + if iface['portip'] not in existing_port_ips: + p = self.conn.create_port(network_id=net.id, fixed_ips=[ + { + 'ip_address': iface['portip'], + 'subnet_id': subnet.id + } + ]) + if p: + internal_port_ids.append(p.id) + + return external_subnet_ids, internal_subnet_ids, internal_port_ids + + def run(self): + + state = self.params['state'] + name = self.params['name'] + network = self.params['network'] + project = self.params['project'] + + if self.params['external_fixed_ips'] and not network: + self.fail_json(msg='network is required when supplying external_fixed_ips') + + if project is not None: + proj = self.conn.get_project(project) + if proj is None: + self.fail(msg='Project %s could not be found' % project) + project_id = proj['id'] + filters = {'tenant_id': project_id} + else: + project_id = None + filters = None + + router = self.conn.get_router(name, filters=filters) + net = None + if network: + net = self.conn.get_network(network) + if not net: + self.fail(msg='network %s not found' % network) + + # Validate and cache the subnet IDs so we can avoid duplicate checks + # and expensive API calls. + external_ids, subnet_internal_ids, internal_portids = self._validate_subnets(filters) + if self.ansible.check_mode: + self.exit_json( + changed=self._system_state_change(router, net, subnet_internal_ids, internal_portids, filters) + ) + + if state == 'present': + changed = False + + if not router: + kwargs = self._build_kwargs(router, net) + if project_id: + kwargs['project_id'] = project_id + router = self.conn.create_router(**kwargs) + for int_s_id in subnet_internal_ids: + self.conn.add_router_interface(router, subnet_id=int_s_id) + # add interface by port id as well + for int_p_id in internal_portids: + self.conn.add_router_interface(router, port_id=int_p_id) + changed = True + else: + if self._needs_update(router, net, subnet_internal_ids, internal_portids, filters): + kwargs = self._build_kwargs(router, net) + updated_router = self.conn.update_router(**kwargs) + + # Protect against update_router() not actually + # updating the router. + if not updated_router: + changed = False + + # On a router update, if any internal interfaces were supplied, + # just detach all existing internal interfaces and attach the new. + if internal_portids or subnet_internal_ids: + router = updated_router + ports = self._router_internal_interfaces(router) + for port in ports: + self.conn.remove_router_interface(router, port_id=port['id']) + if internal_portids: + external_ids, subnet_internal_ids, internal_portids = self._validate_subnets(filters) + for int_p_id in internal_portids: + self.conn.add_router_interface(router, port_id=int_p_id) + changed = True + if subnet_internal_ids: + for s_id in subnet_internal_ids: + self.conn.add_router_interface(router, subnet_id=s_id) + changed = True + + self.exit(changed=changed, router=router, id=router['id']) + + elif state == 'absent': + if not router: + self.exit(changed=False) + else: + # We need to detach all internal interfaces on a router before + # we will be allowed to delete it. + ports = self._router_internal_interfaces(router) + router_id = router['id'] + for port in ports: + self.conn.remove_router_interface(router, port_id=port['id']) + self.conn.delete_router(router_id) + self.exit_json(changed=True) + + +def main(): + module = RouterModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_routers_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_routers_info.py new file mode 100644 index 00000000..1eea10f0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_routers_info.py @@ -0,0 +1,190 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Bram Verschueren <verschueren.bram@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: routers_info +short_description: Retrieve information about one or more OpenStack routers. +author: OpenStack Ansible SIG +description: + - Retrieve information about one or more routers from OpenStack. +options: + name: + description: + - Name or ID of the router + required: false + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + required: false + type: dict + suboptions: + project_id: + description: + - Filter the list result by the ID of the project that owns the resource. + type: str + aliases: + - tenant_id + name: + description: + - Filter the list result by the human-readable name of the resource. + type: str + description: + description: + - Filter the list result by the human-readable description of the resource. + type: str + admin_state_up: + description: + - Filter the list result by the administrative state of the resource, which is up (true) or down (false). + type: bool + revision_number: + description: + - Filter the list result by the revision number of the resource. + type: int + tags: + description: + - A list of tags to filter the list result by. Resources that match all tags in this list will be returned. + type: list +requirements: + - "python >= 3.6" + - "openstacksdk" +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Gather information about routers + openstack.cloud.routers_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + register: result + +- name: Show openstack routers + debug: + msg: "{{ result.openstack_routers }}" + +- name: Gather information about a router by name + openstack.cloud.routers_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + name: router1 + register: result + +- name: Show openstack routers + debug: + msg: "{{ result.openstack_routers }}" + +- name: Gather information about a router with filter + openstack.cloud.routers_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + filters: + tenant_id: bc3ea709c96849d6b81f54640400a19f + register: result + +- name: Show openstack routers + debug: + msg: "{{ result.openstack_routers }}" +''' + +RETURN = ''' +openstack_routers: + description: has all the openstack information about the routers + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the router. + returned: success + type: str + status: + description: Router status. + returned: success + type: str + external_gateway_info: + description: The external gateway information of the router. + returned: success + type: dict + interfaces_info: + description: List of connected interfaces. + returned: success + type: list + distributed: + description: Indicates a distributed router. + returned: success + type: bool + ha: + description: Indicates a highly-available router. + returned: success + type: bool + project_id: + description: Project id associated with this router. + returned: success + type: str + routes: + description: The extra routes configuration for L3 router. + returned: success + type: list +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class RouterInfoModule(OpenStackModule): + + deprecated_names = ('os_routers_info', 'openstack.cloud.os_routers_info') + + argument_spec = dict( + name=dict(required=False, default=None), + filters=dict(required=False, type='dict', default=None) + ) + + def run(self): + + kwargs = self.check_versioned( + filters=self.params['filters'] + ) + if self.params['name']: + kwargs['name_or_id'] = self.params['name'] + routers = self.conn.search_routers(**kwargs) + + for router in routers: + interfaces_info = [] + for port in self.conn.list_router_interfaces(router): + if port.device_owner != "network:router_gateway": + for ip_spec in port.fixed_ips: + int_info = { + 'port_id': port.id, + 'ip_address': ip_spec.get('ip_address'), + 'subnet_id': ip_spec.get('subnet_id') + } + interfaces_info.append(int_info) + router['interfaces_info'] = interfaces_info + + self.exit(changed=False, openstack_routers=routers) + + +def main(): + module = RouterInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_security_group.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_security_group.py new file mode 100644 index 00000000..8208a1c2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_security_group.py @@ -0,0 +1,153 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: security_group +short_description: Add/Delete security groups from an OpenStack cloud. +author: OpenStack Ansible SIG +description: + - Add or Remove security groups from an OpenStack cloud. +options: + name: + description: + - Name that has to be given to the security group. This module + requires that security group names be unique. + required: true + type: str + description: + description: + - Long description of the purpose of the security group + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + project: + description: + - Unique name or ID of the project. + required: false + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a security group +- openstack.cloud.security_group: + cloud: mordred + state: present + name: foo + description: security group for foo servers + +# Update the existing 'foo' security group description +- openstack.cloud.security_group: + cloud: mordred + state: present + name: foo + description: updated description for the foo security group + +# Create a security group for a given project +- openstack.cloud.security_group: + cloud: mordred + state: present + name: foo + project: myproj +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class SecurityGroupModule(OpenStackModule): + + argument_spec = dict( + name=dict(required=True), + description=dict(default=''), + state=dict(default='present', choices=['absent', 'present']), + project=dict(default=None), + ) + + def _needs_update(self, secgroup): + """Check for differences in the updatable values. + + NOTE: We don't currently allow name updates. + """ + if secgroup['description'] != self.params['description']: + return True + return False + + def _system_state_change(self, secgroup): + state = self.params['state'] + if state == 'present': + if not secgroup: + return True + return self._needs_update(secgroup) + if state == 'absent' and secgroup: + return True + return False + + def run(self): + + name = self.params['name'] + state = self.params['state'] + description = self.params['description'] + project = self.params['project'] + + if project is not None: + proj = self.conn.get_project(project) + if proj is None: + self.fail_json(msg='Project %s could not be found' % project) + project_id = proj['id'] + else: + project_id = self.conn.current_project_id + + if project_id: + filters = {'tenant_id': project_id} + else: + filters = None + + secgroup = self.conn.get_security_group(name, filters=filters) + + if self.ansible.check_mode: + self.exit(changed=self._system_state_change(secgroup)) + + changed = False + if state == 'present': + if not secgroup: + kwargs = {} + if project_id: + kwargs['project_id'] = project_id + secgroup = self.conn.create_security_group(name, description, + **kwargs) + changed = True + else: + if self._needs_update(secgroup): + secgroup = self.conn.update_security_group( + secgroup['id'], description=description) + changed = True + self.exit( + changed=changed, id=secgroup['id'], secgroup=secgroup) + + if state == 'absent': + if secgroup: + self.conn.delete_security_group(secgroup['id']) + changed = True + self.exit(changed=changed) + + +def main(): + module = SecurityGroupModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_security_group_rule.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_security_group_rule.py new file mode 100644 index 00000000..6a0e0c99 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_security_group_rule.py @@ -0,0 +1,385 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: security_group_rule +short_description: Add/Delete rule from an existing security group +author: OpenStack Ansible SIG +description: + - Add or Remove rule from an existing security group +options: + security_group: + description: + - Name or ID of the security group + required: true + type: str + protocol: + description: + - IP protocols ANY TCP UDP ICMP 112 (VRRP) 132 (SCTP) + choices: ['any', 'tcp', 'udp', 'icmp', '112', '132', None] + type: str + port_range_min: + description: + - Starting port + type: int + port_range_max: + description: + - Ending port + type: int + remote_ip_prefix: + description: + - Source IP address(es) in CIDR notation (exclusive with remote_group) + type: str + remote_group: + description: + - Name or ID of the Security group to link (exclusive with + remote_ip_prefix) + type: str + ethertype: + description: + - Must be IPv4 or IPv6, and addresses represented in CIDR must + match the ingress or egress rules. Not all providers support IPv6. + choices: ['IPv4', 'IPv6'] + default: IPv4 + type: str + direction: + description: + - The direction in which the security group rule is applied. Not + all providers support egress. + choices: ['egress', 'ingress'] + default: ingress + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + project: + description: + - Unique name or ID of the project. + required: false + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a security group rule +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: tcp + port_range_min: 80 + port_range_max: 80 + remote_ip_prefix: 0.0.0.0/0 + +# Create a security group rule for ping +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: icmp + remote_ip_prefix: 0.0.0.0/0 + +# Another way to create the ping rule +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: icmp + port_range_min: -1 + port_range_max: -1 + remote_ip_prefix: 0.0.0.0/0 + +# Create a TCP rule covering all ports +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: tcp + port_range_min: 1 + port_range_max: 65535 + remote_ip_prefix: 0.0.0.0/0 + +# Another way to create the TCP rule above (defaults to all ports) +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: tcp + remote_ip_prefix: 0.0.0.0/0 + +# Create a rule for VRRP with numbered protocol 112 +- openstack.cloud.security_group_rule: + security_group: loadbalancer_sg + protocol: 112 + remote_group: loadbalancer-node_sg + +# Create a security group rule for a given project +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: icmp + remote_ip_prefix: 0.0.0.0/0 + project: myproj + +# Remove the default created egress rule for IPv4 +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: any + remote_ip_prefix: 0.0.0.0/0 +''' + +RETURN = ''' +id: + description: Unique rule UUID. + type: str + returned: state == present +direction: + description: The direction in which the security group rule is applied. + type: str + sample: 'egress' + returned: state == present +ethertype: + description: One of IPv4 or IPv6. + type: str + sample: 'IPv4' + returned: state == present +port_range_min: + description: The minimum port number in the range that is matched by + the security group rule. + type: int + sample: 8000 + returned: state == present +port_range_max: + description: The maximum port number in the range that is matched by + the security group rule. + type: int + sample: 8000 + returned: state == present +protocol: + description: The protocol that is matched by the security group rule. + type: str + sample: 'tcp' + returned: state == present +remote_ip_prefix: + description: The remote IP prefix to be associated with this security group rule. + type: str + sample: '0.0.0.0/0' + returned: state == present +security_group_id: + description: The security group ID to associate with this security group rule. + type: str + returned: state == present +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + OpenStackModule) + + +def _ports_match(protocol, module_min, module_max, rule_min, rule_max): + """ + Capture the complex port matching logic. + + The port values coming in for the module might be -1 (for ICMP), + which will work only for Nova, but this is handled by sdk. Likewise, + they might be None, which works for Neutron, but not Nova. This too is + handled by sdk. Since sdk will consistently return these port + values as None, we need to convert any -1 values input to the module + to None here for comparison. + + For TCP and UDP protocols, None values for both min and max are + represented as the range 1-65535 for Nova, but remain None for + Neutron. sdk returns the full range when Nova is the backend (since + that is how Nova stores them), and None values for Neutron. If None + values are input to the module for both values, then we need to adjust + for comparison. + """ + + # Check if the user is supplying -1 for ICMP. + if protocol == 'icmp': + if module_min and int(module_min) == -1: + module_min = None + if module_max and int(module_max) == -1: + module_max = None + + # Rules with 'any' protocol do not match ports + if protocol == 'any': + return True + + # Check if the user is supplying -1, 1 to 65535 or None values for full TPC/UDP port range. + if protocol in ['tcp', 'udp'] or protocol is None: + if ( + not module_min and not module_max + or (int(module_min) in [-1, 1] + and int(module_max) in [-1, 65535]) + ): + if ( + not rule_min and not rule_max + or (int(rule_min) in [-1, 1] + and int(rule_max) in [-1, 65535]) + ): + # (None, None) == (1, 65535) == (-1, -1) + return True + + # Sanity check to make sure we don't have type comparison issues. + if module_min: + module_min = int(module_min) + if module_max: + module_max = int(module_max) + if rule_min: + rule_min = int(rule_min) + if rule_max: + rule_max = int(rule_max) + + return module_min == rule_min and module_max == rule_max + + +class SecurityGroupRuleModule(OpenStackModule): + deprecated_names = ('os_security_group_rule', 'openstack.cloud.os_security_group_rule') + + argument_spec = dict( + security_group=dict(required=True), + # NOTE(Shrews): None is an acceptable protocol value for + # Neutron, but Nova will balk at this. + protocol=dict(default=None, + choices=[None, 'any', 'tcp', 'udp', 'icmp', '112', '132']), + port_range_min=dict(required=False, type='int'), + port_range_max=dict(required=False, type='int'), + remote_ip_prefix=dict(required=False, default=None), + remote_group=dict(required=False, default=None), + ethertype=dict(default='IPv4', + choices=['IPv4', 'IPv6']), + direction=dict(default='ingress', + choices=['egress', 'ingress']), + state=dict(default='present', + choices=['absent', 'present']), + project=dict(default=None), + ) + + module_kwargs = dict( + mutually_exclusive=[ + ['remote_ip_prefix', 'remote_group'], + ] + ) + + def _find_matching_rule(self, secgroup, remotegroup): + """ + Find a rule in the group that matches the module parameters. + :returns: The matching rule dict, or None if no matches. + """ + protocol = self.params['protocol'] + remote_ip_prefix = self.params['remote_ip_prefix'] + ethertype = self.params['ethertype'] + direction = self.params['direction'] + remote_group_id = remotegroup['id'] + + for rule in secgroup['security_group_rules']: + if ( + protocol == rule['protocol'] + and remote_ip_prefix == rule['remote_ip_prefix'] + and ethertype == rule['ethertype'] + and direction == rule['direction'] + and remote_group_id == rule['remote_group_id'] + and _ports_match( + protocol, + self.params['port_range_min'], + self.params['port_range_max'], + rule['port_range_min'], + rule['port_range_max']) + ): + return rule + return None + + def _system_state_change(self, secgroup, remotegroup): + state = self.params['state'] + if secgroup: + rule_exists = self._find_matching_rule(secgroup, remotegroup) + else: + return False + + if state == 'present' and not rule_exists: + return True + if state == 'absent' and rule_exists: + return True + return False + + def run(self): + + state = self.params['state'] + security_group = self.params['security_group'] + remote_group = self.params['remote_group'] + project = self.params['project'] + changed = False + + if project is not None: + proj = self.conn.get_project(project) + if proj is None: + self.fail_json(msg='Project %s could not be found' % project) + project_id = proj['id'] + else: + project_id = self.conn.current_project_id + + if project_id and not remote_group: + filters = {'tenant_id': project_id} + else: + filters = None + + secgroup = self.conn.get_security_group(security_group, filters=filters) + + if remote_group: + remotegroup = self.conn.get_security_group(remote_group, filters=filters) + else: + remotegroup = {'id': None} + + if self.ansible.check_mode: + self.exit_json(changed=self._system_state_change(secgroup, remotegroup)) + + if state == 'present': + if self.params['protocol'] == 'any': + self.params['protocol'] = None + + if not secgroup: + self.fail_json(msg='Could not find security group %s' % security_group) + + rule = self._find_matching_rule(secgroup, remotegroup) + if not rule: + kwargs = {} + if project_id: + kwargs['project_id'] = project_id + rule = self.conn.create_security_group_rule( + secgroup['id'], + port_range_min=self.params['port_range_min'], + port_range_max=self.params['port_range_max'], + protocol=self.params['protocol'], + remote_ip_prefix=self.params['remote_ip_prefix'], + remote_group_id=remotegroup['id'], + direction=self.params['direction'], + ethertype=self.params['ethertype'], + **kwargs + ) + changed = True + self.exit_json(changed=changed, rule=rule, id=rule['id']) + + if state == 'absent' and secgroup: + rule = self._find_matching_rule(secgroup, remotegroup) + if rule: + self.conn.delete_security_group_rule(rule['id']) + changed = True + + self.exit_json(changed=changed) + + +def main(): + module = SecurityGroupRuleModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server.py new file mode 100644 index 00000000..570425a2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server.py @@ -0,0 +1,799 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright 2019 Red Hat, Inc. +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# Copyright (c) 2013, John Dewey <john@dewey.ws> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: server +short_description: Create/Delete Compute Instances from OpenStack +author: OpenStack Ansible SIG +description: + - Create or Remove compute instances from OpenStack. +options: + name: + description: + - Name that has to be given to the instance. It is also possible to + specify the ID of the instance instead of its name if I(state) is I(absent). + required: true + type: str + image: + description: + - The name or id of the base image to boot. + - Required when I(boot_from_volume=true) + type: str + image_exclude: + description: + - Text to use to filter image names, for the case, such as HP, where + there are multiple image names matching the common identifying + portions. image_exclude is a negative match filter - it is text that + may not exist in the image name. + type: str + default: "(deprecated)" + flavor: + description: + - The name or id of the flavor in which the new instance has to be + created. + - Exactly one of I(flavor) and I(flavor_ram) must be defined when + I(state=present). + type: str + flavor_ram: + description: + - The minimum amount of ram in MB that the flavor in which the new + instance has to be created must have. + - Exactly one of I(flavor) and I(flavor_ram) must be defined when + I(state=present). + type: int + flavor_include: + description: + - Text to use to filter flavor names, for the case, such as Rackspace, + where there are multiple flavors that have the same ram count. + flavor_include is a positive match filter - it must exist in the + flavor name. + type: str + key_name: + description: + - The key pair name to be used when creating a instance + type: str + security_groups: + description: + - Names of the security groups to which the instance should be + added. This may be a YAML list or a comma separated string. + type: list + default: ['default'] + elements: str + network: + description: + - Name or ID of a network to attach this instance to. A simpler + version of the nics parameter, only one of network or nics should + be supplied. + type: str + nics: + description: + - A list of networks to which the instance's interface should + be attached. Networks may be referenced by net-id/net-name/port-id + or port-name. + - 'Also this accepts a string containing a list of (net/port)-(id/name) + Eg: nics: "net-id=uuid-1,port-name=myport" + Only one of network or nics should be supplied.' + type: list + elements: raw + suboptions: + tag: + description: + - 'A "tag" for the specific port to be passed via metadata. + Eg: tag: test_tag' + auto_ip: + description: + - Ensure instance has public ip however the cloud wants to do that + type: bool + default: 'yes' + aliases: ['auto_floating_ip', 'public_ip'] + floating_ips: + description: + - list of valid floating IPs that pre-exist to assign to this node + type: list + elements: str + floating_ip_pools: + description: + - Name of floating IP pool from which to choose a floating IP + type: list + elements: str + meta: + description: + - 'A list of key value pairs that should be provided as a metadata to + the new instance or a string containing a list of key-value pairs. + Eg: meta: "key1=value1,key2=value2"' + type: raw + wait: + description: + - If the module should wait for the instance to be created. + type: bool + default: 'yes' + timeout: + description: + - The amount of time the module should wait for the instance to get + into active state. + default: 180 + type: int + config_drive: + description: + - Whether to boot the server with config drive enabled + type: bool + default: 'no' + userdata: + description: + - Opaque blob of data which is made available to the instance + type: str + aliases: ['user_data'] + boot_from_volume: + description: + - Should the instance boot from a persistent volume created based on + the image given. Mutually exclusive with boot_volume. + type: bool + default: 'no' + volume_size: + description: + - The size of the volume to create in GB if booting from volume based + on an image. + type: int + boot_volume: + description: + - Volume name or id to use as the volume to boot from. Implies + boot_from_volume. Mutually exclusive with image and boot_from_volume. + aliases: ['root_volume'] + type: str + terminate_volume: + description: + - If C(yes), delete volume when deleting instance (if booted from volume) + type: bool + default: 'no' + volumes: + description: + - A list of preexisting volumes names or ids to attach to the instance + default: [] + type: list + elements: str + scheduler_hints: + description: + - Arbitrary key/value pairs to the scheduler for custom use + type: dict + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + delete_fip: + description: + - When I(state) is absent and this option is true, any floating IP + associated with the instance will be deleted along with the instance. + type: bool + default: 'no' + reuse_ips: + description: + - When I(auto_ip) is true and this option is true, the I(auto_ip) code + will attempt to re-use unassigned floating ips in the project before + creating a new one. It is important to note that it is impossible + to safely do this concurrently, so if your use case involves + concurrent server creation, it is highly recommended to set this to + false and to delete the floating ip associated with a server when + the server is deleted using I(delete_fip). + type: bool + default: 'yes' + availability_zone: + description: + - Availability zone in which to create the server. + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Create a new instance and attaches to a network and passes metadata to the instance + openstack.cloud.server: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + timeout: 200 + flavor: 4 + nics: + - net-id: 34605f38-e52a-25d2-b6ec-754a13ffb723 + - net-name: another_network + meta: + hostname: test1 + group: uge_master + +# Create a new instance in HP Cloud AE1 region availability zone az2 and +# automatically assigns a floating IP +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + state: present + auth: + auth_url: https://identity.example.com + username: username + password: Equality7-2521 + project_name: username-project1 + name: vm1 + region_name: region-b.geo-1 + availability_zone: az2 + image: 9302692b-b787-4b52-a3a6-daebb79cb498 + key_name: test + timeout: 200 + flavor: 101 + security_groups: default + auto_ip: yes + +# Create a new instance in named cloud mordred availability zone az2 +# and assigns a pre-known floating IP +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + state: present + cloud: mordred + name: vm1 + availability_zone: az2 + image: 9302692b-b787-4b52-a3a6-daebb79cb498 + key_name: test + timeout: 200 + flavor: 101 + floating_ips: + - 12.34.56.79 + +# Create a new instance with 4G of RAM on Ubuntu Trusty, ignoring +# deprecated images +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + name: vm1 + state: present + cloud: mordred + region_name: region-b.geo-1 + image: Ubuntu Server 14.04 + image_exclude: deprecated + flavor_ram: 4096 + +# Create a new instance with 4G of RAM on Ubuntu Trusty on a Performance node +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + name: vm1 + cloud: rax-dfw + state: present + image: Ubuntu 14.04 LTS (Trusty Tahr) (PVHVM) + flavor_ram: 4096 + flavor_include: Performance + +# Creates a new instance and attaches to multiple network +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance with a string + openstack.cloud.server: + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + timeout: 200 + flavor: 4 + nics: "net-id=4cb08b20-62fe-11e5-9d70-feff819cdc9f,net-id=542f0430-62fe-11e5-9d70-feff819cdc9f..." + +- name: Creates a new instance and attaches to a network and passes metadata to the instance + openstack.cloud.server: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + timeout: 200 + flavor: 4 + nics: + - net-id: 34605f38-e52a-25d2-b6ec-754a13ffb723 + - net-name: another_network + meta: "hostname=test1,group=uge_master" + +- name: Creates a new instance and attaches to a specific network + openstack.cloud.server: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + timeout: 200 + flavor: 4 + network: another_network + +# Create a new instance with 4G of RAM on a 75G Ubuntu Trusty volume +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + name: vm1 + state: present + cloud: mordred + region_name: ams01 + image: Ubuntu Server 14.04 + flavor_ram: 4096 + boot_from_volume: True + volume_size: 75 + +# Creates a new instance with 2 volumes attached +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + name: vm1 + state: present + cloud: mordred + region_name: ams01 + image: Ubuntu Server 14.04 + flavor_ram: 4096 + volumes: + - photos + - music + +# Creates a new instance with provisioning userdata using Cloud-Init +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + name: vm1 + state: present + image: "Ubuntu Server 14.04" + flavor: "P-1" + network: "Production" + userdata: | + #cloud-config + chpasswd: + list: | + ubuntu:{{ default_password }} + expire: False + packages: + - ansible + package_upgrade: true + +# Creates a new instance with provisioning userdata using Bash Scripts +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + name: vm1 + state: present + image: "Ubuntu Server 14.04" + flavor: "P-1" + network: "Production" + userdata: | + {%- raw -%}#!/bin/bash + echo " up ip route add 10.0.0.0/8 via {% endraw -%}{{ intra_router }}{%- raw -%}" >> /etc/network/interfaces.d/eth0.conf + echo " down ip route del 10.0.0.0/8" >> /etc/network/interfaces.d/eth0.conf + ifdown eth0 && ifup eth0 + {% endraw %} + +# Create a new instance with server group for (anti-)affinity +# server group ID is returned from openstack.cloud.server_group module. +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + state: present + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + flavor: 4 + scheduler_hints: + group: f5c8c61a-9230-400a-8ed2-3b023c190a7f + +# Create an instance with "tags" for the nic +- name: Create instance with nics "tags" + openstack.cloud.server: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + flavor: 4 + nics: + - port-name: net1_port1 + tag: test_tag + - net-name: another_network + +# Deletes an instance via its ID +- name: remove an instance + hosts: localhost + tasks: + - name: remove an instance + openstack.cloud.server: + name: abcdef01-2345-6789-0abc-def0123456789 + state: absent + +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_find_nova_addresses, OpenStackModule) + + +def _parse_nics(nics): + for net in nics: + if isinstance(net, str): + for nic in net.split(','): + yield dict((nic.split('='),)) + else: + yield net + + +def _parse_meta(meta): + if isinstance(meta, str): + metas = {} + for kv_str in meta.split(","): + k, v = kv_str.split("=") + metas[k] = v + return metas + if not meta: + return {} + return meta + + +class ServerModule(OpenStackModule): + deprecated_names = ('os_server', 'openstack.cloud.os_server') + + argument_spec = dict( + name=dict(required=True), + image=dict(default=None), + image_exclude=dict(default='(deprecated)'), + flavor=dict(default=None), + flavor_ram=dict(default=None, type='int'), + flavor_include=dict(default=None), + key_name=dict(default=None), + security_groups=dict(default=['default'], type='list', elements='str'), + network=dict(default=None), + nics=dict(default=[], type='list', elements='raw'), + meta=dict(default=None, type='raw'), + userdata=dict(default=None, aliases=['user_data']), + config_drive=dict(default=False, type='bool'), + auto_ip=dict(default=True, type='bool', aliases=['auto_floating_ip', 'public_ip']), + floating_ips=dict(default=None, type='list', elements='str'), + floating_ip_pools=dict(default=None, type='list', elements='str'), + volume_size=dict(default=None, type='int'), + boot_from_volume=dict(default=False, type='bool'), + boot_volume=dict(default=None, aliases=['root_volume']), + terminate_volume=dict(default=False, type='bool'), + volumes=dict(default=[], type='list', elements='str'), + scheduler_hints=dict(default=None, type='dict'), + state=dict(default='present', choices=['absent', 'present']), + delete_fip=dict(default=False, type='bool'), + reuse_ips=dict(default=True, type='bool'), + ) + module_kwargs = dict( + mutually_exclusive=[ + ['auto_ip', 'floating_ips'], + ['auto_ip', 'floating_ip_pools'], + ['floating_ips', 'floating_ip_pools'], + ['flavor', 'flavor_ram'], + ['image', 'boot_volume'], + ['boot_from_volume', 'boot_volume'], + ['nics', 'network'], + ], + required_if=[ + ('boot_from_volume', True, ['volume_size', 'image']), + ], + ) + + def run(self): + + state = self.params['state'] + image = self.params['image'] + boot_volume = self.params['boot_volume'] + flavor = self.params['flavor'] + flavor_ram = self.params['flavor_ram'] + + if state == 'present': + if not (image or boot_volume): + self.fail( + msg="Parameter 'image' or 'boot_volume' is required " + "if state == 'present'" + ) + if not flavor and not flavor_ram: + self.fail( + msg="Parameter 'flavor' or 'flavor_ram' is required " + "if state == 'present'" + ) + + if state == 'present': + self._get_server_state() + self._create_server() + elif state == 'absent': + self._get_server_state() + self._delete_server() + + def _exit_hostvars(self, server, changed=True): + hostvars = self.conn.get_openstack_vars(server) + self.exit( + changed=changed, server=server, id=server.id, openstack=hostvars) + + def _get_server_state(self): + state = self.params['state'] + server = self.conn.get_server(self.params['name']) + if server and state == 'present': + if server.status not in ('ACTIVE', 'SHUTOFF', 'PAUSED', 'SUSPENDED'): + self.fail( + msg="The instance is available but not Active state: " + server.status) + (ip_changed, server) = self._check_ips(server) + (sg_changed, server) = self._check_security_groups(server) + (server_changed, server) = self._update_server(server) + self._exit_hostvars(server, ip_changed or sg_changed or server_changed) + if server and state == 'absent': + return True + if state == 'absent': + self.exit(changed=False, result="not present") + return True + + def _create_server(self): + flavor = self.params['flavor'] + flavor_ram = self.params['flavor_ram'] + flavor_include = self.params['flavor_include'] + + image_id = None + if not self.params['boot_volume']: + image_id = self.conn.get_image_id( + self.params['image'], self.params['image_exclude']) + if not image_id: + self.fail( + msg="Could not find image %s" % self.params['image']) + + if flavor: + flavor_dict = self.conn.get_flavor(flavor) + if not flavor_dict: + self.fail(msg="Could not find flavor %s" % flavor) + else: + flavor_dict = self.conn.get_flavor_by_ram(flavor_ram, flavor_include) + if not flavor_dict: + self.fail(msg="Could not find any matching flavor") + + nics = self._network_args() + + self.params['meta'] = _parse_meta(self.params['meta']) + + bootkwargs = self.check_versioned( + name=self.params['name'], + image=image_id, + flavor=flavor_dict['id'], + nics=nics, + meta=self.params['meta'], + security_groups=self.params['security_groups'], + userdata=self.params['userdata'], + config_drive=self.params['config_drive'], + ) + for optional_param in ( + 'key_name', 'availability_zone', 'network', + 'scheduler_hints', 'volume_size', 'volumes'): + if self.params[optional_param]: + bootkwargs[optional_param] = self.params[optional_param] + + server = self.conn.create_server( + ip_pool=self.params['floating_ip_pools'], + ips=self.params['floating_ips'], + auto_ip=self.params['auto_ip'], + boot_volume=self.params['boot_volume'], + boot_from_volume=self.params['boot_from_volume'], + terminate_volume=self.params['terminate_volume'], + reuse_ips=self.params['reuse_ips'], + wait=self.params['wait'], timeout=self.params['timeout'], + **bootkwargs + ) + + self._exit_hostvars(server) + + def _update_server(self, server): + changed = False + + self.params['meta'] = _parse_meta(self.params['meta']) + + # self.conn.set_server_metadata only updates the key=value pairs, it doesn't + # touch existing ones + update_meta = {} + for (k, v) in self.params['meta'].items(): + if k not in server.metadata or server.metadata[k] != v: + update_meta[k] = v + + if update_meta: + self.conn.set_server_metadata(server, update_meta) + changed = True + # Refresh server vars + server = self.conn.get_server(self.params['name']) + + return (changed, server) + + def _delete_server(self): + try: + self.conn.delete_server( + self.params['name'], wait=self.params['wait'], + timeout=self.params['timeout'], + delete_ips=self.params['delete_fip']) + except Exception as e: + self.fail(msg="Error in deleting vm: %s" % e) + self.exit(changed=True, result='deleted') + + def _network_args(self): + args = [] + nics = self.params['nics'] + + if not isinstance(nics, list): + self.fail(msg='The \'nics\' parameter must be a list.') + + for num, net in enumerate(_parse_nics(nics)): + if not isinstance(net, dict): + self.fail( + msg='Each entry in the \'nics\' parameter must be a dict.') + + if net.get('net-id'): + args.append(net) + elif net.get('net-name'): + by_name = self.conn.get_network(net['net-name']) + if not by_name: + self.fail( + msg='Could not find network by net-name: %s' % + net['net-name']) + resolved_net = net.copy() + del resolved_net['net-name'] + resolved_net['net-id'] = by_name['id'] + args.append(resolved_net) + elif net.get('port-id'): + args.append(net) + elif net.get('port-name'): + by_name = self.conn.get_port(net['port-name']) + if not by_name: + self.fail( + msg='Could not find port by port-name: %s' % + net['port-name']) + resolved_net = net.copy() + del resolved_net['port-name'] + resolved_net['port-id'] = by_name['id'] + args.append(resolved_net) + + if 'tag' in net: + args[num]['tag'] = net['tag'] + return args + + def _detach_ip_list(self, server, extra_ips): + for ip in extra_ips: + ip_id = self.conn.get_floating_ip( + id=None, filters={'floating_ip_address': ip}) + self.conn.detach_ip_from_server( + server_id=server.id, floating_ip_id=ip_id) + + def _check_ips(self, server): + changed = False + + auto_ip = self.params['auto_ip'] + floating_ips = self.params['floating_ips'] + floating_ip_pools = self.params['floating_ip_pools'] + + if floating_ip_pools or floating_ips: + ips = openstack_find_nova_addresses(server.addresses, 'floating') + if not ips: + # If we're configured to have a floating but we don't have one, + # let's add one + server = self.conn.add_ips_to_server( + server, + auto_ip=auto_ip, + ips=floating_ips, + ip_pool=floating_ip_pools, + wait=self.params['wait'], + timeout=self.params['timeout'], + ) + changed = True + elif floating_ips: + # we were configured to have specific ips, let's make sure we have + # those + missing_ips = [] + for ip in floating_ips: + if ip not in ips: + missing_ips.append(ip) + if missing_ips: + server = self.conn.add_ip_list(server, missing_ips, + wait=self.params['wait'], + timeout=self.params['timeout']) + changed = True + extra_ips = [] + for ip in ips: + if ip not in floating_ips: + extra_ips.append(ip) + if extra_ips: + self._detach_ip_list(server, extra_ips) + changed = True + elif auto_ip: + if server['interface_ip']: + changed = False + else: + # We're configured for auto_ip but we're not showing an + # interface_ip. Maybe someone deleted an IP out from under us. + server = self.conn.add_ips_to_server( + server, + auto_ip=auto_ip, + ips=floating_ips, + ip_pool=floating_ip_pools, + wait=self.params['wait'], + timeout=self.params['timeout'], + ) + changed = True + return (changed, server) + + def _check_security_groups(self, server): + changed = False + + # server security groups were added to shade in 1.19. Until then this + # module simply ignored trying to update security groups and only set them + # on newly created hosts. + if not ( + hasattr(self.conn, 'add_server_security_groups') + and hasattr(self.conn, 'remove_server_security_groups') + ): + return changed, server + + module_security_groups = set(self.params['security_groups']) + server_security_groups = set(sg['name'] for sg in server.security_groups) + + add_sgs = module_security_groups - server_security_groups + remove_sgs = server_security_groups - module_security_groups + + if add_sgs: + self.conn.add_server_security_groups(server, list(add_sgs)) + changed = True + + if remove_sgs: + self.conn.remove_server_security_groups(server, list(remove_sgs)) + changed = True + + return (changed, server) + + +def main(): + module = ServerModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_action.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_action.py new file mode 100644 index 00000000..68bd62b6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_action.py @@ -0,0 +1,182 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright (c) 2015, Jesse Keating <jlk@derpops.bike> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: server_action +short_description: Perform actions on Compute Instances from OpenStack +author: OpenStack Ansible SIG +description: + - Perform server actions on an existing compute instance from OpenStack. + This module does not return any data other than changed true/false. + When I(action) is 'rebuild', then I(image) parameter is required. +options: + server: + description: + - Name or ID of the instance + required: true + type: str + wait: + description: + - If the module should wait for the instance action to be performed. + type: bool + default: 'yes' + timeout: + description: + - The amount of time the module should wait for the instance to perform + the requested action. + default: 180 + type: int + action: + description: + - Perform the given action. The lock and unlock actions always return + changed as the servers API does not provide lock status. + choices: [stop, start, pause, unpause, lock, unlock, suspend, resume, + rebuild] + type: str + required: true + image: + description: + - Image the server should be rebuilt with + type: str + admin_password: + description: + - Admin password for server to rebuild + type: str + +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Pauses a compute instance +- openstack.cloud.server_action: + action: pause + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + server: vm1 + timeout: 200 +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +_action_map = {'stop': 'SHUTOFF', + 'start': 'ACTIVE', + 'pause': 'PAUSED', + 'unpause': 'ACTIVE', + 'lock': 'ACTIVE', # API doesn't show lock/unlock status + 'unlock': 'ACTIVE', + 'suspend': 'SUSPENDED', + 'resume': 'ACTIVE', + 'rebuild': 'ACTIVE'} + +_admin_actions = ['pause', 'unpause', 'suspend', 'resume', 'lock', 'unlock'] + + +class ServerActionModule(OpenStackModule): + deprecated_names = ('os_server_action', 'openstack.cloud.os_server_action') + + argument_spec = dict( + server=dict(required=True, type='str'), + action=dict(required=True, type='str', + choices=['stop', 'start', 'pause', 'unpause', + 'lock', 'unlock', 'suspend', 'resume', + 'rebuild']), + image=dict(required=False, type='str'), + admin_password=dict(required=False, type='str'), + ) + module_kwargs = dict( + required_if=[('action', 'rebuild', ['image'])], + supports_check_mode=True, + ) + + def run(self): + os_server = self._preliminary_checks() + self._execute_server_action(os_server) + # for some reason we don't wait for lock and unlock before exit + if self.params['action'] not in ('lock', 'unlock'): + if self.params['wait']: + self._wait(os_server) + self.exit_json(changed=True) + + def _preliminary_checks(self): + # Using Munch object for getting information about a server + os_server = self.conn.get_server(self.params['server']) + if not os_server: + self.fail_json(msg='Could not find server %s' % self.params['server']) + # check mode + if self.ansible.check_mode: + self.exit_json(changed=self.__system_state_change(os_server)) + # examine special cases + # lock, unlock and rebuild don't depend on state, just do it + if self.params['action'] not in ('lock', 'unlock', 'rebuild'): + if not self.__system_state_change(os_server): + self.exit_json(changed=False) + return os_server + + def _execute_server_action(self, os_server): + if self.params['action'] == 'rebuild': + return self._rebuild_server(os_server) + action_name = self.params['action'] + "_server" + try: + func_name = getattr(self.conn.compute, action_name) + except AttributeError: + self.fail_json( + msg="Method %s wasn't found in OpenstackSDK compute" % action_name) + func_name(os_server) + + def _rebuild_server(self, os_server): + # rebuild should ensure images exists + try: + image = self.conn.get_image(self.params['image']) + except Exception as e: + self.fail_json( + msg="Can't find the image %s: %s" % (self.params['image'], e)) + if not image: + self.fail_json(msg="Image %s was not found!" % self.params['image']) + # admin_password is required by SDK, but not required by Nova API + if self.params['admin_password']: + self.conn.compute.rebuild_server( + server=os_server, + name=os_server['name'], + image=image['id'], + admin_password=self.params['admin_password'] + ) + else: + self.conn.compute.post( + '/servers/{server_id}/action'.format( + server_id=os_server['id']), + json={'rebuild': {'imageRef': image['id']}}) + + def _wait(self, os_server): + """Wait for the server to reach the desired state for the given action.""" + # Using Server object for wait_for_server function + server = self.conn.compute.find_server(self.params['server']) + self.conn.compute.wait_for_server( + server, + status=_action_map[self.params['action']], + wait=self.params['timeout']) + + def __system_state_change(self, os_server): + """Check if system state would change.""" + return os_server.status != _action_map[self.params['action']] + + +def main(): + module = ServerActionModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_group.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_group.py new file mode 100644 index 00000000..444874b6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_group.py @@ -0,0 +1,166 @@ +#!/usr/bin/python + +# Copyright (c) 2016 Catalyst IT Limited +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: server_group +short_description: Manage OpenStack server groups +author: OpenStack Ansible SIG +description: + - Add or remove server groups from OpenStack. +options: + state: + description: + - Indicate desired state of the resource. When I(state) is 'present', + then I(policies) is required. + choices: ['present', 'absent'] + required: false + default: present + type: str + name: + description: + - Server group name. + required: true + type: str + policies: + description: + - A list of one or more policy names to associate with the server + group. The list must contain at least one policy name. The current + valid policy names are anti-affinity, affinity, soft-anti-affinity + and soft-affinity. + required: false + type: list + elements: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a server group with 'affinity' policy. +- openstack.cloud.server_group: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: my_server_group + policies: + - affinity + +# Delete 'my_server_group' server group. +- openstack.cloud.server_group: + state: absent + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: my_server_group +''' + +RETURN = ''' +id: + description: Unique UUID. + returned: success + type: str +name: + description: The name of the server group. + returned: success + type: str +policies: + description: A list of one or more policy names of the server group. + returned: success + type: list +members: + description: A list of members in the server group. + returned: success + type: list +metadata: + description: Metadata key and value pairs. + returned: success + type: dict +project_id: + description: The project ID who owns the server group. + returned: success + type: str +user_id: + description: The user ID who owns the server group. + returned: success + type: str +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(state, server_group): + if state == 'present' and not server_group: + return True + if state == 'absent' and server_group: + return True + + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + policies=dict(required=False, type='list', elements='str'), + state=dict(default='present', choices=['absent', 'present']), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params['name'] + policies = module.params['policies'] + state = module.params['state'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + server_group = cloud.get_server_group(name) + + if module.check_mode: + module.exit_json( + changed=_system_state_change(state, server_group) + ) + + changed = False + if state == 'present': + if not server_group: + if not policies: + module.fail_json( + msg="Parameter 'policies' is required in Server Group " + "Create" + ) + server_group = cloud.create_server_group(name, policies) + changed = True + + module.exit_json( + changed=changed, + id=server_group['id'], + server_group=server_group + ) + if state == 'absent': + if server_group: + cloud.delete_server_group(server_group['id']) + changed = True + module.exit_json(changed=changed) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_info.py new file mode 100644 index 00000000..e9fb3795 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_info.py @@ -0,0 +1,93 @@ +#!/usr/bin/python + +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: server_info +short_description: Retrieve information about one or more compute instances +author: OpenStack Ansible SIG +description: + - Retrieve information about server instances from OpenStack. + - This module was called C(os_server_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.server_info) module no longer returns C(ansible_facts)! +notes: + - The result contains a list of servers. +options: + server: + description: + - restrict results to servers with names or UUID matching + this glob expression (e.g., <web*>). + type: str + detailed: + description: + - when true, return additional detail about servers at the expense + of additional API calls. + type: bool + default: 'no' + filters: + description: + - restrict results to servers matching a dictionary of + filters + type: dict + all_projects: + description: + - Whether to list servers from all projects or just the current auth + scoped project. + type: bool + default: 'no' +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather information about all servers named <web*> that are in an active state: +- openstack.cloud.server_info: + cloud: rax-dfw + server: web* + filters: + vm_state: active + register: result +- debug: + msg: "{{ result.openstack_servers }}" +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class ServerInfoModule(OpenStackModule): + + deprecated_names = ('os_server_info', 'openstack.cloud.os_server_info') + + argument_spec = dict( + server=dict(required=False), + detailed=dict(required=False, type='bool', default=False), + filters=dict(required=False, type='dict', default=None), + all_projects=dict(required=False, type='bool', default=False), + ) + + def run(self): + + kwargs = self.check_versioned( + detailed=self.params['detailed'], + filters=self.params['filters'], + all_projects=self.params['all_projects'] + ) + if self.params['server']: + kwargs['name_or_id'] = self.params['server'] + openstack_servers = self.conn.search_servers(**kwargs) + self.exit(changed=False, openstack_servers=openstack_servers) + + +def main(): + module = ServerInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_metadata.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_metadata.py new file mode 100644 index 00000000..acc16d31 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_metadata.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright (c) 2016, Mario Santos <mario.rf.santos@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: server_metadata +short_description: Add/Update/Delete Metadata in Compute Instances from OpenStack +author: OpenStack Ansible SIG +description: + - Add, Update or Remove metadata in compute instances from OpenStack. +options: + server: + description: + - Name of the instance to update the metadata + required: true + aliases: ['name'] + type: str + meta: + description: + - 'A list of key value pairs that should be provided as a metadata to + the instance or a string containing a list of key-value pairs. + Eg: meta: "key1=value1,key2=value2"' + required: true + type: dict + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + availability_zone: + description: + - Availability zone in which to create the snapshot. + required: false + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Creates or updates hostname=test1 as metadata of the server instance vm1 +- name: add metadata to compute instance + hosts: localhost + tasks: + - name: add metadata to instance + openstack.cloud.server_metadata: + state: present + auth: + auth_url: https://openstack-api.example.com:35357/v2.0/ + username: admin + password: admin + project_name: admin + name: vm1 + meta: + hostname: test1 + group: group1 + +# Removes the keys under meta from the instance named vm1 +- name: delete metadata from compute instance + hosts: localhost + tasks: + - name: delete metadata from instance + openstack.cloud.server_metadata: + state: absent + auth: + auth_url: https://openstack-api.example.com:35357/v2.0/ + username: admin + password: admin + project_name: admin + name: vm1 + meta: + hostname: + group: +''' + +RETURN = ''' +server_id: + description: The compute instance id where the change was made + returned: success + type: str + sample: "324c4e91-3e03-4f62-9a4d-06119a8a8d16" +metadata: + description: The metadata of compute instance after the change + returned: success + type: dict + sample: {'key1': 'value1', 'key2': 'value2'} +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module, +) + + +def _needs_update(server_metadata=None, metadata=None): + if server_metadata is None: + server_metadata = {} + if metadata is None: + metadata = {} + return len(set(metadata.items()) - set(server_metadata.items())) != 0 + + +def _get_keys_to_delete(server_metadata_keys=None, metadata_keys=None): + if server_metadata_keys is None: + server_metadata_keys = [] + if metadata_keys is None: + metadata_keys = [] + return set(server_metadata_keys) & set(metadata_keys) + + +def main(): + argument_spec = openstack_full_argument_spec( + server=dict(required=True, aliases=['name']), + meta=dict(required=True, type='dict'), + state=dict(default='present', choices=['absent', 'present']), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + state = module.params['state'] + server_param = module.params['server'] + meta_param = module.params['meta'] + changed = False + + sdk, cloud = openstack_cloud_from_module(module) + try: + server = cloud.get_server(server_param) + if not server: + module.fail_json( + msg='Could not find server {0}'.format(server_param)) + + if state == 'present': + # check if it needs update + if _needs_update(server_metadata=server.metadata, + metadata=meta_param): + if not module.check_mode: + cloud.set_server_metadata(server_param, meta_param) + changed = True + elif state == 'absent': + # remove from params the keys that do not exist in the server + keys_to_delete = _get_keys_to_delete(server.metadata.keys(), + meta_param.keys()) + if len(keys_to_delete) > 0: + if not module.check_mode: + cloud.delete_server_metadata(server_param, keys_to_delete) + changed = True + + if changed: + server = cloud.get_server(server_param) + + module.exit_json( + changed=changed, server_id=server.id, metadata=server.metadata) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=e.message, extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_volume.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_volume.py new file mode 100644 index 00000000..9046723d --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_server_volume.py @@ -0,0 +1,136 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: server_volume +short_description: Attach/Detach Volumes from OpenStack VM's +author: OpenStack Ansible SIG +description: + - Attach or Detach volumes from OpenStack VM's +options: + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + required: false + type: str + server: + description: + - Name or ID of server you want to attach a volume to + required: true + type: str + volume: + description: + - Name or id of volume you want to attach to a server + required: true + type: str + device: + description: + - Device you want to attach. Defaults to auto finding a device name. + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Attaches a volume to a compute host +- name: attach a volume + hosts: localhost + tasks: + - name: attach volume to host + openstack.cloud.server_volume: + state: present + cloud: mordred + server: Mysql-server + volume: mysql-data + device: /dev/vdb +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +def _system_state_change(state, device): + """Check if system state would change.""" + if state == 'present': + if device: + return False + return True + if state == 'absent': + if device: + return True + return False + return False + + +class ServerVolumeModule(OpenStackModule): + + argument_spec = dict( + server=dict(required=True), + volume=dict(required=True), + device=dict(default=None), # None == auto choose device name + state=dict(default='present', choices=['absent', 'present']), + ) + + def run(self): + + state = self.params['state'] + wait = self.params['wait'] + timeout = self.params['timeout'] + + server = self.conn.get_server(self.params['server']) + volume = self.conn.get_volume(self.params['volume']) + + if not volume: + self.fail(msg='volume %s is not found' % self.params['volume']) + + dev = self.conn.get_volume_attach_device(volume, server.id) + + if self.ansible.check_mode: + self.exit(changed=_system_state_change(state, dev)) + + if state == 'present': + changed = False + if not dev: + changed = True + self.conn.attach_volume(server, volume, self.params['device'], + wait=wait, timeout=timeout) + + server = self.conn.get_server(self.params['server']) # refresh + volume = self.conn.get_volume(self.params['volume']) # refresh + hostvars = self.conn.get_openstack_vars(server) + + self.exit( + changed=changed, + id=volume['id'], + attachments=volume['attachments'], + openstack=hostvars + ) + + elif state == 'absent': + if not dev: + # Volume is not attached to this server + self.exit(changed=False) + + self.conn.detach_volume(server, volume, wait=wait, timeout=timeout) + self.exit( + changed=True, + result='Detached volume from server' + ) + + +def main(): + module = ServerVolumeModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_stack.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_stack.py new file mode 100644 index 00000000..3d1d6853 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_stack.py @@ -0,0 +1,274 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (c) 2016, Mathieu Bultel <mbultel@redhat.com> +# (c) 2016, Steve Baker <sbaker@redhat.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: stack +short_description: Add/Remove Heat Stack +author: OpenStack Ansible SIG +description: + - Add or Remove a Stack to an OpenStack Heat +options: + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + type: str + name: + description: + - Name of the stack that should be created, name could be char and digit, no space + required: true + type: str + tag: + description: + - Tag for the stack that should be created, name could be char and digit, no space + type: str + template: + description: + - Path of the template file to use for the stack creation + type: str + environment: + description: + - List of environment files that should be used for the stack creation + type: list + elements: str + parameters: + description: + - Dictionary of parameters for the stack creation + type: dict + rollback: + description: + - Rollback stack creation + type: bool + default: false + timeout: + description: + - Maximum number of seconds to wait for the stack creation + default: 3600 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' +EXAMPLES = ''' +--- +- name: create stack + ignore_errors: True + register: stack_create + openstack.cloud.stack: + name: "{{ stack_name }}" + tag: "{{ tag_name }}" + state: present + template: "/path/to/my_stack.yaml" + environment: + - /path/to/resource-registry.yaml + - /path/to/environment.yaml + parameters: + bmc_flavor: m1.medium + bmc_image: CentOS + key_name: default + private_net: "{{ private_net_param }}" + node_count: 2 + name: undercloud + image: CentOS + my_flavor: m1.large + external_net: "{{ external_net_param }}" +''' + +RETURN = ''' +id: + description: Stack ID. + type: str + sample: "97a3f543-8136-4570-920e-fd7605c989d6" + returned: always + +stack: + description: stack info + type: complex + returned: always + contains: + action: + description: Action, could be Create or Update. + type: str + sample: "CREATE" + creation_time: + description: Time when the action has been made. + type: str + sample: "2016-07-05T17:38:12Z" + description: + description: Description of the Stack provided in the heat template. + type: str + sample: "HOT template to create a new instance and networks" + id: + description: Stack ID. + type: str + sample: "97a3f543-8136-4570-920e-fd7605c989d6" + name: + description: Name of the Stack + type: str + sample: "test-stack" + identifier: + description: Identifier of the current Stack action. + type: str + sample: "test-stack/97a3f543-8136-4570-920e-fd7605c989d6" + links: + description: Links to the current Stack. + type: list + elements: dict + sample: "[{'href': 'http://foo:8004/v1/7f6a/stacks/test-stack/97a3f543-8136-4570-920e-fd7605c989d6']" + outputs: + description: Output returned by the Stack. + type: list + elements: dict + sample: "{'description': 'IP address of server1 in private network', + 'output_key': 'server1_private_ip', + 'output_value': '10.1.10.103'}" + parameters: + description: Parameters of the current Stack + type: dict + sample: "{'OS::project_id': '7f6a3a3e01164a4eb4eecb2ab7742101', + 'OS::stack_id': '97a3f543-8136-4570-920e-fd7605c989d6', + 'OS::stack_name': 'test-stack', + 'stack_status': 'CREATE_COMPLETE', + 'stack_status_reason': 'Stack CREATE completed successfully', + 'status': 'COMPLETE', + 'template_description': 'HOT template to create a new instance and networks', + 'timeout_mins': 60, + 'updated_time': null}" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) +from ansible.module_utils._text import to_native +from distutils.version import StrictVersion + + +def _create_stack(module, stack, cloud, sdk, parameters): + try: + stack = cloud.create_stack(module.params['name'], + template_file=module.params['template'], + environment_files=module.params['environment'], + timeout=module.params['timeout'], + wait=True, + rollback=module.params['rollback'], + **parameters) + + stack = cloud.get_stack(stack.id, None) + if stack.stack_status == 'CREATE_COMPLETE': + return stack + else: + module.fail_json(msg="Failure in creating stack: {0}".format(stack)) + except sdk.exceptions.OpenStackCloudException as e: + if hasattr(e, 'response'): + module.fail_json(msg=to_native(e), response=e.response.json()) + else: + module.fail_json(msg=to_native(e)) + + +def _update_stack(module, stack, cloud, sdk, parameters): + try: + stack = cloud.update_stack( + module.params['name'], + template_file=module.params['template'], + environment_files=module.params['environment'], + timeout=module.params['timeout'], + rollback=module.params['rollback'], + wait=module.params['wait'], + **parameters) + + if stack['stack_status'] == 'UPDATE_COMPLETE': + return stack + else: + module.fail_json(msg="Failure in updating stack: %s" % + stack['stack_status_reason']) + except sdk.exceptions.OpenStackCloudException as e: + if hasattr(e, 'response'): + module.fail_json(msg=to_native(e), response=e.response.json()) + else: + module.fail_json(msg=to_native(e)) + + +def _system_state_change(module, stack, cloud): + state = module.params['state'] + if state == 'present': + if not stack: + return True + if state == 'absent' and stack: + return True + return False + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + tag=dict(required=False, default=None), + template=dict(default=None), + environment=dict(default=None, type='list', elements='str'), + parameters=dict(default={}, type='dict'), + rollback=dict(default=False, type='bool'), + timeout=dict(default=3600, type='int'), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + state = module.params['state'] + name = module.params['name'] + # Check for required parameters when state == 'present' + if state == 'present': + for p in ['template']: + if not module.params[p]: + module.fail_json(msg='%s required with present state' % p) + + sdk, cloud = openstack_cloud_from_module(module) + try: + stack = cloud.get_stack(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, stack, cloud)) + + if state == 'present': + parameters = module.params['parameters'] + if module.params['tag']: + parameters['tags'] = module.params['tag'] + min_version = '0.28.0' + if StrictVersion(sdk.version.__version__) < StrictVersion(min_version) and stack: + module.warn("To update tags using openstack.cloud.stack module, the" + "installed version of the openstacksdk" + "library MUST be >={min_version}" + "".format(min_version=min_version)) + if not stack: + stack = _create_stack(module, stack, cloud, sdk, parameters) + else: + stack = _update_stack(module, stack, cloud, sdk, parameters) + module.exit_json(changed=True, + stack=stack, + id=stack.id) + elif state == 'absent': + if not stack: + changed = False + else: + changed = True + if not cloud.delete_stack(name, wait=module.params['wait']): + module.fail_json(msg='delete stack failed for stack: %s' % name) + module.exit_json(changed=changed) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=to_native(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_subnet.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_subnet.py new file mode 100644 index 00000000..84861877 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_subnet.py @@ -0,0 +1,368 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (c) 2013, Benno Joy <benno@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: subnet +short_description: Add/Remove subnet to an OpenStack network +author: OpenStack Ansible SIG +description: + - Add or Remove a subnet to an OpenStack network +options: + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + type: str + network_name: + description: + - Name of the network to which the subnet should be attached + - Required when I(state) is 'present' + type: str + name: + description: + - The name of the subnet that should be created. Although Neutron + allows for non-unique subnet names, this module enforces subnet + name uniqueness. + required: true + type: str + cidr: + description: + - The CIDR representation of the subnet that should be assigned to + the subnet. Required when I(state) is 'present' and a subnetpool + is not specified. + type: str + ip_version: + description: + - The IP version of the subnet 4 or 6 + default: 4 + type: str + choices: ['4', '6'] + enable_dhcp: + description: + - Whether DHCP should be enabled for this subnet. + type: bool + default: 'yes' + gateway_ip: + description: + - The ip that would be assigned to the gateway for this subnet + type: str + no_gateway_ip: + description: + - The gateway IP would not be assigned for this subnet + type: bool + default: 'no' + dns_nameservers: + description: + - List of DNS nameservers for this subnet. + type: list + elements: str + allocation_pool_start: + description: + - From the subnet pool the starting address from which the IP should + be allocated. + type: str + allocation_pool_end: + description: + - From the subnet pool the last IP that should be assigned to the + virtual machines. + type: str + host_routes: + description: + - A list of host route dictionaries for the subnet. + type: list + elements: dict + suboptions: + destination: + description: The destination network (CIDR). + type: str + required: true + nexthop: + description: The next hop (aka gateway) for the I(destination). + type: str + required: true + ipv6_ra_mode: + description: + - IPv6 router advertisement mode + choices: ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'] + type: str + ipv6_address_mode: + description: + - IPv6 address mode + choices: ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'] + type: str + use_default_subnetpool: + description: + - Use the default subnetpool for I(ip_version) to obtain a CIDR. + type: bool + default: 'no' + project: + description: + - Project name or ID containing the subnet (name admin-only) + type: str + extra_specs: + description: + - Dictionary with extra key/value pairs passed to the API + required: false + default: {} + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a new (or update an existing) subnet on the specified network +- openstack.cloud.subnet: + state: present + network_name: network1 + name: net1subnet + cidr: 192.168.0.0/24 + dns_nameservers: + - 8.8.8.7 + - 8.8.8.8 + host_routes: + - destination: 0.0.0.0/0 + nexthop: 12.34.56.78 + - destination: 192.168.0.0/24 + nexthop: 192.168.0.1 + +# Delete a subnet +- openstack.cloud.subnet: + state: absent + name: net1subnet + +# Create an ipv6 stateless subnet +- openstack.cloud.subnet: + state: present + name: intv6 + network_name: internal + ip_version: 6 + cidr: 2db8:1::/64 + dns_nameservers: + - 2001:4860:4860::8888 + - 2001:4860:4860::8844 + ipv6_ra_mode: dhcpv6-stateless + ipv6_address_mode: dhcpv6-stateless +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class SubnetModule(OpenStackModule): + ipv6_mode_choices = ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'] + argument_spec = dict( + name=dict(type='str', required=True), + network_name=dict(type='str'), + cidr=dict(type='str'), + ip_version=dict(type='str', default='4', choices=['4', '6']), + enable_dhcp=dict(type='bool', default=True), + gateway_ip=dict(type='str'), + no_gateway_ip=dict(type='bool', default=False), + dns_nameservers=dict(type='list', default=None, elements='str'), + allocation_pool_start=dict(type='str'), + allocation_pool_end=dict(type='str'), + host_routes=dict(type='list', default=None, elements='dict'), + ipv6_ra_mode=dict(type='str', choices=ipv6_mode_choices), + ipv6_address_mode=dict(type='str', choices=ipv6_mode_choices), + use_default_subnetpool=dict(type='bool', default=False), + extra_specs=dict(type='dict', default=dict()), + state=dict(type='str', default='present', choices=['absent', 'present']), + project=dict(type='str'), + ) + + module_kwargs = dict( + supports_check_mode=True, + required_together=[['allocation_pool_end', 'allocation_pool_start']] + ) + + def _can_update(self, subnet, filters=None): + """Check for differences in non-updatable values""" + network_name = self.params['network_name'] + ip_version = int(self.params['ip_version']) + ipv6_ra_mode = self.params['ipv6_ra_mode'] + ipv6_a_mode = self.params['ipv6_address_mode'] + + if network_name: + network = self.conn.get_network(network_name, filters) + if network: + netid = network['id'] + if netid != subnet['network_id']: + self.fail_json(msg='Cannot update network_name in existing subnet') + else: + self.fail_json(msg='No network found for %s' % network_name) + + if ip_version and subnet['ip_version'] != ip_version: + self.fail_json(msg='Cannot update ip_version in existing subnet') + if ipv6_ra_mode and subnet.get('ipv6_ra_mode', None) != ipv6_ra_mode: + self.fail_json(msg='Cannot update ipv6_ra_mode in existing subnet') + if ipv6_a_mode and subnet.get('ipv6_address_mode', None) != ipv6_a_mode: + self.fail_json(msg='Cannot update ipv6_address_mode in existing subnet') + + def _needs_update(self, subnet, filters=None): + """Check for differences in the updatable values.""" + + # First check if we are trying to update something we're not allowed to + self._can_update(subnet, filters) + + # now check for the things we are allowed to update + enable_dhcp = self.params['enable_dhcp'] + subnet_name = self.params['name'] + pool_start = self.params['allocation_pool_start'] + pool_end = self.params['allocation_pool_end'] + gateway_ip = self.params['gateway_ip'] + no_gateway_ip = self.params['no_gateway_ip'] + dns = self.params['dns_nameservers'] + host_routes = self.params['host_routes'] + curr_pool = dict(start=pool_start, end=pool_end) + + if subnet['enable_dhcp'] != enable_dhcp: + return True + if subnet_name and subnet['name'] != subnet_name: + return True + if not subnet['allocation_pools'] and pool_start and pool_end: + return True + if subnet['allocation_pools'] != [curr_pool]: + return True + if gateway_ip and subnet['gateway_ip'] != gateway_ip: + return True + if dns and sorted(subnet['dns_nameservers']) != sorted(dns): + return True + if host_routes: + curr_hr = sorted(subnet['host_routes'], key=lambda t: t.keys()) + new_hr = sorted(host_routes, key=lambda t: t.keys()) + if curr_hr != new_hr: + return True + if no_gateway_ip and subnet['gateway_ip']: + return True + return False + + def _system_state_change(self, subnet, filters=None): + state = self.params['state'] + if state == 'present': + if not subnet: + return True + return self._needs_update(subnet, filters) + if state == 'absent' and subnet: + return True + return False + + def run(self): + + state = self.params['state'] + network_name = self.params['network_name'] + cidr = self.params['cidr'] + ip_version = self.params['ip_version'] + enable_dhcp = self.params['enable_dhcp'] + subnet_name = self.params['name'] + gateway_ip = self.params['gateway_ip'] + no_gateway_ip = self.params['no_gateway_ip'] + dns = self.params['dns_nameservers'] + pool_start = self.params['allocation_pool_start'] + pool_end = self.params['allocation_pool_end'] + host_routes = self.params['host_routes'] + ipv6_ra_mode = self.params['ipv6_ra_mode'] + ipv6_a_mode = self.params['ipv6_address_mode'] + use_default_subnetpool = self.params['use_default_subnetpool'] + project = self.params.pop('project') + extra_specs = self.params['extra_specs'] + + # Check for required parameters when state == 'present' + if state == 'present': + if not self.params['network_name']: + self.fail(msg='network_name required with present state') + if ( + not self.params['cidr'] + and not use_default_subnetpool + and not extra_specs.get('subnetpool_id', False) + ): + self.fail(msg='cidr or use_default_subnetpool or ' + 'subnetpool_id required with present state') + + if pool_start and pool_end: + pool = [dict(start=pool_start, end=pool_end)] + else: + pool = None + + if no_gateway_ip and gateway_ip: + self.fail_json(msg='no_gateway_ip is not allowed with gateway_ip') + + if project is not None: + proj = self.conn.get_project(project) + if proj is None: + self.fail_json(msg='Project %s could not be found' % project) + project_id = proj['id'] + filters = {'tenant_id': project_id} + else: + project_id = None + filters = None + + subnet = self.conn.get_subnet(subnet_name, filters=filters) + + if self.ansible.check_mode: + self.exit_json(changed=self._system_state_change(subnet, filters)) + + if state == 'present': + if not subnet: + kwargs = dict( + cidr=cidr, + ip_version=ip_version, + enable_dhcp=enable_dhcp, + subnet_name=subnet_name, + gateway_ip=gateway_ip, + disable_gateway_ip=no_gateway_ip, + dns_nameservers=dns, + allocation_pools=pool, + host_routes=host_routes, + ipv6_ra_mode=ipv6_ra_mode, + ipv6_address_mode=ipv6_a_mode, + tenant_id=project_id) + dup_args = set(kwargs.keys()) & set(extra_specs.keys()) + if dup_args: + raise ValueError('Duplicate key(s) {0} in extra_specs' + .format(list(dup_args))) + if use_default_subnetpool: + kwargs['use_default_subnetpool'] = use_default_subnetpool + kwargs = dict(kwargs, **extra_specs) + subnet = self.conn.create_subnet(network_name, **kwargs) + changed = True + else: + if self._needs_update(subnet, filters): + subnet = self.conn.update_subnet(subnet['id'], + subnet_name=subnet_name, + enable_dhcp=enable_dhcp, + gateway_ip=gateway_ip, + disable_gateway_ip=no_gateway_ip, + dns_nameservers=dns, + allocation_pools=pool, + host_routes=host_routes) + changed = True + else: + changed = False + self.exit_json(changed=changed, + subnet=subnet, + id=subnet['id']) + + elif state == 'absent': + if not subnet: + changed = False + else: + changed = True + self.conn.delete_subnet(subnet_name) + self.exit_json(changed=changed) + + +def main(): + module = SubnetModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_subnets_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_subnets_info.py new file mode 100644 index 00000000..ac18b90c --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_subnets_info.py @@ -0,0 +1,161 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: subnets_info +short_description: Retrieve information about one or more OpenStack subnets. +author: OpenStack Ansible SIG +description: + - Retrieve information about one or more subnets from OpenStack. + - This module was called C(openstack.cloud.subnets_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.subnets_info) module no longer returns C(ansible_facts)! +options: + name: + description: + - Name or ID of the subnet. + - Alias 'subnet' added in version 2.8. + required: false + aliases: ['subnet'] + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + required: false + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Gather information about previously created subnets + openstack.cloud.subnets_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + register: result + +- name: Show openstack subnets + debug: + msg: "{{ result.openstack_subnets }}" + +- name: Gather information about a previously created subnet by name + openstack.cloud.subnets_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + name: subnet1 + register: result + +- name: Show openstack subnets + debug: + msg: "{{ result.openstack_subnets }}" + +- name: Gather information about a previously created subnet with filter + # Note: name and filters parameters are not mutually exclusive + openstack.cloud.subnets_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + filters: + tenant_id: 55e2ce24b2a245b09f181bf025724cbe + register: result + +- name: Show openstack subnets + debug: + msg: "{{ result.openstack_subnets }}" +''' + +RETURN = ''' +openstack_subnets: + description: has all the openstack information about the subnets + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the subnet. + returned: success + type: str + network_id: + description: Network ID this subnet belongs in. + returned: success + type: str + cidr: + description: Subnet's CIDR. + returned: success + type: str + gateway_ip: + description: Subnet's gateway ip. + returned: success + type: str + enable_dhcp: + description: DHCP enable flag for this subnet. + returned: success + type: bool + ip_version: + description: IP version for this subnet. + returned: success + type: int + tenant_id: + description: Tenant id associated with this subnet. + returned: success + type: str + dns_nameservers: + description: DNS name servers for this subnet. + returned: success + type: list + elements: str + allocation_pools: + description: Allocation pools associated with this subnet. + returned: success + type: list + elements: dict +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class SubnetInfoModule(OpenStackModule): + + deprecated_names = ('subnets_facts', 'openstack.cloud.subnets_facts') + + argument_spec = dict( + name=dict(required=False, default=None, aliases=['subnet']), + filters=dict(required=False, type='dict', default=None) + ) + + def run(self): + kwargs = self.check_versioned( + filters=self.params['filters'] + ) + if self.params['name']: + kwargs['name_or_id'] = self.params['name'] + subnets = self.conn.search_subnets(**kwargs) + + self.exit(changed=False, openstack_subnets=subnets) + + +def main(): + module = SubnetInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_user.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_user.py new file mode 100644 index 00000000..79ee5f23 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_user.py @@ -0,0 +1,293 @@ +#!/usr/bin/python +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_user +short_description: Manage OpenStack Identity Users +author: OpenStack Ansible SIG +description: + - Manage OpenStack Identity users. Users can be created, + updated or deleted using this module. A user will be updated + if I(name) matches an existing user and I(state) is present. + The value for I(name) cannot be updated without deleting and + re-creating the user. +options: + name: + description: + - Username for the user + required: true + type: str + password: + description: + - Password for the user + type: str + update_password: + required: false + choices: ['always', 'on_create'] + description: + - C(always) will attempt to update password. C(on_create) will only + set the password for newly created users. + type: str + email: + description: + - Email address for the user + type: str + description: + description: + - Description about the user + type: str + default_project: + description: + - Project name or ID that the user should be associated with by default + type: str + domain: + description: + - Domain to create the user in if the cloud supports domains + type: str + enabled: + description: + - Is the user enabled + type: bool + default: 'yes' + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a user +- openstack.cloud.identity_user: + cloud: mycloud + state: present + name: demouser + password: secret + email: demo@example.com + domain: default + default_project: demo + +# Delete a user +- openstack.cloud.identity_user: + cloud: mycloud + state: absent + name: demouser + +# Create a user but don't update password if user exists +- openstack.cloud.identity_user: + cloud: mycloud + state: present + name: demouser + password: secret + update_password: on_create + email: demo@example.com + domain: default + default_project: demo + +# Create a user without password +- openstack.cloud.identity_user: + cloud: mycloud + state: present + name: demouser + email: demo@example.com + domain: default + default_project: demo +''' + + +RETURN = ''' +user: + description: Dictionary describing the user. + returned: On success when I(state) is 'present' + type: complex + contains: + default_project_id: + description: User default project ID. Only present with Keystone >= v3. + type: str + sample: "4427115787be45f08f0ec22a03bfc735" + domain_id: + description: User domain ID. Only present with Keystone >= v3. + type: str + sample: "default" + email: + description: User email address + type: str + sample: "demo@example.com" + id: + description: User ID + type: str + sample: "f59382db809c43139982ca4189404650" + name: + description: User name + type: str + sample: "demouser" +''' +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _needs_update(params_dict, user): + for k in params_dict: + if k not in ('password', 'update_password') and user[k] != params_dict[k]: + return True + + # We don't get password back in the user object, so assume any supplied + # password is a change. + if ( + params_dict['password'] is not None + and params_dict['update_password'] == 'always' + ): + return True + + return False + + +def _get_domain_id(cloud, domain): + try: + # We assume admin is passing domain id + domain_id = cloud.get_domain(domain)['id'] + except Exception: + # If we fail, maybe admin is passing a domain name. + # Note that domains have unique names, just like id. + try: + domain_id = cloud.search_domains(filters={'name': domain})[0]['id'] + except Exception: + # Ok, let's hope the user is non-admin and passing a sane id + domain_id = domain + + return domain_id + + +def _get_default_project_id(cloud, default_project, domain_id, module): + project = cloud.get_project(default_project, domain_id=domain_id) + if not project: + module.fail_json(msg='Default project %s is not valid' % default_project) + + return project['id'] + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + password=dict(required=False, default=None, no_log=True), + email=dict(required=False, default=None), + default_project=dict(required=False, default=None), + description=dict(type='str'), + domain=dict(required=False, default=None), + enabled=dict(default=True, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + update_password=dict(default=None, choices=['always', 'on_create']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule( + argument_spec, + **module_kwargs) + + name = module.params['name'] + password = module.params.get('password') + email = module.params['email'] + default_project = module.params['default_project'] + domain = module.params['domain'] + enabled = module.params['enabled'] + state = module.params['state'] + update_password = module.params['update_password'] + description = module.params['description'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + domain_id = None + if domain: + domain_id = _get_domain_id(cloud, domain) + user = cloud.get_user(name, domain_id=domain_id) + else: + user = cloud.get_user(name) + + if state == 'present': + if update_password in ('always', 'on_create'): + if not password: + msg = "update_password is %s but a password value is missing" % update_password + module.fail_json(msg=msg) + default_project_id = None + if default_project: + default_project_id = _get_default_project_id(cloud, default_project, domain_id, module) + + if user is None: + if description is not None: + user = cloud.create_user( + name=name, password=password, email=email, + default_project=default_project_id, domain_id=domain_id, + enabled=enabled, description=description) + else: + user = cloud.create_user( + name=name, password=password, email=email, + default_project=default_project_id, domain_id=domain_id, + enabled=enabled) + changed = True + else: + params_dict = {'email': email, 'enabled': enabled, + 'password': password, + 'update_password': update_password} + if description is not None: + params_dict['description'] = description + if domain_id is not None: + params_dict['domain_id'] = domain_id + if default_project_id is not None: + params_dict['default_project_id'] = default_project_id + + if _needs_update(params_dict, user): + if update_password == 'always': + if description is not None: + user = cloud.update_user( + user['id'], password=password, email=email, + default_project=default_project_id, + domain_id=domain_id, enabled=enabled, description=description) + else: + user = cloud.update_user( + user['id'], password=password, email=email, + default_project=default_project_id, + domain_id=domain_id, enabled=enabled) + else: + if description is not None: + user = cloud.update_user( + user['id'], email=email, + default_project=default_project_id, + domain_id=domain_id, enabled=enabled, description=description) + else: + user = cloud.update_user( + user['id'], email=email, + default_project=default_project_id, + domain_id=domain_id, enabled=enabled) + changed = True + else: + changed = False + module.exit_json(changed=changed, user=user) + + elif state == 'absent': + if user is None: + changed = False + else: + if domain: + cloud.delete_user(user['id'], domain_id=domain_id) + else: + cloud.delete_user(user['id']) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_user_group.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_user_group.py new file mode 100644 index 00000000..59760acf --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_user_group.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: group_assignment +short_description: Associate OpenStack Identity users and groups +author: OpenStack Ansible SIG +description: + - Add and remove users from groups +options: + user: + description: + - Name or id for the user + required: true + type: str + group: + description: + - Name or id for the group. + required: true + type: str + state: + description: + - Should the user be present or absent in the group + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Add the demo user to the demo group +- openstack.cloud.group_assignment: + cloud: mycloud + user: demo + group: demo +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(state, in_group): + if state == 'present' and not in_group: + return True + if state == 'absent' and in_group: + return True + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + user=dict(required=True), + group=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + user = module.params['user'] + group = module.params['group'] + state = module.params['state'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + in_group = cloud.is_user_in_group(user, group) + + if module.check_mode: + module.exit_json(changed=_system_state_change(state, in_group)) + + changed = False + if state == 'present': + if not in_group: + cloud.add_user_to_group(user, group) + changed = True + + elif state == 'absent': + if in_group: + cloud.remove_user_from_group(user, group) + changed = True + + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_user_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_user_info.py new file mode 100644 index 00000000..9538f701 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_user_info.py @@ -0,0 +1,168 @@ +#!/usr/bin/python +# Copyright (c) 2016 Hewlett-Packard Enterprise Corporation +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: identity_user_info +short_description: Retrieve information about one or more OpenStack users +author: OpenStack Ansible SIG +description: + - Retrieve information about a one or more OpenStack users + - This module was called C(openstack.cloud.identity_user_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.identity_user_info) module no longer returns C(ansible_facts)! +options: + name: + description: + - Name or ID of the user + type: str + domain: + description: + - Name or ID of the domain containing the user if the cloud supports domains + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather information about previously created users +- openstack.cloud.identity_user_info: + cloud: awesomecloud + register: result +- debug: + msg: "{{ result.openstack_users }}" + +# Gather information about a previously created user by name +- openstack.cloud.identity_user_info: + cloud: awesomecloud + name: demouser + register: result +- debug: + msg: "{{ result.openstack_users }}" + +# Gather information about a previously created user in a specific domain +- openstack.cloud.identity_user_info: + cloud: awesomecloud + name: demouser + domain: admindomain + register: result +- debug: + msg: "{{ result.openstack_users }}" + +# Gather information about a previously created user in a specific domain with filter +- openstack.cloud.identity_user_info: + cloud: awesomecloud + name: demouser + domain: admindomain + filters: + enabled: False + register: result +- debug: + msg: "{{ result.openstack_users }}" +''' + + +RETURN = ''' +openstack_users: + description: has all the OpenStack information about users + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the user. + returned: success + type: str + enabled: + description: Flag to indicate if the user is enabled + returned: success + type: bool + domain_id: + description: Domain ID containing the user + returned: success + type: str + default_project_id: + description: Default project ID of the user + returned: success + type: str + email: + description: Email of the user + returned: success + type: str + username: + description: Username of the user + returned: success + type: str +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_full_argument_spec, + openstack_cloud_from_module, +) + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + domain=dict(required=False, default=None), + filters=dict(required=False, type='dict', default=None), + ) + + module = AnsibleModule(argument_spec) + is_old_facts = module._name == 'openstack.cloud.identity_user_facts' + if is_old_facts: + module.deprecate("The 'openstack.cloud.identity_user_facts' module has been renamed to 'openstack.cloud.identity_user_info', " + "and the renamed one no longer returns ansible_facts", version='2.13') + + sdk, opcloud = openstack_cloud_from_module(module) + try: + name = module.params['name'] + domain = module.params['domain'] + filters = module.params['filters'] + + if domain: + try: + # We assume admin is passing domain id + dom = opcloud.get_domain(domain)['id'] + domain = dom + except Exception: + # If we fail, maybe admin is passing a domain name. + # Note that domains have unique names, just like id. + dom = opcloud.search_domains(filters={'name': domain}) + if dom: + domain = dom[0]['id'] + else: + module.fail_json(msg='Domain name or ID does not exist') + + if not filters: + filters = {} + + filters['domain_id'] = domain + + users = opcloud.search_users(name, filters) + if is_old_facts: + module.exit_json(changed=False, ansible_facts=dict( + openstack_users=users)) + else: + module.exit_json(changed=False, openstack_users=users) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_user_role.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_user_role.py new file mode 100644 index 00000000..bc615a21 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_user_role.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# Copyright (c) 2016 IBM +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: role_assignment +short_description: Associate OpenStack Identity users and roles +author: OpenStack Ansible SIG +description: + - Grant and revoke roles in either project or domain context for + OpenStack Identity Users. +options: + role: + description: + - Name or ID for the role. + required: true + type: str + user: + description: + - Name or ID for the user. If I(user) is not specified, then + I(group) is required. Both may not be specified. + type: str + group: + description: + - Name or ID for the group. Valid only with keystone version 3. + If I(group) is not specified, then I(user) is required. Both + may not be specified. + type: str + project: + description: + - Name or ID of the project to scope the role association to. + If you are using keystone version 2, then this value is required. + type: str + domain: + description: + - Name or ID of the domain to scope the role association to. Valid only + with keystone version 3, and required if I(project) is not specified. + type: str + state: + description: + - Should the roles be present or absent on the user. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Grant an admin role on the user admin in the project project1 +- openstack.cloud.role_assignment: + cloud: mycloud + user: admin + role: admin + project: project1 + +# Revoke the admin role from the user barney in the newyork domain +- openstack.cloud.role_assignment: + cloud: mycloud + state: absent + user: barney + role: admin + domain: newyork +''' + +RETURN = ''' +# +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(state, assignment): + if state == 'present' and not assignment: + return True + elif state == 'absent' and assignment: + return True + return False + + +def _build_kwargs(user, group, project, domain): + kwargs = {} + if user: + kwargs['user'] = user + if group: + kwargs['group'] = group + if project: + kwargs['project'] = project + if domain: + kwargs['domain'] = domain + return kwargs + + +def main(): + argument_spec = openstack_full_argument_spec( + role=dict(required=True), + user=dict(required=False), + group=dict(required=False), + project=dict(required=False), + domain=dict(required=False), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs( + required_one_of=[ + ['user', 'group'] + ]) + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + role = module.params.get('role') + user = module.params.get('user') + group = module.params.get('group') + project = module.params.get('project') + domain = module.params.get('domain') + state = module.params.get('state') + + sdk, cloud = openstack_cloud_from_module(module) + try: + filters = {} + domain_id = None + + r = cloud.get_role(role) + if r is None: + module.fail_json(msg="Role %s is not valid" % role) + filters['role'] = r['id'] + + if domain: + d = cloud.get_domain(name_or_id=domain) + if d is None: + module.fail_json(msg="Domain %s is not valid" % domain) + filters['domain'] = d['id'] + domain_id = d['id'] + if user: + if domain: + u = cloud.get_user(user, domain_id=filters['domain']) + else: + u = cloud.get_user(user) + + if u is None: + module.fail_json(msg="User %s is not valid" % user) + filters['user'] = u['id'] + if group: + if domain: + g = cloud.get_group(group, domain_id=filters['domain']) + else: + g = cloud.get_group(group) + if g is None: + module.fail_json(msg="Group %s is not valid" % group) + filters['group'] = g['id'] + if project: + if domain: + p = cloud.get_project(project, domain_id=filters['domain']) + # OpenStack won't allow us to use both a domain and project as + # filter. Once we identified the project (using the domain as + # a filter criteria), we need to remove the domain itself from + # the filters list. + domain_id = filters.pop('domain') + else: + p = cloud.get_project(project) + + if p is None: + module.fail_json(msg="Project %s is not valid" % project) + filters['project'] = p['id'] + + assignment = cloud.list_role_assignments(filters=filters) + + if module.check_mode: + module.exit_json(changed=_system_state_change(state, assignment)) + + changed = False + + if state == 'present': + if not assignment: + kwargs = _build_kwargs(user, group, project, domain_id) + cloud.grant_role(role, **kwargs) + changed = True + + elif state == 'absent': + if assignment: + kwargs = _build_kwargs(user, group, project, domain_id) + cloud.revoke_role(role, **kwargs) + changed = True + + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_volume.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_volume.py new file mode 100644 index 00000000..8e953c74 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_volume.py @@ -0,0 +1,261 @@ +#!/usr/bin/python + +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: volume +short_description: Create/Delete Cinder Volumes +author: OpenStack Ansible SIG +description: + - Create or Remove cinder block storage volumes +options: + size: + description: + - Size of volume in GB. This parameter is required when the + I(state) parameter is 'present'. + type: int + display_name: + description: + - Name of volume + required: true + type: str + aliases: [name] + display_description: + description: + - String describing the volume + type: str + aliases: [description] + volume_type: + description: + - Volume type for volume + type: str + image: + description: + - Image name or id for boot from volume + type: str + snapshot_id: + description: + - Volume snapshot id to create from + type: str + volume: + description: + - Volume name or id to create from + type: str + bootable: + description: + - Bootable flag for volume. + type: bool + default: False + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + scheduler_hints: + description: + - Scheduler hints passed to volume API in form of dict + type: dict + metadata: + description: + - Metadata for the volume + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Creates a new volume +- name: create a volume + hosts: localhost + tasks: + - name: create 40g test volume + openstack.cloud.volume: + state: present + cloud: mordred + availability_zone: az2 + size: 40 + display_name: test_volume + scheduler_hints: + same_host: 243e8d3c-8f47-4a61-93d6-7215c344b0c0 +''' + +RETURNS = ''' +id: + description: Cinder's unique ID for this volume + returned: always + type: str + sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 + +volume: + description: Cinder's representation of the volume object + returned: always + type: dict + sample: {'...'} +''' +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class VolumeModule(OpenStackModule): + + argument_spec = dict( + size=dict(type='int'), + volume_type=dict(type='str'), + display_name=dict(required=True, aliases=['name'], type='str'), + display_description=dict(aliases=['description'], type='str'), + image=dict(type='str'), + snapshot_id=dict(type='str'), + volume=dict(type='str'), + state=dict(default='present', choices=['absent', 'present'], type='str'), + scheduler_hints=dict(type='dict'), + metadata=dict(type='dict'), + bootable=dict(type='bool', default=False) + ) + + module_kwargs = dict( + mutually_exclusive=[ + ['image', 'snapshot_id', 'volume'], + ], + required_if=[ + ['state', 'present', ['size']], + ], + ) + + def _needs_update(self, volume): + ''' + check for differences in updatable values, at the moment + openstacksdk only supports extending the volume size, this + may change in the future. + :returns: bool + ''' + compare_simple = ['size'] + + for k in compare_simple: + if self.params[k] is not None and self.params[k] != volume.get(k): + return True + + return False + + def _modify_volume(self, volume): + ''' + modify volume, the only modification to an existing volume + available at the moment is extending the size, this is + limited by the openstacksdk and may change whenever the + functionality is extended. + ''' + volume = self.conn.get_volume(self.params['display_name']) + diff = {'before': volume, 'after': ''} + size = self.params['size'] + + if size < volume.get('size'): + self.fail_json( + msg='Cannot shrink volumes, size: {0} < {1}'.format(size, volume.get('size')) + ) + + if not self._needs_update(volume): + diff['after'] = volume + self.exit_json(changed=False, id=volume['id'], volume=volume, diff=diff) + + if self.ansible.check_mode: + diff['after'] = volume + self.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff) + + self.conn.volume.extend_volume( + volume.id, + size + ) + diff['after'] = self.conn.get_volume(self.params['display_name']) + self.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff) + + def _present_volume(self): + + diff = {'before': '', 'after': ''} + + volume_args = dict( + size=self.params['size'], + volume_type=self.params['volume_type'], + display_name=self.params['display_name'], + display_description=self.params['display_description'], + snapshot_id=self.params['snapshot_id'], + bootable=self.params['bootable'], + availability_zone=self.params['availability_zone'], + ) + if self.params['image']: + image_id = self.conn.get_image_id(self.params['image']) + volume_args['imageRef'] = image_id + + if self.params['volume']: + volume_id = self.conn.get_volume_id(self.params['volume']) + if not volume_id: + self.fail_json(msg="Failed to find volume '%s'" % self.params['volume']) + volume_args['source_volid'] = volume_id + + if self.params['scheduler_hints']: + volume_args['scheduler_hints'] = self.params['scheduler_hints'] + + if self.params['metadata']: + volume_args['metadata'] = self.params['metadata'] + + if self.ansible.check_mode: + diff['after'] = volume_args + self.exit_json(changed=True, id=None, volume=volume_args, diff=diff) + + volume = self.conn.create_volume( + wait=self.params['wait'], timeout=self.params['timeout'], + **volume_args) + diff['after'] = volume + self.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff) + + def _absent_volume(self, volume): + changed = False + diff = {'before': '', 'after': ''} + + if self.conn.volume_exists(self.params['display_name']): + volume = self.conn.get_volume(self.params['display_name']) + diff['before'] = volume + + if self.ansible.check_mode: + self.exit_json(changed=True, diff=diff) + + try: + changed = self.conn.delete_volume(name_or_id=self.params['display_name'], + wait=self.params['wait'], + timeout=self.params['timeout']) + except self.sdk.exceptions.ResourceTimeout: + diff['after'] = volume + self.exit_json(changed=changed, diff=diff) + + self.exit_json(changed=changed, diff=diff) + + def run(self): + + state = self.params['state'] + if self.conn.volume_exists(self.params['display_name']): + volume = self.conn.get_volume(self.params['display_name']) + else: + volume = None + + if state == 'present': + if not volume: + self._present_volume() + elif self._needs_update(volume): + self._modify_volume(volume) + else: + self.exit_json(changed=False, id=volume['id'], volume=volume) + if state == 'absent': + self._absent_volume(volume) + + +def main(): + module = VolumeModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_volume_snapshot.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_volume_snapshot.py new file mode 100644 index 00000000..7c27fcc2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_volume_snapshot.py @@ -0,0 +1,174 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright (c) 2016, Mario Santos <mario.rf.santos@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: volume_snapshot +short_description: Create/Delete Cinder Volume Snapshots +author: OpenStack Ansible SIG +description: + - Create or Delete cinder block storage volume snapshots +options: + display_name: + description: + - Name of the snapshot + required: true + aliases: ['name'] + type: str + display_description: + description: + - String describing the snapshot + aliases: ['description'] + type: str + volume: + description: + - The volume name or id to create/delete the snapshot + required: True + type: str + force: + description: + - Allows or disallows snapshot of a volume to be created when the volume + is attached to an instance. + type: bool + default: 'no' + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Creates a snapshot on volume 'test_volume' +- name: create and delete snapshot + hosts: localhost + tasks: + - name: create snapshot + openstack.cloud.volume_snapshot: + state: present + cloud: mordred + availability_zone: az2 + display_name: test_snapshot + volume: test_volume + - name: delete snapshot + openstack.cloud.volume_snapshot: + state: absent + cloud: mordred + availability_zone: az2 + display_name: test_snapshot + volume: test_volume +''' + +RETURN = ''' +snapshot: + description: The snapshot instance after the change + returned: success + type: dict + sample: + id: 837aca54-c0ee-47a2-bf9a-35e1b4fdac0c + name: test_snapshot + volume_id: ec646a7c-6a35-4857-b38b-808105a24be6 + size: 2 + status: available + display_name: test_snapshot +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module, +) + + +def _present_volume_snapshot(module, cloud): + volume = cloud.get_volume(module.params['volume']) + snapshot = cloud.get_volume_snapshot(module.params['display_name'], + filters={'volume_id': volume.id}) + if not snapshot: + snapshot = cloud.create_volume_snapshot(volume.id, + force=module.params['force'], + wait=module.params['wait'], + timeout=module.params[ + 'timeout'], + name=module.params['display_name'], + description=module.params.get( + 'display_description') + ) + module.exit_json(changed=True, snapshot=snapshot) + else: + module.exit_json(changed=False, snapshot=snapshot) + + +def _absent_volume_snapshot(module, cloud): + volume = cloud.get_volume(module.params['volume']) + snapshot = cloud.get_volume_snapshot(module.params['display_name'], + filters={'volume_id': volume.id}) + if not snapshot: + module.exit_json(changed=False) + else: + cloud.delete_volume_snapshot(name_or_id=snapshot.id, + wait=module.params['wait'], + timeout=module.params['timeout'], + ) + module.exit_json(changed=True, snapshot_id=snapshot.id) + + +def _system_state_change(module, cloud): + volume = cloud.get_volume(module.params['volume']) + snapshot = cloud.get_volume_snapshot(module.params['display_name'], + filters={'volume_id': volume.id}) + state = module.params['state'] + + if state == 'present': + return snapshot is None + if state == 'absent': + return snapshot is not None + + +def main(): + argument_spec = openstack_full_argument_spec( + display_name=dict(required=True, aliases=['name']), + display_description=dict(default=None, aliases=['description']), + volume=dict(required=True), + force=dict(required=False, default=False, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + sdk, cloud = openstack_cloud_from_module(module) + + state = module.params['state'] + + try: + if cloud.volume_exists(module.params['volume']): + if module.check_mode: + module.exit_json(changed=_system_state_change(module, cloud)) + if state == 'present': + _present_volume_snapshot(module, cloud) + if state == 'absent': + _absent_volume_snapshot(module, cloud) + else: + module.fail_json( + msg="No volume with name or id '{0}' was found.".format( + module.params['volume'])) + except (sdk.exceptions.OpenStackCloudException, sdk.exceptions.ResourceTimeout) as e: + module.fail_json(msg=e.message) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_zone.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_zone.py new file mode 100644 index 00000000..98cf655e --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/os_zone.py @@ -0,0 +1,244 @@ +#!/usr/bin/python +# Copyright (c) 2016 Hewlett-Packard Enterprise +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: dns_zone +short_description: Manage OpenStack DNS zones +author: OpenStack Ansible SIG +description: + - Manage OpenStack DNS zones. Zones can be created, deleted or + updated. Only the I(email), I(description), I(ttl) and I(masters) values + can be updated. +options: + name: + description: + - Zone name + required: true + type: str + zone_type: + description: + - Zone type + choices: [primary, secondary] + type: str + email: + description: + - Email of the zone owner (only applies if zone_type is primary) + type: str + description: + description: + - Zone description + type: str + ttl: + description: + - TTL (Time To Live) value in seconds + type: int + masters: + description: + - Master nameservers (only applies if zone_type is secondary) + type: list + elements: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a zone named "example.net" +- openstack.cloud.dns_zone: + cloud: mycloud + state: present + name: example.net. + zone_type: primary + email: test@example.net + description: Test zone + ttl: 3600 + +# Update the TTL on existing "example.net." zone +- openstack.cloud.dns_zone: + cloud: mycloud + state: present + name: example.net. + ttl: 7200 + +# Delete zone named "example.net." +- openstack.cloud.dns_zone: + cloud: mycloud + state: absent + name: example.net. +''' + +RETURN = ''' +zone: + description: Dictionary describing the zone. + returned: On success when I(state) is 'present'. + type: complex + contains: + id: + description: Unique zone ID + type: str + sample: "c1c530a3-3619-46f3-b0f6-236927b2618c" + name: + description: Zone name + type: str + sample: "example.net." + type: + description: Zone type + type: str + sample: "PRIMARY" + email: + description: Zone owner email + type: str + sample: "test@example.net" + description: + description: Zone description + type: str + sample: "Test description" + ttl: + description: Zone TTL value + type: int + sample: 3600 + masters: + description: Zone master nameservers + type: list + sample: [] +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class DnsZoneModule(OpenStackModule): + + argument_spec = dict( + name=dict(required=True, type='str'), + zone_type=dict(required=False, choices=['primary', 'secondary'], type='str'), + email=dict(required=False, type='str'), + description=dict(required=False, type='str'), + ttl=dict(required=False, type='int'), + masters=dict(required=False, type='list', elements='str'), + state=dict(default='present', choices=['absent', 'present'], type='str'), + ) + + def _system_state_change(self, state, email, description, ttl, masters, zone): + if state == 'present': + if not zone: + return True + if email is not None and zone.email != email: + return True + if description is not None and zone.description != description: + return True + if ttl is not None and zone.ttl != ttl: + return True + if masters is not None and zone.masters != masters: + return True + if state == 'absent' and zone: + return True + return False + + def _wait(self, timeout, zone, state): + """Wait for a zone to reach the desired state for the given state.""" + + for count in self.sdk.utils.iterate_timeout( + timeout, + "Timeout waiting for zone to be %s" % state): + + if (state == 'absent' and zone is None) or (state == 'present' and zone and zone.status == 'ACTIVE'): + return + + try: + zone = self.conn.get_zone(zone.id) + except Exception: + continue + + if zone and zone.status == 'ERROR': + self.fail_json(msg="Zone reached ERROR state while waiting for it to be %s" % state) + + def run(self): + + name = self.params['name'] + state = self.params['state'] + wait = self.params['wait'] + timeout = self.params['timeout'] + + zone = self.conn.get_zone(name) + + if state == 'present': + + zone_type = self.params['zone_type'] + email = self.params['email'] + description = self.params['description'] + ttl = self.params['ttl'] + masters = self.params['masters'] + + kwargs = {} + + if email: + kwargs['email'] = email + if description: + kwargs['description'] = description + if ttl: + kwargs['ttl'] = ttl + if masters: + kwargs['masters'] = masters + + if self.ansible.check_mode: + self.exit_json(changed=self._system_state_change(state, email, + description, ttl, + masters, zone)) + + if zone is None: + zone = self.conn.create_zone( + name=name, zone_type=zone_type, **kwargs) + changed = True + else: + if masters is None: + masters = [] + + pre_update_zone = zone + changed = self._system_state_change(state, email, + description, ttl, + masters, pre_update_zone) + if changed: + zone = self.conn.update_zone( + name, **kwargs) + + if wait: + self._wait(timeout, zone, state) + + self.exit_json(changed=changed, zone=zone) + + elif state == 'absent': + if self.ansible.check_mode: + self.exit_json(changed=self._system_state_change(state, None, + None, None, + None, zone)) + + if zone is None: + changed = False + else: + self.conn.delete_zone(name) + changed = True + + if wait: + self._wait(timeout, zone, state) + + self.exit_json(changed=changed) + + +def main(): + module = DnsZoneModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/port.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/port.py new file mode 100644 index 00000000..79a48411 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/port.py @@ -0,0 +1,474 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: port +short_description: Add/Update/Delete ports from an OpenStack cloud. +author: OpenStack Ansible SIG +description: + - Add, Update or Remove ports from an OpenStack cloud. A I(state) of + 'present' will ensure the port is created or updated if required. +options: + network: + description: + - Network ID or name this port belongs to. + - Required when creating a new port. + type: str + name: + description: + - Name that has to be given to the port. + type: str + fixed_ips: + description: + - Desired IP and/or subnet for this port. Subnet is referenced by + subnet_id and IP is referenced by ip_address. + type: list + elements: dict + suboptions: + ip_address: + description: The fixed IP address to attempt to allocate. + required: true + type: str + subnet_id: + description: The subnet to attach the IP address to. + type: str + admin_state_up: + description: + - Sets admin state. + type: bool + mac_address: + description: + - MAC address of this port. + type: str + security_groups: + description: + - Security group(s) ID(s) or name(s) associated with the port (comma + separated string or YAML list) + type: list + elements: str + no_security_groups: + description: + - Do not associate a security group with this port. + type: bool + default: 'no' + allowed_address_pairs: + description: + - "Allowed address pairs list. Allowed address pairs are supported with + dictionary structure. + e.g. allowed_address_pairs: + - ip_address: 10.1.0.12 + mac_address: ab:cd:ef:12:34:56 + - ip_address: ..." + type: list + elements: dict + suboptions: + ip_address: + description: The IP address. + type: str + mac_address: + description: The MAC address. + type: str + extra_dhcp_opts: + description: + - "Extra dhcp options to be assigned to this port. Extra options are + supported with dictionary structure. Note that options cannot be removed + only updated. + e.g. extra_dhcp_opts: + - opt_name: opt name1 + opt_value: value1 + ip_version: 4 + - opt_name: ..." + type: list + elements: dict + suboptions: + opt_name: + description: The name of the DHCP option to set. + type: str + required: true + opt_value: + description: The value of the DHCP option to set. + type: str + required: true + ip_version: + description: The IP version this DHCP option is for. + type: int + required: true + device_owner: + description: + - The ID of the entity that uses this port. + type: str + device_id: + description: + - Device ID of device using this port. + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + vnic_type: + description: + - The type of the port that should be created + choices: [normal, direct, direct-physical, macvtap, baremetal, virtio-forwarder] + type: str + port_security_enabled: + description: + - Whether to enable or disable the port security on the network. + type: bool +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a port +- openstack.cloud.port: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: port1 + network: foo + +# Create a port with a static IP +- openstack.cloud.port: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: port1 + network: foo + fixed_ips: + - ip_address: 10.1.0.21 + +# Create a port with No security groups +- openstack.cloud.port: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: port1 + network: foo + no_security_groups: True + +# Update the existing 'port1' port with multiple security groups (version 1) +- openstack.cloud.port: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: port1 + security_groups: 1496e8c7-4918-482a-9172-f4f00fc4a3a5,057d4bdf-6d4d-472... + +# Update the existing 'port1' port with multiple security groups (version 2) +- openstack.cloud.port: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: port1 + security_groups: + - 1496e8c7-4918-482a-9172-f4f00fc4a3a5 + - 057d4bdf-6d4d-472... + +# Create port of type 'direct' +- openstack.cloud.port: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: port1 + network: foo + vnic_type: direct +''' + +RETURN = ''' +id: + description: Unique UUID. + returned: success + type: str +name: + description: Name given to the port. + returned: success + type: str +network_id: + description: Network ID this port belongs in. + returned: success + type: str +security_groups: + description: Security group(s) associated with this port. + returned: success + type: list +status: + description: Port's status. + returned: success + type: str +fixed_ips: + description: Fixed ip(s) associated with this port. + returned: success + type: list +tenant_id: + description: Tenant id associated with this port. + returned: success + type: str +allowed_address_pairs: + description: Allowed address pairs with this port. + returned: success + type: list +admin_state_up: + description: Admin state up flag for this port. + returned: success + type: bool +vnic_type: + description: Type of the created port + returned: success + type: str +port_security_enabled: + description: Port security state on the network. + returned: success + type: bool +''' + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + +try: + from collections import OrderedDict + HAS_ORDEREDDICT = True +except ImportError: + try: + from ordereddict import OrderedDict + HAS_ORDEREDDICT = True + except ImportError: + HAS_ORDEREDDICT = False + + +def _needs_update(module, port, cloud): + """Check for differences in the updatable values. + + NOTE: We don't currently allow name updates. + """ + compare_simple = ['admin_state_up', + 'mac_address', + 'device_owner', + 'device_id', + 'binding:vnic_type', + 'port_security_enabled'] + compare_list_dict = ['allowed_address_pairs', + 'extra_dhcp_opts'] + compare_list = ['security_groups'] + + for key in compare_simple: + if module.params[key] is not None and module.params[key] != port[key]: + return True + for key in compare_list: + if ( + module.params[key] is not None + and set(module.params[key]) != set(port[key]) + ): + return True + + for key in compare_list_dict: + if not module.params[key]: + if not port[key]: + return True + + # sort dicts in list + port_ordered = [OrderedDict(sorted(d.items())) for d in port[key]] + param_ordered = [OrderedDict(sorted(d.items())) for d in module.params[key]] + + for d in param_ordered: + if d not in port_ordered: + return True + + for d in port_ordered: + if d not in param_ordered: + return True + + # NOTE: if port was created or updated with 'no_security_groups=True', + # subsequent updates without 'no_security_groups' flag or + # 'no_security_groups=False' and no specified 'security_groups', will not + # result in an update to the port where the default security group is + # applied. + if module.params['no_security_groups'] and port['security_groups'] != []: + return True + + if module.params['fixed_ips'] is not None: + for item in module.params['fixed_ips']: + if 'ip_address' in item: + # if ip_address in request does not match any in existing port, + # update is required. + if not any(match['ip_address'] == item['ip_address'] + for match in port['fixed_ips']): + return True + if 'subnet_id' in item: + return True + for item in port['fixed_ips']: + # if ip_address in existing port does not match any in request, + # update is required. + if not any(match.get('ip_address') == item['ip_address'] + for match in module.params['fixed_ips']): + return True + + return False + + +def _system_state_change(module, port, cloud): + state = module.params['state'] + if state == 'present': + if not port: + return True + return _needs_update(module, port, cloud) + if state == 'absent' and port: + return True + return False + + +def _compose_port_args(module, cloud): + port_kwargs = {} + optional_parameters = ['name', + 'fixed_ips', + 'admin_state_up', + 'mac_address', + 'security_groups', + 'allowed_address_pairs', + 'extra_dhcp_opts', + 'device_owner', + 'device_id', + 'binding:vnic_type', + 'port_security_enabled'] + for optional_param in optional_parameters: + if module.params[optional_param] is not None: + port_kwargs[optional_param] = module.params[optional_param] + + if module.params['no_security_groups']: + port_kwargs['security_groups'] = [] + + return port_kwargs + + +def get_security_group_id(module, cloud, security_group_name_or_id): + security_group = cloud.get_security_group(security_group_name_or_id) + if not security_group: + module.fail_json(msg="Security group: %s, was not found" + % security_group_name_or_id) + return security_group['id'] + + +def main(): + argument_spec = openstack_full_argument_spec( + network=dict(required=False), + name=dict(required=False), + fixed_ips=dict(type='list', default=None, elements='dict'), + admin_state_up=dict(type='bool', default=None), + mac_address=dict(default=None), + security_groups=dict(default=None, type='list', elements='str'), + no_security_groups=dict(default=False, type='bool'), + allowed_address_pairs=dict(type='list', default=None, elements='dict'), + extra_dhcp_opts=dict(type='list', default=None, elements='dict'), + device_owner=dict(default=None), + device_id=dict(default=None), + state=dict(default='present', choices=['absent', 'present']), + vnic_type=dict(default=None, + choices=['normal', 'direct', 'direct-physical', + 'macvtap', 'baremetal', 'virtio-forwarder']), + port_security_enabled=dict(default=None, type='bool') + ) + + module_kwargs = openstack_module_kwargs( + mutually_exclusive=[ + ['no_security_groups', 'security_groups'], + ] + ) + + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + if not HAS_ORDEREDDICT: + module.fail_json(msg=missing_required_lib('ordereddict')) + + name = module.params['name'] + state = module.params['state'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + if module.params['security_groups']: + # translate security_groups to UUID's if names where provided + module.params['security_groups'] = [ + get_security_group_id(module, cloud, v) + for v in module.params['security_groups'] + ] + + # Neutron API accept 'binding:vnic_type' as an argument + # for the port type. + module.params['binding:vnic_type'] = module.params.pop('vnic_type') + + port = None + network_id = None + if name: + port = cloud.get_port(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, port, cloud)) + + changed = False + if state == 'present': + if not port: + network = module.params['network'] + if not network: + module.fail_json( + msg="Parameter 'network' is required in Port Create" + ) + port_kwargs = _compose_port_args(module, cloud) + network_object = cloud.get_network(network) + + if network_object: + network_id = network_object['id'] + else: + module.fail_json( + msg="Specified network was not found." + ) + + port = cloud.create_port(network_id, **port_kwargs) + changed = True + else: + if _needs_update(module, port, cloud): + port_kwargs = _compose_port_args(module, cloud) + port = cloud.update_port(port['id'], **port_kwargs) + changed = True + module.exit_json(changed=changed, id=port['id'], port=port) + + if state == 'absent': + if port: + cloud.delete_port(port['id']) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/port_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/port_info.py new file mode 100644 index 00000000..1db90ec8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/port_info.py @@ -0,0 +1,217 @@ +#!/usr/bin/python + +# Copyright (c) 2016 IBM +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +module: port_info +short_description: Retrieve information about ports within OpenStack. +author: OpenStack Ansible SIG +description: + - Retrieve information about ports from OpenStack. + - This module was called C(openstack.cloud.port_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.port_info) module no longer returns C(ansible_facts)! +options: + port: + description: + - Unique name or ID of a port. + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements + of this dictionary will be matched against the returned port + dictionaries. Matching is currently limited to strings within + the port dictionary, or strings within nested dictionaries. + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather information about all ports +- openstack.cloud.port_info: + cloud: mycloud + register: result + +- debug: + msg: "{{ result.openstack_ports }}" + +# Gather information about a single port +- openstack.cloud.port_info: + cloud: mycloud + port: 6140317d-e676-31e1-8a4a-b1913814a471 + +# Gather information about all ports that have device_id set to a specific value +# and with a status of ACTIVE. +- openstack.cloud.port_info: + cloud: mycloud + filters: + device_id: 1038a010-3a37-4a9d-82ea-652f1da36597 + status: ACTIVE +''' + +RETURN = ''' +openstack_ports: + description: List of port dictionaries. A subset of the dictionary keys + listed below may be returned, depending on your cloud provider. + returned: always, but can be null + type: complex + contains: + admin_state_up: + description: The administrative state of the router, which is + up (true) or down (false). + returned: success + type: bool + sample: true + allowed_address_pairs: + description: A set of zero or more allowed address pairs. An + address pair consists of an IP address and MAC address. + returned: success + type: list + sample: [] + "binding:host_id": + description: The UUID of the host where the port is allocated. + returned: success + type: str + sample: "b4bd682d-234a-4091-aa5b-4b025a6a7759" + "binding:profile": + description: A dictionary the enables the application running on + the host to pass and receive VIF port-specific + information to the plug-in. + returned: success + type: dict + sample: {} + "binding:vif_details": + description: A dictionary that enables the application to pass + information about functions that the Networking API + provides. + returned: success + type: dict + sample: {"port_filter": true} + "binding:vif_type": + description: The VIF type for the port. + returned: success + type: dict + sample: "ovs" + "binding:vnic_type": + description: The virtual network interface card (vNIC) type that is + bound to the neutron port. + returned: success + type: str + sample: "normal" + device_id: + description: The UUID of the device that uses this port. + returned: success + type: str + sample: "b4bd682d-234a-4091-aa5b-4b025a6a7759" + device_owner: + description: The UUID of the entity that uses this port. + returned: success + type: str + sample: "network:router_interface" + dns_assignment: + description: DNS assignment information. + returned: success + type: list + dns_name: + description: DNS name + returned: success + type: str + sample: "" + extra_dhcp_opts: + description: A set of zero or more extra DHCP option pairs. + An option pair consists of an option value and name. + returned: success + type: list + sample: [] + fixed_ips: + description: The IP addresses for the port. Includes the IP address + and UUID of the subnet. + returned: success + type: list + id: + description: The UUID of the port. + returned: success + type: str + sample: "3ec25c97-7052-4ab8-a8ba-92faf84148de" + ip_address: + description: The IP address. + returned: success + type: str + sample: "127.0.0.1" + mac_address: + description: The MAC address. + returned: success + type: str + sample: "00:00:5E:00:53:42" + name: + description: The port name. + returned: success + type: str + sample: "port_name" + network_id: + description: The UUID of the attached network. + returned: success + type: str + sample: "dd1ede4f-3952-4131-aab6-3b8902268c7d" + port_security_enabled: + description: The port security status. The status is enabled (true) or disabled (false). + returned: success + type: bool + sample: false + security_groups: + description: The UUIDs of any attached security groups. + returned: success + type: list + status: + description: The port status. + returned: success + type: str + sample: "ACTIVE" + tenant_id: + description: The UUID of the tenant who owns the network. + returned: success + type: str + sample: "51fce036d7984ba6af4f6c849f65ef00" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + argument_spec = openstack_full_argument_spec( + port=dict(required=False), + filters=dict(type='dict', required=False), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + is_old_facts = module._name == 'openstack.cloud.port_facts' + if is_old_facts: + module.deprecate("The 'openstack.cloud.port_facts' module has been renamed to 'openstack.cloud.port_info', " + "and the renamed one no longer returns ansible_facts", version='2.13') + + port = module.params.get('port') + filters = module.params.get('filters') + + sdk, cloud = openstack_cloud_from_module(module) + try: + ports = cloud.search_ports(port, filters) + if is_old_facts: + module.exit_json(changed=False, ansible_facts=dict( + openstack_ports=ports)) + else: + module.exit_json(changed=False, openstack_ports=ports) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/project.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/project.py new file mode 100644 index 00000000..5791a1d7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/project.py @@ -0,0 +1,232 @@ +#!/usr/bin/python +# Copyright (c) 2015 IBM Corporation +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: project +short_description: Manage OpenStack Projects +author: OpenStack Ansible SIG +description: + - Manage OpenStack Projects. Projects can be created, + updated or deleted using this module. A project will be updated + if I(name) matches an existing project and I(state) is present. + The value for I(name) cannot be updated without deleting and + re-creating the project. +options: + name: + description: + - Name for the project + required: true + type: str + description: + description: + - Description for the project + type: str + domain_id: + description: + - Domain id to create the project in if the cloud supports domains. + aliases: ['domain'] + type: str + enabled: + description: + - Is the project enabled + type: bool + default: 'yes' + properties: + description: + - Additional properties to be associated with this project. Requires + openstacksdk>0.45. + type: dict + default: {} + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a project +- openstack.cloud.project: + cloud: mycloud + endpoint_type: admin + state: present + name: demoproject + description: demodescription + domain_id: demoid + enabled: True + properties: + internal_alias: demo_project + +# Delete a project +- openstack.cloud.project: + cloud: mycloud + endpoint_type: admin + state: absent + name: demoproject +''' + + +RETURN = ''' +project: + description: Dictionary describing the project. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Project ID + type: str + sample: "f59382db809c43139982ca4189404650" + name: + description: Project name + type: str + sample: "demoproject" + description: + description: Project description + type: str + sample: "demodescription" + enabled: + description: Boolean to indicate if project is enabled + type: bool + sample: True +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _needs_update(module, project): + keys = ('description', 'enabled') + for key in keys: + if module.params[key] is not None and module.params[key] != project.get(key): + return True + + properties = module.params['properties'] + if properties: + project_properties = project.get('properties') + for k, v in properties.items(): + if v is not None and v != project_properties[k]: + return True + + return False + + +def _system_state_change(module, project): + state = module.params['state'] + if state == 'present': + if project is None: + changed = True + else: + if _needs_update(module, project): + changed = True + else: + changed = False + + elif state == 'absent': + if project is None: + changed = False + else: + changed = True + + return changed + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + description=dict(required=False, default=None), + domain_id=dict(required=False, default=None, aliases=['domain']), + properties=dict(type='dict', default={}), + enabled=dict(default=True, type='bool'), + state=dict(default='present', choices=['absent', 'present']) + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params['name'] + description = module.params['description'] + domain = module.params.get('domain_id') + enabled = module.params['enabled'] + properties = module.params['properties'] + state = module.params['state'] + + min_version = None + + if properties: + min_version = '0.45.1' + + sdk, cloud = openstack_cloud_from_module(module, min_version) + try: + if domain: + try: + # We assume admin is passing domain id + dom = cloud.get_domain(domain)['id'] + domain = dom + except Exception: + # If we fail, maybe admin is passing a domain name. + # Note that domains have unique names, just like id. + try: + dom = cloud.search_domains(filters={'name': domain})[0]['id'] + domain = dom + except Exception: + # Ok, let's hope the user is non-admin and passing a sane id + pass + + if domain: + project = cloud.get_project(name, domain_id=domain) + else: + project = cloud.get_project(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, project)) + + if state == 'present': + if project is None: + project = cloud.create_project( + name=name, description=description, + domain_id=domain, + enabled=enabled) + changed = True + + project = cloud.update_project( + project['id'], description=description, + enabled=enabled, **properties) + else: + if _needs_update(module, project): + project = cloud.update_project( + project['id'], description=description, + enabled=enabled, **properties) + changed = True + else: + changed = False + module.exit_json(changed=changed, project=project) + + elif state == 'absent': + if project is None: + changed = False + else: + cloud.delete_project(project['id']) + changed = True + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=e.message, extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/project_access.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/project_access.py new file mode 100644 index 00000000..09b64e27 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/project_access.py @@ -0,0 +1,198 @@ +#!/usr/bin/python + +# This module 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 software 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 software. If not, see <http://www.gnu.org/licenses/>. + +DOCUMENTATION = ''' +--- +module: project_access +short_description: Manage OpenStack compute flavors access +author: OpenStack Ansible SIG +description: + - Add or remove flavor, volume_type or other resources access + from OpenStack. +options: + state: + description: + - Indicate desired state of the resource. + choices: ['present', 'absent'] + required: false + default: present + type: str + target_project_id: + description: + - Project id. + required: true + type: str + resource_type: + description: + - The resource type (eg. nova_flavor, cinder_volume_type). + required: true + type: str + resource_name: + description: + - The resource name (eg. tiny). + required: true + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: "Enable access to tiny flavor to your tenant." + openstack.cloud.project_access: + cloud: mycloud + state: present + target_project_id: f0f1f2f3f4f5f67f8f9e0e1 + resource_name: tiny + resource_type: nova_flavor + + +- name: "Disable access to the given flavor to project" + openstack.cloud.project_access: + cloud: mycloud + state: absent + target_project_id: f0f1f2f3f4f5f67f8f9e0e1 + resource_name: tiny + resource_type: nova_flavor +''' + +RETURN = ''' +flavor: + description: Dictionary describing the flavor. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Flavor ID. + returned: success + type: str + sample: "515256b8-7027-4d73-aa54-4e30a4a4a339" + name: + description: Flavor name. + returned: success + type: str + sample: "tiny" + +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def main(): + argument_spec = openstack_full_argument_spec( + state=dict(required=False, default='present', + choices=['absent', 'present']), + + target_project_id=dict(required=True, type='str'), + resource_type=dict(required=True, type='str'), + resource_name=dict(required=True, type='str'), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + required_if=[ + ('state', 'present', ['target_project_id']) + ], + **module_kwargs) + + sdk, cloud = openstack_cloud_from_module(module) + + state = module.params['state'] + resource_name = module.params['resource_name'] + resource_type = module.params['resource_type'] + target_project_id = module.params['target_project_id'] + + try: + if resource_type == 'nova_flavor': + # returns Munch({'NAME_ATTR': 'name', + # 'tenant_id': u'37e55da59ec842649d84230f3a24eed5', + # 'HUMAN_ID': False, + # 'flavor_id': u'6d4d37b9-0480-4a8c-b8c9-f77deaad73f9', + # 'request_ids': [], 'human_id': None}), + _get_resource = cloud.get_flavor + _list_resource_access = cloud.list_flavor_access + _add_resource_access = cloud.add_flavor_access + _remove_resource_access = cloud.remove_flavor_access + elif resource_type == 'cinder_volume_type': + # returns [Munch({ + # 'project_id': u'178cdb9955b047eea7afbe582038dc94', + # 'properties': {'request_ids': [], 'NAME_ATTR': 'name', + # 'human_id': None, + # 'HUMAN_ID': False}, + # 'id': u'd5573023-b290-42c8-b232-7c5ca493667f'}), + _get_resource = cloud.get_volume_type + _list_resource_access = cloud.get_volume_type_access + _add_resource_access = cloud.add_volume_type_access + _remove_resource_access = cloud.remove_volume_type_access + else: + module.exit_json(changed=False, + resource_name=resource_name, + resource_type=resource_type, + error="Not implemented.") + + resource = _get_resource(resource_name) + if not resource: + module.exit_json(changed=False, + resource_name=resource_name, + resource_type=resource_type, + error="Not found.") + resource_id = getattr(resource, 'id', resource['id']) + # _list_resource_access returns a list of dicts containing 'project_id' + acls = _list_resource_access(resource_id) + + if not all(acl.get('project_id') for acl in acls): + module.exit_json(changed=False, + resource_name=resource_name, + resource_type=resource_type, + error="Missing project_id in resource output.") + allowed_tenants = [acl['project_id'] for acl in acls] + + changed_access = any(( + state == 'present' and target_project_id not in allowed_tenants, + state == 'absent' and target_project_id in allowed_tenants + )) + if module.check_mode or not changed_access: + module.exit_json(changed=changed_access, + resource=resource, + id=resource_id) + + if state == 'present': + _add_resource_access( + resource_id, target_project_id + ) + elif state == 'absent': + _remove_resource_access( + resource_id, target_project_id + ) + + module.exit_json(changed=True, + resource=resource, + id=resource_id) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), **module.params) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/project_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/project_info.py new file mode 100644 index 00000000..421dc36e --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/project_info.py @@ -0,0 +1,157 @@ +#!/usr/bin/python +# Copyright (c) 2016 Hewlett-Packard Enterprise Corporation +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: project_info +short_description: Retrieve information about one or more OpenStack projects +author: OpenStack Ansible SIG +description: + - Retrieve information about a one or more OpenStack projects + - This module was called C(openstack.cloud.project_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.project_info) module no longer returns C(ansible_facts)! +options: + name: + description: + - Name or ID of the project + type: str + domain: + description: + - Name or ID of the domain containing the project if the cloud supports domains + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather information about previously created projects +- openstack.cloud.project_info: + cloud: awesomecloud + register: result +- debug: + msg: "{{ result.openstack_projects }}" + +# Gather information about a previously created project by name +- openstack.cloud.project_info: + cloud: awesomecloud + name: demoproject + register: result +- debug: + msg: "{{ result.openstack_projects }}" + +# Gather information about a previously created project in a specific domain +- openstack.cloud.project_info: + cloud: awesomecloud + name: demoproject + domain: admindomain + register: result +- debug: + msg: "{{ result.openstack_projects }}" + +# Gather information about a previously created project in a specific domain with filter +- openstack.cloud.project_info: + cloud: awesomecloud + name: demoproject + domain: admindomain + filters: + enabled: False + register: result +- debug: + msg: "{{ result.openstack_projects }}" +''' + + +RETURN = ''' +openstack_projects: + description: has all the OpenStack information about projects + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the project. + returned: success + type: str + description: + description: Description of the project + returned: success + type: str + enabled: + description: Flag to indicate if the project is enabled + returned: success + type: bool + domain_id: + description: Domain ID containing the project (keystone v3 clouds only) + returned: success + type: bool +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec, openstack_cloud_from_module + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=False, default=None), + domain=dict(required=False, default=None), + filters=dict(required=False, type='dict', default=None), + ) + + module = AnsibleModule(argument_spec) + is_old_facts = module._name == 'openstack.cloud.project_facts' + if is_old_facts: + module.deprecate("The 'openstack.cloud.project_facts' module has been renamed to 'openstack.cloud.project_info', " + "and the renamed one no longer returns ansible_facts", version='2.13') + + sdk, opcloud = openstack_cloud_from_module(module) + try: + name = module.params['name'] + domain = module.params['domain'] + filters = module.params['filters'] + + if domain: + try: + # We assume admin is passing domain id + dom = opcloud.get_domain(domain)['id'] + domain = dom + except Exception: + # If we fail, maybe admin is passing a domain name. + # Note that domains have unique names, just like id. + dom = opcloud.search_domains(filters={'name': domain}) + if dom: + domain = dom[0]['id'] + else: + module.fail_json(msg='Domain name or ID does not exist') + + if not filters: + filters = {} + + filters['domain_id'] = domain + + projects = opcloud.search_projects(name, filters) + if is_old_facts: + module.exit_json(changed=False, ansible_facts=dict( + openstack_projects=projects)) + else: + module.exit_json(changed=False, openstack_projects=projects) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/quota.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/quota.py new file mode 100644 index 00000000..25c28977 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/quota.py @@ -0,0 +1,496 @@ +#!/usr/bin/python +# Copyright (c) 2016 Pason System Corporation +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: quota +short_description: Manage OpenStack Quotas +author: OpenStack Ansible SIG +description: + - Manage OpenStack Quotas. Quotas can be created, + updated or deleted using this module. A quota will be updated + if matches an existing project and is present. +options: + name: + description: + - Name of the OpenStack Project to manage. + required: true + type: str + state: + description: + - A value of present sets the quota and a value of absent resets the quota to system defaults. + default: present + type: str + choices: ['absent', 'present'] + backup_gigabytes: + description: Maximum size of backups in GB's. + type: int + backups: + description: Maximum number of backups allowed. + type: int + cores: + description: Maximum number of CPU's per project. + type: int + fixed_ips: + description: Number of fixed IP's to allow. + type: int + floating_ips: + description: Number of floating IP's to allow in Compute. + aliases: ['compute_floating_ips'] + type: int + floatingip: + description: Number of floating IP's to allow in Network. + aliases: ['network_floating_ips'] + type: int + gigabytes: + description: Maximum volume storage allowed for project. + type: int + gigabytes_types: + description: + - Per driver volume storage quotas. Keys should be + prefixed with C(gigabytes_) values should be ints. + type: dict + injected_file_size: + description: Maximum file size in bytes. + type: int + injected_files: + description: Number of injected files to allow. + type: int + injected_path_size: + description: Maximum path size. + type: int + instances: + description: Maximum number of instances allowed. + type: int + key_pairs: + description: Number of key pairs to allow. + type: int + loadbalancer: + description: Number of load balancers to allow. + type: int + network: + description: Number of networks to allow. + type: int + per_volume_gigabytes: + description: Maximum size in GB's of individual volumes. + type: int + pool: + description: Number of load balancer pools to allow. + type: int + port: + description: Number of Network ports to allow, this needs to be greater than the instances limit. + type: int + properties: + description: Number of properties to allow. + type: int + ram: + description: Maximum amount of ram in MB to allow. + type: int + rbac_policy: + description: Number of policies to allow. + type: int + router: + description: Number of routers to allow. + type: int + security_group_rule: + description: Number of rules per security group to allow. + type: int + security_group: + description: Number of security groups to allow. + type: int + server_group_members: + description: Number of server group members to allow. + type: int + server_groups: + description: Number of server groups to allow. + type: int + snapshots: + description: Number of snapshots to allow. + type: int + snapshots_types: + description: + - Per-driver volume snapshot quotas. Keys should be + prefixed with C(snapshots_) values should be ints. + type: dict + subnet: + description: Number of subnets to allow. + type: int + subnetpool: + description: Number of subnet pools to allow. + type: int + volumes: + description: Number of volumes to allow. + type: int + volumes_types: + description: + - Per-driver volume count quotas. Keys should be + prefixed with C(gigabytes_) values should be ints. + type: dict + project: + description: Unused, kept for compatability + type: int + +requirements: + - "python >= 3.6" + - "openstacksdk >= 0.13.0" + - "keystoneauth1 >= 3.4.0" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# List a Project Quota +- openstack.cloud.quota: + cloud: mycloud + name: demoproject + +# Set a Project back to the defaults +- openstack.cloud.quota: + cloud: mycloud + name: demoproject + state: absent + +# Update a Project Quota for cores +- openstack.cloud.quota: + cloud: mycloud + name: demoproject + cores: 100 + +# Update a Project Quota +- openstack.cloud.quota: + name: demoproject + cores: 1000 + volumes: 20 + volumes_type: + - volume_lvm: 10 + +# Complete example based on list of projects +- name: Update quotas + openstack.cloud.quota: + name: "{{ item.name }}" + backup_gigabytes: "{{ item.backup_gigabytes }}" + backups: "{{ item.backups }}" + cores: "{{ item.cores }}" + fixed_ips: "{{ item.fixed_ips }}" + floating_ips: "{{ item.floating_ips }}" + floatingip: "{{ item.floatingip }}" + gigabytes: "{{ item.gigabytes }}" + injected_file_size: "{{ item.injected_file_size }}" + injected_files: "{{ item.injected_files }}" + injected_path_size: "{{ item.injected_path_size }}" + instances: "{{ item.instances }}" + key_pairs: "{{ item.key_pairs }}" + loadbalancer: "{{ item.loadbalancer }}" + per_volume_gigabytes: "{{ item.per_volume_gigabytes }}" + pool: "{{ item.pool }}" + port: "{{ item.port }}" + properties: "{{ item.properties }}" + ram: "{{ item.ram }}" + security_group_rule: "{{ item.security_group_rule }}" + security_group: "{{ item.security_group }}" + server_group_members: "{{ item.server_group_members }}" + server_groups: "{{ item.server_groups }}" + snapshots: "{{ item.snapshots }}" + volumes: "{{ item.volumes }}" + volumes_types: + volumes_lvm: "{{ item.volumes_lvm }}" + snapshots_types: + snapshots_lvm: "{{ item.snapshots_lvm }}" + gigabytes_types: + gigabytes_lvm: "{{ item.gigabytes_lvm }}" + with_items: + - "{{ projects }}" + when: item.state == "present" +''' + +RETURN = ''' +openstack_quotas: + description: Dictionary describing the project quota. + returned: Regardless if changes where made or not + type: dict + sample: + openstack_quotas: { + compute: { + cores: 150, + fixed_ips: -1, + floating_ips: 10, + injected_file_content_bytes: 10240, + injected_file_path_bytes: 255, + injected_files: 5, + instances: 100, + key_pairs: 100, + metadata_items: 128, + ram: 153600, + security_group_rules: 20, + security_groups: 10, + server_group_members: 10, + server_groups: 10 + }, + network: { + floatingip: 50, + loadbalancer: 10, + network: 10, + pool: 10, + port: 160, + rbac_policy: 10, + router: 10, + security_group: 10, + security_group_rule: 100, + subnet: 10, + subnetpool: -1 + }, + volume: { + backup_gigabytes: 1000, + backups: 10, + gigabytes: 1000, + gigabytes_lvm: -1, + per_volume_gigabytes: -1, + snapshots: 10, + snapshots_lvm: -1, + volumes: 10, + volumes_lvm: -1 + } + } + +''' + +import traceback + +KEYSTONEAUTH1_IMP_ERR = None +try: + from keystoneauth1 import exceptions as ksa_exceptions + HAS_KEYSTONEAUTH1 = True +except ImportError: + KEYSTONEAUTH1_IMP_ERR = traceback.format_exc() + HAS_KEYSTONEAUTH1 = False + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_full_argument_spec, + openstack_cloud_from_module, +) + + +def _get_volume_quotas(cloud, project): + + return cloud.get_volume_quotas(project) + + +def _get_network_quotas(cloud, project): + + return cloud.get_network_quotas(project) + + +def _get_compute_quotas(cloud, project): + + return cloud.get_compute_quotas(project) + + +def _get_quotas(sdk, module, cloud, project): + + quota = {} + try: + quota['volume'] = _get_volume_quotas(cloud, project) + except ksa_exceptions.EndpointNotFound: + module.warn("No public endpoint for volumev2 service was found. Ignoring volume quotas.") + + try: + quota['network'] = _get_network_quotas(cloud, project) + except ksa_exceptions.EndpointNotFound: + module.warn("No public endpoint for network service was found. Ignoring network quotas.") + + quota['compute'] = _get_compute_quotas(cloud, project) + + for quota_type in quota.keys(): + quota[quota_type] = _scrub_results(quota[quota_type]) + + return quota + + +def _scrub_results(quota): + + filter_attr = [ + 'HUMAN_ID', + 'NAME_ATTR', + 'human_id', + 'request_ids', + 'x_openstack_request_ids', + ] + + for attr in filter_attr: + if attr in quota: + del quota[attr] + + return quota + + +def _system_state_change_details(module, project_quota_output): + + quota_change_request = {} + changes_required = False + + for quota_type in project_quota_output.keys(): + for quota_option in project_quota_output[quota_type].keys(): + if quota_option in module.params and module.params[quota_option] is not None: + if project_quota_output[quota_type][quota_option] != module.params[quota_option]: + changes_required = True + + if quota_type not in quota_change_request: + quota_change_request[quota_type] = {} + + quota_change_request[quota_type][quota_option] = module.params[quota_option] + + return (changes_required, quota_change_request) + + +def _system_state_change(module, project_quota_output): + """ + Determine if changes are required to the current project quota. + + This is done by comparing the current project_quota_output against + the desired quota settings set on the module params. + """ + + changes_required, quota_change_request = _system_state_change_details( + module, + project_quota_output + ) + + if changes_required: + return True + else: + return False + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + backup_gigabytes=dict(required=False, type='int', default=None), + backups=dict(required=False, type='int', default=None), + cores=dict(required=False, type='int', default=None), + fixed_ips=dict(required=False, type='int', default=None), + floating_ips=dict(required=False, type='int', default=None, aliases=['compute_floating_ips']), + floatingip=dict(required=False, type='int', default=None, aliases=['network_floating_ips']), + gigabytes=dict(required=False, type='int', default=None), + gigabytes_types=dict(required=False, type='dict', default={}), + injected_file_size=dict(required=False, type='int', default=None), + injected_files=dict(required=False, type='int', default=None), + injected_path_size=dict(required=False, type='int', default=None), + instances=dict(required=False, type='int', default=None), + key_pairs=dict(required=False, type='int', default=None), + loadbalancer=dict(required=False, type='int', default=None), + network=dict(required=False, type='int', default=None), + per_volume_gigabytes=dict(required=False, type='int', default=None), + pool=dict(required=False, type='int', default=None), + port=dict(required=False, type='int', default=None), + project=dict(required=False, type='int', default=None), + properties=dict(required=False, type='int', default=None), + ram=dict(required=False, type='int', default=None), + rbac_policy=dict(required=False, type='int', default=None), + router=dict(required=False, type='int', default=None), + security_group_rule=dict(required=False, type='int', default=None), + security_group=dict(required=False, type='int', default=None), + server_group_members=dict(required=False, type='int', default=None), + server_groups=dict(required=False, type='int', default=None), + snapshots=dict(required=False, type='int', default=None), + snapshots_types=dict(required=False, type='dict', default={}), + subnet=dict(required=False, type='int', default=None), + subnetpool=dict(required=False, type='int', default=None), + volumes=dict(required=False, type='int', default=None), + volumes_types=dict(required=False, type='dict', default={}) + ) + + module = AnsibleModule(argument_spec, + supports_check_mode=True + ) + + if not HAS_KEYSTONEAUTH1: + module.fail_json(msg=missing_required_lib("keystoneauth1"), exception=KEYSTONEAUTH1_IMP_ERR) + + sdk, cloud = openstack_cloud_from_module(module) + try: + cloud_params = dict(module.params) + + # In order to handle the different volume types we update module params after. + dynamic_types = [ + 'gigabytes_types', + 'snapshots_types', + 'volumes_types', + ] + + for dynamic_type in dynamic_types: + for k, v in module.params[dynamic_type].items(): + module.params[k] = int(v) + + # Get current quota values + project_quota_output = _get_quotas( + sdk, module, cloud, cloud_params['name']) + changes_required = False + + if module.params['state'] == "absent": + # If a quota state is set to absent we should assume there will be changes. + # The default quota values are not accessible so we can not determine if + # no changes will occur or not. + if module.check_mode: + module.exit_json(changed=True) + + # Calling delete_network_quotas when a quota has not been set results + # in an error, according to the sdk docs it should return the + # current quota. + # The following error string is returned: + # network client call failed: Quota for tenant 69dd91d217e949f1a0b35a4b901741dc could not be found. + neutron_msg1 = "network client call failed: Quota for tenant" + neutron_msg2 = "could not be found" + + for quota_type in project_quota_output.keys(): + quota_call = getattr(cloud, 'delete_%s_quotas' % (quota_type)) + try: + quota_call(cloud_params['name']) + except sdk.exceptions.OpenStackCloudException as e: + error_msg = str(e) + if error_msg.find(neutron_msg1) > -1 and error_msg.find(neutron_msg2) > -1: + pass + else: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + project_quota_output = _get_quotas( + sdk, module, cloud, cloud_params['name']) + changes_required = True + + elif module.params['state'] == "present": + if module.check_mode: + module.exit_json(changed=_system_state_change(module, project_quota_output)) + + changes_required, quota_change_request = _system_state_change_details( + module, + project_quota_output + ) + + if changes_required: + for quota_type in quota_change_request.keys(): + quota_call = getattr(cloud, 'set_%s_quotas' % (quota_type)) + quota_call(cloud_params['name'], **quota_change_request[quota_type]) + + # Get quota state post changes for validation + project_quota_update = _get_quotas( + sdk, module, cloud, cloud_params['name']) + + if project_quota_output == project_quota_update: + module.fail_json(msg='Could not apply quota update') + + project_quota_output = project_quota_update + + module.exit_json(changed=changes_required, + openstack_quotas=project_quota_output + ) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/recordset.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/recordset.py new file mode 100644 index 00000000..5f89a281 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/recordset.py @@ -0,0 +1,237 @@ +#!/usr/bin/python +# Copyright (c) 2016 Hewlett-Packard Enterprise +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: recordset +short_description: Manage OpenStack DNS recordsets +author: OpenStack Ansible SIG +description: + - Manage OpenStack DNS recordsets. Recordsets can be created, deleted or + updated. Only the I(records), I(description), and I(ttl) values + can be updated. +options: + zone: + description: + - Zone managing the recordset + required: true + type: str + name: + description: + - Name of the recordset. It must be ended with name of dns zone. + required: true + type: str + recordset_type: + description: + - Recordset type + - Required when I(state=present). + choices: ['a', 'aaaa', 'mx', 'cname', 'txt', 'ns', 'srv', 'ptr', 'caa'] + type: str + records: + description: + - List of recordset definitions. + - Required when I(state=present). + type: list + elements: str + description: + description: + - Description of the recordset + type: str + ttl: + description: + - TTL (Time To Live) value in seconds + type: int + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a recordset named "www.example.net." +- openstack.cloud.recordset: + cloud: mycloud + state: present + zone: example.net. + name: www.example.net. + recordset_type: "a" + records: ['10.1.1.1'] + description: test recordset + ttl: 3600 + +# Update the TTL on existing "www.example.net." recordset +- openstack.cloud.recordset: + cloud: mycloud + state: present + zone: example.net. + name: www.example.net. + ttl: 7200 + +# Delete recordset named "www.example.net." +- openstack.cloud.recordset: + cloud: mycloud + state: absent + zone: example.net. + name: www.example.net. +''' + +RETURN = ''' +recordset: + description: Dictionary describing the recordset. + returned: On success when I(state) is 'present'. + type: complex + contains: + id: + description: Unique recordset ID + type: str + sample: "c1c530a3-3619-46f3-b0f6-236927b2618c" + name: + description: Recordset name + type: str + sample: "www.example.net." + zone_id: + description: Zone id + type: str + sample: 9508e177-41d8-434e-962c-6fe6ca880af7 + type: + description: Recordset type + type: str + sample: "A" + description: + description: Recordset description + type: str + sample: "Test description" + ttl: + description: Zone TTL value + type: int + sample: 3600 + records: + description: Recordset records + type: list + sample: ['10.0.0.1'] +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(state, records, description, ttl, recordset): + if state == 'present': + if recordset is None: + return True + if records is not None and recordset['records'] != records: + return True + if description is not None and recordset['description'] != description: + return True + if ttl is not None and recordset['ttl'] != ttl: + return True + if state == 'absent' and recordset: + return True + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + zone=dict(required=True), + name=dict(required=True), + recordset_type=dict(required=False, choices=['a', 'aaaa', 'mx', 'cname', 'txt', 'ns', 'srv', 'ptr', 'caa']), + records=dict(required=False, type='list', elements='str'), + description=dict(required=False, default=None), + ttl=dict(required=False, type='int'), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + required_if=[ + ('state', 'present', + ['recordset_type', 'records'])], + supports_check_mode=True, + **module_kwargs) + + module.module_min_sdk_version = '0.28.0' + zone = module.params.get('zone') + name = module.params.get('name') + state = module.params.get('state') + + sdk, cloud = openstack_cloud_from_module(module) + recordsets = cloud.search_recordsets(zone, name_or_id=name) + + if recordsets: + recordset = recordsets[0] + try: + recordset_id = recordset['id'] + except KeyError as e: + module.fail_json(msg=str(e)) + else: + # recordsets is filtered by type and should never be more than 1 return + recordset = None + + if state == 'present': + recordset_type = module.params.get('recordset_type').upper() + records = module.params.get('records') + description = module.params.get('description') + ttl = module.params.get('ttl') + + kwargs = {} + if description: + kwargs['description'] = description + kwargs['records'] = records + + if module.check_mode: + module.exit_json(changed=_system_state_change(state, + records, description, + ttl, recordset)) + + if recordset is None: + if ttl: + kwargs['ttl'] = ttl + else: + kwargs['ttl'] = 300 + + recordset = cloud.create_recordset( + zone=zone, name=name, recordset_type=recordset_type, + **kwargs) + changed = True + else: + + if ttl: + kwargs['ttl'] = ttl + + pre_update_recordset = recordset + changed = _system_state_change(state, records, + description, ttl, + pre_update_recordset) + if changed: + recordset = cloud.update_recordset( + zone=zone, name_or_id=recordset_id, **kwargs) + + module.exit_json(changed=changed, recordset=recordset) + + elif state == 'absent': + if module.check_mode: + module.exit_json(changed=_system_state_change(state, + None, None, + None, recordset)) + + if recordset is None: + changed = False + else: + cloud.delete_recordset(zone, recordset_id) + changed = True + module.exit_json(changed=changed) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/role_assignment.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/role_assignment.py new file mode 100644 index 00000000..bc615a21 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/role_assignment.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# Copyright (c) 2016 IBM +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: role_assignment +short_description: Associate OpenStack Identity users and roles +author: OpenStack Ansible SIG +description: + - Grant and revoke roles in either project or domain context for + OpenStack Identity Users. +options: + role: + description: + - Name or ID for the role. + required: true + type: str + user: + description: + - Name or ID for the user. If I(user) is not specified, then + I(group) is required. Both may not be specified. + type: str + group: + description: + - Name or ID for the group. Valid only with keystone version 3. + If I(group) is not specified, then I(user) is required. Both + may not be specified. + type: str + project: + description: + - Name or ID of the project to scope the role association to. + If you are using keystone version 2, then this value is required. + type: str + domain: + description: + - Name or ID of the domain to scope the role association to. Valid only + with keystone version 3, and required if I(project) is not specified. + type: str + state: + description: + - Should the roles be present or absent on the user. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Grant an admin role on the user admin in the project project1 +- openstack.cloud.role_assignment: + cloud: mycloud + user: admin + role: admin + project: project1 + +# Revoke the admin role from the user barney in the newyork domain +- openstack.cloud.role_assignment: + cloud: mycloud + state: absent + user: barney + role: admin + domain: newyork +''' + +RETURN = ''' +# +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(state, assignment): + if state == 'present' and not assignment: + return True + elif state == 'absent' and assignment: + return True + return False + + +def _build_kwargs(user, group, project, domain): + kwargs = {} + if user: + kwargs['user'] = user + if group: + kwargs['group'] = group + if project: + kwargs['project'] = project + if domain: + kwargs['domain'] = domain + return kwargs + + +def main(): + argument_spec = openstack_full_argument_spec( + role=dict(required=True), + user=dict(required=False), + group=dict(required=False), + project=dict(required=False), + domain=dict(required=False), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs( + required_one_of=[ + ['user', 'group'] + ]) + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + role = module.params.get('role') + user = module.params.get('user') + group = module.params.get('group') + project = module.params.get('project') + domain = module.params.get('domain') + state = module.params.get('state') + + sdk, cloud = openstack_cloud_from_module(module) + try: + filters = {} + domain_id = None + + r = cloud.get_role(role) + if r is None: + module.fail_json(msg="Role %s is not valid" % role) + filters['role'] = r['id'] + + if domain: + d = cloud.get_domain(name_or_id=domain) + if d is None: + module.fail_json(msg="Domain %s is not valid" % domain) + filters['domain'] = d['id'] + domain_id = d['id'] + if user: + if domain: + u = cloud.get_user(user, domain_id=filters['domain']) + else: + u = cloud.get_user(user) + + if u is None: + module.fail_json(msg="User %s is not valid" % user) + filters['user'] = u['id'] + if group: + if domain: + g = cloud.get_group(group, domain_id=filters['domain']) + else: + g = cloud.get_group(group) + if g is None: + module.fail_json(msg="Group %s is not valid" % group) + filters['group'] = g['id'] + if project: + if domain: + p = cloud.get_project(project, domain_id=filters['domain']) + # OpenStack won't allow us to use both a domain and project as + # filter. Once we identified the project (using the domain as + # a filter criteria), we need to remove the domain itself from + # the filters list. + domain_id = filters.pop('domain') + else: + p = cloud.get_project(project) + + if p is None: + module.fail_json(msg="Project %s is not valid" % project) + filters['project'] = p['id'] + + assignment = cloud.list_role_assignments(filters=filters) + + if module.check_mode: + module.exit_json(changed=_system_state_change(state, assignment)) + + changed = False + + if state == 'present': + if not assignment: + kwargs = _build_kwargs(user, group, project, domain_id) + cloud.grant_role(role, **kwargs) + changed = True + + elif state == 'absent': + if assignment: + kwargs = _build_kwargs(user, group, project, domain_id) + cloud.revoke_role(role, **kwargs) + changed = True + + module.exit_json(changed=changed) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/router.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/router.py new file mode 100644 index 00000000..a90a06b3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/router.py @@ -0,0 +1,484 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: router +short_description: Create or delete routers from OpenStack +author: OpenStack Ansible SIG +description: + - Create or Delete routers from OpenStack. Although Neutron allows + routers to share the same name, this module enforces name uniqueness + to be more user friendly. +options: + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + type: str + name: + description: + - Name to be give to the router + required: true + type: str + admin_state_up: + description: + - Desired admin state of the created or existing router. + type: bool + default: 'yes' + enable_snat: + description: + - Enable Source NAT (SNAT) attribute. + type: bool + network: + description: + - Unique name or ID of the external gateway network. + - required I(interfaces) or I(enable_snat) are provided. + type: str + project: + description: + - Unique name or ID of the project. + type: str + external_fixed_ips: + description: + - The IP address parameters for the external gateway network. Each + is a dictionary with the subnet name or ID (subnet) and the IP + address to assign on the subnet (ip). If no IP is specified, + one is automatically assigned from that subnet. + type: list + elements: dict + suboptions: + ip: + description: The fixed IP address to attempt to allocate. + required: true + type: str + subnet: + description: The subnet to attach the IP address to. + type: str + interfaces: + description: + - List of subnets to attach to the router internal interface. Default + gateway associated with the subnet will be automatically attached + with the router's internal interface. + In order to provide an ip address different from the default + gateway,parameters are passed as dictionary with keys as network + name or ID (I(net)), subnet name or ID (I(subnet)) and the IP of + port (I(portip)) from the network. + User defined portip is often required when a multiple router need + to be connected to a single subnet for which the default gateway has + been already used. + type: list + elements: raw +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a simple router, not attached to a gateway or subnets. +- openstack.cloud.router: + cloud: mycloud + state: present + name: simple_router + +# Create a simple router, not attached to a gateway or subnets for a given project. +- openstack.cloud.router: + cloud: mycloud + state: present + name: simple_router + project: myproj + +# Creates a router attached to ext_network1 on an IPv4 subnet and one +# internal subnet interface. +- openstack.cloud.router: + cloud: mycloud + state: present + name: router1 + network: ext_network1 + external_fixed_ips: + - subnet: public-subnet + ip: 172.24.4.2 + interfaces: + - private-subnet + +# Create another router with two internal subnet interfaces.One with user defined port +# ip and another with default gateway. +- openstack.cloud.router: + cloud: mycloud + state: present + name: router2 + network: ext_network1 + interfaces: + - net: private-net + subnet: private-subnet + portip: 10.1.1.10 + - project-subnet + +# Create another router with two internal subnet interface.One with user defined port +# ip and and another with default gateway. +- openstack.cloud.router: + cloud: mycloud + state: present + name: router2 + network: ext_network1 + interfaces: + - net: private-net + subnet: private-subnet + portip: 10.1.1.10 + - project-subnet + +# Create another router with two internal subnet interface. one with user defined port +# ip and and another with default gateway. +- openstack.cloud.router: + cloud: mycloud + state: present + name: router2 + network: ext_network1 + interfaces: + - net: private-net + subnet: private-subnet + portip: 10.1.1.10 + - project-subnet + +# Update existing router1 external gateway to include the IPv6 subnet. +# Note that since 'interfaces' is not provided, any existing internal +# interfaces on an existing router will be left intact. +- openstack.cloud.router: + cloud: mycloud + state: present + name: router1 + network: ext_network1 + external_fixed_ips: + - subnet: public-subnet + ip: 172.24.4.2 + - subnet: ipv6-public-subnet + ip: 2001:db8::3 + +# Delete router1 +- openstack.cloud.router: + cloud: mycloud + state: absent + name: router1 +''' + +RETURN = ''' +router: + description: Dictionary describing the router. + returned: On success when I(state) is 'present' + type: complex + contains: + id: + description: Router ID. + type: str + sample: "474acfe5-be34-494c-b339-50f06aa143e4" + name: + description: Router name. + type: str + sample: "router1" + admin_state_up: + description: Administrative state of the router. + type: bool + sample: true + status: + description: The router status. + type: str + sample: "ACTIVE" + tenant_id: + description: The tenant ID. + type: str + sample: "861174b82b43463c9edc5202aadc60ef" + external_gateway_info: + description: The external gateway parameters. + type: dict + sample: { + "enable_snat": true, + "external_fixed_ips": [ + { + "ip_address": "10.6.6.99", + "subnet_id": "4272cb52-a456-4c20-8f3c-c26024ecfa81" + } + ] + } + routes: + description: The extra routes configuration for L3 router. + type: list +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +ROUTER_INTERFACE_OWNERS = set([ + 'network:router_interface', + 'network:router_interface_distributed', + 'network:ha_router_replicated_interface' +]) + + +class RouterModule(OpenStackModule): + argument_spec = dict( + state=dict(default='present', choices=['absent', 'present']), + name=dict(required=True), + admin_state_up=dict(type='bool', default=True), + enable_snat=dict(type='bool'), + network=dict(default=None), + interfaces=dict(type='list', default=None, elements='raw'), + external_fixed_ips=dict(type='list', default=None, elements='dict'), + project=dict(default=None) + ) + + def _router_internal_interfaces(self, router): + for port in self.conn.list_router_interfaces(router, 'internal'): + if port['device_owner'] in ROUTER_INTERFACE_OWNERS: + yield port + + def _needs_update(self, router, network, internal_subnet_ids, internal_port_ids, filters=None): + """Decide if the given router needs an update. + """ + if router['admin_state_up'] != self.params['admin_state_up']: + return True + if router['external_gateway_info']: + # check if enable_snat is set in module params + if self.params['enable_snat'] is not None: + if router['external_gateway_info'].get('enable_snat', True) != self.params['enable_snat']: + return True + if network: + if not router['external_gateway_info']: + return True + elif router['external_gateway_info']['network_id'] != network['id']: + return True + + # check external interfaces + if self.params['external_fixed_ips']: + for new_iface in self.params['external_fixed_ips']: + subnet = self.conn.get_subnet(new_iface['subnet'], filters) + exists = False + + # compare the requested interface with existing, looking for an existing match + for existing_iface in router['external_gateway_info']['external_fixed_ips']: + if existing_iface['subnet_id'] == subnet['id']: + if 'ip' in new_iface: + if existing_iface['ip_address'] == new_iface['ip']: + # both subnet id and ip address match + exists = True + break + else: + # only the subnet was given, so ip doesn't matter + exists = True + break + + # this interface isn't present on the existing router + if not exists: + return True + + # check internal interfaces + if self.params['interfaces']: + existing_subnet_ids = [] + for port in self._router_internal_interfaces(router): + if 'fixed_ips' in port: + for fixed_ip in port['fixed_ips']: + existing_subnet_ids.append(fixed_ip['subnet_id']) + + for iface in self.params['interfaces']: + if isinstance(iface, dict): + for p_id in internal_port_ids: + p = self.conn.get_port(name_or_id=p_id) + if 'fixed_ips' in p: + for fip in p['fixed_ips']: + internal_subnet_ids.append(fip['subnet_id']) + + if set(internal_subnet_ids) != set(existing_subnet_ids): + return True + + return False + + def _system_state_change(self, router, network, internal_ids, internal_portids, filters=None): + """Check if the system state would be changed.""" + state = self.params['state'] + if state == 'absent' and router: + return True + if state == 'present': + if not router: + return True + return self._needs_update(router, network, internal_ids, internal_portids, filters) + return False + + def _build_kwargs(self, router, network): + kwargs = { + 'admin_state_up': self.params['admin_state_up'], + } + + if router: + kwargs['name_or_id'] = router['id'] + else: + kwargs['name'] = self.params['name'] + + if network: + kwargs['ext_gateway_net_id'] = network['id'] + # can't send enable_snat unless we have a network + if self.params.get('enable_snat') is not None: + kwargs['enable_snat'] = self.params['enable_snat'] + + if self.params['external_fixed_ips']: + kwargs['ext_fixed_ips'] = [] + for iface in self.params['external_fixed_ips']: + subnet = self.conn.get_subnet(iface['subnet']) + d = {'subnet_id': subnet['id']} + if 'ip' in iface: + d['ip_address'] = iface['ip'] + kwargs['ext_fixed_ips'].append(d) + + return kwargs + + def _validate_subnets(self, filters=None): + external_subnet_ids = [] + internal_subnet_ids = [] + internal_port_ids = [] + existing_port_ips = [] + if self.params['external_fixed_ips']: + for iface in self.params['external_fixed_ips']: + subnet = self.conn.get_subnet(iface['subnet']) + if not subnet: + self.fail_json(msg='subnet %s not found' % iface['subnet']) + external_subnet_ids.append(subnet['id']) + + if self.params['interfaces']: + for iface in self.params['interfaces']: + if isinstance(iface, str): + subnet = self.conn.get_subnet(iface, filters) + if not subnet: + self.fail(msg='subnet %s not found' % iface) + internal_subnet_ids.append(subnet['id']) + elif isinstance(iface, dict): + subnet = self.conn.get_subnet(iface['subnet'], filters) + if not subnet: + self.fail(msg='subnet %s not found' % iface['subnet']) + net = self.conn.get_network(iface['net']) + if not net: + self.fail(msg='net %s not found' % iface['net']) + if "portip" not in iface: + internal_subnet_ids.append(subnet['id']) + elif not iface['portip']: + self.fail(msg='put an ip in portip or remove it from list to assign default port to router') + else: + for existing_port in self.conn.list_ports(filters={'network_id': net.id}): + for fixed_ip in existing_port['fixed_ips']: + if iface['portip'] == fixed_ip['ip_address']: + internal_port_ids.append(existing_port.id) + existing_port_ips.append(fixed_ip['ip_address']) + if iface['portip'] not in existing_port_ips: + p = self.conn.create_port(network_id=net.id, fixed_ips=[ + { + 'ip_address': iface['portip'], + 'subnet_id': subnet.id + } + ]) + if p: + internal_port_ids.append(p.id) + + return external_subnet_ids, internal_subnet_ids, internal_port_ids + + def run(self): + + state = self.params['state'] + name = self.params['name'] + network = self.params['network'] + project = self.params['project'] + + if self.params['external_fixed_ips'] and not network: + self.fail_json(msg='network is required when supplying external_fixed_ips') + + if project is not None: + proj = self.conn.get_project(project) + if proj is None: + self.fail(msg='Project %s could not be found' % project) + project_id = proj['id'] + filters = {'tenant_id': project_id} + else: + project_id = None + filters = None + + router = self.conn.get_router(name, filters=filters) + net = None + if network: + net = self.conn.get_network(network) + if not net: + self.fail(msg='network %s not found' % network) + + # Validate and cache the subnet IDs so we can avoid duplicate checks + # and expensive API calls. + external_ids, subnet_internal_ids, internal_portids = self._validate_subnets(filters) + if self.ansible.check_mode: + self.exit_json( + changed=self._system_state_change(router, net, subnet_internal_ids, internal_portids, filters) + ) + + if state == 'present': + changed = False + + if not router: + kwargs = self._build_kwargs(router, net) + if project_id: + kwargs['project_id'] = project_id + router = self.conn.create_router(**kwargs) + for int_s_id in subnet_internal_ids: + self.conn.add_router_interface(router, subnet_id=int_s_id) + # add interface by port id as well + for int_p_id in internal_portids: + self.conn.add_router_interface(router, port_id=int_p_id) + changed = True + else: + if self._needs_update(router, net, subnet_internal_ids, internal_portids, filters): + kwargs = self._build_kwargs(router, net) + updated_router = self.conn.update_router(**kwargs) + + # Protect against update_router() not actually + # updating the router. + if not updated_router: + changed = False + + # On a router update, if any internal interfaces were supplied, + # just detach all existing internal interfaces and attach the new. + if internal_portids or subnet_internal_ids: + router = updated_router + ports = self._router_internal_interfaces(router) + for port in ports: + self.conn.remove_router_interface(router, port_id=port['id']) + if internal_portids: + external_ids, subnet_internal_ids, internal_portids = self._validate_subnets(filters) + for int_p_id in internal_portids: + self.conn.add_router_interface(router, port_id=int_p_id) + changed = True + if subnet_internal_ids: + for s_id in subnet_internal_ids: + self.conn.add_router_interface(router, subnet_id=s_id) + changed = True + + self.exit(changed=changed, router=router, id=router['id']) + + elif state == 'absent': + if not router: + self.exit(changed=False) + else: + # We need to detach all internal interfaces on a router before + # we will be allowed to delete it. + ports = self._router_internal_interfaces(router) + router_id = router['id'] + for port in ports: + self.conn.remove_router_interface(router, port_id=port['id']) + self.conn.delete_router(router_id) + self.exit_json(changed=True) + + +def main(): + module = RouterModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/routers_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/routers_info.py new file mode 100644 index 00000000..1eea10f0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/routers_info.py @@ -0,0 +1,190 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Bram Verschueren <verschueren.bram@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: routers_info +short_description: Retrieve information about one or more OpenStack routers. +author: OpenStack Ansible SIG +description: + - Retrieve information about one or more routers from OpenStack. +options: + name: + description: + - Name or ID of the router + required: false + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + required: false + type: dict + suboptions: + project_id: + description: + - Filter the list result by the ID of the project that owns the resource. + type: str + aliases: + - tenant_id + name: + description: + - Filter the list result by the human-readable name of the resource. + type: str + description: + description: + - Filter the list result by the human-readable description of the resource. + type: str + admin_state_up: + description: + - Filter the list result by the administrative state of the resource, which is up (true) or down (false). + type: bool + revision_number: + description: + - Filter the list result by the revision number of the resource. + type: int + tags: + description: + - A list of tags to filter the list result by. Resources that match all tags in this list will be returned. + type: list +requirements: + - "python >= 3.6" + - "openstacksdk" +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Gather information about routers + openstack.cloud.routers_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + register: result + +- name: Show openstack routers + debug: + msg: "{{ result.openstack_routers }}" + +- name: Gather information about a router by name + openstack.cloud.routers_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + name: router1 + register: result + +- name: Show openstack routers + debug: + msg: "{{ result.openstack_routers }}" + +- name: Gather information about a router with filter + openstack.cloud.routers_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + filters: + tenant_id: bc3ea709c96849d6b81f54640400a19f + register: result + +- name: Show openstack routers + debug: + msg: "{{ result.openstack_routers }}" +''' + +RETURN = ''' +openstack_routers: + description: has all the openstack information about the routers + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the router. + returned: success + type: str + status: + description: Router status. + returned: success + type: str + external_gateway_info: + description: The external gateway information of the router. + returned: success + type: dict + interfaces_info: + description: List of connected interfaces. + returned: success + type: list + distributed: + description: Indicates a distributed router. + returned: success + type: bool + ha: + description: Indicates a highly-available router. + returned: success + type: bool + project_id: + description: Project id associated with this router. + returned: success + type: str + routes: + description: The extra routes configuration for L3 router. + returned: success + type: list +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class RouterInfoModule(OpenStackModule): + + deprecated_names = ('os_routers_info', 'openstack.cloud.os_routers_info') + + argument_spec = dict( + name=dict(required=False, default=None), + filters=dict(required=False, type='dict', default=None) + ) + + def run(self): + + kwargs = self.check_versioned( + filters=self.params['filters'] + ) + if self.params['name']: + kwargs['name_or_id'] = self.params['name'] + routers = self.conn.search_routers(**kwargs) + + for router in routers: + interfaces_info = [] + for port in self.conn.list_router_interfaces(router): + if port.device_owner != "network:router_gateway": + for ip_spec in port.fixed_ips: + int_info = { + 'port_id': port.id, + 'ip_address': ip_spec.get('ip_address'), + 'subnet_id': ip_spec.get('subnet_id') + } + interfaces_info.append(int_info) + router['interfaces_info'] = interfaces_info + + self.exit(changed=False, openstack_routers=routers) + + +def main(): + module = RouterInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/security_group.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/security_group.py new file mode 100644 index 00000000..8208a1c2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/security_group.py @@ -0,0 +1,153 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: security_group +short_description: Add/Delete security groups from an OpenStack cloud. +author: OpenStack Ansible SIG +description: + - Add or Remove security groups from an OpenStack cloud. +options: + name: + description: + - Name that has to be given to the security group. This module + requires that security group names be unique. + required: true + type: str + description: + description: + - Long description of the purpose of the security group + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + project: + description: + - Unique name or ID of the project. + required: false + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a security group +- openstack.cloud.security_group: + cloud: mordred + state: present + name: foo + description: security group for foo servers + +# Update the existing 'foo' security group description +- openstack.cloud.security_group: + cloud: mordred + state: present + name: foo + description: updated description for the foo security group + +# Create a security group for a given project +- openstack.cloud.security_group: + cloud: mordred + state: present + name: foo + project: myproj +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class SecurityGroupModule(OpenStackModule): + + argument_spec = dict( + name=dict(required=True), + description=dict(default=''), + state=dict(default='present', choices=['absent', 'present']), + project=dict(default=None), + ) + + def _needs_update(self, secgroup): + """Check for differences in the updatable values. + + NOTE: We don't currently allow name updates. + """ + if secgroup['description'] != self.params['description']: + return True + return False + + def _system_state_change(self, secgroup): + state = self.params['state'] + if state == 'present': + if not secgroup: + return True + return self._needs_update(secgroup) + if state == 'absent' and secgroup: + return True + return False + + def run(self): + + name = self.params['name'] + state = self.params['state'] + description = self.params['description'] + project = self.params['project'] + + if project is not None: + proj = self.conn.get_project(project) + if proj is None: + self.fail_json(msg='Project %s could not be found' % project) + project_id = proj['id'] + else: + project_id = self.conn.current_project_id + + if project_id: + filters = {'tenant_id': project_id} + else: + filters = None + + secgroup = self.conn.get_security_group(name, filters=filters) + + if self.ansible.check_mode: + self.exit(changed=self._system_state_change(secgroup)) + + changed = False + if state == 'present': + if not secgroup: + kwargs = {} + if project_id: + kwargs['project_id'] = project_id + secgroup = self.conn.create_security_group(name, description, + **kwargs) + changed = True + else: + if self._needs_update(secgroup): + secgroup = self.conn.update_security_group( + secgroup['id'], description=description) + changed = True + self.exit( + changed=changed, id=secgroup['id'], secgroup=secgroup) + + if state == 'absent': + if secgroup: + self.conn.delete_security_group(secgroup['id']) + changed = True + self.exit(changed=changed) + + +def main(): + module = SecurityGroupModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/security_group_rule.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/security_group_rule.py new file mode 100644 index 00000000..6a0e0c99 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/security_group_rule.py @@ -0,0 +1,385 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: security_group_rule +short_description: Add/Delete rule from an existing security group +author: OpenStack Ansible SIG +description: + - Add or Remove rule from an existing security group +options: + security_group: + description: + - Name or ID of the security group + required: true + type: str + protocol: + description: + - IP protocols ANY TCP UDP ICMP 112 (VRRP) 132 (SCTP) + choices: ['any', 'tcp', 'udp', 'icmp', '112', '132', None] + type: str + port_range_min: + description: + - Starting port + type: int + port_range_max: + description: + - Ending port + type: int + remote_ip_prefix: + description: + - Source IP address(es) in CIDR notation (exclusive with remote_group) + type: str + remote_group: + description: + - Name or ID of the Security group to link (exclusive with + remote_ip_prefix) + type: str + ethertype: + description: + - Must be IPv4 or IPv6, and addresses represented in CIDR must + match the ingress or egress rules. Not all providers support IPv6. + choices: ['IPv4', 'IPv6'] + default: IPv4 + type: str + direction: + description: + - The direction in which the security group rule is applied. Not + all providers support egress. + choices: ['egress', 'ingress'] + default: ingress + type: str + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + project: + description: + - Unique name or ID of the project. + required: false + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a security group rule +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: tcp + port_range_min: 80 + port_range_max: 80 + remote_ip_prefix: 0.0.0.0/0 + +# Create a security group rule for ping +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: icmp + remote_ip_prefix: 0.0.0.0/0 + +# Another way to create the ping rule +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: icmp + port_range_min: -1 + port_range_max: -1 + remote_ip_prefix: 0.0.0.0/0 + +# Create a TCP rule covering all ports +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: tcp + port_range_min: 1 + port_range_max: 65535 + remote_ip_prefix: 0.0.0.0/0 + +# Another way to create the TCP rule above (defaults to all ports) +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: tcp + remote_ip_prefix: 0.0.0.0/0 + +# Create a rule for VRRP with numbered protocol 112 +- openstack.cloud.security_group_rule: + security_group: loadbalancer_sg + protocol: 112 + remote_group: loadbalancer-node_sg + +# Create a security group rule for a given project +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: icmp + remote_ip_prefix: 0.0.0.0/0 + project: myproj + +# Remove the default created egress rule for IPv4 +- openstack.cloud.security_group_rule: + cloud: mordred + security_group: foo + protocol: any + remote_ip_prefix: 0.0.0.0/0 +''' + +RETURN = ''' +id: + description: Unique rule UUID. + type: str + returned: state == present +direction: + description: The direction in which the security group rule is applied. + type: str + sample: 'egress' + returned: state == present +ethertype: + description: One of IPv4 or IPv6. + type: str + sample: 'IPv4' + returned: state == present +port_range_min: + description: The minimum port number in the range that is matched by + the security group rule. + type: int + sample: 8000 + returned: state == present +port_range_max: + description: The maximum port number in the range that is matched by + the security group rule. + type: int + sample: 8000 + returned: state == present +protocol: + description: The protocol that is matched by the security group rule. + type: str + sample: 'tcp' + returned: state == present +remote_ip_prefix: + description: The remote IP prefix to be associated with this security group rule. + type: str + sample: '0.0.0.0/0' + returned: state == present +security_group_id: + description: The security group ID to associate with this security group rule. + type: str + returned: state == present +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + OpenStackModule) + + +def _ports_match(protocol, module_min, module_max, rule_min, rule_max): + """ + Capture the complex port matching logic. + + The port values coming in for the module might be -1 (for ICMP), + which will work only for Nova, but this is handled by sdk. Likewise, + they might be None, which works for Neutron, but not Nova. This too is + handled by sdk. Since sdk will consistently return these port + values as None, we need to convert any -1 values input to the module + to None here for comparison. + + For TCP and UDP protocols, None values for both min and max are + represented as the range 1-65535 for Nova, but remain None for + Neutron. sdk returns the full range when Nova is the backend (since + that is how Nova stores them), and None values for Neutron. If None + values are input to the module for both values, then we need to adjust + for comparison. + """ + + # Check if the user is supplying -1 for ICMP. + if protocol == 'icmp': + if module_min and int(module_min) == -1: + module_min = None + if module_max and int(module_max) == -1: + module_max = None + + # Rules with 'any' protocol do not match ports + if protocol == 'any': + return True + + # Check if the user is supplying -1, 1 to 65535 or None values for full TPC/UDP port range. + if protocol in ['tcp', 'udp'] or protocol is None: + if ( + not module_min and not module_max + or (int(module_min) in [-1, 1] + and int(module_max) in [-1, 65535]) + ): + if ( + not rule_min and not rule_max + or (int(rule_min) in [-1, 1] + and int(rule_max) in [-1, 65535]) + ): + # (None, None) == (1, 65535) == (-1, -1) + return True + + # Sanity check to make sure we don't have type comparison issues. + if module_min: + module_min = int(module_min) + if module_max: + module_max = int(module_max) + if rule_min: + rule_min = int(rule_min) + if rule_max: + rule_max = int(rule_max) + + return module_min == rule_min and module_max == rule_max + + +class SecurityGroupRuleModule(OpenStackModule): + deprecated_names = ('os_security_group_rule', 'openstack.cloud.os_security_group_rule') + + argument_spec = dict( + security_group=dict(required=True), + # NOTE(Shrews): None is an acceptable protocol value for + # Neutron, but Nova will balk at this. + protocol=dict(default=None, + choices=[None, 'any', 'tcp', 'udp', 'icmp', '112', '132']), + port_range_min=dict(required=False, type='int'), + port_range_max=dict(required=False, type='int'), + remote_ip_prefix=dict(required=False, default=None), + remote_group=dict(required=False, default=None), + ethertype=dict(default='IPv4', + choices=['IPv4', 'IPv6']), + direction=dict(default='ingress', + choices=['egress', 'ingress']), + state=dict(default='present', + choices=['absent', 'present']), + project=dict(default=None), + ) + + module_kwargs = dict( + mutually_exclusive=[ + ['remote_ip_prefix', 'remote_group'], + ] + ) + + def _find_matching_rule(self, secgroup, remotegroup): + """ + Find a rule in the group that matches the module parameters. + :returns: The matching rule dict, or None if no matches. + """ + protocol = self.params['protocol'] + remote_ip_prefix = self.params['remote_ip_prefix'] + ethertype = self.params['ethertype'] + direction = self.params['direction'] + remote_group_id = remotegroup['id'] + + for rule in secgroup['security_group_rules']: + if ( + protocol == rule['protocol'] + and remote_ip_prefix == rule['remote_ip_prefix'] + and ethertype == rule['ethertype'] + and direction == rule['direction'] + and remote_group_id == rule['remote_group_id'] + and _ports_match( + protocol, + self.params['port_range_min'], + self.params['port_range_max'], + rule['port_range_min'], + rule['port_range_max']) + ): + return rule + return None + + def _system_state_change(self, secgroup, remotegroup): + state = self.params['state'] + if secgroup: + rule_exists = self._find_matching_rule(secgroup, remotegroup) + else: + return False + + if state == 'present' and not rule_exists: + return True + if state == 'absent' and rule_exists: + return True + return False + + def run(self): + + state = self.params['state'] + security_group = self.params['security_group'] + remote_group = self.params['remote_group'] + project = self.params['project'] + changed = False + + if project is not None: + proj = self.conn.get_project(project) + if proj is None: + self.fail_json(msg='Project %s could not be found' % project) + project_id = proj['id'] + else: + project_id = self.conn.current_project_id + + if project_id and not remote_group: + filters = {'tenant_id': project_id} + else: + filters = None + + secgroup = self.conn.get_security_group(security_group, filters=filters) + + if remote_group: + remotegroup = self.conn.get_security_group(remote_group, filters=filters) + else: + remotegroup = {'id': None} + + if self.ansible.check_mode: + self.exit_json(changed=self._system_state_change(secgroup, remotegroup)) + + if state == 'present': + if self.params['protocol'] == 'any': + self.params['protocol'] = None + + if not secgroup: + self.fail_json(msg='Could not find security group %s' % security_group) + + rule = self._find_matching_rule(secgroup, remotegroup) + if not rule: + kwargs = {} + if project_id: + kwargs['project_id'] = project_id + rule = self.conn.create_security_group_rule( + secgroup['id'], + port_range_min=self.params['port_range_min'], + port_range_max=self.params['port_range_max'], + protocol=self.params['protocol'], + remote_ip_prefix=self.params['remote_ip_prefix'], + remote_group_id=remotegroup['id'], + direction=self.params['direction'], + ethertype=self.params['ethertype'], + **kwargs + ) + changed = True + self.exit_json(changed=changed, rule=rule, id=rule['id']) + + if state == 'absent' and secgroup: + rule = self._find_matching_rule(secgroup, remotegroup) + if rule: + self.conn.delete_security_group_rule(rule['id']) + changed = True + + self.exit_json(changed=changed) + + +def main(): + module = SecurityGroupRuleModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server.py new file mode 100644 index 00000000..570425a2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server.py @@ -0,0 +1,799 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright 2019 Red Hat, Inc. +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy <benno@ansible.com> +# Copyright (c) 2013, John Dewey <john@dewey.ws> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: server +short_description: Create/Delete Compute Instances from OpenStack +author: OpenStack Ansible SIG +description: + - Create or Remove compute instances from OpenStack. +options: + name: + description: + - Name that has to be given to the instance. It is also possible to + specify the ID of the instance instead of its name if I(state) is I(absent). + required: true + type: str + image: + description: + - The name or id of the base image to boot. + - Required when I(boot_from_volume=true) + type: str + image_exclude: + description: + - Text to use to filter image names, for the case, such as HP, where + there are multiple image names matching the common identifying + portions. image_exclude is a negative match filter - it is text that + may not exist in the image name. + type: str + default: "(deprecated)" + flavor: + description: + - The name or id of the flavor in which the new instance has to be + created. + - Exactly one of I(flavor) and I(flavor_ram) must be defined when + I(state=present). + type: str + flavor_ram: + description: + - The minimum amount of ram in MB that the flavor in which the new + instance has to be created must have. + - Exactly one of I(flavor) and I(flavor_ram) must be defined when + I(state=present). + type: int + flavor_include: + description: + - Text to use to filter flavor names, for the case, such as Rackspace, + where there are multiple flavors that have the same ram count. + flavor_include is a positive match filter - it must exist in the + flavor name. + type: str + key_name: + description: + - The key pair name to be used when creating a instance + type: str + security_groups: + description: + - Names of the security groups to which the instance should be + added. This may be a YAML list or a comma separated string. + type: list + default: ['default'] + elements: str + network: + description: + - Name or ID of a network to attach this instance to. A simpler + version of the nics parameter, only one of network or nics should + be supplied. + type: str + nics: + description: + - A list of networks to which the instance's interface should + be attached. Networks may be referenced by net-id/net-name/port-id + or port-name. + - 'Also this accepts a string containing a list of (net/port)-(id/name) + Eg: nics: "net-id=uuid-1,port-name=myport" + Only one of network or nics should be supplied.' + type: list + elements: raw + suboptions: + tag: + description: + - 'A "tag" for the specific port to be passed via metadata. + Eg: tag: test_tag' + auto_ip: + description: + - Ensure instance has public ip however the cloud wants to do that + type: bool + default: 'yes' + aliases: ['auto_floating_ip', 'public_ip'] + floating_ips: + description: + - list of valid floating IPs that pre-exist to assign to this node + type: list + elements: str + floating_ip_pools: + description: + - Name of floating IP pool from which to choose a floating IP + type: list + elements: str + meta: + description: + - 'A list of key value pairs that should be provided as a metadata to + the new instance or a string containing a list of key-value pairs. + Eg: meta: "key1=value1,key2=value2"' + type: raw + wait: + description: + - If the module should wait for the instance to be created. + type: bool + default: 'yes' + timeout: + description: + - The amount of time the module should wait for the instance to get + into active state. + default: 180 + type: int + config_drive: + description: + - Whether to boot the server with config drive enabled + type: bool + default: 'no' + userdata: + description: + - Opaque blob of data which is made available to the instance + type: str + aliases: ['user_data'] + boot_from_volume: + description: + - Should the instance boot from a persistent volume created based on + the image given. Mutually exclusive with boot_volume. + type: bool + default: 'no' + volume_size: + description: + - The size of the volume to create in GB if booting from volume based + on an image. + type: int + boot_volume: + description: + - Volume name or id to use as the volume to boot from. Implies + boot_from_volume. Mutually exclusive with image and boot_from_volume. + aliases: ['root_volume'] + type: str + terminate_volume: + description: + - If C(yes), delete volume when deleting instance (if booted from volume) + type: bool + default: 'no' + volumes: + description: + - A list of preexisting volumes names or ids to attach to the instance + default: [] + type: list + elements: str + scheduler_hints: + description: + - Arbitrary key/value pairs to the scheduler for custom use + type: dict + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + delete_fip: + description: + - When I(state) is absent and this option is true, any floating IP + associated with the instance will be deleted along with the instance. + type: bool + default: 'no' + reuse_ips: + description: + - When I(auto_ip) is true and this option is true, the I(auto_ip) code + will attempt to re-use unassigned floating ips in the project before + creating a new one. It is important to note that it is impossible + to safely do this concurrently, so if your use case involves + concurrent server creation, it is highly recommended to set this to + false and to delete the floating ip associated with a server when + the server is deleted using I(delete_fip). + type: bool + default: 'yes' + availability_zone: + description: + - Availability zone in which to create the server. + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Create a new instance and attaches to a network and passes metadata to the instance + openstack.cloud.server: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + timeout: 200 + flavor: 4 + nics: + - net-id: 34605f38-e52a-25d2-b6ec-754a13ffb723 + - net-name: another_network + meta: + hostname: test1 + group: uge_master + +# Create a new instance in HP Cloud AE1 region availability zone az2 and +# automatically assigns a floating IP +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + state: present + auth: + auth_url: https://identity.example.com + username: username + password: Equality7-2521 + project_name: username-project1 + name: vm1 + region_name: region-b.geo-1 + availability_zone: az2 + image: 9302692b-b787-4b52-a3a6-daebb79cb498 + key_name: test + timeout: 200 + flavor: 101 + security_groups: default + auto_ip: yes + +# Create a new instance in named cloud mordred availability zone az2 +# and assigns a pre-known floating IP +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + state: present + cloud: mordred + name: vm1 + availability_zone: az2 + image: 9302692b-b787-4b52-a3a6-daebb79cb498 + key_name: test + timeout: 200 + flavor: 101 + floating_ips: + - 12.34.56.79 + +# Create a new instance with 4G of RAM on Ubuntu Trusty, ignoring +# deprecated images +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + name: vm1 + state: present + cloud: mordred + region_name: region-b.geo-1 + image: Ubuntu Server 14.04 + image_exclude: deprecated + flavor_ram: 4096 + +# Create a new instance with 4G of RAM on Ubuntu Trusty on a Performance node +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + name: vm1 + cloud: rax-dfw + state: present + image: Ubuntu 14.04 LTS (Trusty Tahr) (PVHVM) + flavor_ram: 4096 + flavor_include: Performance + +# Creates a new instance and attaches to multiple network +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance with a string + openstack.cloud.server: + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + timeout: 200 + flavor: 4 + nics: "net-id=4cb08b20-62fe-11e5-9d70-feff819cdc9f,net-id=542f0430-62fe-11e5-9d70-feff819cdc9f..." + +- name: Creates a new instance and attaches to a network and passes metadata to the instance + openstack.cloud.server: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + timeout: 200 + flavor: 4 + nics: + - net-id: 34605f38-e52a-25d2-b6ec-754a13ffb723 + - net-name: another_network + meta: "hostname=test1,group=uge_master" + +- name: Creates a new instance and attaches to a specific network + openstack.cloud.server: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + timeout: 200 + flavor: 4 + network: another_network + +# Create a new instance with 4G of RAM on a 75G Ubuntu Trusty volume +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + name: vm1 + state: present + cloud: mordred + region_name: ams01 + image: Ubuntu Server 14.04 + flavor_ram: 4096 + boot_from_volume: True + volume_size: 75 + +# Creates a new instance with 2 volumes attached +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + name: vm1 + state: present + cloud: mordred + region_name: ams01 + image: Ubuntu Server 14.04 + flavor_ram: 4096 + volumes: + - photos + - music + +# Creates a new instance with provisioning userdata using Cloud-Init +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + name: vm1 + state: present + image: "Ubuntu Server 14.04" + flavor: "P-1" + network: "Production" + userdata: | + #cloud-config + chpasswd: + list: | + ubuntu:{{ default_password }} + expire: False + packages: + - ansible + package_upgrade: true + +# Creates a new instance with provisioning userdata using Bash Scripts +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + name: vm1 + state: present + image: "Ubuntu Server 14.04" + flavor: "P-1" + network: "Production" + userdata: | + {%- raw -%}#!/bin/bash + echo " up ip route add 10.0.0.0/8 via {% endraw -%}{{ intra_router }}{%- raw -%}" >> /etc/network/interfaces.d/eth0.conf + echo " down ip route del 10.0.0.0/8" >> /etc/network/interfaces.d/eth0.conf + ifdown eth0 && ifup eth0 + {% endraw %} + +# Create a new instance with server group for (anti-)affinity +# server group ID is returned from openstack.cloud.server_group module. +- name: launch a compute instance + hosts: localhost + tasks: + - name: launch an instance + openstack.cloud.server: + state: present + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + flavor: 4 + scheduler_hints: + group: f5c8c61a-9230-400a-8ed2-3b023c190a7f + +# Create an instance with "tags" for the nic +- name: Create instance with nics "tags" + openstack.cloud.server: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: vm1 + image: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + flavor: 4 + nics: + - port-name: net1_port1 + tag: test_tag + - net-name: another_network + +# Deletes an instance via its ID +- name: remove an instance + hosts: localhost + tasks: + - name: remove an instance + openstack.cloud.server: + name: abcdef01-2345-6789-0abc-def0123456789 + state: absent + +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_find_nova_addresses, OpenStackModule) + + +def _parse_nics(nics): + for net in nics: + if isinstance(net, str): + for nic in net.split(','): + yield dict((nic.split('='),)) + else: + yield net + + +def _parse_meta(meta): + if isinstance(meta, str): + metas = {} + for kv_str in meta.split(","): + k, v = kv_str.split("=") + metas[k] = v + return metas + if not meta: + return {} + return meta + + +class ServerModule(OpenStackModule): + deprecated_names = ('os_server', 'openstack.cloud.os_server') + + argument_spec = dict( + name=dict(required=True), + image=dict(default=None), + image_exclude=dict(default='(deprecated)'), + flavor=dict(default=None), + flavor_ram=dict(default=None, type='int'), + flavor_include=dict(default=None), + key_name=dict(default=None), + security_groups=dict(default=['default'], type='list', elements='str'), + network=dict(default=None), + nics=dict(default=[], type='list', elements='raw'), + meta=dict(default=None, type='raw'), + userdata=dict(default=None, aliases=['user_data']), + config_drive=dict(default=False, type='bool'), + auto_ip=dict(default=True, type='bool', aliases=['auto_floating_ip', 'public_ip']), + floating_ips=dict(default=None, type='list', elements='str'), + floating_ip_pools=dict(default=None, type='list', elements='str'), + volume_size=dict(default=None, type='int'), + boot_from_volume=dict(default=False, type='bool'), + boot_volume=dict(default=None, aliases=['root_volume']), + terminate_volume=dict(default=False, type='bool'), + volumes=dict(default=[], type='list', elements='str'), + scheduler_hints=dict(default=None, type='dict'), + state=dict(default='present', choices=['absent', 'present']), + delete_fip=dict(default=False, type='bool'), + reuse_ips=dict(default=True, type='bool'), + ) + module_kwargs = dict( + mutually_exclusive=[ + ['auto_ip', 'floating_ips'], + ['auto_ip', 'floating_ip_pools'], + ['floating_ips', 'floating_ip_pools'], + ['flavor', 'flavor_ram'], + ['image', 'boot_volume'], + ['boot_from_volume', 'boot_volume'], + ['nics', 'network'], + ], + required_if=[ + ('boot_from_volume', True, ['volume_size', 'image']), + ], + ) + + def run(self): + + state = self.params['state'] + image = self.params['image'] + boot_volume = self.params['boot_volume'] + flavor = self.params['flavor'] + flavor_ram = self.params['flavor_ram'] + + if state == 'present': + if not (image or boot_volume): + self.fail( + msg="Parameter 'image' or 'boot_volume' is required " + "if state == 'present'" + ) + if not flavor and not flavor_ram: + self.fail( + msg="Parameter 'flavor' or 'flavor_ram' is required " + "if state == 'present'" + ) + + if state == 'present': + self._get_server_state() + self._create_server() + elif state == 'absent': + self._get_server_state() + self._delete_server() + + def _exit_hostvars(self, server, changed=True): + hostvars = self.conn.get_openstack_vars(server) + self.exit( + changed=changed, server=server, id=server.id, openstack=hostvars) + + def _get_server_state(self): + state = self.params['state'] + server = self.conn.get_server(self.params['name']) + if server and state == 'present': + if server.status not in ('ACTIVE', 'SHUTOFF', 'PAUSED', 'SUSPENDED'): + self.fail( + msg="The instance is available but not Active state: " + server.status) + (ip_changed, server) = self._check_ips(server) + (sg_changed, server) = self._check_security_groups(server) + (server_changed, server) = self._update_server(server) + self._exit_hostvars(server, ip_changed or sg_changed or server_changed) + if server and state == 'absent': + return True + if state == 'absent': + self.exit(changed=False, result="not present") + return True + + def _create_server(self): + flavor = self.params['flavor'] + flavor_ram = self.params['flavor_ram'] + flavor_include = self.params['flavor_include'] + + image_id = None + if not self.params['boot_volume']: + image_id = self.conn.get_image_id( + self.params['image'], self.params['image_exclude']) + if not image_id: + self.fail( + msg="Could not find image %s" % self.params['image']) + + if flavor: + flavor_dict = self.conn.get_flavor(flavor) + if not flavor_dict: + self.fail(msg="Could not find flavor %s" % flavor) + else: + flavor_dict = self.conn.get_flavor_by_ram(flavor_ram, flavor_include) + if not flavor_dict: + self.fail(msg="Could not find any matching flavor") + + nics = self._network_args() + + self.params['meta'] = _parse_meta(self.params['meta']) + + bootkwargs = self.check_versioned( + name=self.params['name'], + image=image_id, + flavor=flavor_dict['id'], + nics=nics, + meta=self.params['meta'], + security_groups=self.params['security_groups'], + userdata=self.params['userdata'], + config_drive=self.params['config_drive'], + ) + for optional_param in ( + 'key_name', 'availability_zone', 'network', + 'scheduler_hints', 'volume_size', 'volumes'): + if self.params[optional_param]: + bootkwargs[optional_param] = self.params[optional_param] + + server = self.conn.create_server( + ip_pool=self.params['floating_ip_pools'], + ips=self.params['floating_ips'], + auto_ip=self.params['auto_ip'], + boot_volume=self.params['boot_volume'], + boot_from_volume=self.params['boot_from_volume'], + terminate_volume=self.params['terminate_volume'], + reuse_ips=self.params['reuse_ips'], + wait=self.params['wait'], timeout=self.params['timeout'], + **bootkwargs + ) + + self._exit_hostvars(server) + + def _update_server(self, server): + changed = False + + self.params['meta'] = _parse_meta(self.params['meta']) + + # self.conn.set_server_metadata only updates the key=value pairs, it doesn't + # touch existing ones + update_meta = {} + for (k, v) in self.params['meta'].items(): + if k not in server.metadata or server.metadata[k] != v: + update_meta[k] = v + + if update_meta: + self.conn.set_server_metadata(server, update_meta) + changed = True + # Refresh server vars + server = self.conn.get_server(self.params['name']) + + return (changed, server) + + def _delete_server(self): + try: + self.conn.delete_server( + self.params['name'], wait=self.params['wait'], + timeout=self.params['timeout'], + delete_ips=self.params['delete_fip']) + except Exception as e: + self.fail(msg="Error in deleting vm: %s" % e) + self.exit(changed=True, result='deleted') + + def _network_args(self): + args = [] + nics = self.params['nics'] + + if not isinstance(nics, list): + self.fail(msg='The \'nics\' parameter must be a list.') + + for num, net in enumerate(_parse_nics(nics)): + if not isinstance(net, dict): + self.fail( + msg='Each entry in the \'nics\' parameter must be a dict.') + + if net.get('net-id'): + args.append(net) + elif net.get('net-name'): + by_name = self.conn.get_network(net['net-name']) + if not by_name: + self.fail( + msg='Could not find network by net-name: %s' % + net['net-name']) + resolved_net = net.copy() + del resolved_net['net-name'] + resolved_net['net-id'] = by_name['id'] + args.append(resolved_net) + elif net.get('port-id'): + args.append(net) + elif net.get('port-name'): + by_name = self.conn.get_port(net['port-name']) + if not by_name: + self.fail( + msg='Could not find port by port-name: %s' % + net['port-name']) + resolved_net = net.copy() + del resolved_net['port-name'] + resolved_net['port-id'] = by_name['id'] + args.append(resolved_net) + + if 'tag' in net: + args[num]['tag'] = net['tag'] + return args + + def _detach_ip_list(self, server, extra_ips): + for ip in extra_ips: + ip_id = self.conn.get_floating_ip( + id=None, filters={'floating_ip_address': ip}) + self.conn.detach_ip_from_server( + server_id=server.id, floating_ip_id=ip_id) + + def _check_ips(self, server): + changed = False + + auto_ip = self.params['auto_ip'] + floating_ips = self.params['floating_ips'] + floating_ip_pools = self.params['floating_ip_pools'] + + if floating_ip_pools or floating_ips: + ips = openstack_find_nova_addresses(server.addresses, 'floating') + if not ips: + # If we're configured to have a floating but we don't have one, + # let's add one + server = self.conn.add_ips_to_server( + server, + auto_ip=auto_ip, + ips=floating_ips, + ip_pool=floating_ip_pools, + wait=self.params['wait'], + timeout=self.params['timeout'], + ) + changed = True + elif floating_ips: + # we were configured to have specific ips, let's make sure we have + # those + missing_ips = [] + for ip in floating_ips: + if ip not in ips: + missing_ips.append(ip) + if missing_ips: + server = self.conn.add_ip_list(server, missing_ips, + wait=self.params['wait'], + timeout=self.params['timeout']) + changed = True + extra_ips = [] + for ip in ips: + if ip not in floating_ips: + extra_ips.append(ip) + if extra_ips: + self._detach_ip_list(server, extra_ips) + changed = True + elif auto_ip: + if server['interface_ip']: + changed = False + else: + # We're configured for auto_ip but we're not showing an + # interface_ip. Maybe someone deleted an IP out from under us. + server = self.conn.add_ips_to_server( + server, + auto_ip=auto_ip, + ips=floating_ips, + ip_pool=floating_ip_pools, + wait=self.params['wait'], + timeout=self.params['timeout'], + ) + changed = True + return (changed, server) + + def _check_security_groups(self, server): + changed = False + + # server security groups were added to shade in 1.19. Until then this + # module simply ignored trying to update security groups and only set them + # on newly created hosts. + if not ( + hasattr(self.conn, 'add_server_security_groups') + and hasattr(self.conn, 'remove_server_security_groups') + ): + return changed, server + + module_security_groups = set(self.params['security_groups']) + server_security_groups = set(sg['name'] for sg in server.security_groups) + + add_sgs = module_security_groups - server_security_groups + remove_sgs = server_security_groups - module_security_groups + + if add_sgs: + self.conn.add_server_security_groups(server, list(add_sgs)) + changed = True + + if remove_sgs: + self.conn.remove_server_security_groups(server, list(remove_sgs)) + changed = True + + return (changed, server) + + +def main(): + module = ServerModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_action.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_action.py new file mode 100644 index 00000000..68bd62b6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_action.py @@ -0,0 +1,182 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright (c) 2015, Jesse Keating <jlk@derpops.bike> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: server_action +short_description: Perform actions on Compute Instances from OpenStack +author: OpenStack Ansible SIG +description: + - Perform server actions on an existing compute instance from OpenStack. + This module does not return any data other than changed true/false. + When I(action) is 'rebuild', then I(image) parameter is required. +options: + server: + description: + - Name or ID of the instance + required: true + type: str + wait: + description: + - If the module should wait for the instance action to be performed. + type: bool + default: 'yes' + timeout: + description: + - The amount of time the module should wait for the instance to perform + the requested action. + default: 180 + type: int + action: + description: + - Perform the given action. The lock and unlock actions always return + changed as the servers API does not provide lock status. + choices: [stop, start, pause, unpause, lock, unlock, suspend, resume, + rebuild] + type: str + required: true + image: + description: + - Image the server should be rebuilt with + type: str + admin_password: + description: + - Admin password for server to rebuild + type: str + +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Pauses a compute instance +- openstack.cloud.server_action: + action: pause + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + server: vm1 + timeout: 200 +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +_action_map = {'stop': 'SHUTOFF', + 'start': 'ACTIVE', + 'pause': 'PAUSED', + 'unpause': 'ACTIVE', + 'lock': 'ACTIVE', # API doesn't show lock/unlock status + 'unlock': 'ACTIVE', + 'suspend': 'SUSPENDED', + 'resume': 'ACTIVE', + 'rebuild': 'ACTIVE'} + +_admin_actions = ['pause', 'unpause', 'suspend', 'resume', 'lock', 'unlock'] + + +class ServerActionModule(OpenStackModule): + deprecated_names = ('os_server_action', 'openstack.cloud.os_server_action') + + argument_spec = dict( + server=dict(required=True, type='str'), + action=dict(required=True, type='str', + choices=['stop', 'start', 'pause', 'unpause', + 'lock', 'unlock', 'suspend', 'resume', + 'rebuild']), + image=dict(required=False, type='str'), + admin_password=dict(required=False, type='str'), + ) + module_kwargs = dict( + required_if=[('action', 'rebuild', ['image'])], + supports_check_mode=True, + ) + + def run(self): + os_server = self._preliminary_checks() + self._execute_server_action(os_server) + # for some reason we don't wait for lock and unlock before exit + if self.params['action'] not in ('lock', 'unlock'): + if self.params['wait']: + self._wait(os_server) + self.exit_json(changed=True) + + def _preliminary_checks(self): + # Using Munch object for getting information about a server + os_server = self.conn.get_server(self.params['server']) + if not os_server: + self.fail_json(msg='Could not find server %s' % self.params['server']) + # check mode + if self.ansible.check_mode: + self.exit_json(changed=self.__system_state_change(os_server)) + # examine special cases + # lock, unlock and rebuild don't depend on state, just do it + if self.params['action'] not in ('lock', 'unlock', 'rebuild'): + if not self.__system_state_change(os_server): + self.exit_json(changed=False) + return os_server + + def _execute_server_action(self, os_server): + if self.params['action'] == 'rebuild': + return self._rebuild_server(os_server) + action_name = self.params['action'] + "_server" + try: + func_name = getattr(self.conn.compute, action_name) + except AttributeError: + self.fail_json( + msg="Method %s wasn't found in OpenstackSDK compute" % action_name) + func_name(os_server) + + def _rebuild_server(self, os_server): + # rebuild should ensure images exists + try: + image = self.conn.get_image(self.params['image']) + except Exception as e: + self.fail_json( + msg="Can't find the image %s: %s" % (self.params['image'], e)) + if not image: + self.fail_json(msg="Image %s was not found!" % self.params['image']) + # admin_password is required by SDK, but not required by Nova API + if self.params['admin_password']: + self.conn.compute.rebuild_server( + server=os_server, + name=os_server['name'], + image=image['id'], + admin_password=self.params['admin_password'] + ) + else: + self.conn.compute.post( + '/servers/{server_id}/action'.format( + server_id=os_server['id']), + json={'rebuild': {'imageRef': image['id']}}) + + def _wait(self, os_server): + """Wait for the server to reach the desired state for the given action.""" + # Using Server object for wait_for_server function + server = self.conn.compute.find_server(self.params['server']) + self.conn.compute.wait_for_server( + server, + status=_action_map[self.params['action']], + wait=self.params['timeout']) + + def __system_state_change(self, os_server): + """Check if system state would change.""" + return os_server.status != _action_map[self.params['action']] + + +def main(): + module = ServerActionModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_group.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_group.py new file mode 100644 index 00000000..444874b6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_group.py @@ -0,0 +1,166 @@ +#!/usr/bin/python + +# Copyright (c) 2016 Catalyst IT Limited +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: server_group +short_description: Manage OpenStack server groups +author: OpenStack Ansible SIG +description: + - Add or remove server groups from OpenStack. +options: + state: + description: + - Indicate desired state of the resource. When I(state) is 'present', + then I(policies) is required. + choices: ['present', 'absent'] + required: false + default: present + type: str + name: + description: + - Server group name. + required: true + type: str + policies: + description: + - A list of one or more policy names to associate with the server + group. The list must contain at least one policy name. The current + valid policy names are anti-affinity, affinity, soft-anti-affinity + and soft-affinity. + required: false + type: list + elements: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a server group with 'affinity' policy. +- openstack.cloud.server_group: + state: present + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: my_server_group + policies: + - affinity + +# Delete 'my_server_group' server group. +- openstack.cloud.server_group: + state: absent + auth: + auth_url: https://identity.example.com + username: admin + password: admin + project_name: admin + name: my_server_group +''' + +RETURN = ''' +id: + description: Unique UUID. + returned: success + type: str +name: + description: The name of the server group. + returned: success + type: str +policies: + description: A list of one or more policy names of the server group. + returned: success + type: list +members: + description: A list of members in the server group. + returned: success + type: list +metadata: + description: Metadata key and value pairs. + returned: success + type: dict +project_id: + description: The project ID who owns the server group. + returned: success + type: str +user_id: + description: The user ID who owns the server group. + returned: success + type: str +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) + + +def _system_state_change(state, server_group): + if state == 'present' and not server_group: + return True + if state == 'absent' and server_group: + return True + + return False + + +def main(): + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + policies=dict(required=False, type='list', elements='str'), + state=dict(default='present', choices=['absent', 'present']), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + **module_kwargs + ) + + name = module.params['name'] + policies = module.params['policies'] + state = module.params['state'] + + sdk, cloud = openstack_cloud_from_module(module) + try: + server_group = cloud.get_server_group(name) + + if module.check_mode: + module.exit_json( + changed=_system_state_change(state, server_group) + ) + + changed = False + if state == 'present': + if not server_group: + if not policies: + module.fail_json( + msg="Parameter 'policies' is required in Server Group " + "Create" + ) + server_group = cloud.create_server_group(name, policies) + changed = True + + module.exit_json( + changed=changed, + id=server_group['id'], + server_group=server_group + ) + if state == 'absent': + if server_group: + cloud.delete_server_group(server_group['id']) + changed = True + module.exit_json(changed=changed) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=str(e), extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_info.py new file mode 100644 index 00000000..e9fb3795 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_info.py @@ -0,0 +1,93 @@ +#!/usr/bin/python + +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: server_info +short_description: Retrieve information about one or more compute instances +author: OpenStack Ansible SIG +description: + - Retrieve information about server instances from OpenStack. + - This module was called C(os_server_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.server_info) module no longer returns C(ansible_facts)! +notes: + - The result contains a list of servers. +options: + server: + description: + - restrict results to servers with names or UUID matching + this glob expression (e.g., <web*>). + type: str + detailed: + description: + - when true, return additional detail about servers at the expense + of additional API calls. + type: bool + default: 'no' + filters: + description: + - restrict results to servers matching a dictionary of + filters + type: dict + all_projects: + description: + - Whether to list servers from all projects or just the current auth + scoped project. + type: bool + default: 'no' +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Gather information about all servers named <web*> that are in an active state: +- openstack.cloud.server_info: + cloud: rax-dfw + server: web* + filters: + vm_state: active + register: result +- debug: + msg: "{{ result.openstack_servers }}" +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class ServerInfoModule(OpenStackModule): + + deprecated_names = ('os_server_info', 'openstack.cloud.os_server_info') + + argument_spec = dict( + server=dict(required=False), + detailed=dict(required=False, type='bool', default=False), + filters=dict(required=False, type='dict', default=None), + all_projects=dict(required=False, type='bool', default=False), + ) + + def run(self): + + kwargs = self.check_versioned( + detailed=self.params['detailed'], + filters=self.params['filters'], + all_projects=self.params['all_projects'] + ) + if self.params['server']: + kwargs['name_or_id'] = self.params['server'] + openstack_servers = self.conn.search_servers(**kwargs) + self.exit(changed=False, openstack_servers=openstack_servers) + + +def main(): + module = ServerInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_metadata.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_metadata.py new file mode 100644 index 00000000..acc16d31 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_metadata.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright (c) 2016, Mario Santos <mario.rf.santos@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: server_metadata +short_description: Add/Update/Delete Metadata in Compute Instances from OpenStack +author: OpenStack Ansible SIG +description: + - Add, Update or Remove metadata in compute instances from OpenStack. +options: + server: + description: + - Name of the instance to update the metadata + required: true + aliases: ['name'] + type: str + meta: + description: + - 'A list of key value pairs that should be provided as a metadata to + the instance or a string containing a list of key-value pairs. + Eg: meta: "key1=value1,key2=value2"' + required: true + type: dict + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + availability_zone: + description: + - Availability zone in which to create the snapshot. + required: false + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Creates or updates hostname=test1 as metadata of the server instance vm1 +- name: add metadata to compute instance + hosts: localhost + tasks: + - name: add metadata to instance + openstack.cloud.server_metadata: + state: present + auth: + auth_url: https://openstack-api.example.com:35357/v2.0/ + username: admin + password: admin + project_name: admin + name: vm1 + meta: + hostname: test1 + group: group1 + +# Removes the keys under meta from the instance named vm1 +- name: delete metadata from compute instance + hosts: localhost + tasks: + - name: delete metadata from instance + openstack.cloud.server_metadata: + state: absent + auth: + auth_url: https://openstack-api.example.com:35357/v2.0/ + username: admin + password: admin + project_name: admin + name: vm1 + meta: + hostname: + group: +''' + +RETURN = ''' +server_id: + description: The compute instance id where the change was made + returned: success + type: str + sample: "324c4e91-3e03-4f62-9a4d-06119a8a8d16" +metadata: + description: The metadata of compute instance after the change + returned: success + type: dict + sample: {'key1': 'value1', 'key2': 'value2'} +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module, +) + + +def _needs_update(server_metadata=None, metadata=None): + if server_metadata is None: + server_metadata = {} + if metadata is None: + metadata = {} + return len(set(metadata.items()) - set(server_metadata.items())) != 0 + + +def _get_keys_to_delete(server_metadata_keys=None, metadata_keys=None): + if server_metadata_keys is None: + server_metadata_keys = [] + if metadata_keys is None: + metadata_keys = [] + return set(server_metadata_keys) & set(metadata_keys) + + +def main(): + argument_spec = openstack_full_argument_spec( + server=dict(required=True, aliases=['name']), + meta=dict(required=True, type='dict'), + state=dict(default='present', choices=['absent', 'present']), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + state = module.params['state'] + server_param = module.params['server'] + meta_param = module.params['meta'] + changed = False + + sdk, cloud = openstack_cloud_from_module(module) + try: + server = cloud.get_server(server_param) + if not server: + module.fail_json( + msg='Could not find server {0}'.format(server_param)) + + if state == 'present': + # check if it needs update + if _needs_update(server_metadata=server.metadata, + metadata=meta_param): + if not module.check_mode: + cloud.set_server_metadata(server_param, meta_param) + changed = True + elif state == 'absent': + # remove from params the keys that do not exist in the server + keys_to_delete = _get_keys_to_delete(server.metadata.keys(), + meta_param.keys()) + if len(keys_to_delete) > 0: + if not module.check_mode: + cloud.delete_server_metadata(server_param, keys_to_delete) + changed = True + + if changed: + server = cloud.get_server(server_param) + + module.exit_json( + changed=changed, server_id=server.id, metadata=server.metadata) + + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=e.message, extra_data=e.extra_data) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_volume.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_volume.py new file mode 100644 index 00000000..9046723d --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/server_volume.py @@ -0,0 +1,136 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: server_volume +short_description: Attach/Detach Volumes from OpenStack VM's +author: OpenStack Ansible SIG +description: + - Attach or Detach volumes from OpenStack VM's +options: + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + required: false + type: str + server: + description: + - Name or ID of server you want to attach a volume to + required: true + type: str + volume: + description: + - Name or id of volume you want to attach to a server + required: true + type: str + device: + description: + - Device you want to attach. Defaults to auto finding a device name. + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Attaches a volume to a compute host +- name: attach a volume + hosts: localhost + tasks: + - name: attach volume to host + openstack.cloud.server_volume: + state: present + cloud: mordred + server: Mysql-server + volume: mysql-data + device: /dev/vdb +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +def _system_state_change(state, device): + """Check if system state would change.""" + if state == 'present': + if device: + return False + return True + if state == 'absent': + if device: + return True + return False + return False + + +class ServerVolumeModule(OpenStackModule): + + argument_spec = dict( + server=dict(required=True), + volume=dict(required=True), + device=dict(default=None), # None == auto choose device name + state=dict(default='present', choices=['absent', 'present']), + ) + + def run(self): + + state = self.params['state'] + wait = self.params['wait'] + timeout = self.params['timeout'] + + server = self.conn.get_server(self.params['server']) + volume = self.conn.get_volume(self.params['volume']) + + if not volume: + self.fail(msg='volume %s is not found' % self.params['volume']) + + dev = self.conn.get_volume_attach_device(volume, server.id) + + if self.ansible.check_mode: + self.exit(changed=_system_state_change(state, dev)) + + if state == 'present': + changed = False + if not dev: + changed = True + self.conn.attach_volume(server, volume, self.params['device'], + wait=wait, timeout=timeout) + + server = self.conn.get_server(self.params['server']) # refresh + volume = self.conn.get_volume(self.params['volume']) # refresh + hostvars = self.conn.get_openstack_vars(server) + + self.exit( + changed=changed, + id=volume['id'], + attachments=volume['attachments'], + openstack=hostvars + ) + + elif state == 'absent': + if not dev: + # Volume is not attached to this server + self.exit(changed=False) + + self.conn.detach_volume(server, volume, wait=wait, timeout=timeout) + self.exit( + changed=True, + result='Detached volume from server' + ) + + +def main(): + module = ServerVolumeModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/stack.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/stack.py new file mode 100644 index 00000000..3d1d6853 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/stack.py @@ -0,0 +1,274 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (c) 2016, Mathieu Bultel <mbultel@redhat.com> +# (c) 2016, Steve Baker <sbaker@redhat.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: stack +short_description: Add/Remove Heat Stack +author: OpenStack Ansible SIG +description: + - Add or Remove a Stack to an OpenStack Heat +options: + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + type: str + name: + description: + - Name of the stack that should be created, name could be char and digit, no space + required: true + type: str + tag: + description: + - Tag for the stack that should be created, name could be char and digit, no space + type: str + template: + description: + - Path of the template file to use for the stack creation + type: str + environment: + description: + - List of environment files that should be used for the stack creation + type: list + elements: str + parameters: + description: + - Dictionary of parameters for the stack creation + type: dict + rollback: + description: + - Rollback stack creation + type: bool + default: false + timeout: + description: + - Maximum number of seconds to wait for the stack creation + default: 3600 + type: int +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' +EXAMPLES = ''' +--- +- name: create stack + ignore_errors: True + register: stack_create + openstack.cloud.stack: + name: "{{ stack_name }}" + tag: "{{ tag_name }}" + state: present + template: "/path/to/my_stack.yaml" + environment: + - /path/to/resource-registry.yaml + - /path/to/environment.yaml + parameters: + bmc_flavor: m1.medium + bmc_image: CentOS + key_name: default + private_net: "{{ private_net_param }}" + node_count: 2 + name: undercloud + image: CentOS + my_flavor: m1.large + external_net: "{{ external_net_param }}" +''' + +RETURN = ''' +id: + description: Stack ID. + type: str + sample: "97a3f543-8136-4570-920e-fd7605c989d6" + returned: always + +stack: + description: stack info + type: complex + returned: always + contains: + action: + description: Action, could be Create or Update. + type: str + sample: "CREATE" + creation_time: + description: Time when the action has been made. + type: str + sample: "2016-07-05T17:38:12Z" + description: + description: Description of the Stack provided in the heat template. + type: str + sample: "HOT template to create a new instance and networks" + id: + description: Stack ID. + type: str + sample: "97a3f543-8136-4570-920e-fd7605c989d6" + name: + description: Name of the Stack + type: str + sample: "test-stack" + identifier: + description: Identifier of the current Stack action. + type: str + sample: "test-stack/97a3f543-8136-4570-920e-fd7605c989d6" + links: + description: Links to the current Stack. + type: list + elements: dict + sample: "[{'href': 'http://foo:8004/v1/7f6a/stacks/test-stack/97a3f543-8136-4570-920e-fd7605c989d6']" + outputs: + description: Output returned by the Stack. + type: list + elements: dict + sample: "{'description': 'IP address of server1 in private network', + 'output_key': 'server1_private_ip', + 'output_value': '10.1.10.103'}" + parameters: + description: Parameters of the current Stack + type: dict + sample: "{'OS::project_id': '7f6a3a3e01164a4eb4eecb2ab7742101', + 'OS::stack_id': '97a3f543-8136-4570-920e-fd7605c989d6', + 'OS::stack_name': 'test-stack', + 'stack_status': 'CREATE_COMPLETE', + 'stack_status_reason': 'Stack CREATE completed successfully', + 'status': 'COMPLETE', + 'template_description': 'HOT template to create a new instance and networks', + 'timeout_mins': 60, + 'updated_time': null}" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module) +from ansible.module_utils._text import to_native +from distutils.version import StrictVersion + + +def _create_stack(module, stack, cloud, sdk, parameters): + try: + stack = cloud.create_stack(module.params['name'], + template_file=module.params['template'], + environment_files=module.params['environment'], + timeout=module.params['timeout'], + wait=True, + rollback=module.params['rollback'], + **parameters) + + stack = cloud.get_stack(stack.id, None) + if stack.stack_status == 'CREATE_COMPLETE': + return stack + else: + module.fail_json(msg="Failure in creating stack: {0}".format(stack)) + except sdk.exceptions.OpenStackCloudException as e: + if hasattr(e, 'response'): + module.fail_json(msg=to_native(e), response=e.response.json()) + else: + module.fail_json(msg=to_native(e)) + + +def _update_stack(module, stack, cloud, sdk, parameters): + try: + stack = cloud.update_stack( + module.params['name'], + template_file=module.params['template'], + environment_files=module.params['environment'], + timeout=module.params['timeout'], + rollback=module.params['rollback'], + wait=module.params['wait'], + **parameters) + + if stack['stack_status'] == 'UPDATE_COMPLETE': + return stack + else: + module.fail_json(msg="Failure in updating stack: %s" % + stack['stack_status_reason']) + except sdk.exceptions.OpenStackCloudException as e: + if hasattr(e, 'response'): + module.fail_json(msg=to_native(e), response=e.response.json()) + else: + module.fail_json(msg=to_native(e)) + + +def _system_state_change(module, stack, cloud): + state = module.params['state'] + if state == 'present': + if not stack: + return True + if state == 'absent' and stack: + return True + return False + + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + tag=dict(required=False, default=None), + template=dict(default=None), + environment=dict(default=None, type='list', elements='str'), + parameters=dict(default={}, type='dict'), + rollback=dict(default=False, type='bool'), + timeout=dict(default=3600, type='int'), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + state = module.params['state'] + name = module.params['name'] + # Check for required parameters when state == 'present' + if state == 'present': + for p in ['template']: + if not module.params[p]: + module.fail_json(msg='%s required with present state' % p) + + sdk, cloud = openstack_cloud_from_module(module) + try: + stack = cloud.get_stack(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, stack, cloud)) + + if state == 'present': + parameters = module.params['parameters'] + if module.params['tag']: + parameters['tags'] = module.params['tag'] + min_version = '0.28.0' + if StrictVersion(sdk.version.__version__) < StrictVersion(min_version) and stack: + module.warn("To update tags using openstack.cloud.stack module, the" + "installed version of the openstacksdk" + "library MUST be >={min_version}" + "".format(min_version=min_version)) + if not stack: + stack = _create_stack(module, stack, cloud, sdk, parameters) + else: + stack = _update_stack(module, stack, cloud, sdk, parameters) + module.exit_json(changed=True, + stack=stack, + id=stack.id) + elif state == 'absent': + if not stack: + changed = False + else: + changed = True + if not cloud.delete_stack(name, wait=module.params['wait']): + module.fail_json(msg='delete stack failed for stack: %s' % name) + module.exit_json(changed=changed) + except sdk.exceptions.OpenStackCloudException as e: + module.fail_json(msg=to_native(e)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/subnet.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/subnet.py new file mode 100644 index 00000000..84861877 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/subnet.py @@ -0,0 +1,368 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (c) 2013, Benno Joy <benno@ansible.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: subnet +short_description: Add/Remove subnet to an OpenStack network +author: OpenStack Ansible SIG +description: + - Add or Remove a subnet to an OpenStack network +options: + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + type: str + network_name: + description: + - Name of the network to which the subnet should be attached + - Required when I(state) is 'present' + type: str + name: + description: + - The name of the subnet that should be created. Although Neutron + allows for non-unique subnet names, this module enforces subnet + name uniqueness. + required: true + type: str + cidr: + description: + - The CIDR representation of the subnet that should be assigned to + the subnet. Required when I(state) is 'present' and a subnetpool + is not specified. + type: str + ip_version: + description: + - The IP version of the subnet 4 or 6 + default: 4 + type: str + choices: ['4', '6'] + enable_dhcp: + description: + - Whether DHCP should be enabled for this subnet. + type: bool + default: 'yes' + gateway_ip: + description: + - The ip that would be assigned to the gateway for this subnet + type: str + no_gateway_ip: + description: + - The gateway IP would not be assigned for this subnet + type: bool + default: 'no' + dns_nameservers: + description: + - List of DNS nameservers for this subnet. + type: list + elements: str + allocation_pool_start: + description: + - From the subnet pool the starting address from which the IP should + be allocated. + type: str + allocation_pool_end: + description: + - From the subnet pool the last IP that should be assigned to the + virtual machines. + type: str + host_routes: + description: + - A list of host route dictionaries for the subnet. + type: list + elements: dict + suboptions: + destination: + description: The destination network (CIDR). + type: str + required: true + nexthop: + description: The next hop (aka gateway) for the I(destination). + type: str + required: true + ipv6_ra_mode: + description: + - IPv6 router advertisement mode + choices: ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'] + type: str + ipv6_address_mode: + description: + - IPv6 address mode + choices: ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'] + type: str + use_default_subnetpool: + description: + - Use the default subnetpool for I(ip_version) to obtain a CIDR. + type: bool + default: 'no' + project: + description: + - Project name or ID containing the subnet (name admin-only) + type: str + extra_specs: + description: + - Dictionary with extra key/value pairs passed to the API + required: false + default: {} + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Create a new (or update an existing) subnet on the specified network +- openstack.cloud.subnet: + state: present + network_name: network1 + name: net1subnet + cidr: 192.168.0.0/24 + dns_nameservers: + - 8.8.8.7 + - 8.8.8.8 + host_routes: + - destination: 0.0.0.0/0 + nexthop: 12.34.56.78 + - destination: 192.168.0.0/24 + nexthop: 192.168.0.1 + +# Delete a subnet +- openstack.cloud.subnet: + state: absent + name: net1subnet + +# Create an ipv6 stateless subnet +- openstack.cloud.subnet: + state: present + name: intv6 + network_name: internal + ip_version: 6 + cidr: 2db8:1::/64 + dns_nameservers: + - 2001:4860:4860::8888 + - 2001:4860:4860::8844 + ipv6_ra_mode: dhcpv6-stateless + ipv6_address_mode: dhcpv6-stateless +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class SubnetModule(OpenStackModule): + ipv6_mode_choices = ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'] + argument_spec = dict( + name=dict(type='str', required=True), + network_name=dict(type='str'), + cidr=dict(type='str'), + ip_version=dict(type='str', default='4', choices=['4', '6']), + enable_dhcp=dict(type='bool', default=True), + gateway_ip=dict(type='str'), + no_gateway_ip=dict(type='bool', default=False), + dns_nameservers=dict(type='list', default=None, elements='str'), + allocation_pool_start=dict(type='str'), + allocation_pool_end=dict(type='str'), + host_routes=dict(type='list', default=None, elements='dict'), + ipv6_ra_mode=dict(type='str', choices=ipv6_mode_choices), + ipv6_address_mode=dict(type='str', choices=ipv6_mode_choices), + use_default_subnetpool=dict(type='bool', default=False), + extra_specs=dict(type='dict', default=dict()), + state=dict(type='str', default='present', choices=['absent', 'present']), + project=dict(type='str'), + ) + + module_kwargs = dict( + supports_check_mode=True, + required_together=[['allocation_pool_end', 'allocation_pool_start']] + ) + + def _can_update(self, subnet, filters=None): + """Check for differences in non-updatable values""" + network_name = self.params['network_name'] + ip_version = int(self.params['ip_version']) + ipv6_ra_mode = self.params['ipv6_ra_mode'] + ipv6_a_mode = self.params['ipv6_address_mode'] + + if network_name: + network = self.conn.get_network(network_name, filters) + if network: + netid = network['id'] + if netid != subnet['network_id']: + self.fail_json(msg='Cannot update network_name in existing subnet') + else: + self.fail_json(msg='No network found for %s' % network_name) + + if ip_version and subnet['ip_version'] != ip_version: + self.fail_json(msg='Cannot update ip_version in existing subnet') + if ipv6_ra_mode and subnet.get('ipv6_ra_mode', None) != ipv6_ra_mode: + self.fail_json(msg='Cannot update ipv6_ra_mode in existing subnet') + if ipv6_a_mode and subnet.get('ipv6_address_mode', None) != ipv6_a_mode: + self.fail_json(msg='Cannot update ipv6_address_mode in existing subnet') + + def _needs_update(self, subnet, filters=None): + """Check for differences in the updatable values.""" + + # First check if we are trying to update something we're not allowed to + self._can_update(subnet, filters) + + # now check for the things we are allowed to update + enable_dhcp = self.params['enable_dhcp'] + subnet_name = self.params['name'] + pool_start = self.params['allocation_pool_start'] + pool_end = self.params['allocation_pool_end'] + gateway_ip = self.params['gateway_ip'] + no_gateway_ip = self.params['no_gateway_ip'] + dns = self.params['dns_nameservers'] + host_routes = self.params['host_routes'] + curr_pool = dict(start=pool_start, end=pool_end) + + if subnet['enable_dhcp'] != enable_dhcp: + return True + if subnet_name and subnet['name'] != subnet_name: + return True + if not subnet['allocation_pools'] and pool_start and pool_end: + return True + if subnet['allocation_pools'] != [curr_pool]: + return True + if gateway_ip and subnet['gateway_ip'] != gateway_ip: + return True + if dns and sorted(subnet['dns_nameservers']) != sorted(dns): + return True + if host_routes: + curr_hr = sorted(subnet['host_routes'], key=lambda t: t.keys()) + new_hr = sorted(host_routes, key=lambda t: t.keys()) + if curr_hr != new_hr: + return True + if no_gateway_ip and subnet['gateway_ip']: + return True + return False + + def _system_state_change(self, subnet, filters=None): + state = self.params['state'] + if state == 'present': + if not subnet: + return True + return self._needs_update(subnet, filters) + if state == 'absent' and subnet: + return True + return False + + def run(self): + + state = self.params['state'] + network_name = self.params['network_name'] + cidr = self.params['cidr'] + ip_version = self.params['ip_version'] + enable_dhcp = self.params['enable_dhcp'] + subnet_name = self.params['name'] + gateway_ip = self.params['gateway_ip'] + no_gateway_ip = self.params['no_gateway_ip'] + dns = self.params['dns_nameservers'] + pool_start = self.params['allocation_pool_start'] + pool_end = self.params['allocation_pool_end'] + host_routes = self.params['host_routes'] + ipv6_ra_mode = self.params['ipv6_ra_mode'] + ipv6_a_mode = self.params['ipv6_address_mode'] + use_default_subnetpool = self.params['use_default_subnetpool'] + project = self.params.pop('project') + extra_specs = self.params['extra_specs'] + + # Check for required parameters when state == 'present' + if state == 'present': + if not self.params['network_name']: + self.fail(msg='network_name required with present state') + if ( + not self.params['cidr'] + and not use_default_subnetpool + and not extra_specs.get('subnetpool_id', False) + ): + self.fail(msg='cidr or use_default_subnetpool or ' + 'subnetpool_id required with present state') + + if pool_start and pool_end: + pool = [dict(start=pool_start, end=pool_end)] + else: + pool = None + + if no_gateway_ip and gateway_ip: + self.fail_json(msg='no_gateway_ip is not allowed with gateway_ip') + + if project is not None: + proj = self.conn.get_project(project) + if proj is None: + self.fail_json(msg='Project %s could not be found' % project) + project_id = proj['id'] + filters = {'tenant_id': project_id} + else: + project_id = None + filters = None + + subnet = self.conn.get_subnet(subnet_name, filters=filters) + + if self.ansible.check_mode: + self.exit_json(changed=self._system_state_change(subnet, filters)) + + if state == 'present': + if not subnet: + kwargs = dict( + cidr=cidr, + ip_version=ip_version, + enable_dhcp=enable_dhcp, + subnet_name=subnet_name, + gateway_ip=gateway_ip, + disable_gateway_ip=no_gateway_ip, + dns_nameservers=dns, + allocation_pools=pool, + host_routes=host_routes, + ipv6_ra_mode=ipv6_ra_mode, + ipv6_address_mode=ipv6_a_mode, + tenant_id=project_id) + dup_args = set(kwargs.keys()) & set(extra_specs.keys()) + if dup_args: + raise ValueError('Duplicate key(s) {0} in extra_specs' + .format(list(dup_args))) + if use_default_subnetpool: + kwargs['use_default_subnetpool'] = use_default_subnetpool + kwargs = dict(kwargs, **extra_specs) + subnet = self.conn.create_subnet(network_name, **kwargs) + changed = True + else: + if self._needs_update(subnet, filters): + subnet = self.conn.update_subnet(subnet['id'], + subnet_name=subnet_name, + enable_dhcp=enable_dhcp, + gateway_ip=gateway_ip, + disable_gateway_ip=no_gateway_ip, + dns_nameservers=dns, + allocation_pools=pool, + host_routes=host_routes) + changed = True + else: + changed = False + self.exit_json(changed=changed, + subnet=subnet, + id=subnet['id']) + + elif state == 'absent': + if not subnet: + changed = False + else: + changed = True + self.conn.delete_subnet(subnet_name) + self.exit_json(changed=changed) + + +def main(): + module = SubnetModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/subnets_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/subnets_info.py new file mode 100644 index 00000000..ac18b90c --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/subnets_info.py @@ -0,0 +1,161 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: subnets_info +short_description: Retrieve information about one or more OpenStack subnets. +author: OpenStack Ansible SIG +description: + - Retrieve information about one or more subnets from OpenStack. + - This module was called C(openstack.cloud.subnets_facts) before Ansible 2.9, returning C(ansible_facts). + Note that the M(openstack.cloud.subnets_info) module no longer returns C(ansible_facts)! +options: + name: + description: + - Name or ID of the subnet. + - Alias 'subnet' added in version 2.8. + required: false + aliases: ['subnet'] + type: str + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + required: false + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +- name: Gather information about previously created subnets + openstack.cloud.subnets_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + register: result + +- name: Show openstack subnets + debug: + msg: "{{ result.openstack_subnets }}" + +- name: Gather information about a previously created subnet by name + openstack.cloud.subnets_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + name: subnet1 + register: result + +- name: Show openstack subnets + debug: + msg: "{{ result.openstack_subnets }}" + +- name: Gather information about a previously created subnet with filter + # Note: name and filters parameters are not mutually exclusive + openstack.cloud.subnets_info: + auth: + auth_url: https://identity.example.com + username: user + password: password + project_name: someproject + filters: + tenant_id: 55e2ce24b2a245b09f181bf025724cbe + register: result + +- name: Show openstack subnets + debug: + msg: "{{ result.openstack_subnets }}" +''' + +RETURN = ''' +openstack_subnets: + description: has all the openstack information about the subnets + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: str + name: + description: Name given to the subnet. + returned: success + type: str + network_id: + description: Network ID this subnet belongs in. + returned: success + type: str + cidr: + description: Subnet's CIDR. + returned: success + type: str + gateway_ip: + description: Subnet's gateway ip. + returned: success + type: str + enable_dhcp: + description: DHCP enable flag for this subnet. + returned: success + type: bool + ip_version: + description: IP version for this subnet. + returned: success + type: int + tenant_id: + description: Tenant id associated with this subnet. + returned: success + type: str + dns_nameservers: + description: DNS name servers for this subnet. + returned: success + type: list + elements: str + allocation_pools: + description: Allocation pools associated with this subnet. + returned: success + type: list + elements: dict +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class SubnetInfoModule(OpenStackModule): + + deprecated_names = ('subnets_facts', 'openstack.cloud.subnets_facts') + + argument_spec = dict( + name=dict(required=False, default=None, aliases=['subnet']), + filters=dict(required=False, type='dict', default=None) + ) + + def run(self): + kwargs = self.check_versioned( + filters=self.params['filters'] + ) + if self.params['name']: + kwargs['name_or_id'] = self.params['name'] + subnets = self.conn.search_subnets(**kwargs) + + self.exit(changed=False, openstack_subnets=subnets) + + +def main(): + module = SubnetInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume.py new file mode 100644 index 00000000..8e953c74 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume.py @@ -0,0 +1,261 @@ +#!/usr/bin/python + +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: volume +short_description: Create/Delete Cinder Volumes +author: OpenStack Ansible SIG +description: + - Create or Remove cinder block storage volumes +options: + size: + description: + - Size of volume in GB. This parameter is required when the + I(state) parameter is 'present'. + type: int + display_name: + description: + - Name of volume + required: true + type: str + aliases: [name] + display_description: + description: + - String describing the volume + type: str + aliases: [description] + volume_type: + description: + - Volume type for volume + type: str + image: + description: + - Image name or id for boot from volume + type: str + snapshot_id: + description: + - Volume snapshot id to create from + type: str + volume: + description: + - Volume name or id to create from + type: str + bootable: + description: + - Bootable flag for volume. + type: bool + default: False + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + scheduler_hints: + description: + - Scheduler hints passed to volume API in form of dict + type: dict + metadata: + description: + - Metadata for the volume + type: dict +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Creates a new volume +- name: create a volume + hosts: localhost + tasks: + - name: create 40g test volume + openstack.cloud.volume: + state: present + cloud: mordred + availability_zone: az2 + size: 40 + display_name: test_volume + scheduler_hints: + same_host: 243e8d3c-8f47-4a61-93d6-7215c344b0c0 +''' + +RETURNS = ''' +id: + description: Cinder's unique ID for this volume + returned: always + type: str + sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 + +volume: + description: Cinder's representation of the volume object + returned: always + type: dict + sample: {'...'} +''' +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class VolumeModule(OpenStackModule): + + argument_spec = dict( + size=dict(type='int'), + volume_type=dict(type='str'), + display_name=dict(required=True, aliases=['name'], type='str'), + display_description=dict(aliases=['description'], type='str'), + image=dict(type='str'), + snapshot_id=dict(type='str'), + volume=dict(type='str'), + state=dict(default='present', choices=['absent', 'present'], type='str'), + scheduler_hints=dict(type='dict'), + metadata=dict(type='dict'), + bootable=dict(type='bool', default=False) + ) + + module_kwargs = dict( + mutually_exclusive=[ + ['image', 'snapshot_id', 'volume'], + ], + required_if=[ + ['state', 'present', ['size']], + ], + ) + + def _needs_update(self, volume): + ''' + check for differences in updatable values, at the moment + openstacksdk only supports extending the volume size, this + may change in the future. + :returns: bool + ''' + compare_simple = ['size'] + + for k in compare_simple: + if self.params[k] is not None and self.params[k] != volume.get(k): + return True + + return False + + def _modify_volume(self, volume): + ''' + modify volume, the only modification to an existing volume + available at the moment is extending the size, this is + limited by the openstacksdk and may change whenever the + functionality is extended. + ''' + volume = self.conn.get_volume(self.params['display_name']) + diff = {'before': volume, 'after': ''} + size = self.params['size'] + + if size < volume.get('size'): + self.fail_json( + msg='Cannot shrink volumes, size: {0} < {1}'.format(size, volume.get('size')) + ) + + if not self._needs_update(volume): + diff['after'] = volume + self.exit_json(changed=False, id=volume['id'], volume=volume, diff=diff) + + if self.ansible.check_mode: + diff['after'] = volume + self.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff) + + self.conn.volume.extend_volume( + volume.id, + size + ) + diff['after'] = self.conn.get_volume(self.params['display_name']) + self.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff) + + def _present_volume(self): + + diff = {'before': '', 'after': ''} + + volume_args = dict( + size=self.params['size'], + volume_type=self.params['volume_type'], + display_name=self.params['display_name'], + display_description=self.params['display_description'], + snapshot_id=self.params['snapshot_id'], + bootable=self.params['bootable'], + availability_zone=self.params['availability_zone'], + ) + if self.params['image']: + image_id = self.conn.get_image_id(self.params['image']) + volume_args['imageRef'] = image_id + + if self.params['volume']: + volume_id = self.conn.get_volume_id(self.params['volume']) + if not volume_id: + self.fail_json(msg="Failed to find volume '%s'" % self.params['volume']) + volume_args['source_volid'] = volume_id + + if self.params['scheduler_hints']: + volume_args['scheduler_hints'] = self.params['scheduler_hints'] + + if self.params['metadata']: + volume_args['metadata'] = self.params['metadata'] + + if self.ansible.check_mode: + diff['after'] = volume_args + self.exit_json(changed=True, id=None, volume=volume_args, diff=diff) + + volume = self.conn.create_volume( + wait=self.params['wait'], timeout=self.params['timeout'], + **volume_args) + diff['after'] = volume + self.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff) + + def _absent_volume(self, volume): + changed = False + diff = {'before': '', 'after': ''} + + if self.conn.volume_exists(self.params['display_name']): + volume = self.conn.get_volume(self.params['display_name']) + diff['before'] = volume + + if self.ansible.check_mode: + self.exit_json(changed=True, diff=diff) + + try: + changed = self.conn.delete_volume(name_or_id=self.params['display_name'], + wait=self.params['wait'], + timeout=self.params['timeout']) + except self.sdk.exceptions.ResourceTimeout: + diff['after'] = volume + self.exit_json(changed=changed, diff=diff) + + self.exit_json(changed=changed, diff=diff) + + def run(self): + + state = self.params['state'] + if self.conn.volume_exists(self.params['display_name']): + volume = self.conn.get_volume(self.params['display_name']) + else: + volume = None + + if state == 'present': + if not volume: + self._present_volume() + elif self._needs_update(volume): + self._modify_volume(volume) + else: + self.exit_json(changed=False, id=volume['id'], volume=volume) + if state == 'absent': + self._absent_volume(volume) + + +def main(): + module = VolumeModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_backup.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_backup.py new file mode 100644 index 00000000..43cacc72 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_backup.py @@ -0,0 +1,221 @@ +#!/usr/bin/python +# coding: utf-8 -*- +# +# Copyright (c) 2020 by Open Telekom Cloud, operated by T-Systems International GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +DOCUMENTATION = ''' +--- +module: volume_backup +short_description: Add/Delete Volume backup +extends_documentation_fragment: openstack.cloud.openstack +author: OpenStack Ansible SIG +description: + - Add or Remove Volume Backup in OTC. +options: + display_name: + description: + - Name that has to be given to the backup + required: true + type: str + aliases: ['name'] + display_description: + description: + - String describing the backup + required: false + type: str + aliases: ['description'] + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + volume: + description: + - Name or ID of the volume. Required when state is True. + type: str + required: False + snapshot: + description: Name or ID of the Snapshot to take backup of + type: str + force: + description: + - Indicates whether to backup, even if the volume is attached. + type: bool + default: False + metadata: + description: Metadata for the backup + type: dict + incremental: + description: The backup mode + type: bool + default: False +requirements: ["openstacksdk"] +''' + +RETURN = ''' +id: + description: The Volume backup ID. + returned: On success when C(state=present) + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +backup: + description: Dictionary describing the Cluster. + returned: On success when C(state=present) + type: complex + contains: + id: + description: Unique UUID. + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" + name: + description: Name given to the load balancer. + type: str + sample: "elb_test" +''' + +EXAMPLES = ''' +- name: Create backup + openstack.cloud.volume_backup: + display_name: test_volume_backup + volume: "test_volume" + +- name: Create backup from snapshot + openstack.cloud.volume_backup: + display_name: test_volume_backup + volume: "test_volume" + snapshot: "test_snapshot" + +- name: Delete volume backup + openstack.cloud.volume_backup: + display_name: test_volume_backup + state: absent +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class VolumeBackupModule(OpenStackModule): + module_min_sdk_version = '0.49.0' + + argument_spec = dict( + display_name=dict(required=True, aliases=['name'], type='str'), + display_description=dict(required=False, aliases=['description'], + type='str'), + volume=dict(required=False, type='str'), + snapshot=dict(required=False, type='str'), + state=dict(default='present', type='str', choices=['absent', 'present']), + force=dict(default=False, type='bool'), + metadata=dict(required=False, type='dict'), + incremental=dict(required=False, default=False, type='bool') + ) + module_kwargs = dict( + required_if=[ + ('state', 'present', ['volume']) + ], + supports_check_mode=True + ) + + def _create_backup(self): + if self.ansible.check_mode: + self.exit_json(changed=True) + + name = self.params['display_name'] + description = self.params['display_description'] + volume = self.params['volume'] + snapshot = self.params['snapshot'] + force = self.params['force'] + is_incremental = self.params['incremental'] + metadata = self.params['metadata'] + + changed = False + + cloud_volume = self.conn.block_storage.find_volume(volume) + cloud_snapshot_id = None + + attrs = { + 'name': name, + 'volume_id': cloud_volume.id, + 'force': force, + 'is_incremental': is_incremental + } + + if snapshot: + cloud_snapshot_id = self.conn.block_storage.find_snapshot( + snapshot, ignore_missing=False).id + attrs['snapshot_id'] = cloud_snapshot_id + + if metadata: + attrs['metadata'] = metadata + + if description: + attrs['description'] = description + + backup = self.conn.block_storage.create_backup(**attrs) + changed = True + + if self.params['wait']: + try: + backup = self.conn.block_storage.wait_for_status( + backup, + status='available', + wait=self.params['timeout']) + self.exit_json( + changed=True, volume_backup=backup.to_dict(), id=backup.id + ) + except self.sdk.exceptions.ResourceTimeout: + self.fail_json( + msg='Timeout failure waiting for backup ' + 'to complete' + ) + + self.exit_json( + changed=changed, volume_backup=backup.to_dict(), id=backup.id + ) + + def _delete_backup(self, backup): + if self.ansible.check_mode: + self.exit_json(changed=True) + + if backup: + self.conn.block_storage.delete_backup(backup) + if self.params['wait']: + try: + self.conn.block_storage.wait_for_delete( + backup, + interval=2, + wait=self.params['timeout']) + except self.sdk.exceptions.ResourceTimeout: + self.fail_json( + msg='Timeout failure waiting for backup ' + 'to be deleted' + ) + + self.exit_json(changed=True) + + def run(self): + name = self.params['display_name'] + + backup = self.conn.block_storage.find_backup(name) + + if self.params['state'] == 'present': + if not backup: + self._create_backup() + else: + # For the moment we do not support backup update, since SDK + # doesn't support it either => do nothing + self.exit_json(changed=False) + + elif self.params['state'] == 'absent': + self._delete_backup(backup) + + +def main(): + module = VolumeBackupModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_backup_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_backup_info.py new file mode 100644 index 00000000..8355c08c --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_backup_info.py @@ -0,0 +1,123 @@ +#!/usr/bin/python +# coding: utf-8 -*- +# +# Copyright (c) 2020 by Open Telekom Cloud, operated by T-Systems International GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +DOCUMENTATION = ''' +--- +module: volume_backup_info +short_description: Get Backups +author: OpenStack Ansible SIG +description: + - Get Backup info from the Openstack cloud. +options: + name: + description: + - Name of the Backup. + type: str + volume: + description: + - Name of the volume. + type: str +requirements: ["openstacksdk"] +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +volume_backups: + description: List of dictionaries describing volume backups. + type: list + elements: dict + returned: always. + contains: + availability_zone: + description: Backup availability zone. + type: str + created_at: + description: Backup creation time. + type: str + description: + description: Backup desciption. + type: str + id: + description: Unique UUID. + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" + is_incremental: + description: Backup incremental property. + type: bool + metadata: + description: Backup metadata. + type: dict + name: + description: Backup Name. + type: str + snapshot_id: + description: Snapshot ID. + type: str + status: + description: Backup status. + type: str + updated_at: + description: Backup update time. + type: str + volume_id: + description: Volume ID. + type: str + +''' + +EXAMPLES = ''' +# Get backups. +- openstack.cloud.volume_backup_info: + register: backup + +- openstack.cloud.volume_backup_info: + name: my_fake_backup + register: backup +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class VolumeBackupInfoModule(OpenStackModule): + module_min_sdk_version = '0.49.0' + + argument_spec = dict( + name=dict(required=False, type='str'), + volume=dict(required=False, type='str') + ) + + def run(self): + name_filter = self.params['name'] + volume = self.params['volume'] + + data = [] + attrs = {} + + if name_filter: + attrs['name'] = name_filter + if volume: + attrs['volume_id'] = self.conn.block_storage.find_volume(volume) + + for raw in self.conn.block_storage.backups(**attrs): + dt = raw.to_dict() + dt.pop('location') + data.append(dt) + + self.exit_json( + changed=False, + volume_backups=data + ) + + +def main(): + module = VolumeBackupInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_info.py new file mode 100644 index 00000000..45abea04 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_info.py @@ -0,0 +1,146 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright (c) 2020, Sagi Shnaidman <sshnaidm@redhat.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: volume_info +short_description: Retrive information about volumes +author: Sagi Shnaidman (@sshnaidm) +description: + - Get information about block storage in openstack +options: + details: + description: + - Whether to provide additional information about volumes + type: bool + all_projects: + description: + - Whether return the volumes in all projects + type: bool + name: + description: + - Name of the volume as a string. + type: str + required: false + status: + description: + - Value of the status of the volume so that you can filter on "available" for example + type: str + required: false + +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: + - openstack.cloud.openstack +''' + +RETURN = ''' +volumes: + description: Volumes in project + returned: always + type: list + elements: dict + sample: + - attachments: [] + availability_zone: nova + consistency_group_id: null + created_at: '2017-11-15T10:51:19.000000' + description: '' + extended_replication_status: null + host: null + id: 103ac6ed-527f-4781-8484-7ff4467e34f5 + image_id: null + is_bootable: true + is_encrypted: false + links: + - href: https://... + rel: self + - href: https://... + rel: bookmark + location: + cloud: cloud + project: + domain_id: null + domain_name: Default + id: cfe04702154742fc964d9403c691c76e + name: username + region_name: regionOne + zone: nova + metadata: + readonly: 'False' + migration_id: null + migration_status: null + name: '' + project_id: cab34702154a42fc96ed9403c691c76e + replication_driver_data: null + replication_status: disabled + size: 9 + snapshot_id: null + source_volume_id: null + status: available + volume_image_metadata: + checksum: a14e113deeee3a3392462f167ed28cb5 + container_format: bare + disk_format: raw + family: centos-7 + image_id: afcf3320-1bf8-4a9a-a24d-5abd639a6e33 + image_name: CentOS-7-x86_64-GenericCloud-1708 + latest: centos-7-latest + min_disk: '0' + min_ram: '0' + official: 'True' + official-image: 'True' + size: '8589934592' + volume_type: null +''' + +EXAMPLES = ''' +- openstack.cloud.volume_info: + +- openstack.cloud.volume_info: + name: myvolume + +- openstack.cloud.volume_info: + all_projects: true + +- openstack.cloud.volume_info: + all_projects: true + details: true +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class VolumeInfoModule(OpenStackModule): + + argument_spec = dict( + details=dict(type='bool', required=False), + all_projects=dict(type='bool', required=False, min_ver='0.19'), + name=dict(type='str', required=False), + status=dict(type='str', required=False), + ) + + def run(self): + kwargs = self.check_versioned( + details=self.params['details'], + name=self.params['name'], + all_projects=self.params['all_projects'], + status=self.params['status'], + ) + result = self.conn.block_storage.volumes(**kwargs) + result = list(result) + self.results.update({'volumes': result}) + + +def main(): + module = VolumeInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_snapshot.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_snapshot.py new file mode 100644 index 00000000..7c27fcc2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_snapshot.py @@ -0,0 +1,174 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright (c) 2016, Mario Santos <mario.rf.santos@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = ''' +--- +module: volume_snapshot +short_description: Create/Delete Cinder Volume Snapshots +author: OpenStack Ansible SIG +description: + - Create or Delete cinder block storage volume snapshots +options: + display_name: + description: + - Name of the snapshot + required: true + aliases: ['name'] + type: str + display_description: + description: + - String describing the snapshot + aliases: ['description'] + type: str + volume: + description: + - The volume name or id to create/delete the snapshot + required: True + type: str + force: + description: + - Allows or disallows snapshot of a volume to be created when the volume + is attached to an instance. + type: bool + default: 'no' + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str +requirements: + - "python >= 3.6" + - "openstacksdk" + +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +EXAMPLES = ''' +# Creates a snapshot on volume 'test_volume' +- name: create and delete snapshot + hosts: localhost + tasks: + - name: create snapshot + openstack.cloud.volume_snapshot: + state: present + cloud: mordred + availability_zone: az2 + display_name: test_snapshot + volume: test_volume + - name: delete snapshot + openstack.cloud.volume_snapshot: + state: absent + cloud: mordred + availability_zone: az2 + display_name: test_snapshot + volume: test_volume +''' + +RETURN = ''' +snapshot: + description: The snapshot instance after the change + returned: success + type: dict + sample: + id: 837aca54-c0ee-47a2-bf9a-35e1b4fdac0c + name: test_snapshot + volume_id: ec646a7c-6a35-4857-b38b-808105a24be6 + size: 2 + status: available + display_name: test_snapshot +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + openstack_full_argument_spec, + openstack_module_kwargs, + openstack_cloud_from_module, +) + + +def _present_volume_snapshot(module, cloud): + volume = cloud.get_volume(module.params['volume']) + snapshot = cloud.get_volume_snapshot(module.params['display_name'], + filters={'volume_id': volume.id}) + if not snapshot: + snapshot = cloud.create_volume_snapshot(volume.id, + force=module.params['force'], + wait=module.params['wait'], + timeout=module.params[ + 'timeout'], + name=module.params['display_name'], + description=module.params.get( + 'display_description') + ) + module.exit_json(changed=True, snapshot=snapshot) + else: + module.exit_json(changed=False, snapshot=snapshot) + + +def _absent_volume_snapshot(module, cloud): + volume = cloud.get_volume(module.params['volume']) + snapshot = cloud.get_volume_snapshot(module.params['display_name'], + filters={'volume_id': volume.id}) + if not snapshot: + module.exit_json(changed=False) + else: + cloud.delete_volume_snapshot(name_or_id=snapshot.id, + wait=module.params['wait'], + timeout=module.params['timeout'], + ) + module.exit_json(changed=True, snapshot_id=snapshot.id) + + +def _system_state_change(module, cloud): + volume = cloud.get_volume(module.params['volume']) + snapshot = cloud.get_volume_snapshot(module.params['display_name'], + filters={'volume_id': volume.id}) + state = module.params['state'] + + if state == 'present': + return snapshot is None + if state == 'absent': + return snapshot is not None + + +def main(): + argument_spec = openstack_full_argument_spec( + display_name=dict(required=True, aliases=['name']), + display_description=dict(default=None, aliases=['description']), + volume=dict(required=True), + force=dict(required=False, default=False, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + sdk, cloud = openstack_cloud_from_module(module) + + state = module.params['state'] + + try: + if cloud.volume_exists(module.params['volume']): + if module.check_mode: + module.exit_json(changed=_system_state_change(module, cloud)) + if state == 'present': + _present_volume_snapshot(module, cloud) + if state == 'absent': + _absent_volume_snapshot(module, cloud) + else: + module.fail_json( + msg="No volume with name or id '{0}' was found.".format( + module.params['volume'])) + except (sdk.exceptions.OpenStackCloudException, sdk.exceptions.ResourceTimeout) as e: + module.fail_json(msg=e.message) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_snapshot_info.py b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_snapshot_info.py new file mode 100644 index 00000000..c3b00f9e --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/volume_snapshot_info.py @@ -0,0 +1,133 @@ +#!/usr/bin/python +# coding: utf-8 -*- +# +# Copyright (c) 2020 by Open Telekom Cloud, operated by T-Systems International GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +DOCUMENTATION = ''' +--- +module: volume_snapshot_info +short_description: Get volume snapshots +author: OpenStack Ansible SIG +description: + - Get Volume Snapshot info from the Openstack cloud. +options: + details: + description: More detailed output + type: bool + default: True + name: + description: + - Name of the Snapshot. + type: str + volume: + description: + - Name of the volume. + type: str + status: + description: + - Specifies the snapshot status. + choices: [creating, available, error, deleting, + error_deleting, rollbacking, backing-up] + type: str +requirements: ["openstacksdk"] +extends_documentation_fragment: +- openstack.cloud.openstack +''' + +RETURN = ''' +volume_snapshots: + description: List of dictionaries describing volume snapshots. + type: list + elements: dict + returned: always. + contains: + created_at: + description: Snapshot creation time. + type: str + description: + description: Snapshot desciption. + type: str + id: + description: Unique UUID. + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" + metadata: + description: Snapshot metadata. + type: dict + name: + description: Snapshot Name. + type: str + status: + description: Snapshot status. + type: str + updated_at: + description: Snapshot update time. + type: str + volume_id: + description: Volume ID. + type: str + +''' + +EXAMPLES = ''' +# Get snapshots. +- openstack.cloud.volume_snapshot_info: + register: snapshots + +- openstack.cloud.volume_snapshotbackup_info: + name: my_fake_snapshot + register: snapshot +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class VolumeSnapshotInfoModule(OpenStackModule): + module_min_sdk_version = '0.49.0' + + argument_spec = dict( + details=dict(default=True, type='bool'), + name=dict(required=False, type='str'), + volume=dict(required=False, type='str'), + status=dict(required=False, type='str', + choices=['creating', 'available', 'error', + 'deleting', 'error_deleting', 'rollbacking', + 'backing-up']), + ) + + def run(self): + + details_filter = self.params['details'] + name_filter = self.params['name'] + volume_filter = self.params['volume'] + status_filter = self.params['status'] + + data = [] + query = {} + if name_filter: + query['name'] = name_filter + if volume_filter: + query['volume_id'] = self.conn.block_storage.find_volume(volume_filter) + if status_filter: + query['status'] = status_filter.lower() + + for raw in self.conn.block_storage.snapshots(details_filter, **query): + dt = raw.to_dict() + dt.pop('location') + data.append(dt) + + self.exit_json( + changed=False, + volume_snapshots=data + ) + + +def main(): + module = VolumeSnapshotInfoModule() + module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/scripts/inventory/openstack.yml b/collections-debian-merged/ansible_collections/openstack/cloud/scripts/inventory/openstack.yml new file mode 100644 index 00000000..8053fb8f --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/scripts/inventory/openstack.yml @@ -0,0 +1,24 @@ +clouds: + vexxhost: + profile: vexxhost + auth: + project_name: 39e296b2-fc96-42bf-8091-cb742fa13da9 + username: fb886a9b-c37b-442a-9be3-964bed961e04 + password: fantastic-password1 + rax: + profile: rackspace + auth: + username: example + password: spectacular-password + project_id: 2352426 + region_name: DFW,ORD,IAD + devstack: + auth: + auth_url: https://devstack.example.com + username: stack + password: stack + project_name: stack +ansible: + use_hostnames: True + expand_hostvars: False + fail_on_errors: True diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/scripts/inventory/openstack_inventory.py b/collections-debian-merged/ansible_collections/openstack/cloud/scripts/inventory/openstack_inventory.py new file mode 100644 index 00000000..ab2d96cb --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/scripts/inventory/openstack_inventory.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python + +# Copyright (c) 2012, Marco Vito Moscaritolo <marco@agavee.com> +# Copyright (c) 2013, Jesse Keating <jesse.keating@rackspace.com> +# Copyright (c) 2015, Hewlett-Packard Development Company, L.P. +# Copyright (c) 2016, Rackspace Australia +# +# This module 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 software 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 software. If not, see <http://www.gnu.org/licenses/>. + +# The OpenStack Inventory module uses os-client-config for configuration. +# https://github.com/openstack/os-client-config +# This means it will either: +# - Respect normal OS_* environment variables like other OpenStack tools +# - Read values from a clouds.yaml file. +# If you want to configure via clouds.yaml, you can put the file in: +# - Current directory +# - ~/.config/openstack/clouds.yaml +# - /etc/openstack/clouds.yaml +# - /etc/ansible/openstack.yml +# The clouds.yaml file can contain entries for multiple clouds and multiple +# regions of those clouds. If it does, this inventory module will by default +# connect to all of them and present them as one contiguous inventory. You +# can limit to one cloud by passing the `--cloud` parameter, or use the +# OS_CLOUD environment variable. If caching is enabled, and a cloud is +# selected, then per-cloud cache folders will be used. +# +# See the adjacent openstack.yml file for an example config file +# There are two ansible inventory specific options that can be set in +# the inventory section. +# expand_hostvars controls whether or not the inventory will make extra API +# calls to fill out additional information about each server +# use_hostnames changes the behavior from registering every host with its UUID +# and making a group of its hostname to only doing this if the +# hostname in question has more than one server +# fail_on_errors causes the inventory to fail and return no hosts if one cloud +# has failed (for example, bad credentials or being offline). +# When set to False, the inventory will return hosts from +# whichever other clouds it can contact. (Default: True) +# +# Also it is possible to pass the correct user by setting an ansible_user: $myuser +# metadata attribute. + +import argparse +import collections +import os +import sys +import time +from distutils.version import StrictVersion +from io import StringIO + +import json + +import openstack as sdk +from openstack.cloud import inventory as sdk_inventory +from openstack.config import loader as cloud_config + +CONFIG_FILES = ['/etc/ansible/openstack.yaml', '/etc/ansible/openstack.yml'] + + +def get_groups_from_server(server_vars, namegroup=True): + groups = [] + + region = server_vars['region'] + cloud = server_vars['cloud'] + metadata = server_vars.get('metadata', {}) + + # Create a group for the cloud + groups.append(cloud) + + # Create a group on region + if region: + groups.append(region) + + # And one by cloud_region + groups.append("%s_%s" % (cloud, region)) + + # Check if group metadata key in servers' metadata + if 'group' in metadata: + groups.append(metadata['group']) + + for extra_group in metadata.get('groups', '').split(','): + if extra_group: + groups.append(extra_group.strip()) + + groups.append('instance-%s' % server_vars['id']) + if namegroup: + groups.append(server_vars['name']) + + for key in ('flavor', 'image'): + if 'name' in server_vars[key]: + groups.append('%s-%s' % (key, server_vars[key]['name'])) + + for key, value in iter(metadata.items()): + groups.append('meta-%s_%s' % (key, value)) + + az = server_vars.get('az', None) + if az: + # Make groups for az, region_az and cloud_region_az + groups.append(az) + groups.append('%s_%s' % (region, az)) + groups.append('%s_%s_%s' % (cloud, region, az)) + return groups + + +def get_host_groups(inventory, refresh=False, cloud=None): + (cache_file, cache_expiration_time) = get_cache_settings(cloud) + if is_cache_stale(cache_file, cache_expiration_time, refresh=refresh): + groups = to_json(get_host_groups_from_cloud(inventory)) + with open(cache_file, 'w') as f: + f.write(groups) + else: + with open(cache_file, 'r') as f: + groups = f.read() + return groups + + +def append_hostvars(hostvars, groups, key, server, namegroup=False): + hostvars[key] = dict( + ansible_ssh_host=server['interface_ip'], + ansible_host=server['interface_ip'], + openstack=server) + + metadata = server.get('metadata', {}) + if 'ansible_user' in metadata: + hostvars[key]['ansible_user'] = metadata['ansible_user'] + + for group in get_groups_from_server(server, namegroup=namegroup): + groups[group].append(key) + + +def get_host_groups_from_cloud(inventory): + groups = collections.defaultdict(list) + firstpass = collections.defaultdict(list) + hostvars = {} + list_args = {} + if hasattr(inventory, 'extra_config'): + use_hostnames = inventory.extra_config['use_hostnames'] + list_args['expand'] = inventory.extra_config['expand_hostvars'] + if StrictVersion(sdk.version.__version__) >= StrictVersion("0.13.0"): + list_args['fail_on_cloud_config'] = \ + inventory.extra_config['fail_on_errors'] + else: + use_hostnames = False + + for server in inventory.list_hosts(**list_args): + + if 'interface_ip' not in server: + continue + firstpass[server['name']].append(server) + for name, servers in firstpass.items(): + if len(servers) == 1 and use_hostnames: + append_hostvars(hostvars, groups, name, servers[0]) + else: + server_ids = set() + # Trap for duplicate results + for server in servers: + server_ids.add(server['id']) + if len(server_ids) == 1 and use_hostnames: + append_hostvars(hostvars, groups, name, servers[0]) + else: + for server in servers: + append_hostvars( + hostvars, groups, server['id'], server, + namegroup=True) + groups['_meta'] = {'hostvars': hostvars} + return groups + + +def is_cache_stale(cache_file, cache_expiration_time, refresh=False): + ''' Determines if cache file has expired, or if it is still valid ''' + if refresh: + return True + if os.path.isfile(cache_file) and os.path.getsize(cache_file) > 0: + mod_time = os.path.getmtime(cache_file) + current_time = time.time() + if (mod_time + cache_expiration_time) > current_time: + return False + return True + + +def get_cache_settings(cloud=None): + config_files = cloud_config.CONFIG_FILES + CONFIG_FILES + if cloud: + config = cloud_config.OpenStackConfig( + config_files=config_files).get_one(cloud=cloud) + else: + config = cloud_config.OpenStackConfig( + config_files=config_files).get_all()[0] + # For inventory-wide caching + cache_expiration_time = config.get_cache_expiration_time() + cache_path = config.get_cache_path() + if cloud: + cache_path = '{0}_{1}'.format(cache_path, cloud) + if not os.path.exists(cache_path): + os.makedirs(cache_path) + cache_file = os.path.join(cache_path, 'ansible-inventory.cache') + return (cache_file, cache_expiration_time) + + +def to_json(in_dict): + return json.dumps(in_dict, sort_keys=True, indent=2) + + +def parse_args(): + parser = argparse.ArgumentParser(description='OpenStack Inventory Module') + parser.add_argument('--cloud', default=os.environ.get('OS_CLOUD'), + help='Cloud name (default: None') + parser.add_argument('--private', + action='store_true', + help='Use private address for ansible host') + parser.add_argument('--refresh', action='store_true', + help='Refresh cached information') + parser.add_argument('--debug', action='store_true', default=False, + help='Enable debug output') + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('--list', action='store_true', + help='List active servers') + group.add_argument('--host', help='List details about the specific host') + + return parser.parse_args() + + +def main(): + args = parse_args() + try: + # openstacksdk library may write to stdout, so redirect this + sys.stdout = StringIO() + config_files = cloud_config.CONFIG_FILES + CONFIG_FILES + sdk.enable_logging(debug=args.debug) + inventory_args = dict( + refresh=args.refresh, + config_files=config_files, + private=args.private, + cloud=args.cloud, + ) + if hasattr(sdk_inventory.OpenStackInventory, 'extra_config'): + inventory_args.update(dict( + config_key='ansible', + config_defaults={ + 'use_hostnames': False, + 'expand_hostvars': True, + 'fail_on_errors': True, + } + )) + + inventory = sdk_inventory.OpenStackInventory(**inventory_args) + + sys.stdout = sys.__stdout__ + if args.list: + output = get_host_groups(inventory, refresh=args.refresh, cloud=args.cloud) + elif args.host: + output = to_json(inventory.get_host(args.host)) + print(output) + except sdk.exceptions.OpenStackCloudException as e: + sys.stderr.write('%s\n' % e.message) + sys.exit(1) + sys.exit(0) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/setup.py b/collections-debian-merged/ansible_collections/openstack/cloud/setup.py new file mode 100644 index 00000000..d6d2ea1b --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/setup.py @@ -0,0 +1,8 @@ +# Copyright Red Hat, Inc. All Rights Reserved. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +import setuptools + +setuptools.setup( + setup_requires=['pbr'], + pbr=True) diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/test-requirements-2.10.txt b/collections-debian-merged/ansible_collections/openstack/cloud/test-requirements-2.10.txt new file mode 100644 index 00000000..cf2f8264 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/test-requirements-2.10.txt @@ -0,0 +1,11 @@ +openstacksdk +ansible-base +pycodestyle +flake8 +pylint +voluptuous +yamllint +rstcheck +ruamel.yaml +#galaxy-importer # see https://review.opendev.org/#/c/743054 +tox diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/test-requirements-2.11.txt b/collections-debian-merged/ansible_collections/openstack/cloud/test-requirements-2.11.txt new file mode 100644 index 00000000..ff1a34e7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/test-requirements-2.11.txt @@ -0,0 +1,11 @@ +openstacksdk +ansible-core +pycodestyle +flake8 +pylint +voluptuous +yamllint +rstcheck +ruamel.yaml +#galaxy-importer # see https://review.opendev.org/#/c/743054 +tox diff --git a/collections-debian-merged/ansible_collections/openstack/cloud/test-requirements-2.9.txt b/collections-debian-merged/ansible_collections/openstack/cloud/test-requirements-2.9.txt new file mode 100644 index 00000000..08007a64 --- /dev/null +++ b/collections-debian-merged/ansible_collections/openstack/cloud/test-requirements-2.9.txt @@ -0,0 +1,11 @@ +openstacksdk +ansible<2.10 +pycodestyle +flake8 +pylint +voluptuous +yamllint +rstcheck +ruamel.yaml +#galaxy-importer # see https://review.opendev.org/#/c/743054 +tox |