Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,250 @@
==============================================
Hetzner Cloud Ansible Collection Release Notes
==============================================
.. contents:: Topics
v1.9.1
======
Bugfixes
--------
- hcloud_server - externally attached networks (using hcloud_server_network) were removed when not specified in the hcloud_server resource
v1.9.0
======
Minor Changes
-------------
- dynamic inventory - add support changing the name of the top level group all servers are added to
- hcloud_firewall - add support for esp and gre protocols
Bugfixes
--------
- hcloud_firewall - the deletion could fail if the firewall was referenced right before
- hcloud_server - fix backup window was given out as "None" instead of null
- hcloud_server_info - fix backup window was given out as "None" instead of null
- hcloud_volume - fix server name was given out as "None" instead of null if no server was attached
- hcloud_volume_info - fix server name was given out as "None" instead of null if no server was attached
v1.8.2
======
Bugfixes
--------
- dynamic inventory - fix crash when having servers without IPs (flexible networks)
- hcloud_server - When state stopped and server is created, do not start the server
- hcloud_server_info - fix crash when having servers without IPs (flexible networks)
v1.8.1
======
v1.8.0
======
New Modules
-----------
Hetzner
~~~~~~~
hcloud
^^^^^^
- hetzner.hcloud.hcloud_primary_ip - Create and manage cloud Primary IPs on the Hetzner Cloud.
v1.7.1
======
Minor Changes
-------------
- inventory - allow filtering by server status
Bugfixes
--------
- hcloud_server_network - fixes changed alias_ips by using sorted
v1.7.0
======
Minor Changes
-------------
- inventory - support jinjia templating within `network`
v1.6.0
======
Minor Changes
-------------
- hcloud_rdns Add support for load balancer
v1.5.0
======
Major Changes
-------------
- Introduction of placement groups
Minor Changes
-------------
- hcloud_firewall Add description field to firewall rules
Bugfixes
--------
- hcloud_rdns improve error message on not existing server/Floating IP
- hcloud_server backups property defaults to None now instead of False
v1.4.4
======
Bugfixes
--------
- hcloud_server Improve Error Message when attaching a not existing firewall to a server
- hcloud_volume Force detaching of volumes on servers before deletion
v1.4.3
======
Bugfixes
--------
- hcloud_server Fix incompatbility with python < 3.6
- hcloud_server Improve error handling when using not existing server types
v1.4.2
======
Bugfixes
--------
- inventory fix image name was set as server type instead of the correct server type
v1.4.1
======
Minor Changes
-------------
- hcloud_server - improve the handling of deprecated images
- hcloud_server - improve the validation and error response for not existing images
- inventory - support jinjia templating within `token`
v1.4.0
======
Security Fixes
--------------
- hcloud_certificate - mark the ``private_key`` parameter as ``no_log`` to prevent potential leaking of secret values (https://github.com/ansible-collections/hetzner.hcloud/pull/70).
Bugfixes
--------
- hcloud_firewall - fix idempotence related to rules comparison (https://github.com/ansible-collections/hetzner.hcloud/pull/71).
- hcloud_load_balancer_service - fix imported wrong HealthCheck from hcloud-python (https://github.com/ansible-collections/hetzner.hcloud/pull/73).
- hcloud_server - fix idempotence related to firewall handling (https://github.com/ansible-collections/hetzner.hcloud/pull/71).
v1.3.1
======
Bugfixes
--------
- hcloud_server - fix a crash related to check mode if ``state=started`` or ``state=stopped`` (https://github.com/ansible-collections/hetzner.hcloud/issues/54).
v1.3.0
======
Minor Changes
-------------
- Add firewalls to hcloud_server module
New Modules
-----------
- hcloud_firewall - Manage Hetzner Cloud Firewalls
v1.2.1
======
Bugfixes
--------
- Inventory Restore Python 2.7 compatibility
v1.2.0
======
Minor Changes
-------------
- Dynamic Inventory Add option to specifiy the token_env variable which is used for identification if now token is set
- Improve imports of API Exception
- hcloud_server_network Allow updating alias ips
- hcloud_subnetwork Allow creating vswitch subnetworks
New Modules
-----------
- hcloud_load_balancer_info - Gather infos about your Hetzner Cloud load_balancers.
v1.1.0
======
Minor Changes
-------------
- hcloud_floating_ip Allow creating Floating IP with protection
- hcloud_load_balancer Allow creating Load Balancer with protection
- hcloud_network Allow creating Network with protection
- hcloud_server Allow creating server with protection
- hcloud_volume Allow creating Volumes with protection
Bugfixes
--------
- hcloud_floating_ip Fix idempotency when floating ip is assigned to server
v1.0.0
======
Minor Changes
-------------
- hcloud_load_balancer Allow changing the type of a Load Balancer
- hcloud_server Allow the creation of servers with enabled backups
v0.2.0
======
Bugfixes
--------
- hcloud inventory plugin - Allow usage of hcloud.yml and hcloud.yaml - this was removed by error within the migration from build-in ansible to our collection
v0.1.0
======
New Modules
-----------
- hcloud_floating_ip - Create and manage cloud Floating IPs on the Hetzner Cloud.
- hcloud_load_balancer - Create and manage cloud Load Balancers on the Hetzner Cloud.
- hcloud_load_balancer_network - Manage the relationship between Hetzner Cloud Networks and Load Balancers
- hcloud_load_balancer_service - Create and manage the services of cloud Load Balancers on the Hetzner Cloud.
- hcloud_load_balancer_target - Manage Hetzner Cloud Load Balancer targets
- hcloud_load_balancer_type_info - Gather infos about the Hetzner Cloud Load Balancer types.

View File

@@ -0,0 +1,675 @@
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>.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
{
"collection_info": {
"namespace": "hetzner",
"name": "hcloud",
"version": "1.9.1",
"authors": [
"Hetzner Cloud (github.com/hetznercloud)"
],
"readme": "README.md",
"tags": [
"hetzner",
"cloud",
"hcloud"
],
"description": "A Collection for managing Hetzner Cloud resources",
"license": [
"GPL-3.0-or-later"
],
"license_file": null,
"dependencies": {
"ansible.netcommon": ">=0.0.1"
},
"repository": "https://github.com/ansible-collections/hetzner.hcloud",
"documentation": "https://docs.ansible.com/ansible/latest/collections/hetzner/hcloud",
"homepage": "https://github.com/ansible-collections/hetzner.hcloud",
"issues": "https://github.com/ansible-collections/hetzner.hcloud/issues"
},
"file_manifest_file": {
"name": "FILES.json",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "f0e3016c6b4532d1e89cdc7aa8cf4c5637767560fd6a1b95a7ef77f59291af3b",
"format": 1
},
"format": 1
}

View File

@@ -0,0 +1,62 @@
[![Build Status](https://dev.azure.com/ansible/hetzner.hcloud/_apis/build/status/CI?branchName=master)](https://dev.azure.com/ansible/hetzner.hcloud/_build?definitionId=35)
[![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/hetzner.hcloud)](https://codecov.io/gh/ansible-collections/hetzner.hcloud)
Ansible Collection: hetzner.hcloud
=================================================
Ansible Hetzner Cloud Collection for controlling your Hetzner Cloud Resources.
## Release notes
See [here](https://github.com/ansible-collections/hetzner.hcloud/tree/master/CHANGELOG.rst).
## Documentation
The documentation for all modules are available through `ansible-doc`.
Sample: `ansible-doc hetzner.hcloud.hcloud_server` shows the documentation for the `hcloud_server` module.
For all modules that were part of Ansible directly (before Ansible 2.11) we also have the documentation published in the
Ansible documentation: https://docs.ansible.com/ansible/latest/collections/hetzner/hcloud/
# Development
## Requirements
You should place the collection (clone the repository) into the Ansible collection path. Normally this
is `~/.ansible/collections/ansible_collections/<namespace>/<collection`, so for our collection it would
be: `~/.ansible/collections/ansible_collections/hetzner/hcloud`.
```
git clone git@github.com:ansible-collections/hetzner.hcloud.git ~/.ansible/collections/ansible_collections/hetzner/hcloud
```
After this you just need `ansible` installed.
## Testing
Testing is done via `ansible-test`. Make sure to have a `cloud-config-hcloud.ini` file in `tests/integration` which
contains the hcloud API token:
```
[default]
hcloud_api_token=<token>
```
After this you should be able to use `ansible-test integration` to perform the integration tests for a specific module.
Sample:
```
ansible-test integration --color --local -vvv hcloud_server // Executed all integration tests for hcloud_server module
```
## Releasing a new version
### Generating changelog from fragments
1. Check if the changelog fragments are available (there should be files in `changelogs/fragments`)
2. Run `antsibull-changelog release --version <version>`, it should remove all fragments and change
the `changelogs/changlog.yaml` and `CHANGELOG.rst`
3. Push the changes to the main branch
4. Tag the release through the Github UI, after this the Github Actions will run and publish the collection to Ansible
Galaxy

View File

@@ -0,0 +1 @@
/.plugin-cache.yaml

View File

@@ -0,0 +1,232 @@
ancestor: null
releases:
0.1.0:
modules:
- description: Create and manage cloud Floating IPs on the Hetzner Cloud.
name: hcloud_floating_ip
namespace: ''
- description: Create and manage cloud Load Balancers on the Hetzner Cloud.
name: hcloud_load_balancer
namespace: ''
- description: Manage the relationship between Hetzner Cloud Networks and Load
Balancers
name: hcloud_load_balancer_network
namespace: ''
- description: Create and manage the services of cloud Load Balancers on the Hetzner
Cloud.
name: hcloud_load_balancer_service
namespace: ''
- description: Manage Hetzner Cloud Load Balancer targets
name: hcloud_load_balancer_target
namespace: ''
- description: Gather infos about the Hetzner Cloud Load Balancer types.
name: hcloud_load_balancer_type_info
namespace: ''
release_date: '2020-06-29'
0.2.0:
changes:
bugfixes:
- hcloud inventory plugin - Allow usage of hcloud.yml and hcloud.yaml - this
was removed by error within the migration from build-in ansible to our collection
fragments:
- inventory-allow-usage-of-pre-migration-configuration-fuiles.yml
release_date: '2020-06-30'
1.0.0:
changes:
minor_changes:
- hcloud_load_balancer Allow changing the type of a Load Balancer
- hcloud_server Allow the creation of servers with enabled backups
fragments:
- gh7-allow-enabling-of-backups-on-server-creation.yml
- lb-allow-change-type.yml
release_date: '2020-08-11'
1.1.0:
changes:
bugfixes:
- hcloud_floating_ip Fix idempotency when floating ip is assigned to server
minor_changes:
- hcloud_floating_ip Allow creating Floating IP with protection
- hcloud_load_balancer Allow creating Load Balancer with protection
- hcloud_network Allow creating Network with protection
- hcloud_server Allow creating server with protection
- hcloud_volume Allow creating Volumes with protection
fragments:
- fix-idempotency-floating-ip.yml
- gh-28-allow-setting-of-protection-on-creation.yml
release_date: '2020-10-05'
1.2.0:
changes:
minor_changes:
- Dynamic Inventory Add option to specifiy the token_env variable which is used
for identification if now token is set
- Improve imports of API Exception
- hcloud_server_network Allow updating alias ips
- hcloud_subnetwork Allow creating vswitch subnetworks
modules:
- description: Gather infos about your Hetzner Cloud load_balancers.
name: hcloud_load_balancer_info
namespace: ''
release_date: '2020-12-01'
1.2.1:
changes:
bugfixes:
- Inventory Restore Python 2.7 compatibility
release_date: '2020-12-16'
1.3.0:
changes:
minor_changes:
- Add firewalls to hcloud_server module
modules:
- description: Manage Hetzner Cloud Firewalls
name: hcloud_firewall
namespace: ''
release_date: '2021-03-11'
1.3.1:
changes:
bugfixes:
- hcloud_server - fix a crash related to check mode if ``state=started`` or
``state=stopped`` (https://github.com/ansible-collections/hetzner.hcloud/issues/54).
fragments:
- 64-hcloud_server_fix_checkmode_state_started.yml
release_date: '2021-03-18'
1.4.0:
changes:
bugfixes:
- hcloud_firewall - fix idempotence related to rules comparison (https://github.com/ansible-collections/hetzner.hcloud/pull/71).
- hcloud_load_balancer_service - fix imported wrong HealthCheck from hcloud-python
(https://github.com/ansible-collections/hetzner.hcloud/pull/73).
- hcloud_server - fix idempotence related to firewall handling (https://github.com/ansible-collections/hetzner.hcloud/pull/71).
security_fixes:
- hcloud_certificate - mark the ``private_key`` parameter as ``no_log`` to prevent
potential leaking of secret values (https://github.com/ansible-collections/hetzner.hcloud/pull/70).
fragments:
- 70-no_log_security_fixes.yml
- 71-hcloud_firewall_fix_idempotence.yml
- 73-hcloud_load_balancer_service_fix_wrong_import.yml
release_date: '2021-04-06'
1.4.1:
changes:
minor_changes:
- hcloud_server - improve the handling of deprecated images
- hcloud_server - improve the validation and error response for not existing
images
- inventory - support jinjia templating within `token`
fragments:
- 74-hcloud_server-improve-error-message-images.yml
release_date: '2021-04-07'
1.4.2:
changes:
bugfixes:
- inventory fix image name was set as server type instead of the correct server
type
fragments:
- inventory-fix-server-type-wrong-value.yml
release_date: '2021-04-14'
1.4.3:
changes:
bugfixes:
- hcloud_server Fix incompatbility with python < 3.6
- hcloud_server Improve error handling when using not existing server types
fragments:
- hcloud-server-py36.yaml
- hcloud-server-server-type.yaml
release_date: '2021-04-22'
1.4.4:
changes:
bugfixes:
- hcloud_server Improve Error Message when attaching a not existing firewall
to a server
- hcloud_volume Force detaching of volumes on servers before deletion
fragments:
- hcloud_server-improve-error-message-on-not-existing-firewall.yml
- hcloud_volume-force-detach-before-deletion.yml
release_date: '2021-07-19'
1.5.0:
changes:
bugfixes:
- hcloud_rdns improve error message on not existing server/Floating IP
- hcloud_server backups property defaults to None now instead of False
major_changes:
- Introduction of placement groups
minor_changes:
- hcloud_firewall Add description field to firewall rules
fragments:
- hcloud_firewall-add-description-field-to-rules.yml
- hcloud_placement_group.yml
- hcloud_rdns-improve-validation-of-input.yml
- hcloud_server_default-backups-to-none.yml
release_date: '2021-08-16'
1.6.0:
changes:
minor_changes:
- hcloud_rdns Add support for load balancer
fragments:
- hcloud_rdns-add-support-for-load-balancers.yml
release_date: '2021-08-17'
1.7.0:
changes:
minor_changes:
- inventory - support jinjia templating within `network`
fragments:
- inventory-network-templating.yml
release_date: '2022-06-09'
1.7.1:
changes:
bugfixes:
- hcloud_server_network - fixes changed alias_ips by using sorted
minor_changes:
- inventory - allow filtering by server status
fragments:
- hcloud_server_network-alias-ips.yaml
- inventory-filter-by-status.yaml
release_date: '2022-06-13'
1.8.0:
modules:
- description: Create and manage cloud Primary IPs on the Hetzner Cloud.
name: hcloud_primary_ip
namespace: hetzner.hcloud
release_date: '2022-06-29'
1.8.1:
release_date: '2022-06-29'
1.8.2:
changes:
bugfixes:
- dynamic inventory - fix crash when having servers without IPs (flexible networks)
- hcloud_server - When state stopped and server is created, do not start the
server
- hcloud_server_info - fix crash when having servers without IPs (flexible networks)
fragments:
- flexible-networks-hcloud-server-info.yml
- inventory.yml
release_date: '2022-09-14'
1.9.0:
changes:
bugfixes:
- hcloud_firewall - the deletion could fail if the firewall was referenced right
before
- hcloud_server - fix backup window was given out as "None" instead of null
- hcloud_server_info - fix backup window was given out as "None" instead of
null
- hcloud_volume - fix server name was given out as "None" instead of null if
no server was attached
- hcloud_volume_info - fix server name was given out as "None" instead of null
if no server was attached
minor_changes:
- dynamic inventory - add support changing the name of the top level group all
servers are added to
- hcloud_firewall - add support for esp and gre protocols
fragments:
- hcloud_firewall-deletion.yml
- hcloud_firewall-esp-gre.yml
- hcloud_inventory.yml
- hcloud_server_backup_window.yml
- hcloud_volume_server_none.yml
release_date: '2022-11-10'
1.9.1:
changes:
bugfixes:
- hcloud_server - externally attached networks (using hcloud_server_network)
were removed when not specified in the hcloud_server resource
fragments:
- hcloud_server-removed-networks.yml
release_date: '2022-12-20'

View File

@@ -0,0 +1,28 @@
title: Hetzner Cloud Ansible Collection
changelog_filename_template: ../CHANGELOG.rst
changelog_filename_version_depth: 0
changes_file: changelog.yaml
changes_format: combined
keep_fragments: false
mention_ancestor: true
new_plugins_after_name: removed_features
notesdir: fragments
prelude_section_name: release_summary
prelude_section_title: Release Summary
sections:
- - major_changes
- Major Changes
- - minor_changes
- Minor Changes
- - breaking_changes
- Breaking Changes / Porting Guide
- - deprecated_features
- Deprecated Features
- - removed_features
- Removed Features (previously deprecated)
- - security_fixes
- Security Fixes
- - bugfixes
- Bugfixes
- - known_issues
- Known Issues

View File

@@ -0,0 +1,35 @@
requires_ansible: '>=2.9.10'
plugin_routing:
modules:
hcloud_location_facts:
deprecation:
removal_version: 2.0.0
warning_text: see plugin documentation for details
hcloud_server_type_facts:
deprecation:
removal_version: 2.0.0
warning_text: see plugin documentation for details
hcloud_image_facts:
deprecation:
removal_version: 2.0.0
warning_text: see plugin documentation for details
hcloud_volume_facts:
deprecation:
removal_version: 2.0.0
warning_text: see plugin documentation for details
hcloud_floating_ip_facts:
deprecation:
removal_version: 2.0.0
warning_text: see plugin documentation for details
hcloud_ssh_key_facts:
deprecation:
removal_version: 2.0.0
warning_text: see plugin documentation for details
hcloud_datacenter_facts:
deprecation:
removal_version: 2.0.0
warning_text: see plugin documentation for details
hcloud_server_facts:
deprecation:
removal_version: 2.0.0
warning_text: see plugin documentation for details

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = '''
options:
api_token:
description:
- This is the API Token for the Hetzner Cloud.
- You can also set this option by using the environment variable HCLOUD_TOKEN
required: True
type: str
endpoint:
description:
- This is the API Endpoint for the Hetzner Cloud.
default: https://api.hetzner.cloud/v1
type: str
requirements:
- hcloud-python >= 1.0.0
seealso:
- name: Documentation for Hetzner Cloud API
description: Complete reference for the Hetzner Cloud API.
link: https://docs.hetzner.cloud/
'''

View File

@@ -0,0 +1,298 @@
# Copyright (c) 2019 Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = r'''
name: hcloud
author:
- Lukas Kaemmerling (@lkaemmerling)
short_description: Ansible dynamic inventory plugin for the Hetzner Cloud.
requirements:
- python >= 2.7
- hcloud-python >= 1.0.0
description:
- Reads inventories from the Hetzner Cloud API.
- Uses a YAML configuration file that ends with hcloud.(yml|yaml).
extends_documentation_fragment:
- constructed
options:
plugin:
description: marks this as an instance of the "hcloud" plugin
required: true
choices: ["hcloud", "hetzner.hcloud.hcloud"]
token:
description: The Hetzner Cloud API Token.
required: false
group:
description: The group all servers are automatically added to.
default: hcloud
type: str
required: false
token_env:
description: Environment variable to load the Hetzner Cloud API Token from.
default: HCLOUD_TOKEN
type: str
required: false
connect_with:
description: Connect to the server using the value from this field.
default: public_ipv4
type: str
choices:
- public_ipv4
- hostname
- ipv4_dns_ptr
- private_ipv4
locations:
description: Populate inventory with instances in this location.
default: []
type: list
elements: str
required: false
types:
description: Populate inventory with instances with this type.
default: []
type: list
elements: str
required: false
images:
description: Populate inventory with instances with this image name, only available for system images.
default: []
type: list
elements: str
required: false
label_selector:
description: Populate inventory with instances with this label.
default: ""
type: str
required: false
network:
description: Populate inventory with instances which are attached to this network name or ID.
default: ""
type: str
required: false
status:
description: Populate inventory with instances with this status.
default: []
type: list
elements: str
required: false
'''
EXAMPLES = r"""
# Minimal example. `HCLOUD_TOKEN` is exposed in environment.
plugin: hcloud
# Example with locations, types, status and token
plugin: hcloud
token: foobar
locations:
- nbg1
types:
- cx11
status:
- running
# Group by a location with prefix e.g. "hcloud_location_nbg1"
# and image_os_flavor without prefix and separator e.g. "ubuntu"
# and status with prefix e.g. "server_status_running"
plugin: hcloud
keyed_groups:
- key: location
prefix: hcloud_location
- key: image_os_flavor
separator: ""
- key: status
prefix: server_status
"""
import os
from ansible.errors import AnsibleError
from ansible.module_utils._text import to_native
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
from ansible.release import __version__
try:
from hcloud import hcloud
from hcloud import APIException
HAS_HCLOUD = True
except ImportError:
HAS_HCLOUD = False
class InventoryModule(BaseInventoryPlugin, Constructable):
NAME = 'hetzner.hcloud.hcloud'
def _configure_hcloud_client(self):
self.token_env = self.get_option("token_env")
self.api_token = self.templar.template(self.get_option("token"), fail_on_undefined=False) or os.getenv(self.token_env)
if self.api_token is None:
raise AnsibleError(
"Please specify a token, via the option token, via environment variable HCLOUD_TOKEN "
"or via custom environment variable set by token_env option."
)
self.endpoint = os.getenv("HCLOUD_ENDPOINT") or "https://api.hetzner.cloud/v1"
self.client = hcloud.Client(token=self.api_token,
api_endpoint=self.endpoint,
application_name="ansible-inventory",
application_version=__version__)
def _test_hcloud_token(self):
try:
# We test the API Token against the location API, because this is the API with the smallest result
# and not controllable from the customer.
self.client.locations.get_all()
except APIException:
raise AnsibleError("Invalid Hetzner Cloud API Token.")
def _get_servers(self):
if len(self.get_option("label_selector")) > 0:
self.servers = self.client.servers.get_all(label_selector=self.get_option("label_selector"))
else:
self.servers = self.client.servers.get_all()
def _filter_servers(self):
if self.get_option("network"):
network = self.templar.template(self.get_option("network"), fail_on_undefined=False) or self.get_option("network")
try:
self.network = self.client.networks.get_by_name(network)
if self.network is None:
self.network = self.client.networks.get_by_id(network)
except APIException:
raise AnsibleError(
"The given network is not found.")
tmp = []
for server in self.servers:
for server_private_network in server.private_net:
if server_private_network.network.id == self.network.id:
tmp.append(server)
self.servers = tmp
if self.get_option("locations"):
tmp = []
for server in self.servers:
if server.datacenter.location.name in self.get_option("locations"):
tmp.append(server)
self.servers = tmp
if self.get_option("types"):
tmp = []
for server in self.servers:
if server.server_type.name in self.get_option("types"):
tmp.append(server)
self.servers = tmp
if self.get_option("images"):
tmp = []
for server in self.servers:
if server.image is not None and server.image.os_flavor in self.get_option("images"):
tmp.append(server)
self.servers = tmp
if self.get_option("status"):
tmp = []
for server in self.servers:
if server.status in self.get_option("status"):
tmp.append(server)
self.servers = tmp
def _set_server_attributes(self, server):
self.inventory.set_variable(server.name, "id", to_native(server.id))
self.inventory.set_variable(server.name, "name", to_native(server.name))
self.inventory.set_variable(server.name, "status", to_native(server.status))
self.inventory.set_variable(server.name, "type", to_native(server.server_type.name))
# Network
if server.public_net.ipv4:
self.inventory.set_variable(server.name, "ipv4", to_native(server.public_net.ipv4.ip))
if server.public_net.ipv6:
self.inventory.set_variable(server.name, "ipv6_network", to_native(server.public_net.ipv6.network))
self.inventory.set_variable(server.name, "ipv6_network_mask", to_native(server.public_net.ipv6.network_mask))
if self.get_option("network"):
for server_private_network in server.private_net:
if server_private_network.network.id == self.network.id:
self.inventory.set_variable(server.name, "private_ipv4", to_native(server_private_network.ip))
if self.get_option("connect_with") == "public_ipv4":
self.inventory.set_variable(server.name, "ansible_host", to_native(server.public_net.ipv4.ip))
elif self.get_option("connect_with") == "hostname":
self.inventory.set_variable(server.name, "ansible_host", to_native(server.name))
elif self.get_option("connect_with") == "ipv4_dns_ptr":
self.inventory.set_variable(server.name, "ansible_host", to_native(server.public_net.ipv4.dns_ptr))
elif self.get_option("connect_with") == "private_ipv4":
if self.get_option("network"):
for server_private_network in server.private_net:
if server_private_network.network.id == self.network.id:
self.inventory.set_variable(server.name, "ansible_host", to_native(server_private_network.ip))
else:
raise AnsibleError(
"You can only connect via private IPv4 if you specify a network")
# Server Type
if server.server_type is not None:
self.inventory.set_variable(server.name, "server_type", to_native(server.server_type.name))
# Datacenter
self.inventory.set_variable(server.name, "datacenter", to_native(server.datacenter.name))
self.inventory.set_variable(server.name, "location", to_native(server.datacenter.location.name))
# Image
if server.image is not None:
self.inventory.set_variable(server.name, "image_id", to_native(server.image.id))
self.inventory.set_variable(server.name, "image_os_flavor", to_native(server.image.os_flavor))
if server.image.name is not None:
self.inventory.set_variable(server.name, "image_name", to_native(server.image.name))
else:
self.inventory.set_variable(server.name, "image_name", to_native(server.image.description))
else:
self.inventory.set_variable(server.name, "image_id", to_native("No Image ID found"))
self.inventory.set_variable(server.name, "image_name", to_native("No Image Name found"))
self.inventory.set_variable(server.name, "image_os_flavor", to_native("No Image OS Flavor found"))
# Labels
self.inventory.set_variable(server.name, "labels", dict(server.labels))
def verify_file(self, path):
"""Return the possibly of a file being consumable by this plugin."""
return (
super(InventoryModule, self).verify_file(path) and
path.endswith(("hcloud.yaml", "hcloud.yml"))
)
def parse(self, inventory, loader, path, cache=True):
super(InventoryModule, self).parse(inventory, loader, path, cache)
if not HAS_HCLOUD:
raise AnsibleError("The Hetzner Cloud dynamic inventory plugin requires hcloud-python.")
self._read_config_data(path)
self._configure_hcloud_client()
self._test_hcloud_token()
self._get_servers()
self._filter_servers()
# Add a top group
self.inventory.add_group(group=self.get_option("group"))
for server in self.servers:
self.inventory.add_host(server.name, group=self.get_option("group"))
self._set_server_attributes(server)
# Use constructed if applicable
strict = self.get_option('strict')
# Composed variables
self._set_composite_vars(self.get_option('compose'), self.inventory.get_host(server.name).get_vars(), server.name, strict=strict)
# Complex groups based on jinja2 conditionals, hosts that meet the conditional are added to group
self._add_host_to_composed_groups(self.get_option('groups'), {}, server.name, strict=strict)
# Create groups based on variable values and add the corresponding hosts to it
self._add_host_to_keyed_groups(self.get_option('keyed_groups'), {}, server.name, strict=strict)

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.ansible_release import __version__
from ansible.module_utils.basic import env_fallback, missing_required_lib
try:
import hcloud
from hcloud import APIException
HAS_HCLOUD = True
except ImportError:
HAS_HCLOUD = False
class Hcloud(object):
def __init__(self, module, represent):
self.module = module
self.represent = represent
self.result = {"changed": False, self.represent: None}
if not HAS_HCLOUD:
module.fail_json(msg=missing_required_lib("hcloud-python"))
self._build_client()
def _build_client(self):
self.client = hcloud.Client(
token=self.module.params["api_token"],
api_endpoint=self.module.params["endpoint"],
application_name="ansible-module",
application_version=__version__,
)
def _mark_as_changed(self):
self.result["changed"] = True
@staticmethod
def base_module_arguments():
return {
"api_token": {
"type": "str",
"required": True,
"fallback": (env_fallback, ["HCLOUD_TOKEN"]),
"no_log": True,
},
"endpoint": {"type": "str", "default": "https://api.hetzner.cloud/v1"},
}
def _prepare_result(self):
"""Prepare the result for every module
:return: dict
"""
return {}
def get_result(self):
if getattr(self, self.represent) is not None:
self.result[self.represent] = self._prepare_result()
return self.result

View File

@@ -0,0 +1,298 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_certificate
short_description: Create and manage certificates on the Hetzner Cloud.
description:
- Create, update and manage certificates on the Hetzner Cloud.
author:
- Lukas Kaemmerling (@lkaemmerling)
options:
id:
description:
- The ID of the Hetzner Cloud certificate to manage.
- Only required if no certificate I(name) is given
type: int
name:
description:
- The Name of the Hetzner Cloud certificate to manage.
- Only required if no certificate I(id) is given or a certificate does not exist.
type: str
labels:
description:
- User-defined labels (key-value pairs)
type: dict
certificate:
description:
- Certificate and chain in PEM format, in order so that each record directly certifies the one preceding.
- Required if certificate does not exist.
type: str
private_key:
description:
- Certificate key in PEM format.
- Required if certificate does not exist.
type: str
domain_names:
description:
- Certificate key in PEM format.
- Required if certificate does not exist.
type: list
default: [ ]
elements: str
type:
description:
- Choose between uploading a Certificate in PEM format or requesting a managed Let's Encrypt Certificate.
default: uploaded
choices: [ uploaded, managed ]
type: str
state:
description:
- State of the certificate.
default: present
choices: [ absent, present ]
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a basic certificate
hcloud_certificate:
name: my-certificate
certificate: "ssh-rsa AAAjjk76kgf...Xt"
private_key: "ssh-rsa AAAjjk76kgf...Xt"
state: present
- name: Create a certificate with labels
hcloud_certificate:
name: my-certificate
certificate: "ssh-rsa AAAjjk76kgf...Xt"
private_key: "ssh-rsa AAAjjk76kgf...Xt"
labels:
key: value
mylabel: 123
state: present
- name: Ensure the certificate is absent (remove if needed)
hcloud_certificate:
name: my-certificate
state: absent
"""
RETURN = """
hcloud_certificate:
description: The certificate instance
returned: Always
type: complex
contains:
id:
description: Numeric identifier of the certificate
returned: always
type: int
sample: 1937415
name:
description: Name of the certificate
returned: always
type: str
sample: my website cert
fingerprint:
description: Fingerprint of the certificate
returned: always
type: str
sample: "03:c7:55:9b:2a:d1:04:17:09:f6:d0:7f:18:34:63:d4:3e:5f"
certificate:
description: Certificate and chain in PEM format
returned: always
type: str
sample: "-----BEGIN CERTIFICATE-----..."
domain_names:
description: List of Domains and Subdomains covered by the Certificate
returned: always
type: dict
not_valid_before:
description: Point in time when the Certificate becomes valid (in ISO-8601 format)
returned: always
type: str
not_valid_after:
description: Point in time when the Certificate stops being valid (in ISO-8601 format)
returned: always
type: str
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud.certificates.domain import Certificate
from hcloud.certificates.domain import Server
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudCertificate(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_certificate")
self.hcloud_certificate = None
def _prepare_result(self):
return {
"id": to_native(self.hcloud_certificate.id),
"name": to_native(self.hcloud_certificate.name),
"type": to_native(self.hcloud_certificate.type),
"fingerprint": to_native(self.hcloud_certificate.fingerprint),
"certificate": to_native(self.hcloud_certificate.certificate),
"not_valid_before": to_native(self.hcloud_certificate.not_valid_before),
"not_valid_after": to_native(self.hcloud_certificate.not_valid_after),
"domain_names": [to_native(domain) for domain in self.hcloud_certificate.domain_names],
"labels": self.hcloud_certificate.labels
}
def _get_certificate(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_certificate = self.client.certificates.get_by_id(
self.module.params.get("id")
)
elif self.module.params.get("name") is not None:
self.hcloud_certificate = self.client.certificates.get_by_name(
self.module.params.get("name")
)
except Exception as e:
self.module.fail_json(msg=e.message)
def _create_certificate(self):
self.module.fail_on_missing_params(
required_params=["name"]
)
params = {
"name": self.module.params.get("name"),
"labels": self.module.params.get("labels")
}
if self.module.params.get('type') == 'uploaded':
self.module.fail_on_missing_params(
required_params=["certificate", "private_key"]
)
params["certificate"] = self.module.params.get("certificate")
params["private_key"] = self.module.params.get("private_key")
if not self.module.check_mode:
try:
self.client.certificates.create(**params)
except Exception as e:
self.module.fail_json(msg=e.message)
else:
self.module.fail_on_missing_params(
required_params=["domain_names"]
)
params["domain_names"] = self.module.params.get("domain_names")
if not self.module.check_mode:
try:
resp = self.client.certificates.create_managed(**params)
resp.action.wait_until_finished(max_retries=1000)
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_certificate()
def _update_certificate(self):
try:
name = self.module.params.get("name")
if name is not None and self.hcloud_certificate.name != name:
self.module.fail_on_missing_params(
required_params=["id"]
)
if not self.module.check_mode:
self.hcloud_certificate.update(name=name)
self._mark_as_changed()
labels = self.module.params.get("labels")
if labels is not None and self.hcloud_certificate.labels != labels:
if not self.module.check_mode:
self.hcloud_certificate.update(labels=labels)
self._mark_as_changed()
except Exception as e:
self.module.fail_json(msg=e.message)
self._get_certificate()
def present_certificate(self):
self._get_certificate()
if self.hcloud_certificate is None:
self._create_certificate()
else:
self._update_certificate()
def delete_certificate(self):
self._get_certificate()
if self.hcloud_certificate is not None:
if not self.module.check_mode:
try:
self.client.certificates.delete(self.hcloud_certificate)
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self.hcloud_certificate = None
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
type={
"choices": ["uploaded", "managed"],
"default": "uploaded",
},
domain_names={"type": "list", "elements": "str", "default": []},
certificate={"type": "str"},
private_key={"type": "str", "no_log": True},
labels={"type": "dict"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
required_one_of=[['id', 'name']],
required_if=[['state', 'present', ['name']]],
supports_check_mode=True,
)
def main():
module = AnsibleHcloudCertificate.define_module()
hcloud = AnsibleHcloudCertificate(module)
state = module.params.get("state")
if state == "absent":
hcloud.delete_certificate()
elif state == "present":
hcloud.present_certificate()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,167 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_certificate_info
short_description: Gather infos about your Hetzner Cloud certificates.
description:
- Gather facts about your Hetzner Cloud certificates.
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the certificate you want to get.
type: int
name:
description:
- The name of the certificate you want to get.
type: str
label_selector:
description:
- The label selector for the certificate you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud certificate infos
hcloud_certificate_info:
register: output
- name: Print the gathered infos
debug:
var: output.hcloud_certificate_info
"""
RETURN = """
hcloud_certificate_info:
description: The certificate instances
returned: Always
type: complex
contains:
id:
description: Numeric identifier of the certificate
returned: always
type: int
sample: 1937415
name:
description: Name of the certificate
returned: always
type: str
sample: my website cert
fingerprint:
description: Fingerprint of the certificate
returned: always
type: str
sample: "03:c7:55:9b:2a:d1:04:17:09:f6:d0:7f:18:34:63:d4:3e:5f"
certificate:
description: Certificate and chain in PEM format
returned: always
type: str
sample: "-----BEGIN CERTIFICATE-----..."
domain_names:
description: List of Domains and Subdomains covered by the Certificate
returned: always
type: dict
not_valid_before:
description: Point in time when the Certificate becomes valid (in ISO-8601 format)
returned: always
type: str
not_valid_after:
description: Point in time when the Certificate stops being valid (in ISO-8601 format)
returned: always
type: str
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudCertificateInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_certificate_info")
self.hcloud_certificate_info = None
def _prepare_result(self):
certificates = []
for certificate in self.hcloud_certificate_info:
if certificate:
certificates.append({
"id": to_native(certificate.id),
"name": to_native(certificate.name),
"fingerprint": to_native(certificate.fingerprint),
"certificate": to_native(certificate.certificate),
"not_valid_before": to_native(certificate.not_valid_before),
"not_valid_after": to_native(certificate.not_valid_after),
"domain_names": [to_native(domain) for domain in certificate.domain_names],
"labels": certificate.labels
})
return certificates
def get_certificates(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_certificate_info = [self.client.certificates.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_certificate_info = [self.client.certificates.get_by_name(
self.module.params.get("name")
)]
elif self.module.params.get("label_selector") is not None:
self.hcloud_certificate_info = self.client.certificates.get_all(
label_selector=self.module.params.get("label_selector"))
else:
self.hcloud_certificate_info = self.client.certificates.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
label_selector={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudCertificateInfo.define_module()
hcloud = AnsibleHcloudCertificateInfo(module)
hcloud.get_certificates()
result = hcloud.get_result()
ansible_info = {
'hcloud_certificate_info': result['hcloud_certificate_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,165 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_datacenter_info
short_description: Gather info about the Hetzner Cloud datacenters.
description:
- Gather info about your Hetzner Cloud datacenters.
- This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts).
Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)!
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the datacenter you want to get.
type: int
name:
description:
- The name of the datacenter you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud datacenter info
hcloud_datacenter_info:
register: output
- name: Print the gathered info
debug:
var: output
"""
RETURN = """
hcloud_datacenter_info:
description:
- The datacenter info as list
- This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts).
Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)!
returned: always
type: complex
contains:
id:
description: Numeric identifier of the datacenter
returned: always
type: int
sample: 1937415
name:
description: Name of the datacenter
returned: always
type: str
sample: fsn1-dc8
description:
description: Detail description of the datacenter
returned: always
type: str
sample: Falkenstein DC 8
location:
description: Name of the location where the datacenter resides in
returned: always
type: str
sample: fsn1
city:
description: City of the location
returned: always
type: str
sample: fsn1
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudDatacenterInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_datacenter_info")
self.hcloud_datacenter_info = None
def _prepare_result(self):
tmp = []
for datacenter in self.hcloud_datacenter_info:
if datacenter is not None:
tmp.append({
"id": to_native(datacenter.id),
"name": to_native(datacenter.name),
"description": to_native(datacenter.description),
"location": to_native(datacenter.location.name)
})
return tmp
def get_datacenters(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_datacenter_info = [self.client.datacenters.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_datacenter_info = [self.client.datacenters.get_by_name(
self.module.params.get("name")
)]
else:
self.hcloud_datacenter_info = self.client.datacenters.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudDatacenterInfo.define_module()
is_old_facts = module._name == 'hcloud_datacenter_facts'
if is_old_facts:
module.deprecate("The 'hcloud_datacenter_facts' module has been renamed to 'hcloud_datacenter_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudDatacenterInfo(module)
hcloud.get_datacenters()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_datacenter_facts': result['hcloud_datacenter_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_datacenter_info': result['hcloud_datacenter_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,165 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_datacenter_info
short_description: Gather info about the Hetzner Cloud datacenters.
description:
- Gather info about your Hetzner Cloud datacenters.
- This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts).
Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)!
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the datacenter you want to get.
type: int
name:
description:
- The name of the datacenter you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud datacenter info
hcloud_datacenter_info:
register: output
- name: Print the gathered info
debug:
var: output
"""
RETURN = """
hcloud_datacenter_info:
description:
- The datacenter info as list
- This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts).
Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)!
returned: always
type: complex
contains:
id:
description: Numeric identifier of the datacenter
returned: always
type: int
sample: 1937415
name:
description: Name of the datacenter
returned: always
type: str
sample: fsn1-dc8
description:
description: Detail description of the datacenter
returned: always
type: str
sample: Falkenstein DC 8
location:
description: Name of the location where the datacenter resides in
returned: always
type: str
sample: fsn1
city:
description: City of the location
returned: always
type: str
sample: fsn1
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudDatacenterInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_datacenter_info")
self.hcloud_datacenter_info = None
def _prepare_result(self):
tmp = []
for datacenter in self.hcloud_datacenter_info:
if datacenter is not None:
tmp.append({
"id": to_native(datacenter.id),
"name": to_native(datacenter.name),
"description": to_native(datacenter.description),
"location": to_native(datacenter.location.name)
})
return tmp
def get_datacenters(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_datacenter_info = [self.client.datacenters.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_datacenter_info = [self.client.datacenters.get_by_name(
self.module.params.get("name")
)]
else:
self.hcloud_datacenter_info = self.client.datacenters.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudDatacenterInfo.define_module()
is_old_facts = module._name == 'hcloud_datacenter_facts'
if is_old_facts:
module.deprecate("The 'hcloud_datacenter_facts' module has been renamed to 'hcloud_datacenter_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudDatacenterInfo(module)
hcloud.get_datacenters()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_datacenter_facts': result['hcloud_datacenter_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_datacenter_info': result['hcloud_datacenter_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,359 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_firewall
short_description: Create and manage firewalls on the Hetzner Cloud.
description:
- Create, update and manage firewalls on the Hetzner Cloud.
author:
- Lukas Kaemmerling (@lkaemmerling)
options:
id:
description:
- The ID of the Hetzner Cloud firewall to manage.
- Only required if no firewall I(name) is given
type: int
name:
description:
- The Name of the Hetzner Cloud firewall to manage.
- Only required if no firewall I(id) is given, or a firewall does not exist.
type: str
labels:
description:
- User-defined labels (key-value pairs)
type: dict
rules:
description:
- List of rules the firewall should contain.
type: list
elements: dict
suboptions:
direction:
description:
- The direction of the firewall rule.
type: str
choices: [ in, out ]
port:
description:
- The port of the firewall rule.
type: str
protocol:
description:
- The protocol of the firewall rule.
type: str
choices: [ icmp, tcp, udp, esp, gre ]
source_ips:
description:
- List of CIDRs that are allowed within this rule
type: list
elements: str
default: [ ]
destination_ips:
description:
- List of CIDRs that are allowed within this rule
type: list
elements: str
default: [ ]
description:
description:
- User defined description of this rule.
type: str
state:
description:
- State of the firewall.
default: present
choices: [ absent, present ]
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a basic firewall
hcloud_firewall:
name: my-firewall
state: present
- name: Create a firewall with rules
hcloud_firewall:
name: my-firewall
rules:
- direction: in
protocol: icmp
source_ips:
- 0.0.0.0/0
- ::/0
description: allow icmp in
state: present
- name: Create a firewall with labels
hcloud_firewall:
name: my-firewall
labels:
key: value
mylabel: 123
state: present
- name: Ensure the firewall is absent (remove if needed)
hcloud_firewall:
name: my-firewall
state: absent
"""
RETURN = """
hcloud_firewall:
description: The firewall instance
returned: Always
type: complex
contains:
id:
description: Numeric identifier of the firewall
returned: always
type: int
sample: 1937415
name:
description: Name of the firewall
returned: always
type: str
sample: my firewall
rules:
description: List of Rules within this Firewall
returned: always
type: complex
contains:
direction:
description: Direction of the Firewall Rule
type: str
returned: always
sample: in
protocol:
description: Protocol of the Firewall Rule
type: str
returned: always
sample: icmp
port:
description: Port of the Firewall Rule, None/Null if protocol is icmp
type: str
returned: always
sample: in
source_ips:
description: Source IPs of the Firewall
type: list
elements: str
returned: always
destination_ips:
description: Source IPs of the Firewall
type: list
elements: str
returned: always
description:
description: User defined description of the Firewall Rule
type: str
returned: always
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
import time
try:
from hcloud.firewalls.domain import FirewallRule
from hcloud import APIException
except ImportError:
APIException = None
FirewallRule = None
class AnsibleHcloudFirewall(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_firewall")
self.hcloud_firewall = None
def _prepare_result(self):
return {
"id": to_native(self.hcloud_firewall.id),
"name": to_native(self.hcloud_firewall.name),
"rules": [self._prepare_result_rule(rule) for rule in self.hcloud_firewall.rules],
"labels": self.hcloud_firewall.labels
}
def _prepare_result_rule(self, rule):
return {
"direction": rule.direction,
"protocol": to_native(rule.protocol),
"port": to_native(rule.port) if rule.port is not None else None,
"source_ips": [to_native(cidr) for cidr in rule.source_ips],
"destination_ips": [to_native(cidr) for cidr in rule.destination_ips],
"description": to_native(rule.description) if rule.description is not None else None,
}
def _get_firewall(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_firewall = self.client.firewalls.get_by_id(
self.module.params.get("id")
)
elif self.module.params.get("name") is not None:
self.hcloud_firewall = self.client.firewalls.get_by_name(
self.module.params.get("name")
)
except Exception as e:
self.module.fail_json(msg=e.message)
def _create_firewall(self):
self.module.fail_on_missing_params(
required_params=["name"]
)
params = {
"name": self.module.params.get("name"),
"labels": self.module.params.get("labels")
}
rules = self.module.params.get("rules")
if rules is not None:
params["rules"] = [
FirewallRule(
direction=rule["direction"],
protocol=rule["protocol"],
source_ips=rule["source_ips"] if rule["source_ips"] is not None else [],
destination_ips=rule["destination_ips"] if rule["destination_ips"] is not None else [],
port=rule["port"],
description=rule["description"],
)
for rule in rules
]
if not self.module.check_mode:
try:
self.client.firewalls.create(**params)
except Exception as e:
self.module.fail_json(msg=e.message, **params)
self._mark_as_changed()
self._get_firewall()
def _update_firewall(self):
name = self.module.params.get("name")
if name is not None and self.hcloud_firewall.name != name:
self.module.fail_on_missing_params(
required_params=["id"]
)
if not self.module.check_mode:
self.hcloud_firewall.update(name=name)
self._mark_as_changed()
labels = self.module.params.get("labels")
if labels is not None and self.hcloud_firewall.labels != labels:
if not self.module.check_mode:
self.hcloud_firewall.update(labels=labels)
self._mark_as_changed()
rules = self.module.params.get("rules")
if rules is not None and rules != [self._prepare_result_rule(rule) for rule in self.hcloud_firewall.rules]:
if not self.module.check_mode:
new_rules = [
FirewallRule(
direction=rule["direction"],
protocol=rule["protocol"],
source_ips=rule["source_ips"] if rule["source_ips"] is not None else [],
destination_ips=rule["destination_ips"] if rule["destination_ips"] is not None else [],
port=rule["port"],
description=rule["description"],
)
for rule in rules
]
self.hcloud_firewall.set_rules(new_rules)
self._mark_as_changed()
self._get_firewall()
def present_firewall(self):
self._get_firewall()
if self.hcloud_firewall is None:
self._create_firewall()
else:
self._update_firewall()
def delete_firewall(self):
self._get_firewall()
if self.hcloud_firewall is not None:
if not self.module.check_mode:
retry_count = 0
while retry_count < 10:
try:
self.client.firewalls.delete(self.hcloud_firewall)
break
except APIException as e:
if "is still in use" in e.message:
retry_count = retry_count + 1
time.sleep(0.5 * retry_count)
else:
self.module.fail_json(msg=e.message)
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self.hcloud_firewall = None
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
rules=dict(
type="list",
elements="dict",
options=dict(
direction={"type": "str", "choices": ["in", "out"]},
protocol={"type": "str", "choices": ["icmp", "udp", "tcp", "esp", "gre"]},
port={"type": "str"},
source_ips={"type": "list", "elements": "str", "default": []},
destination_ips={"type": "list", "elements": "str", "default": []},
description={"type": "str"},
),
required_together=[["direction", "protocol"]],
),
labels={"type": "dict"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
required_one_of=[['id', 'name']],
required_if=[['state', 'present', ['name']]],
supports_check_mode=True,
)
def main():
module = AnsibleHcloudFirewall.define_module()
hcloud = AnsibleHcloudFirewall(module)
state = module.params.get("state")
if state == "absent":
hcloud.delete_firewall()
elif state == "present":
hcloud.present_firewall()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,360 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_floating_ip
short_description: Create and manage cloud Floating IPs on the Hetzner Cloud.
description:
- Create, update and manage cloud Floating IPs on the Hetzner Cloud.
author:
- Lukas Kaemmerling (@lkaemmerling)
version_added: 0.1.0
options:
id:
description:
- The ID of the Hetzner Cloud Floating IPs to manage.
- Only required if no Floating IP I(name) is given.
type: int
name:
description:
- The Name of the Hetzner Cloud Floating IPs to manage.
- Only required if no Floating IP I(id) is given or a Floating IP does not exist.
type: str
description:
description:
- The Description of the Hetzner Cloud Floating IPs.
type: str
home_location:
description:
- Home Location of the Hetzner Cloud Floating IP.
- Required if no I(server) is given and Floating IP does not exist.
type: str
server:
description:
- Server Name the Floating IP should be assigned to.
- Required if no I(home_location) is given and Floating IP does not exist.
type: str
type:
description:
- Type of the Floating IP.
- Required if Floating IP does not exist
choices: [ ipv4, ipv6 ]
type: str
force:
description:
- Force the assignment or deletion of the Floating IP.
type: bool
delete_protection:
description:
- Protect the Floating IP for deletion.
type: bool
labels:
description:
- User-defined labels (key-value pairs).
type: dict
state:
description:
- State of the Floating IP.
default: present
choices: [ absent, present ]
type: str
requirements:
- hcloud-python >= 1.6.0
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a basic IPv4 Floating IP
hcloud_floating_ip:
name: my-floating-ip
home_location: fsn1
type: ipv4
state: present
- name: Create a basic IPv6 Floating IP
hcloud_floating_ip:
name: my-floating-ip
home_location: fsn1
type: ipv6
state: present
- name: Assign a Floating IP to a server
hcloud_floating_ip:
name: my-floating-ip
server: 1234
state: present
- name: Assign a Floating IP to another server
hcloud_floating_ip:
name: my-floating-ip
server: 1234
force: yes
state: present
- name: Floating IP should be absent
hcloud_floating_ip:
name: my-floating-ip
state: absent
"""
RETURN = """
hcloud_floating_ip:
description: The Floating IP instance
returned: Always
type: complex
contains:
id:
description: ID of the Floating IP
type: int
returned: Always
sample: 12345
name:
description: Name of the Floating IP
type: str
returned: Always
sample: my-floating-ip
description:
description: Description of the Floating IP
type: str
returned: Always
sample: my-floating-ip
ip:
description: IP Address of the Floating IP
type: str
returned: Always
sample: 116.203.104.109
type:
description: Type of the Floating IP
type: str
returned: Always
sample: ipv4
home_location:
description: Name of the home location of the Floating IP
type: str
returned: Always
sample: fsn1
server:
description: Name of the server the Floating IP is assigned to.
type: str
returned: Always
sample: "my-server"
delete_protection:
description: True if Floating IP is protected for deletion
type: bool
returned: always
sample: false
version_added: "0.1.0"
labels:
description: User-defined labels (key-value pairs)
type: dict
returned: Always
sample:
key: value
mylabel: 123
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudFloatingIP(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_floating_ip")
self.hcloud_floating_ip = None
def _prepare_result(self):
server = None
if self.hcloud_floating_ip.server is not None:
server = to_native(self.hcloud_floating_ip.server.name)
return {
"id": to_native(self.hcloud_floating_ip.id),
"name": to_native(self.hcloud_floating_ip.name),
"description": to_native(self.hcloud_floating_ip.description),
"ip": to_native(self.hcloud_floating_ip.ip),
"type": to_native(self.hcloud_floating_ip.type),
"home_location": to_native(self.hcloud_floating_ip.home_location.name),
"labels": self.hcloud_floating_ip.labels,
"server": server,
"delete_protection": self.hcloud_floating_ip.protection["delete"],
}
def _get_floating_ip(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_floating_ip = self.client.floating_ips.get_by_id(
self.module.params.get("id")
)
else:
self.hcloud_floating_ip = self.client.floating_ips.get_by_name(
self.module.params.get("name")
)
except Exception as e:
self.module.fail_json(msg=e.message)
def _create_floating_ip(self):
self.module.fail_on_missing_params(
required_params=["type"]
)
try:
params = {
"description": self.module.params.get("description"),
"type": self.module.params.get("type"),
"name": self.module.params.get("name"),
}
if self.module.params.get("home_location") is not None:
params["home_location"] = self.client.locations.get_by_name(
self.module.params.get("home_location")
)
elif self.module.params.get("server") is not None:
params["server"] = self.client.servers.get_by_name(
self.module.params.get("server")
)
else:
self.module.fail_json(msg="one of the following is required: home_location, server")
if self.module.params.get("labels") is not None:
params["labels"] = self.module.params.get("labels")
if not self.module.check_mode:
resp = self.client.floating_ips.create(**params)
self.hcloud_floating_ip = resp.floating_ip
delete_protection = self.module.params.get("delete_protection")
if delete_protection is not None:
self.hcloud_floating_ip.change_protection(delete=delete_protection).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_floating_ip()
def _update_floating_ip(self):
try:
labels = self.module.params.get("labels")
if labels is not None and labels != self.hcloud_floating_ip.labels:
if not self.module.check_mode:
self.hcloud_floating_ip.update(labels=labels)
self._mark_as_changed()
description = self.module.params.get("description")
if description is not None and description != self.hcloud_floating_ip.description:
if not self.module.check_mode:
self.hcloud_floating_ip.update(description=description)
self._mark_as_changed()
server = self.module.params.get("server")
if server is not None and self.hcloud_floating_ip.server is not None:
if self.module.params.get("force") and server != self.hcloud_floating_ip.server.name:
if not self.module.check_mode:
self.hcloud_floating_ip.assign(
self.client.servers.get_by_name(server)
)
self._mark_as_changed()
elif server != self.hcloud_floating_ip.server.name:
self.module.warn(
"Floating IP is already assigned to another server %s. You need to unassign the Floating IP or use force=yes."
% self.hcloud_floating_ip.server.name
)
self._mark_as_changed()
elif server is not None and self.hcloud_floating_ip.server is None:
if not self.module.check_mode:
self.hcloud_floating_ip.assign(
self.client.servers.get_by_name(server)
)
self._mark_as_changed()
elif server is None and self.hcloud_floating_ip.server is not None:
if not self.module.check_mode:
self.hcloud_floating_ip.unassign()
self._mark_as_changed()
delete_protection = self.module.params.get("delete_protection")
if delete_protection is not None and delete_protection != self.hcloud_floating_ip.protection["delete"]:
if not self.module.check_mode:
self.hcloud_floating_ip.change_protection(delete=delete_protection).wait_until_finished()
self._mark_as_changed()
self._get_floating_ip()
except Exception as e:
self.module.fail_json(msg=e.message)
def present_floating_ip(self):
self._get_floating_ip()
if self.hcloud_floating_ip is None:
self._create_floating_ip()
else:
self._update_floating_ip()
def delete_floating_ip(self):
try:
self._get_floating_ip()
if self.hcloud_floating_ip is not None:
if self.module.params.get("force") or self.hcloud_floating_ip.server is None:
if not self.module.check_mode:
self.client.floating_ips.delete(self.hcloud_floating_ip)
else:
self.module.warn(
"Floating IP is currently assigned to server %s. You need to unassign the Floating IP or use force=yes."
% self.hcloud_floating_ip.server.name
)
self._mark_as_changed()
self.hcloud_floating_ip = None
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
description={"type": "str"},
server={"type": "str"},
home_location={"type": "str"},
force={"type": "bool"},
type={"choices": ["ipv4", "ipv6"]},
labels={"type": "dict"},
delete_protection={"type": "bool"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
required_one_of=[['id', 'name']],
mutually_exclusive=[['home_location', 'server']],
supports_check_mode=True,
)
def main():
module = AnsibleHcloudFloatingIP.define_module()
hcloud = AnsibleHcloudFloatingIP(module)
state = module.params["state"]
if state == "absent":
hcloud.delete_floating_ip()
elif state == "present":
hcloud.present_floating_ip()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,190 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_floating_ip_info
short_description: Gather infos about the Hetzner Cloud Floating IPs.
description:
- Gather facts about your Hetzner Cloud Floating IPs.
- This module was called C(hcloud_floating_ip_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_floating_ip_facts).
Note that the M(hetzner.hcloud.hcloud_floating_ip_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_floating_ip_info)!
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the Floating IP you want to get.
type: int
label_selector:
description:
- The label selector for the Floating IP you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud Floating ip infos
hcloud_floating_ip_info:
register: output
- name: Print the gathered infos
debug:
var: output
"""
RETURN = """
hcloud_floating_ip_info:
description: The Floating ip infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the Floating IP
returned: always
type: int
sample: 1937415
name:
description: Name of the Floating IP
returned: Always
type: str
sample: my-floating-ip
version_added: "0.1.0"
description:
description: Description of the Floating IP
returned: always
type: str
sample: Falkenstein DC 8
ip:
description: IP address of the Floating IP
returned: always
type: str
sample: 131.232.99.1
type:
description: Type of the Floating IP
returned: always
type: str
sample: ipv4
server:
description: Name of the server where the Floating IP is assigned to.
returned: always
type: str
sample: my-server
home_location:
description: Location the Floating IP was created in
returned: always
type: str
sample: fsn1
delete_protection:
description: True if the Floating IP is protected for deletion
returned: always
type: bool
version_added: "0.1.0"
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudFloatingIPInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_floating_ip_info")
self.hcloud_floating_ip_info = None
def _prepare_result(self):
tmp = []
for floating_ip in self.hcloud_floating_ip_info:
if floating_ip is not None:
server_name = None
if floating_ip.server is not None:
server_name = floating_ip.server.name
tmp.append({
"id": to_native(floating_ip.id),
"name": to_native(floating_ip.name),
"description": to_native(floating_ip.description),
"ip": to_native(floating_ip.ip),
"type": to_native(floating_ip.type),
"server": to_native(server_name),
"home_location": to_native(floating_ip.home_location.name),
"labels": floating_ip.labels,
"delete_protection": floating_ip.protection["delete"],
})
return tmp
def get_floating_ips(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_floating_ip_info = [self.client.floating_ips.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("label_selector") is not None:
self.hcloud_floating_ip_info = self.client.floating_ips.get_all(
label_selector=self.module.params.get("label_selector"))
else:
self.hcloud_floating_ip_info = self.client.floating_ips.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
label_selector={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudFloatingIPInfo.define_module()
is_old_facts = module._name == 'hcloud_floating_ip_facts'
if is_old_facts:
module.deprecate("The 'hcloud_floating_ip_facts' module has been renamed to 'hcloud_floating_ip_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudFloatingIPInfo(module)
hcloud.get_floating_ips()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_floating_ip_facts': result['hcloud_floating_ip_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_floating_ip_info': result['hcloud_floating_ip_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,190 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_floating_ip_info
short_description: Gather infos about the Hetzner Cloud Floating IPs.
description:
- Gather facts about your Hetzner Cloud Floating IPs.
- This module was called C(hcloud_floating_ip_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_floating_ip_facts).
Note that the M(hetzner.hcloud.hcloud_floating_ip_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_floating_ip_info)!
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the Floating IP you want to get.
type: int
label_selector:
description:
- The label selector for the Floating IP you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud Floating ip infos
hcloud_floating_ip_info:
register: output
- name: Print the gathered infos
debug:
var: output
"""
RETURN = """
hcloud_floating_ip_info:
description: The Floating ip infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the Floating IP
returned: always
type: int
sample: 1937415
name:
description: Name of the Floating IP
returned: Always
type: str
sample: my-floating-ip
version_added: "0.1.0"
description:
description: Description of the Floating IP
returned: always
type: str
sample: Falkenstein DC 8
ip:
description: IP address of the Floating IP
returned: always
type: str
sample: 131.232.99.1
type:
description: Type of the Floating IP
returned: always
type: str
sample: ipv4
server:
description: Name of the server where the Floating IP is assigned to.
returned: always
type: str
sample: my-server
home_location:
description: Location the Floating IP was created in
returned: always
type: str
sample: fsn1
delete_protection:
description: True if the Floating IP is protected for deletion
returned: always
type: bool
version_added: "0.1.0"
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudFloatingIPInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_floating_ip_info")
self.hcloud_floating_ip_info = None
def _prepare_result(self):
tmp = []
for floating_ip in self.hcloud_floating_ip_info:
if floating_ip is not None:
server_name = None
if floating_ip.server is not None:
server_name = floating_ip.server.name
tmp.append({
"id": to_native(floating_ip.id),
"name": to_native(floating_ip.name),
"description": to_native(floating_ip.description),
"ip": to_native(floating_ip.ip),
"type": to_native(floating_ip.type),
"server": to_native(server_name),
"home_location": to_native(floating_ip.home_location.name),
"labels": floating_ip.labels,
"delete_protection": floating_ip.protection["delete"],
})
return tmp
def get_floating_ips(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_floating_ip_info = [self.client.floating_ips.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("label_selector") is not None:
self.hcloud_floating_ip_info = self.client.floating_ips.get_all(
label_selector=self.module.params.get("label_selector"))
else:
self.hcloud_floating_ip_info = self.client.floating_ips.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
label_selector={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudFloatingIPInfo.define_module()
is_old_facts = module._name == 'hcloud_floating_ip_facts'
if is_old_facts:
module.deprecate("The 'hcloud_floating_ip_facts' module has been renamed to 'hcloud_floating_ip_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudFloatingIPInfo(module)
hcloud.get_floating_ips()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_floating_ip_facts': result['hcloud_floating_ip_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_floating_ip_info': result['hcloud_floating_ip_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,203 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_image_info
short_description: Gather infos about your Hetzner Cloud images.
description:
- Gather infos about your Hetzner Cloud images.
- This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts).
Note that the M(hetzner.hcloud.hcloud_image_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_image_info)!
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the image you want to get.
type: int
name:
description:
- The name of the image you want to get.
type: str
label_selector:
description:
- The label selector for the images you want to get.
type: str
type:
description:
- The label selector for the images you want to get.
default: system
choices: [ system, snapshot, backup ]
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud image infos
hcloud_image_info:
register: output
- name: Print the gathered infos
debug:
var: output
"""
RETURN = """
hcloud_image_info:
description: The image infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the image
returned: always
type: int
sample: 1937415
type:
description: Type of the image
returned: always
type: str
sample: system
status:
description: Status of the image
returned: always
type: str
sample: available
name:
description: Name of the image
returned: always
type: str
sample: ubuntu-18.04
description:
description: Detail description of the image
returned: always
type: str
sample: Ubuntu 18.04 Standard 64 bit
os_flavor:
description: OS flavor of the image
returned: always
type: str
sample: ubuntu
os_version:
description: OS version of the image
returned: always
type: str
sample: 18.04
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudImageInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_image_info")
self.hcloud_image_info = None
def _prepare_result(self):
tmp = []
for image in self.hcloud_image_info:
if image is not None:
tmp.append({
"id": to_native(image.id),
"status": to_native(image.status),
"type": to_native(image.type),
"name": to_native(image.name),
"description": to_native(image.description),
"os_flavor": to_native(image.os_flavor),
"os_version": to_native(image.os_version),
"labels": image.labels,
})
return tmp
def get_images(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_image_info = [self.client.images.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_image_info = [self.client.images.get_by_name(
self.module.params.get("name")
)]
else:
params = {}
label_selector = self.module.params.get("label_selector")
if label_selector:
params["label_selector"] = label_selector
image_type = self.module.params.get("type")
if image_type:
params["type"] = image_type
self.hcloud_image_info = self.client.images.get_all(**params)
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
label_selector={"type": "str"},
type={"choices": ["system", "snapshot", "backup"], "default": "system", "type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudImageInfo.define_module()
is_old_facts = module._name == 'hcloud_image_facts'
if is_old_facts:
module.deprecate("The 'hcloud_image_facts' module has been renamed to 'hcloud_image_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudImageInfo(module)
hcloud.get_images()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_imagen_facts': result['hcloud_image_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_image_info': result['hcloud_image_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,203 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_image_info
short_description: Gather infos about your Hetzner Cloud images.
description:
- Gather infos about your Hetzner Cloud images.
- This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts).
Note that the M(hetzner.hcloud.hcloud_image_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_image_info)!
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the image you want to get.
type: int
name:
description:
- The name of the image you want to get.
type: str
label_selector:
description:
- The label selector for the images you want to get.
type: str
type:
description:
- The label selector for the images you want to get.
default: system
choices: [ system, snapshot, backup ]
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud image infos
hcloud_image_info:
register: output
- name: Print the gathered infos
debug:
var: output
"""
RETURN = """
hcloud_image_info:
description: The image infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the image
returned: always
type: int
sample: 1937415
type:
description: Type of the image
returned: always
type: str
sample: system
status:
description: Status of the image
returned: always
type: str
sample: available
name:
description: Name of the image
returned: always
type: str
sample: ubuntu-18.04
description:
description: Detail description of the image
returned: always
type: str
sample: Ubuntu 18.04 Standard 64 bit
os_flavor:
description: OS flavor of the image
returned: always
type: str
sample: ubuntu
os_version:
description: OS version of the image
returned: always
type: str
sample: 18.04
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudImageInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_image_info")
self.hcloud_image_info = None
def _prepare_result(self):
tmp = []
for image in self.hcloud_image_info:
if image is not None:
tmp.append({
"id": to_native(image.id),
"status": to_native(image.status),
"type": to_native(image.type),
"name": to_native(image.name),
"description": to_native(image.description),
"os_flavor": to_native(image.os_flavor),
"os_version": to_native(image.os_version),
"labels": image.labels,
})
return tmp
def get_images(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_image_info = [self.client.images.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_image_info = [self.client.images.get_by_name(
self.module.params.get("name")
)]
else:
params = {}
label_selector = self.module.params.get("label_selector")
if label_selector:
params["label_selector"] = label_selector
image_type = self.module.params.get("type")
if image_type:
params["type"] = image_type
self.hcloud_image_info = self.client.images.get_all(**params)
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
label_selector={"type": "str"},
type={"choices": ["system", "snapshot", "backup"], "default": "system", "type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudImageInfo.define_module()
is_old_facts = module._name == 'hcloud_image_facts'
if is_old_facts:
module.deprecate("The 'hcloud_image_facts' module has been renamed to 'hcloud_image_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudImageInfo(module)
hcloud.get_images()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_imagen_facts': result['hcloud_image_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_image_info': result['hcloud_image_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,324 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_load_balancer
short_description: Create and manage cloud Load Balancers on the Hetzner Cloud.
description:
- Create, update and manage cloud Load Balancers on the Hetzner Cloud.
author:
- Lukas Kaemmerling (@LKaemmerling)
version_added: 0.1.0
options:
id:
description:
- The ID of the Hetzner Cloud Load Balancer to manage.
- Only required if no Load Balancer I(name) is given
type: int
name:
description:
- The Name of the Hetzner Cloud Load Balancer to manage.
- Only required if no Load Balancer I(id) is given or a Load Balancer does not exist.
type: str
load_balancer_type:
description:
- The Load Balancer Type of the Hetzner Cloud Load Balancer to manage.
- Required if Load Balancer does not exist.
type: str
location:
description:
- Location of Load Balancer.
- Required if no I(network_zone) is given and Load Balancer does not exist.
type: str
network_zone:
description:
- Network Zone of Load Balancer.
- Required of no I(location) is given and Load Balancer does not exist.
type: str
labels:
description:
- User-defined labels (key-value pairs).
type: dict
disable_public_interface:
description:
- Disables the public interface.
type: bool
default: False
delete_protection:
description:
- Protect the Load Balancer for deletion.
type: bool
state:
description:
- State of the Load Balancer.
default: present
choices: [ absent, present ]
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
requirements:
- hcloud-python >= 1.8.0
'''
EXAMPLES = """
- name: Create a basic Load Balancer
hcloud_load_balancer:
name: my-Load Balancer
load_balancer_type: lb11
location: fsn1
state: present
- name: Ensure the Load Balancer is absent (remove if needed)
hcloud_load_balancer:
name: my-Load Balancer
state: absent
"""
RETURN = """
hcloud_load_balancer:
description: The Load Balancer instance
returned: Always
type: complex
contains:
id:
description: Numeric identifier of the Load Balancer
returned: always
type: int
sample: 1937415
name:
description: Name of the Load Balancer
returned: always
type: str
sample: my-Load-Balancer
status:
description: Status of the Load Balancer
returned: always
type: str
sample: running
load_balancer_type:
description: Name of the Load Balancer type of the Load Balancer
returned: always
type: str
sample: cx11
ipv4_address:
description: Public IPv4 address of the Load Balancer
returned: always
type: str
sample: 116.203.104.109
ipv6_address:
description: Public IPv6 address of the Load Balancer
returned: always
type: str
sample: 2a01:4f8:1c1c:c140::1
location:
description: Name of the location of the Load Balancer
returned: always
type: str
sample: fsn1
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
delete_protection:
description: True if Load Balancer is protected for deletion
type: bool
returned: always
sample: false
disable_public_interface:
description: True if Load Balancer public interface is disabled
type: bool
returned: always
sample: false
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud.load_balancers.domain import LoadBalancer
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudLoadBalancer(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_load_balancer")
self.hcloud_load_balancer = None
def _prepare_result(self):
private_ipv4_address = None if len(self.hcloud_load_balancer.private_net) == 0 else to_native(
self.hcloud_load_balancer.private_net[0].ip)
return {
"id": to_native(self.hcloud_load_balancer.id),
"name": to_native(self.hcloud_load_balancer.name),
"ipv4_address": to_native(self.hcloud_load_balancer.public_net.ipv4.ip),
"ipv6_address": to_native(self.hcloud_load_balancer.public_net.ipv6.ip),
"private_ipv4_address": private_ipv4_address,
"load_balancer_type": to_native(self.hcloud_load_balancer.load_balancer_type.name),
"location": to_native(self.hcloud_load_balancer.location.name),
"labels": self.hcloud_load_balancer.labels,
"delete_protection": self.hcloud_load_balancer.protection["delete"],
"disable_public_interface": False if self.hcloud_load_balancer.public_net.enabled else True,
}
def _get_load_balancer(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_load_balancer = self.client.load_balancers.get_by_id(
self.module.params.get("id")
)
else:
self.hcloud_load_balancer = self.client.load_balancers.get_by_name(
self.module.params.get("name")
)
except Exception as e:
self.module.fail_json(msg=e.message)
def _create_load_balancer(self):
self.module.fail_on_missing_params(
required_params=["name", "load_balancer_type"]
)
try:
params = {
"name": self.module.params.get("name"),
"load_balancer_type": self.client.load_balancer_types.get_by_name(
self.module.params.get("load_balancer_type")
),
"labels": self.module.params.get("labels"),
}
if self.module.params.get("location") is None and self.module.params.get("network_zone") is None:
self.module.fail_json(msg="one of the following is required: location, network_zone")
elif self.module.params.get("location") is not None and self.module.params.get("network_zone") is None:
params["location"] = self.client.locations.get_by_name(
self.module.params.get("location")
)
elif self.module.params.get("location") is None and self.module.params.get("network_zone") is not None:
params["network_zone"] = self.module.params.get("network_zone")
if not self.module.check_mode:
resp = self.client.load_balancers.create(**params)
resp.action.wait_until_finished(max_retries=1000)
delete_protection = self.module.params.get("delete_protection")
if delete_protection is not None:
self._get_load_balancer()
self.hcloud_load_balancer.change_protection(delete=delete_protection).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_load_balancer()
def _update_load_balancer(self):
try:
labels = self.module.params.get("labels")
if labels is not None and labels != self.hcloud_load_balancer.labels:
if not self.module.check_mode:
self.hcloud_load_balancer.update(labels=labels)
self._mark_as_changed()
delete_protection = self.module.params.get("delete_protection")
if delete_protection is not None and delete_protection != self.hcloud_load_balancer.protection["delete"]:
if not self.module.check_mode:
self.hcloud_load_balancer.change_protection(delete=delete_protection).wait_until_finished()
self._mark_as_changed()
self._get_load_balancer()
disable_public_interface = self.module.params.get("disable_public_interface")
if disable_public_interface is not None and disable_public_interface != (not self.hcloud_load_balancer.public_net.enabled):
if not self.module.check_mode:
if disable_public_interface is True:
self.hcloud_load_balancer.disable_public_interface().wait_until_finished()
else:
self.hcloud_load_balancer.enable_public_interface().wait_until_finished()
self._mark_as_changed()
load_balancer_type = self.module.params.get("load_balancer_type")
if load_balancer_type is not None and self.hcloud_load_balancer.load_balancer_type.name != load_balancer_type:
new_load_balancer_type = self.client.load_balancer_types.get_by_name(load_balancer_type)
if not new_load_balancer_type:
self.module.fail_json(msg="unknown load balancer type")
if not self.module.check_mode:
self.hcloud_load_balancer.change_type(
load_balancer_type=new_load_balancer_type,
).wait_until_finished(max_retries=1000)
self._mark_as_changed()
self._get_load_balancer()
except Exception as e:
self.module.fail_json(msg=e.message)
def present_load_balancer(self):
self._get_load_balancer()
if self.hcloud_load_balancer is None:
self._create_load_balancer()
else:
self._update_load_balancer()
def delete_load_balancer(self):
try:
self._get_load_balancer()
if self.hcloud_load_balancer is not None:
if not self.module.check_mode:
self.client.load_balancers.delete(self.hcloud_load_balancer)
self._mark_as_changed()
self.hcloud_load_balancer = None
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
load_balancer_type={"type": "str"},
location={"type": "str"},
network_zone={"type": "str"},
labels={"type": "dict"},
delete_protection={"type": "bool"},
disable_public_interface={"type": "bool", "default": False},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
required_one_of=[['id', 'name']],
mutually_exclusive=[["location", "network_zone"]],
supports_check_mode=True,
)
def main():
module = AnsibleHcloudLoadBalancer.define_module()
hcloud = AnsibleHcloudLoadBalancer(module)
state = module.params.get("state")
if state == "absent":
hcloud.delete_load_balancer()
elif state == "present":
hcloud.present_load_balancer()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,403 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_load_balancer_info
short_description: Gather infos about your Hetzner Cloud Load Balancers.
description:
- Gather infos about your Hetzner Cloud Load Balancers..
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the Load Balancers you want to get.
type: int
name:
description:
- The name of the Load Balancers you want to get.
type: str
label_selector:
description:
- The label selector for the Load Balancers you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud load_balancer infos
hcloud_load_balancer_info:
register: output
- name: Print the gathered infos
debug:
var: output
"""
RETURN = """
hcloud_load_balancer_info:
description: The load_balancer infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the Load Balancer
returned: always
type: int
sample: 1937415
name:
description: Name of the Load Balancer
returned: always
type: str
sample: my-Load-Balancer
status:
description: Status of the Load Balancer
returned: always
type: str
sample: running
load_balancer_type:
description: Name of the Load Balancer type of the Load Balancer
returned: always
type: str
sample: cx11
ipv4_address:
description: Public IPv4 address of the Load Balancer
returned: always
type: str
sample: 116.203.104.109
ipv6_address:
description: Public IPv6 address of the Load Balancer
returned: always
type: str
sample: 2a01:4f8:1c1c:c140::1
location:
description: Name of the location of the Load Balancer
returned: always
type: str
sample: fsn1
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
delete_protection:
description: True if Load Balancer is protected for deletion
type: bool
returned: always
sample: false
disable_public_interface:
description: True if Load Balancer public interface is disabled
type: bool
returned: always
sample: false
targets:
description: The targets of the Load Balancer
returned: always
type: complex
contains:
type:
description: Type of the Load Balancer Target
type: str
returned: always
sample: server
load_balancer:
description: Name of the Load Balancer
type: str
returned: always
sample: my-LoadBalancer
server:
description: Name of the Server
type: str
returned: if I(type) is server
sample: my-server
label_selector:
description: Label Selector
type: str
returned: if I(type) is label_selector
sample: application=backend
ip:
description: IP of the dedicated server
type: str
returned: if I(type) is ip
sample: 127.0.0.1
use_private_ip:
description:
- Route the traffic over the private IP of the Load Balancer through a Hetzner Cloud Network.
- Load Balancer needs to be attached to a network. See M(hetzner.hcloud.hcloud.hcloud_load_balancer_network)
type: bool
sample: true
returned: always
services:
description: all services from this Load Balancer
returned: Always
type: complex
contains:
listen_port:
description: The port the service listens on, i.e. the port users can connect to.
returned: always
type: int
sample: 443
protocol:
description: Protocol of the service
returned: always
type: str
sample: http
destination_port:
description:
- The port traffic is forwarded to, i.e. the port the targets are listening and accepting connections on.
returned: always
type: int
sample: 80
proxyprotocol:
description:
- Enable the PROXY protocol.
returned: always
type: bool
sample: false
http:
description: Configuration for HTTP and HTTPS services
returned: always
type: complex
contains:
cookie_name:
description: Name of the cookie which will be set when you enable sticky sessions
returned: always
type: str
sample: HCLBSTICKY
cookie_lifetime:
description: Lifetime of the cookie which will be set when you enable sticky sessions, in seconds
returned: always
type: int
sample: 3600
certificates:
description: List of Names or IDs of certificates
returned: always
type: list
elements: str
sticky_sessions:
description: Enable or disable sticky_sessions
returned: always
type: bool
sample: true
redirect_http:
description: Redirect Traffic from Port 80 to Port 443, only available if protocol is https
returned: always
type: bool
sample: false
health_check:
description: Configuration for health checks
returned: always
type: complex
contains:
protocol:
description: Protocol the health checks will be performed over
returned: always
type: str
sample: http
port:
description: Port the health check will be performed on
returned: always
type: int
sample: 80
interval:
description: Interval of health checks, in seconds
returned: always
type: int
sample: 15
timeout:
description: Timeout of health checks, in seconds
returned: always
type: int
sample: 10
retries:
description: Number of retries until a target is marked as unhealthy
returned: always
type: int
sample: 3
http:
description: Additional Configuration of health checks with protocol http/https
returned: always
type: complex
contains:
domain:
description: Domain we will set within the HTTP HOST header
returned: always
type: str
sample: example.com
path:
description: Path we will try to access
returned: always
type: str
sample: /
response:
description: Response we expect, if response is not within the health check response the target is unhealthy
returned: always
type: str
status_codes:
description: List of HTTP status codes we expect to get when we perform the health check.
returned: always
type: list
elements: str
sample: ["2??","3??"]
tls:
description: Verify the TLS certificate, only available if health check protocol is https
returned: always
type: bool
sample: false
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudLoadBalancerInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_load_balancer_info")
self.hcloud_load_balancer_info = None
def _prepare_result(self):
tmp = []
for load_balancer in self.hcloud_load_balancer_info:
if load_balancer is not None:
services = [self._prepare_service_result(service) for service in load_balancer.services]
targets = [self._prepare_target_result(target) for target in load_balancer.targets]
private_ipv4_address = None if len(load_balancer.private_net) == 0 else to_native(
load_balancer.private_net[0].ip)
tmp.append({
"id": to_native(load_balancer.id),
"name": to_native(load_balancer.name),
"ipv4_address": to_native(load_balancer.public_net.ipv4.ip),
"ipv6_address": to_native(load_balancer.public_net.ipv6.ip),
"private_ipv4_address": private_ipv4_address,
"load_balancer_type": to_native(load_balancer.load_balancer_type.name),
"location": to_native(load_balancer.location.name),
"labels": load_balancer.labels,
"delete_protection": load_balancer.protection["delete"],
"disable_public_interface": False if load_balancer.public_net.enabled else True,
"targets": targets,
"services": services
})
return tmp
@staticmethod
def _prepare_service_result(service):
http = None
if service.protocol != "tcp":
http = {
"cookie_name": to_native(service.http.cookie_name),
"cookie_lifetime": service.http.cookie_name,
"redirect_http": service.http.redirect_http,
"sticky_sessions": service.http.sticky_sessions,
"certificates": [to_native(certificate.name) for certificate in
service.http.certificates],
}
health_check = {
"protocol": to_native(service.health_check.protocol),
"port": service.health_check.port,
"interval": service.health_check.interval,
"timeout": service.health_check.timeout,
"retries": service.health_check.retries,
}
if service.health_check.protocol != "tcp":
health_check["http"] = {
"domain": to_native(service.health_check.http.domain),
"path": to_native(service.health_check.http.path),
"response": to_native(service.health_check.http.response),
"certificates": [to_native(status_code) for status_code in
service.health_check.http.status_codes],
"tls": service.health_check.http.tls,
}
return {
"protocol": to_native(service.protocol),
"listen_port": service.listen_port,
"destination_port": service.destination_port,
"proxyprotocol": service.proxyprotocol,
"http": http,
"health_check": health_check,
}
@staticmethod
def _prepare_target_result(target):
result = {
"type": to_native(target.type),
"use_private_ip": target.use_private_ip
}
if target.type == "server":
result["server"] = to_native(target.server.name)
elif target.type == "label_selector":
result["label_selector"] = to_native(target.label_selector.selector)
elif target.type == "ip":
result["ip"] = to_native(target.ip.ip)
return result
def get_load_balancers(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_load_balancer_info = [self.client.load_balancers.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_load_balancer_info = [self.client.load_balancers.get_by_name(
self.module.params.get("name")
)]
else:
params = {}
label_selector = self.module.params.get("label_selector")
if label_selector:
params["label_selector"] = label_selector
self.hcloud_load_balancer_info = self.client.load_balancers.get_all(**params)
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
label_selector={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudLoadBalancerInfo.define_module()
hcloud = AnsibleHcloudLoadBalancerInfo(module)
hcloud.get_load_balancers()
result = hcloud.get_result()
ansible_info = {
'hcloud_load_balancer_info': result['hcloud_load_balancer_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,215 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_load_balancer_network
short_description: Manage the relationship between Hetzner Cloud Networks and Load Balancers
description:
- Create and delete the relationship Hetzner Cloud Networks and Load Balancers
author:
- Lukas Kaemmerling (@lkaemmerling)
version_added: 0.1.0
options:
network:
description:
- The name of the Hetzner Cloud Networks.
type: str
required: true
load_balancer:
description:
- The name of the Hetzner Cloud Load Balancer.
type: str
required: true
ip:
description:
- The IP the Load Balancer should have.
type: str
state:
description:
- State of the load_balancer_network.
default: present
choices: [ absent, present ]
type: str
requirements:
- hcloud-python >= 1.8.1
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a basic Load Balancer network
hcloud_load_balancer_network:
network: my-network
load_balancer: my-LoadBalancer
state: present
- name: Create a Load Balancer network and specify the ip address
hcloud_load_balancer_network:
network: my-network
load_balancer: my-LoadBalancer
ip: 10.0.0.1
state: present
- name: Ensure the Load Balancer network is absent (remove if needed)
hcloud_load_balancer_network:
network: my-network
load_balancer: my-LoadBalancer
state: absent
"""
RETURN = """
hcloud_load_balancer_network:
description: The relationship between a Load Balancer and a network
returned: always
type: complex
contains:
network:
description: Name of the Network
type: str
returned: always
sample: my-network
load_balancer:
description: Name of the Load Balancer
type: str
returned: always
sample: my-LoadBalancer
ip:
description: IP of the Load Balancer within the Network ip range
type: str
returned: always
sample: 10.0.0.8
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
NetworkSubnet = None
class AnsibleHcloudLoadBalancerNetwork(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_load_balancer_network")
self.hcloud_network = None
self.hcloud_load_balancer = None
self.hcloud_load_balancer_network = None
def _prepare_result(self):
return {
"network": to_native(self.hcloud_network.name),
"load_balancer": to_native(self.hcloud_load_balancer.name),
"ip": to_native(self.hcloud_load_balancer_network.ip),
}
def _get_load_balancer_and_network(self):
try:
network = self.module.params.get("network")
self.hcloud_network = self.client.networks.get_by_name(network)
if not self.hcloud_network:
self.module.fail_json(msg="Network does not exist: %s" % network)
load_balancer_name = self.module.params.get("load_balancer")
self.hcloud_load_balancer = self.client.load_balancers.get_by_name(
load_balancer_name
)
if not self.hcloud_load_balancer:
self.module.fail_json(msg="Load balancer does not exist: %s" % load_balancer_name)
self.hcloud_load_balancer_network = None
except Exception as e:
self.module.fail_json(msg=e.message)
def _get_load_balancer_network(self):
for privateNet in self.hcloud_load_balancer.private_net:
if privateNet.network.id == self.hcloud_network.id:
self.hcloud_load_balancer_network = privateNet
def _create_load_balancer_network(self):
params = {
"network": self.hcloud_network
}
if self.module.params.get("ip") is not None:
params["ip"] = self.module.params.get("ip")
if not self.module.check_mode:
try:
self.hcloud_load_balancer.attach_to_network(**params).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_load_balancer_and_network()
self._get_load_balancer_network()
def present_load_balancer_network(self):
self._get_load_balancer_and_network()
self._get_load_balancer_network()
if self.hcloud_load_balancer_network is None:
self._create_load_balancer_network()
def delete_load_balancer_network(self):
self._get_load_balancer_and_network()
self._get_load_balancer_network()
if self.hcloud_load_balancer_network is not None and self.hcloud_load_balancer is not None:
if not self.module.check_mode:
try:
self.hcloud_load_balancer.detach_from_network(
self.hcloud_load_balancer_network.network).wait_until_finished()
self._mark_as_changed()
except Exception as e:
self.module.fail_json(msg=e.message)
self.hcloud_load_balancer_network = None
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
network={"type": "str", "required": True},
load_balancer={"type": "str", "required": True},
ip={"type": "str"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudLoadBalancerNetwork.define_module()
hcloud = AnsibleHcloudLoadBalancerNetwork(module)
state = module.params["state"]
if state == "absent":
hcloud.delete_load_balancer_network()
elif state == "present":
hcloud.present_load_balancer_network()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,620 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_load_balancer_service
short_description: Create and manage the services of cloud Load Balancers on the Hetzner Cloud.
description:
- Create, update and manage the services of cloud Load Balancers on the Hetzner Cloud.
author:
- Lukas Kaemmerling (@LKaemmerling)
version_added: 0.1.0
options:
load_balancer:
description:
- The Name of the Hetzner Cloud Load Balancer the service belongs to
type: str
required: true
listen_port:
description:
- The port the service listens on, i.e. the port users can connect to.
type: int
required: true
destination_port:
description:
- The port traffic is forwarded to, i.e. the port the targets are listening and accepting connections on.
- Required if services does not exist and protocol is tcp.
type: int
protocol:
description:
- Protocol of the service.
- Required if Load Balancer does not exist.
type: str
choices: [ http, https, tcp ]
proxyprotocol:
description:
- Enable the PROXY protocol.
type: bool
default: False
http:
description:
- Configuration for HTTP and HTTPS services
type: dict
suboptions:
cookie_name:
description:
- Name of the cookie which will be set when you enable sticky sessions
type: str
cookie_lifetime:
description:
- Lifetime of the cookie which will be set when you enable sticky sessions, in seconds
type: int
certificates:
description:
- List of Names or IDs of certificates
type: list
elements: str
sticky_sessions:
description:
- Enable or disable sticky_sessions
type: bool
default: False
redirect_http:
description:
- Redirect Traffic from Port 80 to Port 443, only available if protocol is https
type: bool
default: False
health_check:
description:
- Configuration for health checks
type: dict
suboptions:
protocol:
description:
- Protocol the health checks will be performed over
type: str
choices: [ http, https, tcp ]
port:
description:
- Port the health check will be performed on
type: int
interval:
description:
- Interval of health checks, in seconds
type: int
timeout:
description:
- Timeout of health checks, in seconds
type: int
retries:
description:
- Number of retries until a target is marked as unhealthy
type: int
http:
description:
- Additional Configuration of health checks with protocol http/https
type: dict
suboptions:
domain:
description:
- Domain we will set within the HTTP HOST header
type: str
path:
description:
- Path we will try to access
type: str
response:
description:
- Response we expect, if response is not within the health check response the target is unhealthy
type: str
status_codes:
description:
- List of HTTP status codes we expect to get when we perform the health check.
type: list
elements: str
tls:
description:
- Verify the TLS certificate, only available if health check protocol is https
type: bool
default: False
state:
description:
- State of the Load Balancer.
default: present
choices: [ absent, present ]
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
requirements:
- hcloud-python >= 1.8.1
'''
EXAMPLES = """
- name: Create a basic Load Balancer service with Port 80
hcloud_load_balancer_service:
load_balancer: my-load-balancer
protocol: http
listen_port: 80
state: present
- name: Ensure the Load Balancer is absent (remove if needed)
hcloud_load_balancer_service:
load_balancer: my-Load Balancer
protocol: http
listen_port: 80
state: absent
"""
RETURN = """
hcloud_load_balancer_service:
description: The Load Balancer service instance
returned: Always
type: complex
contains:
load_balancer:
description: The name of the Load Balancer where the service belongs to
returned: always
type: str
sample: my-load-balancer
listen_port:
description: The port the service listens on, i.e. the port users can connect to.
returned: always
type: int
sample: 443
protocol:
description: Protocol of the service
returned: always
type: str
sample: http
destination_port:
description:
- The port traffic is forwarded to, i.e. the port the targets are listening and accepting connections on.
returned: always
type: int
sample: 80
proxyprotocol:
description:
- Enable the PROXY protocol.
returned: always
type: bool
sample: false
http:
description: Configuration for HTTP and HTTPS services
returned: always
type: complex
contains:
cookie_name:
description: Name of the cookie which will be set when you enable sticky sessions
returned: always
type: str
sample: HCLBSTICKY
cookie_lifetime:
description: Lifetime of the cookie which will be set when you enable sticky sessions, in seconds
returned: always
type: int
sample: 3600
certificates:
description: List of Names or IDs of certificates
returned: always
type: list
elements: str
sticky_sessions:
description: Enable or disable sticky_sessions
returned: always
type: bool
sample: true
redirect_http:
description: Redirect Traffic from Port 80 to Port 443, only available if protocol is https
returned: always
type: bool
sample: false
health_check:
description: Configuration for health checks
returned: always
type: complex
contains:
protocol:
description: Protocol the health checks will be performed over
returned: always
type: str
sample: http
port:
description: Port the health check will be performed on
returned: always
type: int
sample: 80
interval:
description: Interval of health checks, in seconds
returned: always
type: int
sample: 15
timeout:
description: Timeout of health checks, in seconds
returned: always
type: int
sample: 10
retries:
description: Number of retries until a target is marked as unhealthy
returned: always
type: int
sample: 3
http:
description: Additional Configuration of health checks with protocol http/https
returned: always
type: complex
contains:
domain:
description: Domain we will set within the HTTP HOST header
returned: always
type: str
sample: example.com
path:
description: Path we will try to access
returned: always
type: str
sample: /
response:
description: Response we expect, if response is not within the health check response the target is unhealthy
returned: always
type: str
status_codes:
description: List of HTTP status codes we expect to get when we perform the health check.
returned: always
type: list
elements: str
sample: ["2??","3??"]
tls:
description: Verify the TLS certificate, only available if health check protocol is https
returned: always
type: bool
sample: false
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud.load_balancers.domain import LoadBalancer, LoadBalancerService, LoadBalancerServiceHttp, \
LoadBalancerHealthCheck, LoadBalancerHealtCheckHttp
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudLoadBalancerService(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_load_balancer_service")
self.hcloud_load_balancer = None
self.hcloud_load_balancer_service = None
def _prepare_result(self):
http = None
if self.hcloud_load_balancer_service.protocol != "tcp":
http = {
"cookie_name": to_native(self.hcloud_load_balancer_service.http.cookie_name),
"cookie_lifetime": self.hcloud_load_balancer_service.http.cookie_name,
"redirect_http": self.hcloud_load_balancer_service.http.redirect_http,
"sticky_sessions": self.hcloud_load_balancer_service.http.sticky_sessions,
"certificates": [to_native(certificate.name) for certificate in
self.hcloud_load_balancer_service.http.certificates],
}
health_check = {
"protocol": to_native(self.hcloud_load_balancer_service.health_check.protocol),
"port": self.hcloud_load_balancer_service.health_check.port,
"interval": self.hcloud_load_balancer_service.health_check.interval,
"timeout": self.hcloud_load_balancer_service.health_check.timeout,
"retries": self.hcloud_load_balancer_service.health_check.retries,
}
if self.hcloud_load_balancer_service.health_check.protocol != "tcp":
health_check["http"] = {
"domain": to_native(self.hcloud_load_balancer_service.health_check.http.domain),
"path": to_native(self.hcloud_load_balancer_service.health_check.http.path),
"response": to_native(self.hcloud_load_balancer_service.health_check.http.response),
"certificates": [to_native(status_code) for status_code in
self.hcloud_load_balancer_service.health_check.http.status_codes],
"tls": self.hcloud_load_balancer_service.health_check.http.tls,
}
return {
"load_balancer": to_native(self.hcloud_load_balancer.name),
"protocol": to_native(self.hcloud_load_balancer_service.protocol),
"listen_port": self.hcloud_load_balancer_service.listen_port,
"destination_port": self.hcloud_load_balancer_service.destination_port,
"proxyprotocol": self.hcloud_load_balancer_service.proxyprotocol,
"http": http,
"health_check": health_check,
}
def _get_load_balancer(self):
try:
load_balancer_name = self.module.params.get("load_balancer")
self.hcloud_load_balancer = self.client.load_balancers.get_by_name(
load_balancer_name
)
if not self.hcloud_load_balancer:
self.module.fail_json(msg="Load balancer does not exist: %s" % load_balancer_name)
self._get_load_balancer_service()
except Exception as e:
self.module.fail_json(msg=e.message)
def _create_load_balancer_service(self):
self.module.fail_on_missing_params(
required_params=["protocol"]
)
if self.module.params.get("protocol") == "tcp":
self.module.fail_on_missing_params(
required_params=["destination_port"]
)
params = {
"protocol": self.module.params.get("protocol"),
"listen_port": self.module.params.get("listen_port"),
"proxyprotocol": self.module.params.get("proxyprotocol")
}
if self.module.params.get("destination_port"):
params["destination_port"] = self.module.params.get("destination_port")
if self.module.params.get("http"):
params["http"] = self.__get_service_http(http_arg=self.module.params.get("http"))
if self.module.params.get("health_check"):
params["health_check"] = self.__get_service_health_checks(
health_check=self.module.params.get("health_check"))
if not self.module.check_mode:
try:
self.hcloud_load_balancer.add_service(LoadBalancerService(**params)).wait_until_finished(
max_retries=1000)
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_load_balancer()
self._get_load_balancer_service()
def __get_service_http(self, http_arg):
service_http = LoadBalancerServiceHttp(certificates=[])
if http_arg.get("cookie_name") is not None:
service_http.cookie_name = http_arg.get("cookie_name")
if http_arg.get("cookie_lifetime") is not None:
service_http.cookie_lifetime = http_arg.get("cookie_lifetime")
if http_arg.get("sticky_sessions") is not None:
service_http.sticky_sessions = http_arg.get("sticky_sessions")
if http_arg.get("redirect_http") is not None:
service_http.redirect_http = http_arg.get("redirect_http")
if http_arg.get("certificates") is not None:
certificates = http_arg.get("certificates")
if certificates is not None:
for certificate in certificates:
hcloud_cert = None
try:
try:
hcloud_cert = self.client.certificates.get_by_name(
certificate
)
except Exception:
hcloud_cert = self.client.certificates.get_by_id(
certificate
)
except Exception as e:
self.module.fail_json(msg=e.message)
service_http.certificates.append(hcloud_cert)
return service_http
def __get_service_health_checks(self, health_check):
service_health_check = LoadBalancerHealthCheck()
if health_check.get("protocol") is not None:
service_health_check.protocol = health_check.get("protocol")
if health_check.get("port") is not None:
service_health_check.port = health_check.get("port")
if health_check.get("interval") is not None:
service_health_check.interval = health_check.get("interval")
if health_check.get("timeout") is not None:
service_health_check.timeout = health_check.get("timeout")
if health_check.get("retries") is not None:
service_health_check.retries = health_check.get("retries")
if health_check.get("http") is not None:
health_check_http = health_check.get("http")
service_health_check.http = LoadBalancerHealtCheckHttp()
if health_check_http.get("domain") is not None:
service_health_check.http.domain = health_check_http.get("domain")
if health_check_http.get("path") is not None:
service_health_check.http.path = health_check_http.get("path")
if health_check_http.get("response") is not None:
service_health_check.http.response = health_check_http.get("response")
if health_check_http.get("status_codes") is not None:
service_health_check.http.status_codes = health_check_http.get("status_codes")
if health_check_http.get("tls") is not None:
service_health_check.http.tls = health_check_http.get("tls")
return service_health_check
def _update_load_balancer_service(self):
changed = False
try:
params = {
"listen_port": self.module.params.get("listen_port"),
}
if self.module.params.get("destination_port") is not None:
if self.hcloud_load_balancer_service.destination_port != self.module.params.get("destination_port"):
params["destination_port"] = self.module.params.get("destination_port")
changed = True
if self.module.params.get("protocol") is not None:
if self.hcloud_load_balancer_service.protocol != self.module.params.get("protocol"):
params["protocol"] = self.module.params.get("protocol")
changed = True
if self.module.params.get("proxyprotocol") is not None:
if self.hcloud_load_balancer_service.proxyprotocol != self.module.params.get("proxyprotocol"):
params["proxyprotocol"] = self.module.params.get("proxyprotocol")
changed = True
if self.module.params.get("http") is not None:
params["http"] = self.__get_service_http(http_arg=self.module.params.get("http"))
changed = True
if self.module.params.get("health_check") is not None:
params["health_check"] = self.__get_service_health_checks(
health_check=self.module.params.get("health_check"))
changed = True
if not self.module.check_mode:
self.hcloud_load_balancer.update_service(LoadBalancerService(**params)).wait_until_finished(
max_retries=1000)
except Exception as e:
self.module.fail_json(msg=e.message)
self._get_load_balancer()
if changed:
self._mark_as_changed()
def _get_load_balancer_service(self):
for service in self.hcloud_load_balancer.services:
if self.module.params.get("listen_port") == service.listen_port:
self.hcloud_load_balancer_service = service
def present_load_balancer_service(self):
self._get_load_balancer()
if self.hcloud_load_balancer_service is None:
self._create_load_balancer_service()
else:
self._update_load_balancer_service()
def delete_load_balancer_service(self):
try:
self._get_load_balancer()
if self.hcloud_load_balancer_service is not None:
if not self.module.check_mode:
try:
self.hcloud_load_balancer.delete_service(self.hcloud_load_balancer_service).wait_until_finished(
max_retries=1000)
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self.hcloud_load_balancer_service = None
except APIException as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
load_balancer={"type": "str", "required": True},
listen_port={"type": "int", "required": True},
destination_port={"type": "int"},
protocol={
"type": "str",
"choices": ["http", "https", "tcp"],
},
proxyprotocol={"type": "bool", "default": False},
http={
"type": "dict",
"options": dict(
cookie_name={
"type": "str"
},
cookie_lifetime={
"type": "int"
},
sticky_sessions={
"type": "bool",
"default": False
},
redirect_http={
"type": "bool",
"default": False
},
certificates={
"type": "list",
"elements": "str"
},
)
},
health_check={
"type": "dict",
"options": dict(
protocol={
"type": "str",
"choices": ["http", "https", "tcp"],
},
port={
"type": "int"
},
interval={
"type": "int"
},
timeout={
"type": "int"
},
retries={
"type": "int"
},
http={
"type": "dict",
"options": dict(
domain={
"type": "str"
},
path={
"type": "str"
},
response={
"type": "str"
},
status_codes={
"type": "list",
"elements": "str"
},
tls={
"type": "bool",
"default": False
},
)
}
)
},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudLoadBalancerService.define_module()
hcloud = AnsibleHcloudLoadBalancerService(module)
state = module.params.get("state")
if state == "absent":
hcloud.delete_load_balancer_service()
elif state == "present":
hcloud.present_load_balancer_service()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,323 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_load_balancer_target
short_description: Manage Hetzner Cloud Load Balancer targets
description:
- Create and delete Hetzner Cloud Load Balancer targets
author:
- Lukas Kaemmerling (@lkaemmerling)
version_added: 0.1.0
options:
type:
description:
- The type of the target.
type: str
choices: [ server, label_selector, ip ]
required: true
load_balancer:
description:
- The name of the Hetzner Cloud Load Balancer.
type: str
required: true
server:
description:
- The name of the Hetzner Cloud Server.
- Required if I(type) is server
type: str
label_selector:
description:
- A Label Selector that will be used to determine the targets dynamically
- Required if I(type) is label_selector
type: str
ip:
description:
- An IP from a Hetzner Dedicated Server, needs to belongs to the same user as the project.
- Required if I(type) is ip
type: str
use_private_ip:
description:
- Route the traffic over the private IP of the Load Balancer through a Hetzner Cloud Network.
- Load Balancer needs to be attached to a network. See M(hetzner.hcloud.hcloud.hcloud_load_balancer_network)
type: bool
default: False
state:
description:
- State of the load_balancer_network.
default: present
choices: [ absent, present ]
type: str
requirements:
- hcloud-python >= 1.8.1
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a server Load Balancer target
hetzner.hcloud.hcloud_load_balancer_target:
type: server
load_balancer: my-LoadBalancer
server: my-server
state: present
- name: Create a label_selector Load Balancer target
hetzner.hcloud.hcloud_load_balancer_target:
type: label_selector
load_balancer: my-LoadBalancer
label_selector: application=backend
state: present
- name: Create an IP Load Balancer target
hetzner.hcloud.hcloud_load_balancer_target:
type: ip
load_balancer: my-LoadBalancer
ip: 127.0.0.1
state: present
- name: Ensure the Load Balancer target is absent (remove if needed)
hetzner.hcloud.hcloud_load_balancer_target:
type: server
load_balancer: my-LoadBalancer
server: my-server
state: absent
"""
RETURN = """
hcloud_load_balancer_target:
description: The relationship between a Load Balancer and a network
returned: always
type: complex
contains:
type:
description: Type of the Load Balancer Target
type: str
returned: always
sample: server
load_balancer:
description: Name of the Load Balancer
type: str
returned: always
sample: my-LoadBalancer
server:
description: Name of the Server
type: str
returned: if I(type) is server
sample: my-server
label_selector:
description: Label Selector
type: str
returned: if I(type) is label_selector
sample: application=backend
ip:
description: IP of the dedicated server
type: str
returned: if I(type) is ip
sample: 127.0.0.1
use_private_ip:
description:
- Route the traffic over the private IP of the Load Balancer through a Hetzner Cloud Network.
- Load Balancer needs to be attached to a network. See M(hetzner.hcloud.hcloud.hcloud_load_balancer_network)
type: bool
sample: true
returned: always
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
from hcloud.load_balancers.domain import LoadBalancerTarget, LoadBalancerTargetLabelSelector, LoadBalancerTargetIP
except ImportError:
APIException = None
LoadBalancerTarget = None
LoadBalancerTargetLabelSelector = None
LoadBalancerTargetIP = None
class AnsibleHcloudLoadBalancerTarget(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_load_balancer_target")
self.hcloud_load_balancer = None
self.hcloud_load_balancer_target = None
self.hcloud_server = None
def _prepare_result(self):
result = {
"type": to_native(self.hcloud_load_balancer_target.type),
"load_balancer": to_native(self.hcloud_load_balancer.name),
"use_private_ip": self.hcloud_load_balancer_target.use_private_ip
}
if self.hcloud_load_balancer_target.type == "server":
result["server"] = to_native(self.hcloud_load_balancer_target.server.name)
elif self.hcloud_load_balancer_target.type == "label_selector":
result["label_selector"] = to_native(self.hcloud_load_balancer_target.label_selector.selector)
elif self.hcloud_load_balancer_target.type == "ip":
result["ip"] = to_native(self.hcloud_load_balancer_target.ip.ip)
return result
def _get_load_balancer_and_target(self):
try:
load_balancer_name = self.module.params.get("load_balancer")
self.hcloud_load_balancer = self.client.load_balancers.get_by_name(
load_balancer_name
)
if not self.hcloud_load_balancer:
self.module.fail_json(msg="Load balancer does not exist: %s" % load_balancer_name)
if self.module.params.get("type") == "server":
server_name = self.module.params.get("server")
self.hcloud_server = self.client.servers.get_by_name(server_name)
if not self.hcloud_server:
self.module.fail_json(msg="Server not found: %s" % server_name)
self.hcloud_load_balancer_target = None
except Exception as e:
self.module.fail_json(msg=e.message)
def _get_load_balancer_target(self):
for target in self.hcloud_load_balancer.targets:
if self.module.params.get("type") == "server" and target.type == "server":
if target.server.id == self.hcloud_server.id:
self.hcloud_load_balancer_target = target
elif self.module.params.get("type") == "label_selector" and target.type == "label_selector":
if target.label_selector.selector == self.module.params.get("label_selector"):
self.hcloud_load_balancer_target = target
elif self.module.params.get("type") == "ip" and target.type == "ip":
if target.ip.ip == self.module.params.get("ip"):
self.hcloud_load_balancer_target = target
def _create_load_balancer_target(self):
params = {
"target": None
}
if self.module.params.get("type") == "server":
self.module.fail_on_missing_params(
required_params=["server"]
)
params["target"] = LoadBalancerTarget(type=self.module.params.get("type"), server=self.hcloud_server,
use_private_ip=self.module.params.get("use_private_ip"))
elif self.module.params.get("type") == "label_selector":
self.module.fail_on_missing_params(
required_params=["label_selector"]
)
params["target"] = LoadBalancerTarget(type=self.module.params.get("type"),
label_selector=LoadBalancerTargetLabelSelector(
selector=self.module.params.get("label_selector")),
use_private_ip=self.module.params.get("use_private_ip"))
elif self.module.params.get("type") == "ip":
self.module.fail_on_missing_params(
required_params=["ip"]
)
params["target"] = LoadBalancerTarget(type=self.module.params.get("type"),
ip=LoadBalancerTargetIP(ip=self.module.params.get("ip")),
use_private_ip=False)
if not self.module.check_mode:
try:
self.hcloud_load_balancer.add_target(**params).wait_until_finished()
except Exception as e:
if e.code == "locked" or e.code == "conflict":
self._create_load_balancer_target()
else:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_load_balancer_and_target()
self._get_load_balancer_target()
def present_load_balancer_target(self):
self._get_load_balancer_and_target()
self._get_load_balancer_target()
if self.hcloud_load_balancer_target is None:
self._create_load_balancer_target()
def delete_load_balancer_target(self):
self._get_load_balancer_and_target()
self._get_load_balancer_target()
if self.hcloud_load_balancer_target is not None and self.hcloud_load_balancer is not None:
if not self.module.check_mode:
target = None
if self.module.params.get("type") == "server":
self.module.fail_on_missing_params(
required_params=["server"]
)
target = LoadBalancerTarget(type=self.module.params.get("type"),
server=self.hcloud_server)
elif self.module.params.get("type") == "label_selector":
self.module.fail_on_missing_params(
required_params=["label_selector"]
)
target = LoadBalancerTarget(type=self.module.params.get("type"),
label_selector=LoadBalancerTargetLabelSelector(
selector=self.module.params.get("label_selector")),
use_private_ip=self.module.params.get("use_private_ip"))
elif self.module.params.get("type") == "ip":
self.module.fail_on_missing_params(
required_params=["ip"]
)
target = LoadBalancerTarget(type=self.module.params.get("type"),
ip=LoadBalancerTargetIP(ip=self.module.params.get("ip")),
use_private_ip=False)
try:
self.hcloud_load_balancer.remove_target(target).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self.hcloud_load_balancer_target = None
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
type={"type": "str", "required": True, "choices": ["server", "label_selector", "ip"]},
load_balancer={"type": "str", "required": True},
server={"type": "str"},
label_selector={"type": "str"},
ip={"type": "str"},
use_private_ip={"type": "bool", "default": False},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudLoadBalancerTarget.define_module()
hcloud = AnsibleHcloudLoadBalancerTarget(module)
state = module.params["state"]
if state == "absent":
hcloud.delete_load_balancer_target()
elif state == "present":
hcloud.present_load_balancer_target()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,163 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_load_balancer_type_info
short_description: Gather infos about the Hetzner Cloud Load Balancer types.
description:
- Gather infos about your Hetzner Cloud Load Balancer types.
author:
- Lukas Kaemmerling (@LKaemmerling)
version_added: 0.1.0
options:
id:
description:
- The ID of the Load Balancer type you want to get.
type: int
name:
description:
- The name of the Load Balancer type you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud Load Balancer type infos
hcloud_load_balancer_type_info:
register: output
- name: Print the gathered infos
debug:
var: output.hcloud_load_balancer_type_info
"""
RETURN = """
hcloud_load_balancer_type_info:
description: The Load Balancer type infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the Load Balancer type
returned: always
type: int
sample: 1937415
name:
description: Name of the Load Balancer type
returned: always
type: str
sample: lb11
description:
description: Description of the Load Balancer type
returned: always
type: str
sample: LB11
max_connections:
description: Number of maximum simultaneous open connections
returned: always
type: int
sample: 1
max_services:
description: Number of services a Load Balancer of this type can have
returned: always
type: int
sample: 1
max_targets:
description: Number of targets a single Load Balancer can have
returned: always
type: int
sample: 25
max_assigned_certificates:
description: Number of SSL Certificates that can be assigned to a single Load Balancer
returned: always
type: int
sample: 5
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
pass
class AnsibleHcloudLoadBalancerTypeInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_load_balancer_type_info")
self.hcloud_load_balancer_type_info = None
def _prepare_result(self):
tmp = []
for load_balancer_type in self.hcloud_load_balancer_type_info:
if load_balancer_type is not None:
tmp.append({
"id": to_native(load_balancer_type.id),
"name": to_native(load_balancer_type.name),
"description": to_native(load_balancer_type.description),
"max_connections": load_balancer_type.max_connections,
"max_services": load_balancer_type.max_services,
"max_targets": load_balancer_type.max_targets,
"max_assigned_certificates": load_balancer_type.max_assigned_certificates
})
return tmp
def get_load_balancer_types(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_load_balancer_type_info = [self.client.load_balancer_types.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_load_balancer_type_info = [self.client.load_balancer_types.get_by_name(
self.module.params.get("name")
)]
else:
self.hcloud_load_balancer_type_info = self.client.load_balancer_types.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudLoadBalancerTypeInfo.define_module()
hcloud = AnsibleHcloudLoadBalancerTypeInfo(module)
hcloud.get_load_balancer_types()
result = hcloud.get_result()
ansible_info = {
'hcloud_load_balancer_type_info': result['hcloud_load_balancer_type_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,164 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_location_info
short_description: Gather infos about your Hetzner Cloud locations.
description:
- Gather infos about your Hetzner Cloud locations.
- This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts).
Note that the M(hetzner.hcloud.hcloud_location_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_location_info)!
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the location you want to get.
type: int
name:
description:
- The name of the location you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud location infos
hcloud_location_info:
register: output
- name: Print the gathered infos
debug:
var: output
"""
RETURN = """
hcloud_location_info:
description: The location infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the location
returned: always
type: int
sample: 1937415
name:
description: Name of the location
returned: always
type: str
sample: fsn1
description:
description: Detail description of the location
returned: always
type: str
sample: Falkenstein DC Park 1
country:
description: Country code of the location
returned: always
type: str
sample: DE
city:
description: City of the location
returned: always
type: str
sample: Falkenstein
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudLocationInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_location_info")
self.hcloud_location_info = None
def _prepare_result(self):
tmp = []
for location in self.hcloud_location_info:
if location is not None:
tmp.append({
"id": to_native(location.id),
"name": to_native(location.name),
"description": to_native(location.description),
"city": to_native(location.city),
"country": to_native(location.country)
})
return tmp
def get_locations(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_location_info = [self.client.locations.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_location_info = [self.client.locations.get_by_name(
self.module.params.get("name")
)]
else:
self.hcloud_location_info = self.client.locations.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudLocationInfo.define_module()
is_old_facts = module._name == 'hcloud_location_facts'
if is_old_facts:
module.deprecate("The 'hcloud_location_info' module has been renamed to 'hcloud_location_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudLocationInfo(module)
hcloud.get_locations()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_location_facts': result['hcloud_location_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_location_info': result['hcloud_location_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,164 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_location_info
short_description: Gather infos about your Hetzner Cloud locations.
description:
- Gather infos about your Hetzner Cloud locations.
- This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts).
Note that the M(hetzner.hcloud.hcloud_location_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_location_info)!
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the location you want to get.
type: int
name:
description:
- The name of the location you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud location infos
hcloud_location_info:
register: output
- name: Print the gathered infos
debug:
var: output
"""
RETURN = """
hcloud_location_info:
description: The location infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the location
returned: always
type: int
sample: 1937415
name:
description: Name of the location
returned: always
type: str
sample: fsn1
description:
description: Detail description of the location
returned: always
type: str
sample: Falkenstein DC Park 1
country:
description: Country code of the location
returned: always
type: str
sample: DE
city:
description: City of the location
returned: always
type: str
sample: Falkenstein
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudLocationInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_location_info")
self.hcloud_location_info = None
def _prepare_result(self):
tmp = []
for location in self.hcloud_location_info:
if location is not None:
tmp.append({
"id": to_native(location.id),
"name": to_native(location.name),
"description": to_native(location.description),
"city": to_native(location.city),
"country": to_native(location.country)
})
return tmp
def get_locations(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_location_info = [self.client.locations.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_location_info = [self.client.locations.get_by_name(
self.module.params.get("name")
)]
else:
self.hcloud_location_info = self.client.locations.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudLocationInfo.define_module()
is_old_facts = module._name == 'hcloud_location_facts'
if is_old_facts:
module.deprecate("The 'hcloud_location_info' module has been renamed to 'hcloud_location_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudLocationInfo(module)
hcloud.get_locations()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_location_facts': result['hcloud_location_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_location_info': result['hcloud_location_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,248 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_network
short_description: Create and manage cloud Networks on the Hetzner Cloud.
description:
- Create, update and manage cloud Networks on the Hetzner Cloud.
- You need at least hcloud-python 1.3.0.
author:
- Lukas Kaemmerling (@lkaemmerling)
options:
id:
description:
- The ID of the Hetzner Cloud Networks to manage.
- Only required if no Network I(name) is given.
type: int
name:
description:
- The Name of the Hetzner Cloud Network to manage.
- Only required if no Network I(id) is given or a Network does not exist.
type: str
ip_range:
description:
- IP range of the Network.
- Required if Network does not exist.
type: str
labels:
description:
- User-defined labels (key-value pairs).
type: dict
delete_protection:
description:
- Protect the Network for deletion.
type: bool
state:
description:
- State of the Network.
default: present
choices: [ absent, present ]
type: str
requirements:
- hcloud-python >= 1.3.0
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a basic network
hcloud_network:
name: my-network
ip_range: 10.0.0.0/8
state: present
- name: Ensure the Network is absent (remove if needed)
hcloud_network:
name: my-network
state: absent
"""
RETURN = """
hcloud_network:
description: The Network
returned: always
type: complex
contains:
id:
description: ID of the Network
type: int
returned: always
sample: 12345
name:
description: Name of the Network
type: str
returned: always
sample: my-volume
ip_range:
description: IP range of the Network
type: str
returned: always
sample: 10.0.0.0/8
delete_protection:
description: True if Network is protected for deletion
type: bool
returned: always
sample: false
version_added: "0.1.0"
labels:
description: User-defined labels (key-value pairs)
type: dict
returned: always
sample:
key: value
mylabel: 123
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudNetwork(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_network")
self.hcloud_network = None
def _prepare_result(self):
return {
"id": to_native(self.hcloud_network.id),
"name": to_native(self.hcloud_network.name),
"ip_range": to_native(self.hcloud_network.ip_range),
"delete_protection": self.hcloud_network.protection["delete"],
"labels": self.hcloud_network.labels,
}
def _get_network(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_network = self.client.networks.get_by_id(
self.module.params.get("id")
)
else:
self.hcloud_network = self.client.networks.get_by_name(
self.module.params.get("name")
)
except Exception as e:
self.module.fail_json(msg=e.message)
def _create_network(self):
self.module.fail_on_missing_params(
required_params=["name", "ip_range"]
)
params = {
"name": self.module.params.get("name"),
"ip_range": self.module.params.get("ip_range"),
"labels": self.module.params.get("labels"),
}
try:
if not self.module.check_mode:
self.client.networks.create(**params)
delete_protection = self.module.params.get("delete_protection")
if delete_protection is not None:
self._get_network()
self.hcloud_network.change_protection(delete=delete_protection).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_network()
def _update_network(self):
try:
labels = self.module.params.get("labels")
if labels is not None and labels != self.hcloud_network.labels:
if not self.module.check_mode:
self.hcloud_network.update(labels=labels)
self._mark_as_changed()
ip_range = self.module.params.get("ip_range")
if ip_range is not None and ip_range != self.hcloud_network.ip_range:
if not self.module.check_mode:
self.hcloud_network.change_ip_range(ip_range=ip_range).wait_until_finished()
self._mark_as_changed()
delete_protection = self.module.params.get("delete_protection")
if delete_protection is not None and delete_protection != self.hcloud_network.protection["delete"]:
if not self.module.check_mode:
self.hcloud_network.change_protection(delete=delete_protection).wait_until_finished()
self._mark_as_changed()
except Exception as e:
self.module.fail_json(msg=e.message)
self._get_network()
def present_network(self):
self._get_network()
if self.hcloud_network is None:
self._create_network()
else:
self._update_network()
def delete_network(self):
try:
self._get_network()
if self.hcloud_network is not None:
if not self.module.check_mode:
self.client.networks.delete(self.hcloud_network)
self._mark_as_changed()
except Exception as e:
self.module.fail_json(msg=e.message)
self.hcloud_network = None
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
ip_range={"type": "str"},
labels={"type": "dict"},
delete_protection={"type": "bool"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
required_one_of=[['id', 'name']],
supports_check_mode=True,
)
def main():
module = AnsibleHcloudNetwork.define_module()
hcloud = AnsibleHcloudNetwork(module)
state = module.params["state"]
if state == "absent":
hcloud.delete_network()
elif state == "present":
hcloud.present_network()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,298 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_network_info
short_description: Gather info about your Hetzner Cloud networks.
description:
- Gather info about your Hetzner Cloud networks.
author:
- Christopher Schmitt (@cschmitt-hcloud)
options:
id:
description:
- The ID of the network you want to get.
type: int
name:
description:
- The name of the network you want to get.
type: str
label_selector:
description:
- The label selector for the network you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud network info
local_action:
module: hcloud_network_info
- name: Print the gathered info
debug:
var: hcloud_network_info
"""
RETURN = """
hcloud_network_info:
description: The network info as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the network
returned: always
type: int
sample: 1937415
name:
description: Name of the network
returned: always
type: str
sample: awesome-network
ip_range:
description: IP range of the network
returned: always
type: str
sample: 10.0.0.0/16
subnetworks:
description: Subnetworks belonging to the network
returned: always
type: complex
contains:
type:
description: Type of the subnetwork.
returned: always
type: str
sample: cloud
network_zone:
description: Network of the subnetwork.
returned: always
type: str
sample: eu-central
ip_range:
description: IP range of the subnetwork
returned: always
type: str
sample: 10.0.0.0/24
gateway:
description: Gateway of this subnetwork
returned: always
type: str
sample: 10.0.0.1
routes:
description: Routes belonging to the network
returned: always
type: complex
contains:
ip_range:
description: Destination network or host of this route.
returned: always
type: str
sample: 10.0.0.0/16
gateway:
description: Gateway of this route
returned: always
type: str
sample: 10.0.0.1
servers:
description: Servers attached to the network
returned: always
type: complex
contains:
id:
description: Numeric identifier of the server
returned: always
type: int
sample: 1937415
name:
description: Name of the server
returned: always
type: str
sample: my-server
status:
description: Status of the server
returned: always
type: str
sample: running
server_type:
description: Name of the server type of the server
returned: always
type: str
sample: cx11
ipv4_address:
description: Public IPv4 address of the server, None if not existing
returned: always
type: str
sample: 116.203.104.109
ipv6:
description: IPv6 network of the server, None if not existing
returned: always
type: str
sample: 2a01:4f8:1c1c:c140::/64
location:
description: Name of the location of the server
returned: always
type: str
sample: fsn1
datacenter:
description: Name of the datacenter of the server
returned: always
type: str
sample: fsn1-dc14
rescue_enabled:
description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot
returned: always
type: bool
sample: false
backup_window:
description: Time window (UTC) in which the backup will run, or null if the backups are not enabled
returned: always
type: bool
sample: 22-02
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
delete_protection:
description: True if the network is protected for deletion
returned: always
type: bool
version_added: "0.1.0"
labels:
description: Labels of the network
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudNetworkInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_network_info")
self.hcloud_network_info = None
def _prepare_result(self):
tmp = []
for network in self.hcloud_network_info:
if network is not None:
subnets = []
for subnet in network.subnets:
prepared_subnet = {
"type": subnet.type,
"ip_range": subnet.ip_range,
"network_zone": subnet.network_zone,
"gateway": subnet.gateway,
}
subnets.append(prepared_subnet)
routes = []
for route in network.routes:
prepared_route = {
"destination": route.destination,
"gateway": route.gateway
}
routes.append(prepared_route)
servers = []
for server in network.servers:
image = None if server.image is None else to_native(server.image.name)
ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip)
ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip)
prepared_server = {
"id": to_native(server.id),
"name": to_native(server.name),
"ipv4_address": ipv4_address,
"ipv6": ipv6,
"image": image,
"server_type": to_native(server.server_type.name),
"datacenter": to_native(server.datacenter.name),
"location": to_native(server.datacenter.location.name),
"rescue_enabled": server.rescue_enabled,
"backup_window": to_native(server.backup_window),
"labels": server.labels,
"status": to_native(server.status),
}
servers.append(prepared_server)
tmp.append({
"id": to_native(network.id),
"name": to_native(network.name),
"ip_range": to_native(network.ip_range),
"subnetworks": subnets,
"routes": routes,
"servers": servers,
"labels": network.labels,
"delete_protection": network.protection["delete"],
})
return tmp
def get_networks(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_network_info = [self.client.networks.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_network_info = [self.client.networks.get_by_name(
self.module.params.get("name")
)]
elif self.module.params.get("label_selector") is not None:
self.hcloud_network_info = self.client.networks.get_all(
label_selector=self.module.params.get("label_selector"))
else:
self.hcloud_network_info = self.client.networks.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
label_selector={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudNetworkInfo.define_module()
hcloud = AnsibleHcloudNetworkInfo(module)
hcloud.get_networks()
result = hcloud.get_result()
info = {
'hcloud_network_info': result['hcloud_network_info']
}
module.exit_json(**info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,230 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = """
---
module: hcloud_placement_group
short_description: Create and manage placement groups on the Hetzner Cloud.
description:
- Create, update and manage placement groups on the Hetzner Cloud.
author:
- Adrian Huber (@Adi146)
options:
id:
description:
- The ID of the Hetzner Cloud placement group to manage.
- Only required if no placement group I(name) is given
type: int
name:
description:
- The Name of the Hetzner Cloud placement group to manage.
- Only required if no placement group I(id) is given, or a placement group does not exist.
type: str
labels:
description:
- User-defined labels (key-value pairs)
type: dict
type:
description:
- The Type of the Hetzner Cloud placement group.
type: str
state:
description:
- State of the placement group.
default: present
choices: [ absent, present ]
type: str
requirements:
- hcloud-python >= 1.15.0
extends_documentation_fragment:
- hetzner.hcloud.hcloud
"""
EXAMPLES = """
- name: Create a basic placement group
hcloud_placement_group:
name: my-placement-group
state: present
type: spread
- name: Create a placement group with labels
hcloud_placement_group:
name: my-placement-group
type: spread
labels:
key: value
mylabel: 123
state: present
- name: Ensure the placement group is absent (remove if needed)
hcloud_placement_group:
name: my-placement-group
state: absent
"""
RETURN = """
hcloud_placement_group:
description: The placement group instance
returned: Always
type: complex
contains:
id:
description: Numeric identifier of the placement group
returned: always
type: int
sample: 1937415
name:
description: Name of the placement group
returned: always
type: str
sample: my placement group
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
type:
description: Type of the placement group
returned: always
type: str
sample: spread
servers:
description: Server IDs of the placement group
returned: always
type: list
elements: int
sample:
- 4711
- 4712
"""
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
class AnsibleHcloudPlacementGroup(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_placement_group")
self.hcloud_placement_group = None
def _prepare_result(self):
return {
"id": to_native(self.hcloud_placement_group.id),
"name": to_native(self.hcloud_placement_group.name),
"labels": self.hcloud_placement_group.labels,
"type": to_native(self.hcloud_placement_group.type),
"servers": self.hcloud_placement_group.servers,
}
def _get_placement_group(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_placement_group = self.client.placement_groups.get_by_id(
self.module.params.get("id")
)
elif self.module.params.get("name") is not None:
self.hcloud_placement_group = self.client.placement_groups.get_by_name(
self.module.params.get("name")
)
except Exception as e:
self.module.fail_json(msg=e.message)
def _create_placement_group(self):
self.module.fail_on_missing_params(
required_params=["name"]
)
params = {
"name": self.module.params.get("name"),
"type": self.module.params.get("type"),
"labels": self.module.params.get("labels"),
}
if not self.module.check_mode:
try:
self.client.placement_groups.create(**params)
except Exception as e:
self.module.fail_json(msg=e.message, **params)
self._mark_as_changed()
self._get_placement_group()
def _update_placement_group(self):
name = self.module.params.get("name")
if name is not None and self.hcloud_placement_group.name != name:
self.module.fail_on_missing_params(
required_params=["id"]
)
if not self.module.check_mode:
self.hcloud_placement_group.update(name=name)
self._mark_as_changed()
labels = self.module.params.get("labels")
if labels is not None and self.hcloud_placement_group.labels != labels:
if not self.module.check_mode:
self.hcloud_placement_group.update(labels=labels)
self._mark_as_changed()
self._get_placement_group()
def present_placement_group(self):
self._get_placement_group()
if self.hcloud_placement_group is None:
self._create_placement_group()
else:
self._update_placement_group()
def delete_placement_group(self):
self._get_placement_group()
if self.hcloud_placement_group is not None:
if not self.module.check_mode:
self.client.placement_groups.delete(self.hcloud_placement_group)
self._mark_as_changed()
self.hcloud_placement_group = None
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
labels={"type": "dict"},
type={"type": "str"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
required_one_of=[['id', 'name']],
required_if=[['state', 'present', ['name']]],
supports_check_mode=True,
)
def main():
module = AnsibleHcloudPlacementGroup.define_module()
hcloud = AnsibleHcloudPlacementGroup(module)
state = module.params.get("state")
if state == "absent":
hcloud.delete_placement_group()
elif state == "present":
hcloud.present_placement_group()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,276 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2022, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_primary_ip
short_description: Create and manage cloud Primary IPs on the Hetzner Cloud.
description:
- Create, update and manage cloud Primary IPs on the Hetzner Cloud.
author:
- Lukas Kaemmerling (@lkaemmerling)
version_added: 1.8.0
options:
id:
description:
- The ID of the Hetzner Cloud Primary IPs to manage.
- Only required if no Primary IP I(name) is given.
type: int
name:
description:
- The Name of the Hetzner Cloud Primary IPs to manage.
- Only required if no Primary IP I(id) is given or a Primary IP does not exist.
type: str
datacenter:
description:
- Home Location of the Hetzner Cloud Primary IP.
- Required if no I(server) is given and Primary IP does not exist.
type: str
type:
description:
- Type of the Primary IP.
- Required if Primary IP does not exist
choices: [ ipv4, ipv6 ]
type: str
auto_delete:
description:
- Delete this Primary IP when the resource it is assigned to is deleted
type: bool
default: no
delete_protection:
description:
- Protect the Primary IP for deletion.
type: bool
labels:
description:
- User-defined labels (key-value pairs).
type: dict
state:
description:
- State of the Primary IP.
default: present
choices: [ absent, present ]
type: str
requirements:
- hcloud-python >= 1.9.0
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a basic IPv4 Primary IP
hcloud_primary_ip:
name: my-primary-ip
datacenter: fsn1-dc14
type: ipv4
state: present
- name: Create a basic IPv6 Primary IP
hcloud_primary_ip:
name: my-primary-ip
datacenter: fsn1-dc14
type: ipv6
state: present
- name: Primary IP should be absent
hcloud_primary_ip:
name: my-primary-ip
state: absent
"""
RETURN = """
hcloud_primary_ip:
description: The Primary IP instance
returned: Always
type: complex
contains:
id:
description: ID of the Primary IP
type: int
returned: Always
sample: 12345
name:
description: Name of the Primary IP
type: str
returned: Always
sample: my-primary-ip
ip:
description: IP Address of the Primary IP
type: str
returned: Always
sample: 116.203.104.109
type:
description: Type of the Primary IP
type: str
returned: Always
sample: ipv4
datacenter:
description: Name of the datacenter of the Primary IP
type: str
returned: Always
sample: fsn1-dc14
delete_protection:
description: True if Primary IP is protected for deletion
type: bool
returned: always
sample: false
labels:
description: User-defined labels (key-value pairs)
type: dict
returned: Always
sample:
key: value
mylabel: 123
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudPrimaryIP(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_primary_ip")
self.hcloud_primary_ip = None
def _prepare_result(self):
return {
"id": to_native(self.hcloud_primary_ip.id),
"name": to_native(self.hcloud_primary_ip.name),
"ip": to_native(self.hcloud_primary_ip.ip),
"type": to_native(self.hcloud_primary_ip.type),
"datacenter": to_native(self.hcloud_primary_ip.datacenter.name),
"labels": self.hcloud_primary_ip.labels,
"delete_protection": self.hcloud_primary_ip.protection["delete"],
}
def _get_primary_ip(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_primary_ip = self.client.primary_ips.get_by_id(
self.module.params.get("id")
)
else:
self.hcloud_primary_ip = self.client.primary_ips.get_by_name(
self.module.params.get("name")
)
except Exception as e:
self.module.fail_json(msg=e.message)
def _create_primary_ip(self):
self.module.fail_on_missing_params(
required_params=["type", "datacenter"]
)
try:
params = {
"type": self.module.params.get("type"),
"name": self.module.params.get("name"),
"datacenter": self.client.datacenters.get_by_name(
self.module.params.get("datacenter")
)
}
if self.module.params.get("labels") is not None:
params["labels"] = self.module.params.get("labels")
if not self.module.check_mode:
resp = self.client.primary_ips.create(**params)
self.hcloud_primary_ip = resp.primary_ip
delete_protection = self.module.params.get("delete_protection")
if delete_protection is not None:
self.hcloud_primary_ip.change_protection(delete=delete_protection).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e)
self._mark_as_changed()
self._get_primary_ip()
def _update_primary_ip(self):
try:
labels = self.module.params.get("labels")
if labels is not None and labels != self.hcloud_primary_ip.labels:
if not self.module.check_mode:
self.hcloud_primary_ip.update(labels=labels)
self._mark_as_changed()
delete_protection = self.module.params.get("delete_protection")
if delete_protection is not None and delete_protection != self.hcloud_primary_ip.protection["delete"]:
if not self.module.check_mode:
self.hcloud_primary_ip.change_protection(delete=delete_protection).wait_until_finished()
self._mark_as_changed()
self._get_primary_ip()
except Exception as e:
self.module.fail_json(msg=e.message)
def present_primary_ip(self):
self._get_primary_ip()
if self.hcloud_primary_ip is None:
self._create_primary_ip()
else:
self._update_primary_ip()
def delete_primary_ip(self):
try:
self._get_primary_ip()
if self.hcloud_primary_ip is not None:
if not self.module.check_mode:
self.client.primary_ips.delete(self.hcloud_primary_ip)
self._mark_as_changed()
self.hcloud_primary_ip = None
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
datacenter={"type": "str"},
auto_delete={"type": "bool", "default": False},
type={"choices": ["ipv4", "ipv6"]},
labels={"type": "dict"},
delete_protection={"type": "bool"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
required_one_of=[['id', 'name']],
supports_check_mode=True,
)
def main():
module = AnsibleHcloudPrimaryIP.define_module()
hcloud = AnsibleHcloudPrimaryIP(module)
state = module.params["state"]
if state == "absent":
hcloud.delete_primary_ip()
elif state == "present":
hcloud.present_primary_ip()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,365 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_rdns
short_description: Create and manage reverse DNS entries on the Hetzner Cloud.
description:
- Create, update and delete reverse DNS entries on the Hetzner Cloud.
author:
- Lukas Kaemmerling (@lkaemmerling)
options:
server:
description:
- The name of the Hetzner Cloud server you want to add the reverse DNS entry to.
type: str
floating_ip:
description:
- The name of the Hetzner Cloud Floating IP you want to add the reverse DNS entry to.
type: str
primary_ip:
description:
- The name of the Hetzner Cloud Primary IP you want to add the reverse DNS entry to.
type: str
load_balancer:
description:
- The name of the Hetzner Cloud Load Balancer you want to add the reverse DNS entry to.
type: str
ip_address:
description:
- The IP address that should point to I(dns_ptr).
type: str
required: true
dns_ptr:
description:
- The DNS address the I(ip_address) should resolve to.
- Omit the param to reset the reverse DNS entry to the default value.
type: str
state:
description:
- State of the reverse DNS entry.
default: present
choices: [ absent, present ]
type: str
requirements:
- hcloud-python >= 1.3.0
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a reverse DNS entry for a server
hcloud_rdns:
server: my-server
ip_address: 123.123.123.123
dns_ptr: example.com
state: present
- name: Create a reverse DNS entry for a Floating IP
hcloud_rdns:
floating_ip: my-floating-ip
ip_address: 123.123.123.123
dns_ptr: example.com
state: present
- name: Create a reverse DNS entry for a Primary IP
hcloud_rdns:
primary_ip: my-primary-ip
ip_address: 123.123.123.123
dns_ptr: example.com
state: present
- name: Create a reverse DNS entry for a Load Balancer
hcloud_rdns:
load_balancer: my-load-balancer
ip_address: 123.123.123.123
dns_ptr: example.com
state: present
- name: Ensure the reverse DNS entry is absent (remove if needed)
hcloud_rdns:
server: my-server
ip_address: 123.123.123.123
dns_ptr: example.com
state: absent
"""
RETURN = """
hcloud_rdns:
description: The reverse DNS entry
returned: always
type: complex
contains:
server:
description: Name of the server
type: str
returned: always
sample: my-server
floating_ip:
description: Name of the Floating IP
type: str
returned: always
sample: my-floating-ip
primary_ip:
description: Name of the Primary IP
type: str
returned: always
sample: my-primary-ip
load_balancer:
description: Name of the Load Balancer
type: str
returned: always
sample: my-load-balancer
ip_address:
description: The IP address that point to the DNS ptr
type: str
returned: always
sample: 123.123.123.123
dns_ptr:
description: The DNS that resolves to the IP
type: str
returned: always
sample: example.com
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudReverseDNS(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_rdns")
self.hcloud_resource = None
self.hcloud_rdns = None
def _prepare_result(self):
result = {
"server": None,
"floating_ip": None,
"load_balancer": None,
"ip_address": to_native(self.hcloud_rdns["ip_address"]),
"dns_ptr": to_native(self.hcloud_rdns["dns_ptr"]),
}
if self.module.params.get("server"):
result["server"] = to_native(self.hcloud_resource.name)
elif self.module.params.get("floating_ip"):
result["floating_ip"] = to_native(self.hcloud_resource.name)
elif self.module.params.get("load_balancer"):
result["load_balancer"] = to_native(self.hcloud_resource.name)
elif self.module.params.get("primary_ip"):
result["primary_ip"] = to_native(self.hcloud_resource.name)
return result
def _get_resource(self):
try:
if self.module.params.get("server"):
self.hcloud_resource = self.client.servers.get_by_name(
self.module.params.get("server")
)
if self.hcloud_resource is None:
self.module.fail_json(msg="The selected server does not exist")
elif self.module.params.get("floating_ip"):
self.hcloud_resource = self.client.floating_ips.get_by_name(
self.module.params.get("floating_ip")
)
if self.hcloud_resource is None:
self.module.fail_json(msg="The selected Floating IP does not exist")
elif self.module.params.get("primary_ip"):
self.hcloud_resource = self.client.primary_ips.get_by_name(
self.module.params.get("primary_ip")
)
if self.hcloud_resource is None:
self.module.fail_json(msg="The selected Floating IP does not exist")
elif self.module.params.get("load_balancer"):
self.hcloud_resource = self.client.load_balancers.get_by_name(
self.module.params.get("load_balancer")
)
if self.hcloud_resource is None:
self.module.fail_json(msg="The selected Load Balancer does not exist")
except Exception as e:
self.module.fail_json(msg=e.message)
def _get_rdns(self):
ip_address = self.module.params.get("ip_address")
if utils.validate_ip_address(ip_address):
if self.module.params.get("server"):
if self.hcloud_resource.public_net.ipv4.ip == ip_address:
self.hcloud_rdns = {
"ip_address": self.hcloud_resource.public_net.ipv4.ip,
"dns_ptr": self.hcloud_resource.public_net.ipv4.dns_ptr,
}
else:
self.module.fail_json(msg="The selected server does not have this IP address")
elif self.module.params.get("floating_ip"):
if self.hcloud_resource.ip == ip_address:
self.hcloud_rdns = {
"ip_address": self.hcloud_resource.ip,
"dns_ptr": self.hcloud_resource.dns_ptr[0]["dns_ptr"],
}
else:
self.module.fail_json(msg="The selected Floating IP does not have this IP address")
elif self.module.params.get("primary_ip"):
if self.hcloud_resource.ip == ip_address:
self.hcloud_rdns = {
"ip_address": self.hcloud_resource.ip,
"dns_ptr": self.hcloud_resource.dns_ptr[0]["dns_ptr"],
}
else:
self.module.fail_json(msg="The selected Primary IP does not have this IP address")
elif self.module.params.get("load_balancer"):
if self.hcloud_resource.public_net.ipv4.ip == ip_address:
self.hcloud_rdns = {
"ip_address": self.hcloud_resource.public_net.ipv4.ip,
"dns_ptr": self.hcloud_resource.public_net.ipv4.dns_ptr,
}
else:
self.module.fail_json(msg="The selected Load Balancer does not have this IP address")
elif utils.validate_ip_v6_address(ip_address):
if self.module.params.get("server"):
for ipv6_address_dns_ptr in self.hcloud_resource.public_net.ipv6.dns_ptr:
if ipv6_address_dns_ptr["ip"] == ip_address:
self.hcloud_rdns = {
"ip_address": ipv6_address_dns_ptr["ip"],
"dns_ptr": ipv6_address_dns_ptr["dns_ptr"],
}
elif self.module.params.get("floating_ip"):
for ipv6_address_dns_ptr in self.hcloud_resource.dns_ptr:
if ipv6_address_dns_ptr["ip"] == ip_address:
self.hcloud_rdns = {
"ip_address": ipv6_address_dns_ptr["ip"],
"dns_ptr": ipv6_address_dns_ptr["dns_ptr"],
}
elif self.module.params.get("primary_ip"):
for ipv6_address_dns_ptr in self.hcloud_resource.dns_ptr:
if ipv6_address_dns_ptr["ip"] == ip_address:
self.hcloud_rdns = {
"ip_address": ipv6_address_dns_ptr["ip"],
"dns_ptr": ipv6_address_dns_ptr["dns_ptr"],
}
elif self.module.params.get("load_balancer"):
for ipv6_address_dns_ptr in self.hcloud_resource.public_net.ipv6.dns_ptr:
if ipv6_address_dns_ptr["ip"] == ip_address:
self.hcloud_rdns = {
"ip_address": ipv6_address_dns_ptr["ip"],
"dns_ptr": ipv6_address_dns_ptr["dns_ptr"],
}
else:
self.module.fail_json(msg="The given IP address is not valid")
def _create_rdns(self):
self.module.fail_on_missing_params(
required_params=["dns_ptr"]
)
params = {
"ip": self.module.params.get("ip_address"),
"dns_ptr": self.module.params.get("dns_ptr"),
}
if not self.module.check_mode:
try:
self.hcloud_resource.change_dns_ptr(**params).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_resource()
self._get_rdns()
def _update_rdns(self):
dns_ptr = self.module.params.get("dns_ptr")
if dns_ptr != self.hcloud_rdns["dns_ptr"]:
params = {
"ip": self.module.params.get("ip_address"),
"dns_ptr": dns_ptr,
}
if not self.module.check_mode:
try:
self.hcloud_resource.change_dns_ptr(**params).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_resource()
self._get_rdns()
def present_rdns(self):
self._get_resource()
self._get_rdns()
if self.hcloud_rdns is None:
self._create_rdns()
else:
self._update_rdns()
def delete_rdns(self):
self._get_resource()
self._get_rdns()
if self.hcloud_rdns is not None:
if not self.module.check_mode:
try:
self.hcloud_resource.change_dns_ptr(ip=self.hcloud_rdns['ip_address'], dns_ptr=None)
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self.hcloud_rdns = None
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
server={"type": "str"},
floating_ip={"type": "str"},
load_balancer={"type": "str"},
primary_ip={"type": "str"},
ip_address={"type": "str", "required": True},
dns_ptr={"type": "str"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
required_one_of=[['server', 'floating_ip', 'load_balancer', 'primary_ip']],
mutually_exclusive=[["server", "floating_ip", 'load_balancer', 'primary_ip']],
supports_check_mode=True,
)
def main():
module = AnsibleHcloudReverseDNS.define_module()
hcloud = AnsibleHcloudReverseDNS(module)
state = module.params["state"]
if state == "absent":
hcloud.delete_rdns()
elif state == "present":
hcloud.present_rdns()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,198 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_route
short_description: Create and delete cloud routes on the Hetzner Cloud.
description:
- Create, update and delete cloud routes on the Hetzner Cloud.
author:
- Lukas Kaemmerling (@lkaemmerling)
options:
network:
description:
- The name of the Hetzner Cloud Network.
type: str
required: true
destination:
description:
- Destination network or host of this route.
type: str
required: true
gateway:
description:
- Gateway for the route.
type: str
required: true
state:
description:
- State of the route.
default: present
choices: [ absent, present ]
type: str
requirements:
- hcloud-python >= 1.3.0
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a basic route
hcloud_route:
network: my-network
destination: 10.100.1.0/24
gateway: 10.0.1.1
state: present
- name: Ensure the route is absent
hcloud_route:
network: my-network
destination: 10.100.1.0/24
gateway: 10.0.1.1
state: absent
"""
RETURN = """
hcloud_route:
description: One Route of a Network
returned: always
type: complex
contains:
network:
description: Name of the Network
type: str
returned: always
sample: my-network
destination:
description: Destination network or host of this route
type: str
returned: always
sample: 10.0.0.0/8
gateway:
description: Gateway of the route
type: str
returned: always
sample: 10.0.0.1
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
from hcloud.networks.domain import NetworkRoute
except ImportError:
APIException = None
NetworkRoute = None
class AnsibleHcloudRoute(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_route")
self.hcloud_network = None
self.hcloud_route = None
def _prepare_result(self):
return {
"network": to_native(self.hcloud_network.name),
"destination": to_native(self.hcloud_route.destination),
"gateway": self.hcloud_route.gateway,
}
def _get_network(self):
try:
self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network"))
self.hcloud_route = None
except Exception as e:
self.module.fail_json(msg=e.message)
def _get_route(self):
destination = self.module.params.get("destination")
gateway = self.module.params.get("gateway")
for route in self.hcloud_network.routes:
if route.destination == destination and route.gateway == gateway:
self.hcloud_route = route
def _create_route(self):
route = NetworkRoute(
destination=self.module.params.get("destination"),
gateway=self.module.params.get('gateway')
)
if not self.module.check_mode:
try:
self.hcloud_network.add_route(route=route).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_network()
self._get_route()
def present_route(self):
self._get_network()
self._get_route()
if self.hcloud_route is None:
self._create_route()
def delete_route(self):
self._get_network()
self._get_route()
if self.hcloud_route is not None and self.hcloud_network is not None:
if not self.module.check_mode:
try:
self.hcloud_network.delete_route(self.hcloud_route).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self.hcloud_route = None
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
network={"type": "str", "required": True},
gateway={"type": "str", "required": True},
destination={"type": "str", "required": True},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudRoute.define_module()
hcloud = AnsibleHcloudRoute(module)
state = module.params["state"]
if state == "absent":
hcloud.delete_route()
elif state == "present":
hcloud.present_route()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,923 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_server
short_description: Create and manage cloud servers on the Hetzner Cloud.
description:
- Create, update and manage cloud servers on the Hetzner Cloud.
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the Hetzner Cloud server to manage.
- Only required if no server I(name) is given
type: int
name:
description:
- The Name of the Hetzner Cloud server to manage.
- Only required if no server I(id) is given or a server does not exist.
type: str
server_type:
description:
- The Server Type of the Hetzner Cloud server to manage.
- Required if server does not exist.
type: str
ssh_keys:
description:
- List of SSH key names
- The key names correspond to the SSH keys configured for your
Hetzner Cloud account access.
type: list
elements: str
volumes:
description:
- List of Volumes IDs that should be attached to the server on server creation.
type: list
elements: str
firewalls:
description:
- List of Firewall IDs that should be attached to the server on server creation.
type: list
elements: str
image:
description:
- Image the server should be created from.
- Required if server does not exist.
type: str
location:
description:
- Location of Server.
- Required if no I(datacenter) is given and server does not exist.
type: str
datacenter:
description:
- Datacenter of Server.
- Required of no I(location) is given and server does not exist.
type: str
backups:
description:
- Enable or disable Backups for the given Server.
type: bool
upgrade_disk:
description:
- Resize the disk size, when resizing a server.
- If you want to downgrade the server later, this value should be False.
type: bool
default: no
enable_ipv4:
description:
- Enables the public ipv4 address
type: bool
default: yes
enable_ipv6:
description:
- Enables the public ipv6 address
type: bool
default: yes
ipv4:
description:
- ID of the ipv4 Primary IP to use. If omitted and enable_ipv4 is true, a new ipv4 Primary IP will automatically be created
type: str
ipv6:
description:
- ID of the ipv6 Primary IP to use. If omitted and enable_ipv6 is true, a new ipv6 Primary IP will automatically be created.
type: str
private_networks:
description:
- List of private networks the server is attached to (name or ID)
- If None, private networks are left as they are (e.g. if previously added by hcloud_server_network),
if it has any other value (including []), only those networks are attached to the server.
type: list
elements: str
force_upgrade:
description:
- Deprecated
- Force the upgrade of the server.
- Power off the server if it is running on upgrade.
type: bool
default: no
force:
description:
- Force the update of the server.
- May power off the server if update.
type: bool
default: no
allow_deprecated_image:
description:
- Allows the creation of servers with deprecated images.
type: bool
default: no
user_data:
description:
- User Data to be passed to the server on creation.
- Only used if server does not exist.
type: str
rescue_mode:
description:
- Add the Hetzner rescue system type you want the server to be booted into.
type: str
labels:
description:
- User-defined labels (key-value pairs).
type: dict
delete_protection:
description:
- Protect the Server for deletion.
- Needs to be the same as I(rebuild_protection).
type: bool
rebuild_protection:
description:
- Protect the Server for rebuild.
- Needs to be the same as I(delete_protection).
type: bool
placement_group:
description:
- Placement Group of the server.
type: str
state:
description:
- State of the server.
default: present
choices: [ absent, present, restarted, started, stopped, rebuild ]
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a basic server
hcloud_server:
name: my-server
server_type: cx11
image: ubuntu-18.04
state: present
- name: Create a basic server with ssh key
hcloud_server:
name: my-server
server_type: cx11
image: ubuntu-18.04
location: fsn1
ssh_keys:
- me@myorganisation
state: present
- name: Resize an existing server
hcloud_server:
name: my-server
server_type: cx21
upgrade_disk: yes
state: present
- name: Ensure the server is absent (remove if needed)
hcloud_server:
name: my-server
state: absent
- name: Ensure the server is started
hcloud_server:
name: my-server
state: started
- name: Ensure the server is stopped
hcloud_server:
name: my-server
state: stopped
- name: Ensure the server is restarted
hcloud_server:
name: my-server
state: restarted
- name: Ensure the server is will be booted in rescue mode and therefore restarted
hcloud_server:
name: my-server
rescue_mode: linux64
state: restarted
- name: Ensure the server is rebuild
hcloud_server:
name: my-server
image: ubuntu-18.04
state: rebuild
- name: Add server to placement group
hcloud_server:
name: my-server
placement_group: my-placement-group
force: True
state: present
- name: Remove server from placement group
hcloud_server:
name: my-server
placement_group: null
state: present
- name: Add server with private network only
hcloud_server:
name: my-server
enable_ipv4: false
enable_ipv6: false
private_networks:
- my-network
- 4711
state: present
"""
RETURN = """
hcloud_server:
description: The server instance
returned: Always
type: complex
contains:
id:
description: Numeric identifier of the server
returned: always
type: int
sample: 1937415
name:
description: Name of the server
returned: always
type: str
sample: my-server
status:
description: Status of the server
returned: always
type: str
sample: running
server_type:
description: Name of the server type of the server
returned: always
type: str
sample: cx11
ipv4_address:
description: Public IPv4 address of the server
returned: always
type: str
sample: 116.203.104.109
ipv6:
description: IPv6 network of the server
returned: always
type: str
sample: 2a01:4f8:1c1c:c140::/64
private_networks:
description: List of private networks the server is attached to (name or ID)
returned: always
type: list
elements: str
sample: ['my-network', 'another-network', '4711']
location:
description: Name of the location of the server
returned: always
type: str
sample: fsn1
placement_group:
description: Placement Group of the server
type: str
returned: always
sample: 4711
version_added: "1.5.0"
datacenter:
description: Name of the datacenter of the server
returned: always
type: str
sample: fsn1-dc14
rescue_enabled:
description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot
returned: always
type: bool
sample: false
backup_window:
description: Time window (UTC) in which the backup will run, or null if the backups are not enabled
returned: always
type: bool
sample: 22-02
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
delete_protection:
description: True if server is protected for deletion
type: bool
returned: always
sample: false
version_added: "0.1.0"
rebuild_protection:
description: True if server is protected for rebuild
type: bool
returned: always
sample: false
version_added: "0.1.0"
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
from datetime import timedelta
try:
from hcloud.volumes.domain import Volume
from hcloud.ssh_keys.domain import SSHKey
from hcloud.servers.domain import Server, ServerCreatePublicNetwork
from hcloud.firewalls.domain import Firewall, FirewallResource
from hcloud.primary_ips.domain import PrimaryIP
from hcloud import APIException
except ImportError:
APIException = None
Volume = None
SSHKey = None
Server = None
ServerCreatePublicNetwork = None
Firewall = None
FirewallResource = None
PrimaryIP = None
class AnsibleHcloudServer(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_server")
self.hcloud_server = None
def _prepare_result(self):
image = None if self.hcloud_server.image is None else to_native(self.hcloud_server.image.name)
placement_group = None if self.hcloud_server.placement_group is None else to_native(
self.hcloud_server.placement_group.name)
ipv4_address = None if self.hcloud_server.public_net.ipv4 is None else to_native(
self.hcloud_server.public_net.ipv4.ip)
ipv6 = None if self.hcloud_server.public_net.ipv6 is None else to_native(self.hcloud_server.public_net.ipv6.ip)
backup_window = None if self.hcloud_server.backup_window is None else to_native(self.hcloud_server.backup_window)
return {
"id": to_native(self.hcloud_server.id),
"name": to_native(self.hcloud_server.name),
"ipv4_address": ipv4_address,
"ipv6": ipv6,
"private_networks": [to_native(net.network.name) for net in self.hcloud_server.private_net],
"image": image,
"server_type": to_native(self.hcloud_server.server_type.name),
"datacenter": to_native(self.hcloud_server.datacenter.name),
"location": to_native(self.hcloud_server.datacenter.location.name),
"placement_group": placement_group,
"rescue_enabled": self.hcloud_server.rescue_enabled,
"backup_window": backup_window,
"labels": self.hcloud_server.labels,
"delete_protection": self.hcloud_server.protection["delete"],
"rebuild_protection": self.hcloud_server.protection["rebuild"],
"status": to_native(self.hcloud_server.status),
}
def _get_server(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_server = self.client.servers.get_by_id(
self.module.params.get("id")
)
else:
self.hcloud_server = self.client.servers.get_by_name(
self.module.params.get("name")
)
except Exception as e:
self.module.fail_json(msg=e.message)
def _create_server(self):
self.module.fail_on_missing_params(
required_params=["name", "server_type", "image"]
)
params = {
"name": self.module.params.get("name"),
"server_type": self._get_server_type(),
"user_data": self.module.params.get("user_data"),
"labels": self.module.params.get("labels"),
"image": self._get_image(),
"placement_group": self._get_placement_group(),
"public_net": ServerCreatePublicNetwork(
enable_ipv4=self.module.params.get("enable_ipv4"),
enable_ipv6=self.module.params.get("enable_ipv6")
)
}
if self.module.params.get("ipv4") is not None:
p = self.client.primary_ips.get_by_name(self.module.params.get("ipv4"))
if not p:
p = self.client.primary_ips.get_by_id(self.module.params.get("ipv4"))
params["public_net"].ipv4 = p
if self.module.params.get("ipv6") is not None:
p = self.client.primary_ips.get_by_name(self.module.params.get("ipv6"))
if not p:
p = self.client.primary_ips.get_by_id(self.module.params.get("ipv6"))
params["public_net"].ipv6 = p
if self.module.params.get("private_networks") is not None:
_networks = []
for network_name_or_id in self.module.params.get("private_networks"):
_networks.append(
self.client.networks.get_by_name(network_name_or_id)
or self.client.networks.get_by_id(network_name_or_id)
)
params["networks"] = _networks
if self.module.params.get("ssh_keys") is not None:
params["ssh_keys"] = [
SSHKey(name=ssh_key_name)
for ssh_key_name in self.module.params.get("ssh_keys")
]
if self.module.params.get("volumes") is not None:
params["volumes"] = [
Volume(id=volume_id) for volume_id in self.module.params.get("volumes")
]
if self.module.params.get("firewalls") is not None:
params["firewalls"] = []
for fw in self.module.params.get("firewalls"):
f = self.client.firewalls.get_by_name(fw)
if f is not None:
# When firewall name is not available look for id instead
params["firewalls"].append(f)
else:
params["firewalls"].append(self.client.firewalls.get_by_id(fw))
if self.module.params.get("location") is None and self.module.params.get("datacenter") is None:
# When not given, the API will choose the location.
params["location"] = None
params["datacenter"] = None
elif self.module.params.get("location") is not None and self.module.params.get("datacenter") is None:
params["location"] = self.client.locations.get_by_name(
self.module.params.get("location")
)
elif self.module.params.get("location") is None and self.module.params.get("datacenter") is not None:
params["datacenter"] = self.client.datacenters.get_by_name(
self.module.params.get("datacenter")
)
if self.module.params.get("state") == "stopped":
params["start_after_create"] = False
if not self.module.check_mode:
try:
resp = self.client.servers.create(**params)
self.result["root_password"] = resp.root_password
resp.action.wait_until_finished(max_retries=1000)
[action.wait_until_finished() for action in resp.next_actions]
rescue_mode = self.module.params.get("rescue_mode")
if rescue_mode:
self._get_server()
self._set_rescue_mode(rescue_mode)
backups = self.module.params.get("backups")
if backups:
self._get_server()
self.hcloud_server.enable_backup().wait_until_finished()
delete_protection = self.module.params.get("delete_protection")
rebuild_protection = self.module.params.get("rebuild_protection")
if delete_protection is not None and rebuild_protection is not None:
self._get_server()
self.hcloud_server.change_protection(delete=delete_protection,
rebuild=rebuild_protection).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_server()
def _get_image(self):
image_resp = self.client.images.get_list(name=self.module.params.get("image"), include_deprecated=True)
images = getattr(image_resp, 'images')
image = None
if images is not None and len(images) > 0:
# If image name is not available look for id instead
image = images[0]
else:
try:
image = self.client.images.get_by_id(self.module.params.get("image"))
except Exception:
self.module.fail_json(msg="Image %s was not found" % self.module.params.get('image'))
if image.deprecated is not None:
available_until = image.deprecated + timedelta(days=90)
if self.module.params.get("allow_deprecated_image"):
self.module.warn(
"You try to use a deprecated image. The image %s will continue to be available until %s.") % (
image.name, available_until.strftime('%Y-%m-%d'))
else:
self.module.fail_json(
msg=("You try to use a deprecated image. The image %s will continue to be available until %s." +
" If you want to use this image use allow_deprecated_image=yes."
) % (image.name, available_until.strftime('%Y-%m-%d')))
return image
def _get_server_type(self):
server_type = self.client.server_types.get_by_name(
self.module.params.get("server_type")
)
if server_type is None:
try:
server_type = self.client.server_types.get_by_id(self.module.params.get("server_type"))
except Exception:
self.module.fail_json(msg="server_type %s was not found" % self.module.params.get('server_type'))
return server_type
def _get_placement_group(self):
if self.module.params.get("placement_group") is None:
return None
placement_group = self.client.placement_groups.get_by_name(
self.module.params.get("placement_group")
)
if placement_group is None:
try:
placement_group = self.client.placement_groups.get_by_id(self.module.params.get("placement_group"))
except Exception:
self.module.fail_json(
msg="placement_group %s was not found" % self.module.params.get("placement_group"))
return placement_group
def _get_primary_ip(self, field):
if self.module.params.get(field) is None:
return None
primary_ip = self.client.primary_ips.get_by_name(
self.module.params.get(field)
)
if primary_ip is None:
try:
primary_ip = self.client.primary_ips.get_by_id(self.module.params.get(field))
except Exception as e:
self.module.fail_json(
msg="primary_ip %s was not found" % self.module.params.get(field))
return primary_ip
def _update_server(self):
if "force_upgrade" in self.module.params:
self.module.warn("force_upgrade is deprecated, use force instead")
try:
previous_server_status = self.hcloud_server.status
rescue_mode = self.module.params.get("rescue_mode")
if rescue_mode and self.hcloud_server.rescue_enabled is False:
if not self.module.check_mode:
self._set_rescue_mode(rescue_mode)
self._mark_as_changed()
elif not rescue_mode and self.hcloud_server.rescue_enabled is True:
if not self.module.check_mode:
self.hcloud_server.disable_rescue().wait_until_finished()
self._mark_as_changed()
if self.module.params.get("backups") and self.hcloud_server.backup_window is None:
if not self.module.check_mode:
self.hcloud_server.enable_backup().wait_until_finished()
self._mark_as_changed()
elif not self.module.params.get("backups") and self.hcloud_server.backup_window is not None:
if not self.module.check_mode:
self.hcloud_server.disable_backup().wait_until_finished()
self._mark_as_changed()
labels = self.module.params.get("labels")
if labels is not None and labels != self.hcloud_server.labels:
if not self.module.check_mode:
self.hcloud_server.update(labels=labels)
self._mark_as_changed()
wanted_firewalls = self.module.params.get("firewalls")
if wanted_firewalls is not None:
# Removing existing but not wanted firewalls
for current_firewall in self.hcloud_server.public_net.firewalls:
if current_firewall.firewall.name not in wanted_firewalls:
self._mark_as_changed()
if not self.module.check_mode:
r = FirewallResource(type="server", server=self.hcloud_server)
actions = self.client.firewalls.remove_from_resources(current_firewall.firewall, [r])
for a in actions:
a.wait_until_finished()
# Adding wanted firewalls that doesn't exist yet
for fname in wanted_firewalls:
found = False
for f in self.hcloud_server.public_net.firewalls:
if f.firewall.name == fname:
found = True
break
if not found:
self._mark_as_changed()
if not self.module.check_mode:
fw = self.client.firewalls.get_by_name(fname)
if fw is None:
self.module.fail_json(msg="firewall %s was not found" % fname)
r = FirewallResource(type="server", server=self.hcloud_server)
actions = self.client.firewalls.apply_to_resources(fw, [r])
for a in actions:
a.wait_until_finished()
if "placement_group" in self.module.params:
if self.module.params["placement_group"] is None and self.hcloud_server.placement_group is not None:
if not self.module.check_mode:
self.hcloud_server.remove_from_placement_group().wait_until_finished()
self._mark_as_changed()
else:
placement_group = self._get_placement_group()
if (
placement_group is not None and
(
self.hcloud_server.placement_group is None or
self.hcloud_server.placement_group.id != placement_group.id
)
):
self.stop_server_if_forced()
if not self.module.check_mode:
self.hcloud_server.add_to_placement_group(placement_group)
self._mark_as_changed()
if "ipv4" in self.module.params:
if (
self.module.params["ipv4"] is None and
self.hcloud_server.public_net.primary_ipv4 is not None and
not self.module.params.get("enable_ipv4")
):
self.stop_server_if_forced()
if not self.module.check_mode:
self.hcloud_server.public_net.primary_ipv4.unassign().wait_until_finished()
self._mark_as_changed()
else:
primary_ip = self._get_primary_ip("ipv4")
if (
primary_ip is not None and
(
self.hcloud_server.public_net.primary_ipv4 is None or
self.hcloud_server.public_net.primary_ipv4.id != primary_ip.id
)
):
self.stop_server_if_forced()
if not self.module.check_mode:
if self.hcloud_server.public_net.primary_ipv4:
self.hcloud_server.public_net.primary_ipv4.unassign().wait_until_finished()
primary_ip.assign(self.hcloud_server.id, "server").wait_until_finished()
self._mark_as_changed()
if "ipv6" in self.module.params:
if (
(self.module.params["ipv6"] is None or self.module.params["ipv6"] == "") and
self.hcloud_server.public_net.primary_ipv6 is not None and
not self.module.params.get("enable_ipv6")
):
self.stop_server_if_forced()
if not self.module.check_mode:
self.hcloud_server.public_net.primary_ipv6.unassign().wait_until_finished()
self._mark_as_changed()
else:
primary_ip = self._get_primary_ip("ipv6")
if (
primary_ip is not None and
(
self.hcloud_server.public_net.primary_ipv6 is None or
self.hcloud_server.public_net.primary_ipv6.id != primary_ip.id
)
):
self.stop_server_if_forced()
if not self.module.check_mode:
if self.hcloud_server.public_net.primary_ipv6 is not None:
self.hcloud_server.public_net.primary_ipv6.unassign().wait_until_finished()
primary_ip.assign(self.hcloud_server.id, "server").wait_until_finished()
self._mark_as_changed()
if "private_networks" in self.module.params and self.module.params["private_networks"] is not None:
if not bool(self.module.params["private_networks"]):
# This handles None, "" and []
networks_target = {}
else:
_networks = {}
for network_name_or_id in self.module.params.get("private_networks"):
_found_network = self.client.networks.get_by_name(network_name_or_id) \
or self.client.networks.get_by_id(network_name_or_id)
_networks.update(
{_found_network.id: _found_network}
)
networks_target = _networks
networks_is = dict()
for p_network in self.hcloud_server.private_net:
networks_is.update({p_network.network.id: p_network.network})
for network_id in set(list(networks_is) + list(networks_target)):
if network_id in networks_is and network_id not in networks_target:
self.stop_server_if_forced()
if not self.module.check_mode:
self.hcloud_server.detach_from_network(networks_is[network_id]).wait_until_finished()
self._mark_as_changed()
elif network_id in networks_target and network_id not in networks_is:
self.stop_server_if_forced()
if not self.module.check_mode:
self.hcloud_server.attach_to_network(networks_target[network_id]).wait_until_finished()
self._mark_as_changed()
server_type = self.module.params.get("server_type")
if server_type is not None and self.hcloud_server.server_type.name != server_type:
self.stop_server_if_forced()
timeout = 100
if self.module.params.get("upgrade_disk"):
timeout = (
1000
) # When we upgrade the disk to the resize progress takes some more time.
if not self.module.check_mode:
self.hcloud_server.change_type(
server_type=self._get_server_type(),
upgrade_disk=self.module.params.get("upgrade_disk"),
).wait_until_finished(timeout)
self._mark_as_changed()
if (
not self.module.check_mode and
(
(
self.module.params.get("state") == "present" and
previous_server_status == Server.STATUS_RUNNING
) or
self.module.params.get("state") == "started"
)
):
self.start_server()
delete_protection = self.module.params.get("delete_protection")
rebuild_protection = self.module.params.get("rebuild_protection")
if (delete_protection is not None and rebuild_protection is not None) and (
delete_protection != self.hcloud_server.protection["delete"] or rebuild_protection !=
self.hcloud_server.protection["rebuild"]):
if not self.module.check_mode:
self.hcloud_server.change_protection(delete=delete_protection,
rebuild=rebuild_protection).wait_until_finished()
self._mark_as_changed()
self._get_server()
except Exception as e:
self.module.fail_json(msg=e)
def _set_rescue_mode(self, rescue_mode):
if self.module.params.get("ssh_keys"):
resp = self.hcloud_server.enable_rescue(type=rescue_mode,
ssh_keys=[self.client.ssh_keys.get_by_name(ssh_key_name).id
for
ssh_key_name in
self.module.params.get("ssh_keys")])
else:
resp = self.hcloud_server.enable_rescue(type=rescue_mode)
resp.action.wait_until_finished()
self.result["root_password"] = resp.root_password
def start_server(self):
try:
if self.hcloud_server:
if self.hcloud_server.status != Server.STATUS_RUNNING:
if not self.module.check_mode:
self.client.servers.power_on(self.hcloud_server).wait_until_finished()
self._mark_as_changed()
self._get_server()
except Exception as e:
self.module.fail_json(msg=e.message)
def stop_server(self):
try:
if self.hcloud_server:
if self.hcloud_server.status != Server.STATUS_OFF:
if not self.module.check_mode:
self.client.servers.power_off(self.hcloud_server).wait_until_finished()
self._mark_as_changed()
self._get_server()
except Exception as e:
self.module.fail_json(msg=e.message)
def stop_server_if_forced(self):
previous_server_status = self.hcloud_server.status
if previous_server_status == Server.STATUS_RUNNING and not self.module.check_mode:
if (
self.module.params.get("force_upgrade") or
self.module.params.get("force") or
self.module.params.get("state") == "stopped"
):
self.stop_server() # Only stopped server can be upgraded
return previous_server_status
else:
self.module.warn(
"You can not upgrade a running instance %s. You need to stop the instance or use force=yes."
% self.hcloud_server.name
)
return None
def rebuild_server(self):
self.module.fail_on_missing_params(
required_params=["image"]
)
try:
if not self.module.check_mode:
image = self._get_image()
self.client.servers.rebuild(self.hcloud_server, image).wait_until_finished()
self._mark_as_changed()
self._get_server()
except Exception as e:
self.module.fail_json(msg=e.message)
def present_server(self):
self._get_server()
if self.hcloud_server is None:
self._create_server()
else:
self._update_server()
def delete_server(self):
try:
self._get_server()
if self.hcloud_server is not None:
if not self.module.check_mode:
self.client.servers.delete(self.hcloud_server).wait_until_finished()
self._mark_as_changed()
self.hcloud_server = None
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
image={"type": "str"},
server_type={"type": "str"},
location={"type": "str"},
datacenter={"type": "str"},
user_data={"type": "str"},
ssh_keys={"type": "list", "elements": "str", "no_log": False},
volumes={"type": "list", "elements": "str"},
firewalls={"type": "list", "elements": "str"},
labels={"type": "dict"},
backups={"type": "bool"},
upgrade_disk={"type": "bool", "default": False},
enable_ipv4={"type": "bool", "default": True},
enable_ipv6={"type": "bool", "default": True},
ipv4={"type": "str"},
ipv6={"type": "str"},
private_networks={"type": "list", "elements": "str", "default": None},
force={"type": "bool", "default": False},
force_upgrade={"type": "bool", "default": False},
allow_deprecated_image={"type": "bool", "default": False},
rescue_mode={"type": "str"},
delete_protection={"type": "bool"},
rebuild_protection={"type": "bool"},
placement_group={"type": "str"},
state={
"choices": ["absent", "present", "restarted", "started", "stopped", "rebuild"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
required_one_of=[['id', 'name']],
mutually_exclusive=[["location", "datacenter"]],
required_together=[["delete_protection", "rebuild_protection"]],
supports_check_mode=True,
)
def main():
module = AnsibleHcloudServer.define_module()
hcloud = AnsibleHcloudServer(module)
state = module.params.get("state")
if state == "absent":
hcloud.delete_server()
elif state == "present":
hcloud.present_server()
elif state == "started":
hcloud.present_server()
hcloud.start_server()
elif state == "stopped":
hcloud.present_server()
hcloud.stop_server()
elif state == "restarted":
hcloud.present_server()
hcloud.stop_server()
hcloud.start_server()
elif state == "rebuild":
hcloud.present_server()
hcloud.rebuild_server()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,242 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_server_info
short_description: Gather infos about your Hetzner Cloud servers.
description:
- Gather infos about your Hetzner Cloud servers.
- This module was called C(hcloud_server_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_facts).
Note that the M(hetzner.hcloud.hcloud_server_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_info)!
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the server you want to get.
type: int
name:
description:
- The name of the server you want to get.
type: str
label_selector:
description:
- The label selector for the server you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud server infos
hcloud_server_info:
register: output
- name: Print the gathered infos
debug:
var: output.hcloud_server_info
"""
RETURN = """
hcloud_server_info:
description: The server infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the server
returned: always
type: int
sample: 1937415
name:
description: Name of the server
returned: always
type: str
sample: my-server
status:
description: Status of the server
returned: always
type: str
sample: running
server_type:
description: Name of the server type of the server
returned: always
type: str
sample: cx11
ipv4_address:
description: Public IPv4 address of the server
returned: always
type: str
sample: 116.203.104.109
ipv6:
description: IPv6 network of the server
returned: always
type: str
sample: 2a01:4f8:1c1c:c140::/64
private_networks:
description: List of private networks the server is attached to (name)
returned: always
type: list
elements: str
sample: ['my-network', 'another-network']
location:
description: Name of the location of the server
returned: always
type: str
sample: fsn1
placement_group:
description: Placement Group of the server
type: str
returned: always
sample: 4711
version_added: "1.5.0"
datacenter:
description: Name of the datacenter of the server
returned: always
type: str
sample: fsn1-dc14
rescue_enabled:
description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot
returned: always
type: bool
sample: false
backup_window:
description: Time window (UTC) in which the backup will run, or null if the backups are not enabled
returned: always
type: bool
sample: 22-02
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
delete_protection:
description: True if server is protected for deletion
type: bool
returned: always
sample: false
version_added: "0.1.0"
rebuild_protection:
description: True if server is protected for rebuild
type: bool
returned: always
sample: false
version_added: "0.1.0"
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudServerInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_server_info")
self.hcloud_server_info = None
def _prepare_result(self):
tmp = []
for server in self.hcloud_server_info:
if server is not None:
image = None if server.image is None else to_native(server.image.name)
placement_group = None if server.placement_group is None else to_native(server.placement_group.name)
ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip)
ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip)
backup_window = None if server.backup_window is None else to_native(server.backup_window)
tmp.append({
"id": to_native(server.id),
"name": to_native(server.name),
"ipv4_address": ipv4_address,
"ipv6": ipv6,
"private_networks": [to_native(net.network.name) for net in server.private_net],
"image": image,
"server_type": to_native(server.server_type.name),
"datacenter": to_native(server.datacenter.name),
"location": to_native(server.datacenter.location.name),
"placement_group": placement_group,
"rescue_enabled": server.rescue_enabled,
"backup_window": backup_window,
"labels": server.labels,
"status": to_native(server.status),
"delete_protection": server.protection["delete"],
"rebuild_protection": server.protection["rebuild"],
})
return tmp
def get_servers(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_server_info = [self.client.servers.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_server_info = [self.client.servers.get_by_name(
self.module.params.get("name")
)]
elif self.module.params.get("label_selector") is not None:
self.hcloud_server_info = self.client.servers.get_all(
label_selector=self.module.params.get("label_selector"))
else:
self.hcloud_server_info = self.client.servers.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
label_selector={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudServerInfo.define_module()
is_old_facts = module._name == 'hcloud_server_facts'
if is_old_facts:
module.deprecate("The 'hcloud_server_facts' module has been renamed to 'hcloud_server_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudServerInfo(module)
hcloud.get_servers()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_server_facts': result['hcloud_server_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_server_info': result['hcloud_server_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,242 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_server_info
short_description: Gather infos about your Hetzner Cloud servers.
description:
- Gather infos about your Hetzner Cloud servers.
- This module was called C(hcloud_server_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_facts).
Note that the M(hetzner.hcloud.hcloud_server_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_info)!
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the server you want to get.
type: int
name:
description:
- The name of the server you want to get.
type: str
label_selector:
description:
- The label selector for the server you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud server infos
hcloud_server_info:
register: output
- name: Print the gathered infos
debug:
var: output.hcloud_server_info
"""
RETURN = """
hcloud_server_info:
description: The server infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the server
returned: always
type: int
sample: 1937415
name:
description: Name of the server
returned: always
type: str
sample: my-server
status:
description: Status of the server
returned: always
type: str
sample: running
server_type:
description: Name of the server type of the server
returned: always
type: str
sample: cx11
ipv4_address:
description: Public IPv4 address of the server
returned: always
type: str
sample: 116.203.104.109
ipv6:
description: IPv6 network of the server
returned: always
type: str
sample: 2a01:4f8:1c1c:c140::/64
private_networks:
description: List of private networks the server is attached to (name)
returned: always
type: list
elements: str
sample: ['my-network', 'another-network']
location:
description: Name of the location of the server
returned: always
type: str
sample: fsn1
placement_group:
description: Placement Group of the server
type: str
returned: always
sample: 4711
version_added: "1.5.0"
datacenter:
description: Name of the datacenter of the server
returned: always
type: str
sample: fsn1-dc14
rescue_enabled:
description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot
returned: always
type: bool
sample: false
backup_window:
description: Time window (UTC) in which the backup will run, or null if the backups are not enabled
returned: always
type: bool
sample: 22-02
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
delete_protection:
description: True if server is protected for deletion
type: bool
returned: always
sample: false
version_added: "0.1.0"
rebuild_protection:
description: True if server is protected for rebuild
type: bool
returned: always
sample: false
version_added: "0.1.0"
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudServerInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_server_info")
self.hcloud_server_info = None
def _prepare_result(self):
tmp = []
for server in self.hcloud_server_info:
if server is not None:
image = None if server.image is None else to_native(server.image.name)
placement_group = None if server.placement_group is None else to_native(server.placement_group.name)
ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip)
ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip)
backup_window = None if server.backup_window is None else to_native(server.backup_window)
tmp.append({
"id": to_native(server.id),
"name": to_native(server.name),
"ipv4_address": ipv4_address,
"ipv6": ipv6,
"private_networks": [to_native(net.network.name) for net in server.private_net],
"image": image,
"server_type": to_native(server.server_type.name),
"datacenter": to_native(server.datacenter.name),
"location": to_native(server.datacenter.location.name),
"placement_group": placement_group,
"rescue_enabled": server.rescue_enabled,
"backup_window": backup_window,
"labels": server.labels,
"status": to_native(server.status),
"delete_protection": server.protection["delete"],
"rebuild_protection": server.protection["rebuild"],
})
return tmp
def get_servers(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_server_info = [self.client.servers.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_server_info = [self.client.servers.get_by_name(
self.module.params.get("name")
)]
elif self.module.params.get("label_selector") is not None:
self.hcloud_server_info = self.client.servers.get_all(
label_selector=self.module.params.get("label_selector"))
else:
self.hcloud_server_info = self.client.servers.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
label_selector={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudServerInfo.define_module()
is_old_facts = module._name == 'hcloud_server_facts'
if is_old_facts:
module.deprecate("The 'hcloud_server_facts' module has been renamed to 'hcloud_server_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudServerInfo(module)
hcloud.get_servers()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_server_facts': result['hcloud_server_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_server_info': result['hcloud_server_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,246 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_server_network
short_description: Manage the relationship between Hetzner Cloud Networks and servers
description:
- Create and delete the relationship Hetzner Cloud Networks and servers
author:
- Lukas Kaemmerling (@lkaemmerling)
options:
network:
description:
- The name of the Hetzner Cloud Networks.
type: str
required: true
server:
description:
- The name of the Hetzner Cloud server.
type: str
required: true
ip:
description:
- The IP the server should have.
type: str
alias_ips:
description:
- Alias IPs the server has.
type: list
elements: str
state:
description:
- State of the server_network.
default: present
choices: [ absent, present ]
type: str
requirements:
- hcloud-python >= 1.3.0
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a basic server network
hcloud_server_network:
network: my-network
server: my-server
state: present
- name: Create a server network and specify the ip address
hcloud_server_network:
network: my-network
server: my-server
ip: 10.0.0.1
state: present
- name: Create a server network and add alias ips
hcloud_server_network:
network: my-network
server: my-server
ip: 10.0.0.1
alias_ips:
- 10.1.0.1
- 10.2.0.1
state: present
- name: Ensure the server network is absent (remove if needed)
hcloud_server_network:
network: my-network
server: my-server
state: absent
"""
RETURN = """
hcloud_server_network:
description: The relationship between a server and a network
returned: always
type: complex
contains:
network:
description: Name of the Network
type: str
returned: always
sample: my-network
server:
description: Name of the server
type: str
returned: always
sample: my-server
ip:
description: IP of the server within the Network ip range
type: str
returned: always
sample: 10.0.0.8
alias_ips:
description: Alias IPs of the server within the Network ip range
type: str
returned: always
sample: [10.1.0.1, ...]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudServerNetwork(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_server_network")
self.hcloud_network = None
self.hcloud_server = None
self.hcloud_server_network = None
def _prepare_result(self):
return {
"network": to_native(self.hcloud_network.name),
"server": to_native(self.hcloud_server.name),
"ip": to_native(self.hcloud_server_network.ip),
"alias_ips": self.hcloud_server_network.alias_ips,
}
def _get_server_and_network(self):
try:
self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network"))
self.hcloud_server = self.client.servers.get_by_name(self.module.params.get("server"))
self.hcloud_server_network = None
except Exception as e:
self.module.fail_json(msg=e.message)
def _get_server_network(self):
for privateNet in self.hcloud_server.private_net:
if privateNet.network.id == self.hcloud_network.id:
self.hcloud_server_network = privateNet
def _create_server_network(self):
params = {
"network": self.hcloud_network
}
if self.module.params.get("ip") is not None:
params["ip"] = self.module.params.get("ip")
if self.module.params.get("alias_ips") is not None:
params["alias_ips"] = self.module.params.get("alias_ips")
if not self.module.check_mode:
try:
self.hcloud_server.attach_to_network(**params).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_server_and_network()
self._get_server_network()
def _update_server_network(self):
params = {
"network": self.hcloud_network
}
alias_ips = self.module.params.get("alias_ips")
if alias_ips is not None and sorted(self.hcloud_server_network.alias_ips) != sorted(alias_ips):
params["alias_ips"] = alias_ips
if not self.module.check_mode:
try:
self.hcloud_server.change_alias_ips(**params).wait_until_finished()
except APIException as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_server_and_network()
self._get_server_network()
def present_server_network(self):
self._get_server_and_network()
self._get_server_network()
if self.hcloud_server_network is None:
self._create_server_network()
else:
self._update_server_network()
def delete_server_network(self):
self._get_server_and_network()
self._get_server_network()
if self.hcloud_server_network is not None and self.hcloud_server is not None:
if not self.module.check_mode:
try:
self.hcloud_server.detach_from_network(self.hcloud_server_network.network).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self.hcloud_server_network = None
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
network={"type": "str", "required": True},
server={"type": "str", "required": True},
ip={"type": "str"},
alias_ips={"type": "list", "elements": "str"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudServerNetwork.define_module()
hcloud = AnsibleHcloudServerNetwork(module)
state = module.params["state"]
if state == "absent":
hcloud.delete_server_network()
elif state == "present":
hcloud.present_server_network()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,182 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_server_type_info
short_description: Gather infos about the Hetzner Cloud server types.
description:
- Gather infos about your Hetzner Cloud server types.
- This module was called C(hcloud_server_type_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_type_facts).
Note that the M(hetzner.hcloud.hcloud_server_type_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_type_info)!
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the server type you want to get.
type: int
name:
description:
- The name of the server type you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud server type infos
hcloud_server_type_info:
register: output
- name: Print the gathered infos
debug:
var: output.hcloud_server_type_info
"""
RETURN = """
hcloud_server_type_info:
description: The server type infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the server type
returned: always
type: int
sample: 1937415
name:
description: Name of the server type
returned: always
type: str
sample: fsn1
description:
description: Detail description of the server type
returned: always
type: str
sample: Falkenstein DC Park 1
cores:
description: Number of cpu cores a server of this type will have
returned: always
type: int
sample: 1
memory:
description: Memory a server of this type will have in GB
returned: always
type: int
sample: 1
disk:
description: Disk size a server of this type will have in GB
returned: always
type: int
sample: 25
storage_type:
description: Type of server boot drive
returned: always
type: str
sample: local
cpu_type:
description: Type of cpu
returned: always
type: str
sample: shared
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudServerTypeInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_server_type_info")
self.hcloud_server_type_info = None
def _prepare_result(self):
tmp = []
for server_type in self.hcloud_server_type_info:
if server_type is not None:
tmp.append({
"id": to_native(server_type.id),
"name": to_native(server_type.name),
"description": to_native(server_type.description),
"cores": server_type.cores,
"memory": server_type.memory,
"disk": server_type.disk,
"storage_type": to_native(server_type.storage_type),
"cpu_type": to_native(server_type.cpu_type)
})
return tmp
def get_server_types(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_server_type_info = [self.client.server_types.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_server_type_info = [self.client.server_types.get_by_name(
self.module.params.get("name")
)]
else:
self.hcloud_server_type_info = self.client.server_types.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudServerTypeInfo.define_module()
is_old_facts = module._name == 'hcloud_server_type_facts'
if is_old_facts:
module.deprecate("The 'hcloud_server_type_info' module has been renamed to 'hcloud_server_type_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudServerTypeInfo(module)
hcloud.get_server_types()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_server_type_info': result['hcloud_server_type_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_server_type_info': result['hcloud_server_type_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,182 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_server_type_info
short_description: Gather infos about the Hetzner Cloud server types.
description:
- Gather infos about your Hetzner Cloud server types.
- This module was called C(hcloud_server_type_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_type_facts).
Note that the M(hetzner.hcloud.hcloud_server_type_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_type_info)!
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the server type you want to get.
type: int
name:
description:
- The name of the server type you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud server type infos
hcloud_server_type_info:
register: output
- name: Print the gathered infos
debug:
var: output.hcloud_server_type_info
"""
RETURN = """
hcloud_server_type_info:
description: The server type infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the server type
returned: always
type: int
sample: 1937415
name:
description: Name of the server type
returned: always
type: str
sample: fsn1
description:
description: Detail description of the server type
returned: always
type: str
sample: Falkenstein DC Park 1
cores:
description: Number of cpu cores a server of this type will have
returned: always
type: int
sample: 1
memory:
description: Memory a server of this type will have in GB
returned: always
type: int
sample: 1
disk:
description: Disk size a server of this type will have in GB
returned: always
type: int
sample: 25
storage_type:
description: Type of server boot drive
returned: always
type: str
sample: local
cpu_type:
description: Type of cpu
returned: always
type: str
sample: shared
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudServerTypeInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_server_type_info")
self.hcloud_server_type_info = None
def _prepare_result(self):
tmp = []
for server_type in self.hcloud_server_type_info:
if server_type is not None:
tmp.append({
"id": to_native(server_type.id),
"name": to_native(server_type.name),
"description": to_native(server_type.description),
"cores": server_type.cores,
"memory": server_type.memory,
"disk": server_type.disk,
"storage_type": to_native(server_type.storage_type),
"cpu_type": to_native(server_type.cpu_type)
})
return tmp
def get_server_types(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_server_type_info = [self.client.server_types.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_server_type_info = [self.client.server_types.get_by_name(
self.module.params.get("name")
)]
else:
self.hcloud_server_type_info = self.client.server_types.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudServerTypeInfo.define_module()
is_old_facts = module._name == 'hcloud_server_type_facts'
if is_old_facts:
module.deprecate("The 'hcloud_server_type_info' module has been renamed to 'hcloud_server_type_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudServerTypeInfo(module)
hcloud.get_server_types()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_server_type_info': result['hcloud_server_type_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_server_type_info': result['hcloud_server_type_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,251 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_ssh_key
short_description: Create and manage ssh keys on the Hetzner Cloud.
description:
- Create, update and manage ssh keys on the Hetzner Cloud.
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the Hetzner Cloud ssh_key to manage.
- Only required if no ssh_key I(name) is given
type: int
name:
description:
- The Name of the Hetzner Cloud ssh_key to manage.
- Only required if no ssh_key I(id) is given or a ssh_key does not exist.
type: str
fingerprint:
description:
- The Fingerprint of the Hetzner Cloud ssh_key to manage.
- Only required if no ssh_key I(id) or I(name) is given.
type: str
labels:
description:
- User-defined labels (key-value pairs)
type: dict
public_key:
description:
- The Public Key to add.
- Required if ssh_key does not exist.
type: str
state:
description:
- State of the ssh_key.
default: present
choices: [ absent, present ]
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a basic ssh_key
hcloud_ssh_key:
name: my-ssh_key
public_key: "ssh-rsa AAAjjk76kgf...Xt"
state: present
- name: Create a ssh_key with labels
hcloud_ssh_key:
name: my-ssh_key
public_key: "ssh-rsa AAAjjk76kgf...Xt"
labels:
key: value
mylabel: 123
state: present
- name: Ensure the ssh_key is absent (remove if needed)
hcloud_ssh_key:
name: my-ssh_key
state: absent
"""
RETURN = """
hcloud_ssh_key:
description: The ssh_key instance
returned: Always
type: complex
contains:
id:
description: ID of the ssh_key
type: int
returned: Always
sample: 12345
name:
description: Name of the ssh_key
type: str
returned: Always
sample: my-ssh-key
fingerprint:
description: Fingerprint of the ssh_key
type: str
returned: Always
sample: b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f
public_key:
description: Public key of the ssh_key
type: str
returned: Always
sample: "ssh-rsa AAAjjk76kgf...Xt"
labels:
description: User-defined labels (key-value pairs)
type: dict
returned: Always
sample:
key: value
mylabel: 123
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud.volumes.domain import Volume
from hcloud.ssh_keys.domain import SSHKey
from hcloud.ssh_keys.domain import Server
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudSSHKey(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_ssh_key")
self.hcloud_ssh_key = None
def _prepare_result(self):
return {
"id": to_native(self.hcloud_ssh_key.id),
"name": to_native(self.hcloud_ssh_key.name),
"fingerprint": to_native(self.hcloud_ssh_key.fingerprint),
"public_key": to_native(self.hcloud_ssh_key.public_key),
"labels": self.hcloud_ssh_key.labels,
}
def _get_ssh_key(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_ssh_key = self.client.ssh_keys.get_by_id(
self.module.params.get("id")
)
elif self.module.params.get("fingerprint") is not None:
self.hcloud_ssh_key = self.client.ssh_keys.get_by_fingerprint(
self.module.params.get("fingerprint")
)
elif self.module.params.get("name") is not None:
self.hcloud_ssh_key = self.client.ssh_keys.get_by_name(
self.module.params.get("name")
)
except Exception as e:
self.module.fail_json(msg=e.message)
def _create_ssh_key(self):
self.module.fail_on_missing_params(
required_params=["name", "public_key"]
)
params = {
"name": self.module.params.get("name"),
"public_key": self.module.params.get("public_key"),
"labels": self.module.params.get("labels")
}
if not self.module.check_mode:
try:
self.client.ssh_keys.create(**params)
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_ssh_key()
def _update_ssh_key(self):
name = self.module.params.get("name")
if name is not None and self.hcloud_ssh_key.name != name:
self.module.fail_on_missing_params(
required_params=["id"]
)
if not self.module.check_mode:
self.hcloud_ssh_key.update(name=name)
self._mark_as_changed()
labels = self.module.params.get("labels")
if labels is not None and self.hcloud_ssh_key.labels != labels:
if not self.module.check_mode:
self.hcloud_ssh_key.update(labels=labels)
self._mark_as_changed()
self._get_ssh_key()
def present_ssh_key(self):
self._get_ssh_key()
if self.hcloud_ssh_key is None:
self._create_ssh_key()
else:
self._update_ssh_key()
def delete_ssh_key(self):
self._get_ssh_key()
if self.hcloud_ssh_key is not None:
if not self.module.check_mode:
try:
self.client.ssh_keys.delete(self.hcloud_ssh_key)
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self.hcloud_ssh_key = None
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
public_key={"type": "str"},
fingerprint={"type": "str"},
labels={"type": "dict"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
required_one_of=[['id', 'name', 'fingerprint']],
required_if=[['state', 'present', ['name']]],
supports_check_mode=True,
)
def main():
module = AnsibleHcloudSSHKey.define_module()
hcloud = AnsibleHcloudSSHKey(module)
state = module.params.get("state")
if state == "absent":
hcloud.delete_ssh_key()
elif state == "present":
hcloud.present_ssh_key()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,174 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_ssh_key_info
short_description: Gather infos about your Hetzner Cloud ssh_keys.
description:
- Gather facts about your Hetzner Cloud ssh_keys.
- This module was called C(hcloud_ssh_key_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_ssh_key_facts).
Note that the M(hetzner.hcloud.hcloud_ssh_key_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_ssh_key_info)!
author:
- Christopher Schmitt (@cschmitt-hcloud)
options:
id:
description:
- The ID of the ssh key you want to get.
type: int
name:
description:
- The name of the ssh key you want to get.
type: str
fingerprint:
description:
- The fingerprint of the ssh key you want to get.
type: str
label_selector:
description:
- The label selector for the ssh key you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud sshkey infos
hcloud_ssh_key_info:
register: output
- name: Print the gathered infos
debug:
var: output.hcloud_ssh_key_info
"""
RETURN = """
hcloud_ssh_key_info:
description: The ssh key instances
returned: Always
type: complex
contains:
id:
description: Numeric identifier of the ssh_key
returned: always
type: int
sample: 1937415
name:
description: Name of the ssh_key
returned: always
type: str
sample: my-ssh-key
fingerprint:
description: Fingerprint of the ssh key
returned: always
type: str
sample: 0e:e0:bd:c7:2d:1f:69:49:94:44:91:f1:19:fd:35:f3
public_key:
description: The actual public key
returned: always
type: str
sample: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpl/tnk74nnQJxxLAtutUApUZMRJxryKh7VXkNbd4g9 john@example.com"
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudSSHKeyInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_ssh_key_info")
self.hcloud_ssh_key_info = None
def _prepare_result(self):
ssh_keys = []
for ssh_key in self.hcloud_ssh_key_info:
if ssh_key:
ssh_keys.append({
"id": to_native(ssh_key.id),
"name": to_native(ssh_key.name),
"fingerprint": to_native(ssh_key.fingerprint),
"public_key": to_native(ssh_key.public_key),
"labels": ssh_key.labels
})
return ssh_keys
def get_ssh_keys(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_name(
self.module.params.get("name")
)]
elif self.module.params.get("fingerprint") is not None:
self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_fingerprint(
self.module.params.get("fingerprint")
)]
elif self.module.params.get("label_selector") is not None:
self.hcloud_ssh_key_info = self.client.ssh_keys.get_all(
label_selector=self.module.params.get("label_selector"))
else:
self.hcloud_ssh_key_info = self.client.ssh_keys.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
fingerprint={"type": "str"},
label_selector={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudSSHKeyInfo.define_module()
is_old_facts = module._name == 'hcloud_ssh_key_facts'
if is_old_facts:
module.deprecate("The 'hcloud_ssh_key_facts' module has been renamed to 'hcloud_ssh_key_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudSSHKeyInfo(module)
hcloud.get_ssh_keys()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_ssh_key_facts': result['hcloud_ssh_key_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_ssh_key_info': result['hcloud_ssh_key_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,174 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_ssh_key_info
short_description: Gather infos about your Hetzner Cloud ssh_keys.
description:
- Gather facts about your Hetzner Cloud ssh_keys.
- This module was called C(hcloud_ssh_key_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_ssh_key_facts).
Note that the M(hetzner.hcloud.hcloud_ssh_key_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_ssh_key_info)!
author:
- Christopher Schmitt (@cschmitt-hcloud)
options:
id:
description:
- The ID of the ssh key you want to get.
type: int
name:
description:
- The name of the ssh key you want to get.
type: str
fingerprint:
description:
- The fingerprint of the ssh key you want to get.
type: str
label_selector:
description:
- The label selector for the ssh key you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud sshkey infos
hcloud_ssh_key_info:
register: output
- name: Print the gathered infos
debug:
var: output.hcloud_ssh_key_info
"""
RETURN = """
hcloud_ssh_key_info:
description: The ssh key instances
returned: Always
type: complex
contains:
id:
description: Numeric identifier of the ssh_key
returned: always
type: int
sample: 1937415
name:
description: Name of the ssh_key
returned: always
type: str
sample: my-ssh-key
fingerprint:
description: Fingerprint of the ssh key
returned: always
type: str
sample: 0e:e0:bd:c7:2d:1f:69:49:94:44:91:f1:19:fd:35:f3
public_key:
description: The actual public key
returned: always
type: str
sample: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpl/tnk74nnQJxxLAtutUApUZMRJxryKh7VXkNbd4g9 john@example.com"
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudSSHKeyInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_ssh_key_info")
self.hcloud_ssh_key_info = None
def _prepare_result(self):
ssh_keys = []
for ssh_key in self.hcloud_ssh_key_info:
if ssh_key:
ssh_keys.append({
"id": to_native(ssh_key.id),
"name": to_native(ssh_key.name),
"fingerprint": to_native(ssh_key.fingerprint),
"public_key": to_native(ssh_key.public_key),
"labels": ssh_key.labels
})
return ssh_keys
def get_ssh_keys(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_name(
self.module.params.get("name")
)]
elif self.module.params.get("fingerprint") is not None:
self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_fingerprint(
self.module.params.get("fingerprint")
)]
elif self.module.params.get("label_selector") is not None:
self.hcloud_ssh_key_info = self.client.ssh_keys.get_all(
label_selector=self.module.params.get("label_selector"))
else:
self.hcloud_ssh_key_info = self.client.ssh_keys.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
fingerprint={"type": "str"},
label_selector={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudSSHKeyInfo.define_module()
is_old_facts = module._name == 'hcloud_ssh_key_facts'
if is_old_facts:
module.deprecate("The 'hcloud_ssh_key_facts' module has been renamed to 'hcloud_ssh_key_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudSSHKeyInfo(module)
hcloud.get_ssh_keys()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_ssh_key_facts': result['hcloud_ssh_key_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_ssh_key_info': result['hcloud_ssh_key_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,249 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_subnetwork
short_description: Manage cloud subnetworks on the Hetzner Cloud.
description:
- Create, update and delete cloud subnetworks on the Hetzner Cloud.
author:
- Lukas Kaemmerling (@lkaemmerling)
options:
network:
description:
- The ID or Name of the Hetzner Cloud Networks.
type: str
required: true
ip_range:
description:
- IP range of the subnetwork.
type: str
required: true
type:
description:
- Type of subnetwork.
type: str
choices: [ server, cloud, vswitch ]
required: true
network_zone:
description:
- Name of network zone.
type: str
required: true
vswitch_id:
description:
- ID of the vSwitch you want to couple with your Network.
- Required if type == vswitch
type: int
state:
description:
- State of the subnetwork.
default: present
choices: [ absent, present ]
type: str
requirements:
- hcloud-python >= 1.10.0
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a basic subnetwork
hcloud_subnetwork:
network: my-network
ip_range: 10.0.0.0/16
network_zone: eu-central
type: cloud
state: present
- name: Create a basic subnetwork
hcloud_subnetwork:
network: my-vswitch-network
ip_range: 10.0.0.0/24
network_zone: eu-central
type: vswitch
vswitch_id: 123
state: present
- name: Ensure the subnetwork is absent (remove if needed)
hcloud_subnetwork:
network: my-network
ip_range: 10.0.0.0/8
network_zone: eu-central
type: cloud
state: absent
"""
RETURN = """
hcloud_subnetwork:
description: One Subnet of a Network
returned: always
type: complex
contains:
network:
description: Name of the Network
type: str
returned: always
sample: my-network
ip_range:
description: IP range of the Network
type: str
returned: always
sample: 10.0.0.0/8
type:
description: Type of subnetwork
type: str
returned: always
sample: server
network_zone:
description: Name of network zone
type: str
returned: always
sample: eu-central
vswitch_id:
description: ID of the vswitch, null if not type vswitch
type: int
returned: always
sample: 123
gateway:
description: Gateway of the subnetwork
type: str
returned: always
sample: 10.0.0.1
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
from hcloud.networks.domain import NetworkSubnet
except ImportError:
APIException = None
NetworkSubnet = None
class AnsibleHcloudSubnetwork(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_subnetwork")
self.hcloud_network = None
self.hcloud_subnetwork = None
def _prepare_result(self):
return {
"network": to_native(self.hcloud_network.name),
"ip_range": to_native(self.hcloud_subnetwork.ip_range),
"type": to_native(self.hcloud_subnetwork.type),
"network_zone": to_native(self.hcloud_subnetwork.network_zone),
"gateway": self.hcloud_subnetwork.gateway,
"vswitch_id": self.hcloud_subnetwork.vswitch_id,
}
def _get_network(self):
try:
self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network"))
self.hcloud_subnetwork = None
except Exception as e:
self.module.fail_json(msg=e.message)
def _get_subnetwork(self):
subnet_ip_range = self.module.params.get("ip_range")
for subnetwork in self.hcloud_network.subnets:
if subnetwork.ip_range == subnet_ip_range:
self.hcloud_subnetwork = subnetwork
def _create_subnetwork(self):
params = {
"ip_range": self.module.params.get("ip_range"),
"type": self.module.params.get('type'),
"network_zone": self.module.params.get('network_zone')
}
if self.module.params.get('type') == NetworkSubnet.TYPE_VSWITCH:
self.module.fail_on_missing_params(
required_params=["vswitch_id"]
)
params["vswitch_id"] = self.module.params.get('vswitch_id')
if not self.module.check_mode:
try:
self.hcloud_network.add_subnet(subnet=NetworkSubnet(**params)).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_network()
self._get_subnetwork()
def present_subnetwork(self):
self._get_network()
self._get_subnetwork()
if self.hcloud_subnetwork is None:
self._create_subnetwork()
def delete_subnetwork(self):
self._get_network()
self._get_subnetwork()
if self.hcloud_subnetwork is not None and self.hcloud_network is not None:
if not self.module.check_mode:
try:
self.hcloud_network.delete_subnet(self.hcloud_subnetwork).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self.hcloud_subnetwork = None
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
network={"type": "str", "required": True},
network_zone={"type": "str", "required": True},
type={
"type": "str",
"required": True,
"choices": ["server", "cloud", "vswitch"]
},
ip_range={"type": "str", "required": True},
vswitch_id={"type": "int"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudSubnetwork.define_module()
hcloud = AnsibleHcloudSubnetwork(module)
state = module.params["state"]
if state == "absent":
hcloud.delete_subnetwork()
elif state == "present":
hcloud.present_subnetwork()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,349 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_volume
short_description: Create and manage block Volume on the Hetzner Cloud.
description:
- Create, update and attach/detach block Volume on the Hetzner Cloud.
author:
- Christopher Schmitt (@cschmitt-hcloud)
options:
id:
description:
- The ID of the Hetzner Cloud Block Volume to manage.
- Only required if no volume I(name) is given
type: int
name:
description:
- The Name of the Hetzner Cloud Block Volume to manage.
- Only required if no volume I(id) is given or a volume does not exist.
type: str
size:
description:
- The size of the Block Volume in GB.
- Required if volume does not yet exists.
type: int
automount:
description:
- Automatically mount the Volume.
type: bool
default: False
format:
description:
- Automatically Format the volume on creation
- Can only be used in case the Volume does not exist.
type: str
choices: [xfs, ext4]
location:
description:
- Location of the Hetzner Cloud Volume.
- Required if no I(server) is given and Volume does not exist.
type: str
server:
description:
- Server Name the Volume should be assigned to.
- Required if no I(location) is given and Volume does not exist.
type: str
delete_protection:
description:
- Protect the Volume for deletion.
type: bool
labels:
description:
- User-defined key-value pairs.
type: dict
state:
description:
- State of the Volume.
default: present
choices: [absent, present]
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Create a Volume
hcloud_volume:
name: my-volume
location: fsn1
size: 100
state: present
- name: Create a Volume and format it with ext4
hcloud_volume:
name: my-volume
location: fsn
format: ext4
size: 100
state: present
- name: Mount a existing Volume and automount
hcloud_volume:
name: my-volume
server: my-server
automount: yes
state: present
- name: Mount a existing Volume and automount
hcloud_volume:
name: my-volume
server: my-server
automount: yes
state: present
- name: Ensure the Volume is absent (remove if needed)
hcloud_volume:
name: my-volume
state: absent
"""
RETURN = """
hcloud_volume:
description: The block Volume
returned: Always
type: complex
contains:
id:
description: ID of the Volume
type: int
returned: Always
sample: 12345
name:
description: Name of the Volume
type: str
returned: Always
sample: my-volume
size:
description: Size in GB of the Volume
type: int
returned: Always
sample: 1337
linux_device:
description: Path to the device that contains the Volume.
returned: always
type: str
sample: /dev/disk/by-id/scsi-0HC_Volume_12345
version_added: "0.1.0"
location:
description: Location name where the Volume is located at
type: str
returned: Always
sample: "fsn1"
labels:
description: User-defined labels (key-value pairs)
type: dict
returned: Always
sample:
key: value
mylabel: 123
server:
description: Server name where the Volume is attached to
type: str
returned: Always
sample: "my-server"
delete_protection:
description: True if Volume is protected for deletion
type: bool
returned: always
sample: false
version_added: "0.1.0"
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud.volumes.domain import Volume
from hcloud.servers.domain import Server
import hcloud
except ImportError:
APIException = None
Volume = None
Server = None
class AnsibleHcloudVolume(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_volume")
self.hcloud_volume = None
def _prepare_result(self):
server_name = None
if self.hcloud_volume.server is not None:
server_name = to_native(self.hcloud_volume.server.name)
return {
"id": to_native(self.hcloud_volume.id),
"name": to_native(self.hcloud_volume.name),
"size": self.hcloud_volume.size,
"location": to_native(self.hcloud_volume.location.name),
"labels": self.hcloud_volume.labels,
"server": server_name,
"linux_device": to_native(self.hcloud_volume.linux_device),
"delete_protection": self.hcloud_volume.protection["delete"],
}
def _get_volume(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_volume = self.client.volumes.get_by_id(
self.module.params.get("id")
)
else:
self.hcloud_volume = self.client.volumes.get_by_name(
self.module.params.get("name")
)
except Exception as e:
self.module.fail_json(msg=e.message)
def _create_volume(self):
self.module.fail_on_missing_params(
required_params=["name", "size"]
)
params = {
"name": self.module.params.get("name"),
"size": self.module.params.get("size"),
"automount": self.module.params.get("automount"),
"format": self.module.params.get("format"),
"labels": self.module.params.get("labels")
}
if self.module.params.get("server") is not None:
params['server'] = self.client.servers.get_by_name(self.module.params.get("server"))
elif self.module.params.get("location") is not None:
params['location'] = self.client.locations.get_by_name(self.module.params.get("location"))
else:
self.module.fail_json(msg="server or location is required")
if not self.module.check_mode:
try:
resp = self.client.volumes.create(**params)
resp.action.wait_until_finished()
[action.wait_until_finished() for action in resp.next_actions]
delete_protection = self.module.params.get("delete_protection")
if delete_protection is not None:
self._get_volume()
self.hcloud_volume.change_protection(delete=delete_protection).wait_until_finished()
except Exception as e:
self.module.fail_json(msg=e.message)
self._mark_as_changed()
self._get_volume()
def _update_volume(self):
try:
size = self.module.params.get("size")
if size:
if self.hcloud_volume.size < size:
if not self.module.check_mode:
self.hcloud_volume.resize(size).wait_until_finished()
self._mark_as_changed()
elif self.hcloud_volume.size > size:
self.module.warn("Shrinking of volumes is not supported")
server_name = self.module.params.get("server")
if server_name:
server = self.client.servers.get_by_name(server_name)
if self.hcloud_volume.server is None or self.hcloud_volume.server.name != server.name:
if not self.module.check_mode:
automount = self.module.params.get("automount", False)
self.hcloud_volume.attach(server, automount=automount).wait_until_finished()
self._mark_as_changed()
else:
if self.hcloud_volume.server is not None:
if not self.module.check_mode:
self.hcloud_volume.detach().wait_until_finished()
self._mark_as_changed()
labels = self.module.params.get("labels")
if labels is not None and labels != self.hcloud_volume.labels:
if not self.module.check_mode:
self.hcloud_volume.update(labels=labels)
self._mark_as_changed()
delete_protection = self.module.params.get("delete_protection")
if delete_protection is not None and delete_protection != self.hcloud_volume.protection["delete"]:
if not self.module.check_mode:
self.hcloud_volume.change_protection(delete=delete_protection).wait_until_finished()
self._mark_as_changed()
self._get_volume()
except Exception as e:
self.module.fail_json(msg=e.message)
def present_volume(self):
self._get_volume()
if self.hcloud_volume is None:
self._create_volume()
else:
self._update_volume()
def delete_volume(self):
try:
self._get_volume()
if self.hcloud_volume is not None:
if not self.module.check_mode:
if self.hcloud_volume.server is not None:
self.hcloud_volume.detach().wait_until_finished()
self.client.volumes.delete(self.hcloud_volume)
self._mark_as_changed()
self.hcloud_volume = None
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
size={"type": "int"},
location={"type": "str"},
server={"type": "str"},
labels={"type": "dict"},
automount={"type": "bool", "default": False},
format={"type": "str",
"choices": ['xfs', 'ext4'],
},
delete_protection={"type": "bool"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
required_one_of=[['id', 'name']],
mutually_exclusive=[["location", "server"]],
supports_check_mode=True,
)
def main():
module = AnsibleHcloudVolume.define_module()
hcloud = AnsibleHcloudVolume(module)
state = module.params.get("state")
if state == "absent":
module.fail_on_missing_params(
required_params=["name"]
)
hcloud.delete_volume()
else:
hcloud.present_volume()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,191 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_volume_info
short_description: Gather infos about your Hetzner Cloud Volumes.
description:
- Gather infos about your Hetzner Cloud Volumes.
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the Volume you want to get.
type: int
name:
description:
- The name of the Volume you want to get.
type: str
label_selector:
description:
- The label selector for the Volume you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud Volume infos
hcloud_volume_info:
register: output
- name: Print the gathered infos
debug:
var: output.hcloud_volume_info
"""
RETURN = """
hcloud_volume_info:
description: The Volume infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the Volume
returned: always
type: int
sample: 1937415
name:
description: Name of the Volume
returned: always
type: str
sample: my-volume
size:
description: Size of the Volume
returned: always
type: str
sample: 10
linux_device:
description: Path to the device that contains the Volume.
returned: always
type: str
sample: /dev/disk/by-id/scsi-0HC_Volume_12345
version_added: "0.1.0"
location:
description: Name of the location where the Volume resides in
returned: always
type: str
sample: fsn1
server:
description: Name of the server where the Volume is attached to
returned: always
type: str
sample: my-server
delete_protection:
description: True if the Volume is protected for deletion
returned: always
type: bool
version_added: "0.1.0"
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudVolumeInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_volume_info")
self.hcloud_volume_info = None
def _prepare_result(self):
tmp = []
for volume in self.hcloud_volume_info:
if volume is not None:
server_name = None
if volume.server is not None:
server_name = to_native(volume.server.name)
tmp.append({
"id": to_native(volume.id),
"name": to_native(volume.name),
"size": volume.size,
"location": to_native(volume.location.name),
"labels": volume.labels,
"server": server_name,
"linux_device": to_native(volume.linux_device),
"delete_protection": volume.protection["delete"],
})
return tmp
def get_volumes(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_volume_info = [self.client.volumes.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_volume_info = [self.client.volumes.get_by_name(
self.module.params.get("name")
)]
elif self.module.params.get("label_selector") is not None:
self.hcloud_volume_info = self.client.volumes.get_all(
label_selector=self.module.params.get("label_selector"))
else:
self.hcloud_volume_info = self.client.volumes.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
label_selector={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudVolumeInfo.define_module()
is_old_facts = module._name == 'hcloud_volume_facts'
if is_old_facts:
module.deprecate("The 'hcloud_volume_facts' module has been renamed to 'hcloud_volume_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudVolumeInfo(module)
hcloud.get_volumes()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_volume_facts': result['hcloud_volume_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_volume_info': result['hcloud_volume_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,191 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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 = '''
---
module: hcloud_volume_info
short_description: Gather infos about your Hetzner Cloud Volumes.
description:
- Gather infos about your Hetzner Cloud Volumes.
author:
- Lukas Kaemmerling (@LKaemmerling)
options:
id:
description:
- The ID of the Volume you want to get.
type: int
name:
description:
- The name of the Volume you want to get.
type: str
label_selector:
description:
- The label selector for the Volume you want to get.
type: str
extends_documentation_fragment:
- hetzner.hcloud.hcloud
'''
EXAMPLES = """
- name: Gather hcloud Volume infos
hcloud_volume_info:
register: output
- name: Print the gathered infos
debug:
var: output.hcloud_volume_info
"""
RETURN = """
hcloud_volume_info:
description: The Volume infos as list
returned: always
type: complex
contains:
id:
description: Numeric identifier of the Volume
returned: always
type: int
sample: 1937415
name:
description: Name of the Volume
returned: always
type: str
sample: my-volume
size:
description: Size of the Volume
returned: always
type: str
sample: 10
linux_device:
description: Path to the device that contains the Volume.
returned: always
type: str
sample: /dev/disk/by-id/scsi-0HC_Volume_12345
version_added: "0.1.0"
location:
description: Name of the location where the Volume resides in
returned: always
type: str
sample: fsn1
server:
description: Name of the server where the Volume is attached to
returned: always
type: str
sample: my-server
delete_protection:
description: True if the Volume is protected for deletion
returned: always
type: bool
version_added: "0.1.0"
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
try:
from hcloud import APIException
except ImportError:
APIException = None
class AnsibleHcloudVolumeInfo(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_volume_info")
self.hcloud_volume_info = None
def _prepare_result(self):
tmp = []
for volume in self.hcloud_volume_info:
if volume is not None:
server_name = None
if volume.server is not None:
server_name = to_native(volume.server.name)
tmp.append({
"id": to_native(volume.id),
"name": to_native(volume.name),
"size": volume.size,
"location": to_native(volume.location.name),
"labels": volume.labels,
"server": server_name,
"linux_device": to_native(volume.linux_device),
"delete_protection": volume.protection["delete"],
})
return tmp
def get_volumes(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_volume_info = [self.client.volumes.get_by_id(
self.module.params.get("id")
)]
elif self.module.params.get("name") is not None:
self.hcloud_volume_info = [self.client.volumes.get_by_name(
self.module.params.get("name")
)]
elif self.module.params.get("label_selector") is not None:
self.hcloud_volume_info = self.client.volumes.get_all(
label_selector=self.module.params.get("label_selector"))
else:
self.hcloud_volume_info = self.client.volumes.get_all()
except Exception as e:
self.module.fail_json(msg=e.message)
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
label_selector={"type": "str"},
**Hcloud.base_module_arguments()
),
supports_check_mode=True,
)
def main():
module = AnsibleHcloudVolumeInfo.define_module()
is_old_facts = module._name == 'hcloud_volume_facts'
if is_old_facts:
module.deprecate("The 'hcloud_volume_facts' module has been renamed to 'hcloud_volume_info', "
"and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
hcloud = AnsibleHcloudVolumeInfo(module)
hcloud.get_volumes()
result = hcloud.get_result()
if is_old_facts:
ansible_info = {
'hcloud_volume_facts': result['hcloud_volume_info']
}
module.exit_json(ansible_facts=ansible_info)
else:
ansible_info = {
'hcloud_volume_info': result['hcloud_volume_info']
}
module.exit_json(**ansible_info)
if __name__ == "__main__":
main()