Migrate openstack modules as a collection

Migrate accordin to Ansible guidelines [1[]] and tool
migrate.py [2]
Also fixed ALL ansible-test sanity issues

Add pep8 and linter job with runs ansible-test sanity test.

[1] https://etherpad.openstack.org/p/openstack-ansible-modules
[2] https://github.com/ansible-community/collection_migration

Change-Id: Ib2b1c8f23aacfca95304132bfe5c4cdedbea0520
This commit is contained in:
Sagi Shnaidman 2020-01-16 13:49:34 +02:00
parent 20619996d5
commit 6ac08e7f0e
92 changed files with 15886 additions and 0 deletions

674
LICENSE Normal file
View File

@ -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>.

4
README.md Normal file
View File

@ -0,0 +1,4 @@
[![GitHub Actions CI/CD build status — Collection test suite](https://github.com/ansible-collection-migration/openstack.cloud/workflows/Collection%20test%20suite/badge.svg?branch=master)](https://github.com/ansible-collection-migration/openstack.cloud/actions?query=workflow%3A%22Collection%20test%20suite%22)
Ansible Collection: openstack.cloud
=================================================

20
galaxy.yml Normal file
View File

@ -0,0 +1,20 @@
namespace: openstack
name: cloud
version: 1.0.0
readme: README.md
authors: Openstack
description: Openstack Ansible modules
license: GPLv3
license_file: LICENSE
tags:
- cloud
- openstack
dependencies: {}
repository: https://opendev.org/openstack/ansible-collections-openstack.git
documentation: https://docs.openstack.org/ansible-collections-openstack
homepage: https://opendev.org
issues: https://review.opendev.org/q/project:openstack/ansible-collections-openstack
build_ignore:
- build_artifact/
- "*.tar.gz"
- zuul.yaml

View File

View File

@ -0,0 +1,100 @@
# -*- 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(os_user_domain_name) or I(os_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: no
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 ]
requirements:
- python >= 2.7
- 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/)
'''

View File

View File

@ -0,0 +1,346 @@
# 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)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
---
name: openstack
plugin_type: inventory
author:
- "Marco Vito Moscaritolo <marco@agavee.com>"
- "Jesse Keating <jesse.keating@rackspace.com>"
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
from ansible.errors import AnsibleParserError
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
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')
HAS_SDK = True
except ImportError:
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:
raise AnsibleParserError(msg)
# The user has pointed us at a clouds.yaml file. Use defaults for
# everything.
if 'clouds' in self._config_data:
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:
try:
source_data = self._cache[cache_key]
except KeyError:
# cache expired or doesn't exist yet
cache_needs_update = True
if not source_data:
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
sdk.enable_logging(stream=sys.stderr)
cloud_inventory = sdk_inventory.OpenStackInventory(
config_files=config_files,
private=self._config_data.get('private', False))
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:
if cloud.name in only_clouds:
new_clouds.append(cloud)
cloud_inventory.clouds = new_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 = cloud_inventory.list_hosts(
expand=expand_hostvars, fail_on_cloud_config=fail_on_errors,
all_projects=all_projects)
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):
# set vars in inventory from hostvars
for host in hostvars:
# create composite vars
self._set_composite_vars(
self._config_data.get('compose'), hostvars[host], host)
# actually update inventory
for key in hostvars[host]:
self.inventory.set_variable(host, key, hostvars[host][key])
# constructed groups based on conditionals
self._add_host_to_composed_groups(
self._config_data.get('groups'), hostvars[host], host)
# constructed groups based on jinja expressions
self._add_host_to_keyed_groups(
self._config_data.get('keyed_groups'), hostvars[host], host)
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):
return True
return False

View File

View File

@ -0,0 +1,164 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# Copyright (c) 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 os
from ansible.module_utils.six import iteritems
from distutils.version import StrictVersion
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=None, 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']),
)
spec.update(kwargs)
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
def openstack_cloud_from_module(module, min_version='0.12.0'):
try:
# Due to the name shadowing we should import other way
import importlib # pylint: disable=import-outside-toplevel
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('0.12.0'), StrictVersion(min_version))
else:
min_version = StrictVersion('0.12.0')
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))

View File

View File

@ -0,0 +1,82 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_auth
short_description: Retrieve an auth token
author: "Monty Taylor (@emonty)"
description:
- Retrieve an auth token from an OpenStack Cloud
requirements:
- "python >= 2.7"
- "openstacksdk"
options:
availability_zone:
description:
- Ignored. Present for backwards compatibility
required: false
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
- name: Authenticate to the cloud and retrieve the service catalog
os_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()

View File

@ -0,0 +1,81 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_client_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: []
requirements: [ os-client-config ]
author: "Monty Taylor (@emonty)"
'''
EXAMPLES = '''
- name: Get list of clouds that do not support security groups
os_client_config:
- debug:
var: "{{ item }}"
with_items: "{{ openstack.clouds | rejectattr('secgroup_source', 'none') | list }}"
- name: Get the information back just about the mordred cloud
os_client_config:
clouds:
- mordred
'''
try:
import os_client_config
from os_client_config import exceptions
HAS_OS_CLIENT_CONFIG = True
except ImportError:
HAS_OS_CLIENT_CONFIG = False
from ansible.module_utils.basic import AnsibleModule
def main():
module = AnsibleModule(argument_spec=dict(
clouds=dict(required=False, type='list', default=[]),
))
if not HAS_OS_CLIENT_CONFIG:
module.fail_json(msg='os-client-config is required for this module')
p = module.params
try:
config = os_client_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.OpenStackConfigException as e:
module.fail_json(msg=str(e))
if __name__ == "__main__":
main()

View File

@ -0,0 +1,285 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_coe_cluster
short_description: Add/Remove COE cluster from OpenStack Cloud
author: "Feilong Wang (@flwang)"
description:
- Add or Remove COE cluster from the OpenStack Container Infra service.
options:
availability_zone:
description:
- Ignored. Present for backwards compatibility
cluster_template_id:
description:
- The template ID of cluster template.
required: true
discovery_url:
description:
- Url used for cluster node discovery
docker_volume_size:
description:
- The size in GB of the docker volume
flavor_id:
description:
- The flavor of the minion node for this ClusterTemplate
keypair:
description:
- Name of the keypair to use.
labels:
description:
- One or more key/value pairs
master_flavor_id:
description:
- The flavor of the master node for this ClusterTemplate
master_count:
description:
- The number of master nodes for this cluster
default: 1
name:
description:
- Name that has to be given to the cluster template
required: true
node_count:
description:
- The number of nodes for this cluster
default: 1
state:
description:
- Indicate desired state of the resource.
choices: [present, absent]
default: present
timeout:
description:
- Timeout for creating the cluster in minutes. Default to 60 mins
if not set
default: 60
requirements: ["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'
uuid:
description:
- Unique UUID for this cluster
type: str
sample: '86246a4d-a16c-4a58-9e96ad7719fe0f9d'
'''
EXAMPLES = '''
# Create a new Kubernetes cluster
- os_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
module.exit_json(changed=changed, cluster=cluster, id=cluster['uuid'])
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()

View File

@ -0,0 +1,376 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_coe_cluster_template
short_description: Add/Remove COE cluster template from OpenStack Cloud
author: "Feilong Wang (@flwang)"
description:
- Add or Remove COE cluster template from the OpenStack Container Infra
service.
options:
availability_zone:
description:
- Ignored. Present for backwards compatibility
coe:
description:
- The Container Orchestration Engine for this clustertemplate
choices: [kubernetes, swarm, mesos]
dns_nameserver:
description:
- The DNS nameserver address
default: '8.8.8.8'
docker_storage_driver:
description:
- Docker storage driver
choices: [devicemapper, overlay, overlay2]
docker_volume_size:
description:
- The size in GB of the docker volume
external_network_id:
description:
- The external network to attach to the Cluster
fixed_network:
description:
- The fixed network name to attach to the Cluster
fixed_subnet:
description:
- The fixed subnet name to attach to the Cluster
flavor_id:
description:
- The flavor of the minion node for this ClusterTemplate
floating_ip_enabled:
description:
- Indicates whether created clusters should have a floating ip or not
type: bool
default: 'yes'
keypair_id:
description:
- Name or ID of the keypair to use.
image_id:
description:
- Image id the cluster will be based on
labels:
description:
- One or more key/value pairs
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
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
master_flavor_id:
description:
- The flavor of the master node for this ClusterTemplate
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
network_driver:
description:
- The name of the driver used for instantiating container networks
choices: [flannel, calico, docker]
no_proxy:
description:
- A comma separated list of IPs for which proxies should not be
used in the cluster
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
state:
description:
- Indicate desired state of the resource.
choices: [present, absent]
default: present
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]
requirements: ["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
- os_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()

View File

@ -0,0 +1,240 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_flavor_info
short_description: Retrieve information about one or more flavors
author: "David Shrewsbury (@Shrews)"
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(os_flavor_facts) before Ansible 2.9, returning C(ansible_facts).
Note that the M(os_flavor_info) module no longer returns C(ansible_facts)!
notes:
- The result contains a list of unsorted flavors.
requirements:
- "python >= 2.7"
- "openstacksdk"
options:
name:
description:
- A flavor name. Cannot be used with I(ram) or I(vcpus) or I(ephemeral).
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: bool
default: 'no'
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: bool
default: 'no'
limit:
description:
- Limits the number of flavors returned. All matching flavors are
returned by default.
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: bool
default: 'no'
availability_zone:
description:
- Ignored. Present for backwards compatibility
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Gather information about all available flavors
- os_flavor_info:
cloud: mycloud
register: result
- debug:
msg: "{{ result.openstack_flavors }}"
# Gather information for the flavor named "xlarge-flavor"
- os_flavor_info:
cloud: mycloud
name: "xlarge-flavor"
# Get all flavors that have exactly 512 MB of RAM.
- os_flavor_info:
cloud: mycloud
ram: "512"
# Get all flavors that have 1024 MB or more of RAM.
- os_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.
- os_flavor_info:
cloud: mycloud
ram: "MIN"
limit: 1
# Get all flavors with 1024 MB of RAM or more, AND exactly 2 virtual CPUs.
- os_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.
- os_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 == 'os_flavor_facts'
if is_old_facts:
module.deprecate("The 'os_flavor_facts' module has been renamed to 'os_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()

View File

@ -0,0 +1,256 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_floating_ip
author: Davide Guerri (@dguerri) <davide.guerri@hp.com>
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
network:
description:
- The name or ID of a neutron external network or a nova pool name.
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.
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.
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"]
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
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
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'
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements: ["openstacksdk"]
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Assign a floating IP to the fist interface of `cattle001` from an exiting
# external network or nova pool. A new floating IP from the first available
# external network is allocated to the project.
- os_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.
- os_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`.
- os_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
- os_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()

169
plugins/modules/os_group.py Normal file
View File

@ -0,0 +1,169 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_group
short_description: Manage OpenStack Identity Groups
author: "Monty Taylor (@emonty), David Shrewsbury (@Shrews)"
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
description:
description:
- Group description
domain_id:
description:
- Domain id to create the group in if the cloud supports domains.
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a group named "demo"
- os_group:
cloud: mycloud
state: present
name: demo
description: "Demo Group"
domain_id: demoid
# Update the description on existing "demo" group
- os_group:
cloud: mycloud
state: present
name: demo
description: "Something else"
domain_id: demoid
# Delete group named "demo"
- os_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()

View File

@ -0,0 +1,172 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_group_info
short_description: Retrieve info about one or more OpenStack groups
author: "Phillipe Smith (@phsmith)"
description:
- Retrieve info about a one or more OpenStack groups.
requirements:
- "python >= 2.7"
- "openstacksdk"
options:
name:
description:
- Name or ID of the group.
required: true
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
availability_zone:
description:
- Ignored. Present for backwards compatibility
type: str
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
os_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
os_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
os_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
os_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()

234
plugins/modules/os_image.py Normal file
View File

@ -0,0 +1,234 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
# TODO(mordred): we need to support "location"(v1) and "locations"(v2)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_image
short_description: Add/Delete images from OpenStack Cloud
author: "Monty Taylor (@emonty)"
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
id:
description:
- The ID of the image when uploading an image
checksum:
description:
- The checksum of the image
disk_format:
description:
- The format of the disk that is getting uploaded
default: qcow2
container_format:
description:
- The format of the container
default: bare
owner:
description:
- The owner of the image
min_disk:
description:
- The minimum disk space (in GB) required to boot this image
min_ram:
description:
- The minimum ram (in MB) required to boot this image
is_public:
description:
- Whether the image can be accessed publicly. Note that publicizing an image requires admin role by default.
type: bool
default: 'yes'
protected:
description:
- Prevent image from being deleted
type: bool
default: 'no'
filename:
description:
- The path to the file which has to be uploaded
ramdisk:
description:
- The name of an existing ramdisk image that will be associated with this image
kernel:
description:
- The name of an existing kernel image that will be associated with this image
properties:
description:
- Additional properties to be associated with this image
default: {}
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
volume:
description:
- ID of a volume to create an image from.
- The volume must be in AVAILABLE state.
requirements: ["openstacksdk"]
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Upload an image from a local file named cirros-0.3.0-x86_64-disk.img
- os_image:
auth:
auth_url: https://identity.example.com
username: admin
password: passme
project_name: admin
os_user_domain_name: Default
os_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
os_volume_snapshot:
auth:
"{{ auth }}"
display_name: myvol_snapshot
volume: myvol
force: yes
register: myvol_snapshot
- name: create volume from snapshot
os_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
os_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()

View File

@ -0,0 +1,198 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
module: os_image_info
short_description: Retrieve information about an image within OpenStack.
author: "Davide Agnello (@dagnello)"
description:
- Retrieve information about a image image from OpenStack.
- This module was called C(os_image_facts) before Ansible 2.9, returning C(ansible_facts).
Note that the M(os_image_info) module no longer returns C(ansible_facts)!
requirements:
- "python >= 2.7"
- "openstacksdk"
options:
image:
description:
- Name or ID of the image
required: false
properties:
description:
- Dict of properties of the images used for query
type: dict
required: false
availability_zone:
description:
- Ignored. Present for backwards compatibility
required: false
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
- name: Gather information about a previously created image named image1
os_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
os_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
os_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 == 'os_image_facts'
if is_old_facts:
module.deprecate("The 'os_image_facts' module has been renamed to 'os_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()

View File

@ -0,0 +1,356 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_ironic
short_description: Create/Delete Bare Metal Resources from OpenStack
author: "Monty Taylor (@emonty)"
description:
- Create or Remove Ironic nodes from OpenStack.
options:
state:
description:
- Indicates desired state of the resource
choices: ['present', 'absent']
default: present
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.
name:
description:
- unique name identifier to be given to the resource.
driver:
description:
- The name of the Ironic Driver to use with this node.
required: true
chassis_uuid:
description:
- Associate the node with a pre-defined chassis.
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.
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.
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
properties:
description:
- Definition of the physical characteristics of this server, used for scheduling purposes
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/include/root-device-hints.html)
for allowed hints)
default: ""
skip_update_of_driver_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.
type: bool
default: 'no'
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements: ["openstacksdk", "jsonpatch"]
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Enroll a node with some basic properties and driver info
- os_ironic:
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.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_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 = openstack_full_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),
properties=dict(type='dict', default={}),
ironic_url=dict(required=False),
chassis_uuid=dict(required=False),
skip_update_of_masked_password=dict(required=False, type='bool'),
state=dict(required=False, default='present')
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec, **module_kwargs)
if not HAS_JSONPATCH:
module.fail_json(msg='jsonpatch is required for this module')
if (module.params['auth_type'] in [None, 'None'] and
module.params['ironic_url'] is None):
module.fail_json(msg="Authentication appears to be disabled, "
"Please define an ironic_url parameter")
if (module.params['ironic_url'] and
module.params['auth_type'] in [None, 'None']):
module.params['auth'] = dict(
endpoint=module.params['ironic_url']
)
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()

View File

@ -0,0 +1,147 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_ironic_inspect
short_description: Explicitly triggers baremetal node introspection in ironic.
author: "Julia Kreger (@juliakreger)"
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.
uuid:
description:
- globally unique identifier (UUID) to identify the host.
name:
description:
- unique name identifier to identify the host in Ironic.
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.
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
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements: ["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
- os_ironic_inspect:
name: "testnode1"
'''
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 _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 = openstack_full_argument_spec(
auth_type=dict(required=False),
uuid=dict(required=False),
name=dict(required=False),
mac=dict(required=False),
ironic_url=dict(required=False),
timeout=dict(default=1200, type='int', required=False),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec, **module_kwargs)
if (module.params['auth_type'] in [None, 'None'] and
module.params['ironic_url'] is None):
module.fail_json(msg="Authentication appears to be disabled, "
"Please define an ironic_url parameter")
if (module.params['ironic_url'] and
module.params['auth_type'] in [None, 'None']):
module.params['auth'] = dict(
endpoint=module.params['ironic_url']
)
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()

View File

@ -0,0 +1,334 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_ironic_node
short_description: Activate/Deactivate Bare Metal Resources from OpenStack
author: "Monty Taylor (@emonty)"
description:
- Deploy to nodes controlled by Ironic.
options:
state:
description:
- Indicates desired state of the resource
choices: ['present', 'absent']
default: present
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: bool
default: 'yes'
uuid:
description:
- globally unique identifier (UUID) to be given to the resource.
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.
config_drive:
description:
- A configdrive file or HTTP(S) URL that will be passed along to the
node.
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.
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.
choices: ['present', 'absent']
default: present
maintenance:
description:
- A setting to allow the direct control if a node is in
maintenance mode.
type: bool
default: 'no'
maintenance_reason:
description:
- A string expression regarding the reason a node is in a
maintenance mode.
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.
availability_zone:
description:
- Ignored. Present for backwards compatibility
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Activate a node by booting an image with a configdrive attached
os_ironic_node:
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
'''
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 _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 = openstack_full_argument_spec(
uuid=dict(required=False),
name=dict(required=False),
instance_info=dict(type='dict', required=False),
config_drive=dict(required=False),
ironic_url=dict(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=True),
wait=dict(type='bool', required=False, default=False),
timeout=dict(required=False, type='int', default=1800),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec, **module_kwargs)
if (module.params['auth_type'] in [None, 'None'] and
module.params['ironic_url'] is None):
module.fail_json(msg="Authentication appears disabled, Please "
"define an ironic_url parameter")
if (module.params['ironic_url'] and
module.params['auth_type'] in [None, 'None']):
module.params['auth'] = dict(
endpoint=module.params['ironic_url']
)
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()

View File

@ -0,0 +1,166 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_keypair
short_description: Add/Delete a keypair from OpenStack
author: "Benno Joy (@bennojoy)"
description:
- Add or Remove key pair from OpenStack
options:
name:
description:
- Name that has to be given to the key pair
required: true
public_key:
description:
- The public key that would be uploaded to nova and injected into VMs
upon creation.
public_key_file:
description:
- Path to local file containing ssh public key. Mutually exclusive
with public_key.
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
availability_zone:
description:
- Ignored. Present for backwards compatibility
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Creates a key pair with the running users public key
- os_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.
- os_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.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, keypair):
state = module.params['state']
if state == 'present' and not keypair:
return True
if state == 'absent' and keypair:
return True
return False
def main():
argument_spec = openstack_full_argument_spec(
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 = openstack_module_kwargs(
mutually_exclusive=[['public_key', 'public_key_file']])
module = AnsibleModule(argument_spec,
supports_check_mode=True,
**module_kwargs)
state = module.params['state']
name = module.params['name']
public_key = module.params['public_key']
if module.params['public_key_file']:
with open(module.params['public_key_file']) as public_key_fh:
public_key = public_key_fh.read().rstrip()
sdk, cloud = openstack_cloud_from_module(module)
try:
keypair = cloud.get_keypair(name)
if module.check_mode:
module.exit_json(changed=_system_state_change(module, keypair))
if state in ('present', 'replace'):
if keypair and keypair['name'] == name:
if public_key and (public_key != keypair['public_key']):
if state == 'present':
module.fail_json(
msg="Key name %s present but key hash not the same"
" as offered. Delete key first." % name
)
else:
cloud.delete_keypair(name)
keypair = cloud.create_keypair(name, public_key)
changed = True
else:
changed = False
else:
keypair = cloud.create_keypair(name, public_key)
changed = True
module.exit_json(changed=changed,
key=keypair,
id=keypair['id'])
elif state == 'absent':
if keypair:
cloud.delete_keypair(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()

View File

@ -0,0 +1,188 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_keystone_domain
short_description: Manage OpenStack Identity Domains
author:
- Monty Taylor (@emonty)
- Haneef Ali (@haneefs)
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
description:
description:
- Description of the domain
enabled:
description:
- Is the domain enabled
type: bool
default: 'yes'
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a domain
- os_keystone_domain:
cloud: mycloud
state: present
name: demo
description: Demo Domain
# Delete a domain
- os_keystone_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()

View File

@ -0,0 +1,143 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_keystone_domain_info
short_description: Retrieve information about one or more OpenStack domains
author: "Ricardo Carrillo Cruz (@rcarrillocruz)"
description:
- Retrieve information about a one or more OpenStack domains
- This module was called C(os_keystone_domain_facts) before Ansible 2.9, returning C(ansible_facts).
Note that the M(os_keystone_domain_info) module no longer returns C(ansible_facts)!
requirements:
- "python >= 2.7"
- "sdk"
options:
name:
description:
- Name or ID of the domain
filters:
description:
- A dictionary of meta data to use for further filtering. Elements of
this dictionary may be additional dictionaries.
availability_zone:
description:
- Ignored. Present for backwards compatibility
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Gather information about previously created domain
- os_keystone_domain_info:
cloud: awesomecloud
register: result
- debug:
msg: "{{ result.openstack_domains }}"
# Gather information about a previously created domain by name
- os_keystone_domain_info:
cloud: awesomecloud
name: demodomain
register: result
- debug:
msg: "{{ result.openstack_domains }}"
# Gather information about a previously created domain with filter
- os_keystone_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 == 'os_keystone_domain_facts'
if is_old_facts:
module.deprecate("The 'os_keystone_domain_facts' module has been renamed to 'os_keystone_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()

View File

@ -0,0 +1,212 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_keystone_endpoint
short_description: Manage OpenStack Identity service endpoints
author:
- Mohammed Naser (@mnaser)
- Alberto Murillo (@albertomurillo)
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
endpoint_interface:
description:
- Interface of the service.
choices: [admin, public, internal]
required: true
url:
description:
- URL of the service.
required: true
region:
description:
- Region that the service belongs to. Note that I(region_name) is used for authentication.
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
requirements:
- openstacksdk >= 0.13.0
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
- name: Create a service for glance
os_keystone_endpoint:
cloud: mycloud
service: glance
endpoint_interface: public
url: http://controller:9292
region: RegionOne
state: present
- name: Delete a service for nova
os_keystone_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:
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()

View File

@ -0,0 +1,130 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_keystone_role
short_description: Manage OpenStack Identity Roles
author:
- Monty Taylor (@emonty)
- David Shrewsbury (@Shrews)
description:
- Manage OpenStack Identity Roles.
options:
name:
description:
- Role Name
required: true
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
required: false
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a role named "demo"
- os_keystone_role:
cloud: mycloud
state: present
name: demo
# Delete the role named "demo"
- os_keystone_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()

View File

@ -0,0 +1,197 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_keystone_service
short_description: Manage OpenStack Identity services
author: "Sam Yaple (@SamYaple)"
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
description:
description:
- Description of the service
enabled:
description:
- Is the service enabled
type: bool
default: 'yes'
service_type:
description:
- The type of service
required: true
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a service for glance
- os_keystone_service:
cloud: mycloud
state: present
name: glance
service_type: image
description: OpenStack Image Service
# Delete a service
- os_keystone_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()

View File

@ -0,0 +1,258 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_listener
short_description: Add/Delete a listener for a load balancer from OpenStack Cloud
author: "Lingxian Kong (@lingxiankong)"
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
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
loadbalancer:
description:
- The name or id of the load balancer that this listener belongs to.
required: true
protocol:
description:
- The protocol for the listener.
choices: [HTTP, HTTPS, TCP, TERMINATED_HTTPS]
default: HTTP
protocol_port:
description:
- The protocol port number for the listener.
default: 80
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
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements: ["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.
- os_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.
- os_listener:
cloud: mycloud
endpoint_type: admin
state: present
name: test-listener
loadbalancer: test-loadbalancer
protocol: HTTP
protocol_port: 8080
wait: no
# Delete a listener
- os_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()

View File

@ -0,0 +1,607 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_loadbalancer
short_description: Add/Delete load balancer from OpenStack Cloud
author: "Lingxian Kong (@lingxiankong)"
description:
- Add or Remove load balancer from the OpenStack load-balancer
service(Octavia). Load balancer update is not supported for now.
options:
name:
description:
- Name that has to be given to the load balancer
required: true
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
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.
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.
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.
vip_address:
description:
- IP address of the load balancer virtual IP.
public_ip_address:
description:
- Public IP address associated with the VIP.
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.
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
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.
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
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements: ["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.
- os_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.
- os_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.
- os_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)
- os_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.
- os_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.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_lb(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.find_load_balancer(lb.id)
if lb:
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)
)
else:
if status == "DELETED":
return None
else:
module.fail_json(
msg="Load Balancer %s transitioned to DELETED" % lb.id
)
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']),
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=[]),
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 = openstack_module_kwargs()
module = AnsibleModule(argument_spec, **module_kwargs)
sdk, cloud = openstack_cloud_from_module(module)
vip_network = module.params['vip_network']
vip_subnet = module.params['vip_subnet']
vip_port = module.params['vip_port']
listeners = module.params['listeners']
public_vip_address = module.params['public_ip_address']
allocate_fip = module.params['auto_public_ip']
delete_fip = module.params['delete_public_ip']
public_network = module.params['public_network']
vip_network_id = None
vip_subnet_id = None
vip_port_id = None
try:
changed = False
lb = cloud.load_balancer.find_load_balancer(
name_or_id=module.params['name'])
if module.params['state'] == 'present':
if not lb:
if not (vip_network or vip_subnet or vip_port):
module.fail_json(
msg="One of vip_network, vip_subnet, or vip_port must "
"be specified for load balancer creation"
)
if vip_network:
network = cloud.get_network(vip_network)
if not network:
module.fail_json(
msg='network %s is not found' % vip_network
)
vip_network_id = network.id
if vip_subnet:
subnet = cloud.get_subnet(vip_subnet)
if not subnet:
module.fail_json(
msg='subnet %s is not found' % vip_subnet
)
vip_subnet_id = subnet.id
if vip_port:
port = cloud.get_port(vip_port)
if not port:
module.fail_json(
msg='port %s is not found' % vip_port
)
vip_port_id = port.id
lb = cloud.load_balancer.create_load_balancer(
name=module.params['name'],
vip_network_id=vip_network_id,
vip_subnet_id=vip_subnet_id,
vip_port_id=vip_port_id,
vip_address=module.params['vip_address'],
)
changed = True
if not listeners and not module.params['wait']:
module.exit_json(
changed=changed,
loadbalancer=lb.to_dict(),
id=lb.id
)
_wait_for_lb(module, cloud, lb, "ACTIVE", ["ERROR"])
for listener_def in listeners:
listener_name = listener_def.get("name")
pool_def = listener_def.get("pool")
if not listener_name:
module.fail_json(msg='listener name is required')
listener = cloud.load_balancer.find_listener(
name_or_id=listener_name
)
if not listener:
_wait_for_lb(module, cloud, lb, "ACTIVE", ["ERROR"])
protocol = listener_def.get("protocol", "HTTP")
protocol_port = listener_def.get("protocol_port", 80)
listener = cloud.load_balancer.create_listener(
name=listener_name,
loadbalancer_id=lb.id,
protocol=protocol,
protocol_port=protocol_port,
)
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:
module.fail_json(msg='pool name is required')
pool = cloud.load_balancer.find_pool(name_or_id=pool_name)
if not pool:
_wait_for_lb(module, cloud, lb, "ACTIVE", ["ERROR"])
protocol = pool_def.get("protocol", "HTTP")
lb_algorithm = pool_def.get("lb_algorithm",
"ROUND_ROBIN")
pool = cloud.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:
module.fail_json(msg='member name is required')
member = cloud.load_balancer.find_member(member_name,
pool.id)
if not member:
_wait_for_lb(module, cloud, lb, "ACTIVE",
["ERROR"])
address = member_def.get("address")
if not address:
module.fail_json(
msg='member address for member %s is '
'required' % member_name
)
subnet_id = member_def.get("subnet")
if subnet_id:
subnet = cloud.get_subnet(subnet_id)
if not subnet:
module.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 = cloud.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 = cloud.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 = cloud.network.find_ip(public_vip_address)
if not fip:
module.fail_json(
msg='Public IP %s is unavailable' % public_vip_address
)
# Release origin public ip first
cloud.network.update_ip(
orig_public_ip,
fixed_ip_address=None,
port_id=None
)
# Associate new public ip
cloud.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 = cloud.network.find_available_ip()
if not fip:
if not public_network:
module.fail_json(msg="Public network is not provided")
pub_net = cloud.network.find_network(public_network)
if not pub_net:
module.fail_json(
msg='Public network %s not found' %
public_network
)
fip = cloud.network.create_ip(
floating_network_id=pub_net.id
)
cloud.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 = cloud.load_balancer.find_load_balancer(name_or_id=lb.id)
lb_dict = lb.to_dict()
lb_dict.update({"public_vip_address": new_public_ip})
module.exit_json(
changed=changed,
loadbalancer=lb_dict,
id=lb.id
)
elif module.params['state'] == 'absent':
changed = False
public_vip_address = None
if lb:
if delete_fip:
ips = cloud.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.
cloud.load_balancer.delete_load_balancer(lb, cascade=True)
changed = True
if module.params['wait']:
_wait_for_lb(module, cloud, lb, "DELETED", ["ERROR"])
if delete_fip and public_vip_address:
cloud.network.delete_ip(public_vip_address)
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()

View File

@ -0,0 +1,229 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_member
short_description: Add/Delete a member for a pool in load balancer from OpenStack Cloud
author: "Lingxian Kong (@lingxiankong)"
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
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
pool:
description:
- The name or id of the pool that this member belongs to.
required: true
protocol_port:
description:
- The protocol port number for the member.
default: 80
address:
description:
- The IP address of the member.
subnet_id:
description:
- The subnet ID the member service is accessible from.
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
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements: ["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.
- os_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
- os_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()

View File

@ -0,0 +1,251 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_network
short_description: Creates/removes networks from OpenStack
author: "Monty Taylor (@emonty)"
description:
- Add or remove network from OpenStack.
options:
name:
description:
- Name to be assigned to the network.
required: true
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
provider_physical_network:
description:
- The physical network where this network object is implemented.
provider_network_type:
description:
- The type of physical network that maps to this network resource.
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.
project:
description:
- Project name or ID containing the network (name admin-only)
availability_zone:
description:
- Ignored. Present for backwards compatibility
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.
type: bool
mtu:
description:
- The maximum transmission unit (MTU) value to address fragmentation.
Network will use OpenStack defaults if this option is
not provided.
type: int
dns_domain:
description:
- The DNS domain value to set.
Network will use Openstack defaults if this option is
not provided.
requirements:
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create an externally accessible network named 'ext_network'.
- os_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.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),
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'),
mtu=dict(required=False, type='int'),
dns_domain=dict(required=False)
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec, **module_kwargs)
state = module.params['state']
name = module.params['name']
shared = module.params['shared']
admin_state_up = module.params['admin_state_up']
external = module.params['external']
provider_physical_network = module.params['provider_physical_network']
provider_network_type = module.params['provider_network_type']
provider_segmentation_id = module.params['provider_segmentation_id']
project = module.params.get('project')
port_security_enabled = module.params.get('port_security_enabled')
mtu = module.params.get('mtu')
dns_domain = module.params.get('dns_domain')
sdk, cloud = openstack_cloud_from_module(module)
try:
if project is not None:
proj = cloud.get_project(project)
if proj is None:
module.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 = cloud.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 = cloud.create_network(name, shared, admin_state_up,
external, provider, project_id,
port_security_enabled=port_security_enabled,
mtu_size=mtu, dns_domain=dns_domain)
else:
net = cloud.create_network(name, shared, admin_state_up,
external, provider,
port_security_enabled=port_security_enabled,
mtu_size=mtu, dns_domain=dns_domain)
changed = True
else:
changed = False
module.exit_json(changed=changed, network=net, id=net['id'])
elif state == 'absent':
if not net:
module.exit_json(changed=False)
else:
cloud.delete_network(name)
module.exit_json(changed=True)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == "__main__":
main()

View File

@ -0,0 +1,160 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_networks_info
short_description: Retrieve information about one or more OpenStack networks.
author: "Davide Agnello (@dagnello)"
description:
- Retrieve information about one or more networks from OpenStack.
- This module was called C(os_networks_facts) before Ansible 2.9, returning C(ansible_facts).
Note that the M(os_networks_info) module no longer returns C(ansible_facts)!
requirements:
- "python >= 2.7"
- "sdk"
options:
name:
description:
- Name or ID of the Network
required: false
filters:
description:
- A dictionary of meta data to use for further filtering. Elements of
this dictionary may be additional dictionaries.
required: false
availability_zone:
description:
- Ignored. Present for backwards compatibility
required: false
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
- name: Gather information about previously created networks
os_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
os_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
os_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.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 = AnsibleModule(argument_spec)
is_old_facts = module._name == 'os_networks_facts'
if is_old_facts:
module.deprecate("The 'os_networks_facts' module has been renamed to 'os_networks_info', "
"and the renamed one no longer returns ansible_facts", version='2.13')
sdk, cloud = openstack_cloud_from_module(module)
try:
networks = cloud.search_networks(module.params['name'],
module.params['filters'])
if is_old_facts:
module.exit_json(changed=False, ansible_facts=dict(
openstack_networks=networks))
else:
module.exit_json(changed=False, openstack_networks=networks)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

View File

@ -0,0 +1,275 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_nova_flavor
short_description: Manage OpenStack compute flavors
author: "David Shrewsbury (@Shrews)"
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
name:
description:
- Flavor name.
required: true
ram:
description:
- Amount of memory, in MB.
vcpus:
description:
- Number of virtual CPUs.
disk:
description:
- Size of local disk, in GB.
default: 0
type: int
ephemeral:
description:
- Ephemeral space size, in GB.
default: 0
swap:
description:
- Swap space size, in MB.
default: 0
rxtx_factor:
description:
- RX/TX factor.
default: 1.0
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"
availability_zone:
description:
- Ignored. Present for backwards compatibility
extra_specs:
description:
- Metadata dictionary
requirements: ["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."
os_nova_flavor:
cloud: mycloud
state: present
name: tiny
ram: 1024
vcpus: 1
disk: 10
ephemeral: 10
- name: "Delete 'tiny' flavor"
os_nova_flavor:
cloud: mycloud
state: absent
name: tiny
- name: Create flavor with metadata
os_nova_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=False),
# 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()

View File

@ -0,0 +1,185 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_nova_host_aggregate
short_description: Manage OpenStack host aggregates
author: "Jakub Jursa (@kuboj)"
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
metadata:
description: Metadata dict.
availability_zone:
description: Availability zone to create aggregate into.
hosts:
description: List of hosts to set for an aggregate.
state:
description: Should the resource be present or absent.
choices: [present, absent]
default: present
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a host aggregate
- os_nova_host_aggregate:
cloud: mycloud
state: present
name: db_aggregate
hosts:
- host1
- host2
metadata:
type: dbcluster
# Delete an aggregate
- os_nova_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) or
(module.params['hosts'] is not None and set(module.params['hosts']) != set(aggregate.hosts)) or
(module.params['availability_zone'] is not None and module.params['availability_zone'] != aggregate.availability_zone) or
(module.params['metadata'] is not None and 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 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'),
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']
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)
if hosts:
for h in hosts:
cloud.add_host_to_aggregate(aggregate.id, h)
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)
if hosts is not None:
for i in (set(aggregate.hosts) - set(hosts)):
cloud.remove_host_from_aggregate(aggregate.id, i)
for i in (set(hosts) - set(aggregate.hosts)):
cloud.add_host_to_aggregate(aggregate.id, i)
changed = True
else:
changed = False
module.exit_json(changed=changed)
elif state == 'absent':
if aggregate is None:
changed = False
else:
if hosts:
for h in hosts:
cloud.remove_host_from_aggregate(aggregate.id, h)
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()

View File

@ -0,0 +1,128 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_object
short_description: Create or Delete objects and containers from OpenStack
author: "Monty Taylor (@emonty)"
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
name:
description:
- Name to be give to the object. If omitted, operations will be on
the entire container
required: false
filename:
description:
- Path to local file to be uploaded.
required: false
container_access:
description:
- desired container access level.
required: false
choices: ['private', 'public']
default: private
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
required: false
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
- name: "Create a object named 'fstab' in the 'config' container"
os_object:
cloud: mordred
state: present
name: fstab
container: config
filename: /etc/fstab
- name: Delete a container called config and all of its contents
os_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()

268
plugins/modules/os_pool.py Normal file
View File

@ -0,0 +1,268 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_pool
short_description: Add/Delete a pool in the load balancing service from OpenStack Cloud
author: "Lingxian Kong (@lingxiankong)"
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
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
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.
listener:
description:
- The name or id of the listener that this pool belongs to.
Either loadbalancer or listener must be specified for pool creation.
protocol:
description:
- The protocol for the pool.
choices: [HTTP, HTTPS, PROXY, TCP, UDP]
default: HTTP
lb_algorithm:
description:
- The load balancing algorithm for the pool.
choices: [LEAST_CONNECTIONS, ROUND_ROBIN, SOURCE_IP]
default: ROUND_ROBIN
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
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements: ["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.
- os_pool:
cloud: mycloud
endpoint_type: admin
state: present
name: test-pool
loadbalancer: test-loadbalancer
protocol: HTTP
lb_algorithm: ROUND_ROBIN
# Delete a pool
- os_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()

441
plugins/modules/os_port.py Normal file
View File

@ -0,0 +1,441 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_port
short_description: Add/Update/Delete ports from an OpenStack cloud.
author: "Davide Agnello (@dagnello)"
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.
requirements:
- "ordereddict unless python >= 2.7"
- "openstacksdk"
options:
network:
description:
- Network ID or name this port belongs to.
required: true
name:
description:
- Name that has to be given to the port.
fixed_ips:
description:
- Desired IP and/or subnet for this port. Subnet is referenced by
subnet_id and IP is referenced by ip_address.
admin_state_up:
description:
- Sets admin state.
type: bool
mac_address:
description:
- MAC address of this port.
security_groups:
description:
- Security group(s) ID(s) or name(s) associated with the port (comma
separated string or YAML list)
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: ..."
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: ..."
device_owner:
description:
- The ID of the entity that uses this port.
device_id:
description:
- Device ID of device using this port.
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
vnic_type:
description:
- The type of the port that should be created
choices: [normal, direct, direct-physical, macvtap, baremetal, virtio-forwarder]
port_security_enabled:
description:
- Whether to enable or disable the port security on the network.
type: bool
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a port
- os_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
- os_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
- os_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)
- os_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)
- os_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'
- os_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),
admin_state_up=dict(type='bool', default=None),
mac_address=dict(default=None),
security_groups=dict(default=None, type='list'),
no_security_groups=dict(default=False, type='bool'),
allowed_address_pairs=dict(type='list', default=None),
extra_dhcp_opts=dict(type='list', default=None),
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()

View File

@ -0,0 +1,227 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
module: os_port_info
short_description: Retrieve information about ports within OpenStack.
author: "David Shrewsbury (@Shrews)"
description:
- Retrieve information about ports from OpenStack.
- This module was called C(os_port_facts) before Ansible 2.9, returning C(ansible_facts).
Note that the M(os_port_info) module no longer returns C(ansible_facts)!
requirements:
- "python >= 2.7"
- "openstacksdk"
options:
port:
description:
- Unique name or ID of a port.
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.
availability_zone:
description:
- Ignored. Present for backwards compatibility
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Gather information about all ports
- os_port_info:
cloud: mycloud
register: result
- debug:
msg: "{{ result.openstack_ports }}"
# Gather information about a single port
- os_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.
- os_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 == 'os_port_facts'
if is_old_facts:
module.deprecate("The 'os_port_facts' module has been renamed to 'os_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()

View File

@ -0,0 +1,214 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_project
short_description: Manage OpenStack Projects
author: "Alberto Gireud (@agireud)"
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
description:
description:
- Description for the project
domain_id:
description:
- Domain id to create the project in if the cloud supports domains.
aliases: ['domain']
enabled:
description:
- Is the project enabled
type: bool
default: 'yes'
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a project
- os_project:
cloud: mycloud
endpoint_type: admin
state: present
name: demoproject
description: demodescription
domain_id: demoid
enabled: True
# Delete a project
- os_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
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']),
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']
state = module.params['state']
sdk, cloud = openstack_cloud_from_module(module)
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
else:
if _needs_update(module, project):
project = cloud.update_project(
project['id'], description=description,
enabled=enabled)
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()

View File

@ -0,0 +1,202 @@
#!/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/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_project_access
short_description: Manage OpenStack compute flavors access
author: "Roberto Polli (@ioggstream)"
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
target_project_id:
description:
- Project id.
required: true
resource_type:
description:
- The resource type (eg. nova_flavor, cinder_volume_type).
resource_name:
description:
- The resource name (eg. tiny).
availability_zone:
description:
- The availability zone of the resource.
requirements:
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
- name: "Enable access to tiny flavor to your tenant."
os_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"
os_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)
changed = False
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()

View File

@ -0,0 +1,167 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_project_info
short_description: Retrieve information about one or more OpenStack projects
author: "Ricardo Carrillo Cruz (@rcarrillocruz)"
description:
- Retrieve information about a one or more OpenStack projects
- This module was called C(os_project_facts) before Ansible 2.9, returning C(ansible_facts).
Note that the M(os_project_info) module no longer returns C(ansible_facts)!
requirements:
- "python >= 2.7"
- "openstacksdk"
options:
name:
description:
- Name or ID of the project
required: true
domain:
description:
- Name or ID of the domain containing the project if the cloud supports domains
filters:
description:
- A dictionary of meta data to use for further filtering. Elements of
this dictionary may be additional dictionaries.
availability_zone:
description:
- Ignored. Present for backwards compatibility
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Gather information about previously created projects
- os_project_info:
cloud: awesomecloud
register: result
- debug:
msg: "{{ result.openstack_projects }}"
# Gather information about a previously created project by name
- os_project_info:
cloud: awesomecloud
name: demoproject
register: result
- debug:
msg: "{{ result.openstack_projects }}"
# Gather information about a previously created project in a specific domain
- os_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
- os_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 == 'os_project_facts'
if is_old_facts:
module.deprecate("The 'os_project_facts' module has been renamed to 'os_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()

464
plugins/modules/os_quota.py Normal file
View File

@ -0,0 +1,464 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_quota
short_description: Manage OpenStack Quotas
author: "Michael Gale (@mgale) <gale.michael@gmail.com>"
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
state:
description:
- A value of present sets the quota and a value of absent resets the quota to system defaults.
default: present
backup_gigabytes:
description: Maximum size of backups in GB's.
backups:
description: Maximum number of backups allowed.
cores:
description: Maximum number of CPU's per project.
fixed_ips:
description: Number of fixed IP's to allow.
floating_ips:
description: Number of floating IP's to allow in Compute.
aliases: ['compute_floating_ips']
floatingip:
description: Number of floating IP's to allow in Network.
aliases: ['network_floating_ips']
gigabytes:
description: Maximum volume storage allowed for project.
gigabytes_lvm:
description: Maximum size in GB's of individual lvm volumes.
injected_file_size:
description: Maximum file size in bytes.
injected_files:
description: Number of injected files to allow.
injected_path_size:
description: Maximum path size.
instances:
description: Maximum number of instances allowed.
key_pairs:
description: Number of key pairs to allow.
loadbalancer:
description: Number of load balancers to allow.
network:
description: Number of networks to allow.
per_volume_gigabytes:
description: Maximum size in GB's of individual volumes.
pool:
description: Number of load balancer pools to allow.
port:
description: Number of Network ports to allow, this needs to be greater than the instances limit.
properties:
description: Number of properties to allow.
ram:
description: Maximum amount of ram in MB to allow.
rbac_policy:
description: Number of policies to allow.
router:
description: Number of routers to allow.
security_group_rule:
description: Number of rules per security group to allow.
security_group:
description: Number of security groups to allow.
server_group_members:
description: Number of server group members to allow.
server_groups:
description: Number of server groups to allow.
snapshots:
description: Number of snapshots to allow.
snapshots_lvm:
description: Number of LVM snapshots to allow.
subnet:
description: Number of subnets to allow.
subnetpool:
description: Number of subnet pools to allow.
volumes:
description: Number of volumes to allow.
volumes_lvm:
description: Number of LVM volumes to allow.
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements:
- "python >= 2.7"
- "openstacksdk >= 0.13.0"
- "keystoneauth1 >= 3.4.0"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# List a Project Quota
- os_quota:
cloud: mycloud
name: demoproject
# Set a Project back to the defaults
- os_quota:
cloud: mycloud
name: demoproject
state: absent
# Update a Project Quota for cores
- os_quota:
cloud: mycloud
name: demoproject
cores: 100
# Update a Project Quota
- os_quota:
name: demoproject
cores: 1000
volumes: 20
volumes_type:
- volume_lvm: 10
# Complete example based on list of projects
- name: Update quotas
os_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: complex
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_module_kwargs,
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()

View File

@ -0,0 +1,239 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_recordset
short_description: Manage OpenStack DNS recordsets
author: "Ricardo Carrillo Cruz (@rcarrillocruz)"
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
name:
description:
- Name of the recordset
required: true
recordset_type:
description:
- Recordset type
required: true
records:
description:
- List of recordset definitions
required: true
description:
description:
- Description of the recordset
ttl:
description:
- TTL (Time To Live) value in seconds
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a recordset named "www.example.net."
- os_recordset:
cloud: mycloud
state: present
zone: example.net.
name: www
recordset_type: primary
records: ['10.1.1.1']
description: test recordset
ttl: 3600
# Update the TTL on existing "www.example.net." recordset
- os_recordset:
cloud: mycloud
state: present
zone: example.net.
name: www
ttl: 7200
# Delete recordset named "www.example.net."
- os_recordset:
cloud: mycloud
state: absent
zone: example.net.
name: www
'''
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, zone, 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),
records=dict(required=False, type='list'),
description=dict(required=False, default=None),
ttl=dict(required=False, default=None, 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)
zone = module.params.get('zone')
name = module.params.get('name')
state = module.params.get('state')
sdk, cloud = openstack_cloud_from_module(module)
try:
recordset_type = module.params.get('recordset_type')
recordset_filter = {'type': recordset_type}
recordsets = cloud.search_recordsets(zone, name_or_id=name, filters=recordset_filter)
if len(recordsets) == 1:
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':
records = module.params.get('records')
description = module.params.get('description')
ttl = module.params.get('ttl')
if module.check_mode:
module.exit_json(changed=_system_state_change(state,
records, description,
ttl, zone,
recordset))
if recordset is None:
recordset = cloud.create_recordset(
zone=zone, name=name, recordset_type=recordset_type,
records=records, description=description, ttl=ttl)
changed = True
else:
if records is None:
records = []
pre_update_recordset = recordset
changed = _system_state_change(state, records,
description, ttl,
zone, pre_update_recordset)
if changed:
zone = cloud.update_recordset(
zone, recordset_id,
records=records,
description=description,
ttl=ttl)
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,
None, recordset))
if recordset is None:
changed = False
else:
cloud.delete_recordset(zone, recordset_id)
changed = True
module.exit_json(changed=changed)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

View File

@ -0,0 +1,488 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_router
short_description: Create or delete routers from OpenStack
author: "David Shrewsbury (@Shrews)"
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
name:
description:
- Name to be give to the router
required: true
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.
project:
description:
- Unique name or ID of the project.
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.
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(net), subnet name or ID (subnet) and the IP of
port (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.
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements: ["openstacksdk"]
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a simple router, not attached to a gateway or subnets.
- os_router:
cloud: mycloud
state: present
name: simple_router
# Create a simple router, not attached to a gateway or subnets for a given project.
- os_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.
- os_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.
- os_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.
- os_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.
- os_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.
- os_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
- os_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.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)
ROUTER_INTERFACE_OWNERS = set([
'network:router_interface',
'network:router_interface_distributed',
'network:ha_router_replicated_interface'
])
def _router_internal_interfaces(cloud, router):
for port in cloud.list_router_interfaces(router, 'internal'):
if port['device_owner'] in ROUTER_INTERFACE_OWNERS:
yield port
def _needs_update(cloud, module, router, network, internal_subnet_ids, internal_port_ids, filters=None):
"""Decide if the given router needs an update.
"""
if router['admin_state_up'] != module.params['admin_state_up']:
return True
if router['external_gateway_info']:
# check if enable_snat is set in module params
if module.params['enable_snat'] is not None:
if router['external_gateway_info'].get('enable_snat', True) != module.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 module.params['external_fixed_ips']:
for new_iface in module.params['external_fixed_ips']:
subnet = cloud.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 module.params['interfaces']:
existing_subnet_ids = []
for port in _router_internal_interfaces(cloud, router):
if 'fixed_ips' in port:
for fixed_ip in port['fixed_ips']:
existing_subnet_ids.append(fixed_ip['subnet_id'])
for iface in module.params['interfaces']:
if isinstance(iface, dict):
for p_id in internal_port_ids:
p = cloud.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):
internal_subnet_ids = []
return True
return False
def _system_state_change(cloud, module, router, network, internal_ids, internal_portids, filters=None):
"""Check if the system state would be changed."""
state = module.params['state']
if state == 'absent' and router:
return True
if state == 'present':
if not router:
return True
return _needs_update(cloud, module, router, network, internal_ids, internal_portids, filters)
return False
def _build_kwargs(cloud, module, router, network):
kwargs = {
'admin_state_up': module.params['admin_state_up'],
}
if router:
kwargs['name_or_id'] = router['id']
else:
kwargs['name'] = module.params['name']
if network:
kwargs['ext_gateway_net_id'] = network['id']
# can't send enable_snat unless we have a network
if module.params.get('enable_snat') is not None:
kwargs['enable_snat'] = module.params['enable_snat']
if module.params['external_fixed_ips']:
kwargs['ext_fixed_ips'] = []
for iface in module.params['external_fixed_ips']:
subnet = cloud.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(module, cloud, filters=None):
external_subnet_ids = []
internal_subnet_ids = []
internal_port_ids = []
existing_port_ips = []
existing_port_ids = []
if module.params['external_fixed_ips']:
for iface in module.params['external_fixed_ips']:
subnet = cloud.get_subnet(iface['subnet'])
if not subnet:
module.fail_json(msg='subnet %s not found' % iface['subnet'])
external_subnet_ids.append(subnet['id'])
if module.params['interfaces']:
for iface in module.params['interfaces']:
if isinstance(iface, str):
subnet = cloud.get_subnet(iface, filters)
if not subnet:
module.fail_json(msg='subnet %s not found' % iface)
internal_subnet_ids.append(subnet['id'])
elif isinstance(iface, dict):
subnet = cloud.get_subnet(iface['subnet'], filters)
if not subnet:
module.fail_json(msg='subnet %s not found' % iface['subnet'])
net = cloud.get_network(iface['net'])
if not net:
module.fail_json(msg='net %s not found' % iface['net'])
if "portip" not in iface:
internal_subnet_ids.append(subnet['id'])
elif not iface['portip']:
module.fail_json(msg='put an ip in portip or remove it from list to assign default port to router')
else:
for existing_port in cloud.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 = cloud.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 main():
argument_spec = openstack_full_argument_spec(
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),
external_fixed_ips=dict(type='list', default=None),
project=dict(default=None)
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec,
supports_check_mode=True,
**module_kwargs)
state = module.params['state']
name = module.params['name']
network = module.params['network']
project = module.params['project']
if module.params['external_fixed_ips'] and not network:
module.fail_json(msg='network is required when supplying external_fixed_ips')
sdk, cloud = openstack_cloud_from_module(module)
try:
if project is not None:
proj = cloud.get_project(project)
if proj is None:
module.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
router = cloud.get_router(name, filters=filters)
net = None
if network:
net = cloud.get_network(network)
if not net:
module.fail_json(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 = _validate_subnets(module, cloud, filters)
if module.check_mode:
module.exit_json(
changed=_system_state_change(cloud, module, router, net, subnet_internal_ids, internal_portids, filters)
)
if state == 'present':
changed = False
if not router:
kwargs = _build_kwargs(cloud, module, router, net)
if project_id:
kwargs['project_id'] = project_id
router = cloud.create_router(**kwargs)
for int_s_id in subnet_internal_ids:
cloud.add_router_interface(router, subnet_id=int_s_id)
changed = True
# add interface by port id as well
for int_p_id in internal_portids:
cloud.add_router_interface(router, port_id=int_p_id)
changed = True
else:
if _needs_update(cloud, module, router, net, subnet_internal_ids, internal_portids, filters):
kwargs = _build_kwargs(cloud, module, router, net)
updated_router = cloud.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 = _router_internal_interfaces(cloud, router)
for port in ports:
cloud.remove_router_interface(router, port_id=port['id'])
if internal_portids:
external_ids, subnet_internal_ids, internal_portids = _validate_subnets(module, cloud, filters)
for int_p_id in internal_portids:
cloud.add_router_interface(router, port_id=int_p_id)
changed = True
if subnet_internal_ids:
for s_id in subnet_internal_ids:
cloud.add_router_interface(router, subnet_id=s_id)
changed = True
module.exit_json(changed=changed,
router=router,
id=router['id'])
elif state == 'absent':
if not router:
module.exit_json(changed=False)
else:
# We need to detach all internal interfaces on a router before
# we will be allowed to delete it.
ports = _router_internal_interfaces(cloud, router)
router_id = router['id']
for port in ports:
cloud.remove_router_interface(router, port_id=port['id'])
cloud.delete_router(router_id)
module.exit_json(changed=True)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

View File

@ -0,0 +1,165 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_security_group
short_description: Add/Delete security groups from an OpenStack cloud.
author: "Monty Taylor (@emonty)"
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
description:
description:
- Long description of the purpose of the security group
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
project:
description:
- Unique name or ID of the project.
required: false
availability_zone:
description:
- Ignored. Present for backwards compatibility
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a security group
- os_security_group:
cloud: mordred
state: present
name: foo
description: security group for foo servers
# Update the existing 'foo' security group description
- os_security_group:
cloud: mordred
state: present
name: foo
description: updated description for the foo security group
# Create a security group for a given project
- os_security_group:
cloud: mordred
state: present
name: foo
project: myproj
'''
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, secgroup):
"""Check for differences in the updatable values.
NOTE: We don't currently allow name updates.
"""
if secgroup['description'] != module.params['description']:
return True
return False
def _system_state_change(module, secgroup):
state = module.params['state']
if state == 'present':
if not secgroup:
return True
return _needs_update(module, secgroup)
if state == 'absent' and secgroup:
return True
return False
def main():
argument_spec = openstack_full_argument_spec(
name=dict(required=True),
description=dict(default=''),
state=dict(default='present', choices=['absent', 'present']),
project=dict(default=None),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec,
supports_check_mode=True,
**module_kwargs)
name = module.params['name']
state = module.params['state']
description = module.params['description']
project = module.params['project']
sdk, cloud = openstack_cloud_from_module(module)
try:
if project is not None:
proj = cloud.get_project(project)
if proj is None:
module.fail_json(msg='Project %s could not be found' % project)
project_id = proj['id']
else:
project_id = cloud.current_project_id
if project_id:
filters = {'tenant_id': project_id}
else:
filters = None
secgroup = cloud.get_security_group(name, filters=filters)
if module.check_mode:
module.exit_json(changed=_system_state_change(module, secgroup))
changed = False
if state == 'present':
if not secgroup:
kwargs = {}
if project_id:
kwargs['project_id'] = project_id
secgroup = cloud.create_security_group(name, description,
**kwargs)
changed = True
else:
if _needs_update(module, secgroup):
secgroup = cloud.update_security_group(
secgroup['id'], description=description)
changed = True
module.exit_json(
changed=changed, id=secgroup['id'], secgroup=secgroup)
if state == 'absent':
if secgroup:
cloud.delete_security_group(secgroup['id'])
changed = True
module.exit_json(changed=changed)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == "__main__":
main()

View File

@ -0,0 +1,387 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_security_group_rule
short_description: Add/Delete rule from an existing security group
author:
- "Benno Joy (@bennojoy)"
- "Jeffrey van Pelt (@Thulium-Drake)"
description:
- Add or Remove rule from an existing security group
options:
security_group:
description:
- Name or ID of the security group
required: true
protocol:
description:
- IP protocols ANY TCP UDP ICMP 112 (VRRP) 132 (SCTP)
choices: ['any', 'tcp', 'udp', 'icmp', '112', '132', None]
port_range_min:
description:
- Starting port
port_range_max:
description:
- Ending port
remote_ip_prefix:
description:
- Source IP address(es) in CIDR notation (exclusive with remote_group)
remote_group:
description:
- Name or ID of the Security group to link (exclusive with
remote_ip_prefix)
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
direction:
description:
- The direction in which the security group rule is applied. Not
all providers support egress.
choices: ['egress', 'ingress']
default: ingress
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
project:
description:
- Unique name or ID of the project.
required: false
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements: ["openstacksdk"]
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a security group rule
- os_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
- os_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
- os_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
- os_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)
- os_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
- os_security_group_rule:
security_group: loadbalancer_sg
protocol: 112
remote_group: loadbalancer-node_sg
# Create a security group rule for a given project
- os_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
- os_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.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 _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 or None values for full TPC/UDP port range.
if protocol in ['tcp', 'udp'] or protocol is None:
if module_min and module_max and int(module_min) == int(module_max) == -1:
module_min = None
module_max = None
if ((module_min is None and module_max is None) and
(rule_min and int(rule_min) == 1 and
rule_max and int(rule_max) == 65535)):
# (None, None) == (1, 65535)
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
def _find_matching_rule(module, secgroup, remotegroup):
"""
Find a rule in the group that matches the module parameters.
:returns: The matching rule dict, or None if no matches.
"""
protocol = module.params['protocol']
remote_ip_prefix = module.params['remote_ip_prefix']
ethertype = module.params['ethertype']
direction = module.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,
module.params['port_range_min'],
module.params['port_range_max'],
rule['port_range_min'],
rule['port_range_max'])):
return rule
return None
def _system_state_change(module, secgroup, remotegroup):
state = module.params['state']
if secgroup:
rule_exists = _find_matching_rule(module, 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 main():
argument_spec = openstack_full_argument_spec(
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 = openstack_module_kwargs(
mutually_exclusive=[
['remote_ip_prefix', 'remote_group'],
]
)
module = AnsibleModule(argument_spec,
supports_check_mode=True,
**module_kwargs)
state = module.params['state']
security_group = module.params['security_group']
remote_group = module.params['remote_group']
project = module.params['project']
changed = False
sdk, cloud = openstack_cloud_from_module(module)
try:
if project is not None:
proj = cloud.get_project(project)
if proj is None:
module.fail_json(msg='Project %s could not be found' % project)
project_id = proj['id']
else:
project_id = cloud.current_project_id
if project_id:
filters = {'tenant_id': project_id}
else:
filters = None
secgroup = cloud.get_security_group(security_group, filters=filters)
if remote_group:
remotegroup = cloud.get_security_group(remote_group,
filters=filters)
else:
remotegroup = {'id': None}
if module.check_mode:
module.exit_json(changed=_system_state_change(module, secgroup, remotegroup))
if state == 'present':
if module.params['protocol'] == 'any':
module.params['protocol'] = None
if not secgroup:
module.fail_json(msg='Could not find security group %s' %
security_group)
rule = _find_matching_rule(module, secgroup, remotegroup)
if not rule:
kwargs = {}
if project_id:
kwargs['project_id'] = project_id
rule = cloud.create_security_group_rule(
secgroup['id'],
port_range_min=module.params['port_range_min'],
port_range_max=module.params['port_range_max'],
protocol=module.params['protocol'],
remote_ip_prefix=module.params['remote_ip_prefix'],
remote_group_id=remotegroup['id'],
direction=module.params['direction'],
ethertype=module.params['ethertype'],
**kwargs
)
changed = True
module.exit_json(changed=changed, rule=rule, id=rule['id'])
if state == 'absent' and secgroup:
rule = _find_matching_rule(module, secgroup, remotegroup)
if rule:
cloud.delete_security_group_rule(rule['id'])
changed = True
module.exit_json(changed=changed)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

View File

@ -0,0 +1,783 @@
#!/usr/bin/python
# coding: utf-8 -*-
# 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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_server
short_description: Create/Delete Compute Instances from OpenStack
author: "Monty Taylor (@emonty)"
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
image:
description:
- The name or id of the base image to boot.
required: true
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. Defaults to "(deprecated)"
flavor:
description:
- The name or id of the flavor in which the new instance has to be
created. Mutually exclusive with flavor_ram
default: 1
flavor_ram:
description:
- The minimum amount of ram in MB that the flavor in which the new
instance has to be created must have. Mutually exclusive with flavor.
default: 1
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.
key_name:
description:
- The key pair name to be used when creating a instance
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.
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.
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.'
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
floating_ip_pools:
description:
- Name of floating IP pool from which to choose a floating IP
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"'
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
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
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.
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']
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: []
scheduler_hints:
description:
- Arbitrary key/value pairs to the scheduler for custom use
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
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.
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
- name: Create a new instance and attaches to a network and passes metadata to the instance
os_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
os_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
os_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
os_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
os_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
os_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
os_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
os_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
os_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
os_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
os_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
os_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 os_server_group module.
- name: launch a compute instance
hosts: localhost
tasks:
- name: launch an instance
os_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"
os_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
os_server:
name: abcdef01-2345-6789-0abc-def0123456789
state: absent
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
openstack_find_nova_addresses, openstack_cloud_from_module,
openstack_full_argument_spec, openstack_module_kwargs)
def _exit_hostvars(module, cloud, server, changed=True):
hostvars = cloud.get_openstack_vars(server)
module.exit_json(
changed=changed, server=server, id=server.id, openstack=hostvars)
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 _network_args(module, cloud):
args = []
nics = module.params['nics']
if not isinstance(nics, list):
module.fail_json(msg='The \'nics\' parameter must be a list.')
for num, net in enumerate(_parse_nics(nics)):
if not isinstance(net, dict):
module.fail_json(
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 = cloud.get_network(net['net-name'])
if not by_name:
module.fail_json(
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 = cloud.get_port(net['port-name'])
if not by_name:
module.fail_json(
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 _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
def _delete_server(module, cloud):
try:
cloud.delete_server(
module.params['name'], wait=module.params['wait'],
timeout=module.params['timeout'],
delete_ips=module.params['delete_fip'])
except Exception as e:
module.fail_json(msg="Error in deleting vm: %s" % e.message)
module.exit_json(changed=True, result='deleted')
def _create_server(module, cloud):
flavor = module.params['flavor']
flavor_ram = module.params['flavor_ram']
flavor_include = module.params['flavor_include']
image_id = None
if not module.params['boot_volume']:
image_id = cloud.get_image_id(
module.params['image'], module.params['image_exclude'])
if not image_id:
module.fail_json(msg="Could not find image %s" %
module.params['image'])
if flavor:
flavor_dict = cloud.get_flavor(flavor)
if not flavor_dict:
module.fail_json(msg="Could not find flavor %s" % flavor)
else:
flavor_dict = cloud.get_flavor_by_ram(flavor_ram, flavor_include)
if not flavor_dict:
module.fail_json(msg="Could not find any matching flavor")
nics = _network_args(module, cloud)
module.params['meta'] = _parse_meta(module.params['meta'])
bootkwargs = dict(
name=module.params['name'],
image=image_id,
flavor=flavor_dict['id'],
nics=nics,
meta=module.params['meta'],
security_groups=module.params['security_groups'],
userdata=module.params['userdata'],
config_drive=module.params['config_drive'],
)
for optional_param in (
'key_name', 'availability_zone', 'network',
'scheduler_hints', 'volume_size', 'volumes'):
if module.params[optional_param]:
bootkwargs[optional_param] = module.params[optional_param]
server = cloud.create_server(
ip_pool=module.params['floating_ip_pools'],
ips=module.params['floating_ips'],
auto_ip=module.params['auto_ip'],
boot_volume=module.params['boot_volume'],
boot_from_volume=module.params['boot_from_volume'],
terminate_volume=module.params['terminate_volume'],
reuse_ips=module.params['reuse_ips'],
wait=module.params['wait'], timeout=module.params['timeout'],
**bootkwargs
)
_exit_hostvars(module, cloud, server)
def _update_server(module, cloud, server):
changed = False
module.params['meta'] = _parse_meta(module.params['meta'])
# cloud.set_server_metadata only updates the key=value pairs, it doesn't
# touch existing ones
update_meta = {}
for (k, v) in module.params['meta'].items():
if k not in server.metadata or server.metadata[k] != v:
update_meta[k] = v
if update_meta:
cloud.set_server_metadata(server, update_meta)
changed = True
# Refresh server vars
server = cloud.get_server(module.params['name'])
return (changed, server)
def _detach_ip_list(cloud, server, extra_ips):
for ip in extra_ips:
ip_id = cloud.get_floating_ip(
id=None, filters={'floating_ip_address': ip})
cloud.detach_ip_from_server(
server_id=server.id, floating_ip_id=ip_id)
def _check_ips(module, cloud, server):
changed = False
auto_ip = module.params['auto_ip']
floating_ips = module.params['floating_ips']
floating_ip_pools = module.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 = cloud.add_ips_to_server(
server,
auto_ip=auto_ip,
ips=floating_ips,
ip_pool=floating_ip_pools,
wait=module.params['wait'],
timeout=module.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 = cloud.add_ip_list(server, missing_ips,
wait=module.params['wait'],
timeout=module.params['timeout'])
changed = True
extra_ips = []
for ip in ips:
if ip not in floating_ips:
extra_ips.append(ip)
if extra_ips:
_detach_ip_list(cloud, 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 = cloud.add_ips_to_server(
server,
auto_ip=auto_ip,
ips=floating_ips,
ip_pool=floating_ip_pools,
wait=module.params['wait'],
timeout=module.params['timeout'],
)
changed = True
return (changed, server)
def _check_security_groups(module, cloud, 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(cloud, 'add_server_security_groups') and
hasattr(cloud, 'remove_server_security_groups')):
return changed, server
module_security_groups = set(module.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:
cloud.add_server_security_groups(server, list(add_sgs))
changed = True
if remove_sgs:
cloud.remove_server_security_groups(server, list(remove_sgs))
changed = True
return (changed, server)
def _get_server_state(module, cloud):
state = module.params['state']
server = cloud.get_server(module.params['name'])
if server and state == 'present':
if server.status not in ('ACTIVE', 'SHUTOFF', 'PAUSED', 'SUSPENDED'):
module.fail_json(
msg="The instance is available but not Active state: " + server.status)
(ip_changed, server) = _check_ips(module, cloud, server)
(sg_changed, server) = _check_security_groups(module, cloud, server)
(server_changed, server) = _update_server(module, cloud, server)
_exit_hostvars(module, cloud, server,
ip_changed or sg_changed or server_changed)
if server and state == 'absent':
return True
if state == 'absent':
module.exit_json(changed=False, result="not present")
return True
def main():
argument_spec = openstack_full_argument_spec(
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'),
network=dict(default=None),
nics=dict(default=[], type='list'),
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'),
floating_ip_pools=dict(default=None, type='list'),
volume_size=dict(default=False, 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'),
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 = openstack_module_kwargs(
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']),
],
)
module = AnsibleModule(argument_spec, **module_kwargs)
state = module.params['state']
image = module.params['image']
boot_volume = module.params['boot_volume']
flavor = module.params['flavor']
flavor_ram = module.params['flavor_ram']
if state == 'present':
if not (image or boot_volume):
module.fail_json(
msg="Parameter 'image' or 'boot_volume' is required "
"if state == 'present'"
)
if not flavor and not flavor_ram:
module.fail_json(
msg="Parameter 'flavor' or 'flavor_ram' is required "
"if state == 'present'"
)
sdk, cloud = openstack_cloud_from_module(module)
try:
if state == 'present':
_get_server_state(module, cloud)
_create_server(module, cloud)
elif state == 'absent':
_get_server_state(module, cloud)
_delete_server(module, cloud)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e), extra_data=e.extra_data)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,248 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_server_action
short_description: Perform actions on Compute Instances from OpenStack
author: "Jesse Keating (@omgjlk)"
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
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
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]
default: present
image:
description:
- Image the server should be rebuilt with
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Pauses a compute instance
- os_server_action:
action: pause
auth:
auth_url: https://identity.example.com
username: admin
password: admin
project_name: admin
server: vm1
timeout: 200
'''
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)
_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']
def _action_url(server_id):
return '/servers/{server_id}/action'.format(server_id=server_id)
def _wait(timeout, cloud, server, action, module, sdk):
"""Wait for the server to reach the desired state for the given action."""
for count in sdk.utils.iterate_timeout(
timeout,
"Timeout waiting for server to complete %s" % action):
try:
server = cloud.get_server(server.id)
except Exception:
continue
if server.status == _action_map[action]:
return
if server.status == 'ERROR':
module.fail_json(msg="Server reached ERROR state while attempting to %s" % action)
def _system_state_change(action, status):
"""Check if system state would change."""
if status == _action_map[action]:
return False
return True
def main():
argument_spec = openstack_full_argument_spec(
server=dict(required=True),
action=dict(required=True, choices=['stop', 'start', 'pause', 'unpause',
'lock', 'unlock', 'suspend', 'resume',
'rebuild']),
image=dict(required=False),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec, supports_check_mode=True,
required_if=[('action', 'rebuild', ['image'])],
**module_kwargs)
action = module.params['action']
wait = module.params['wait']
timeout = module.params['timeout']
image = module.params['image']
sdk, cloud = openstack_cloud_from_module(module)
try:
server = cloud.get_server(module.params['server'])
if not server:
module.fail_json(msg='Could not find server %s' % server)
status = server.status
if module.check_mode:
module.exit_json(changed=_system_state_change(action, status))
if action == 'stop':
if not _system_state_change(action, status):
module.exit_json(changed=False)
cloud.compute.post(
_action_url(server.id),
json={'os-stop': None})
if wait:
_wait(timeout, cloud, server, action, module, sdk)
module.exit_json(changed=True)
if action == 'start':
if not _system_state_change(action, status):
module.exit_json(changed=False)
cloud.compute.post(
_action_url(server.id),
json={'os-start': None})
if wait:
_wait(timeout, cloud, server, action, module, sdk)
module.exit_json(changed=True)
if action == 'pause':
if not _system_state_change(action, status):
module.exit_json(changed=False)
cloud.compute.post(
_action_url(server.id),
json={'pause': None})
if wait:
_wait(timeout, cloud, server, action, module, sdk)
module.exit_json(changed=True)
elif action == 'unpause':
if not _system_state_change(action, status):
module.exit_json(changed=False)
cloud.compute.post(
_action_url(server.id),
json={'unpause': None})
if wait:
_wait(timeout, cloud, server, action, module, sdk)
module.exit_json(changed=True)
elif action == 'lock':
# lock doesn't set a state, just do it
cloud.compute.post(
_action_url(server.id),
json={'lock': None})
module.exit_json(changed=True)
elif action == 'unlock':
# unlock doesn't set a state, just do it
cloud.compute.post(
_action_url(server.id),
json={'unlock': None})
module.exit_json(changed=True)
elif action == 'suspend':
if not _system_state_change(action, status):
module.exit_json(changed=False)
cloud.compute.post(
_action_url(server.id),
json={'suspend': None})
if wait:
_wait(timeout, cloud, server, action, module, sdk)
module.exit_json(changed=True)
elif action == 'resume':
if not _system_state_change(action, status):
module.exit_json(changed=False)
cloud.compute.post(
_action_url(server.id),
json={'resume': None})
if wait:
_wait(timeout, cloud, server, action, module, sdk)
module.exit_json(changed=True)
elif action == 'rebuild':
image = cloud.get_image(image)
if image is None:
module.fail_json(msg="Image does not exist")
# rebuild doesn't set a state, just do it
cloud.compute.post(
_action_url(server.id),
json={'rebuild': None})
if wait:
_wait(timeout, cloud, server, action, module, sdk)
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()

View File

@ -0,0 +1,175 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_server_group
short_description: Manage OpenStack server groups
author: "Lingxian Kong (@kong)"
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
name:
description:
- Server group name.
required: true
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
availability_zone:
description:
- Ignored. Present for backwards compatibility
required: false
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a server group with 'affinity' policy.
- os_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.
- os_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'),
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()

View File

@ -0,0 +1,116 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_server_info
short_description: Retrieve information about one or more compute instances
author: Monty (@emonty)
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(os_server_info) module no longer returns C(ansible_facts)!
notes:
- The result contains a list of servers.
requirements:
- "python >= 2.7"
- "openstacksdk"
options:
server:
description:
- restrict results to servers with names or UUID matching
this glob expression (e.g., <web*>).
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
availability_zone:
description:
- Ignored. Present for backwards compatibility
all_projects:
description:
- Whether to list servers from all projects or just the current auth
scoped project.
type: bool
default: 'no'
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Gather information about all servers named <web*> that are in an active state:
- os_server_info:
cloud: rax-dfw
server: web*
filters:
vm_state: active
register: result
- debug:
msg: "{{ result.openstack_servers }}"
'''
import fnmatch
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(
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),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec, **module_kwargs)
is_old_facts = module._name == 'os_server_facts'
if is_old_facts:
module.deprecate("The 'os_server_facts' module has been renamed to 'os_server_info', "
"and the renamed one no longer returns ansible_facts", version='2.13')
sdk, cloud = openstack_cloud_from_module(module)
try:
openstack_servers = cloud.search_servers(
detailed=module.params['detailed'], filters=module.params['filters'],
all_projects=module.params['all_projects'])
if module.params['server']:
# filter servers by name
pattern = module.params['server']
# TODO(mordred) This is handled by sdk now
openstack_servers = [server for server in openstack_servers
if fnmatch.fnmatch(server['name'], pattern) or fnmatch.fnmatch(server['id'], pattern)]
if is_old_facts:
module.exit_json(changed=False, ansible_facts=dict(
openstack_servers=openstack_servers))
else:
module.exit_json(changed=False, openstack_servers=openstack_servers)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

View File

@ -0,0 +1,172 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'community',
'metadata_version': '1.1'}
DOCUMENTATION = '''
---
module: os_server_metadata
short_description: Add/Update/Delete Metadata in Compute Instances from OpenStack
author: "Mario Santos (@ruizink)"
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']
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
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Availability zone in which to create the snapshot.
required: false
requirements:
- "python >= 2.7"
- "openstack"
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
os_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
os_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()

View File

@ -0,0 +1,149 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_server_volume
short_description: Attach/Detach Volumes from OpenStack VM's
author: "Monty Taylor (@emonty)"
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
server:
description:
- Name or ID of server you want to attach a volume to
required: true
volume:
description:
- Name or id of volume you want to attach to a server
required: true
device:
description:
- Device you want to attach. Defaults to auto finding a device name.
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements:
- "python >= 2.7"
- "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
os_server_volume:
state: present
cloud: mordred
server: Mysql-server
volume: mysql-data
device: /dev/vdb
'''
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, 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
def main():
argument_spec = openstack_full_argument_spec(
server=dict(required=True),
volume=dict(required=True),
device=dict(default=None), # None == auto choose device name
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']
wait = module.params['wait']
timeout = module.params['timeout']
sdk, cloud = openstack_cloud_from_module(module)
try:
server = cloud.get_server(module.params['server'])
volume = cloud.get_volume(module.params['volume'])
if not volume:
module.fail_json(msg='volume %s is not found' % module.params['volume'])
dev = cloud.get_volume_attach_device(volume, server.id)
if module.check_mode:
module.exit_json(changed=_system_state_change(state, dev))
if state == 'present':
changed = False
if not dev:
changed = True
cloud.attach_volume(server, volume, module.params['device'],
wait=wait, timeout=timeout)
server = cloud.get_server(module.params['server']) # refresh
volume = cloud.get_volume(module.params['volume']) # refresh
hostvars = cloud.get_openstack_vars(server)
module.exit_json(
changed=changed,
id=volume['id'],
attachments=volume['attachments'],
openstack=hostvars
)
elif state == 'absent':
if not dev:
# Volume is not attached to this server
module.exit_json(changed=False)
cloud.detach_volume(server, volume, wait=wait, timeout=timeout)
module.exit_json(
changed=True,
result='Detached volume from server'
)
except (sdk.exceptions.OpenStackCloudException, sdk.exceptions.ResourceTimeout) as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

280
plugins/modules/os_stack.py Normal file
View File

@ -0,0 +1,280 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_stack
short_description: Add/Remove Heat Stack
author:
- "Mathieu Bultel (@matbu)"
- "Steve Baker (@steveb)"
description:
- Add or Remove a Stack to an OpenStack Heat
options:
state:
description:
- Indicate desired state of the resource
choices: ['present', 'absent']
default: present
name:
description:
- Name of the stack that should be created, name could be char and digit, no space
required: true
tag:
description:
- Tag for the stack that should be created, name could be char and digit, no space
template:
description:
- Path of the template file to use for the stack creation
environment:
description:
- List of environment files that should be used for the stack creation
parameters:
description:
- Dictionary of parameters for the stack creation
rollback:
description:
- Rollback stack creation
type: bool
default: 'yes'
timeout:
description:
- Maximum number of seconds to wait for the stack creation
default: 3600
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
---
- name: create stack
ignore_errors: True
register: stack_create
os_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'),
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 os_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()

View File

@ -0,0 +1,360 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_subnet
short_description: Add/Remove subnet to an OpenStack network
author: "Monty Taylor (@emonty)"
description:
- Add or Remove a subnet to an OpenStack network
options:
state:
description:
- Indicate desired state of the resource
choices: ['present', 'absent']
default: present
network_name:
description:
- Name of the network to which the subnet should be attached
- Required when I(state) is 'present'
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
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.
ip_version:
description:
- The IP version of the subnet 4 or 6
default: 4
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
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.
allocation_pool_start:
description:
- From the subnet pool the starting address from which the IP should
be allocated.
allocation_pool_end:
description:
- From the subnet pool the last IP that should be assigned to the
virtual machines.
host_routes:
description:
- A list of host route dictionaries for the subnet.
ipv6_ra_mode:
description:
- IPv6 router advertisement mode
choices: ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac']
ipv6_address_mode:
description:
- IPv6 address mode
choices: ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac']
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)
availability_zone:
description:
- Ignored. Present for backwards compatibility
extra_specs:
description:
- Dictionary with extra key/value pairs passed to the API
required: false
default: {}
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a new (or update an existing) subnet on the specified network
- os_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
- os_subnet:
state: absent
name: net1subnet
# Create an ipv6 stateless subnet
- os_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.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 _can_update(subnet, module, cloud, filters=None):
"""Check for differences in non-updatable values"""
network_name = module.params['network_name']
ip_version = int(module.params['ip_version'])
ipv6_ra_mode = module.params['ipv6_ra_mode']
ipv6_a_mode = module.params['ipv6_address_mode']
if network_name:
network = cloud.get_network(network_name, filters)
if network:
netid = network['id']
else:
module.fail_json(msg='No network found for %s' % network_name)
if netid != subnet['network_id']:
module.fail_json(msg='Cannot update network_name in existing \
subnet')
if ip_version and subnet['ip_version'] != ip_version:
module.fail_json(msg='Cannot update ip_version in existing subnet')
if ipv6_ra_mode and subnet.get('ipv6_ra_mode', None) != ipv6_ra_mode:
module.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:
module.fail_json(msg='Cannot update ipv6_address_mode in existing \
subnet')
def _needs_update(subnet, module, cloud, filters=None):
"""Check for differences in the updatable values."""
# First check if we are trying to update something we're not allowed to
_can_update(subnet, module, cloud, filters)
# now check for the things we are allowed to update
enable_dhcp = module.params['enable_dhcp']
subnet_name = module.params['name']
pool_start = module.params['allocation_pool_start']
pool_end = module.params['allocation_pool_end']
gateway_ip = module.params['gateway_ip']
no_gateway_ip = module.params['no_gateway_ip']
dns = module.params['dns_nameservers']
host_routes = module.params['host_routes']
curr_pool = subnet['allocation_pools'][0]
if subnet['enable_dhcp'] != enable_dhcp:
return True
if subnet_name and subnet['name'] != subnet_name:
return True
if pool_start and curr_pool['start'] != pool_start:
return True
if pool_end and curr_pool['end'] != pool_end:
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(module, subnet, cloud, filters=None):
state = module.params['state']
if state == 'present':
if not subnet:
return True
return _needs_update(subnet, module, cloud, filters)
if state == 'absent' and subnet:
return True
return False
def main():
ipv6_mode_choices = ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac']
argument_spec = openstack_full_argument_spec(
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),
allocation_pool_start=dict(type='str'),
allocation_pool_end=dict(type='str'),
host_routes=dict(type='list', default=None),
ipv6_ra_mode=dict(type='str', choice=ipv6_mode_choices),
ipv6_address_mode=dict(type='str', choice=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 = openstack_module_kwargs()
module = AnsibleModule(argument_spec,
supports_check_mode=True,
required_together=[
['allocation_pool_end', 'allocation_pool_start'],
],
**module_kwargs)
state = module.params['state']
network_name = module.params['network_name']
cidr = module.params['cidr']
ip_version = module.params['ip_version']
enable_dhcp = module.params['enable_dhcp']
subnet_name = module.params['name']
gateway_ip = module.params['gateway_ip']
no_gateway_ip = module.params['no_gateway_ip']
dns = module.params['dns_nameservers']
pool_start = module.params['allocation_pool_start']
pool_end = module.params['allocation_pool_end']
host_routes = module.params['host_routes']
ipv6_ra_mode = module.params['ipv6_ra_mode']
ipv6_a_mode = module.params['ipv6_address_mode']
use_default_subnetpool = module.params['use_default_subnetpool']
project = module.params.pop('project')
extra_specs = module.params['extra_specs']
# Check for required parameters when state == 'present'
if state == 'present':
if not module.params['network_name']:
module.fail_json(msg='network_name required with present state')
if (not module.params['cidr'] and not use_default_subnetpool and
not extra_specs.get('subnetpool_id', False)):
module.fail_json(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:
module.fail_json(msg='no_gateway_ip is not allowed with gateway_ip')
sdk, cloud = openstack_cloud_from_module(module)
try:
if project is not None:
proj = cloud.get_project(project)
if proj is None:
module.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 = cloud.get_subnet(subnet_name, filters=filters)
if module.check_mode:
module.exit_json(changed=_system_state_change(module, subnet,
cloud, 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 = cloud.create_subnet(network_name, **kwargs)
changed = True
else:
if _needs_update(subnet, module, cloud, filters):
cloud.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
module.exit_json(changed=changed,
subnet=subnet,
id=subnet['id'])
elif state == 'absent':
if not subnet:
changed = False
else:
changed = True
cloud.delete_subnet(subnet_name)
module.exit_json(changed=changed)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

View File

@ -0,0 +1,174 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_subnets_info
short_description: Retrieve information about one or more OpenStack subnets.
author: "Davide Agnello (@dagnello)"
description:
- Retrieve information about one or more subnets from OpenStack.
- This module was called C(os_subnets_facts) before Ansible 2.9, returning C(ansible_facts).
Note that the M(os_subnets_info) module no longer returns C(ansible_facts)!
requirements:
- "python >= 2.7"
- "openstacksdk"
options:
name:
description:
- Name or ID of the subnet.
- Alias 'subnet' added in version 2.8.
required: false
aliases: ['subnet']
filters:
description:
- A dictionary of meta data to use for further filtering. Elements of
this dictionary may be additional dictionaries.
required: false
availability_zone:
description:
- Ignored. Present for backwards compatibility
required: false
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
- name: Gather information about previously created subnets
os_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
os_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
os_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.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, aliases=['subnet']),
filters=dict(required=False, type='dict', default=None)
)
module = AnsibleModule(argument_spec)
is_old_facts = module._name == 'os_subnets_facts'
if is_old_facts:
module.deprecate("The 'os_subnets_facts' module has been renamed to 'os_subnets_info', "
"and the renamed one no longer returns ansible_facts", version='2.13')
sdk, cloud = openstack_cloud_from_module(module)
try:
subnets = cloud.search_subnets(module.params['name'],
module.params['filters'])
if is_old_facts:
module.exit_json(changed=False, ansible_facts=dict(
openstack_subnets=subnets))
else:
module.exit_json(changed=False, openstack_subnets=subnets)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

297
plugins/modules/os_user.py Normal file
View File

@ -0,0 +1,297 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_user
short_description: Manage OpenStack Identity Users
author: David Shrewsbury (@Shrews)
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
password:
description:
- Password for the user
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.
email:
description:
- Email address for the user
description:
description:
- Description about the user
default_project:
description:
- Project name or ID that the user should be associated with by default
domain:
description:
- Domain to create the user in if the cloud supports domains
enabled:
description:
- Is the user enabled
type: bool
default: 'yes'
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a user
- os_user:
cloud: mycloud
state: present
name: demouser
password: secret
email: demo@example.com
domain: default
default_project: demo
# Delete a user
- os_user:
cloud: mycloud
state: absent
name: demouser
# Create a user but don't update password if user exists
- os_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
- os_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 distutils.version import StrictVersion
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()

View File

@ -0,0 +1,110 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_user_group
short_description: Associate OpenStack Identity users and groups
author: "Monty Taylor (@emonty)"
description:
- Add and remove users from groups
options:
user:
description:
- Name or id for the user
required: true
group:
description:
- Name or id for the group.
required: true
state:
description:
- Should the user be present or absent in the group
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
required: false
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Add the demo user to the demo group
- os_user_group:
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()

View File

@ -0,0 +1,177 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_user_info
short_description: Retrieve information about one or more OpenStack users
author: "Ricardo Carrillo Cruz (@rcarrillocruz)"
description:
- Retrieve information about a one or more OpenStack users
- This module was called C(os_user_facts) before Ansible 2.9, returning C(ansible_facts).
Note that the M(os_user_info) module no longer returns C(ansible_facts)!
requirements:
- "python >= 2.7"
- "openstacksdk"
options:
name:
description:
- Name or ID of the user
required: true
domain:
description:
- Name or ID of the domain containing the user if the cloud supports domains
filters:
description:
- A dictionary of meta data to use for further filtering. Elements of
this dictionary may be additional dictionaries.
availability_zone:
description:
- Ignored. Present for backwards compatibility
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Gather information about previously created users
- os_user_info:
cloud: awesomecloud
register: result
- debug:
msg: "{{ result.openstack_users }}"
# Gather information about a previously created user by name
- os_user_info:
cloud: awesomecloud
name: demouser
register: result
- debug:
msg: "{{ result.openstack_users }}"
# Gather information about a previously created user in a specific domain
- os_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
- os_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_module_kwargs,
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 == 'os_user_facts'
if is_old_facts:
module.deprecate("The 'os_user_facts' module has been renamed to 'os_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()

View File

@ -0,0 +1,203 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_user_role
short_description: Associate OpenStack Identity users and roles
author: "Monty Taylor (@emonty), David Shrewsbury (@Shrews)"
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
user:
description:
- Name or ID for the user. If I(user) is not specified, then
I(group) is required. Both may not be specified.
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.
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.
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.
state:
description:
- Should the roles be present or absent on the user.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Grant an admin role on the user admin in the project project1
- os_user_role:
cloud: mycloud
user: admin
role: admin
project: project1
# Revoke the admin role from the user barney in the newyork domain
- os_user_role:
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 = {}
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']
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:
g = cloud.get_group(group)
if g is None:
module.fail_json(msg="Group %s is not valid" % group)
filters['group'] = g['id']
domain_id = None
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()

View File

@ -0,0 +1,263 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_volume
short_description: Create/Delete Cinder Volumes
author: "Monty Taylor (@emonty)"
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'.
display_name:
description:
- Name of volume
required: true
display_description:
description:
- String describing the volume
volume_type:
description:
- Volume type for volume
image:
description:
- Image name or id for boot from volume
snapshot_id:
description:
- Volume snapshot id to create from
volume:
description:
- Volume name or id to create from
bootable:
description:
- Bootable flag for volume.
type: bool
default: False
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
scheduler_hints:
description:
- Scheduler hints passed to volume API in form of dict
metadata:
description:
- Metadata for the volume
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Creates a new volume
- name: create a volume
hosts: localhost
tasks:
- name: create 40g test volume
os_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 distutils.version import StrictVersion
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, 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 module.params[k] is not None and module.params[k] != volume.get(k):
return True
return False
def _modify_volume(module, cloud):
'''
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 = cloud.get_volume(module.params['display_name'])
diff = {'before': volume, 'after': ''}
size = module.params['size']
if size < volume.get('size'):
module.fail_json(
msg='Cannot shrink volumes, size: {0} < {1}'.format(size, volume.get('size'))
)
if not _needs_update(module, volume):
diff['after'] = volume
module.exit_json(changed=False, id=volume['id'], volume=volume, diff=diff)
if module.check_mode:
diff['after'] = volume
module.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff)
cloud.volume.extend_volume(
volume.id,
size
)
diff['after'] = cloud.get_volume(module.params['display_name'])
module.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff)
def _present_volume(module, cloud):
if cloud.volume_exists(module.params['display_name']):
v = cloud.get_volume(module.params['display_name'])
if not _needs_update(module, v):
module.exit_json(changed=False, id=v['id'], volume=v)
_modify_volume(module, cloud)
diff = {'before': '', 'after': ''}
volume_args = dict(
size=module.params['size'],
volume_type=module.params['volume_type'],
display_name=module.params['display_name'],
display_description=module.params['display_description'],
snapshot_id=module.params['snapshot_id'],
bootable=module.params['bootable'],
availability_zone=module.params['availability_zone'],
)
if module.params['image']:
image_id = cloud.get_image_id(module.params['image'])
volume_args['imageRef'] = image_id
if module.params['volume']:
volume_id = cloud.get_volume_id(module.params['volume'])
if not volume_id:
module.fail_json(msg="Failed to find volume '%s'" % module.params['volume'])
volume_args['source_volid'] = volume_id
if module.params['scheduler_hints']:
volume_args['scheduler_hints'] = module.params['scheduler_hints']
if module.params['metadata']:
volume_args['metadata'] = module.params['metadata']
if module.check_mode:
diff['after'] = volume_args
module.exit_json(changed=True, id=None, volume=volume_args, diff=diff)
volume = cloud.create_volume(
wait=module.params['wait'], timeout=module.params['timeout'],
**volume_args)
diff['after'] = volume
module.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff)
def _absent_volume(module, cloud, sdk):
changed = False
diff = {'before': '', 'after': ''}
if cloud.volume_exists(module.params['display_name']):
volume = cloud.get_volume(module.params['display_name'])
diff['before'] = volume
if module.check_mode:
module.exit_json(changed=True, diff=diff)
try:
changed = cloud.delete_volume(name_or_id=module.params['display_name'],
wait=module.params['wait'],
timeout=module.params['timeout'])
except sdk.exceptions.ResourceTimeout:
diff['after'] = volume
module.exit_json(changed=changed, diff=diff)
module.exit_json(changed=changed, diff=diff)
def main():
argument_spec = openstack_full_argument_spec(
size=dict(default=None, type='int'),
volume_type=dict(default=None),
display_name=dict(required=True, aliases=['name']),
display_description=dict(default=None, aliases=['description']),
image=dict(default=None),
snapshot_id=dict(default=None),
volume=dict(default=None),
state=dict(default='present', choices=['absent', 'present']),
scheduler_hints=dict(default=None, type='dict'),
metadata=dict(default=None, type='dict'),
bootable=dict(type='bool', default=False)
)
module_kwargs = openstack_module_kwargs(
mutually_exclusive=[
['image', 'snapshot_id', 'volume'],
],
)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, **module_kwargs)
state = module.params['state']
if state == 'present' and not module.params['size']:
module.fail_json(msg="Size is required when state is 'present'")
sdk, cloud = openstack_cloud_from_module(module)
try:
if state == 'present':
_present_volume(module, cloud)
if state == 'absent':
_absent_volume(module, cloud, sdk)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

View File

@ -0,0 +1,179 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'community',
'metadata_version': '1.1'}
DOCUMENTATION = '''
---
module: os_volume_snapshot
short_description: Create/Delete Cinder Volume Snapshots
author: "Mario Santos (@ruizink)"
description:
- Create or Delete cinder block storage volume snapshots
options:
display_name:
description:
- Name of the snapshot
required: true
aliases: ['name']
display_description:
description:
- String describing the snapshot
aliases: ['description']
volume:
description:
- The volume name or id to create/delete the snapshot
required: True
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
availability_zone:
description:
- Availability zone in which to create the snapshot.
requirements:
- "python >= 2.7"
- "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
os_volume_snapshot:
state: present
cloud: mordred
availability_zone: az2
display_name: test_snapshot
volume: test_volume
- name: delete snapshot
os_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()

246
plugins/modules/os_zone.py Normal file
View File

@ -0,0 +1,246 @@
#!/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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_zone
short_description: Manage OpenStack DNS zones
author: "Ricardo Carrillo Cruz (@rcarrillocruz)"
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
zone_type:
description:
- Zone type
choices: [primary, secondary]
email:
description:
- Email of the zone owner (only applies if zone_type is primary)
description:
description:
- Zone description
ttl:
description:
- TTL (Time To Live) value in seconds
masters:
description:
- Master nameservers (only applies if zone_type is secondary)
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
availability_zone:
description:
- Ignored. Present for backwards compatibility
requirements:
- "python >= 2.7"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a zone named "example.net"
- os_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
- os_zone:
cloud: mycloud
state: present
name: example.net.
ttl: 7200
# Delete zone named "example.net."
- os_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.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, 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(timeout, cloud, zone, state, module, sdk):
"""Wait for a zone to reach the desired state for the given state."""
for count in 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 = cloud.get_zone(zone.id)
except Exception:
continue
if zone and zone.status == 'ERROR':
module.fail_json(msg="Zone reached ERROR state while waiting for it to be %s" % state)
def main():
argument_spec = openstack_full_argument_spec(
name=dict(required=True),
zone_type=dict(required=False, choice=['primary', 'secondary']),
email=dict(required=False, default=None),
description=dict(required=False, default=None),
ttl=dict(required=False, default=None, type='int'),
masters=dict(required=False, default=None, type='list'),
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')
wait = module.params.get('wait')
timeout = module.params.get('timeout')
sdk, cloud = openstack_cloud_from_module(module)
try:
zone = cloud.get_zone(name)
if state == 'present':
zone_type = module.params.get('zone_type')
email = module.params.get('email')
description = module.params.get('description')
ttl = module.params.get('ttl')
masters = module.params.get('masters')
if module.check_mode:
module.exit_json(changed=_system_state_change(state, email,
description, ttl,
masters, zone))
if zone is None:
zone = cloud.create_zone(
name=name, zone_type=zone_type, email=email,
description=description, ttl=ttl, masters=masters)
changed = True
else:
if masters is None:
masters = []
pre_update_zone = zone
changed = _system_state_change(state, email,
description, ttl,
masters, pre_update_zone)
if changed:
zone = cloud.update_zone(
name, email=email,
description=description,
ttl=ttl, masters=masters)
if wait:
_wait(timeout, cloud, zone, state, module, sdk)
module.exit_json(changed=changed, zone=zone)
elif state == 'absent':
if module.check_mode:
module.exit_json(changed=_system_state_change(state, None,
None, None,
None, zone))
if zone is None:
changed = False
else:
cloud.delete_zone(name)
changed = True
if wait:
_wait(timeout, cloud, zone, state, module, sdk)
module.exit_json(changed=changed)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()

6
test-requirements.txt Normal file
View File

@ -0,0 +1,6 @@
ansible
pycodestyle
flake8
pylint
voluptuous
yamllint

1
tests/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
output/

View File

@ -0,0 +1,135 @@
plugins/module_utils/openstack.py future-import-boilerplate
plugins/module_utils/openstack.py metaclass-boilerplate
plugins/modules/os_auth.py validate-modules:doc-missing-type
plugins/modules/os_client_config.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_coe_cluster.py validate-modules:doc-missing-type
plugins/modules/os_coe_cluster.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_coe_cluster_template.py validate-modules:doc-missing-type
plugins/modules/os_coe_cluster_template.py validate-modules:doc-required-mismatch
plugins/modules/os_coe_cluster_template.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_flavor_info.py validate-modules:doc-default-does-not-match-spec
plugins/modules/os_flavor_info.py validate-modules:doc-missing-type
plugins/modules/os_flavor_info.py validate-modules:implied-parameter-type-mismatch
plugins/modules/os_flavor_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_floating_ip.py validate-modules:doc-missing-type
plugins/modules/os_group.py validate-modules:doc-missing-type
plugins/modules/os_group_info.py validate-modules:doc-required-mismatch
plugins/modules/os_image.py validate-modules:doc-choices-do-not-match-spec
plugins/modules/os_image.py validate-modules:doc-default-does-not-match-spec
plugins/modules/os_image.py validate-modules:doc-missing-type
plugins/modules/os_image.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_image_info.py validate-modules:doc-missing-type
plugins/modules/os_ironic.py validate-modules:doc-choices-do-not-match-spec
plugins/modules/os_ironic.py validate-modules:doc-missing-type
plugins/modules/os_ironic.py validate-modules:doc-required-mismatch
plugins/modules/os_ironic.py validate-modules:nonexistent-parameter-documented
plugins/modules/os_ironic.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_ironic.py validate-modules:undocumented-parameter
plugins/modules/os_ironic_inspect.py validate-modules:doc-missing-type
plugins/modules/os_ironic_node.py validate-modules:doc-choices-do-not-match-spec
plugins/modules/os_ironic_node.py validate-modules:doc-default-does-not-match-spec
plugins/modules/os_ironic_node.py validate-modules:doc-missing-type
plugins/modules/os_ironic_node.py validate-modules:implied-parameter-type-mismatch
plugins/modules/os_ironic_node.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_ironic_node.py validate-modules:undocumented-parameter
plugins/modules/os_keypair.py validate-modules:doc-missing-type
plugins/modules/os_keystone_domain.py validate-modules:doc-missing-type
plugins/modules/os_keystone_domain_info.py validate-modules:doc-missing-type
plugins/modules/os_keystone_domain_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_keystone_endpoint.py validate-modules:doc-missing-type
plugins/modules/os_keystone_endpoint.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_keystone_endpoint.py validate-modules:undocumented-parameter
plugins/modules/os_keystone_role.py validate-modules:doc-missing-type
plugins/modules/os_keystone_service.py validate-modules:doc-missing-type
plugins/modules/os_listener.py validate-modules:doc-missing-type
plugins/modules/os_listener.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_loadbalancer.py validate-modules:doc-missing-type
plugins/modules/os_loadbalancer.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_member.py validate-modules:doc-missing-type
plugins/modules/os_member.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_network.py validate-modules:doc-missing-type
plugins/modules/os_network.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_networks_info.py validate-modules:doc-missing-type
plugins/modules/os_networks_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_nova_flavor.py validate-modules:doc-missing-type
plugins/modules/os_nova_flavor.py validate-modules:doc-required-mismatch
plugins/modules/os_nova_flavor.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_nova_host_aggregate.py validate-modules:doc-missing-type
plugins/modules/os_nova_host_aggregate.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_object.py validate-modules:doc-missing-type
plugins/modules/os_pool.py validate-modules:doc-missing-type
plugins/modules/os_port.py validate-modules:doc-missing-type
plugins/modules/os_port.py validate-modules:doc-required-mismatch
plugins/modules/os_port.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_port_info.py validate-modules:doc-missing-type
plugins/modules/os_port_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_project.py validate-modules:doc-missing-type
plugins/modules/os_project_access.py validate-modules:doc-missing-type
plugins/modules/os_project_access.py validate-modules:doc-required-mismatch
plugins/modules/os_project_access.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_project_info.py validate-modules:doc-missing-type
plugins/modules/os_project_info.py validate-modules:doc-required-mismatch
plugins/modules/os_project_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_quota.py validate-modules:doc-choices-do-not-match-spec
plugins/modules/os_quota.py validate-modules:doc-missing-type
plugins/modules/os_quota.py validate-modules:nonexistent-parameter-documented
plugins/modules/os_quota.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_quota.py validate-modules:return-syntax-error
plugins/modules/os_quota.py validate-modules:undocumented-parameter
plugins/modules/os_recordset.py validate-modules:doc-missing-type
plugins/modules/os_recordset.py validate-modules:doc-required-mismatch
plugins/modules/os_recordset.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_router.py validate-modules:doc-missing-type
plugins/modules/os_router.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_security_group.py validate-modules:doc-missing-type
plugins/modules/os_security_group_rule.py validate-modules:doc-missing-type
plugins/modules/os_security_group_rule.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_server.py validate-modules:doc-default-does-not-match-spec
plugins/modules/os_server.py validate-modules:doc-missing-type
plugins/modules/os_server.py validate-modules:doc-required-mismatch
plugins/modules/os_server.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_server.py validate-modules:undocumented-parameter
plugins/modules/os_server_action.py validate-modules:doc-default-does-not-match-spec
plugins/modules/os_server_action.py validate-modules:doc-missing-type
plugins/modules/os_server_action.py validate-modules:doc-required-mismatch
plugins/modules/os_server_group.py validate-modules:doc-missing-type
plugins/modules/os_server_group.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_server_info.py validate-modules:doc-missing-type
plugins/modules/os_server_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_server_metadata.py validate-modules:doc-missing-type
plugins/modules/os_server_metadata.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_server_volume.py validate-modules:doc-missing-type
plugins/modules/os_stack.py validate-modules:doc-default-does-not-match-spec
plugins/modules/os_stack.py validate-modules:doc-missing-type
plugins/modules/os_stack.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_subnet.py validate-modules:doc-choices-do-not-match-spec
plugins/modules/os_subnet.py validate-modules:doc-missing-type
plugins/modules/os_subnet.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_subnets_info.py validate-modules:doc-missing-type
plugins/modules/os_subnets_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_user.py validate-modules:doc-missing-type
plugins/modules/os_user.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_user_group.py validate-modules:doc-missing-type
plugins/modules/os_user_info.py validate-modules:doc-missing-type
plugins/modules/os_user_info.py validate-modules:doc-required-mismatch
plugins/modules/os_user_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_user_role.py validate-modules:doc-missing-type
plugins/modules/os_volume.py validate-modules:doc-missing-type
plugins/modules/os_volume.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_volume.py validate-modules:undocumented-parameter
plugins/modules/os_volume_snapshot.py validate-modules:doc-missing-type
plugins/modules/os_zone.py validate-modules:doc-choices-do-not-match-spec
plugins/modules/os_zone.py validate-modules:doc-missing-type
plugins/modules/os_zone.py validate-modules:parameter-type-not-in-doc
plugins/doc_fragments/openstack.py future-import-boilerplate
plugins/doc_fragments/openstack.py metaclass-boilerplate
tests/unit/mock/path.py future-import-boilerplate
tests/unit/mock/path.py metaclass-boilerplate
tests/unit/mock/yaml_helper.py future-import-boilerplate
tests/unit/mock/yaml_helper.py metaclass-boilerplate
tests/unit/modules/cloud/openstack/test_os_server.py future-import-boilerplate
tests/unit/modules/cloud/openstack/test_os_server.py metaclass-boilerplate
tests/unit/modules/conftest.py future-import-boilerplate
tests/unit/modules/conftest.py metaclass-boilerplate
tests/unit/modules/utils.py future-import-boilerplate
tests/unit/modules/utils.py metaclass-boilerplate

124
tests/sanity/ignore-2.9.txt Normal file
View File

@ -0,0 +1,124 @@
plugins/module_utils/openstack.py future-import-boilerplate
plugins/module_utils/openstack.py metaclass-boilerplate
plugins/modules/os_auth.py validate-modules:doc-missing-type
plugins/modules/os_client_config.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_coe_cluster.py validate-modules:doc-missing-type
plugins/modules/os_coe_cluster.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_coe_cluster_template.py validate-modules:doc-missing-type
plugins/modules/os_coe_cluster_template.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_flavor_info.py validate-modules:doc-default-does-not-match-spec
plugins/modules/os_flavor_info.py validate-modules:doc-missing-type
plugins/modules/os_flavor_info.py validate-modules:implied-parameter-type-mismatch
plugins/modules/os_flavor_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_floating_ip.py validate-modules:doc-missing-type
plugins/modules/os_group.py validate-modules:doc-missing-type
plugins/modules/os_image.py validate-modules:doc-choices-do-not-match-spec
plugins/modules/os_image.py validate-modules:doc-default-does-not-match-spec
plugins/modules/os_image.py validate-modules:doc-missing-type
plugins/modules/os_image.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_image_info.py validate-modules:doc-missing-type
plugins/modules/os_ironic.py validate-modules:doc-choices-do-not-match-spec
plugins/modules/os_ironic.py validate-modules:doc-missing-type
plugins/modules/os_ironic.py validate-modules:nonexistent-parameter-documented
plugins/modules/os_ironic.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_ironic.py validate-modules:undocumented-parameter
plugins/modules/os_ironic_inspect.py validate-modules:doc-missing-type
plugins/modules/os_ironic_node.py validate-modules:doc-choices-do-not-match-spec
plugins/modules/os_ironic_node.py validate-modules:doc-default-does-not-match-spec
plugins/modules/os_ironic_node.py validate-modules:doc-missing-type
plugins/modules/os_ironic_node.py validate-modules:implied-parameter-type-mismatch
plugins/modules/os_ironic_node.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_ironic_node.py validate-modules:undocumented-parameter
plugins/modules/os_keypair.py validate-modules:doc-missing-type
plugins/modules/os_keystone_domain.py validate-modules:doc-missing-type
plugins/modules/os_keystone_domain_info.py validate-modules:doc-missing-type
plugins/modules/os_keystone_domain_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_keystone_endpoint.py validate-modules:doc-missing-type
plugins/modules/os_keystone_endpoint.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_keystone_endpoint.py validate-modules:undocumented-parameter
plugins/modules/os_keystone_role.py validate-modules:doc-missing-type
plugins/modules/os_keystone_service.py validate-modules:doc-missing-type
plugins/modules/os_listener.py validate-modules:doc-missing-type
plugins/modules/os_listener.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_loadbalancer.py validate-modules:doc-missing-type
plugins/modules/os_loadbalancer.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_member.py validate-modules:doc-missing-type
plugins/modules/os_member.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_network.py validate-modules:doc-missing-type
plugins/modules/os_network.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_networks_info.py validate-modules:doc-missing-type
plugins/modules/os_networks_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_nova_flavor.py validate-modules:doc-missing-type
plugins/modules/os_nova_flavor.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_nova_host_aggregate.py validate-modules:doc-missing-type
plugins/modules/os_nova_host_aggregate.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_object.py validate-modules:doc-missing-type
plugins/modules/os_pool.py validate-modules:doc-missing-type
plugins/modules/os_port.py validate-modules:doc-missing-type
plugins/modules/os_port.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_port_info.py validate-modules:doc-missing-type
plugins/modules/os_port_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_project.py validate-modules:doc-missing-type
plugins/modules/os_project_access.py validate-modules:doc-missing-type
plugins/modules/os_project_access.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_project_info.py validate-modules:doc-missing-type
plugins/modules/os_project_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_quota.py validate-modules:doc-choices-do-not-match-spec
plugins/modules/os_quota.py validate-modules:doc-missing-type
plugins/modules/os_quota.py validate-modules:nonexistent-parameter-documented
plugins/modules/os_quota.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_quota.py validate-modules:return-syntax-error
plugins/modules/os_quota.py validate-modules:undocumented-parameter
plugins/modules/os_recordset.py validate-modules:doc-missing-type
plugins/modules/os_recordset.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_router.py validate-modules:doc-missing-type
plugins/modules/os_router.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_security_group.py validate-modules:doc-missing-type
plugins/modules/os_security_group_rule.py validate-modules:doc-missing-type
plugins/modules/os_security_group_rule.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_server.py validate-modules:doc-default-does-not-match-spec
plugins/modules/os_server.py validate-modules:doc-missing-type
plugins/modules/os_server.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_server.py validate-modules:undocumented-parameter
plugins/modules/os_server_action.py validate-modules:doc-default-does-not-match-spec
plugins/modules/os_server_action.py validate-modules:doc-missing-type
plugins/modules/os_server_group.py validate-modules:doc-missing-type
plugins/modules/os_server_group.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_server_info.py validate-modules:doc-missing-type
plugins/modules/os_server_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_server_metadata.py validate-modules:doc-missing-type
plugins/modules/os_server_metadata.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_server_volume.py validate-modules:doc-missing-type
plugins/modules/os_stack.py validate-modules:doc-default-does-not-match-spec
plugins/modules/os_stack.py validate-modules:doc-missing-type
plugins/modules/os_stack.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_subnet.py validate-modules:doc-choices-do-not-match-spec
plugins/modules/os_subnet.py validate-modules:doc-missing-type
plugins/modules/os_subnet.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_subnets_info.py validate-modules:doc-missing-type
plugins/modules/os_subnets_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_user.py validate-modules:doc-missing-type
plugins/modules/os_user.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_user_group.py validate-modules:doc-missing-type
plugins/modules/os_user_info.py validate-modules:doc-missing-type
plugins/modules/os_user_info.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_user_role.py validate-modules:doc-missing-type
plugins/modules/os_volume.py validate-modules:doc-missing-type
plugins/modules/os_volume.py validate-modules:parameter-type-not-in-doc
plugins/modules/os_volume.py validate-modules:undocumented-parameter
plugins/modules/os_volume_snapshot.py validate-modules:doc-missing-type
plugins/modules/os_zone.py validate-modules:doc-choices-do-not-match-spec
plugins/modules/os_zone.py validate-modules:doc-missing-type
plugins/modules/os_zone.py validate-modules:parameter-type-not-in-doc
plugins/doc_fragments/openstack.py future-import-boilerplate
plugins/doc_fragments/openstack.py metaclass-boilerplate
tests/unit/mock/path.py future-import-boilerplate
tests/unit/mock/path.py metaclass-boilerplate
tests/unit/mock/yaml_helper.py future-import-boilerplate
tests/unit/mock/yaml_helper.py metaclass-boilerplate
tests/unit/modules/cloud/openstack/test_os_server.py future-import-boilerplate
tests/unit/modules/cloud/openstack/test_os_server.py metaclass-boilerplate
tests/unit/modules/conftest.py future-import-boilerplate
tests/unit/modules/conftest.py metaclass-boilerplate
tests/unit/modules/utils.py future-import-boilerplate
tests/unit/modules/utils.py metaclass-boilerplate

View File

@ -0,0 +1,4 @@
packaging # needed for update-bundled and changelog
sphinx ; python_version >= '3.5' # docs build requires python 3+
sphinx-notfound-page ; python_version >= '3.5' # docs build requires python 3+
straight.plugin ; python_version >= '3.5' # needed for hacking/build-ansible.py which will host changelog generation and requires python 3+

0
tests/unit/__init__.py Normal file
View File

View File

View File

@ -0,0 +1,33 @@
# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
#
# This file is part of Ansible
#
# Ansible 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.
#
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Compat for python2.7
#
# One unittest needs to import builtins via __import__() so we need to have
# the string that represents it
try:
import __builtin__
except ImportError:
BUILTINS = 'builtins'
else:
BUILTINS = '__builtin__'

122
tests/unit/compat/mock.py Normal file
View File

@ -0,0 +1,122 @@
# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
#
# This file is part of Ansible
#
# Ansible 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.
#
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
'''
Compat module for Python3.x's unittest.mock module
'''
import sys
# Python 2.7
# Note: Could use the pypi mock library on python3.x as well as python2.x. It
# is the same as the python3 stdlib mock library
try:
# Allow wildcard import because we really do want to import all of mock's
# symbols into this compat shim
# pylint: disable=wildcard-import,unused-wildcard-import
from unittest.mock import *
except ImportError:
# Python 2
# pylint: disable=wildcard-import,unused-wildcard-import
try:
from mock import *
except ImportError:
print('You need the mock library installed on python2.x to run tests')
# Prior to 3.4.4, mock_open cannot handle binary read_data
if sys.version_info >= (3,) and sys.version_info < (3, 4, 4):
file_spec = None
def _iterate_read_data(read_data):
# Helper for mock_open:
# Retrieve lines from read_data via a generator so that separate calls to
# readline, read, and readlines are properly interleaved
sep = b'\n' if isinstance(read_data, bytes) else '\n'
data_as_list = [l + sep for l in read_data.split(sep)]
if data_as_list[-1] == sep:
# If the last line ended in a newline, the list comprehension will have an
# extra entry that's just a newline. Remove this.
data_as_list = data_as_list[:-1]
else:
# If there wasn't an extra newline by itself, then the file being
# emulated doesn't have a newline to end the last line remove the
# newline that our naive format() added
data_as_list[-1] = data_as_list[-1][:-1]
for line in data_as_list:
yield line
def mock_open(mock=None, read_data=''):
"""
A helper function to create a mock to replace the use of `open`. It works
for `open` called directly or used as a context manager.
The `mock` argument is the mock object to configure. If `None` (the
default) then a `MagicMock` will be created for you, with the API limited
to methods or attributes available on standard file handles.
`read_data` is a string for the `read` methoddline`, and `readlines` of the
file handle to return. This is an empty string by default.
"""
def _readlines_side_effect(*args, **kwargs):
if handle.readlines.return_value is not None:
return handle.readlines.return_value
return list(_data)
def _read_side_effect(*args, **kwargs):
if handle.read.return_value is not None:
return handle.read.return_value
return type(read_data)().join(_data)
def _readline_side_effect():
if handle.readline.return_value is not None:
while True:
yield handle.readline.return_value
for line in _data:
yield line
global file_spec
if file_spec is None:
import _io # pylint: disable=import-outside-toplevel
file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO))))
if mock is None:
mock = MagicMock(name='open', spec=open)
handle = MagicMock(spec=file_spec)
handle.__enter__.return_value = handle
_data = _iterate_read_data(read_data)
handle.write.return_value = None
handle.read.return_value = None
handle.readline.return_value = None
handle.readlines.return_value = None
handle.read.side_effect = _read_side_effect
handle.readline.side_effect = _readline_side_effect()
handle.readlines.side_effect = _readlines_side_effect
mock.return_value = handle
return mock

View File

@ -0,0 +1,38 @@
# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
#
# This file is part of Ansible
#
# Ansible 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.
#
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
'''
Compat module for Python2.7's unittest module
'''
import sys
# Allow wildcard import because we really do want to import all of
# unittests's symbols into this compat shim
# pylint: disable=wildcard-import,unused-wildcard-import
if sys.version_info < (2, 7):
try:
# Need unittest2 on python2.6
from unittest2 import *
except ImportError:
print('You need unittest2 installed on python2.6.x to run tests')
else:
from unittest import *

View File

116
tests/unit/mock/loader.py Normal file
View File

@ -0,0 +1,116 @@
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible 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.
#
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
from ansible.errors import AnsibleParserError
from ansible.parsing.dataloader import DataLoader
from ansible.module_utils._text import to_bytes, to_text
class DictDataLoader(DataLoader):
def __init__(self, file_mapping=None):
file_mapping = {} if file_mapping is None else file_mapping
assert type(file_mapping) == dict
super(DictDataLoader, self).__init__()
self._file_mapping = file_mapping
self._build_known_directories()
self._vault_secrets = None
def load_from_file(self, path, cache=True, unsafe=False):
path = to_text(path)
if path in self._file_mapping:
return self.load(self._file_mapping[path], path)
return None
# TODO: the real _get_file_contents returns a bytestring, so we actually convert the
# unicode/text it's created with to utf-8
def _get_file_contents(self, path):
path = to_text(path)
if path in self._file_mapping:
return (to_bytes(self._file_mapping[path]), False)
else:
raise AnsibleParserError("file not found: %s" % path)
def path_exists(self, path):
path = to_text(path)
return path in self._file_mapping or path in self._known_directories
def is_file(self, path):
path = to_text(path)
return path in self._file_mapping
def is_directory(self, path):
path = to_text(path)
return path in self._known_directories
def list_directory(self, path):
ret = []
path = to_text(path)
for x in (list(self._file_mapping.keys()) + self._known_directories):
if x.startswith(path):
if os.path.dirname(x) == path:
ret.append(os.path.basename(x))
return ret
def is_executable(self, path):
# FIXME: figure out a way to make paths return true for this
return False
def _add_known_directory(self, directory):
if directory not in self._known_directories:
self._known_directories.append(directory)
def _build_known_directories(self):
self._known_directories = []
for path in self._file_mapping:
dirname = os.path.dirname(path)
while dirname not in ('/', ''):
self._add_known_directory(dirname)
dirname = os.path.dirname(dirname)
def push(self, path, content):
rebuild_dirs = False
if path not in self._file_mapping:
rebuild_dirs = True
self._file_mapping[path] = content
if rebuild_dirs:
self._build_known_directories()
def pop(self, path):
if path in self._file_mapping:
del self._file_mapping[path]
self._build_known_directories()
def clear(self):
self._file_mapping = dict()
self._known_directories = []
def get_basedir(self):
return os.getcwd()
def set_vault_secrets(self, vault_secrets):
self._vault_secrets = vault_secrets

5
tests/unit/mock/path.py Normal file
View File

@ -0,0 +1,5 @@
from ansible_collections.openstack.cloud.tests.unit.compat.mock import MagicMock
from ansible.utils.path import unfrackpath
mock_unfrackpath_noop = MagicMock(spec_set=unfrackpath, side_effect=lambda x, *args, **kwargs: x)

View File

@ -0,0 +1,90 @@
# (c) 2016, Matt Davis <mdavis@ansible.com>
# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com>
#
# This file is part of Ansible
#
# Ansible 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.
#
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import sys
import json
from contextlib import contextmanager
from io import BytesIO, StringIO
from ansible_collections.openstack.cloud.tests.unit.compat import unittest
from ansible.module_utils.six import PY3
from ansible.module_utils._text import to_bytes
@contextmanager
def swap_stdin_and_argv(stdin_data='', argv_data=tuple()):
"""
context manager that temporarily masks the test runner's values for stdin and argv
"""
real_stdin = sys.stdin
real_argv = sys.argv
if PY3:
fake_stream = StringIO(stdin_data)
fake_stream.buffer = BytesIO(to_bytes(stdin_data))
else:
fake_stream = BytesIO(to_bytes(stdin_data))
try:
sys.stdin = fake_stream
sys.argv = argv_data
yield
finally:
sys.stdin = real_stdin
sys.argv = real_argv
@contextmanager
def swap_stdout():
"""
context manager that temporarily replaces stdout for tests that need to verify output
"""
old_stdout = sys.stdout
if PY3:
fake_stream = StringIO()
else:
fake_stream = BytesIO()
try:
sys.stdout = fake_stream
yield fake_stream
finally:
sys.stdout = old_stdout
class ModuleTestCase(unittest.TestCase):
def setUp(self, module_args=None):
if module_args is None:
module_args = {'_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False}
args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args))
# unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually
self.stdin_swap = swap_stdin_and_argv(stdin_data=args)
self.stdin_swap.__enter__()
def tearDown(self):
# unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually
self.stdin_swap.__exit__(None, None, None)

View File

@ -0,0 +1,39 @@
# Ansible 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.
#
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.module_utils._text import to_bytes
from ansible.parsing.vault import VaultSecret
class TextVaultSecret(VaultSecret):
'''A secret piece of text. ie, a password. Tracks text encoding.
The text encoding of the text may not be the default text encoding so
we keep track of the encoding so we encode it to the same bytes.'''
def __init__(self, text, encoding=None, errors=None, _bytes=None):
super(TextVaultSecret, self).__init__()
self.text = text
self.encoding = encoding or 'utf-8'
self._bytes = _bytes
self.errors = errors or 'strict'
@property
def bytes(self):
'''The text encoded with encoding, unless we specifically set _bytes.'''
return self._bytes or to_bytes(self.text, encoding=self.encoding, errors=self.errors)

View File

@ -0,0 +1,121 @@
import io
import yaml
from ansible.module_utils.six import PY3
from ansible.parsing.yaml.loader import AnsibleLoader
from ansible.parsing.yaml.dumper import AnsibleDumper
class YamlTestUtils(object):
"""Mixin class to combine with a unittest.TestCase subclass."""
def _loader(self, stream):
"""Vault related tests will want to override this.
Vault cases should setup a AnsibleLoader that has the vault password."""
return AnsibleLoader(stream)
def _dump_stream(self, obj, stream, dumper=None):
"""Dump to a py2-unicode or py3-string stream."""
if PY3:
return yaml.dump(obj, stream, Dumper=dumper)
else:
return yaml.dump(obj, stream, Dumper=dumper, encoding=None)
def _dump_string(self, obj, dumper=None):
"""Dump to a py2-unicode or py3-string"""
if PY3:
return yaml.dump(obj, Dumper=dumper)
else:
return yaml.dump(obj, Dumper=dumper, encoding=None)
def _dump_load_cycle(self, obj):
# Each pass though a dump or load revs the 'generation'
# obj to yaml string
string_from_object_dump = self._dump_string(obj, dumper=AnsibleDumper)
# wrap a stream/file like StringIO around that yaml
stream_from_object_dump = io.StringIO(string_from_object_dump)
loader = self._loader(stream_from_object_dump)
# load the yaml stream to create a new instance of the object (gen 2)
obj_2 = loader.get_data()
# dump the gen 2 objects directory to strings
string_from_object_dump_2 = self._dump_string(obj_2,
dumper=AnsibleDumper)
# The gen 1 and gen 2 yaml strings
self.assertEqual(string_from_object_dump, string_from_object_dump_2)
# the gen 1 (orig) and gen 2 py object
self.assertEqual(obj, obj_2)
# again! gen 3... load strings into py objects
stream_3 = io.StringIO(string_from_object_dump_2)
loader_3 = self._loader(stream_3)
obj_3 = loader_3.get_data()
string_from_object_dump_3 = self._dump_string(obj_3, dumper=AnsibleDumper)
self.assertEqual(obj, obj_3)
# should be transitive, but...
self.assertEqual(obj_2, obj_3)
self.assertEqual(string_from_object_dump, string_from_object_dump_3)
def _old_dump_load_cycle(self, obj):
'''Dump the passed in object to yaml, load it back up, dump again, compare.'''
stream = io.StringIO()
yaml_string = self._dump_string(obj, dumper=AnsibleDumper)
self._dump_stream(obj, stream, dumper=AnsibleDumper)
yaml_string_from_stream = stream.getvalue()
# reset stream
stream.seek(0)
loader = self._loader(stream)
# loader = AnsibleLoader(stream, vault_password=self.vault_password)
obj_from_stream = loader.get_data()
stream_from_string = io.StringIO(yaml_string)
loader2 = self._loader(stream_from_string)
# loader2 = AnsibleLoader(stream_from_string, vault_password=self.vault_password)
obj_from_string = loader2.get_data()
stream_obj_from_stream = io.StringIO()
stream_obj_from_string = io.StringIO()
if PY3:
yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper)
yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper)
else:
yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper, encoding=None)
yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper, encoding=None)
yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue()
yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue()
stream_obj_from_stream.seek(0)
stream_obj_from_string.seek(0)
if PY3:
yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper)
yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper)
else:
yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper, encoding=None)
yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper, encoding=None)
assert yaml_string == yaml_string_obj_from_stream
assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string
assert (yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string == yaml_string_stream_obj_from_stream ==
yaml_string_stream_obj_from_string)
assert obj == obj_from_stream
assert obj == obj_from_string
assert obj == yaml_string_obj_from_stream
assert obj == yaml_string_obj_from_string
assert obj == obj_from_stream == obj_from_string == yaml_string_obj_from_stream == yaml_string_obj_from_string
return {'obj': obj,
'yaml_string': yaml_string,
'yaml_string_from_stream': yaml_string_from_stream,
'obj_from_stream': obj_from_stream,
'obj_from_string': obj_from_string,
'yaml_string_obj_from_string': yaml_string_obj_from_string}

View File

View File

View File

@ -0,0 +1,224 @@
import collections
import inspect
import mock
import pytest
import yaml
from ansible.module_utils.six import string_types
from ansible_collections.openstack.cloud.plugins.modules import os_server
class AnsibleFail(Exception):
pass
class AnsibleExit(Exception):
pass
def params_from_doc(func):
'''This function extracts the docstring from the specified function,
parses it as a YAML document, and returns parameters for the os_server
module.'''
doc = inspect.getdoc(func)
cfg = yaml.load(doc)
for task in cfg:
for module, params in task.items():
for k, v in params.items():
if k in ['nics'] and isinstance(v, string_types):
params[k] = [v]
task[module] = collections.defaultdict(str,
params)
return cfg[0]['os_server']
class FakeCloud(object):
ports = [
{'name': 'port1', 'id': '1234'},
{'name': 'port2', 'id': '4321'},
]
networks = [
{'name': 'network1', 'id': '5678'},
{'name': 'network2', 'id': '8765'},
]
images = [
{'name': 'cirros', 'id': '1'},
{'name': 'fedora', 'id': '2'},
]
flavors = [
{'name': 'm1.small', 'id': '1', 'flavor_ram': 1024},
{'name': 'm1.tiny', 'id': '2', 'flavor_ram': 512},
]
def _find(self, source, name):
for item in source:
if item['name'] == name or item['id'] == name:
return item
def get_image_id(self, name, exclude=None):
image = self._find(self.images, name)
if image:
return image['id']
def get_flavor(self, name):
return self._find(self.flavors, name)
def get_flavor_by_ram(self, ram, include=None):
for flavor in self.flavors:
if flavor['ram'] >= ram and (include is None or include in
flavor['name']):
return flavor
def get_port(self, name):
return self._find(self.ports, name)
def get_network(self, name):
return self._find(self.networks, name)
def get_openstack_vars(self, server):
return server
create_server = mock.MagicMock()
class TestNetworkArgs(object):
'''This class exercises the _network_args function of the
os_server module. For each test, we parse the YAML document
contained in the docstring to retrieve the module parameters for the
test.'''
def setup_method(self, method):
self.cloud = FakeCloud()
self.module = mock.MagicMock()
self.module.params = params_from_doc(method)
def test_nics_string_net_id(self):
'''
- os_server:
nics: net-id=1234
'''
args = os_server._network_args(self.module, self.cloud)
assert(args[0]['net-id'] == '1234')
def test_nics_string_net_id_list(self):
'''
- os_server:
nics: net-id=1234,net-id=4321
'''
args = os_server._network_args(self.module, self.cloud)
assert(args[0]['net-id'] == '1234')
assert(args[1]['net-id'] == '4321')
def test_nics_string_port_id(self):
'''
- os_server:
nics: port-id=1234
'''
args = os_server._network_args(self.module, self.cloud)
assert(args[0]['port-id'] == '1234')
def test_nics_string_net_name(self):
'''
- os_server:
nics: net-name=network1
'''
args = os_server._network_args(self.module, self.cloud)
assert(args[0]['net-id'] == '5678')
def test_nics_string_port_name(self):
'''
- os_server:
nics: port-name=port1
'''
args = os_server._network_args(self.module, self.cloud)
assert(args[0]['port-id'] == '1234')
def test_nics_structured_net_id(self):
'''
- os_server:
nics:
- net-id: '1234'
'''
args = os_server._network_args(self.module, self.cloud)
assert(args[0]['net-id'] == '1234')
def test_nics_structured_mixed(self):
'''
- os_server:
nics:
- net-id: '1234'
- port-name: port1
- 'net-name=network1,port-id=4321'
'''
args = os_server._network_args(self.module, self.cloud)
assert(args[0]['net-id'] == '1234')
assert(args[1]['port-id'] == '1234')
assert(args[2]['net-id'] == '5678')
assert(args[3]['port-id'] == '4321')
class TestCreateServer(object):
def setup_method(self, method):
self.cloud = FakeCloud()
self.module = mock.MagicMock()
self.module.params = params_from_doc(method)
self.module.fail_json.side_effect = AnsibleFail()
self.module.exit_json.side_effect = AnsibleExit()
self.meta = mock.MagicMock()
self.meta.gett_hostvars_from_server.return_value = {
'id': '1234'
}
os_server.meta = self.meta
def test_create_server(self):
'''
- os_server:
image: cirros
flavor: m1.tiny
nics:
- net-name: network1
meta:
- key: value
'''
with pytest.raises(AnsibleExit):
os_server._create_server(self.module, self.cloud)
assert(self.cloud.create_server.call_count == 1)
assert(self.cloud.create_server.call_args[1]['image'] == self.cloud.get_image_id('cirros'))
assert(self.cloud.create_server.call_args[1]['flavor'] == self.cloud.get_flavor('m1.tiny')['id'])
assert(self.cloud.create_server.call_args[1]['nics'][0]['net-id'] == self.cloud.get_network('network1')['id'])
def test_create_server_bad_flavor(self):
'''
- os_server:
image: cirros
flavor: missing_flavor
nics:
- net-name: network1
'''
with pytest.raises(AnsibleFail):
os_server._create_server(self.module, self.cloud)
assert('missing_flavor' in
self.module.fail_json.call_args[1]['msg'])
def test_create_server_bad_nic(self):
'''
- os_server:
image: cirros
flavor: m1.tiny
nics:
- net-name: missing_network
'''
with pytest.raises(AnsibleFail):
os_server._create_server(self.module, self.cloud)
assert('missing_network' in
self.module.fail_json.call_args[1]['msg'])

View File

@ -0,0 +1,28 @@
# Copyright (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
import json
import pytest
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_bytes
from ansible.module_utils.common._collections_compat import MutableMapping
@pytest.fixture
def patch_ansible_module(request, mocker):
if isinstance(request.param, string_types):
args = request.param
elif isinstance(request.param, MutableMapping):
if 'ANSIBLE_MODULE_ARGS' not in request.param:
request.param = {'ANSIBLE_MODULE_ARGS': request.param}
if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']:
request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp'
if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']:
request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False
args = json.dumps(request.param)
else:
raise Exception('Malformed data to the patch_ansible_module pytest fixture')
mocker.patch('ansible.module_utils.basic._ANSIBLE_ARGS', to_bytes(args))

View File

@ -0,0 +1,47 @@
import json
from ansible_collections.openstack.cloud.tests.unit.compat import unittest
from ansible_collections.openstack.cloud.tests.unit.compat.mock import patch
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
def set_module_args(args):
if '_ansible_remote_tmp' not in args:
args['_ansible_remote_tmp'] = '/tmp'
if '_ansible_keep_remote_files' not in args:
args['_ansible_keep_remote_files'] = False
args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
basic._ANSIBLE_ARGS = to_bytes(args)
class AnsibleExitJson(Exception):
pass
class AnsibleFailJson(Exception):
pass
def exit_json(*args, **kwargs):
if 'changed' not in kwargs:
kwargs['changed'] = False
raise AnsibleExitJson(kwargs)
def fail_json(*args, **kwargs):
kwargs['failed'] = True
raise AnsibleFailJson(kwargs)
class ModuleTestCase(unittest.TestCase):
def setUp(self):
self.mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json)
self.mock_module.start()
self.mock_sleep = patch('time.sleep')
self.mock_sleep.start()
set_module_args({})
self.addCleanup(self.mock_module.stop)
self.addCleanup(self.mock_sleep.stop)

View File

View File

View File

@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Lars Kellogg-Stedman <lars@redhat.com>
#
# This file is part of Ansible
#
# Ansible 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.
#
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
from ansible_collections.openstack.cloud.plugins.inventory.openstack import InventoryModule
from ansible.inventory.data import InventoryData
from ansible.template import Templar
config_data = {
'plugin': 'openstack',
'compose': {
'composed_var': '"testvar-" + testvar',
},
'groups': {
'testgroup': '"host" in inventory_hostname',
},
'keyed_groups':
[{
'prefix': 'keyed',
'key': 'testvar',
}]
}
hostvars = {
'host0': {
'inventory_hostname': 'host0',
'testvar': '0',
},
'host1': {
'inventory_hostname': 'host1',
'testvar': '1',
},
}
@pytest.fixture(scope="module")
def inventory():
inventory = InventoryModule()
inventory._config_data = config_data
inventory.inventory = InventoryData()
inventory.templar = Templar(loader=None)
for host in hostvars:
inventory.inventory.add_host(host)
return inventory
def test_simple_groups(inventory):
inventory._set_variables(hostvars, {})
groups = inventory.inventory.get_groups_dict()
assert 'testgroup' in groups
assert len(groups['testgroup']) == len(hostvars)
def test_keyed_groups(inventory):
inventory._set_variables(hostvars, {})
assert 'keyed_0' in inventory.inventory.groups
assert 'keyed_1' in inventory.inventory.groups
def test_composed_vars(inventory):
inventory._set_variables(hostvars, {})
for host in hostvars:
assert host in inventory.inventory.hosts
host = inventory.inventory.get_host(host)
assert host.vars['composed_var'] == 'testvar-{testvar}'.format(**hostvars[host.name])

View File

@ -0,0 +1,42 @@
boto3
placebo
pycrypto
passlib
pypsrp
python-memcached
pytz
pyvmomi
redis
requests
setuptools > 0.6 # pytest-xdist installed via requirements does not work with very old setuptools (sanity_ok)
unittest2 ; python_version < '2.7'
importlib ; python_version < '2.7'
netaddr
ipaddress
netapp-lib
solidfire-sdk-python
# requirements for F5 specific modules
f5-sdk ; python_version >= '2.7'
f5-icontrol-rest ; python_version >= '2.7'
deepdiff
# requirement for Fortinet specific modules
pyFMG
# requirement for aci_rest module
xmljson
# requirement for winrm connection plugin tests
pexpect
# requirement for the linode module
linode-python # APIv3
linode_api4 ; python_version > '2.6' # APIv4
# requirement for the gitlab module
python-gitlab
httmock
# requirment for kubevirt modules
openshift ; python_version >= '2.7'

49
tox.ini Normal file
View File

@ -0,0 +1,49 @@
[tox]
minversion = 3.1
envlist = pep8
skipsdist = True
ignore_basepython_conflict = True
[testenv]
skip_install = True
install_command = pip3 install {opts} {packages}
passenv = OS_*
setenv =
VIRTUAL_ENV={envdir}
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=C
OS_LOG_CAPTURE={env:OS_LOG_CAPTURE:true}
OS_STDOUT_CAPTURE={env:OS_STDOUT_CAPTURE:true}
OS_STDERR_CAPTURE={env:OS_STDERR_CAPTURE:true}
deps =
-r{toxinidir}/test-requirements.txt
commands = stestr run {posargs}
stestr slowest
[testenv:pep8]
commands =
flake8
[testenv:linters]
passenv = *
commands =
ansible-galaxy collection build --force {toxinidir} --output-path {toxinidir}/build_artifact
ansible-galaxy collection install {toxinidir}/build_artifact/openstack-cloud-1.0.0.tar.gz --force -p {toxinidir}
/bin/bash -c "cd ansible_collections/openstack/cloud && ansible-test sanity"
[testenv:venv]
deps =
-r{toxinidir}/test-requirements.txt
commands = {posargs}
[flake8]
# W503 Is supposed to be off by default but in the latest pycodestyle isn't.
# Also, both openstacksdk and Donald Knuth disagree with the rule. Line
# breaks should occur before the binary operator for readability.
# H4 are rules for docstrings. Maybe we should clean them?
# E501,E402,H301,H236,F401,E128 are ignored so we can import the existing
# modules unchanged and then clean them in subsequent patches.
ignore = W503,H4,E501,E402,H301,H236,F401,E128,W504,F841,F403,F405
show-source = True
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build

11
zuul.yaml Normal file
View File

@ -0,0 +1,11 @@
# yamllint disable
---
- project:
check:
jobs:
- tox-pep8
- openstack-tox-linters
gate:
jobs:
- tox-pep8
- openstack-tox-linters