gandi.cli-1.2/ 0000755 0001750 0001750 00000000000 13227415174 014070 5 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/LICENSE 0000644 0001750 0001750 00000104513 12441335654 015102 0 ustar sayoun sayoun 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
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.
Copyright (C)
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 .
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:
Copyright (C)
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
.
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
.
gandi.cli-1.2/gandi/ 0000755 0001750 0001750 00000000000 13227415174 015152 5 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/gandi/cli/ 0000755 0001750 0001750 00000000000 13227415174 015721 5 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/gandi/cli/modules/ 0000755 0001750 0001750 00000000000 13227415174 017371 5 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/gandi/cli/modules/snapshotprofile.py 0000644 0001750 0001750 00000004220 12576313413 023160 0 ustar sayoun sayoun 0000000 0000000 """ Snapshot profile commands module. """
from gandi.cli.core.base import GandiModule
from gandi.cli.core.utils import DuplicateResults
class SnapshotProfile(GandiModule):
""" Module to handle CLI commands.
$ gandi snapshotprofile info
$ gandi snapshotprofile list
"""
@classmethod
def from_name(cls, name):
""" Retrieve a snapshot profile accsociated to a name."""
snps = cls.list({'name': name})
if len(snps) == 1:
return snps[0]['id']
elif not snps:
return
raise DuplicateResults('snapshot profile name %s is ambiguous.' % name)
@classmethod
def usable_id(cls, id):
""" Retrieve id from input which can be name or id."""
try:
qry_id = cls.from_name(id)
if not qry_id:
qry_id = int(id)
except DuplicateResults as exc:
cls.error(exc.errors)
except Exception:
qry_id = None
if not qry_id:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id
@classmethod
def list(cls, options=None, target=None):
""" List all snapshot profiles."""
options = options or {}
result = []
if not target or target == 'paas':
for profile in cls.safe_call('paas.snapshotprofile.list', options):
profile['target'] = 'paas'
result.append((profile['id'], profile))
if not target or target == 'vm':
for profile in cls.safe_call('hosting.snapshotprofile.list',
options):
profile['target'] = 'vm'
result.append((profile['id'], profile))
result = sorted(result, key=lambda item: item[0])
return [profile for id_, profile in result]
@classmethod
def info(cls, resource):
"""Display information about a snapshot profile."""
snps = cls.list({'id': cls.usable_id(resource)})
if len(snps) == 1:
return snps[0]
elif not snps:
return
raise DuplicateResults('snapshot profile %s is ambiguous.' % resource)
gandi.cli-1.2/gandi/cli/modules/disk.py 0000644 0001750 0001750 00000021052 13203325477 020675 0 ustar sayoun sayoun 0000000 0000000 """ Disk commands module. """
from gandi.cli.core.base import GandiModule
from gandi.cli.core.utils import DuplicateResults
from gandi.cli.core.params import DISK_MAXLIST
from .iaas import Iaas, Datacenter, Image
class Disk(GandiModule):
""" Module to handle CLI commands.
$ gandi disk create
$ gandi disk delete
$ gandi disk attach
$ gandi disk detach
$ gandi disk info
$ gandi disk list
$ gandi disk rollback
$ gandi disk snapshot
$ gandi disk update
"""
@classmethod
def from_name(cls, name):
""" Retrieve a disk id associated to a name. """
disks = cls.list({'name': name})
if len(disks) == 1:
return disks[0]['id']
elif not disks:
return
raise DuplicateResults('disk name %s is ambiguous.' % name)
@classmethod
def usable_id(cls, id):
""" Retrieve id from input which can be name or id."""
try:
qry_id = cls.from_name(id)
if not qry_id:
qry_id = int(id)
except DuplicateResults as exc:
cls.error(exc.errors)
except Exception:
qry_id = None
if not qry_id:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id
@classmethod
def list(cls, options=None):
""" List all disks."""
options = options or {}
return cls.call('hosting.disk.list', options)
@classmethod
def list_create(cls, datacenter=None, label=None):
"""List available disks for vm creation."""
options = {
'items_per_page': DISK_MAXLIST
}
if datacenter:
datacenter_id = int(Datacenter.usable_id(datacenter))
options['datacenter_id'] = datacenter_id
# implement a filter by label as API doesn't handle it
images = cls.safe_call('hosting.disk.list', options)
if not label:
return images
return [img for img in images
if label.lower() in img['name'].lower()]
@classmethod
def _info(cls, disk_id):
""" Get information about a disk."""
return cls.call('hosting.disk.info', disk_id)
@classmethod
def info(cls, name):
""" Get information about a disk."""
return cls._info(cls.usable_id(name))
@staticmethod
def disk_param(name, size, snapshot_profile, cmdline=None, kernel=None):
""" Return disk parameter structure. """
disk_params = {}
if cmdline:
disk_params['cmdline'] = cmdline
if kernel:
disk_params['kernel'] = kernel
if name:
disk_params['name'] = name
if snapshot_profile is not None:
disk_params['snapshot_profile'] = snapshot_profile
if size:
disk_params['size'] = size
return disk_params
@classmethod
def update(cls, resource, name, size, snapshot_profile,
background, cmdline=None, kernel=None):
""" Update this disk. """
if isinstance(size, tuple):
prefix, size = size
if prefix == '+':
disk_info = cls.info(resource)
current_size = disk_info['size']
size = current_size + size
disk_params = cls.disk_param(name, size, snapshot_profile,
cmdline, kernel)
result = cls.call('hosting.disk.update',
cls.usable_id(resource),
disk_params)
if background:
return result
# interactive mode, run a progress bar
cls.echo('Updating your disk.')
cls.display_progress(result)
@classmethod
def _detach(cls, disk_id):
""" Detach a disk from a vm. """
disk = cls._info(disk_id)
opers = []
if disk.get('vms_id'):
for vm_id in disk['vms_id']:
cls.echo('The disk is still attached to the vm %s.' % vm_id)
cls.echo('Will detach it.')
opers.append(cls.call('hosting.vm.disk_detach',
vm_id, disk_id))
return opers
@classmethod
def detach(cls, resources, background):
if not isinstance(resources, (list, tuple)):
resources = [resources]
resources = [cls.usable_id(item) for item in resources]
opers = []
for disk_id in resources:
opers.extend(cls._detach(disk_id))
if opers and not background:
cls.echo('Detaching your disk(s).')
cls.display_progress(opers)
return opers
@classmethod
def delete(cls, resources, background=False):
""" Delete this disk."""
if not isinstance(resources, (list, tuple)):
resources = [resources]
resources = [cls.usable_id(item) for item in resources]
opers = []
for disk_id in resources:
opers.extend(cls._detach(disk_id))
if opers:
cls.echo('Detaching your disk(s).')
cls.display_progress(opers)
opers = []
for disk_id in resources:
oper = cls.call('hosting.disk.delete', disk_id)
opers.append(oper)
if background:
return opers
cls.echo('Deleting your disk.')
cls.display_progress(opers)
return opers
@classmethod
def _attach(cls, disk_id, vm_id, options=None):
""" Attach a disk to a vm. """
options = options or {}
oper = cls.call('hosting.vm.disk_attach', vm_id, disk_id, options)
return oper
@classmethod
def attach(cls, disk, vm, background, position=None, read_only=False):
from gandi.cli.modules.iaas import Iaas as VM
vm_id = VM.usable_id(vm)
disk_id = cls.usable_id(disk)
disk_info = cls._info(disk_id)
options = {}
if position is not None:
options['position'] = position
if read_only:
options['access'] = 'read'
need_detach = disk_info.get('vms_id')
if need_detach:
if disk_info.get('vms_id') == [vm_id]:
cls.echo('This disk is already attached to this vm.')
return
# detach disk
detach_op = cls._detach(disk_id)
# interactive mode, run a progress bar
cls.echo('Detaching your disk.')
cls.display_progress(detach_op)
oper = cls._attach(disk_id, vm_id, options)
if oper and not background:
cls.echo('Attaching your disk(s).')
cls.display_progress(oper)
return oper
@classmethod
def create(cls, name, vm, size, snapshotprofile, datacenter,
source, disk_type='data', background=False):
""" Create a disk and attach it to a vm. """
if isinstance(size, tuple):
prefix, size = size
if source:
size = None
disk_params = cls.disk_param(name, size, snapshotprofile)
disk_params['datacenter_id'] = int(Datacenter.usable_id(datacenter))
disk_params['type'] = disk_type
if source:
disk_id = int(Image.usable_id(source,
disk_params['datacenter_id']))
result = cls.call('hosting.disk.create_from', disk_params, disk_id)
else:
result = cls.call('hosting.disk.create', disk_params)
if background and not vm:
return result
# interactive mode, run a progress bar
cls.echo('Creating your disk.')
cls.display_progress(result)
if not vm:
return
vm_id = Iaas.usable_id(vm)
result = cls._attach(result['disk_id'], vm_id)
if background:
return result
cls.echo('Attaching your disk.')
cls.display_progress(result)
@classmethod
def rollback(cls, resource, background=False):
""" Rollback a disk from a snapshot. """
disk_id = cls.usable_id(resource)
result = cls.call('hosting.disk.rollback_from', disk_id)
if background:
return result
cls.echo('Disk rollback in progress.')
cls.display_progress(result)
return result
@classmethod
def migrate(cls, resource, datacenter_id, background=False):
""" Migrate a disk to another datacenter. """
disk_id = cls.usable_id(resource)
result = cls.call('hosting.disk.migrate', disk_id, datacenter_id)
if background:
return result
cls.echo('Disk migration in progress.')
cls.display_progress(result)
return result
gandi.cli-1.2/gandi/cli/modules/docker.py 0000644 0001750 0001750 00000002320 12623134755 021211 0 ustar sayoun sayoun 0000000 0000000 import os
import subprocess
from gandi.cli.core.base import GandiModule
from gandi.cli.modules.iaas import Iaas
from gandi.cli.core.utils import unixpipe
class Docker(GandiModule):
"""
Module to handle docker vms.
$ gandi docker create
$ gandi docker help
$ gandi docker ps
$ gandi docker
Note that you can use a per-project docker vm by using
a local directory gandi configuration using:
$ gandi config set dockervm foobar
Or override the current global vm using:
$ gandi docker --vm bar ps
"""
@classmethod
def handle(cls, vm, args):
"""
Setup forwarding connection to given VM and pipe docker cmds over SSH.
"""
docker = Iaas.info(vm)
if not docker:
raise Exception('docker vm %s not found' % vm)
if docker['state'] != 'running':
Iaas.start(vm)
# XXX
remote_addr = docker['ifaces'][0]['ips'][0]['ip']
port = unixpipe.setup(remote_addr, 'root', '/var/run/docker.sock')
os.environ['DOCKER_HOST'] = 'tcp://localhost:%d' % port
cls.echo('using DOCKER_HOST=%s' % os.environ['DOCKER_HOST'])
subprocess.call(['docker'] + list(args))
gandi.cli-1.2/gandi/cli/modules/api.py 0000644 0001750 0001750 00000000437 12441335654 020521 0 ustar sayoun sayoun 0000000 0000000 """ API commands module. """
from gandi.cli.core.base import GandiModule
class Api(GandiModule):
""" Module to handle CLI commands.
$ gandi api
"""
@classmethod
def info(cls):
"""Display information about API."""
return cls.call('version.info')
gandi.cli-1.2/gandi/cli/modules/datacenter.py 0000644 0001750 0001750 00000010475 13155510475 022064 0 ustar sayoun sayoun 0000000 0000000 """ Datacenter commands module. """
from __future__ import print_function
from gandi.cli.core.base import GandiModule
from gandi.cli.core.utils import DatacenterClosed, DatacenterLimited
class Datacenter(GandiModule):
""" Module to handle CLI commands.
$ gandi datacenters
"""
@classmethod
def list(cls, options=None):
"""List available datacenters."""
return cls.safe_call('hosting.datacenter.list', options or {})
@classmethod
def list_migration_choice(cls, datacenter):
"""List available datacenters for migration from given datacenter."""
datacenter_id = cls.usable_id(datacenter)
dc_list = cls.list()
available_dcs = [dc for dc in dc_list
if dc['id'] == datacenter_id][0]['can_migrate_to']
choices = [dc for dc in dc_list
if dc['id'] in available_dcs]
return choices
@classmethod
def is_opened(cls, dc_code, type_):
"""List opened datacenters for given type."""
options = {'dc_code': dc_code, '%s_opened' % type_: True}
datacenters = cls.safe_call('hosting.datacenter.list', options)
if not datacenters:
# try with ISO code
options = {'iso': dc_code, '%s_opened' % type_: True}
datacenters = cls.safe_call('hosting.datacenter.list', options)
if not datacenters:
raise DatacenterClosed(r'/!\ Datacenter %s is closed, please '
'choose another datacenter.' % dc_code)
datacenter = datacenters[0]
if datacenter.get('%s_closed_for' % type_) == 'NEW':
dc_close_date = datacenter.get('deactivate_at', '')
if dc_close_date:
dc_close_date = dc_close_date.strftime('%d/%m/%Y')
raise DatacenterLimited(dc_close_date)
@classmethod
def filtered_list(cls, name=None, obj=None):
"""List datacenters matching name and compatible
with obj"""
options = {}
if name:
options['id'] = cls.usable_id(name)
def obj_ok(dc, obj):
if not obj or obj['datacenter_id'] == dc['id']:
return True
return False
return [x for x in cls.list(options) if obj_ok(x, obj)]
@classmethod
def from_iso(cls, iso):
"""Retrieve the first datacenter id associated to an ISO."""
result = cls.list({'sort_by': 'id ASC'})
dc_isos = {}
for dc in result:
if dc['iso'] not in dc_isos:
dc_isos[dc['iso']] = dc['id']
return dc_isos.get(iso)
@classmethod
def from_name(cls, name):
"""Retrieve datacenter id associated to a name."""
result = cls.list()
dc_names = {}
for dc in result:
dc_names[dc['name']] = dc['id']
return dc_names.get(name)
@classmethod
def from_country(cls, country):
"""Retrieve the first datacenter id associated to a country."""
result = cls.list({'sort_by': 'id ASC'})
dc_countries = {}
for dc in result:
if dc['country'] not in dc_countries:
dc_countries[dc['country']] = dc['id']
return dc_countries.get(country)
@classmethod
def from_dc_code(cls, dc_code):
"""Retrieve the datacenter id associated to a dc_code"""
result = cls.list()
dc_codes = {}
for dc in result:
if dc.get('dc_code'):
dc_codes[dc['dc_code']] = dc['id']
return dc_codes.get(dc_code)
@classmethod
def usable_id(cls, id):
""" Retrieve id from input which can be ISO, name, country, dc_code."""
try:
# id is maybe a dc_code
qry_id = cls.from_dc_code(id)
if not qry_id:
# id is maybe a ISO
qry_id = cls.from_iso(id)
if qry_id:
cls.deprecated('ISO code for datacenter filter use '
'dc_code instead')
if not qry_id:
# id is maybe a country
qry_id = cls.from_country(id)
if not qry_id:
qry_id = int(id)
except Exception:
qry_id = None
if not qry_id:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id
gandi.cli-1.2/gandi/cli/modules/record.py 0000644 0001750 0001750 00000010133 13227414205 021211 0 ustar sayoun sayoun 0000000 0000000 """ Record commands module. """
from gandi.cli.core.base import GandiModule
class Zone(GandiModule):
""" Helper class for domain DNS zones. """
@classmethod
def new(cls, zone_id):
"""Create a new zone version."""
return cls.call('domain.zone.version.new', zone_id)
@classmethod
def set(cls, zone_id, version_id):
"""Set active version of a zone."""
return cls.call('domain.zone.version.set', zone_id, version_id)
@classmethod
def delete(cls, zone_id, version_id):
"""Delete a version of a zone."""
return cls.call('domain.zone.version.delete', zone_id, version_id)
class Record(GandiModule):
""" Module to handle CLI commands.
$ gandi record list
$ gandi record create
"""
@classmethod
def list(cls, zone_id, options=None):
"""List zone records for a zone."""
options = options if options else {}
return cls.call('domain.zone.record.list', zone_id, 0, options)
@classmethod
def add(cls, zone_id, version_id, record):
"""Add record to a zone."""
return cls.call('domain.zone.record.add', zone_id, version_id, record)
@classmethod
def create(cls, zone_id, record):
"""Create a new zone version for record."""
cls.echo('Creating new zone version')
new_version_id = Zone.new(zone_id)
cls.echo('Updating zone version')
cls.add(zone_id, new_version_id, record)
cls.echo('Activation of new zone version')
Zone.set(zone_id, new_version_id)
return new_version_id
@classmethod
def delete(cls, zone_id, record):
"""Delete a record for a zone"""
cls.echo('Creating new zone record')
new_version_id = Zone.new(zone_id)
cls.echo('Deleting zone record')
cls.call('domain.zone.record.delete', zone_id, new_version_id, record)
cls.echo('Activation of new zone version')
Zone.set(zone_id, new_version_id)
return new_version_id
@classmethod
def zone_update(cls, zone_id, records):
"""Update records for a zone"""
cls.echo('Creating new zone file')
new_version_id = Zone.new(zone_id)
cls.echo('Updating zone records')
cls.call('domain.zone.record.set', zone_id, new_version_id, records)
cls.echo('Activation of new zone version')
Zone.set(zone_id, new_version_id)
return new_version_id
@classmethod
def update(cls, zone_id, old_record, new_record):
"""Update a record in a zone file"""
cls.echo('Creating new zone file')
new_version_id = Zone.new(zone_id)
new_record = new_record.replace(' IN', '')
new_record = new_record.split(' ', 4)
params_newrecord = {'name': new_record[0], 'ttl': int(new_record[1]),
'type': new_record[2], 'value': new_record[3]}
old_record = old_record.replace(' IN', '')
old_record = old_record.split(' ', 4)
try:
params = {'name': old_record[0], 'ttl': int(old_record[1]),
'type': old_record[2], 'value': old_record[3]}
except IndexError:
# failed to retrieve all values, try only use the name
params = {'name': old_record[0]}
record = cls.call('domain.zone.record.list', zone_id, new_version_id,
params)
if record:
cls.echo('Updating zone records')
try:
cls.call('domain.zone.record.update', zone_id,
new_version_id, {'id': record[0]['id']},
params_newrecord)
cls.echo('Activation of new zone version')
Zone.set(zone_id, new_version_id)
return new_version_id
except Exception as err:
cls.echo('An error as occured: %s' % err)
Zone.delete(zone_id, new_version_id)
else:
cls.echo('The record to update does not exist. Check records'
' already created with `gandi record list example.com'
' --output`')
return False
gandi.cli-1.2/gandi/cli/modules/oper.py 0000644 0001750 0001750 00000001127 12623134755 020713 0 ustar sayoun sayoun 0000000 0000000 """ Operation commands module. """
from gandi.cli.core.base import GandiModule
class Oper(GandiModule):
""" Module to handle CLI commands.
$ gandi oper info
$ gandi oper list
"""
@classmethod
def list(cls, options):
"""List operation."""
return cls.call('operation.list', options)
@classmethod
def count(cls, options):
"""Count operation."""
return cls.call('operation.count', options)
@classmethod
def info(cls, id):
"""Display information about an operation."""
return cls.call('operation.info', id)
gandi.cli-1.2/gandi/cli/modules/domain.py 0000644 0001750 0001750 00000007344 12714035303 021212 0 ustar sayoun sayoun 0000000 0000000 """ Domain commands module. """
import time
from gandi.cli.core.base import GandiModule
from gandi.cli.core.utils import DomainNotAvailable
class Domain(GandiModule):
""" Module to handle CLI commands.
$ gandi domain create
$ gandi domain info
$ gandi domain list
"""
@classmethod
def list(cls, options):
"""List operation."""
return cls.call('domain.list', options)
@classmethod
def info(cls, fqdn):
"""Display information about a domain."""
return cls.call('domain.info', fqdn)
@classmethod
def create(cls, fqdn, duration, owner, admin, tech, bill, nameserver,
background):
"""Create a domain."""
fqdn = fqdn.lower()
if not background and not cls.intty():
background = True
result = cls.call('domain.available', [fqdn])
while result[fqdn] == 'pending':
time.sleep(1)
result = cls.call('domain.available', [fqdn])
if result[fqdn] == 'unavailable':
raise DomainNotAvailable('%s is not available' % fqdn)
# retrieve handle of user and save it to configuration
user_handle = cls.call('contact.info')['handle']
cls.configure(True, 'api.handle', user_handle)
owner_ = owner or user_handle
admin_ = admin or user_handle
tech_ = tech or user_handle
bill_ = bill or user_handle
domain_params = {
'duration': duration,
'owner': owner_,
'admin': admin_,
'tech': tech_,
'bill': bill_,
}
if nameserver:
domain_params['nameservers'] = nameserver
result = cls.call('domain.create', fqdn, domain_params)
if background:
return result
# interactive mode, run a progress bar
cls.echo('Creating your domain.')
cls.display_progress(result)
cls.echo('Your domain %s has been created.' % fqdn)
@classmethod
def renew(cls, fqdn, duration, background):
"""Renew a domain."""
fqdn = fqdn.lower()
if not background and not cls.intty():
background = True
domain_info = cls.info(fqdn)
current_year = domain_info['date_registry_end'].year
domain_params = {
'duration': duration,
'current_year': current_year,
}
result = cls.call('domain.renew', fqdn, domain_params)
if background:
return result
# interactive mode, run a progress bar
cls.echo('Renewing your domain.')
cls.display_progress(result)
cls.echo('Your domain %s has been renewed.' % fqdn)
@classmethod
def autorenew_deactivate(cls, fqdn):
"""Activate deautorenew"""
fqdn = fqdn.lower()
result = cls.call('domain.autorenew.deactivate', fqdn)
return result
@classmethod
def autorenew_activate(cls, fqdn):
"""Activate autorenew"""
fqdn = fqdn.lower()
result = cls.call('domain.autorenew.activate', fqdn)
return result
@classmethod
def from_fqdn(cls, fqdn):
"""Retrieve domain id associated to a FQDN."""
result = cls.list({'fqdn': fqdn})
if len(result) > 0:
return result[0]['id']
@classmethod
def usable_id(cls, id):
"""Retrieve id from input which can be fqdn or id."""
# Check if it's already an integer.
try:
qry_id = int(id)
except Exception:
# Otherwise, assume it's a FQDN.
# This will return `None` if the FQDN is not found.
qry_id = cls.from_fqdn(id)
if not qry_id:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id
gandi.cli-1.2/gandi/cli/modules/forward.py 0000644 0001750 0001750 00000004234 12656121545 021413 0 ustar sayoun sayoun 0000000 0000000 """ Forward commands module. """
from gandi.cli.core.base import GandiModule
class Forward(GandiModule):
""" Module to handle CLI commands.
$ gandi forward create
$ gandi forward delete
$ gandi forward list
$ gandi forward update
"""
@classmethod
def list(cls, domain, options):
"""List forwards for a given domain name."""
return cls.call('domain.forward.list', domain, options)
@classmethod
def delete(cls, domain, source):
"""Delete a domain mail forward."""
return cls.call('domain.forward.delete', domain, source)
@classmethod
def create(cls, domain, source, destinations):
"""Create a domain mail forward."""
cls.echo('Creating mail forward %s@%s' % (source, domain))
options = {'destinations': list(destinations)}
result = cls.call('domain.forward.create', domain, source, options)
return result
@classmethod
def get_destinations(cls, domain, source):
"""Retrieve forward information."""
forwards = cls.list(domain, {'items_per_page': 500})
for fwd in forwards:
if fwd['source'] == source:
return fwd['destinations']
return []
@classmethod
def update(cls, domain, source, dest_add, dest_del):
"""Update a domain mail forward destinations."""
result = None
if dest_add or dest_del:
current_destinations = cls.get_destinations(domain, source)
fwds = current_destinations[:]
if dest_add:
for dest in dest_add:
if dest not in fwds:
fwds.append(dest)
if dest_del:
for dest in dest_del:
if dest in fwds:
fwds.remove(dest)
if ((len(current_destinations) != len(fwds))
or (current_destinations != fwds)):
cls.echo('Updating mail forward %s@%s' % (source, domain))
options = {'destinations': fwds}
result = cls.call('domain.forward.update', domain, source,
options)
return result
gandi.cli-1.2/gandi/cli/modules/contact.py 0000644 0001750 0001750 00000001422 12746404125 021374 0 ustar sayoun sayoun 0000000 0000000 """ Contact commands module. """
from gandi.cli.core.base import GandiModule
class Contact(GandiModule):
""" Module to handle CLI commands."""
@classmethod
def info(cls):
"""Display information about a Contact."""
return cls.call('contact.info')
@classmethod
def create(cls, params):
"""Create a new contact."""
return cls.call('contact.create', params, empty_key=True)
@classmethod
def create_dry_run(cls, params):
"""Create a new contact."""
return cls.call('contact.create', dict(params), empty_key=True,
dry_run=True, return_dry_run=True)
@classmethod
def balance(cls):
"""Retrieve balance status for a Contact."""
return cls.call('contact.balance')
gandi.cli-1.2/gandi/cli/modules/status.py 0000644 0001750 0001750 00000003467 13160664756 021310 0 ustar sayoun sayoun 0000000 0000000 """ Status commands module. """
try:
import urllib.parse as uparse
except ImportError:
import urllib as uparse
from gandi.cli.core.base import GandiModule
class Status(GandiModule):
""" Module to handle CLI commands.
$ gandi status
"""
base_url = 'https://status.gandi.net'
api_url = 'https://status.gandi.net/api'
@classmethod
def descriptions(cls):
""" Retrieve status descriptions from status.gandi.net. """
schema = cls.json_get('%s/status/schema' % cls.api_url, empty_key=True,
send_key=False)
descs = {}
for val in schema['fields']['status']['value']:
descs.update(val)
return descs
@classmethod
def services(cls):
"""Retrieve services statuses from status.gandi.net."""
return cls.json_get('%s/services' % cls.api_url, empty_key=True,
send_key=False)
@classmethod
def status(cls):
"""Retrieve global status from status.gandi.net."""
return cls.json_get('%s/status' % cls.api_url, empty_key=True,
send_key=False)
@classmethod
def events(cls, filters):
"""Retrieve events details from status.gandi.net."""
current = filters.pop('current', False)
current_params = []
if current:
current_params = [('current', 'true')]
filter_url = uparse.urlencode(sorted(list(filters.items())) + current_params) # noqa
events = cls.json_get('%s/events?%s' % (cls.api_url, filter_url),
empty_key=True, send_key=False)
return events
@classmethod
def event_timeline(cls, event):
"""Retrieve event timeline url for status.gandi.net."""
return '%s/timeline/events/%s' % (cls.base_url, event['id'])
gandi.cli-1.2/gandi/cli/modules/dns.py 0000644 0001750 0001750 00000010556 13227142762 020536 0 ustar sayoun sayoun 0000000 0000000 """ LiveDNS commands module. """
import json
from gandi.cli.core.base import GandiModule
class Dns(GandiModule):
""" Module to handle CLI commands.
$ gandi dns create
$ gandi dns delete
$ gandi dns domain.list
$ gandi dns list
$ gandi dns keys create
$ gandi dns keys delete
$ gandi dns keys info
$ gandi dns keys list
$ gandi dns keys recover
"""
api_url = 'https://dns.api.gandi.net/api/v5'
@classmethod
def get_sort_url(cls, url, sort_by=None):
if sort_by:
if not sort_by.startswith('rrset'):
sort_key = 'rrset_%s' % sort_by
else:
sort_key = sort_by
url = '%s?sort_by=%s' % (url, sort_key)
return url
@classmethod
def list(cls):
"""List domains."""
return cls.json_get('%s/domains' % cls.api_url)
@classmethod
def type_list(cls):
"""List supported records type."""
return cls.json_get('%s/dns/rrtypes' % cls.api_url)
@classmethod
def get_fqdn_info(cls, fqdn):
"""Retrieve information about a domain"""
return cls.json_get('%s/domains/%s' % (cls.api_url, fqdn))
@classmethod
def records(cls, fqdn, sort_by=None, text=False):
"""Display records information about a domain."""
meta = cls.get_fqdn_info(fqdn)
url = meta['domain_records_href']
kwargs = {}
if text:
kwargs = {'headers': {'Accept': 'text/plain'}}
return cls.json_get(cls.get_sort_url(url, sort_by), **kwargs)
@classmethod
def add_record(cls, fqdn, name, type, value, ttl):
"""Create record for a domain."""
data = {
"rrset_name": name,
"rrset_type": type,
"rrset_values": value,
}
if ttl:
data['rrset_ttl'] = int(ttl)
meta = cls.get_fqdn_info(fqdn)
url = meta['domain_records_href']
return cls.json_post(url, data=json.dumps(data))
@classmethod
def update_record(cls, fqdn, name, type, value, ttl, content):
"""Update all records for a domain."""
data = {
"rrset_name": name,
"rrset_type": type,
"rrset_values": value,
}
if ttl:
data['rrset_ttl'] = int(ttl)
meta = cls.get_fqdn_info(fqdn)
if content:
url = meta['domain_records_href']
kwargs = {'headers': {'Content-Type': 'text/plain'},
'data': content}
return cls.json_put(url, **kwargs)
url = '%s/domains/%s/records/%s/%s' % (cls.api_url, fqdn, name, type)
return cls.json_put(url, data=json.dumps(data))
@classmethod
def del_record(cls, fqdn, name, type):
"""Delete record for a domain."""
meta = cls.get_fqdn_info(fqdn)
url = meta['domain_records_href']
delete_url = url
if name:
delete_url = '%s/%s' % (delete_url, name)
if type:
delete_url = '%s/%s' % (delete_url, type)
return cls.json_delete(delete_url)
@classmethod
def keys(cls, fqdn, sort_by=None):
"""Display keys information about a domain."""
meta = cls.get_fqdn_info(fqdn)
url = meta['domain_keys_href']
return cls.json_get(cls.get_sort_url(url, sort_by))
@classmethod
def keys_info(cls, fqdn, key):
"""Retrieve key information."""
return cls.json_get('%s/domains/%s/keys/%s' %
(cls.api_url, fqdn, key))
@classmethod
def keys_create(cls, fqdn, flag):
"""Create new key entry for a domain."""
data = {
"flags": flag,
}
meta = cls.get_fqdn_info(fqdn)
url = meta['domain_keys_href']
ret, headers = cls.json_post(url, data=json.dumps(data),
return_header=True)
return cls.json_get(headers['location'])
@classmethod
def keys_delete(cls, fqdn, key):
"""Delete a key for a domain."""
return cls.json_delete('%s/domains/%s/keys/%s' %
(cls.api_url, fqdn, key))
@classmethod
def keys_recover(cls, fqdn, key):
"""Recover deleted key for a domain."""
data = {
"deleted": False,
}
return cls.json_put('%s/domains/%s/keys/%s' % (cls.api_url, fqdn, key),
data=json.dumps(data),)
gandi.cli-1.2/gandi/cli/modules/cert.py 0000644 0001750 0001750 00000034463 13117771234 020712 0 ustar sayoun sayoun 0000000 0000000 """ Certificate commands module. """
import os
import re
from click import UsageError
from gandi.cli.core.base import GandiModule
CROSSED_PEM = '''-----BEGIN CERTIFICATE-----
MIIFdzCCBF+gAwIBAgIQE+oocFv07O0MNmMJgGFDNjANBgkqhkiG9w0BAQwFADBv
MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
gYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5MRQwEgYDVQQHEwtK
ZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMS4wLAYD
VQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAgBJlFzYOw9sIs9CsVw127c0n00yt
UINh4qogTQktZAnczomfzD2p7PbPwdzx07HWezcoEStH2jnGvDoZtF+mvX2do2NC
tnbyqTsrkfjib9DsFiCQCT7i6HTJGLSR1GJk23+jBvGIGGqQIjy8/hPwhxR79uQf
jtTkUcYRZ0YIUcuGFFQ/vDP+fmyc/xadGL1RjjWmp2bIcmfbIWax1Jt4A8BQOujM
8Ny8nkz+rwWWNR9XWrf/zvk9tyy29lTdyOcSOk2uTIq3XJq0tyA9yn8iNK5+O2hm
AUTnAU5GU5szYPeUvlM3kHND8zLDU+/bqv50TmnHa4xgk97Exwzf4TKuzJM7UXiV
Z4vuPVb+DNBpDxsP8yUmazNt925H+nND5X4OpWaxKXwyhGNVicQNwZNUMBkTrNN9
N6frXTpsNVzbQdcS2qlJC9/YgIoJk2KOtWbPJYjNhLixP6Q5D9kCnusSTJV882sF
qV4Wg8y4Z+LoE53MW4LTTLPtW//e5XOsIzstAL81VXQJSdhJWBp/kjbmUZIO8yZ9
HE0XvMnsQybQv0FfQKlERPSZ51eHnlAfV1SoPv10Yy+xUGUJ5lhCLkMaTLTwJUdZ
+gQek9QmRkpQgbLevni3/GcV4clXhB4PY9bpYrrWX1Uu6lzGKAgEJTm4Diup8kyX
HAc/DVL17e8vgg8CAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTv
A73gJMtUGjAdBgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/
BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1Ud
HwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4
dGVybmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0
dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAJNl9jeD
lQ9ew4IcH9Z35zyKwKoJ8OkLJvHgwmp1ocd5yblSYMgpEg7wrQPWCcR23+WmgZWn
RtqCV6mVksW2jwMibDN3wXsyF24HzloUQToFJBv2FAY7qCUkDrvMKnXduXBBP3zQ
YzYhBx9G/2CkkeFnvN4ffhkUyWNnkepnB2u0j4vAbkN9w6GAbLIevFOFfdyQoaS8
Le9Gclc1Bb+7RrtubTeZtv8jkpHGbkD4jylW6l/VXxRTrPBPYer3IsynVgviuDQf
Jtl7GQVoP7o81DgGotPmjw7jtHFtQELFhLRAlSv0ZaBIefYdgWOWnU914Ph85I6p
0fKtirOMxyHNwu8=
-----END CERTIFICATE-----'''
URLS = {
1: {
'std': {
'default': {
'der': 'http://crt.gandi.net/GandiStandardSSLCA.crt',
'pem':
'http://www.gandi.net/static/CAs/GandiStandardSSLCA.pem',
},
},
'pro': {
'sgc': {
'pem':
'http://www.gandi.net/static/CAs/GandiSGCSSLCA.pem',
},
'default': {
'der': 'http://crt.gandi.net/GandiProSSLCA.crt',
'pem':
'http://www.gandi.net/static/CAs/GandiProSSLCA.pem',
},
},
},
2: {
'std': {
'default': {
'der': ['http://crt.gandi.net/GandiStandardSSLCA2.crt',
'http://crt.usertrust.com/USERTrustRSAAddTrustCA.crt'],
'pem': [
'http://www.gandi.net/static/CAs/GandiStandardSSLCA2.pem'],
},
},
'pro': {
'default': {
'der': ['http://crt.gandi.net/GandiProSSLCA2.crt',
'http://crt.usertrust.com/USERTrustRSAAddTrustCA.crt'],
'pem': ['http://www.gandi.net/static/CAs/GandiProSSLCA2.pem',
CROSSED_PEM],
},
},
},
}
class Certificate(GandiModule):
urls = URLS
""" Module to handle CLI commands.
$ gandi certificate change-dcv
$ gandi certificate create
$ gandi certificate delete
$ gandi certificate export
$ gandi certificate info
$ gandi certificate list
$ gandi certificate packages
$ gandi certificate resend-dcv
$ gandi certificate update
"""
@classmethod
def get_latest_valid(cls, hosts):
""" Retrieve valid certificates by fqdn. """
certs = cls.list({'status': 'valid', 'items_per_page': 500})
possible = None
if not isinstance(hosts, (tuple, list)):
hosts = [hosts]
for cert in certs:
cert_hosts = set([cert['cn']] + cert['altnames'])
if len(set(hosts) - cert_hosts) == 0:
if (possible and possible['date_end'] < cert['date_end']
or not possible):
possible = cert
return possible
@classmethod
def from_cn(cls, common_name):
""" Retrieve a certificate by its common name. """
# search with cn
result_cn = [(cert['id'], [cert['cn']] + cert['altnames'])
for cert in cls.list({'status': ['pending', 'valid'],
'items_per_page': 500,
'cn': common_name})]
# search with altname
result_alt = [(cert['id'], [cert['cn']] + cert['altnames'])
for cert in cls.list({'status': ['pending', 'valid'],
'items_per_page': 500,
'altname': common_name})]
result = result_cn + result_alt
ret = {}
for id_, fqdns in result:
for fqdn in fqdns:
ret.setdefault(fqdn, []).append(id_)
cert_id = ret.get(common_name)
if not cert_id:
return
return cert_id
@classmethod
def usable_ids(cls, id, accept_multi=True):
""" Retrieve id from input which can be an id or a cn."""
try:
qry_id = [int(id)]
except ValueError:
try:
qry_id = cls.from_cn(id)
except Exception:
qry_id = None
if not qry_id or not accept_multi and len(qry_id) != 1:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id if accept_multi else qry_id[0]
@classmethod
def usable_id(cls, id):
""" Retrieve id from single input."""
return cls.usable_ids(id, False)
@classmethod
def package_list(cls, options=None):
""" List possible certificate packages."""
options = options or {}
try:
return cls.safe_call('cert.package.list', options)
except UsageError as err:
if err.code == 150020:
return []
raise
__packages__ = None
@classmethod
def package_get(cls, package_name):
if not cls.__packages__:
cls.__packages__ = dict([(pkg['name'], pkg)
for pkg in cls.package_list()])
return cls.__packages__.get(package_name)
@classmethod
def list(cls, options=None):
""" List certificates."""
options = options or {}
return cls.call('cert.list', options)
@classmethod
def info(cls, id):
""" Display information about a certificate."""
return cls.call('cert.info', id)
@classmethod
def get_package(cls, common_name, type='std', max_altname=None,
altnames=None, warranty=None):
type = type or 'std'
if max_altname:
if max_altname < len(altnames):
cls.echo('You choose --max-altname %s but you have more '
'altnames (%s)' % (max_altname, len(altnames)))
return
else:
if '*' in common_name:
max_altname = 'w'
elif not altnames:
max_altname = 1
else:
for max_ in [1, 3, 5, 10, 20]:
if len(altnames) < max_:
max_altname = max_
break
if not max_altname:
cls.echo('Too many altnames, max is 20.')
return
pack_filter = 'cert_%s_%s_' % (type, max_altname)
if warranty:
pack_filter += '%s_' % (warranty)
packages = [item['name']
for item in cls.package_list()
if item['name'].startswith(pack_filter)]
return packages[0] if packages else None
@classmethod
def advice_dcv_method(cls, csr, package, altnames, dcv_method):
""" Display dcv_method information. """
params = {'csr': csr, 'package': package, 'dcv_method': dcv_method}
result = cls.call('cert.get_dcv_params', params)
if dcv_method == 'dns':
cls.echo('You have to add these records in your domain zone :')
cls.echo('\n'.join(result['message']))
@classmethod
def change_dcv(cls, oper_id, dcv_method):
""" Change dcv method."""
cls.call('cert.change_dcv', oper_id, dcv_method)
@classmethod
def resend_dcv(cls, oper_id):
""" Resend dcv. """
cls.call('cert.resend_dcv', oper_id)
@classmethod
def create(cls, csr, duration, package, altnames=None, dcv_method=None):
""" Create a new certificate. """
params = {'csr': csr, 'package': package, 'duration': duration}
if altnames:
params['altnames'] = altnames
if dcv_method:
params['dcv_method'] = dcv_method
if dcv_method in ('dns', 'file'):
cls.advice_dcv_method(csr, package, altnames, dcv_method)
try:
result = cls.call('cert.create', params)
except UsageError:
params['--dry-run'] = True
msg = '\n'.join(['%s (%s)' % (err['reason'], err['attr'])
for err in cls.call('cert.create', params)])
cls.error(msg)
raise
return result
@classmethod
def update(cls, cert_id, csr, private_key, country, state, city,
organisation, branch, altnames, dcv_method):
""" Update a certificate. """
cert = cls.info(cert_id)
if cert['status'] != 'valid':
cls.error('The certificate must be in valid status to be updated.')
return
common_name = cert['cn']
csr = cls.process_csr(common_name, csr, private_key, country, state,
city, organisation, branch)
if not csr:
return
params = {'csr': csr}
if altnames:
params['altnames'] = []
for altname in altnames:
params['altnames'].extend(altname.split(','))
if dcv_method:
params['dcv_method'] = dcv_method
try:
result = cls.call('cert.update', cert_id, params)
except UsageError:
params['--dry-run'] = True
msg = cls.call('cert.update', cert_id, params)
if msg:
cls.error(str(msg))
raise
return result
@staticmethod
def private_key(common_name):
return common_name.replace('*.', 'wildcard.') + '.key'
@classmethod
def gen_pk(cls, common_name, private_key):
if private_key:
cmd = 'openssl req -new -key %(key)s -out %(csr)s -subj "%(subj)s"'
if not os.path.exists(private_key):
content = private_key
private_key = cls.private_key(common_name)
with open(private_key, 'w') as fhandle:
fhandle.write(content)
else:
private_key = cls.private_key(common_name)
# TODO check if it exists
cmd = ('openssl req -new -newkey rsa:2048 -sha256 -nodes '
'-out %(csr)s -keyout %(key)s -subj "%(subj)s"')
return cmd, private_key
@classmethod
def create_csr(cls, common_name, private_key=None, params=None):
""" Create CSR. """
params = params or []
params = [(key, val) for key, val in params if val]
subj = '/' + '/'.join(['='.join(value) for value in params])
cmd, private_key = cls.gen_pk(common_name, private_key)
if private_key.endswith('.crt') or private_key.endswith('.key'):
csr_file = re.sub(r'\.(crt|key)$', '.csr', private_key)
else:
csr_file = private_key + '.csr'
cmd = cmd % {'csr': csr_file, 'key': private_key, 'subj': subj}
result = cls.execute(cmd)
if not result:
cls.echo('CSR creation failed')
cls.echo(cmd)
return
return csr_file
@classmethod
def get_common_name(cls, csr):
""" Read information from CSR. """
from tempfile import NamedTemporaryFile
fhandle = NamedTemporaryFile()
fhandle.write(csr.encode('latin1'))
fhandle.flush()
output = cls.exec_output('openssl req -noout -subject -in %s' %
fhandle.name)
if not output:
return
common_name = output.split('=')[-1].strip()
fhandle.close()
return common_name
@classmethod
def process_csr(cls, common_name, csr=None, private_key=None,
country=None, state=None, city=None, organisation=None,
branch=None):
""" Create a PK and a CSR if needed."""
if csr:
if branch or organisation or city or state or country:
cls.echo('Following options are only used to generate'
' the CSR.')
else:
params = (('CN', common_name),
('OU', branch),
('O', organisation),
('L', city),
('ST', state),
('C', country))
params = [(key, val) for key, val in params if val]
csr = cls.create_csr(common_name, private_key, params)
if csr and os.path.exists(csr):
with open(csr) as fcsr:
csr = fcsr.read()
return csr
@classmethod
def pretty_format_cert(cls, cert):
""" Pretty display of a certificate."""
crt = cert.get('cert')
if crt:
crt = ('-----BEGIN CERTIFICATE-----\n' +
'\n'.join([crt[index * 64:(index + 1) * 64]
for index in range(int(len(crt) / 64) + 1)]).rstrip('\n') + # noqa
'\n-----END CERTIFICATE-----')
return crt
@classmethod
def delete(cls, cert_id, background=False):
""" Delete a certificate."""
result = cls.call('cert.delete', cert_id)
if background:
return result
cls.echo("Deleting your certificate.")
cls.display_progress(result)
cls.echo('Your certificate %s has been deleted.' % cert_id)
return result
gandi.cli-1.2/gandi/cli/modules/__init__.py 0000644 0001750 0001750 00000000122 12441335654 021476 0 ustar sayoun sayoun 0000000 0000000 """ Contains CLI modules used by commands.
One module per command namespace.
"""
gandi.cli-1.2/gandi/cli/modules/paas.py 0000644 0001750 0001750 00000027157 13046322151 020672 0 ustar sayoun sayoun 0000000 0000000 """ PaaS commands module. """
import re
import sys
from gandi.cli.core.base import GandiModule
from gandi.cli.modules.metric import Metric
from gandi.cli.modules.vhost import Vhost
from gandi.cli.modules.datacenter import Datacenter
from gandi.cli.modules.sshkey import SshkeyHelper
class Paas(GandiModule, SshkeyHelper):
""" Module to handle CLI commands.
$ gandi paas attach
$ gandi paas clone
$ gandi paas console
$ gandi paas create
$ gandi paas delete
$ gandi paas info
$ gandi paas list
$ gandi paas restart
$ gandi paas types
$ gandi paas update
"""
@classmethod
def type_list(cls, options=None):
"""List type of PaaS instances."""
return cls.safe_call('paas.type.list', options)
@classmethod
def clone(cls, name, vhost, directory, origin):
"""Clone a PaaS instance's vhost into a local git repository."""
paas_info = cls.info(name)
if 'php' in paas_info['type'] and not vhost:
cls.error('PHP instances require indicating the VHOST to clone '
'with --vhost ')
paas_access = '%s@%s' % (paas_info['user'], paas_info['git_server'])
remote_url = 'ssh+git://%s/%s.git' % (paas_access, vhost)
command = 'git clone %s %s --origin %s' \
% (remote_url, directory, origin)
init_git = cls.execute(command)
if init_git:
cls.echo('Use `git push %s master` to push your code to the '
'instance.' % (origin))
cls.echo('Then `$ gandi deploy` to build and deploy your '
'application.')
@classmethod
def attach(cls, name, vhost, remote_name):
"""Attach an instance's vhost to a remote from the local repository."""
paas_access = cls.get('paas_access')
if not paas_access:
paas_info = cls.info(name)
paas_access = '%s@%s' \
% (paas_info['user'], paas_info['git_server'])
remote_url = 'ssh+git://%s/%s.git' % (paas_access, vhost)
ret = cls.execute('git remote add %s %s' % (remote_name, remote_url,))
if ret:
cls.echo('Added remote `%s` to your local git repository.'
% (remote_name))
cls.echo('Use `git push %s master` to push your code to the '
'instance.' % (remote_name))
cls.echo('Then `$ gandi deploy` to build and deploy your '
'application.')
@classmethod
def deploy(cls, remote_name, branch):
"""Deploy a PaaS instance."""
def get_remote_url(remote):
return 'git config --local --get remote.%s.url' % (remote)
remote_url = cls.exec_output(get_remote_url(remote_name)) \
.replace('\n', '')
if not remote_url or not re.search('gpaas.net|gandi.net', remote_url):
remote_name = ('$(git config --local --get branch.%s.remote)' %
branch)
remote_url = cls.exec_output(get_remote_url(remote_name)) \
.replace('\n', '')
error = None
if not remote_url:
error = True
cls.echo('Error: Could not find git remote '
'to extract deploy url from.')
elif not re.search('gpaas.net|gandi.net', remote_url):
error = True
cls.echo('Error: %s is not a valid Simple Hosting git remote.'
% (remote_url))
if error:
cls.echo("""This usually happens when:
- the current directory has no Simple Hosting git remote attached,
in this case, please see $ gandi paas attach --help
- the local branch being deployed hasn't been pushed to the \
remote repository yet,
in this case, please try $ git push %s
""" % (branch))
cls.echo('Otherwise, it\'s recommended to use'
' the --remote and/or --branch options:\n'
'$ gandi deploy --remote [--branch ]')
sys.exit(2)
remote_url_no_protocol = remote_url.split('://')[1]
splitted_url = remote_url_no_protocol.split('/')
paas_access = splitted_url[0]
deploy_git_host = splitted_url[1]
command = "ssh %s 'deploy %s %s'" \
% (paas_access, deploy_git_host, branch)
cls.execute(command)
@classmethod
def list(cls, options=None):
"""List PaaS instances."""
return cls.call('paas.list', options)
@classmethod
def info(cls, id):
"""Display information about a PaaS instance."""
return cls.call('paas.info', cls.usable_id(id))
@classmethod
def quota(cls, id):
"""return disk quota used/free"""
sampler = {'unit': 'minutes', 'value': 1, 'function': 'avg'}
query = 'vfs.df.bytes.all'
metrics = Metric.query(id, 60, query, 'paas', sampler)
df = {'free': 0, 'used': 0}
for metric in metrics:
what = metric['size'].pop()
# we need the most recent points
metric['points'].reverse()
for point in metric['points']:
if 'value' in point:
df[what] = point['value']
break
return df
@classmethod
def cache(cls, id):
"""return the number of query cache for the last 24H"""
sampler = {'unit': 'days', 'value': 1, 'function': 'sum'}
query = 'webacc.requests.cache.all'
metrics = Metric.query(id, 60 * 60 * 24, query, 'paas', sampler)
cache = {'hit': 0, 'miss': 0, 'not': 0, 'pass': 0}
for metric in metrics:
what = metric['cache'].pop()
for point in metric['points']:
value = point.get('value', 0)
cache[what] += value
return cache
@classmethod
def delete(cls, resources, background=False):
"""Delete a PaaS instance."""
if not isinstance(resources, (list, tuple)):
resources = [resources]
opers = []
for item in resources:
oper = cls.call('paas.delete', cls.usable_id(item))
if isinstance(oper, list):
opers.extend(oper)
else:
opers.append(oper)
if background:
return opers
# interactive mode, run a progress bar
cls.echo('Deleting your PaaS instance.')
cls.display_progress(opers)
@classmethod
def update(cls, id, name, size, quantity, password, sshkey, upgrade,
console, snapshot_profile, reset_mysql_password, background):
"""Update a PaaS instance."""
if not background and not cls.intty():
background = True
paas_params = {}
if name:
paas_params['name'] = name
if size:
paas_params['size'] = size
if quantity:
paas_params['quantity'] = quantity
if password:
paas_params['password'] = password
paas_params.update(cls.convert_sshkey(sshkey))
if upgrade:
paas_params['upgrade'] = upgrade
if console:
paas_params['console'] = console
# XXX to delete a snapshot_profile the value has to be an empty string
if snapshot_profile is not None:
paas_params['snapshot_profile'] = snapshot_profile
if reset_mysql_password:
paas_params['reset_mysql_password'] = reset_mysql_password
result = cls.call('paas.update', cls.usable_id(id), paas_params)
if background:
return result
# interactive mode, run a progress bar
cls.echo('Updating your PaaS instance.')
cls.display_progress(result)
@classmethod
def create(cls, name, size, type, quantity, duration, datacenter, vhosts,
password, snapshot_profile, background, sshkey):
"""Create a new PaaS instance."""
if not background and not cls.intty():
background = True
datacenter_id_ = int(Datacenter.usable_id(datacenter))
paas_params = {
'name': name,
'size': size,
'type': type,
'duration': duration,
'datacenter_id': datacenter_id_,
}
if password:
paas_params['password'] = password
if quantity:
paas_params['quantity'] = quantity
paas_params.update(cls.convert_sshkey(sshkey))
if snapshot_profile:
paas_params['snapshot_profile'] = snapshot_profile
result = cls.call('paas.create', paas_params)
if not background:
# interactive mode, run a progress bar
cls.echo('Creating your PaaS instance.')
cls.display_progress(result)
cls.echo('Your PaaS instance %s has been created.' % name)
if vhosts:
paas_info = cls.info(name)
Vhost.create(paas_info, vhosts, True, background)
return result
@classmethod
def restart(cls, resources, background=False):
"""Restart a PaaS instance."""
if not isinstance(resources, (list, tuple)):
resources = [resources]
opers = []
for item in resources:
oper = cls.call('paas.restart', cls.usable_id(item))
if isinstance(oper, list):
opers.extend(oper)
else:
opers.append(oper)
if background:
return opers
# interactive mode, run a progress bar
cls.echo('Restarting your PaaS instance.')
cls.display_progress(opers)
@classmethod
def resource_list(cls):
""" Get the possible list of resources (name, id and vhosts). """
items = cls.list({'items_per_page': 500})
ret = [paas['name'] for paas in items]
ret.extend([str(paas['id']) for paas in items])
for paas in items:
paas = cls.info(paas['id'])
ret.extend([vhost['name'] for vhost in paas['vhosts']])
return ret
@classmethod
def console(cls, id):
"""Open a console to a PaaS instance."""
oper = cls.call('paas.update', cls.usable_id(id), {'console': 1})
cls.echo('Activation of the console on your PaaS instance')
cls.display_progress(oper)
console_url = Paas.info(cls.usable_id(id))['console']
access = 'ssh %s' % console_url
cls.execute(access)
@classmethod
def usable_id(cls, id):
""" Retrieve id from input which can be hostname, vhost, id."""
try:
# id is maybe a hostname
qry_id = cls.from_hostname(id)
if not qry_id:
# id is maybe a vhost
qry_id = cls.from_vhost(id)
if not qry_id:
qry_id = int(id)
except Exception:
qry_id = None
if not qry_id:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id
@classmethod
def from_vhost(cls, vhost):
"""Retrieve paas instance id associated to a vhost."""
result = Vhost().list()
paas_hosts = {}
for host in result:
paas_hosts[host['name']] = host['paas_id']
return paas_hosts.get(vhost)
@classmethod
def from_hostname(cls, hostname):
"""Retrieve paas instance id associated to a host."""
result = cls.list({'items_per_page': 500})
paas_hosts = {}
for host in result:
paas_hosts[host['name']] = host['id']
return paas_hosts.get(hostname)
@classmethod
def list_names(cls):
"""Retrieve paas id and names."""
ret = dict([(item['id'], item['name'])
for item in cls.list({'items_per_page': 500})])
return ret
gandi.cli-1.2/gandi/cli/modules/sshkey.py 0000644 0001750 0001750 00000005626 12663546474 021275 0 ustar sayoun sayoun 0000000 0000000 """ SSH key commands module. """
import os
from gandi.cli.core.base import GandiModule
from gandi.cli.core.utils import DuplicateResults
class Sshkey(GandiModule):
""" Module to handle CLI commands.
$ gandi sshkey create
$ gandi sshkey delete
$ gandi sshkey info
$ gandi sshkey list
"""
@classmethod
def from_name(cls, name):
"""Retrieve a sshkey id associated to a name."""
sshkeys = cls.list({'name': name})
if len(sshkeys) == 1:
return sshkeys[0]['id']
elif not sshkeys:
return
raise DuplicateResults('sshkey name %s is ambiguous.' % name)
@classmethod
def usable_id(cls, id):
""" Retrieve id from input which can be name or id."""
try:
# id is maybe a sshkey name
qry_id = cls.from_name(id)
if not qry_id:
qry_id = int(id)
except DuplicateResults as exc:
cls.error(exc.errors)
except Exception:
qry_id = None
if not qry_id:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id
@classmethod
def list(cls, options=None):
""" List ssh keys."""
options = options if options else {}
return cls.call('hosting.ssh.list', options)
@classmethod
def info(cls, id):
""" Display information about an ssh key. """
return cls.call('hosting.ssh.info', cls.usable_id(id))
@classmethod
def create(cls, name, value):
""" Create a new ssh key."""
sshkey_params = {
'name': name,
'value': value,
}
result = cls.call('hosting.ssh.create', sshkey_params)
return result
@classmethod
def delete(cls, id):
"""Delete this ssh key."""
return cls.call('hosting.ssh.delete', cls.usable_id(id))
class SshkeyHelper(object):
""" Helper class to handle sshkey configuration entry. """
@classmethod
def convert_sshkey(cls, sshkey):
""" Return dict param with valid entries for vm/paas methods. """
params = {}
if sshkey:
params['keys'] = []
for ssh in sshkey:
if os.path.exists(os.path.expanduser(ssh)):
if 'ssh_key' in params:
cls.echo("Can't have more than one sshkey file.")
continue
with open(ssh) as fdesc:
sshkey_ = fdesc.read()
if sshkey_:
params['ssh_key'] = sshkey_
else:
sshkey_id = Sshkey.usable_id(ssh)
if sshkey_id:
params['keys'].append(sshkey_id)
else:
cls.echo('This is not a ssh key %s' % ssh)
if not params['keys']:
params.pop('keys')
return params
gandi.cli-1.2/gandi/cli/modules/metric.py 0000644 0001750 0001750 00000001710 12506522740 021222 0 ustar sayoun sayoun 0000000 0000000 """ metric module. """
import time
from datetime import datetime
from gandi.cli.core.base import GandiModule
class Metric(GandiModule):
""" Module to query metrics
"""
@classmethod
def query(cls, resources, time_range, query, resource_type, sampler):
"""Query statistics for given resources."""
if not isinstance(resources, (list, tuple)):
resources = [resources]
now = time.time()
start_utc = datetime.utcfromtimestamp(now - time_range)
end_utc = datetime.utcfromtimestamp(now)
date_format = '%Y-%m-%d %H:%M:%S'
start = start_utc.strftime(date_format)
end = end_utc.strftime(date_format)
query = {'start': start,
'end': end,
'query': query,
'resource_id': resources,
'resource_type': resource_type,
'sampler': sampler}
return cls.call('hosting.metric.query', query)
gandi.cli-1.2/gandi/cli/modules/vhost.py 0000644 0001750 0001750 00000003212 12653625522 021106 0 ustar sayoun sayoun 0000000 0000000 """ Vhost commands module. """
import os
from gandi.cli.core.base import GandiModule
class Vhost(GandiModule):
""" Module to handle CLI commands.
$ gandi vhost create
$ gandi vhost delete
$ gandi vhost info
$ gandi vhost list
"""
@classmethod
def list(cls, options=None):
""" List paas vhosts (in the future it should handle iaas vhosts)."""
options = options or {}
return cls.call('paas.vhost.list', options)
@classmethod
def info(cls, name):
""" Display information about a vhost. """
return cls.call('paas.vhost.info', name)
@classmethod
def create(cls, paas_info, vhost, alter_zone, background):
""" Create a new vhost. """
if not background and not cls.intty():
background = True
params = {'paas_id': paas_info['id'],
'vhost': vhost,
'zone_alter': alter_zone}
result = cls.call('paas.vhost.create', params, dry_run=True)
if background:
return result
cls.echo('Creating a new vhost.')
cls.display_progress(result)
cls.echo('Your vhost %s has been created.' % vhost)
return result
@classmethod
def delete(cls, resources, background=False):
""" Delete this vhost. """
if not isinstance(resources, (list, tuple)):
resources = [resources]
opers = []
for item in resources:
oper = cls.call('paas.vhost.delete', item)
opers.append(oper)
if background:
return opers
cls.echo('Deleting your vhost.')
cls.display_progress(opers)
gandi.cli-1.2/gandi/cli/modules/iaas.py 0000644 0001750 0001750 00000050374 13227414346 020671 0 ustar sayoun sayoun 0000000 0000000 """ VM commands module. """
import math
import os
import socket
import time
import errno
from gandi.cli.core.base import GandiModule
from gandi.cli.core.utils import randomstring
from gandi.cli.modules.datacenter import Datacenter
from gandi.cli.modules.sshkey import SshkeyHelper
from gandi.cli.core.utils import MigrationNotFinalized
class Iaas(GandiModule, SshkeyHelper):
""" Module to handle CLI commands.
$ gandi vm console
$ gandi vm create
$ gandi vm delete
$ gandi vm images
$ gandi vm info
$ gandi vm kernels
$ gandi vm list
$ gandi vm reboot
$ gandi vm ssh
$ gandi vm start
$ gandi vm stop
$ gandi vm update
"""
@classmethod
def list(cls, options=None):
"""List virtual machines."""
if not options:
options = {}
return cls.call('hosting.vm.list', options)
@classmethod
def resource_list(cls):
""" Get the possible list of resources (hostname, id). """
items = cls.list({'items_per_page': 500})
ret = [vm['hostname'] for vm in items]
ret.extend([str(vm['id']) for vm in items])
return ret
@classmethod
def info(cls, id):
"""Display information about a virtual machine."""
return cls.call('hosting.vm.info', cls.usable_id(id))
@classmethod
def stop(cls, resources, background=False):
"""Stop a virtual machine."""
if not isinstance(resources, (list, tuple)):
resources = [resources]
opers = []
for item in resources:
oper = cls.call('hosting.vm.stop', cls.usable_id(item))
if isinstance(oper, list):
opers.extend(oper)
else:
opers.append(oper)
if background:
return opers
# interactive mode, run a progress bar
instance_info = "'%s'" % ', '.join(resources)
cls.echo('Stopping your Virtual Machine(s) %s.' % instance_info)
cls.display_progress(opers)
@classmethod
def start(cls, resources, background=False):
"""Start a virtual machine."""
if not isinstance(resources, (list, tuple)):
resources = [resources]
opers = []
for item in resources:
oper = cls.call('hosting.vm.start', cls.usable_id(item))
if isinstance(oper, list):
opers.extend(oper)
else:
opers.append(oper)
if background:
return opers
# interactive mode, run a progress bar
instance_info = "'%s'" % ', '.join(resources)
cls.echo('Starting your Virtual Machine(s) %s.' % instance_info)
cls.display_progress(opers)
@classmethod
def reboot(cls, resources, background=False):
"""Reboot a virtual machine."""
if not isinstance(resources, (list, tuple)):
resources = [resources]
opers = []
for item in resources:
oper = cls.call('hosting.vm.reboot', cls.usable_id(item))
if isinstance(oper, list):
opers.extend(oper)
else:
opers.append(oper)
if background:
return opers
# interactive mode, run a progress bar
instance_info = "'%s'" % ', '.join(resources)
cls.echo('Rebooting your Virtual Machine(s) %s.' % instance_info)
cls.display_progress(opers)
@classmethod
def delete(cls, resources, background=False):
"""Delete a virtual machine."""
if not isinstance(resources, (list, tuple)):
resources = [resources]
opers = []
for item in resources:
oper = cls.call('hosting.vm.delete', cls.usable_id(item))
if not oper:
continue
if isinstance(oper, list):
opers.extend(oper)
else:
opers.append(oper)
if background:
return opers
# interactive mode, run a progress bar
instance_info = "'%s'" % ', '.join(resources)
cls.echo('Deleting your Virtual Machine(s) %s.' % instance_info)
if opers:
cls.display_progress(opers)
@classmethod
def required_max_memory(cls, id, memory):
"""
Recommend a max_memory setting for this vm given memory. If the
VM already has a nice setting, return None. The max_memory
param cannot be fixed too high, because page table allocation
would cost too much for small memory profile. Use a range as below.
"""
best = int(max(2 ** math.ceil(math.log(memory, 2)), 2048))
actual_vm = cls.info(id)
if (actual_vm['state'] == 'running'
and actual_vm['vm_max_memory'] != best):
return best
@classmethod
def update(cls, id, memory, cores, console, password, background,
max_memory):
"""Update a virtual machine."""
if not background and not cls.intty():
background = True
vm_params = {}
if memory:
vm_params['memory'] = memory
if cores:
vm_params['cores'] = cores
if console:
vm_params['console'] = console
if password:
vm_params['password'] = password
if max_memory:
vm_params['vm_max_memory'] = max_memory
result = cls.call('hosting.vm.update', cls.usable_id(id), vm_params)
if background:
return result
# interactive mode, run a progress bar
cls.echo('Updating your Virtual Machine %s.' % id)
cls.display_progress(result)
@classmethod
def create(cls, datacenter, memory, cores, ip_version, bandwidth,
login, password, hostname, image, run, background, sshkey,
size, vlan, ip, script, script_args, ssh):
"""Create a new virtual machine."""
from gandi.cli.modules.network import Ip, Iface
if not background and not cls.intty():
background = True
datacenter_id_ = int(Datacenter.usable_id(datacenter))
if not hostname:
hostname = randomstring('vm')
disk_name = 'sys_%s' % hostname[2:]
else:
disk_name = 'sys_%s' % hostname.replace('.', '')
vm_params = {
'hostname': hostname,
'datacenter_id': datacenter_id_,
'memory': memory,
'cores': cores,
}
if login:
vm_params['login'] = login
if run:
vm_params['run'] = run
if password:
vm_params['password'] = password
if ip_version:
vm_params['ip_version'] = ip_version
vm_params['bandwidth'] = bandwidth
if script:
with open(script) as fd:
vm_params['script'] = fd.read()
if script_args:
vm_params['script_args'] = script_args
vm_params.update(cls.convert_sshkey(sshkey))
# XXX: name of disk is limited to 15 chars in ext2fs, ext3fs
# but api allow 255, so we limit to 15 for now
disk_params = {'datacenter_id': vm_params['datacenter_id'],
'name': disk_name[:15]}
if size:
if isinstance(size, tuple):
prefix, size = size
disk_params['size'] = size
sys_disk_id_ = int(Image.usable_id(image, datacenter_id_))
ip_summary = []
if ip_version == 4:
ip_summary = ['v4', 'v6']
elif ip_version == 6:
ip_summary = ['v6']
if vlan:
ip_ = None
ip_summary.append('private')
if ip:
try:
ip_ = Ip.info(ip)
except Exception:
pass
else:
if not Ip._check_and_detach(ip_, None):
return
if ip_:
iface_id = ip_['iface_id']
else:
ip_create = Ip.create(4,
vm_params['datacenter_id'],
bandwidth,
None,
vlan,
ip)
iface_id = ip_create['iface_id']
# if there is a public ip, will attach this one later, else give
# the iface to vm.create
if not ip_version:
vm_params['iface_id'] = iface_id
result = cls.call('hosting.vm.create_from', vm_params, disk_params,
sys_disk_id_)
cls.echo('* Configuration used: %d cores, %dMb memory, ip %s, '
'image %s, hostname: %s, datacenter: %s' %
(cores, memory, '+'.join(ip_summary), image, hostname,
datacenter))
# background mode, bail out now (skip interactive part)
if background and (not vlan or not ip_version):
return result
# interactive mode, run a progress bar
cls.echo('Creating your Virtual Machine %s.' % hostname)
cls.display_progress(result)
cls.echo('Your Virtual Machine %s has been created.' % hostname)
vm_id = None
for oper in result:
if oper.get('vm_id'):
vm_id = oper.get('vm_id')
break
if vlan and ip_version:
attach = Iface._attach(iface_id, vm_id)
if background:
return attach
if 'ssh_key' not in vm_params and 'keys' not in vm_params:
return
if vm_id and ip_version:
cls.wait_for_sshd(vm_id)
if ssh:
cls.ssh_keyscan(vm_id)
cls.ssh(vm_id, 'root', None)
@classmethod
def need_finalize(cls, resource):
"""Check if vm migration need to be finalized."""
vm_id = cls.usable_id(resource)
params = {'type': 'hosting_migration_vm',
'step': 'RUN',
'vm_id': vm_id}
result = cls.call('operation.list', params)
if not result or len(result) > 1:
raise MigrationNotFinalized('Cannot find VM %s '
'migration operation.' % resource)
need_finalize = result[0]['params']['inner_step'] == 'wait_finalize'
if not need_finalize:
raise MigrationNotFinalized('VM %s migration does not need '
'finalization.' % resource)
@classmethod
def check_can_migrate(cls, resource):
"""Check if virtual machine can be migrated to another datacenter."""
vm_id = cls.usable_id(resource)
result = cls.call('hosting.vm.can_migrate', vm_id)
if not result['can_migrate']:
if result['matched']:
matched = result['matched'][0]
cls.echo('Your VM %s cannot be migrated yet. Migration will '
'be available when datacenter %s is opened.'
% (resource, matched))
else:
cls.echo('Your VM %s cannot be migrated.' % resource)
return False
return True
@classmethod
def migrate(cls, resource, background=False, finalize=False):
""" Migrate a virtual machine to another datacenter. """
vm_id = cls.usable_id(resource)
if finalize:
verb = 'Finalizing'
result = cls.call('hosting.vm.migrate', vm_id, True)
else:
verb = 'Starting'
result = cls.call('hosting.vm.migrate', vm_id, False)
dcs = {}
for dc in Datacenter.list():
dcs[dc['id']] = dc['dc_code']
oper = cls.call('operation.info', result['id'])
dc_from = dcs[oper['params']['from_dc_id']]
dc_to = dcs[oper['params']['to_dc_id']]
migration_msg = ('* %s the migration of VM %s '
'from datacenter %s to %s'
% (verb, resource, dc_from, dc_to))
cls.echo(migration_msg)
if background:
return result
cls.echo('VM migration in progress.')
cls.display_progress(result)
cls.echo('Your VM %s has been migrated.' % resource)
return result
@classmethod
def from_hostname(cls, hostname):
"""Retrieve virtual machine id associated to a hostname."""
result = cls.list({'hostname': str(hostname)})
if result:
return result[0]['id']
@classmethod
def usable_id(cls, id):
""" Retrieve id from input which can be hostname or id."""
try:
# id is maybe a hostname
qry_id = cls.from_hostname(id)
if not qry_id:
qry_id = int(id)
except Exception:
qry_id = None
if not qry_id:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id
@classmethod
def vm_ip(cls, vm_id):
"""Return the first usable ip address for this vm.
Returns a (version, ip) tuple."""
vm_info = cls.info(vm_id)
for iface in vm_info['ifaces']:
if iface['type'] == 'private':
continue
for ip in iface['ips']:
return ip['version'], ip['ip']
@classmethod
def wait_for_sshd(cls, vm_id):
"""Insist on having the vm booted and sshd
listening"""
cls.echo('Waiting for the vm to come online')
version, ip_addr = cls.vm_ip(vm_id)
give_up = time.time() + 300
last_error = None
while time.time() < give_up:
try:
inet = socket.AF_INET
if version == 6:
inet = socket.AF_INET6
sd = socket.socket(inet, socket.SOCK_STREAM,
socket.IPPROTO_TCP)
sd.settimeout(5)
sd.connect((ip_addr, 22))
sd.recv(1024)
return
except socket.error as err:
if err.errno == errno.EHOSTUNREACH and version == 6:
cls.error('%s is not reachable, you may be missing '
'IPv6 connectivity' % ip_addr)
last_error = err
time.sleep(1)
except Exception as err:
last_error = err
time.sleep(1)
cls.error('VM did not spin up (last error: %s)' % last_error)
@classmethod
def ssh_keyscan(cls, vm_id):
"""Wipe this old key and learn the new one from a freshly
created vm. This is a security risk for this VM, however
we dont have another way to learn the key yet, so do this
for the user."""
cls.echo('Wiping old key and learning the new one')
_version, ip_addr = cls.vm_ip(vm_id)
cls.execute('ssh-keygen -R "%s"' % ip_addr)
for _ in range(5):
output = cls.exec_output('ssh-keyscan "%s"' % ip_addr)
if output:
with open(os.path.expanduser('~/.ssh/known_hosts'), 'a') as f:
f.write(output)
return True
time.sleep(.5)
@classmethod
def scp(cls, vm_id, login, identity, local_file, remote_file):
"""Copy file to remote VM."""
cmd = ['scp']
if identity:
cmd.extend(('-i', identity,))
version, ip_addr = cls.vm_ip(vm_id)
if version == 6:
ip_addr = '[%s]' % ip_addr
cmd.extend((local_file, '%s@%s:%s' %
(login, ip_addr, remote_file),))
cls.echo('Running %s' % ' '.join(cmd))
for _ in range(5):
ret = cls.execute(cmd, False)
if ret:
break
time.sleep(.5)
return ret
@classmethod
def ssh(cls, vm_id, login, identity, args=None):
"""Spawn an ssh session to virtual machine."""
cmd = ['ssh']
if identity:
cmd.extend(('-i', identity,))
version, ip_addr = cls.vm_ip(vm_id)
if version == 6:
cmd.append('-6')
if not ip_addr:
cls.echo('No IP address found for vm %s, aborting.' % vm_id)
return
cmd.append('%s@%s' % (login, ip_addr,))
if args:
cmd.extend(args)
cls.echo('Requesting access using: %s ...' % ' '.join(cmd))
return cls.execute(cmd, False)
@classmethod
def console(cls, id):
"""Open a console to virtual machine."""
vm_info = cls.info(id)
if not vm_info['console']:
# first activate console
cls.update(id, memory=None, cores=None, console=True,
password=None, background=False, max_memory=None)
# now we can connect
# retrieve ip of vm
vm_info = cls.info(id)
version, ip_addr = cls.vm_ip(id)
console_url = vm_info.get('console_url', 'console.gandi.net')
access = 'ssh %s@%s' % (ip_addr, console_url)
cls.execute(access)
class Image(GandiModule):
""" Module to handle CLI commands.
$ gandi vm images
"""
@classmethod
def list(cls, datacenter=None, label=None):
"""List available images for vm creation."""
options = {}
if datacenter:
datacenter_id = int(Datacenter.usable_id(datacenter))
options['datacenter_id'] = datacenter_id
# implement a filter by label as API doesn't handle it
images = cls.safe_call('hosting.image.list', options)
if not label:
return images
return [img for img in images
if label.lower() in img['label'].lower()]
@classmethod
def is_deprecated(cls, label, datacenter=None):
"""Check if image if flagged as deprecated."""
images = cls.list(datacenter, label)
images_visibility = dict([(image['label'], image['visibility'])
for image in images])
return images_visibility.get(label, 'all') == 'deprecated'
@classmethod
def from_label(cls, label, datacenter=None):
"""Retrieve disk image id associated to a label."""
result = cls.list(datacenter=datacenter)
image_labels = dict([(image['label'], image['disk_id'])
for image in result])
return image_labels.get(label)
@classmethod
def from_sysdisk(cls, label):
"""Retrieve disk id from available system disks"""
disks = cls.safe_call('hosting.disk.list', {'name': label})
if len(disks):
return disks[0]['id']
@classmethod
def usable_id(cls, id, datacenter=None):
""" Retrieve id from input which can be label or id."""
try:
qry_id = int(id)
except Exception:
# if id is a string, prefer a system disk then a label
qry_id = cls.from_sysdisk(id) or cls.from_label(id, datacenter)
if not qry_id:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id
class Kernel(GandiModule):
""" Module to handle Gandi Kernels. """
@classmethod
def list(cls, datacenter=None, flavor=None, match='', exact_match=False):
""" List available kernels for datacenter."""
if not datacenter:
dc_ids = [dc['id'] for dc in Datacenter.filtered_list()]
kmap = {}
for dc_id in dc_ids:
vals = cls.safe_call('hosting.disk.list_kernels', dc_id)
for key in vals:
kmap.setdefault(key, []).extend(vals.get(key, []))
# remove duplicates
for key in kmap:
kmap[key] = list(set(kmap[key]))
else:
dc_id = Datacenter.usable_id(datacenter)
kmap = cls.safe_call('hosting.disk.list_kernels', dc_id)
if match:
for flav in kmap:
if exact_match:
kmap[flav] = [x for x in kmap[flav] if match == x]
else:
kmap[flav] = [x for x in kmap[flav] if match in x]
if flavor:
if flavor not in kmap:
cls.error('flavor %s not supported here' % flavor)
return dict([(flavor, kmap[flavor])])
return kmap
@classmethod
def is_available(cls, disk, kernel):
""" Check if kernel is available for disk."""
kmap = cls.list(disk['datacenter_id'], None, kernel, True)
for flavor in kmap:
if kernel in kmap[flavor]:
return True
return False
gandi.cli-1.2/gandi/cli/modules/mail.py 0000644 0001750 0001750 00000005010 12656121545 020662 0 ustar sayoun sayoun 0000000 0000000 """ Mail commands module. """
from gandi.cli.core.base import GandiModule
class Mail(GandiModule):
""" Module to handle CLI commands.
$ gandi mail create
$ gandi mail delete
$ gandi mail info
$ gandi mail list
$ gandi mail purge
$ gandi mail update
"""
@classmethod
def list(cls, domain, options):
"""List mailboxes for a given domain name."""
return cls.call('domain.mailbox.list', domain, options)
@classmethod
def info(cls, domain, login):
"""Display information about a mailbox."""
return cls.call('domain.mailbox.info', domain, login)
@classmethod
def create(cls, domain, login, options, alias):
"""Create a mailbox."""
cls.echo('Creating your mailbox.')
result = cls.call('domain.mailbox.create', domain, login, options)
if alias:
cls.echo('Creating aliases.')
result = cls.set_alias(domain, login, list(alias))
return result
@classmethod
def delete(cls, domain, login):
"""Delete a mailbox."""
return cls.call('domain.mailbox.delete', domain, login)
@classmethod
def update(cls, domain, login, options, alias_add, alias_del):
"""Update a mailbox."""
cls.echo('Updating your mailbox.')
result = cls.call('domain.mailbox.update', domain, login, options)
if alias_add or alias_del:
current_aliases = cls.info(domain, login)['aliases']
aliases = current_aliases[:]
if alias_add:
for alias in alias_add:
if alias not in aliases:
aliases.append(alias)
if alias_del:
for alias in alias_del:
if alias in aliases:
aliases.remove(alias)
if ((len(current_aliases) != len(aliases))
or (current_aliases != aliases)):
cls.echo('Updating aliases.')
result = cls.set_alias(domain, login, aliases)
return result
@classmethod
def purge(cls, domain, login, background=False):
"""Purge a mailbox."""
oper = cls.call('domain.mailbox.purge', domain, login)
if background:
return oper
else:
cls.echo('Purging in progress')
cls.display_progress(oper)
@classmethod
def set_alias(cls, domain, login, aliases):
"""Update aliases on a mailbox."""
return cls.call('domain.mailbox.alias.set', domain, login, aliases)
gandi.cli-1.2/gandi/cli/modules/webacc.py 0000644 0001750 0001750 00000026510 12663631621 021173 0 ustar sayoun sayoun 0000000 0000000 """ Webaccelerator commands module """
from gandi.cli.core.base import GandiModule
from gandi.cli.modules.datacenter import Datacenter
class Webacc(GandiModule):
""" Module to handle CLI commands.
$ gandi webacc list
$ gandi webacc info
$ gandi webacc create
$ gandi webacc add
$ gandi webacc delete
$ gandi webacc enable
$ gandi webacc disable
$ gandi webacc probe
"""
@classmethod
def list(cls, options=None):
""" List all webaccelerator """
if not options:
options = {}
return cls.call('hosting.rproxy.list', options)
@classmethod
def info(cls, id):
""" Get information about a Webaccelerator """
return cls.call('hosting.rproxy.info', cls.usable_id(id))
@classmethod
def create(cls, name, datacenter, backends, vhosts, algorithm,
ssl_enable, zone_alter):
""" Create a webaccelerator """
datacenter_id_ = int(Datacenter.usable_id(datacenter))
params = {
'datacenter_id': datacenter_id_,
'name': name,
'lb': {'algorithm': algorithm},
'override': True,
'ssl_enable': ssl_enable,
'zone_alter': zone_alter
}
if vhosts:
params['vhosts'] = vhosts
if backends:
params['servers'] = backends
try:
result = cls.call('hosting.rproxy.create', params)
cls.echo('Creating your webaccelerator %s' % params['name'])
cls.display_progress(result)
cls.echo('Your webaccelerator have been created')
return result
except Exception as err:
if err.code == 580142:
for vhost in params['vhosts']:
dns_entry = cls.call(
'hosting.rproxy.vhost.get_dns_entries',
{'datacenter': datacenter_id_, 'vhost': vhost})
txt_record = "@ 3600 IN TXT \"%s=%s\"" % (dns_entry['key'],
dns_entry['txt'])
cname_record = "%s 3600 IN CNAME %s" % (dns_entry['key'],
dns_entry['cname'])
cls.echo('The domain %s don\'t use Gandi DNS or you have'
' not sufficient right to alter the zone file. '
'Edit your zone file adding this TXT and CNAME '
'record and try again :' % vhost)
cls.echo(txt_record)
cls.echo(cname_record)
cls.echo('\nOr add a file containing %s at :\n'
'http://%s/%s.txt\n' % (dns_entry['txt'],
dns_entry['domain'],
dns_entry['txt']))
cls.separator_line('-', 4)
else:
cls.echo(err)
@classmethod
def update(cls, resource, new_name, algorithm, ssl_enable, ssl_disable):
""" Update a webaccelerator"""
params = {}
if new_name:
params['name'] = new_name
if algorithm:
params['lb'] = {'algorithm': algorithm}
if ssl_enable:
params['ssl_enable'] = ssl_enable
if ssl_disable:
params['ssl_enable'] = False
result = cls.call('hosting.rproxy.update', cls.usable_id(resource),
params)
cls.echo('Updating your webaccelerator')
cls.display_progress(result)
cls.echo('The webaccelerator have been udated')
return result
@classmethod
def delete(cls, name):
""" Delete a webaccelerator """
result = cls.call('hosting.rproxy.delete', cls.usable_id(name))
cls.echo('Deleting your webaccelerator named %s' % name)
cls.display_progress(result)
cls.echo('Webaccelerator have been deleted')
return result
@classmethod
def backend_list(cls, options):
""" List all servers used by webaccelerator """
return cls.call('hosting.rproxy.server.list', options)
@classmethod
def backend_add(cls, name, backend):
""" Add a backend into a webaccelerator """
oper = cls.call(
'hosting.rproxy.server.create', cls.usable_id(name), backend)
cls.echo('Adding backend %s:%s into webaccelerator' %
(backend['ip'], backend['port']))
cls.display_progress(oper)
cls.echo('Backend added')
return oper
@classmethod
def backend_remove(cls, backend):
""" Remove a backend on a webaccelerator """
server = cls.backend_list(backend)
if server:
oper = cls.call('hosting.rproxy.server.delete', server[0]['id'])
cls.echo('Removing backend %s:%s into webaccelerator' %
(backend['ip'], backend['port']))
cls.display_progress(oper)
cls.echo('Your backend have been removed')
return oper
else:
return cls.echo('No backend found')
@classmethod
def backend_enable(cls, backend):
""" Enable a backend for a server """
server = cls.backend_list(backend)
if server:
oper = cls.call('hosting.rproxy.server.enable', server[0]['id'])
cls.echo('Activating backend %s' % server[0]['ip'])
cls.display_progress(oper)
cls.echo('Backend activated')
return oper
else:
return cls.echo('No backend found')
@classmethod
def backend_disable(cls, backend):
""" Disable a backend for a server """
server = cls.backend_list(backend)
oper = cls.call('hosting.rproxy.server.disable',
server[0]['id'])
cls.echo('Desactivating backend on server %s' %
server[0]['ip'])
cls.display_progress(oper)
cls.echo('Backend desactivated')
return oper
@classmethod
def vhost_list(cls):
""" List all vhosts used by webaccelerator """
return cls.call('hosting.rproxy.vhost.list')
@classmethod
def vhost_add(cls, resource, params):
""" Add a vhost into a webaccelerator """
try:
oper = cls.call(
'hosting.rproxy.vhost.create', cls.usable_id(resource), params)
cls.echo('Adding your virtual host (%s) into %s' %
(params['vhost'], resource))
cls.display_progress(oper)
cls.echo('Your virtual host habe been added')
return oper
except Exception as err:
if err.code == 580142:
dc = cls.info(resource)
dns_entry = cls.call('hosting.rproxy.vhost.get_dns_entries',
{'datacenter': dc['datacenter']['id'],
'vhost': params['vhost']})
txt_record = "%s 3600 IN TXT \"%s=%s\"" % (dns_entry['key'],
dns_entry['key'],
dns_entry['txt'])
cname_record = "%s 3600 IN CNAME %s" % (dns_entry['key'],
dns_entry['cname'])
cls.echo('The domain don\'t use Gandi DNS or you have not'
' sufficient right to alter the zone file. '
'Edit your zone file adding this TXT and CNAME '
'record and try again :')
cls.echo(txt_record)
cls.echo(cname_record)
cls.echo('\nOr add a file containing %s at :\n'
'http://%s/%s.txt\n' % (dns_entry['txt'],
dns_entry['domain'],
dns_entry['txt']))
else:
cls.echo(err)
@classmethod
def vhost_remove(cls, name):
""" Delete a vhost in a webaccelerator """
oper = cls.call('hosting.rproxy.vhost.delete', name)
cls.echo('Deleting your virtual host %s' % name)
cls.display_progress(oper)
cls.echo('Your virtual host have been removed')
return oper
@classmethod
def probe(cls, resource, enable, disable, test, host, interval,
http_method, http_response, threshold, timeout, url, window):
""" Set a probe for a webaccelerator """
params = {
'host': host,
'interval': interval,
'method': http_method,
'response': http_response,
'threshold': threshold,
'timeout': timeout,
'url': url,
'window': window
}
if enable:
params['enable'] = True
elif disable:
params['enable'] = False
if test:
result = cls.call(
'hosting.rproxy.probe.test', cls.usable_id(resource), params)
else:
result = cls.call(
'hosting.rproxy.probe.update', cls.usable_id(resource), params)
cls.display_progress(result)
return result
@classmethod
def probe_enable(cls, resource):
""" Activate a probe on a webaccelerator """
oper = cls.call('hosting.rproxy.probe.enable', cls.usable_id(resource))
cls.echo('Activating probe on %s' % resource)
cls.display_progress(oper)
cls.echo('The probe have been activated')
return oper
@classmethod
def probe_disable(cls, resource):
""" Disable a probe on a webaccelerator """
oper = cls.call('hosting.rproxy.probe.disable',
cls.usable_id(resource))
cls.echo('Desactivating probe on %s' % resource)
cls.display_progress(oper)
cls.echo('The probe have been desactivated')
return oper
@classmethod
def usable_id(cls, id):
""" Retrieve id from input which can be hostname, vhost, id. """
try:
# id is maybe a hostname
qry_id = cls.from_name(id)
if not qry_id:
# id is maybe an ip
qry_id = cls.from_ip(id)
if not qry_id:
qry_id = cls.from_vhost(id)
except Exception:
qry_id = None
if not qry_id:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id
@classmethod
def from_name(cls, name):
"""Retrieve webacc id associated to a webacc name."""
result = cls.list({'items_per_page': 500})
webaccs = {}
for webacc in result:
webaccs[webacc['name']] = webacc['id']
return webaccs.get(name)
@classmethod
def from_ip(cls, ip):
"""Retrieve webacc id associated to a webacc ip"""
result = cls.list({'items_per_page': 500})
webaccs = {}
for webacc in result:
for server in webacc['servers']:
webaccs[server['ip']] = webacc['id']
return webaccs.get(ip)
@classmethod
def from_vhost(cls, vhost):
"""Retrieve webbacc id associated to a webacc vhost"""
result = cls.list({'items_per_page': 500})
webaccs = {}
for webacc in result:
for vhost in webacc['vhosts']:
webaccs[vhost['name']] = webacc['id']
return webaccs.get(vhost)
gandi.cli-1.2/gandi/cli/modules/account.py 0000644 0001750 0001750 00000002331 12656121545 021377 0 ustar sayoun sayoun 0000000 0000000 """ Hosting account module. """
from gandi.cli.core.base import GandiModule
class Account(GandiModule):
""" Module to handle CLI commands.
$ gandi account info
"""
@classmethod
def info(cls):
"""Get information about the hosting account in use"""
return cls.call('hosting.account.info')
@classmethod
def creditusage(cls):
"""Get credit usage per hour"""
rating = cls.call('hosting.rating.list')
if not rating:
return 0
rating = rating.pop()
usage = [sum(resource.values())
for resource in rating.values()
if isinstance(resource, dict)]
return sum(usage)
@classmethod
def all(cls):
""" Get all informations about this account """
account = cls.info()
creditusage = cls.creditusage()
if not creditusage:
return account
left = account['credits'] / creditusage
years, hours = divmod(left, 365 * 24)
months, hours = divmod(hours, 31 * 24)
days, hours = divmod(hours, 24)
account.update({'credit_usage': creditusage,
'left': (years, months, days, hours)})
return account
gandi.cli-1.2/gandi/cli/modules/hostedcert.py 0000644 0001750 0001750 00000012423 12663631621 022111 0 ustar sayoun sayoun 0000000 0000000 """ Hosted certificate commands module. """
import os
from gandi.cli.core.base import GandiModule
class HostedCert(GandiModule):
""" Module to handle CLI commands.
$ gandi certstore list
$ gandi certstore info
$ gandi certstore create
$ gandi certstore delete
"""
@classmethod
def from_fqdn(cls, fqdn):
return cls.list({'fqdns': '%s' % fqdn, 'state': 'created'})
@classmethod
def usable_id(cls, id):
""" Retrieve id from single input. """
hcs = cls.from_fqdn(id)
if hcs:
return [hc_['id'] for hc_ in hcs]
try:
return int(id)
except (TypeError, ValueError):
pass
@classmethod
def list(cls, options=None):
""" List hosted certificates. """
options = options or {}
return cls.call('cert.hosted.list', options)
@classmethod
def info(cls, id):
""" Display information about a hosted certificate. """
return cls.call('cert.hosted.info', id)
@classmethod
def infos(cls, fqdn):
""" Display information about hosted certificates for a fqdn. """
if isinstance(fqdn, (list, tuple)):
ids = []
for fqd_ in fqdn:
ids.extend(cls.infos(fqd_))
return ids
ids = cls.usable_id(fqdn)
if not ids:
return []
if not isinstance(ids, (list, tuple)):
ids = [ids]
return [cls.info(id_) for id_ in ids]
@classmethod
def create(cls, key, crt):
""" Add a new crt in the hosted cert store. """
options = {'crt': crt, 'key': key}
return cls.call('cert.hosted.create', options)
@classmethod
def delete(cls, id_):
""" Remove a cert from the hosted cert store. """
return cls.call('cert.hosted.delete', id_)
@classmethod
def activate_ssl(cls, vhosts, ssl, private_key, poll_cert):
from .cert import Certificate
if not ssl:
return True
if not isinstance(vhosts, (list, tuple)):
vhosts = [vhosts]
missing = []
for vhost in vhosts:
try:
hostedcert = cls.infos(vhost)
if hostedcert:
cls.debug('There is a cert in store for %s' % vhost)
else:
missing.append(vhost)
except ValueError:
missing.append(vhost)
if not missing:
return True
vhosts = missing
cls.debug('Trying to get certificate or generate it for %s' %
(', '.join(vhosts)))
vhost = vhosts[0]
altnames = vhosts[1:]
cert = Certificate.get_latest_valid(vhosts)
if cert:
if not private_key:
cls.echo('Please give the private key for certificate id %s '
'(CN: %s)' % (cert['id'], cert['cn']))
return False
cls.echo('Will use the certificate id %s (CN: %s)' %
(cert['id'], cert['cn']))
if os.path.isfile(private_key):
with open(private_key) as fhandle:
private_key = fhandle.read()
crt = Certificate.pretty_format_cert(cert)
cls.create(private_key, crt)
elif poll_cert:
cls.echo('This operation will take a long time waiting for the '
'certificate to be generated.')
# create the certificate
csr = Certificate.process_csr(vhost, private_key=private_key)
package = Certificate.get_package(vhost, altnames=altnames)
oper = Certificate.create(csr, 1, package, altnames=altnames)
cls.echo('If the term close, you can check the certificate create '
'operation with :')
cls.echo('$ gandi certificate follow %s' % oper['id'])
cls.echo("And when it's DONE you can continue doing :")
cls.echo('$ gandi certificate export %s' % vhost)
cls.echo('$ gandi certstore create --private-key %s --certificate '
'%s' % (vhost.replace('*.', 'wildcard.') + '.key',
vhost.replace('*.', 'wildcard.') + '.crt'))
cls.echo('And then relaunch the current command.\n')
cls.echo('Creating the certificate for %s%s' %
(vhost,
' (%s)' % ', '.join(altnames) if altnames else ''))
cls.display_progress(oper)
# create the hosted certificate.
# this will always give us a file name.
_, private_key = Certificate.gen_pk(vhost, private_key)
with open(private_key) as fhandle:
private_key = fhandle.read()
cert = Certificate.get_latest_valid(vhosts)
crt = Certificate.pretty_format_cert(cert)
cls.create(private_key, crt)
else:
cls.echo('There is no certificate for %s.' % ', '.join(vhosts))
cls.echo('Create the certificate with (for exemple) :')
cls.echo('$ gandi certificate create --cn %s --type std %s' %
(vhost, '--altnames=%s' % ','.join(altnames) if altnames
else ''))
cls.echo('Or relaunch the current command with --poll-cert option')
return True
gandi.cli-1.2/gandi/cli/modules/network.py 0000644 0001750 0001750 00000031175 12656121545 021444 0 ustar sayoun sayoun 0000000 0000000 """ Iface and vlan commands module. """
from click.exceptions import UsageError
from gandi.cli.core.base import GandiModule
from gandi.cli.modules.datacenter import Datacenter
from gandi.cli.modules.iaas import Iaas
class Ip(GandiModule):
""" Module to handle CLI commands.
$ gandi ip list
$ gandi ip info
$ gandi ip create
$ gandi ip attach
$ gandi ip detach
$ gandi ip delete
"""
@classmethod
def list(cls, options=None):
"""List ip"""
options = options or {}
return cls.call('hosting.ip.list', options)
@classmethod
def _info(cls, ip_id):
""" Get information about an ip."""
return cls.call('hosting.ip.info', ip_id)
@classmethod
def info(cls, resource):
""" Get information about an up."""
return cls._info(cls.usable_id(resource))
@classmethod
def create(cls, ip_version, datacenter, bandwidth, vm=None, vlan=None,
ip=None, background=False):
""" Create a public ip and attach it if vm is given. """
return Iface.create(ip_version, datacenter, bandwidth, vlan, vm, ip,
background)
@classmethod
def update(cls, resource, params, background=False):
""" Update this IP """
cls.echo('Updating your IP')
result = cls.call('hosting.ip.update', cls.usable_id(resource),
params)
if not background:
cls.display_progress(result)
return result
@classmethod
def resource_list(cls):
""" Get the possible list of resources (name, id). """
items = cls.list({'items_per_page': 500})
ret = [str(ip['id']) for ip in items]
ret.extend([ip['ip'] for ip in items])
return ret
@classmethod
def _check_and_detach(cls, ip_, vm_=None):
# if the ip exists and is attached, we have to detach it
iface = Iface.info(ip_['iface_id'])
if iface.get('vm_id'):
if vm_ and iface['vm_id'] == vm_.get('id'):
return False
detach = Iface._detach(iface['id'])
cls.display_progress(detach)
return True
@classmethod
def attach(cls, ip, vm, background=False, force=False):
""" Attach """
vm_ = Iaas.info(vm)
ip_ = cls.info(ip)
if not cls._check_and_detach(ip_, vm_):
return
# then we should attach the ip to the vm
attach = Iface._attach(ip_['iface_id'], vm_['id'])
if not background:
cls.display_progress(attach)
return attach
@classmethod
def _detach(cls, ip_, iface, background=False, force=False):
detach = Iface._detach(iface['id'])
if background:
return detach
if detach:
cls.display_progress(detach)
return detach
@classmethod
def detach(cls, resource, background=False, force=False):
try:
ip_ = cls.info(resource)
except UsageError:
cls.error("Can't find this ip %s" % resource)
iface = Iface.info(ip_['iface_id'])
return cls._detach(ip_, iface, background, force)
@classmethod
def delete(cls, resources, background=False, force=False):
"""Delete an ip by deleting the iface"""
if not isinstance(resources, (list, tuple)):
resources = [resources]
ifaces = []
for item in resources:
try:
ip_ = cls.info(item)
except UsageError:
cls.error("Can't find this ip %s" % item)
iface = Iface.info(ip_['iface_id'])
ifaces.append(iface['id'])
return Iface.delete(ifaces, background)
@classmethod
def from_ip(cls, ip):
"""Retrieve ip id associated to an ip."""
ips = dict([(ip_['ip'], ip_['id'])
for ip_ in cls.list({'items_per_page': 500})])
return ips.get(ip)
@classmethod
def usable_id(cls, id):
""" Retrieve id from input which can be ip or id."""
try:
# id is maybe an ip
qry_id = cls.from_ip(id)
if not qry_id:
qry_id = int(id)
except Exception:
qry_id = None
if not qry_id:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id
class Vlan(GandiModule):
""" Module to handle CLI commands.
$ gandi vlan list
$ gandi vlan info
$ gandi vlan create
$ gandi vlan update
$ gandi vlan delete
"""
@classmethod
def list(cls, datacenter=None):
"""List virtual machine vlan
(in the future it should also handle PaaS vlan)."""
options = {}
if datacenter:
datacenter_id = int(Datacenter.usable_id(datacenter))
options['datacenter_id'] = datacenter_id
return cls.call('hosting.vlan.list', options)
@classmethod
def resource_list(cls):
""" Get the possible list of resources (name, id). """
items = cls.list()
ret = [vlan['name'] for vlan in items]
ret.extend([str(vlan['id']) for vlan in items])
return ret
@classmethod
def ifaces(cls, name):
""" Get vlan attached ifaces. """
ifaces = Iface.list({'vlan_id': cls.usable_id(name)})
ret = []
for iface in ifaces:
ret.append(Iface.info(iface['id']))
return ret
@classmethod
def _info(cls, vlan_id):
""" Get information about a vlan."""
return cls.call('hosting.vlan.info', vlan_id)
@classmethod
def info(cls, name):
""" Get information about a vlan."""
return cls._info(cls.usable_id(name))
@classmethod
def delete(cls, resources, background=False):
"""Delete a vlan."""
if not isinstance(resources, (list, tuple)):
resources = [resources]
opers = []
for item in resources:
oper = cls.call('hosting.vlan.delete', cls.usable_id(item))
if not oper:
continue
if isinstance(oper, list):
opers.extend(oper)
else:
opers.append(oper)
if background:
return opers
# interactive mode, run a progress bar
cls.echo('Deleting your vlan.')
if opers:
cls.display_progress(opers)
@classmethod
def create(cls, name, datacenter, subnet=None, gateway=None,
background=False):
"""Create a new vlan."""
if not background and not cls.intty():
background = True
datacenter_id_ = int(Datacenter.usable_id(datacenter))
vlan_params = {
'name': name,
'datacenter_id': datacenter_id_,
}
if subnet:
vlan_params['subnet'] = subnet
if gateway:
vlan_params['gateway'] = gateway
result = cls.call('hosting.vlan.create', vlan_params)
if not background:
# interactive mode, run a progress bar
cls.echo('Creating your vlan.')
cls.display_progress(result)
cls.echo('Your vlan %s has been created.' % name)
return result
@classmethod
def update(cls, id, params):
"""Update an existing vlan."""
cls.echo('Updating your vlan.')
result = cls.call('hosting.vlan.update', cls.usable_id(id), params)
return result
@classmethod
def from_name(cls, name):
"""Retrieve vlan id associated to a name."""
result = cls.list()
vlans = {}
for vlan in result:
vlans[vlan['name']] = vlan['id']
return vlans.get(name)
@classmethod
def usable_id(cls, id):
""" Retrieve id from input which can be name or id."""
try:
# id is maybe a name
qry_id = cls.from_name(id)
if not qry_id:
qry_id = int(id)
except Exception:
qry_id = None
if not qry_id:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id
class Iface(GandiModule):
""" Module to handle CLI commands.
$ gandi iface list
$ gandi iface info
$ gandi iface create
$ gandi iface delete
$ gandi iface update
"""
@classmethod
def list(cls, options=None):
""" List all ifaces."""
options = options or {}
return cls.call('hosting.iface.list', options)
@classmethod
def _info(cls, iface_id):
""" Get information about an iface."""
return cls.call('hosting.iface.info', iface_id)
@classmethod
def info(cls, num):
""" Get information about an iface."""
return cls._info(cls.usable_id(num))
@classmethod
def usable_id(cls, id):
""" Retrieve id from input which can be num or id."""
try:
qry_id = int(id)
except Exception:
qry_id = None
if not qry_id:
msg = 'unknown identifier %s' % id
cls.error(msg)
return qry_id
@classmethod
def _attach(cls, iface_id, vm_id):
""" Attach an iface to a vm. """
oper = cls.call('hosting.vm.iface_attach', vm_id, iface_id)
return oper
@classmethod
def create(cls, ip_version, datacenter, bandwidth, vlan, vm, ip,
background):
""" Create a new iface """
if not background and not cls.intty():
background = True
datacenter_id_ = int(Datacenter.usable_id(datacenter))
iface_params = {
'ip_version': ip_version,
'datacenter_id': datacenter_id_,
'bandwidth': bandwidth,
}
if vlan:
iface_params['vlan'] = Vlan.usable_id(vlan)
if ip:
iface_params['ip'] = ip
result = cls.call('hosting.iface.create', iface_params)
if background and not vm:
return result
# interactive mode, run a progress bar
cls.echo('Creating your iface.')
cls.display_progress(result)
iface_info = cls._info(result['iface_id'])
cls.echo('Your iface has been created with the following IP '
'addresses:')
for _ip in iface_info['ips']:
cls.echo('ip%d:\t%s' % (_ip['version'], _ip['ip']))
if not vm:
return result
vm_id = Iaas.usable_id(vm)
result = cls._attach(result['iface_id'], vm_id)
if background:
return result
cls.echo('Attaching your iface.')
cls.display_progress(result)
return result
@classmethod
def _detach(cls, iface_id):
""" Detach an iface from a vm. """
iface = cls._info(iface_id)
opers = []
vm_id = iface.get('vm_id')
if vm_id:
cls.echo('The iface is still attached to the vm %s.' % vm_id)
cls.echo('Will detach it.')
opers.append(cls.call('hosting.vm.iface_detach', vm_id, iface_id))
return opers
@classmethod
def delete(cls, resources, background=False):
""" Delete this iface."""
if not isinstance(resources, (list, tuple)):
resources = [resources]
resources = [cls.usable_id(item) for item in resources]
opers = []
for iface_id in resources:
opers.extend(cls._detach(iface_id))
if opers:
cls.echo('Detaching your iface(s).')
cls.display_progress(opers)
opers = []
for iface_id in resources:
oper = cls.call('hosting.iface.delete', iface_id)
opers.append(oper)
if background:
return opers
cls.echo('Detaching/deleting your iface(s).')
cls.display_progress(opers)
return opers
@classmethod
def update(cls, id, bandwidth, vm, background):
""" Update this iface. """
if not background and not cls.intty():
background = True
iface_params = {}
iface_id = cls.usable_id(id)
if bandwidth:
iface_params['bandwidth'] = bandwidth
if iface_params:
result = cls.call('hosting.iface.update', iface_id, iface_params)
if background:
return result
# interactive mode, run a progress bar
cls.echo('Updating your iface %s.' % id)
cls.display_progress(result)
if not vm:
return
vm_id = Iaas.usable_id(vm)
opers = cls._detach(iface_id)
if opers:
cls.echo('Detaching iface.')
cls.display_progress(opers)
result = cls._attach(iface_id, vm_id)
if background:
return result
cls.echo('Attaching your iface.')
cls.display_progress(result)
gandi.cli-1.2/gandi/cli/tests/ 0000755 0001750 0001750 00000000000 13227415174 017063 5 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/gandi/cli/tests/__init__.py 0000644 0001750 0001750 00000000000 12453203306 021152 0 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/gandi/cli/tests/commands/ 0000755 0001750 0001750 00000000000 13227415174 020664 5 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/gandi/cli/tests/commands/test_oper.py 0000644 0001750 0001750 00000002134 13164644453 023246 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
from .base import CommandTestCase
from gandi.cli.commands import oper
class OperTestCase(CommandTestCase):
def test_list(self):
result = self.invoke_with_exceptions(oper.list, [])
self.assertEqual(result.output, """\
id : 99002
type : hosting_migration_vm
step : RUN
----------
id : 99001
type : hosting_migration_vm
step : RUN
----------
id : 100303
type : certificate_update
step : WAIT
----------
id : 100302
type : certificate_update
step : RUN
----------
id : 100300
type : certificate_update
step : RUN
----------
id : 100200
type : billing_prepaid_add_money
step : BILL
----------
id : 100100
type : domain_renew
step : BILL
""")
self.assertEqual(result.exit_code, 0)
def test_info(self):
result = self.invoke_with_exceptions(oper.info, ['100100'])
self.assertEqual(result.output, """\
id : 100100
type : domain_renew
step : BILL
last_error:
""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_snapshotprofile.py 0000644 0001750 0001750 00000003663 12623134755 025527 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
from .base import CommandTestCase
from gandi.cli.commands import snapshotprofile
class SnapshotprofileTestCase(CommandTestCase):
def test_list(self):
result = self.invoke_with_exceptions(snapshotprofile.list, [])
self.assertEqual(result.output, """\
id : 1
name : minimal
kept_total : 2
target : vm
----------
id : 2
name : full_week
kept_total : 7
target : vm
----------
id : 3
name : security
kept_total : 10
target : vm
----------
id : 7
name : paas_normal
kept_total : 3
target : paas
""")
self.assertEqual(result.exit_code, 0)
def test_list_filter_paas(self):
args = ['--only-paas']
result = self.invoke_with_exceptions(snapshotprofile.list, args)
self.assertEqual(result.output, """\
id : 7
name : paas_normal
kept_total : 3
target : paas
""")
self.assertEqual(result.exit_code, 0)
def test_list_filter_vm(self):
args = ['--only-vm']
result = self.invoke_with_exceptions(snapshotprofile.list, args)
self.assertEqual(result.output, """\
id : 1
name : minimal
kept_total : 2
target : vm
----------
id : 2
name : full_week
kept_total : 7
target : vm
----------
id : 3
name : security
kept_total : 10
target : vm
""")
self.assertEqual(result.exit_code, 0)
def test_info(self):
args = ['security']
result = self.invoke_with_exceptions(snapshotprofile.info, args)
self.assertEqual(result.output, """\
id : 3
name : security
kept_total : 10
target : vm
quota_factor : 2.0
----------
name : hourly6
kept_version : 3
----------
name : daily
kept_version : 6
----------
name : weekly4
kept_version : 1
""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_config.py 0000644 0001750 0001750 00000005664 12623134755 023557 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
""" Configuration namespace tests. """
import os
from .base import CommandTestCase
from gandi.cli.commands import config
class ConfigTestCase(CommandTestCase):
def test_get_empty(self):
result = self.invoke_with_exceptions(config.get, [])
self.assertEqual(result.exit_code, 2)
def test_get_unknown(self):
result = self.invoke_with_exceptions(config.get, ['unknown-key'])
self.assertEqual(result.output, """No value found.
""")
self.assertEqual(result.exit_code, 1)
def test_get(self):
result = self.invoke_with_exceptions(config.get, ['api'])
self.assertEqual(result.exit_code, 0)
def test_set_empty(self):
result = self.invoke_with_exceptions(config.set, [])
self.assertEqual(result.exit_code, 2)
result = self.invoke_with_exceptions(config.set, ['some-key'])
self.assertEqual(result.exit_code, 2)
def test_set_empty_value(self):
result = self.invoke_with_exceptions(config.set, ['some-key'])
self.assertEqual(result.exit_code, 2)
def test_set_get(self):
result = self.invoke_with_exceptions(config.set, ['dummy'])
self.assertEqual(result.exit_code, 2)
result = self.invoke_with_exceptions(config.set, ['dummy',
'v4lu3'])
self.assertEqual(result.exit_code, 0)
result = self.invoke_with_exceptions(config.get, ['dummy'])
self.assertEqual(result.output, """v4lu3
""")
self.assertEqual(result.exit_code, 0)
def test_delete_empty(self):
result = self.invoke_with_exceptions(config.set, [])
self.assertEqual(result.exit_code, 2)
def test_delete(self):
result = self.invoke_with_exceptions(config.set, ['dummy', 'value'])
self.assertEqual(result.exit_code, 0)
result = self.invoke_with_exceptions(config.get, ['dummy'])
self.assertEqual(result.output, """value
""")
self.assertEqual(result.exit_code, 0)
result = self.invoke_with_exceptions(config.delete, ['dummy'])
self.assertEqual(result.exit_code, 0)
result = self.invoke_with_exceptions(config.get, ['unknown-key'])
self.assertEqual(result.output, """No value found.
""")
self.assertEqual(result.exit_code, 1)
def test_edit(self):
os.environ['EDITOR'] = '/usr/bin/nano'
result = self.invoke_with_exceptions(config.get, ['editor'])
self.assertEqual(result.output, """/usr/bin/nano
""")
self.assertEqual(result.exit_code, 0)
del os.environ['EDITOR']
result = self.invoke_with_exceptions(config.set, ['editor',
'/usr/bin/vi'])
self.assertEqual(result.exit_code, 0)
result = self.invoke_with_exceptions(config.get, ['editor'])
self.assertEqual(result.output, """/usr/bin/vi
""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_status.py 0000644 0001750 0001750 00000017531 13160664756 023637 0 ustar sayoun sayoun 0000000 0000000 import json
from functools import partial
from ..compat import mock
from .base import CommandTestCase
from gandi.cli.commands import root
# disable SSL requests warning for tests
import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
RESPONSES = {
'https://status.gandi.net/api/status/schema': {
'status': 200,
'headers': 'application/json',
'body': """{"fields": {"status": {"value": [
{"SUNNY": "All services are up and running"},
{"CLOUDY": "A scheduled maintenance ongoing"},
{"FOGGY": "Incident which are not impacting our services."},
{"STORMY": "An incident ongoing"}]}}}"""
},
}
def _mock_requests(status, method, url, *args, **kwargs):
content = None
if status == 'SUNNY':
if url == 'https://status.gandi.net/api/services':
content = """[{"description": "IAAS",
"name": "IAAS",
"status": "SUNNY"},
{"description": "PAAS",
"name": "PAAS",
"status": "SUNNY"},
{"description": "Site",
"name": "Site",
"status": "SUNNY"},
{"description": "API",
"name": "API",
"status": "SUNNY"},
{"description": "SSL",
"name": "SSL",
"status": "SUNNY"},
{"description": "Domain",
"name": "Domain",
"status": "SUNNY"},
{"description": "Email",
"name": "Email",
"status": "SUNNY"}]"""
if url == 'https://status.gandi.net/api/events?category=Incident¤t=true': # noqa
content = """[]"""
if status == 'STORMY':
if url == 'https://status.gandi.net/api/services':
content = """[{"description": "IAAS",
"name": "IAAS",
"status": "SUNNY"},
{"description": "PAAS",
"name": "PAAS",
"status": "STORMY"},
{"description": "Site",
"name": "Site",
"status": "SUNNY"},
{"description": "API",
"name": "API",
"status": "SUNNY"},
{"description": "SSL",
"name": "SSL",
"status": "SUNNY"},
{"description": "Domain",
"name": "Domain",
"status": "SUNNY"},
{"description": "Email",
"name": "Email",
"status": "SUNNY"}]"""
if url == 'https://status.gandi.net/api/events?category=Incident&services=PAAS¤t=true': # noqa
content = """
[{"category": "Incident",
"date_end": "2014-10-08T06:20:00+00:00",
"date_start": "2014-10-07T18:00:00+00:00",
"duration": 740,
"estimate_date_end": "2014-10-08T06:20:00+00:00",
"id": "7",
"services": [
"IAAS",
"PAAS"
],
"title": "Incident on a storage unit on Paris datacenter"}]
"""
if status == 'FOGGY':
if url == 'https://status.gandi.net/api/services':
content = """[{"description": "IAAS",
"name": "IAAS",
"status": "SUNNY"},
{"description": "PAAS",
"name": "PAAS",
"status": "SUNNY"},
{"description": "Site",
"name": "Site",
"status": "SUNNY"},
{"description": "API",
"name": "API",
"status": "SUNNY"},
{"description": "SSL",
"name": "SSL",
"status": "SUNNY"},
{"description": "Domain",
"name": "Domain",
"status": "SUNNY"},
{"description": "Email",
"name": "Email",
"status": "SUNNY"}]"""
if url == 'https://status.gandi.net/api/events?category=Incident¤t=true': # noqa
content = """
[{"category": "Incident",
"date_end": "2015-04-15T22:06:43+00:00",
"date_start": "2015-04-15T21:30:00+00:00",
"duration": 36,
"estimate_date_end": "2015-04-15T22:30:00+00:00",
"id": "15",
"services": [],
"title": "Reachability issue on our website"
}]
"""
if url == 'https://status.gandi.net/api/status':
content = """{"status": "%s"}""" % status
elif not content:
content = RESPONSES[url]['body']
content = json.loads(content)
mock_resp = mock.Mock()
mock_resp.status_code = 200
mock_resp.content = content
mock_resp.json = mock.Mock(return_value=content)
return mock_resp
class StatusTestCase(CommandTestCase):
@mock.patch('gandi.cli.core.client.requests.request')
def test_status(self, mock_request):
mock_request.side_effect = partial(_mock_requests, 'SUNNY')
result = self.invoke_with_exceptions(root.status, [])
wanted = ("""\
IAAS : All services are up and running
PAAS : All services are up and running
Site : All services are up and running
API : All services are up and running
SSL : All services are up and running
Domain : All services are up and running
Email : All services are up and running
""")
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_status_service(self, mock_request):
mock_request.side_effect = partial(_mock_requests, 'SUNNY')
result = self.invoke_with_exceptions(root.status, ['ssl'])
wanted = ("""\
SSL : All services are up and running
""")
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_status_service_incident(self, mock_request):
mock_request.side_effect = partial(_mock_requests, 'STORMY')
result = self.invoke_with_exceptions(root.status, ['paas'])
url = 'https://status.gandi.net/timeline/events/7'
wanted = ("""\
PAAS : Incident on a storage unit on Paris datacenter - %s
""") % url
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_status_no_service_incident(self, mock_request):
mock_request.side_effect = partial(_mock_requests, 'FOGGY')
result = self.invoke_with_exceptions(root.status, [])
wanted = ("""\
Reachability issue on our website - https://status.gandi.net/timeline/events/15
IAAS : All services are up and running
PAAS : All services are up and running
Site : All services are up and running
API : All services are up and running
SSL : All services are up and running
Domain : All services are up and running
Email : All services are up and running
""")
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_domain.py 0000644 0001750 0001750 00000021612 12714035303 023536 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
import re
from datetime import datetime
from .base import CommandTestCase
from ..compat import mock
from gandi.cli.commands import domain
from gandi.cli.core.utils import DomainNotAvailable
class DomainTestCase(CommandTestCase):
def test_list(self):
result = self.invoke_with_exceptions(domain.list, [])
self.assertEqual(result.output, """iheartcli.com
cli.sexy
""")
self.assertEqual(result.exit_code, 0)
def test_info(self):
with mock.patch('gandi.cli.core.utils.datetime') as mock_datetime:
mock_datetime.now.return_value = datetime(2015, 7, 1)
mock_datetime.side_effect = lambda *args, **kw: datetime(*args,
**kw)
result = self.invoke_with_exceptions(domain.info,
['iheartcli.com'])
self.assertEqual(result.output, """owner : AA1-GANDI
admin : AA2-GANDI
bill : AA3-GANDI
tech : AA5-GANDI
reseller : AA4-GANDI
fqdn : iheartcli.com
nameservers : a.dns.gandi.net, b.dns.gandi.net, c.dns.gandi.net
services : gandidns
zone_id : 424242
tags : bla
created : 2010-09-22 15:06:18
expires : 2015-09-22 00:00:00 (in 83 days)
updated : 2014-09-21 03:10:07
""")
self.assertEqual(result.exit_code, 0)
def test_create_no_option_no_argument(self):
args = ['--duration', 1,
'--owner', 'OWNER1-GANDI',
'--admin', 'ADMIN1-GANDI',
'--tech', 'TECH1-GANDI',
'--bill', 'BILL1-GANDI',
]
result = self.invoke_with_exceptions(domain.create, args,
input='idontlike.website\n')
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
Name of the domain: idontlike.website
Creating your domain.
\rProgress: [###] 100.00% 00:00:00 \n\
Your domain idontlike.website has been created.""")
self.assertEqual(result.exit_code, 0)
# self.assertEqual(result.output.strip(), """\
# Name of the domain.:\
# """)
def test_create_option(self):
result = self.invoke_with_exceptions(domain.create,
['--domain', 'idontlike.website',
'--duration', 1,
'--owner', 'OWNER1-GANDI',
'--admin', 'ADMIN1-GANDI',
'--tech', 'TECH1-GANDI',
'--bill', 'BILL1-GANDI',
'--nameserver', 'a.domain.tld',
'--nameserver', 'b.domain.tld',
])
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
/!\ --domain option is deprecated and will be removed upon next release.
You should use 'gandi domain create idontlike.website' instead.
Creating your domain.
\rProgress: [###] 100.00% 00:00:00 \n\
Your domain idontlike.website has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_argument(self):
result = self.invoke_with_exceptions(domain.create,
['idontlike.website',
'--duration', 1,
'--owner', 'OWNER1-GANDI',
'--admin', 'ADMIN1-GANDI',
'--tech', 'TECH1-GANDI',
'--bill', 'BILL1-GANDI',
])
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
Creating your domain.
\rProgress: [###] 100.00% 00:00:00 \n\
Your domain idontlike.website has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_argument_and_option_different(self):
result = self.invoke_with_exceptions(domain.create,
['idontlike.website',
'--domain', 'idontlike.bike',
'--duration', 1,
'--owner', 'OWNER1-GANDI',
'--admin', 'ADMIN1-GANDI',
'--tech', 'TECH1-GANDI',
'--bill', 'BILL1-GANDI',
])
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
/!\ --domain option is deprecated and will be removed upon next release.
You should use 'gandi domain create idontlike.bike' instead.
/!\ You specified both an option and an argument which are different, \
please choose only one between: idontlike.bike and idontlike.website.""")
self.assertEqual(result.exit_code, 0)
def test_create_argument_and_option_equal(self):
result = self.invoke_with_exceptions(domain.create,
['idontlike.website',
'--domain', 'idontlike.website',
'--duration', 1,
'--owner', 'OWNER1-GANDI',
'--admin', 'ADMIN1-GANDI',
'--tech', 'TECH1-GANDI',
'--bill', 'BILL1-GANDI',
])
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
/!\ --domain option is deprecated and will be removed upon next release.
You should use 'gandi domain create idontlike.website' instead.
Creating your domain.
\rProgress: [###] 100.00% 00:00:00 \n\
Your domain idontlike.website has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_background_argument(self):
args = ['roflozor.com', '--background']
result = self.invoke_with_exceptions(domain.create, args)
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
Duration [1]: \n\
{'id': 400, 'step': 'WAIT'}""")
self.assertEqual(result.exit_code, 0)
def test_create_background_option(self):
args = ['--domain', 'roflozor.com', '--background']
result = self.invoke_with_exceptions(domain.create, args)
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
Duration [1]: \n\
/!\ --domain option is deprecated and will be removed upon next release.
You should use 'gandi domain create roflozor.com' instead.
{'id': 400, 'step': 'WAIT'}""")
self.assertEqual(result.exit_code, 0)
def test_renew(self):
result = self.invoke_with_exceptions(domain.renew,
['iheartcli.com',
'--duration', 1,
])
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
Renewing your domain.
\rProgress: [###] 100.00% 00:00:00 \n\
Your domain iheartcli.com has been renewed.""")
self.assertEqual(result.exit_code, 0)
def test_renew_background_ok(self):
args = ['iheartcli.com', '--background']
result = self.invoke_with_exceptions(domain.renew, args)
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
Duration [1]: \n\
{'id': 400, 'step': 'WAIT'}""")
self.assertEqual(result.exit_code, 0)
def test_available_with_exception(self):
self.assertRaises(DomainNotAvailable,
self.invoke_with_exceptions, domain.create,
['--domain', 'unavailable1.website',
'--duration', 1,
'--owner', 'OWNER1-GANDI',
'--admin', 'ADMIN1-GANDI',
'--tech', 'TECH1-GANDI',
'--bill', 'BILL1-GANDI',
])
def test_available_with_exception_argument(self):
self.assertRaises(DomainNotAvailable,
self.invoke_with_exceptions, domain.create,
['unavailable1.website',
'--duration', 1,
'--owner', 'OWNER1-GANDI',
'--admin', 'ADMIN1-GANDI',
'--tech', 'TECH1-GANDI',
'--bill', 'BILL1-GANDI',
])
gandi.cli-1.2/gandi/cli/tests/commands/test_vlan.py 0000644 0001750 0001750 00000023562 13227142754 023246 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
import re
from .base import CommandTestCase
from gandi.cli.commands import vlan
from gandi.cli.core.base import GandiContextHelper
class VlanTestCase(CommandTestCase):
def test_list(self):
result = self.invoke_with_exceptions(vlan.list, [])
self.assertEqual(result.output, """\
name : vlantest
state : created
datacenter: FR-SD2
----------
name : pouet
state : created
datacenter: FR-SD2
----------
name : intranet
state : created
datacenter: FR-SD3
""")
self.assertEqual(result.exit_code, 0)
def test_list_filters(self):
args = ['--id', '--subnet', '--gateway']
result = self.invoke_with_exceptions(vlan.list, args)
self.assertEqual(result.output, """\
name : vlantest
state : created
id : 123
subnet : 10.7.13.0/24
gateway : 10.7.13.254
datacenter: FR-SD2
----------
name : pouet
state : created
id : 717
subnet : 192.168.232.0/24
gateway : 192.168.232.254
datacenter: FR-SD2
----------
name : intranet
state : created
id : 999
subnet : 10.7.242.0/24
gateway : 10.7.242.254
datacenter: FR-SD3
""")
self.assertEqual(result.exit_code, 0)
def test_list_filter_datacenter(self):
args = ['--id', '--subnet', '--gateway', '--datacenter', 'FR-SD3']
result = self.invoke_with_exceptions(vlan.list, args)
self.assertEqual(result.output, """\
name : intranet
state : created
id : 999
subnet : 10.7.242.0/24
gateway : 10.7.242.254
datacenter: FR-SD3
""")
self.assertEqual(result.exit_code, 0)
def test_info(self):
args = ['vlantest']
result = self.invoke_with_exceptions(vlan.info, args)
self.assertEqual(result.output, """\
name : vlantest
state : created
subnet : 10.7.13.0/24
gateway : 10.7.13.254
datacenter : FR-SD2
""")
self.assertEqual(result.exit_code, 0)
def test_info_ip(self):
args = ['pouet', '--ip']
result = self.invoke_with_exceptions(vlan.info, args)
self.assertEqual(result.output, """\
name : pouet
state : created
subnet : 192.168.232.0/24
gateway : 192.168.232.254 don't exists
datacenter : FR-SD2
----------
bandwidth : 102400.0
vm : server02
ip : 192.168.232.252
----------
bandwidth : 204800.0
vm : server02
ip : 192.168.232.253
""")
self.assertEqual(result.exit_code, 0)
def test_delete(self):
args = ['intranet']
result = self.invoke_with_exceptions(vlan.delete, args, input='y\n',
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure to delete vlan 'intranet'? [y/N]: y
Deleting your vlan.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_force(self):
args = ['intranet', '--force']
result = self.invoke_with_exceptions(vlan.delete, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Deleting your vlan.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_background(self):
args = ['intranet', '--force', '--bg']
result = self.invoke_with_exceptions(vlan.delete, args,
obj=GandiContextHelper())
self.assertEqual(result.output, """\
id : 200
step : WAIT
""")
self.assertEqual(result.exit_code, 0)
def test_delete_unknown(self):
args = ['vlanunknown']
result = self.invoke_with_exceptions(vlan.delete, args)
self.assertEqual(result.output, """\
Sorry vlan vlanunknown does not exist
Please use one of the following: ['vlantest', 'pouet', 'intranet', \
'123', '717', '999']
""")
self.assertEqual(result.exit_code, 0)
def test_delete_refuse(self):
args = ['intranet']
result = self.invoke_with_exceptions(vlan.delete, args, input='\n',
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure to delete vlan 'intranet'? [y/N]:""")
self.assertEqual(result.exit_code, 0)
def test_create(self):
args = ['--name', 'testvlan', '--datacenter', 'FR-SD3',
'--subnet', '10.7.70.0/24', '--gateway', '10.7.70.254']
result = self.invoke_with_exceptions(vlan.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Creating your vlan.
\rProgress: [###] 100.00% 00:00:00 \
\nYour vlan testvlan has been created.""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.vlan.create'][0][0]
self.assertEqual(params['datacenter_id'], 4)
self.assertEqual(params['subnet'], '10.7.70.0/24')
self.assertEqual(params['name'], 'testvlan')
self.assertEqual(params['gateway'], '10.7.70.254')
def test_create_datacenter_limited(self):
args = ['--name', 'testvlan', '--datacenter', 'FR-SD2',
'--subnet', '10.7.70.0/24', '--gateway', '10.7.70.254']
result = self.invoke_with_exceptions(vlan.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
/!\ Datacenter FR-SD2 will be closed on 25/12/2017, please consider using \
another datacenter.
Creating your vlan.
\rProgress: [###] 100.00% 00:00:00 \
\nYour vlan testvlan has been created.""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.vlan.create'][0][0]
self.assertEqual(params['datacenter_id'], 1)
self.assertEqual(params['subnet'], '10.7.70.0/24')
self.assertEqual(params['name'], 'testvlan')
self.assertEqual(params['gateway'], '10.7.70.254')
def test_create_datacenter_closed(self):
args = ['--name', 'testvlan', '--datacenter', 'US-BA1',
'--subnet', '10.7.70.0/24', '--gateway', '10.7.70.254']
result = self.invoke_with_exceptions(vlan.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Error: /!\ Datacenter US-BA1 is closed, please choose another datacenter.""")
self.assertEqual(result.exit_code, 1)
def test_create_background(self):
args = ['--name', 'testvlanbg', '--bg']
result = self.invoke_with_exceptions(vlan.create, args,
obj=GandiContextHelper())
self.assertEqual(result.output.strip(), """\
{'id': 200, 'step': 'WAIT'}""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.vlan.create'][0][0]
self.assertEqual(params['datacenter_id'], 5)
self.assertEqual(params['name'], 'testvlanbg')
def test_update(self):
args = ['pouet', '--name', 'chocolat',
'--gateway', '10.7.70.254',
'--bandwidth', '204800']
result = self.invoke_with_exceptions(vlan.update, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your vlan.""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.vlan.update'][0][1]
self.assertEqual(params['name'], 'chocolat')
self.assertEqual(params['gateway'], '10.7.70.254')
def test_update_gateway_vm_unknown(self):
args = ['pouet', '--name', 'chocolat',
'--gateway', 'server01',
'--bandwidth', '204800']
result = self.invoke_with_exceptions(vlan.update, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Can't find 'server01' in 'pouet' vlan""")
self.assertEqual(result.exit_code, 0)
def test_update_gateway_vm(self):
args = ['pouet', '--name', 'chocolat',
'--gateway', 'server01',
'--create',
'--bandwidth', '204800']
result = self.invoke_with_exceptions(vlan.update, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Will create a new ip in this vlan for vm server01
Creating your iface.
\rProgress: [###] 100.00% 00:00:00 \
\nYour iface has been created with the following IP addresses:
ip4:\t95.142.160.181
ip6:\t2001:4b98:dc0:47:216:3eff:feb2:3862
Attaching your iface.
\rProgress: [###] 100.00% 00:00:00 \
\nUpdating your vlan.""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.vlan.update'][0][1]
self.assertEqual(params['name'], 'chocolat')
self.assertEqual(params['gateway'], '95.142.160.181')
def test_update_gateway_multiple_ips(self):
args = ['pouet', '--name', 'chocolat',
'--gateway', 'server02']
result = self.invoke_with_exceptions(vlan.update, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
This vm has two ips in the vlan, don't know which one to choose \
(213.167.231.3, 192.168.232.252)""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_record.py 0000644 0001750 0001750 00000024445 13227414205 023556 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from ..compat import mock
from .base import CommandTestCase
from gandi.cli.commands import record
class RecordTestCase(CommandTestCase):
def test_list(self):
result = self.invoke_with_exceptions(record.list, ['iheartcli.com'])
self.assertEqual(result.output, """\
name : *
type : A
value : 73.246.104.110
ttl : 10800
----------
name : @
type : A
value : 73.246.104.110
ttl : 10800
----------
name : much
type : A
value : 192.243.24.132
ttl : 10800
----------
name : blog
type : CNAME
value : blogs.vip.gandi.net.
ttl : 10800
----------
name : cloud
type : CNAME
value : gpaas6.dc0.gandi.net.
ttl : 10800
----------
name : imap
type : CNAME
value : access.mail.gandi.net.
ttl : 10800
----------
name : pop
type : CNAME
value : access.mail.gandi.net.
ttl : 10800
----------
name : smtp
type : CNAME
value : relay.mail.gandi.net.
ttl : 10800
----------
name : webmail
type : CNAME
value : agent.mail.gandi.net.
ttl : 10800
----------
name : @
type : MX
value : 50 fb.mail.gandi.net.
ttl : 10800
----------
name : @
type : MX
value : 10 spool.mail.gandi.net.
ttl : 10800
""")
self.assertEqual(result.exit_code, 0)
def test_list_format_text(self):
args = ['iheartcli.com', '--format', 'text']
result = self.invoke_with_exceptions(record.list, args)
self.assertEqual(result.output, """\
* 10800 IN A 73.246.104.110
@ 10800 IN A 73.246.104.110
much 10800 IN A 192.243.24.132
blog 10800 IN CNAME blogs.vip.gandi.net.
cloud 10800 IN CNAME gpaas6.dc0.gandi.net.
imap 10800 IN CNAME access.mail.gandi.net.
pop 10800 IN CNAME access.mail.gandi.net.
smtp 10800 IN CNAME relay.mail.gandi.net.
webmail 10800 IN CNAME agent.mail.gandi.net.
@ 10800 IN MX 50 fb.mail.gandi.net.
@ 10800 IN MX 10 spool.mail.gandi.net.
""")
self.assertEqual(result.exit_code, 0)
def test_list_format_json(self):
args = ['iheartcli.com', '--format', 'json']
result = self.invoke_with_exceptions(record.list, args)
self.assertEqual(result.output, """\
[
{
"id": 337085079,
"name": "*",
"ttl": 10800,
"type": "A",
"value": "73.246.104.110"
},
{
"id": 337085078,
"name": "@",
"ttl": 10800,
"type": "A",
"value": "73.246.104.110"
},
{
"id": 337085081,
"name": "much",
"ttl": 10800,
"type": "A",
"value": "192.243.24.132"
},
{
"id": 337085072,
"name": "blog",
"ttl": 10800,
"type": "CNAME",
"value": "blogs.vip.gandi.net."
},
{
"id": 337085082,
"name": "cloud",
"ttl": 10800,
"type": "CNAME",
"value": "gpaas6.dc0.gandi.net."
},
{
"id": 337085075,
"name": "imap",
"ttl": 10800,
"type": "CNAME",
"value": "access.mail.gandi.net."
},
{
"id": 337085071,
"name": "pop",
"ttl": 10800,
"type": "CNAME",
"value": "access.mail.gandi.net."
},
{
"id": 337085074,
"name": "smtp",
"ttl": 10800,
"type": "CNAME",
"value": "relay.mail.gandi.net."
},
{
"id": 337085073,
"name": "webmail",
"ttl": 10800,
"type": "CNAME",
"value": "agent.mail.gandi.net."
},
{
"id": 337085077,
"name": "@",
"ttl": 10800,
"type": "MX",
"value": "50 fb.mail.gandi.net."
},
{
"id": 337085076,
"name": "@",
"ttl": 10800,
"type": "MX",
"value": "10 spool.mail.gandi.net."
}
]
""")
self.assertEqual(result.exit_code, 0)
def test_list_no_zone(self):
result = self.invoke_with_exceptions(record.list, ['cli.sexy'])
self.assertEqual(result.output, """\
No zone records found, domain cli.sexy doesn't seems to be managed at Gandi.
""")
self.assertEqual(result.exit_code, 0)
def test_list_output(self):
args = ['iheartcli.com', '--output']
with mock.patch('gandi.cli.commands.record.open',
create=True) as mock_open:
mock_open.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(record.list, args)
self.assertEqual(result.output, """\
Your zone file have been writen in iheartcli.com_424242
""")
self.assertEqual(result.exit_code, 0)
def test_list_output_reset(self):
args = ['iheartcli.com', '--output']
with mock.patch('gandi.cli.commands.record.os.path.isfile',
create=True) as mock_isfile:
mock_isfile.return_value = mock.MagicMock()
with mock.patch('gandi.cli.commands.record.open',
create=True) as mock_open:
mock_open.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(record.list, args)
self.assertEqual(result.output, """\
Your zone file have been writen in iheartcli.com_424242
""")
self.assertEqual(result.exit_code, 0)
def test_create_no_zone(self):
args = ['cli.sexy', '--name', '@', '--type', 'A',
'--value', '127.0.0.1']
result = self.invoke_with_exceptions(record.create, args)
self.assertEqual(result.output, """\
No zone records found, domain cli.sexy doesn't seems to be managed at Gandi.
""")
self.assertEqual(result.exit_code, 0)
def test_create(self):
args = ['iheartcli.com', '--name', '@', '--type', 'A', '--ttl', 3600,
'--value', '127.0.0.1']
result = self.invoke_with_exceptions(record.create, args)
self.assertEqual(result.output, """\
Creating new zone version
Updating zone version
Activation of new zone version
""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['domain.zone.record.add'][0][2]
self.assertEqual(params['name'], '@')
self.assertEqual(params['type'], 'A')
self.assertEqual(params['value'], '127.0.0.1')
self.assertEqual(params['ttl'], 3600)
def test_delete_no_zone(self):
args = ['cli.sexy']
result = self.invoke_with_exceptions(record.delete, args)
self.assertEqual(result.output, """\
No zone records found, domain cli.sexy doesn't seems to be managed at Gandi.
""")
self.assertEqual(result.exit_code, 0)
def test_delete_all_ko(self):
args = ['iheartcli.com']
result = self.invoke_with_exceptions(record.delete, args, input='N\n')
self.assertEqual(result.output, """\
This command without parameters --type, --name or --value will remove all \
records in this zone file. Are you sur to perform this action ? [y/N]: N
""")
self.assertEqual(result.exit_code, 0)
def test_delete_all(self):
args = ['iheartcli.com']
result = self.invoke_with_exceptions(record.delete, args, input='y\n')
self.assertEqual(result.output, """\
This command without parameters --type, --name or --value will remove all \
records in this zone file. Are you sur to perform this action ? [y/N]: y
Creating new zone record
Deleting zone record
Activation of new zone version
""")
self.assertEqual(result.exit_code, 0)
def test_delete(self):
args = ['iheartcli.com', '--name', '@', '--type', 'A',
'--value', '127.0.0.1']
result = self.invoke_with_exceptions(record.delete, args)
self.assertEqual(result.output, """\
Creating new zone record
Deleting zone record
Activation of new zone version
""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['domain.zone.record.delete'][0][2]
self.assertEqual(params['name'], '@')
self.assertEqual(params['type'], 'A')
self.assertEqual(params['value'], '127.0.0.1')
def test_update_no_zone(self):
args = ['cli.sexy']
result = self.invoke_with_exceptions(record.update, args)
self.assertEqual(result.output, """\
No zone records found, domain cli.sexy doesn't seems to be managed at Gandi.
""")
self.assertEqual(result.exit_code, 0)
def test_update_no_param(self):
args = ['iheartcli.com']
result = self.invoke_with_exceptions(record.update, args)
self.assertEqual(result.output, """\
You must indicate a zone file or a record. \
Use `gandi record update --help` for more information
""")
self.assertEqual(result.exit_code, 0)
def test_update(self):
args = ['iheartcli.com',
'--record', '* 10800 A 73.246.104.110',
'--new-record', '@ 3600 A 127.0.0.1']
result = self.invoke_with_exceptions(record.update, args)
self.assertEqual(result.output, """\
Creating new zone file
Updating zone records
Activation of new zone version
""")
self.assertEqual(result.exit_code, 0)
def test_update_name(self):
args = ['iheartcli.com',
'--record', '*',
'--new-record', '@ 3600 A 127.0.0.1']
result = self.invoke_with_exceptions(record.update, args)
self.assertEqual(result.output, """\
Creating new zone file
Updating zone records
Activation of new zone version
""")
self.assertEqual(result.exit_code, 0)
def test_update_file(self):
args = ['iheartcli.com', '--file', 'sandbox/example.txt']
content = """\
* 10800 IN A 73.246.104.110
@ 10800 IN A 73.246.104.110
much 10800 IN A 192.243.24.132
blog 10800 IN CNAME blogs.vip.gandi.net.
cloud 10800 IN CNAME gpaas6.dc0.gandi.net.
imap 10800 IN CNAME access.mail.gandi.net.
pop 10800 IN CNAME access.mail.gandi.net.
smtp 10800 IN CNAME relay.mail.gandi.net.
webmail 10800 IN CNAME agent.mail.gandi.net.
@ 10800 IN MX 50 fb.mail.gandi.net.
@ 10800 IN MX 10 spool.mail.gandi.net.
"""
result = self.isolated_invoke_with_exceptions(record.update, args,
temp_content=content)
self.assertEqual(result.output, """\
Creating new zone file
Updating zone records
Activation of new zone version
""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_ip.py 0000644 0001750 0001750 00000052637 13120224646 022714 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
import re
from .base import CommandTestCase
from gandi.cli.commands import ip
from gandi.cli.core.base import GandiContextHelper
class IpTestCase(CommandTestCase):
def test_list(self):
result = self.invoke_with_exceptions(ip.list, [])
self.assertEqual(result.output, """\
ip : 95.142.160.181
state : created
type : public
datacenter : FR-SD2
----------
ip : 2001:4b98:dc2:43:216:3eff:fece:e25f
state : created
type : public
datacenter : LU-BI1
----------
ip : 2001:4b98:dc0:47:216:3eff:feb2:3862
state : created
type : public
datacenter : FR-SD2
----------
ip : 192.168.232.253
state : created
type : private
vlan : pouet
datacenter : FR-SD2
----------
ip : 192.168.232.252
state : created
type : private
vlan : pouet
datacenter : FR-SD2
""")
self.assertEqual(result.exit_code, 0)
def test_list_details(self):
args = ['--id', '--version', '--vm', '--reverse']
result = self.invoke_with_exceptions(ip.list, args)
self.assertEqual(result.output, """\
ip : 95.142.160.181
state : created
id : 203968
version : 4
reverse : xvm-160-181.dc0.ghst.net
type : public
vm : server01
datacenter : FR-SD2
----------
ip : 2001:4b98:dc2:43:216:3eff:fece:e25f
state : created
id : 204557
version : 6
reverse : xvm6-dc2-fece-e25f.ghst.net
type : public
datacenter : LU-BI1
----------
ip : 2001:4b98:dc0:47:216:3eff:feb2:3862
state : created
id : 204558
version : 6
reverse : xvm6-dc0-feb2-3862.ghst.net
type : public
vm : server01
datacenter : FR-SD2
----------
ip : 192.168.232.253
state : created
id : 2361
version : 4
reverse :
type : private
vlan : pouet
vm : server02
datacenter : FR-SD2
----------
ip : 192.168.232.252
state : created
id : 2361
version : 4
reverse :
type : private
vlan : pouet
vm : server02
datacenter : FR-SD2
""")
self.assertEqual(result.exit_code, 0)
def test_list_attached(self):
result = self.invoke_with_exceptions(ip.list, ['--attached'])
self.assertEqual(result.output, """\
ip : 95.142.160.181
state : created
type : public
datacenter : FR-SD2
----------
ip : 2001:4b98:dc0:47:216:3eff:feb2:3862
state : created
type : public
datacenter : FR-SD2
----------
ip : 192.168.232.253
state : created
type : private
vlan : pouet
datacenter : FR-SD2
----------
ip : 192.168.232.252
state : created
type : private
vlan : pouet
datacenter : FR-SD2
""")
self.assertEqual(result.exit_code, 0)
def test_list_detached(self):
result = self.invoke_with_exceptions(ip.list, ['--detached'])
self.assertEqual(result.output, """\
ip : 2001:4b98:dc2:43:216:3eff:fece:e25f
state : created
type : public
datacenter : LU-BI1
""")
self.assertEqual(result.exit_code, 0)
def test_list_filter_type(self):
result = self.invoke_with_exceptions(ip.list, ['--type', 'private'])
self.assertEqual(result.output, """\
ip : 2001:4b98:dc2:43:216:3eff:fece:e25f
state : created
type : public
datacenter : LU-BI1
----------
ip : 192.168.232.253
state : created
type : private
vlan : pouet
datacenter : FR-SD2
----------
ip : 192.168.232.252
state : created
type : private
vlan : pouet
datacenter : FR-SD2
""")
self.assertEqual(result.exit_code, 0)
def test_list_filter_datacenter(self):
result = self.invoke_with_exceptions(ip.list, ['--datacenter', 'FR'])
self.assertEqual(result.output, """\
ip : 95.142.160.181
state : created
type : public
datacenter : FR-SD2
----------
ip : 2001:4b98:dc0:47:216:3eff:feb2:3862
state : created
type : public
datacenter : FR-SD2
----------
ip : 192.168.232.253
state : created
type : private
vlan : pouet
datacenter : FR-SD2
----------
ip : 192.168.232.252
state : created
type : private
vlan : pouet
datacenter : FR-SD2
""")
self.assertEqual(result.exit_code, 0)
def test_list_filter_vlan(self):
result = self.invoke_with_exceptions(ip.list, ['--vlan', 'pouet'])
self.assertEqual(result.output, """\
ip : 192.168.232.253
state : created
type : private
vlan : pouet
datacenter : FR-SD2
----------
ip : 192.168.232.252
state : created
type : private
vlan : pouet
datacenter : FR-SD2
""")
self.assertEqual(result.exit_code, 0)
def test_list_attached_detached(self):
args = ['--detached', '--attached']
result = self.invoke_with_exceptions(ip.list, args)
self.assertEqual(result.output, """\
You can't set --attached and --detached at the same time.
""")
self.assertEqual(result.exit_code, 0)
def test_info(self):
args = ['95.142.160.181']
result = self.invoke_with_exceptions(ip.info, args)
self.assertEqual(result.output, """\
ip : 95.142.160.181
state : created
reverse : xvm-160-181.dc0.ghst.net
type : public
vm : server01
datacenter : FR-SD2
""")
self.assertEqual(result.exit_code, 0)
def test_update_ko(self):
args = ['95.142.160.181']
result = self.invoke_with_exceptions(ip.update, args)
self.assertEqual(result.output, '')
self.assertEqual(result.exit_code, 0)
def test_update_reverse(self):
args = ['95.142.160.181', '--reverse', 'plop.bloup.com']
result = self.invoke_with_exceptions(ip.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your IP
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_attach_ko(self):
args = ['395.142.160.181', 'vm1426759833']
result = self.invoke_with_exceptions(ip.attach, args)
self.assertTrue("Can't find this ip 395.142.160.181" in result.output)
self.assertEqual(result.exit_code, 2)
def test_attach_already(self):
args = ['95.142.160.181', 'server01']
result = self.invoke_with_exceptions(ip.attach, args, input='y\n')
self.assertEqual(result.output, """\
This ip is already attached to this vm.
""")
self.assertEqual(result.exit_code, 0)
def test_attach(self):
args = ['95.142.160.181', 'vm1426759833']
result = self.invoke_with_exceptions(ip.attach, args, input='y\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure you want to detach 95.142.160.181 from vm 152967 [y/N]: y
The iface is still attached to the vm 152967.
Will detach it.
\rProgress: [###] 100.00% 00:00:00 \
\n\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_attach_force(self):
args = ['95.142.160.181', 'vm1426759833', '--force']
result = self.invoke_with_exceptions(ip.attach, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
The iface is still attached to the vm 152967.
Will detach it.
\rProgress: [###] 100.00% 00:00:00 \
\n\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_attach_refuse(self):
args = ['95.142.160.181', 'vm1426759833']
result = self.invoke_with_exceptions(ip.attach, args, input='N\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure you want to detach 95.142.160.181 from vm 152967 [y/N]: N""")
self.assertEqual(result.exit_code, 0)
def test_attach_background(self):
args = ['95.142.160.181', 'vm1426759833', '--force', '--bg']
result = self.invoke_with_exceptions(ip.attach, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
The iface is still attached to the vm 152967.
Will detach it.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_create_default(self):
args = []
result = self.invoke_with_exceptions(ip.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Creating your iface.
\rProgress: [###] 100.00% 00:00:00 \
\nYour iface has been created with the following IP addresses:
ip4:\t95.142.160.181
ip6:\t2001:4b98:dc0:47:216:3eff:feb2:3862""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.iface.create'][0][0]
self.assertEqual(params['datacenter_id'], 3)
self.assertEqual(params['bandwidth'], 102400)
self.assertEqual(params['ip_version'], 4)
def test_create_datacenter_limited(self):
args = ['--datacenter', 'FR-SD2', '--bandwidth', '51200',
'--ip-version', '6']
result = self.invoke_with_exceptions(ip.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
/!\ Datacenter FR-SD2 will be closed on 25/12/2017, please consider using \
another datacenter.
Creating your iface.
\rProgress: [###] 100.00% 00:00:00 \
\nYour iface has been created with the following IP addresses:
ip4:\t95.142.160.181
ip6:\t2001:4b98:dc0:47:216:3eff:feb2:3862""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.iface.create'][0][0]
self.assertEqual(params['datacenter_id'], 1)
self.assertEqual(params['bandwidth'], 51200)
self.assertEqual(params['ip_version'], 6)
def test_create_datacenter_closed(self):
args = ['--datacenter', 'US-BA1', '--bandwidth', '51200',
'--ip-version', '6']
result = self.invoke_with_exceptions(ip.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Error: /!\ Datacenter US-BA1 is closed, please choose another datacenter.""")
self.assertEqual(result.exit_code, 1)
def test_create_params(self):
args = ['--datacenter', 'FR', '--bandwidth', '51200',
'--ip-version', '6']
result = self.invoke_with_exceptions(ip.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
/!\ Datacenter FR will be closed on 25/12/2017, please consider using \
another datacenter.
Creating your iface.
\rProgress: [###] 100.00% 00:00:00 \
\nYour iface has been created with the following IP addresses:
ip4:\t95.142.160.181
ip6:\t2001:4b98:dc0:47:216:3eff:feb2:3862""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.iface.create'][0][0]
self.assertEqual(params['datacenter_id'], 1)
self.assertEqual(params['bandwidth'], 51200)
self.assertEqual(params['ip_version'], 6)
def test_create_params_vlan_ko(self):
args = ['--datacenter', 'FR', '--bandwidth', '51200',
'--ip-version', '6', '--vlan', 'pouet']
result = self.invoke_with_exceptions(ip.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
You must have an --ip-version to 4 when having a vlan.""")
self.assertEqual(result.exit_code, 0)
def test_create_params_vlan_ok(self):
args = ['--datacenter', 'FR', '--bandwidth', '51200',
'--ip-version', '4', '--vlan', 'pouet']
result = self.invoke_with_exceptions(ip.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
/!\ Datacenter FR will be closed on 25/12/2017, please consider using \
another datacenter.
Creating your iface.
\rProgress: [###] 100.00% 00:00:00 \
\nYour iface has been created with the following IP addresses:
ip4:\t95.142.160.181
ip6:\t2001:4b98:dc0:47:216:3eff:feb2:3862""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.iface.create'][0][0]
self.assertEqual(params['datacenter_id'], 1)
self.assertEqual(params['bandwidth'], 51200)
self.assertEqual(params['ip_version'], 4)
self.assertEqual(params['vlan'], 717)
def test_create_params_ip_ko(self):
args = ['--datacenter', 'FR', '--bandwidth', '51200',
'--ip-version', '4', '--ip', '10.50.10.10']
result = self.invoke_with_exceptions(ip.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
You must have a --vlan when giving an --ip.""")
self.assertEqual(result.exit_code, 0)
def test_create_params_ip_ok(self):
args = ['--datacenter', 'FR', '--bandwidth', '51200',
'--ip-version', '4', '--ip', '10.50.10.10',
'--vlan', 'pouet']
result = self.invoke_with_exceptions(ip.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
/!\ Datacenter FR will be closed on 25/12/2017, please consider using \
another datacenter.
Creating your iface.
\rProgress: [###] 100.00% 00:00:00 \
\nYour iface has been created with the following IP addresses:
ip4:\t10.50.10.10""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.iface.create'][0][0]
self.assertEqual(params['datacenter_id'], 1)
self.assertEqual(params['bandwidth'], 51200)
self.assertEqual(params['ip_version'], 4)
self.assertEqual(params['vlan'], 717)
self.assertEqual(params['ip'], '10.50.10.10')
def test_create_params_attach_ko(self):
args = ['--datacenter', 'US', '--bandwidth', '51200',
'--ip-version', '4', '--attach', 'server01']
result = self.invoke_with_exceptions(ip.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
The datacenter you provided does not match the datacenter of the \
vm you want to attach to.""")
self.assertEqual(result.exit_code, 0)
def test_create_params_attach_ok(self):
args = ['--datacenter', 'FR', '--bandwidth', '51200',
'--ip-version', '4', '--ip', '10.50.10.10',
'--vlan', 'pouet', '--attach', 'server01']
result = self.invoke_with_exceptions(ip.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
/!\ Datacenter FR will be closed on 25/12/2017, please consider using \
another datacenter.
Creating your iface.
\rProgress: [###] 100.00% 00:00:00 \
\nYour iface has been created with the following IP addresses:
ip4:\t10.50.10.10
Attaching your iface.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.iface.create'][0][0]
self.assertEqual(params['datacenter_id'], 1)
self.assertEqual(params['bandwidth'], 51200)
self.assertEqual(params['ip_version'], 4)
self.assertEqual(params['vlan'], 717)
self.assertEqual(params['ip'], '10.50.10.10')
def test_create_background(self):
args = ['--datacenter', 'FR', '--bandwidth', '51200',
'--ip-version', '4', '--ip', '10.50.10.10',
'--vlan', 'pouet', '--attach', 'server01',
'--background']
self.maxDiff = None
result = self.invoke_with_exceptions(ip.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
/!\ Datacenter FR will be closed on 25/12/2017, please consider using \
another datacenter.
Creating your iface.
\rProgress: [###] 100.00% 00:00:00 \
\nYour iface has been created with the following IP addresses:
ip4:\t10.50.10.10""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.iface.create'][0][0]
self.assertEqual(params['datacenter_id'], 1)
self.assertEqual(params['bandwidth'], 51200)
self.assertEqual(params['ip_version'], 4)
self.assertEqual(params['vlan'], 717)
self.assertEqual(params['ip'], '10.50.10.10')
def test_detach(self):
args = ['95.142.160.181']
result = self.invoke_with_exceptions(ip.detach, args, input='y\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure you want to detach ip 95.142.160.181? [y/N]: y\
\nThe iface is still attached to the vm 152967.
Will detach it.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_detach_refuse(self):
args = ['95.142.160.181']
result = self.invoke_with_exceptions(ip.detach, args, input='N\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure you want to detach ip 95.142.160.181? [y/N]: N""")
self.assertEqual(result.exit_code, 0)
def test_detach_force(self):
args = ['95.142.160.181', '--force']
result = self.invoke_with_exceptions(ip.detach, args, input='y\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
The iface is still attached to the vm 152967.
Will detach it.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_unknown(self):
args = ['395.142.160.181']
result = self.invoke_with_exceptions(ip.delete, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Sorry interface 395.142.160.181 does not exist
Please use one of the following: ['203968', '204557', '204558', '2361', \
'2361', '95.142.160.181', '2001:4b98:dc2:43:216:3eff:fece:e25f', \
'2001:4b98:dc0:47:216:3eff:feb2:3862', '192.168.232.253', '192.168.232.252'\
]""")
self.assertEqual(result.exit_code, 0)
def test_delete(self):
args = ['95.142.160.181']
result = self.invoke_with_exceptions(ip.delete, args, input='y\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure you want to delete ip(s) 95.142.160.181 [y/N]: y
The iface is still attached to the vm 152967.
Will detach it.
Detaching your iface(s).
\rProgress: [###] 100.00% 00:00:00 \
\nDetaching/deleting your iface(s).
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_refuse(self):
args = ['95.142.160.181']
result = self.invoke_with_exceptions(ip.delete, args, input='N\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure you want to delete ip(s) 95.142.160.181 [y/N]: N""")
self.assertEqual(result.exit_code, 0)
def test_delete_force(self):
args = ['95.142.160.181', '--force']
result = self.invoke_with_exceptions(ip.delete, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
The iface is still attached to the vm 152967.
Will detach it.
Detaching your iface(s).
\rProgress: [###] 100.00% 00:00:00 \
\nDetaching/deleting your iface(s).
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_background(self):
args = ['95.142.160.181', '--background']
result = self.invoke_with_exceptions(ip.delete, args, input='y\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure you want to delete ip(s) 95.142.160.181 [y/N]: y
The iface is still attached to the vm 152967.
Will detach it.
Detaching your iface(s).
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_multiple(self):
args = ['95.142.160.181', '2001:4b98:dc2:43:216:3eff:fece:e25f']
result = self.invoke_with_exceptions(ip.delete, args, input='y\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure you want to delete ip(s) 2001:4b98:dc2:43:216:3eff:fece:e25f, \
95.142.160.181 [y/N]: y
The iface is still attached to the vm 152967.
Will detach it.
Detaching your iface(s).
\rProgress: [###] 100.00% 00:00:00 \
\nDetaching/deleting your iface(s).
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_root.py 0000644 0001750 0001750 00000000520 12507007717 023255 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
from .base import CommandTestCase
from gandi.cli.commands import root
class RootTestCase(CommandTestCase):
def test_api(self):
result = self.invoke_with_exceptions(root.api, [])
self.assertEqual(result.output, """\
API version: 3.3.42
""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/base.py 0000644 0001750 0001750 00000004414 13155510475 022153 0 ustar sayoun sayoun 0000000 0000000 import os
from click.testing import CliRunner
from gandi.cli.core.base import GandiModule
from ..compat import unittest, mock
from ..fixtures.api import Api
from ..fixtures.mocks import MockObject
class CommandTestCase(unittest.TestCase):
base_mocks = [
('gandi.cli.core.base.GandiModule.save', MockObject.blank_func),
('gandi.cli.core.base.GandiModule.execute', MockObject.execute),
('gandi.cli.core.base.GandiModule.deprecated', MockObject.deprecated),
]
mocks = []
def setUp(self):
self.runner = CliRunner()
self.mocks = self.mocks + self.base_mocks
self.mocks = [mock.patch(*mock_args) for mock_args in self.mocks]
for dummy in self.mocks:
dummy.start()
GandiModule._api = Api()
GandiModule._api._calls = {}
GandiModule._conffiles = {'global': {'api': {'env': 'test',
'key': 'apikey0001'},
'apirest': {'key': 'apikey002'}}}
GandiModule._poll_freq = 0.1
self.api_calls = GandiModule._api._calls
def tearDown(self):
GandiModule._api = None
GandiModule._conffiles = {}
for dummy in reversed(self.mocks):
dummy.stop()
def invoke_with_exceptions(self, cli, args, catch_exceptions=False,
**kwargs):
return self.runner.invoke(cli, args, catch_exceptions=catch_exceptions,
**kwargs)
def isolated_invoke_with_exceptions(self, cli, args,
catch_exceptions=False,
temp_dir=None,
temp_name=None,
temp_content=None,
**kwargs):
temp_dir = temp_dir or 'sandbox'
temp_name = temp_name or 'example.txt'
with self.runner.isolated_filesystem():
os.mkdir(temp_dir)
with open('%s/%s' % (temp_dir, temp_name), 'w') as f:
f.write(temp_content)
return self.runner.invoke(cli, args,
catch_exceptions=catch_exceptions,
**kwargs)
gandi.cli-1.2/gandi/cli/tests/commands/test_certstore.py 0000644 0001750 0001750 00000016273 12623134755 024322 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
from ..compat import mock
from .base import CommandTestCase
from gandi.cli.commands import certstore
class CertStoreTestCase(CommandTestCase):
def test_list(self):
result = self.invoke_with_exceptions(certstore.list, [])
self.assertEqual(result.output, """\
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test1.domain.fr
----------
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test1.domain.fr
----------
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test2.domain.fr
----------
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test3.domain.fr
----------
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test4.domain.fr
----------
subject : /OU=Domain Control Validated/OU=Gandi Standard Wildcard SSL/CN=*.domain.fr
""")
self.assertEqual(result.exit_code, 0)
def test_list_all(self):
result = self.invoke_with_exceptions(certstore.list, ['--id',
'--vhosts',
'--dates',
'--fqdns'])
self.assertEqual(result.output, """\
id : 1
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test1.domain.fr
date_created: 20150407T00:00:00
date_expire : 20160316T00:00:00
----------
fqdn : test1.domain.fr
----------
vhost : test1.domain.fr
type : paas
----------
id : 2
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test1.domain.fr
date_created: 20150407T00:00:00
date_expire : 20160316T00:00:00
----------
fqdn : test1.domain.fr
----------
vhost : test1.domain.fr
type : paas
----------
id : 3
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test2.domain.fr
date_created: 20150408T00:00:00
date_expire : 20160408T00:00:00
----------
fqdn : test2.domain.fr
----------
id : 4
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test3.domain.fr
date_created: 20150408T00:00:00
date_expire : 20160408T00:00:00
----------
fqdn : test3.domain.fr
----------
id : 5
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test4.domain.fr
date_created: 20150408T00:00:00
date_expire : 20160408T00:00:00
----------
fqdn : test4.domain.fr
----------
id : 6
subject : /OU=Domain Control Validated/OU=Gandi Standard Wildcard SSL/CN=*.domain.fr
date_created: 20150409T00:00:00
date_expire : 20160409T00:00:00
----------
fqdn : *.domain.fr
----------
vhost : *.domain.fr
type : paas
""")
self.assertEqual(result.exit_code, 0)
def test_info_fqdn(self):
result = self.invoke_with_exceptions(certstore.info, ['test1.domain.fr'])
self.assertEqual(result.output, """\
id : 1
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test1.domain.fr
date_created: 20150407T00:00:00
date_expire : 20160316T00:00:00
----------
fqdn : test1.domain.fr
----------
vhost : test1.domain.fr
type : paas
----------
id : 2
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test1.domain.fr
date_created: 20150407T00:00:00
date_expire : 20160316T00:00:00
----------
fqdn : test1.domain.fr
----------
vhost : test1.domain.fr
type : paas
""")
self.assertEqual(result.exit_code, 0)
def test_info_id(self):
result = self.invoke_with_exceptions(certstore.info, ['1'])
self.assertEqual(result.output, """\
id : 1
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test1.domain.fr
date_created: 20150407T00:00:00
date_expire : 20160316T00:00:00
----------
fqdn : test1.domain.fr
----------
vhost : test1.domain.fr
type : paas
""")
self.assertEqual(result.exit_code, 0)
def test_create(self):
result = self.invoke_with_exceptions(certstore.create,
['--pk', 'PK', '--crt', 'CRT'])
self.assertEqual(result.output, """\
id : 5
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test4.domain.fr
date_created: 20150408T00:00:00
date_expire : 20160408T00:00:00
----------
fqdn : test4.domain.fr
""")
self.assertEqual(result.exit_code, 0)
def test_create_id(self):
result = self.invoke_with_exceptions(certstore.create,
['--pk', 'PK', '--crt-id', '701'])
self.assertEqual(result.output, """\
id : 5
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test4.domain.fr
date_created: 20150408T00:00:00
date_expire : 20160408T00:00:00
----------
fqdn : test4.domain.fr
""")
self.assertEqual(result.exit_code, 0)
def test_create_missing(self):
result = self.invoke_with_exceptions(certstore.create,
['--pk', 'PK'])
self.assertEqual(result.output, """\
One of --certificate or --certificate-id is needed.
""")
self.assertEqual(result.exit_code, 0)
def test_create_too_many(self):
args = ['--pk', 'PK', '--crt', 'CRT', '--crt-id', '999']
result = self.invoke_with_exceptions(certstore.create, args)
self.assertEqual(result.output, """\
Only one of --certificate or --certificate-id is needed.
id : 5
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test4.domain.fr
date_created: 20150408T00:00:00
date_expire : 20160408T00:00:00
----------
fqdn : test4.domain.fr
""")
self.assertEqual(result.exit_code, 0)
def test_create_parameter_files(self):
args = ['--pk', '/tmp/pk.key', '--crt', '/tmp/key.crt']
with mock.patch('gandi.cli.commands.certstore.os.path.isfile',
create=True) as mock_isfile:
mock_isfile.return_value = True
with mock.patch('gandi.cli.commands.certstore.open',
create=True) as mock_open:
mock_open.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(certstore.create, args)
self.assertEqual(result.output, """\
id : 5
subject : /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test4.domain.fr
date_created: 20150408T00:00:00
date_expire : 20160408T00:00:00
----------
fqdn : test4.domain.fr
""")
self.assertEqual(result.exit_code, 0)
def test_delete(self):
result = self.invoke_with_exceptions(certstore.delete, ['1'])
self.assertEqual(result.output, """\
Are you sure to delete the following hosted certificates ?
1: /OU=Domain Control Validated/OU=Gandi Standard SSL/CN=test1.domain.fr
[y/N]: \
""")
self.assertEqual(result.exit_code, 0)
result = self.invoke_with_exceptions(certstore.delete, ['1', '-f'])
self.assertEqual(result.output, """\
""")
self.assertEqual(result.exit_code, 0)
def test_delete_unknown(self):
args = ['100.fr', '-f']
result = self.invoke_with_exceptions(certstore.delete, args)
self.assertEqual(result.output, '')
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_contact.py 0000644 0001750 0001750 00000013233 12623134755 023734 0 ustar sayoun sayoun 0000000 0000000 import re
from .base import CommandTestCase
from ..fixtures.mocks import MockObject
from gandi.cli.core.base import GandiModule
from gandi.cli.commands import contact
class ContactTestCase(CommandTestCase):
mocks = [('gandi.cli.commands.contact.webbrowser.open',
MockObject.blank_func)]
def test_create_dry_run_ok(self):
args = []
inputs = ('0\nPeter\nParker\npeter.parker@spiderman.org\n'
'Central Park\n2600\nNew York\nUSA\n555-123-456\n'
'plokiploki\nplokiploki\n+011.555123456\napikey0001\n')
result = self.invoke_with_exceptions(contact.create, args,
input=inputs)
self.assertEqual(re.sub(r'\[\d+\]', '[1234567890123456]',
result.output.strip()), """\
Choose your contact type
0- individual
1- company
2- association
3- public body
4- reseller
: 0
What is your first name: Peter
What is your last name: Parker
What is your email address: peter.parker@spiderman.org
What is your street address: Central Park
What is your zipcode: 2600
Which city: New York
Which country: USA
What is your telephone number: 555-123-456
Please enter your password [1234567890123456]: \
\nRepeat for confirmation: \
\nphone: string '555-123-456' does not match '^\\+\\d{1,3}\\.\\d+$'
What is your telephone number: +011.555123456
Please activate you public api access from gandi website, and get the apikey.
Your handle is PP0000-GANDI, and the password is the one you defined.
What is your production apikey: apikey0001
You already have an apikey defined, if you want to use the newly created \
contact, use the env var : \
\nexport API_KEY=apikey0001""")
self.assertEqual(result.exit_code, 0)
def test_create_dry_run_unknown_ok(self):
args = []
inputs = ('0\nPeter\nParker\ngreen.goblin@spiderman.org\n'
'Central Park\n2600\nNew York\nUSA\n555-123-456\n'
'plokiploki\nplokiploki\n+011.555123456\napikey0001\n')
result = self.invoke_with_exceptions(contact.create, args,
input=inputs)
self.assertEqual(re.sub(r'\[\d+\]', '[1234567890123456]',
result.output.strip()), """\
Choose your contact type
0- individual
1- company
2- association
3- public body
4- reseller
: 0
What is your first name: Peter
What is your last name: Parker
What is your email address: green.goblin@spiderman.org
What is your street address: Central Park
What is your zipcode: 2600
Which city: New York
Which country: USA
What is your telephone number: 555-123-456
Please enter your password [1234567890123456]: \
\nRepeat for confirmation: \
\nphone: string '555-123-456' does not match '^\\+\\d{1,3}\\.\\d+$'
What is your telephone number: +011.555123456
planet: Pluto not in list Sun, Mercury, Venus, Earth, Mars, Jupiter, Saturn, \
Uranus, Neptune""")
self.assertEqual(result.exit_code, 0)
def test_create_ok(self):
args = []
inputs = ('0\nPeter\nParker\npeter.parker@spiderman.org\n'
'Central Park\n2600\nNew York\nUSA\n+011.555123456\n'
'plokiploki\nplokiploki\napikey0001\n')
result = self.invoke_with_exceptions(contact.create, args,
input=inputs)
self.assertEqual(re.sub(r'\[\d+\]', '[1234567890123456]',
result.output.strip()), """\
Choose your contact type
0- individual
1- company
2- association
3- public body
4- reseller
: 0
What is your first name: Peter
What is your last name: Parker
What is your email address: peter.parker@spiderman.org
What is your street address: Central Park
What is your zipcode: 2600
Which city: New York
Which country: USA
What is your telephone number: +011.555123456
Please enter your password [1234567890123456]: \
\nRepeat for confirmation: \
\nPlease activate you public api access from gandi website, and get the apikey.
Your handle is PP0000-GANDI, and the password is the one you defined.
What is your production apikey: apikey0001
You already have an apikey defined, if you want to use the newly created \
contact, use the env var : \
\nexport API_KEY=apikey0001""")
self.assertEqual(result.exit_code, 0)
def test_create_apikey_ok(self):
args = []
inputs = ('0\nPeter\nParker\npeter.parker@spiderman.org\n'
'Central Park\n2600\nNew York\nUSA\n+011.555123456\n'
'plokiploki\nplokiploki\napikey0002\n')
GandiModule._conffiles = {'global': {}}
result = self.invoke_with_exceptions(contact.create, args,
input=inputs)
self.assertEqual(re.sub(r'\[\d+\]', '[1234567890123456]',
result.output.strip()), """\
Choose your contact type
0- individual
1- company
2- association
3- public body
4- reseller
: 0
What is your first name: Peter
What is your last name: Parker
What is your email address: peter.parker@spiderman.org
What is your street address: Central Park
What is your zipcode: 2600
Which city: New York
Which country: USA
What is your telephone number: +011.555123456
Please enter your password [1234567890123456]: \
\nRepeat for confirmation: \
\nPlease activate you public api access from gandi website, and get the apikey.
Your handle is PP0000-GANDI, and the password is the one you defined.
What is your production apikey: apikey0002
Will save your apikey into the config file.""")
self.assertEqual(result.exit_code, 0)
self.assertTrue('api' in GandiModule._conffiles['global'])
api_key = GandiModule._conffiles['global']['api'].get('key')
self.assertEqual(api_key,'apikey0002')
gandi.cli-1.2/gandi/cli/tests/commands/test_disk.py 0000644 0001750 0001750 00000057004 13227142754 023236 0 ustar sayoun sayoun 0000000 0000000 import re
from click.exceptions import ClickException
from .base import CommandTestCase
from gandi.cli.commands import disk
from gandi.cli.core.utils.size import disk_check_size
from gandi.cli.core.base import GandiContextHelper
class DiskTestCase(CommandTestCase):
def test_list(self):
result = self.invoke_with_exceptions(disk.list, [])
self.assertEqual(result.output, """name : sys_1426759833
state : created
size : 3072
----------
name : sys_server01
state : created
size : 3072
----------
name : data
state : created
size : 3072
----------
name : snaptest
state : created
size : 3072
----------
name : newdisk
state : created
size : 3072
""")
self.assertEqual(result.exit_code, 0)
def test_list_vm(self):
result = self.invoke_with_exceptions(disk.list, ['--vm'])
self.assertEqual(result.output, """name : sys_1426759833
state : created
size : 3072
vm : vm1426759833
----------
name : sys_server01
state : created
size : 3072
vm : server01
----------
name : data
state : created
size : 3072
vm : server01
----------
name : snaptest
state : created
size : 3072
----------
name : newdisk
state : created
size : 3072
""")
self.assertEqual(result.exit_code, 0)
def test_list_id(self):
result = self.invoke_with_exceptions(disk.list, ['--id'])
self.assertEqual(result.output, """name : sys_1426759833
state : created
size : 3072
id : 4969232
----------
name : sys_server01
state : created
size : 3072
id : 4969249
----------
name : data
state : created
size : 3072
id : 4970079
----------
name : snaptest
state : created
size : 3072
id : 663497
----------
name : newdisk
state : created
size : 3072
id : 4969233
""")
self.assertEqual(result.exit_code, 0)
def test_list_type(self):
result = self.invoke_with_exceptions(disk.list, ['--type'])
self.assertEqual(result.output, """name : sys_1426759833
state : created
size : 3072
type : data
----------
name : sys_server01
state : created
size : 3072
type : data
----------
name : data
state : created
size : 3072
type : data
----------
name : snaptest
state : created
size : 3072
type : snapshot
----------
name : newdisk
state : created
size : 3072
type : data
""")
self.assertEqual(result.exit_code, 0)
def test_list_only_data(self):
result = self.invoke_with_exceptions(disk.list, ['--only-data'])
self.assertEqual(result.output, """name : sys_1426759833
state : created
size : 3072
----------
name : sys_server01
state : created
size : 3072
----------
name : data
state : created
size : 3072
----------
name : newdisk
state : created
size : 3072
""")
self.assertEqual(result.exit_code, 0)
def test_list_only_snapshot(self):
result = self.invoke_with_exceptions(disk.list, ['--only-snapshot'])
self.assertEqual(result.output, """name : snaptest
state : created
size : 3072
""")
self.assertEqual(result.exit_code, 0)
def test_list_snapshotprofile(self):
result = self.invoke_with_exceptions(disk.list, ['--snapshotprofile'])
self.assertEqual(result.output, """name : sys_1426759833
state : created
size : 3072
----------
name : sys_server01
state : created
size : 3072
----------
name : data
state : created
size : 3072
profile : minimal
----------
name : snaptest
state : created
size : 3072
----------
name : newdisk
state : created
size : 3072
""")
self.assertEqual(result.exit_code, 0)
def test_list_filter_datacenter(self):
args = ['--datacenter', 'LU-BI1']
result = self.invoke_with_exceptions(disk.list, args)
self.assertEqual(result.output, """\
name : sys_1426759833
state : created
size : 3072
----------
name : newdisk
state : created
size : 3072
""")
self.assertEqual(result.exit_code, 0)
def test_list_attached(self):
result = self.invoke_with_exceptions(disk.list, ['--attached'])
self.assertEqual(result.output, """\
name : sys_1426759833
state : created
size : 3072
----------
name : sys_server01
state : created
size : 3072
----------
name : data
state : created
size : 3072
""")
self.assertEqual(result.exit_code, 0)
def test_list_detached(self):
result = self.invoke_with_exceptions(disk.list, ['--detached'])
self.assertEqual(result.output, """\
name : snaptest
state : created
size : 3072
----------
name : newdisk
state : created
size : 3072
""")
self.assertEqual(result.exit_code, 0)
def test_list_attached_detached_ko(self):
args = ['--detached', '--attached']
result = self.invoke_with_exceptions(disk.list, args)
self.assertEqual(result.output, """\
Usage: list [OPTIONS]
Error: You cannot use both --attached and --detached.
""")
self.assertEqual(result.exit_code, 2)
def test_info(self):
result = self.invoke_with_exceptions(disk.info, ['sys_server01'])
self.assertEqual(result.output, """name : sys_server01
state : created
size : 3072
type : data
id : 4969249
kernel : 3.12-x86_64 (hvm)
cmdline : root=/dev/sda ro nosep console=ttyS0
datacenter: FR-SD2
vm : server01
""")
self.assertEqual(result.exit_code, 0)
def test_info_multiple(self):
args = ['sys_server01', 'data']
result = self.invoke_with_exceptions(disk.info, args)
self.assertEqual(result.output, """name : data
state : created
size : 3072
type : data
id : 4970079
datacenter: FR-SD2
vm : server01
----------
name : sys_server01
state : created
size : 3072
type : data
id : 4969249
kernel : 3.12-x86_64 (hvm)
cmdline : root=/dev/sda ro nosep console=ttyS0
datacenter: FR-SD2
vm : server01
""")
self.assertEqual(result.exit_code, 0)
def test_check_size(self):
result = disk_check_size(None, None, 2048)
self.assertEqual(result, 2048)
self.assertRaises(ClickException, disk_check_size, None, None, 2040)
def test_detach(self):
result = self.invoke_with_exceptions(disk.detach, ['data'])
self.assertEqual(result.output.strip(),
"Are you sure you want to detach data? [y/N]:")
self.assertEqual(result.exit_code, 0)
def test_detach_forced(self):
result = self.invoke_with_exceptions(disk.detach, ['-f', 'data'])
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
The disk is still attached to the vm 152967.
Will detach it.
Detaching your disk(s).
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_detach_background(self):
args = ['data', '--bg', '-f']
result = self.invoke_with_exceptions(disk.detach, args)
self.assertEqual(result.output, """\
The disk is still attached to the vm 152967.
Will detach it.
[{'id': 200, 'step': 'WAIT'}]
""")
def test_attach(self):
args = ['snaptest', 'server01']
result = self.invoke_with_exceptions(disk.attach, args)
self.assertEqual(result.output.strip(), """\
Are you sure you want to attach disk 'snaptest' to vm 'server01'? [y/N]:\
""")
self.assertEqual(result.exit_code, 0)
def test_attach_forced(self):
args = ['snaptest', 'server01', '-f']
result = self.invoke_with_exceptions(disk.attach, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Attaching your disk(s).
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_attach_must_detach(self):
args = ['data', 'vm1426759833']
result = self.invoke_with_exceptions(disk.attach, args,
input='y\n')
self.assertEqual(result.output.strip(), """\
Are you sure you want to attach disk 'data' to vm 'vm1426759833'? [y/N]: y\
\nThis disk is still attached
Are you sure you want to detach data? [y/N]:""")
self.assertEqual(result.exit_code, 0)
def test_attach_must_detach_forced(self):
args = ['data', 'vm1426759833', '-f']
result = self.invoke_with_exceptions(disk.attach, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
The disk is still attached to the vm 152967.
Will detach it.
Detaching your disk.
\rProgress: [###] 100.00% 00:00:00 \
\nAttaching your disk(s).
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_attach_forced_background(self):
args = ['snaptest', 'server01', '-f', '--bg']
result = self.invoke_with_exceptions(disk.attach, args)
self.assertEqual(result.output.strip(), "{'id': 200, 'step': 'WAIT'}")
self.assertEqual(result.exit_code, 0)
def test_update_name(self):
args = ['data', '--name', 'data2']
result = self.invoke_with_exceptions(disk.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_update_kernel(self):
args = ['data', '--kernel', '3.12-x86_64 (hvm)']
result = self.invoke_with_exceptions(disk.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_update_kernel_unavailable(self):
args = ['data', '--kernel', '3.12-x86_64']
result = self.invoke_with_exceptions(disk.update, args)
self.assertEqual(result.output, """\
Usage: update [OPTIONS] RESOURCE
Error: Kernel 3.12-x86_64 is not available for disk data
""")
self.assertEqual(result.exit_code, 2)
def test_update_cmdline(self):
args = ['data', '--cmdline',
'root=/dev/xvda1 loglevel=4 console=hvc0 nosep ro']
result = self.invoke_with_exceptions(disk.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_update_snapshotprofile_ko(self):
args = ['data', '--snapshotprofile', '7']
result = self.invoke_with_exceptions(disk.update, args)
self.assertEqual(result.exit_code, 2)
def test_update_snapshotprofile(self):
args = ['data', '--snapshotprofile', '2']
result = self.invoke_with_exceptions(disk.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_update_snapshotprofile_delete(self):
args = ['data', '--delete-snapshotprofile']
result = self.invoke_with_exceptions(disk.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_update_snapshotprofile_conflict(self):
args = ['data', '--delete-snapshotprofile', '--snapshotprofile', '2']
result = self.invoke_with_exceptions(disk.update, args)
self.assertEqual(result.exit_code, 2)
def test_update_size(self):
args = ['data', '--size', '5G']
result = self.invoke_with_exceptions(disk.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
self.assertEqual(self.api_calls['hosting.disk.update'][0][1],
{'size': 5120})
def test_update_size_prefix(self):
args = ['data', '--size', '+3G']
result = self.invoke_with_exceptions(disk.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
self.assertEqual(self.api_calls['hosting.disk.update'][0][1],
{'size': 6144})
def test_update_background(self):
args = ['data', '--name', 'data2', '--bg']
result = self.invoke_with_exceptions(disk.update, args)
self.assertEqual(result.output.strip(), "{'id': 200, 'step': 'WAIT'}")
self.assertEqual(result.exit_code, 0)
def test_delete(self):
result = self.invoke_with_exceptions(disk.delete, ['data'])
self.assertEqual(result.output.strip(),
"Are you sure you want to delete disk 'data'? [y/N]:")
self.assertEqual(result.exit_code, 0)
def test_delete_multiple(self):
result = self.invoke_with_exceptions(disk.delete, ['data', 'snaptest'])
self.assertEqual(result.output.strip(), """\
Are you sure you want to delete disk 'data, snaptest'? [y/N]:""")
self.assertEqual(result.exit_code, 0)
def test_delete_attached(self):
result = self.invoke_with_exceptions(disk.delete, ['data', '-f'])
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
The disk is still attached to the vm 152967.
Will detach it.
Detaching your disk(s).
\rProgress: [###] 100.00% 00:00:00 \
\nDeleting your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_force(self):
result = self.invoke_with_exceptions(disk.delete, ['snaptest', '-f'])
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Deleting your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_background(self):
args = ['snaptest', '-f', '--bg']
result = self.invoke_with_exceptions(disk.delete, args)
self.assertEqual(result.output.strip(), """\
id : 200
step : WAIT""")
self.assertEqual(result.exit_code, 0)
def test_rollback(self):
args = ['snaptest']
result = self.invoke_with_exceptions(disk.rollback, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Disk rollback in progress.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_rollback_background(self):
args = ['snaptest', '--bg']
result = self.invoke_with_exceptions(disk.rollback, args)
self.assertEqual(result.output.strip(), "{'id': 200, 'step': 'WAIT'}")
self.assertEqual(result.exit_code, 0)
def test_migrate(self):
args = ['snaptest']
result = self.invoke_with_exceptions(disk.migrate, args, input='y\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure you want to migrate disk snaptest ? [y/N]: y\
\n* Starting the migration of disk snaptest from datacenter FR-SD2 to FR-SD3
Disk migration in progress.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_migrate_background(self):
args = ['snaptest', '--bg', '-f']
result = self.invoke_with_exceptions(disk.migrate, args)
self.assertEqual(result.output.strip(), """\
* Starting the migration of disk snaptest from datacenter FR-SD2 to FR-SD3
id : 200
step : WAIT""")
self.assertEqual(result.exit_code, 0)
def test_migrate_not_available(self):
args = ['newdisk']
result = self.invoke_with_exceptions(disk.migrate, args, input='y\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
No datacenter is available for migration""")
self.assertEqual(result.exit_code, 0)
def test_migrate_noconfirm(self):
args = ['snaptest']
result = self.invoke_with_exceptions(disk.migrate, args, input='\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure you want to migrate disk snaptest ? [y/N]:""")
self.assertEqual(result.exit_code, 0)
def test_migrate_attached(self):
args = ['data']
result = self.invoke_with_exceptions(disk.migrate, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Cannot start the migration: disk data is attached. \
Please detach the disk before starting the migration.""")
self.assertEqual(result.exit_code, 0)
def test_snapshot(self):
args = ['snaptest']
result = self.invoke_with_exceptions(disk.snapshot, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Creating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.disk.create_from'][0][0]
self.assertTrue(params['name'].startswith('snp'))
def test_snapshot_background(self):
args = ['snaptest', '--bg']
result = self.invoke_with_exceptions(disk.snapshot, args)
self.assertEqual(result.output.strip(), "{'id': 200, 'step': 'WAIT'}")
self.assertEqual(result.exit_code, 0)
def test_snapshot_name(self):
args = ['snaptest', '--name', 'snappy']
result = self.invoke_with_exceptions(disk.snapshot, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Creating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.disk.create_from'][0][0]
self.assertEqual(params['name'], 'snappy')
def test_create_default_ok(self):
args = []
result = self.invoke_with_exceptions(disk.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Creating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.disk.create'][0][0]
self.assertEqual(params['type'], 'data')
self.assertEqual(params['size'], 3072)
self.assertTrue(params['name'].startswith('vdi'))
def test_create_params(self):
args = ['--name', 'newdisk', '--size', '5G', '--datacenter', 'FR-SD3',
'--snapshotprofile', '3']
result = self.invoke_with_exceptions(disk.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Creating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.disk.create'][0][0]
self.assertEqual(params['datacenter_id'], 4)
self.assertEqual(params['size'], 5120)
self.assertEqual(params['name'], 'newdisk')
self.assertEqual(params['snapshot_profile'], 3)
def test_create_datacenter_closed(self):
args = ['--name', 'newdisk', '--size', '5G', '--datacenter', 'US-BA1',
'--snapshotprofile', '3']
result = self.invoke_with_exceptions(disk.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Error: /!\ Datacenter US-BA1 is closed, please choose another datacenter.""")
self.assertEqual(result.exit_code, 1)
def test_create_datacenter_limited(self):
args = ['--name', 'newdisk', '--size', '5G', '--datacenter', 'FR-SD2',
'--snapshotprofile', '3']
result = self.invoke_with_exceptions(disk.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
/!\ Datacenter FR-SD2 will be closed on 25/12/2017, please consider using \
another datacenter.
Creating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.disk.create'][0][0]
self.assertEqual(params['datacenter_id'], 1)
self.assertEqual(params['size'], 5120)
self.assertEqual(params['name'], 'newdisk')
self.assertEqual(params['snapshot_profile'], 3)
def test_create_params_snapshot_ko(self):
args = ['--name', 'newdisk', '--size', '5G', '--datacenter', 'FR',
'--snapshotprofile', '7']
result = self.invoke_with_exceptions(disk.create, args,
obj=GandiContextHelper())
self.assertEqual(result.exit_code, 2)
def test_create_default_background(self):
args = ['--bg']
result = self.invoke_with_exceptions(disk.create, args,
obj=GandiContextHelper())
self.assertEqual(result.output.strip(), """\
id : 200
step : WAIT""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.disk.create'][0][0]
self.assertEqual(params['type'], 'data')
self.assertEqual(params['size'], 3072)
self.assertTrue(params['name'].startswith('vdi'))
def test_create_vm(self):
args = ['--vm', 'server01']
result = self.invoke_with_exceptions(disk.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
/!\ VM server01 datacenter will be used instead of FR-SD5.
Creating your disk.
\rProgress: [###] 100.00% 00:00:00 \
\nAttaching your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.disk.create'][0][0]
self.assertEqual(params['type'], 'data')
self.assertEqual(params['size'], 3072)
self.assertEqual(params['datacenter_id'], 1)
self.assertTrue(params['name'].startswith('vdi'))
def test_create_source(self):
args = ['--source', 'sys_server01']
result = self.invoke_with_exceptions(disk.create, args,
obj=GandiContextHelper())
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Creating your disk.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
params, disk_id = self.api_calls['hosting.disk.create_from'][0]
self.assertEqual(params['type'], 'data')
self.assertTrue(params['name'].startswith('vdi'))
self.assertEqual(disk_id, 4969249)
gandi.cli-1.2/gandi/cli/tests/commands/__init__.py 0000644 0001750 0001750 00000000000 12501555263 022761 0 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/gandi/cli/tests/commands/test_dns.py 0000644 0001750 0001750 00000057557 13227142762 023104 0 ustar sayoun sayoun 0000000 0000000 from ..compat import mock, ReasonableBytesIO
from .base import CommandTestCase
from gandi.cli.commands import dns
# disable SSL requests warning for tests
import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
RESPONSES = {
'https://dns.api.gandi.net/api/v5/domains': {
'status': 200,
'headers': 'application/json',
'body': [{'domain_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com', # noqa
'domain_records_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records', # noqa
'fqdn': 'iheartcli.com'},
{'domain_href': 'https://dns.api.gandi.net/api/v5/domains/cli.sexy', # noqa
'domain_records_href': 'https://dns.api.gandi.net/api/v5/domains/cli.sexy/records', # noqa
'fqdn': 'cli.sexy'}],
},
'https://dns.api.gandi.net/api/v5/domains/iheartcli.com': {
'status': 200,
'headers': 'application/json',
'body': {'domain_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com', # noqa
'domain_keys_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/keys', # noqa
'domain_records_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records', # noqa
'fqdn': 'iheartcli.com',
'zone_href': 'https://dns.api.gandi.net/api/v5/zones/397c514-e7cb-11e6-9429-00163e6dc886', # noqa
'zone_records_href': 'https://dns.api.gandi.net/api/v5/zones/397c514-e7cb-11e6-9429-00163e6dc886/records', # noqa
'zone_uuid': '397c514-e7cb-11e6-9429-00163e6dc886'}
},
'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records?sort_by=rrset_name': { # noqa
'status': 200,
'headers': 'application/json',
'body': [{'rrset_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records/%40/A', # noqa
'rrset_name': '@',
'rrset_ttl': 10800,
'rrset_type': 'A',
'rrset_values': ['217.70.184.38']},
{'rrset_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records/%40/MX', # noqa
'rrset_name': '@',
'rrset_ttl': 10800,
'rrset_type': 'MX',
'rrset_values': ['50 fb.mail.gandi.net.', '10 spool.mail.gandi.net.']}, # noqa
{'rrset_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records/blog/CNAME', # noqa
'rrset_name': 'blog',
'rrset_ttl': 10800,
'rrset_type': 'CNAME',
'rrset_values': ['blogs.vip.gandi.net.']},
{'rrset_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records/imap/CNAME', # noqa
'rrset_name': 'imap',
'rrset_ttl': 10800,
'rrset_type': 'CNAME',
'rrset_values': ['access.mail.gandi.net.']},
{'rrset_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records/pop/CNAME', # noqa
'rrset_name': 'pop',
'rrset_ttl': 10800,
'rrset_type': 'CNAME',
'rrset_values': ['access.mail.gandi.net.']},
{'rrset_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records/smtp/CNAME', # noqa
'rrset_name': 'smtp',
'rrset_ttl': 10800,
'rrset_type': 'CNAME',
'rrset_values': ['relay.mail.gandi.net.']},
{'rrset_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records/webmail/CNAME', # noqa
'rrset_name': 'webmail',
'rrset_ttl': 10800,
'rrset_type': 'CNAME',
'rrset_values': ['webmail.gandi.net.']},
{'rrset_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records/www/CNAME', # noqa
'rrset_name': 'www',
'rrset_ttl': 10800,
'rrset_type': 'CNAME',
'rrset_values': ['webredir.vip.gandi.net.']}],
},
'https://dns.api.gandi.net/api/v5/dns/rrtypes': {
'status': 200,
'headers': 'application/json',
'body': ['A', 'AAAA', 'CAA', 'CDS', 'CNAME', 'DNAME', 'DS', 'LOC',
'MX', 'NS', 'PTR', 'SPF', 'SRV', 'SSHFP', 'TLSA', 'TXT',
'WKS'],
},
'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records': {
'status': 201,
'headers': 'application/json',
'body': {'message': 'DNS Record Created'},
},
'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records/blog/CNAME': { # noqa
'status': 204,
'headers': 'application/json',
'body': {},
},
'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/keys': {
'status': 200,
'headers': {'content-type': 'application/json',
'location': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/keys/3415833-2314-4a86-ba1c-c3c58608a168'}, # noqa
'body': [{'algorithm': 13,
'algorithm_name': 'ECDSAP256SHA256',
'deleted': False,
'ds': 'iheartcli.com. 3600 IN DS 5411 13 2 6153c39cfe4ff8673635490515e19f5336f5b7ee9c5ca4572fc44b24a0e794a', # noqa
'flags': 256,
'fqdn': 'iheartcli.com',
'key_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/keys/3415833-2314-4a86-ba1c-c3c58608a168', # noqa
'status': 'active',
'uuid': '3415833-2314-4a86-ba1c-c3c58608a168'},
{'algorithm': 13,
'algorithm_name': 'ECDSAP256SHA256',
'deleted': False,
'ds': 'iheartcli.com. 3600 IN DS 43819 13 2 b4e6ed591f28f4a269b9adfaedec836ea0fe63a8f7f5097108297afa5492b70', # noqa
'flags': 256,
'fqdn': 'iheartcli.com',
'key_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/keys/adaab60-bb17-40ed-a13e-88376fe28c86', # noqa
'status': 'active',
'uuid': 'adaab60-bb17-40ed-a13e-88376fe28c86'}],
},
'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/keys/3415833-2314-4a86-ba1c-c3c58608a168': { # noqa
'status': 200,
'headers': 'application/json',
'body': {'algorithm': 13,
'algorithm_name': 'ECDSAP256SHA256',
'deleted': False,
'ds': 'iheartcli.com. 3600 IN DS 5411 13 2 6153c39cfe4ff8673635490515e19f5336f5b7ee9c5ca4572fc44b24a0e794a', # noqa
'flags': 256,
'fqdn': 'iheartcli.com',
'public_key': 'Gnhra3gcNHUL0d05Ia6F/tgBzDD/Km6c2XFZA9RAOcjk/qg9aodc79MQtsTx4/CBlTmCSRIxlXWm1yMmV3LOlw==', # noqa
'fingerprint': '626168cae12c674f38958b324e10c7bb63ed74cc9d649bf04766a7c095c865787', # noqa
'key_href': 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/keys/3415833-2314-4a86-ba1c-c3c58608a168', # noqa
'status': 'active',
'tag': 40658,
'uuid': '3415833-2314-4a86-ba1c-c3c58608a168'},
},
'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/keys/adaab60-bb17-40ed-a13e-88376fe28c86': { # noqa
'status': 204,
'headers': 'application/json',
'body': {},
},
}
def _mock_requests(method, url, *args, **kwargs):
# print(method, url, args, kwargs)
content = RESPONSES[url]['body']
headers = RESPONSES[url]['headers']
if kwargs.get('headers', {}).get('Accept') == 'text/plain':
content = """\
@ 10800 IN A 217.70.184.38
@ 10800 IN MX 10 spool.mail.gandi.net.
@ 10800 IN MX 50 fb.mail.gandi.net.
@ 10800 IN SOA ns1.gandi.net. hostmaster.gandi.net. 197539823 10800 3600 604800 10800
blog 10800 IN CNAME blogs.vip.gandi.net.
imap 10800 IN CNAME access.mail.gandi.net.
pop 10800 IN CNAME access.mail.gandi.net.
smtp 10800 IN CNAME relay.mail.gandi.net.
webmail 10800 IN CNAME webmail.gandi.net.
www 10800 IN CNAME webredir.vip.gandi.net.""" # noqa
content_hdr = kwargs.get('headers', {}).get('Content-Type')
if method == 'PUT' and content_hdr == 'text/plain':
content = {'message': 'DNS Record Created'}
if method == 'PUT' and url == 'https://dns.api.gandi.net/api/v5/domains/iheartcli.com/records/blog/CNAME': # noqa
content = {'message': 'DNS Record Created'}
mock_resp = mock.Mock()
mock_resp.status_code = 200
mock_resp.content = content
mock_resp.headers = headers
mock_resp.json = mock.Mock(return_value=content)
return mock_resp
class DnsTestCase(CommandTestCase):
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_domain_list(self, mock_request):
mock_request.side_effect = _mock_requests
result = self.invoke_with_exceptions(dns.domain_list, [])
wanted = """\
iheartcli.com
cli.sexy
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_list(self, mock_request):
mock_request.side_effect = _mock_requests
result = self.invoke_with_exceptions(dns.list, ['iheartcli.com'])
wanted = """\
name : @
ttl : 10800
type : A
values : 217.70.184.38
----------
name : @
ttl : 10800
type : MX
values : 50 fb.mail.gandi.net., 10 spool.mail.gandi.net.
----------
name : blog
ttl : 10800
type : CNAME
values : blogs.vip.gandi.net.
----------
name : imap
ttl : 10800
type : CNAME
values : access.mail.gandi.net.
----------
name : pop
ttl : 10800
type : CNAME
values : access.mail.gandi.net.
----------
name : smtp
ttl : 10800
type : CNAME
values : relay.mail.gandi.net.
----------
name : webmail
ttl : 10800
type : CNAME
values : webmail.gandi.net.
----------
name : www
ttl : 10800
type : CNAME
values : webredir.vip.gandi.net.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_list_filter(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', '--type', 'CNAME']
result = self.invoke_with_exceptions(dns.list, args)
wanted = """\
----------
name : blog
ttl : 10800
type : CNAME
values : blogs.vip.gandi.net.
----------
name : imap
ttl : 10800
type : CNAME
values : access.mail.gandi.net.
----------
name : pop
ttl : 10800
type : CNAME
values : access.mail.gandi.net.
----------
name : smtp
ttl : 10800
type : CNAME
values : relay.mail.gandi.net.
----------
name : webmail
ttl : 10800
type : CNAME
values : webmail.gandi.net.
----------
name : www
ttl : 10800
type : CNAME
values : webredir.vip.gandi.net.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_list_filter_args(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', 'blog', 'CNAME']
result = self.invoke_with_exceptions(dns.list, args)
wanted = """\
----------
name : blog
ttl : 10800
type : CNAME
values : blogs.vip.gandi.net.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_list_unknown(self, mock_request):
mock_request.side_effect = _mock_requests
result = self.invoke_with_exceptions(dns.list, ['example.com'])
wanted = """\
Sorry domain example.com does not exist
Please use one of the following: iheartcli.com, cli.sexy
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_list_text(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', '--text']
result = self.invoke_with_exceptions(dns.list, args)
wanted = """\
@ 10800 IN A 217.70.184.38
@ 10800 IN MX 10 spool.mail.gandi.net.
@ 10800 IN MX 50 fb.mail.gandi.net.
@ 10800 IN SOA ns1.gandi.net. hostmaster.gandi.net. 197539823 10800 3600 604800 10800
blog 10800 IN CNAME blogs.vip.gandi.net.
imap 10800 IN CNAME access.mail.gandi.net.
pop 10800 IN CNAME access.mail.gandi.net.
smtp 10800 IN CNAME relay.mail.gandi.net.
webmail 10800 IN CNAME webmail.gandi.net.
www 10800 IN CNAME webredir.vip.gandi.net.
""" # noqa
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_create(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', 'blog', 'cname', 'blog.cli.sexy']
result = self.invoke_with_exceptions(dns.create, args)
wanted = """DNS Record Created
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_update_missing(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com']
result = self.invoke_with_exceptions(dns.update, args)
wanted = """Cannot find parameters for zone content to update.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_update_unknown(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['example.com']
result = self.invoke_with_exceptions(dns.update, args)
wanted = """\
Sorry domain example.com does not exist
Please use one of the following: iheartcli.com, cli.sexy
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_update_argument_ok(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', '--file', 'sandbox/example.txt']
content = """\
blog 10800 IN CNAME blogs.vip.gandi.net.
"""
result = self.isolated_invoke_with_exceptions(dns.update, args,
temp_content=content)
wanted = """DNS Record Created
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_update_parameters_ok(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', 'blog', 'cname', 'blog.cli.sexy']
result = self.invoke_with_exceptions(dns.update, args)
wanted = """DNS Record Created
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_update_parameters_ko(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', 'blog', 'cname']
result = self.invoke_with_exceptions(dns.update, args)
wanted = """You must provide one or more value parameter.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_update_pipe_ok(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com']
content = b"""\
blog 10800 IN CNAME blogs.vip.gandi.net.
"""
result = self.invoke_with_exceptions(dns.update, args,
input=ReasonableBytesIO(content))
wanted = """DNS Record Created
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_create_multiple(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', 'blog', 'cname', 'blog.cli.sexy',
'glop.cli.sexy']
result = self.invoke_with_exceptions(dns.create, args)
wanted = """DNS Record Created
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_create_type_case(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', 'blog', 'CNAME', 'blog.cli.sexy']
result = self.invoke_with_exceptions(dns.create, args)
wanted = """DNS Record Created
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_create_unknown(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['example.com', 'blog', 'CNAME', 'blog.cli.sexy']
result = self.invoke_with_exceptions(dns.create, args)
wanted = """\
Sorry domain example.com does not exist
Please use one of the following: iheartcli.com, cli.sexy
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_delete(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', 'blog', 'CNAME', '-f']
result = self.invoke_with_exceptions(dns.delete, args)
wanted = """Delete successful.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_delete_unknown(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['example.com', 'blog', 'CNAME', '-f']
result = self.invoke_with_exceptions(dns.delete, args)
wanted = """\
Sorry domain example.com does not exist
Please use one of the following: iheartcli.com, cli.sexy
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_delete_all(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com']
result = self.invoke_with_exceptions(dns.delete, args, input='\n')
wanted = """\
Are you sure to delete all records for domain iheartcli.com ? [y/N]: \n"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_delete_name(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', 'blog']
result = self.invoke_with_exceptions(dns.delete, args, input='\n')
wanted = """\
Are you sure to delete all 'blog' name records for domain iheartcli.com ? [y/N]: \n""" # noqa
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_delete_prompt(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', 'blog', 'CNAME']
result = self.invoke_with_exceptions(dns.delete, args, input='\n')
wanted = """\
Are you sure to delete all 'blog' records of type CNAME for domain iheartcli.com ? [y/N]: \n""" # noqa
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_keys_list(self, mock_request):
mock_request.side_effect = _mock_requests
result = self.invoke_with_exceptions(dns.keys_list, ['iheartcli.com'])
wanted = """\
uuid : 3415833-2314-4a86-ba1c-c3c58608a168
algorithm : 13
algorithm_name : ECDSAP256SHA256
ds : iheartcli.com. 3600 IN DS 5411 13 2 6153c39cfe4ff8673635490515e19f5336f5b7ee9c5ca4572fc44b24a0e794a
flags : 256
status : active
----------
uuid : adaab60-bb17-40ed-a13e-88376fe28c86
algorithm : 13
algorithm_name : ECDSAP256SHA256
ds : iheartcli.com. 3600 IN DS 43819 13 2 b4e6ed591f28f4a269b9adfaedec836ea0fe63a8f7f5097108297afa5492b70
flags : 256
status : active
""" # noqa
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_keys_info(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', '3415833-2314-4a86-ba1c-c3c58608a168']
result = self.invoke_with_exceptions(dns.keys_info, args)
wanted = """\
uuid : 3415833-2314-4a86-ba1c-c3c58608a168
algorithm : 13
algorithm_name : ECDSAP256SHA256
ds : iheartcli.com. 3600 IN DS 5411 13 2 6153c39cfe4ff8673635490515e19f5336f5b7ee9c5ca4572fc44b24a0e794a
fingerprint : 626168cae12c674f38958b324e10c7bb63ed74cc9d649bf04766a7c095c865787
public_key : Gnhra3gcNHUL0d05Ia6F/tgBzDD/Km6c2XFZA9RAOcjk/qg9aodc79MQtsTx4/CBlTmCSRIxlXWm1yMmV3LOlw==
flags : 256
tag : 40658
status : active
""" # noqa
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_keys_create(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', '256']
result = self.invoke_with_exceptions(dns.keys_create, args)
wanted = """\
uuid : 3415833-2314-4a86-ba1c-c3c58608a168
algorithm : 13
algorithm_name : ECDSAP256SHA256
ds : iheartcli.com. 3600 IN DS 5411 13 2 6153c39cfe4ff8673635490515e19f5336f5b7ee9c5ca4572fc44b24a0e794a
fingerprint : 626168cae12c674f38958b324e10c7bb63ed74cc9d649bf04766a7c095c865787
public_key : Gnhra3gcNHUL0d05Ia6F/tgBzDD/Km6c2XFZA9RAOcjk/qg9aodc79MQtsTx4/CBlTmCSRIxlXWm1yMmV3LOlw==
flags : 256
tag : 40658
status : active
""" # noqa
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_keys_delete_ok(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', 'adaab60-bb17-40ed-a13e-88376fe28c86', '-f']
result = self.invoke_with_exceptions(dns.keys_delete, args)
wanted = """Delete successful.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_keys_delete_prompt(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', 'adaab60-bb17-40ed-a13e-88376fe28c86']
result = self.invoke_with_exceptions(dns.keys_delete, args, input='\n')
wanted = """\
Are you sure you want to delete key adaab60-bb17-40ed-a13e-88376fe28c86 on \
domain iheartcli.com? [y/N]: \n"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.client.requests.request')
def test_dns_keys_recover(self, mock_request):
mock_request.side_effect = _mock_requests
args = ['iheartcli.com', 'adaab60-bb17-40ed-a13e-88376fe28c86']
result = self.invoke_with_exceptions(dns.keys_recover, args)
wanted = """Recover successful.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_vhost.py 0000644 0001750 0001750 00000012335 12715030026 023432 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
import re
from .base import CommandTestCase
from gandi.cli.commands import vhost
class VhostTestCase(CommandTestCase):
def test_list(self):
args = ['--id', '--names']
result = self.invoke_with_exceptions(vhost.list, args)
self.assertEqual(result.output, """\
name : aa3e0e26f8.url-de-test.ws
state : running
date_creation : 20130903T22:11:54
paas_id : 126276
paas_name : paas_owncloud
----------
name : cloud.cat.lol
state : running
date_creation : 20130903T22:24:06
paas_id : 126276
paas_name : paas_owncloud
----------
name : 187832c2b34.testurl.ws
state : running
date_creation : 20141025T15:50:54
paas_id : 163744
paas_name : paas_cozycloud
----------
name : cloud.iheartcli.com
state : running
date_creation : 20141025T15:50:54
paas_id : 163744
paas_name : paas_cozycloud
----------
name : cli.sexy
state : running
date_creation : 20150728T17:50:56
paas_id : 163744
paas_name : paas_cozycloud
""")
self.assertEqual(result.exit_code, 0)
def test_info(self):
args = ['cloud.cat.lol', 'cloud.iheartcli.com', '--id']
result = self.invoke_with_exceptions(vhost.info, args)
self.assertEqual(result.output, """\
name : cloud.cat.lol
state : running
date_creation : 20130903T22:24:06
ssl : disabled
paas_id : 126276
paas_name : paas_owncloud
----------
name : cloud.iheartcli.com
state : running
date_creation : 20141025T15:50:54
ssl : disabled
paas_id : 163744
paas_name : paas_cozycloud
""")
self.assertEqual(result.exit_code, 0)
def test_create(self):
args = ['pouet.lol.cat', '--paas', 'paas_owncloud']
result = self.invoke_with_exceptions(vhost.create, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Creating a new vhost.
\rProgress: [###] 100.00% 00:00:00 \
\nYour vhost pouet.lol.cat has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_ssl(self):
args = ['pouet.lol.cat', '--paas', 'paas_owncloud', '--ssl']
result = self.invoke_with_exceptions(vhost.create, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Please give the private key for certificate id 710 (CN: lol.cat)""")
self.assertEqual(result.exit_code, 0)
def test_create_background(self):
args = ['pouet.lol.cat', '--paas', 'paas_owncloud', '--bg']
result = self.invoke_with_exceptions(vhost.create, args)
self.assertEqual(result.output.strip(), """\
{'id': 200, 'step': 'WAIT'}""")
self.assertEqual(result.exit_code, 0)
def test_update_ssl_ko(self):
args = ['pouet.lol.cat', '--ssl']
result = self.invoke_with_exceptions(vhost.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Please give the private key for certificate id 710 (CN: lol.cat)""")
self.assertEqual(result.exit_code, 0)
def test_update_ssl_ok(self):
args = ['unknown.lol.cat', '--ssl']
result = self.invoke_with_exceptions(vhost.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
There is no certificate for unknown.lol.cat.
Create the certificate with (for exemple) :
$ gandi certificate create --cn unknown.lol.cat --type std \
\nOr relaunch the current command with --poll-cert option""")
self.assertEqual(result.exit_code, 0)
def test_delete_prompt_ok(self):
args = ['pouet.lol.cat']
result = self.invoke_with_exceptions(vhost.delete, args,
input='y\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure to delete vhost pouet.lol.cat? [y/N]: y
Deleting your vhost.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_prompt_ko(self):
args = ['pouet.lol.cat']
result = self.invoke_with_exceptions(vhost.delete, args,
input='N\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure to delete vhost pouet.lol.cat? [y/N]: N""")
self.assertEqual(result.exit_code, 0)
def test_delete_force(self):
args = ['pouet.lol.cat', '--force']
result = self.invoke_with_exceptions(vhost.delete, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Deleting your vhost.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_background(self):
args = ['pouet.lol.cat', '--force', '--bg']
result = self.invoke_with_exceptions(vhost.delete, args)
self.assertEqual(result.output.strip(), """\
name : rproxy_update
paas_id : 1177220
date_creation: 20150728T17:50:56""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_mail.py 0000644 0001750 0001750 00000012770 12766207173 023233 0 ustar sayoun sayoun 0000000 0000000 import re
from .base import CommandTestCase
from gandi.cli.commands import mail
from gandi.cli.core.base import GandiContextHelper
class MailTestCase(CommandTestCase):
def test_list(self):
result = self.invoke_with_exceptions(mail.list, ['iheartcli.com'])
self.assertEqual(result.output, """admin
""")
self.assertEqual(result.exit_code, 0)
def test_info(self):
args = ['admin@iheartcli.com']
result = self.invoke_with_exceptions(mail.info, args)
self.assertEqual(result.output, """login : admin
aliases :
fallback email :
quota usage : 233 KiB / unlimited
responder active: no
responder text :
""")
self.assertEqual(result.exit_code, 0)
def test_create(self):
args = ['contact@iheartcli.com', '--quota', '2', '--fallback',
'admin@cli.sexy', '--alias', 'god@iheartcli.com']
result = self.invoke_with_exceptions(mail.create, args,
obj=GandiContextHelper(),
input='plokiploki\nplokiploki\n')
self.assertEqual(result.output, """password: \
\nRepeat for confirmation: \
\nCreating your mailbox.
Creating aliases.
""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['domain.mailbox.create'][0][2]
self.assertEqual(params['password'], 'plokiploki')
self.assertEqual(params['quota'], 2)
self.assertEqual(params['fallback_email'], 'admin@cli.sexy')
def test_create_with_password(self):
args = ['contact2@iheartcli.com', '--quota', '2', '--fallback',
'admin@cli.sexy', '--alias', 'god@iheartcli.com',
'--password', 'password_for_create']
result = self.invoke_with_exceptions(mail.create, args,
obj=GandiContextHelper())
self.assertEqual(result.output, """Creating your mailbox.
Creating aliases.
""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['domain.mailbox.create'][0][2]
self.assertEqual(params['password'], 'password_for_create')
self.assertEqual(params['quota'], 2)
self.assertEqual(params['fallback_email'], 'admin@cli.sexy')
def test_delete_force(self):
args = ['admin@iheartcli.com', '--force']
result = self.invoke_with_exceptions(mail.delete, args)
self.assertEqual(result.output, '')
self.assertEqual(result.exit_code, 0)
def test_delete(self):
args = ['admin@iheartcli.com']
result = self.invoke_with_exceptions(mail.delete, args, input='y\n')
self.assertEqual(result.output, """\
Are you sure to delete the mailbox admin@iheartcli.com ? [y/N]: y
""")
self.assertEqual(result.exit_code, 0)
def test_delete_force_refused(self):
args = ['admin@iheartcli.com']
result = self.invoke_with_exceptions(mail.delete, args, input='\n')
self.assertEqual(result.output, """\
Are you sure to delete the mailbox admin@iheartcli.com ? [y/N]: \n""")
self.assertEqual(result.exit_code, 0)
def test_udpate(self):
args = ['admin@iheartcli.com', '--quota', '2', '--fallback',
'admin@cli.sexy', '--alias-add', 'doge@iheartcli.com',
'--alias-del', 'god@iheartcli.com', '--password']
result = self.invoke_with_exceptions(mail.update, args,
obj=GandiContextHelper(),
input='plokiploki\nplokiploki\n')
self.assertEqual(result.output, """password: \
\nRepeat for confirmation: \
\nUpdating your mailbox.
Updating aliases.
""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['domain.mailbox.update'][0][2]
self.assertEqual(params['password'], 'plokiploki')
self.assertEqual(params['quota'], 2)
self.assertEqual(params['fallback_email'], 'admin@cli.sexy')
def test_purge(self):
args = ['admin@iheartcli.com']
result = self.invoke_with_exceptions(mail.purge, args, input='y\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure to purge mailbox admin@iheartcli.com ? [y/N]: y
Purging in progress
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_purge_alias(self):
args = ['admin@iheartcli.com', '--alias']
result = self.invoke_with_exceptions(mail.purge, args, input='y\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure to purge all aliases for mailbox admin@iheartcli.com ? [y/N]: y\
""")
self.assertEqual(result.exit_code, 0)
def test_purge_alias_refused(self):
args = ['admin@iheartcli.com', '--alias']
result = self.invoke_with_exceptions(mail.purge, args, input='\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure to purge all aliases for mailbox admin@iheartcli.com ? [y/N]:\
""")
self.assertEqual(result.exit_code, 0)
def test_purge_refused(self):
args = ['admin@iheartcli.com']
result = self.invoke_with_exceptions(mail.purge, args, input='\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure to purge mailbox admin@iheartcli.com ? [y/N]:\
""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_forward.py 0000644 0001750 0001750 00000005302 12656121545 023742 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from .base import CommandTestCase
from gandi.cli.commands import forward
class ForwardTestCase(CommandTestCase):
def test_list(self):
result = self.invoke_with_exceptions(forward.list, ['iheartcli.com'])
self.assertEqual(result.output, """\
admin : admin@cli.sexy
admin : grumpy@cat.lol
contact : contact@cli.sexy
""")
self.assertEqual(result.exit_code, 0)
def test_create(self):
args = ['backup@iheartcli.com', '--destination', 'backup@cat.lol']
result = self.invoke_with_exceptions(forward.create, args)
self.assertEqual(result.output, """\
Creating mail forward backup@iheartcli.com
""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['domain.forward.create'][0][2]
self.assertEqual(params['destinations'], ['backup@cat.lol'])
def test_delete_force(self):
args = ['admin@iheartcli.com', '--force']
result = self.invoke_with_exceptions(forward.delete, args)
self.assertEqual(result.output, '')
self.assertEqual(result.exit_code, 0)
def test_delete(self):
args = ['admin@iheartcli.com']
result = self.invoke_with_exceptions(forward.delete, args, input='y\n')
self.assertEqual(result.output, """\
Are you sure to delete the domain mail forward admin@iheartcli.com ? [y/N]: y
""")
self.assertEqual(result.exit_code, 0)
def test_delete_force_refused(self):
args = ['admin@iheartcli.com']
result = self.invoke_with_exceptions(forward.delete, args, input='\n')
self.assertEqual(result.output, """\
Are you sure to delete the domain mail forward admin@iheartcli.com ? [y/N]: \
\n""")
self.assertEqual(result.exit_code, 0)
def test_update(self):
args = ['admin@iheartcli.com',
'--dest-add', 'doge@iheartcli.com',
'--dest-del', 'grumpy@cat.lol']
result = self.invoke_with_exceptions(forward.update, args)
self.assertEqual(result.output, """\
Updating mail forward admin@iheartcli.com
""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['domain.forward.update'][0][2]
self.assertEqual(params['destinations'], ['admin@cli.sexy',
'doge@iheartcli.com'])
def test_update_no_param(self):
args = ['admin@iheartcli.com']
result = self.invoke_with_exceptions(forward.update, args)
self.assertEqual(result.output, """\
Nothing to update: you must provide destinations to update, use \
--dest-add/-a or -dest-del/-d parameters.
""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_paas.py 0000644 0001750 0001750 00000056727 13227414171 023236 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from functools import partial
import re
from ..compat import mock, StringIO, ConfigParser
from .base import CommandTestCase
from gandi.cli.commands import paas
from gandi.cli.core.base import GandiContextHelper
def _mock_output(git_content, command, *args, **kwargs):
buf = StringIO(git_content)
config = ConfigParser.ConfigParser()
try:
config.read_file(buf)
except:
config.readfp(buf)
if command == 'git config --local --get remote.gandi.url':
try:
return config.get('remote "gandi"', 'url')
except:
return ''
if command == 'git config --local --get remote.origin.url':
try:
return config.get('remote "origin"', 'url')
except:
return ''
if command == 'git config --local --get remote.$(git config --local --get branch.stable.remote).url': # noqa
try:
return config.get('remote "production"', 'url')
except:
return ''
if command == 'git config --local --get remote.$(git config --local --get branch.master.remote).url': # noqa
try:
return config.get('remote "origin"', 'url')
except:
return ''
return ''
class PaasTestCase(CommandTestCase):
def test_list(self):
result = self.invoke_with_exceptions(paas.list, [])
self.assertEqual(result.output, """\
name : paas_owncloud
state : halted
vhost : aa3e0e26f8.url-de-test.ws
vhost : cloud.cat.lol
----------
name : paas_cozycloud
state : running
vhost : 187832c2b34.testurl.ws
vhost : cloud.iheartcli.com
vhost : cli.sexy
""")
self.assertEqual(result.exit_code, 0)
def test_list_id(self):
result = self.invoke_with_exceptions(paas.list, ['--id'])
self.assertEqual(result.output, """\
name : paas_owncloud
state : halted
id : 126276
vhost : aa3e0e26f8.url-de-test.ws
vhost : cloud.cat.lol
----------
name : paas_cozycloud
state : running
id : 163744
vhost : 187832c2b34.testurl.ws
vhost : cloud.iheartcli.com
vhost : cli.sexy
""")
self.assertEqual(result.exit_code, 0)
def test_list_type(self):
result = self.invoke_with_exceptions(paas.list, ['--type'])
self.assertEqual(result.output, """\
name : paas_owncloud
state : halted
type : phpmysql
vhost : aa3e0e26f8.url-de-test.ws
vhost : cloud.cat.lol
----------
name : paas_cozycloud
state : running
type : nodejsmongodb
vhost : 187832c2b34.testurl.ws
vhost : cloud.iheartcli.com
vhost : cli.sexy
""")
self.assertEqual(result.exit_code, 0)
def test_list_filter_state(self):
result = self.invoke_with_exceptions(paas.list, ['--state', 'halted'])
self.assertEqual(result.output, """\
name : paas_owncloud
state : halted
vhost : aa3e0e26f8.url-de-test.ws
vhost : cloud.cat.lol
""")
self.assertEqual(result.exit_code, 0)
def test_info(self):
result = self.invoke_with_exceptions(paas.info, ['paas_cozycloud'])
self.assertEqual(result.output, """\
name : paas_cozycloud
type : nodejsmongodb
size : s
console : 185290@console.dc2.gpaas.net
git_server : git.dc2.gpaas.net
sftp_server: sftp.dc2.gpaas.net
vhost : 187832c2b34.testurl.ws
vhost : cloud.iheartcli.com
vhost : cli.sexy
datacenter : LU-BI1
quota used : 0.5%
snapshot :
""")
self.assertEqual(result.exit_code, 0)
def test_info_stat(self):
args = ['paas_cozycloud', '--stat']
result = self.invoke_with_exceptions(paas.info, args)
self.assertEqual(result.output, """\
name : paas_cozycloud
type : nodejsmongodb
size : s
console : 185290@console.dc2.gpaas.net
git_server : git.dc2.gpaas.net
sftp_server: sftp.dc2.gpaas.net
vhost : 187832c2b34.testurl.ws
vhost : cloud.iheartcli.com
vhost : cli.sexy
datacenter : LU-BI1
quota used : 0.5%
snapshot :
cache :
\thit : 0.0%
\tmiss : 100.0%
\tnot : 0.0%
\tpass : 0.0%
""")
self.assertEqual(result.exit_code, 0)
def test_types(self):
result = self.invoke_with_exceptions(paas.types, [])
self.assertEqual(result.output, """\
phpmysql
phppgsql
nodejspgsql
nodejsmongodb
nodejsmysql
phpmongodb
pythonmysql
pythonpgsql
pythonmongodb
rubymysql
rubypgsql
rubymongodb
""")
self.assertEqual(result.exit_code, 0)
def test_console(self):
result = self.invoke_with_exceptions(paas.console, ['paas_cozycloud'])
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
/!\ Use ~. ssh escape key to exit.
Activation of the console on your PaaS instance
\rProgress: [###] 100.00% 00:00:00 \n\
ssh 185290@console.dc2.gpaas.net""")
self.assertEqual(result.exit_code, 0)
def test_clone_missing(self):
result = self.invoke_with_exceptions(paas.clone, [])
self.assertEqual(result.output, """\
Usage: clone [OPTIONS] NAME
Error: Missing argument "name".
""")
self.assertEqual(result.exit_code, 2)
def test_clone(self):
with mock.patch('gandi.cli.modules.vhost.os.chdir',
create=True) as mock_chdir:
mock_chdir.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(paas.clone, ['cli.sexy'])
self.assertEqual(result.output, """\
git clone ssh+git://185290@git.dc2.gpaas.net/default.git cli.sexy \
--origin gandi
Use `git push gandi master` to push your code to the instance.
Then `$ gandi deploy` to build and deploy your application.
""")
self.assertEqual(result.exit_code, 0)
def test_clone_directory(self):
with mock.patch('gandi.cli.modules.vhost.os.chdir',
create=True) as mock_chdir:
mock_chdir.return_value = mock.MagicMock()
args = ['cli.sexy', '--directory', 'project']
result = self.invoke_with_exceptions(paas.clone, args)
self.assertEqual(result.output, """\
git clone ssh+git://185290@git.dc2.gpaas.net/default.git project --origin gandi
Use `git push gandi master` to push your code to the instance.
Then `$ gandi deploy` to build and deploy your application.
""")
self.assertEqual(result.exit_code, 0)
def test_clone_vhost(self):
with mock.patch('gandi.cli.modules.vhost.os.chdir',
create=True) as mock_chdir:
mock_chdir.return_value = mock.MagicMock()
args = ['paas_cozycloud', '--vhost', 'cli.sexy']
result = self.invoke_with_exceptions(paas.clone, args)
self.assertEqual(result.output, """\
git clone ssh+git://185290@git.dc2.gpaas.net/cli.sexy.git cli.sexy \
--origin gandi
Use `git push gandi master` to push your code to the instance.
Then `$ gandi deploy` to build and deploy your application.
""")
self.assertEqual(result.exit_code, 0)
def test_attach(self):
result = self.invoke_with_exceptions(paas.attach, ['paas_cozycloud'])
self.assertEqual(result.output, """\
git remote add gandi ssh+git://185290@git.dc2.gpaas.net/default.git
Added remote `gandi` to your local git repository.
Use `git push gandi master` to push your code to the instance.
Then `$ gandi deploy` to build and deploy your application.
""")
self.assertEqual(result.exit_code, 0)
def test_attach_remote(self):
args = ['paas_cozycloud', '--remote', 'production']
result = self.invoke_with_exceptions(paas.attach, args)
self.assertEqual(result.output, """\
git remote add production ssh+git://185290@git.dc2.gpaas.net/default.git
Added remote `production` to your local git repository.
Use `git push production master` to push your code to the instance.
Then `$ gandi deploy` to build and deploy your application.
""")
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.base.GandiModule.exec_output')
def test_deploy_invalid_remote_empty(self, mock_exec_output):
args = []
git_content = """
[blabla]
dummy=dududududud
"""
mock_exec_output.side_effect = partial(_mock_output, git_content)
result = self.invoke_with_exceptions(paas.deploy, args)
self.assertEqual(result.output, """\
Error: Could not find git remote to extract deploy url from.
This usually happens when:
- the current directory has no Simple Hosting git remote attached,
in this case, please see $ gandi paas attach --help
- the local branch being deployed hasn't been pushed \
to the remote repository yet,
in this case, please try $ git push master
Otherwise, it's recommended to use the --remote and/or --branch options:
$ gandi deploy --remote [--branch ]
""")
self.assertEqual(result.exit_code, 2)
@mock.patch('gandi.cli.core.base.GandiModule.exec_output')
def test_deploy_invalid_remote_content(self, mock_exec_output):
args = []
self.maxDiff = None
git_content = """
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = https://github.com/Gandi/gandi.cli.git
[branch "master"]
remote = origin
merge = refs/heads/master
"""
mock_exec_output.side_effect = partial(_mock_output, git_content)
result = self.invoke_with_exceptions(paas.deploy, args)
self.assertEqual(result.output, """\
Error: https://github.com/Gandi/gandi.cli.git \
is not a valid Simple Hosting git remote.
This usually happens when:
- the current directory has no Simple Hosting git remote attached,
in this case, please see $ gandi paas attach --help
- the local branch being deployed hasn't been pushed \
to the remote repository yet,
in this case, please try $ git push master
Otherwise, it's recommended to use the --remote and/or --branch options:
$ gandi deploy --remote [--branch ]
""")
self.assertEqual(result.exit_code, 2)
@mock.patch('gandi.cli.core.base.GandiModule.exec_output')
def test_deploy(self, mock_exec_output):
args = []
git_content = """
[remote "gandi"]
fetch = +refs/heads/*:refs/remotes/gandi/*
url = ssh+git://185290@git.dc2.gpaas.net/default.git
"""
mock_exec_output.side_effect = partial(_mock_output, git_content)
result = self.invoke_with_exceptions(paas.deploy, args)
self.assertEqual(result.output, """\
ssh 185290@git.dc2.gpaas.net 'deploy default.git master'
""")
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.base.GandiModule.exec_output')
def test_deploy_remote(self, mock_exec_output):
args = ['--remote', 'origin']
git_content = """
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = ssh+git://185290@git.dc2.gpaas.net/default.git
[branch "master"]
remote = origin
merge = refs/heads/master
"""
mock_exec_output.side_effect = partial(_mock_output, git_content)
result = self.invoke_with_exceptions(paas.deploy, args)
self.assertEqual(result.output, """\
ssh 185290@git.dc2.gpaas.net 'deploy default.git master'
""")
self.assertEqual(result.exit_code, 0)
@mock.patch('gandi.cli.core.base.GandiModule.exec_output')
def test_deploy_guess_remote_with_branch(self, mock_exec_output):
args = ['--branch', 'stable']
git_content = """
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = https://github.com/Gandi/gandi.cli.git
[remote "production"]
fetch = +refs/heads/*:refs/remotes/production/*
url = ssh+git://185290@git.dc2.gpaas.net/default.git
[branch "master"]
remote = origin
merge = refs/heads/master
[branch "stable"]
remote = production
merge = refs/heads/stable
"""
mock_exec_output.side_effect = partial(_mock_output, git_content)
result = self.invoke_with_exceptions(paas.deploy, args)
self.assertEqual(result.output, """\
ssh 185290@git.dc2.gpaas.net 'deploy default.git stable'
""")
self.assertEqual(result.exit_code, 0)
def test_delete_unknown(self):
result = self.invoke_with_exceptions(paas.delete, ['unknown_paas'])
self.assertEqual(result.output, """\
Sorry PaaS instance unknown_paas does not exist
Please use one of the following: ['paas_owncloud', 'paas_cozycloud', \
'126276', '163744', 'aa3e0e26f8.url-de-test.ws', 'cloud.cat.lol', \
'187832c2b34.testurl.ws', 'cloud.iheartcli.com', 'cli.sexy']
""")
self.assertEqual(result.exit_code, 0)
def test_delete(self):
result = self.invoke_with_exceptions(paas.delete, ['paas_owncloud'])
self.assertEqual(result.output.strip(), """\
Are you sure to delete PaaS instance 'paas_owncloud'? [y/N]:""")
self.assertEqual(result.exit_code, 0)
def test_delete_ko(self):
args = ['paas_owncloud']
result = self.invoke_with_exceptions(paas.delete, args, input='N\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure to delete PaaS instance 'paas_owncloud'? [y/N]: N\
""")
self.assertEqual(result.exit_code, 0)
def test_delete_ok(self):
args = ['paas_owncloud']
result = self.invoke_with_exceptions(paas.delete, args, input='y\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure to delete PaaS instance 'paas_owncloud'? [y/N]: y
Deleting your PaaS instance.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_background(self):
args = ['paas_owncloud', '--force', '--background']
result = self.invoke_with_exceptions(paas.delete, args)
self.assertEqual(result.output, """\
id : 200
step : WAIT
""")
self.assertEqual(result.exit_code, 0)
def test_create_default(self):
args = []
result = self.invoke_with_exceptions(paas.create, args,
obj=GandiContextHelper(),
input='ploki\nploki\n')
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(re.sub(r'paas\d+', 'paas', output), """\
password: \nRepeat for confirmation: \n\
Creating your PaaS instance.
\rProgress: [###] 100.00% 00:00:00 \n\
Your PaaS instance paas has been created.""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['paas.create'][0][0]
self.assertEqual(params['datacenter_id'], 3)
self.assertEqual(params['size'], 's')
self.assertEqual(params['duration'], '1m')
self.assertEqual(params['password'], 'ploki')
self.assertTrue(params['name'].startswith('paas'))
def test_create_size(self):
args = ['--size', 's+']
result = self.invoke_with_exceptions(paas.create, args,
obj=GandiContextHelper(),
input='ploki\nploki\n')
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(re.sub(r'paas\d+', 'paas', output), """\
password: \nRepeat for confirmation: \n\
Creating your PaaS instance.
\rProgress: [###] 100.00% 00:00:00 \n\
Your PaaS instance paas has been created.""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['paas.create'][0][0]
self.assertEqual(params['datacenter_id'], 3)
self.assertEqual(params['size'], 's+')
self.assertEqual(params['duration'], '1m')
self.assertEqual(params['password'], 'ploki')
self.assertTrue(params['name'].startswith('paas'))
def test_create_name(self):
args = ['--name', '123456']
result = self.invoke_with_exceptions(paas.create, args,
obj=GandiContextHelper(),
input='ploki\nploki\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
password: \nRepeat for confirmation: \n\
Creating your PaaS instance.
\rProgress: [###] 100.00% 00:00:00 \n\
Your PaaS instance 123456 has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_name_vhost(self):
self.maxDiff = None
args = ['--name', '123456', '--vhosts', 'ploki.fr', '--ssl']
with mock.patch('gandi.cli.modules.vhost.os.chdir',
create=True) as mock_chdir:
mock_chdir.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(paas.create, args,
obj=GandiContextHelper(),
input='ploki\nploki\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
password: \nRepeat for confirmation: \n\
There is no certificate for ploki.fr.
Create the certificate with (for exemple) :
$ gandi certificate create --cn ploki.fr --type std \n\
Or relaunch the current command with --poll-cert option
Creating your PaaS instance.
\rProgress: [###] 100.00% 00:00:00 \n\
Your PaaS instance 123456 has been created.
Creating a new vhost.
\rProgress: [###] 100.00% 00:00:00 \n\
Your vhost ploki.fr has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_name_vhost_ssl(self):
self.maxDiff = None
args = ['--name', '123456', '--vhosts', 'inter.net', '--ssl']
with mock.patch('gandi.cli.modules.vhost.os.chdir',
create=True) as mock_chdir:
mock_chdir.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(paas.create, args,
obj=GandiContextHelper(),
input='ploki\nploki\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
password: \nRepeat for confirmation: \n\
Please give the private key for certificate id 706 (CN: inter.net)""")
self.assertEqual(result.exit_code, 0)
def test_create_datacenter_limited(self):
args = ['--datacenter', 'FR-SD2']
result = self.invoke_with_exceptions(paas.create, args,
obj=GandiContextHelper(),
input='ploki\nploki\n')
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(re.sub(r'paas\d+', 'paas', output), """\
/!\ Datacenter FR-SD2 will be closed on 25/12/2017, please consider using \
another datacenter.
password: \nRepeat for confirmation: \n\
Creating your PaaS instance.
\rProgress: [###] 100.00% 00:00:00 \n\
Your PaaS instance paas has been created.""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['paas.create'][0][0]
self.assertEqual(params['datacenter_id'], 1)
self.assertEqual(params['size'], 's')
self.assertEqual(params['duration'], '1m')
self.assertEqual(params['password'], 'ploki')
self.assertTrue(params['name'].startswith('paas'))
self.assertEqual(result.exit_code, 0)
def test_create_datacenter_closed(self):
args = ['--datacenter', 'US-BA1']
result = self.invoke_with_exceptions(paas.create, args,
obj=GandiContextHelper(),
input='ploki\nploki\n')
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(re.sub(r'paas\d+', 'paas', output), """\
Error: /!\ Datacenter US-BA1 is closed, please choose another datacenter.""")
self.assertEqual(result.exit_code, 1)
def test_update(self):
args = ['paas_owncloud']
result = self.invoke_with_exceptions(paas.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your PaaS instance.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_update_for_upgrade(self):
args = ['paas_owncloud', '--upgrade']
result = self.invoke_with_exceptions(paas.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your PaaS instance.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['paas.update'][0][1]
self.assertEqual(params['upgrade'], True)
def test_update_snapshotprofile_conflict(self):
args = ['paas_owncloud', '--delete-snapshotprofile',
'--snapshotprofile', '7']
result = self.invoke_with_exceptions(paas.update, args)
self.assertEqual(result.exit_code, 2)
def test_update_password(self):
args = ['paas_owncloud', '--password']
result = self.invoke_with_exceptions(paas.update, args,
obj=GandiContextHelper(),
input='ploki\nploki\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
password: \nRepeat for confirmation: \n\
Updating your PaaS instance.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_update_delete_snapshotprofile(self):
args = ['paas_owncloud', '--delete-snapshotprofile']
result = self.invoke_with_exceptions(paas.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your PaaS instance.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['paas.update'][0][1]
self.assertFalse('upgrade' in params)
def test_update_background(self):
args = ['paas_owncloud', '--bg']
result = self.invoke_with_exceptions(paas.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
{'id': 200, 'step': 'WAIT'}""")
self.assertEqual(result.exit_code, 0)
def test_restart_unknown(self):
args = ['unknown_paas']
result = self.invoke_with_exceptions(paas.restart, args)
self.assertEqual(result.output, """\
Sorry PaaS instance unknown_paas does not exist
Please use one of the following: ['paas_owncloud', 'paas_cozycloud', \
'126276', '163744', 'aa3e0e26f8.url-de-test.ws', 'cloud.cat.lol', \
'187832c2b34.testurl.ws', 'cloud.iheartcli.com', 'cli.sexy']
""")
def test_restart_prompt_ko(self):
args = ['paas_owncloud']
result = self.invoke_with_exceptions(paas.restart, args, input='N\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure to restart PaaS instance 'paas_owncloud'? [y/N]: N\
""")
self.assertEqual(result.exit_code, 0)
def test_restart(self):
args = ['paas_owncloud', '--force']
result = self.invoke_with_exceptions(paas.restart, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Restarting your PaaS instance.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_restart_background(self):
args = ['paas_owncloud', '--force', '--bg']
result = self.invoke_with_exceptions(paas.restart, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
id : 200
step : WAIT""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_sshkey.py 0000644 0001750 0001750 00000006104 13164644514 023606 0 ustar sayoun sayoun 0000000 0000000 from .base import CommandTestCase
from gandi.cli.commands import sshkey
class SSHKeyTestCase(CommandTestCase):
def test_list(self):
args = ['--id']
result = self.invoke_with_exceptions(sshkey.list, args)
self.assertEqual(result.output, """\
name : default
fingerprint : b3:11:67:10:2e:1b:a5:66:ed:16:24:98:3e:2e:ed:f5
id : 134
----------
name : mysecretkey
fingerprint : 09:11:21:e3:90:3c:7d:d5:06:d9:6f:f9:36:e1:99:a6
id : 141
""")
self.assertEqual(result.exit_code, 0)
def test_info(self):
args = ['default', '--id', '--value']
result = self.invoke_with_exceptions(sshkey.info, args)
self.assertEqual(result.output, """\
name : default
fingerprint : b3:11:67:10:2e:1b:a5:66:ed:16:24:98:3e:2e:ed:f5
id : 134
value : ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC63QZAW3tusdv+JuyzOoXTND9/wxKogMwZbxBPPtoN7Hjnyn0kUUHMJ6ji5xpbatRYKOeGAoZDW2TXojvbJdQj7tWsRr7ES0qB9qhDGVSDIJWRQ6f9MQCCLjV5tpBTAwb unknown@lol.cat
""") # noqa
self.assertEqual(result.exit_code, 0)
def test_delete(self):
args = ['mysecretkey']
result = self.invoke_with_exceptions(sshkey.delete, args)
self.assertEqual(result.output, '')
self.assertEqual(result.exit_code, 0)
def test_create_value_and_filename_ko(self):
args = ['--name', 'newkey', '--value',
'ssh-rsa LjV5tpBTAwb unknown@inter.net',
'--filename', 'sandbox/example.txt']
content = """\
ssh-rsa LjV5tpBTAwb unknown@inter.net
"""
result = self.isolated_invoke_with_exceptions(sshkey.create, args,
temp_content=content)
self.assertEqual(result.output, """\
Usage: create [OPTIONS]
Error: You must not set value AND filename.
""")
self.assertEqual(result.exit_code, 2)
def test_create_no_value_and_no_filename_ko(self):
args = ['--name', 'newkey']
result = self.invoke_with_exceptions(sshkey.create, args)
self.assertEqual(result.output, """\
Usage: create [OPTIONS]
Error: You must set value OR filename.
""")
self.assertEqual(result.exit_code, 2)
def test_create_value_ok(self):
args = ['--name', 'newkey', '--value',
'ssh-rsa LjV5tpBTAwb unknown@inter.net']
result = self.invoke_with_exceptions(sshkey.create, args)
self.assertEqual(result.output, """\
id : 145
name : newkey
fingerprint : b3:11:67:10:2e:1b:a5:55:ed:16:24:98:3e:2e:ed:f5
""")
self.assertEqual(result.exit_code, 0)
def test_create_file_ok(self):
args = ['--name', 'newkey', '--filename', 'sandbox/example.txt']
content = """\
ssh-rsa LjV5tpBTAwb unknown@inter.net
"""
result = self.isolated_invoke_with_exceptions(sshkey.create, args,
temp_content=content)
self.assertEqual(result.output, """\
id : 145
name : newkey
fingerprint : b3:11:67:10:2e:1b:a5:55:ed:16:24:98:3e:2e:ed:f5
""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_webacc.py 0000644 0001750 0001750 00000045134 13120224646 023522 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import re
from .base import CommandTestCase
from gandi.cli.commands import webacc
class WebaccTestCase(CommandTestCase):
def test_list(self):
result = self.invoke_with_exceptions(webacc.list, [])
self.assertEqual(result.output, """\
name : webacc01
state : running
ssl : Disable
Vhosts :
Backends :
\tBackend with ip address 195.142.160.181 no longer exists.
\tYou should remove it.
\tip : 195.142.160.181
\tport : 80
\tstate : running
----
name : testwebacc
state : running
ssl : Disable
Vhosts :
\tvhost : pouet.iheartcli.com
\tssl : Disable
Backends :
\tname : server01
\tip : 95.142.160.181
\tport : 80
\tstate : running
""")
self.assertEqual(result.exit_code, 0)
def test_list_output_json(self):
args = ['--format', 'json']
result = self.invoke_with_exceptions(webacc.list, args)
self.assertEqual(result.output, """\
[{"datacenter_id": 3, "date_created": "20160115T162658", "id": 12138, \
"name": "webacc01", "probe": {"enable": true, "host": null, "interval": null, \
"method": null, "response": null, "threshold": null, "timeout": null, \
"url": null, "window": null}, "servers": [{"fallback": false, "id": 14988, \
"ip": "195.142.160.181", "port": 80, "rproxy_id": 132691, \
"state": "running"}], "ssl_enable": false, "state": "running", \
"uuid": 12138, "vhosts": []}, {"datacenter_id": 1, \
"date_created": "20160115T162658", "id": 13263, "name": "testwebacc", \
"probe": {"enable": true, "host": "95.142.160.181", "interval": 10, \
"method": "GET", "response": 200, "threshold": 3, "timeout": 5, "url": "/", \
"window": 5}, "servers": [{"fallback": false, "id": 4988, \
"ip": "95.142.160.181", "port": 80, "rproxy_id": 13269, "state": "running"}], \
"ssl_enable": false, "state": "running", "uuid": 13263, \
"vhosts": [{"cert_id": null, "id": 5171, "name": "pouet.iheartcli.com", \
"rproxy_id": 13263, "state": "running"}]}]
""")
self.assertEqual(result.exit_code, 0)
def test_info(self):
result = self.invoke_with_exceptions(webacc.info, ['testwebacc'])
self.assertEqual(result.output, """\
name : testwebacc
state : running
datacenter : Equinix Paris
ssl : Disable
algorithm : client-ip
Vhosts :
\tvhost : pouet.iheartcli.com
\tssl : None
Backends :
\tname : server01
\tip : 95.142.160.181
\tport : 80
\tstate : running
Probe :
\tstate : Enabled
\thost : 95.142.160.181
\tinterval : 10
\tmethod : GET
\tresponse : 200
\tthreshold : 3
\ttimeout : 5
\turl : /
\twindow : 5
""")
self.assertEqual(result.exit_code, 0)
def test_info_output_json(self):
args = ['testwebacc', '--format', 'json']
result = self.invoke_with_exceptions(webacc.info, args)
self.assertEqual(result.output, """\
{"datacenter": {"country": "France", "dc_code": "FR-SD2", "id": 1, \
"iso": "FR", "name": "Equinix Paris"}, "date_created": "20160115T162658", \
"id": 13263, "lb": {"algorithm": "client-ip"}, "name": "testwebacc", \
"probe": {"enable": true, "host": "95.142.160.181", "interval": 10, \
"method": "GET", "response": 200, "threshold": 3, "timeout": 5, "url": "/", \
"window": 5}, "servers": [{"fallback": false, "id": 4988, \
"ip": "95.142.160.181", "port": 80, "rproxy_id": 13269, "state": "running"}], \
"ssl_enable": false, "state": "running", "uuid": 13263, \
"vhosts": [{"cert_id": null, "id": 5171, "name": "pouet.iheartcli.com", \
"rproxy_id": 13263, "state": "running"}]}
""")
self.assertEqual(result.exit_code, 0)
def test_info_backend_expired(self):
result = self.invoke_with_exceptions(webacc.info, ['webacc01'])
self.assertEqual(result.output, """\
name : webacc01
state : running
datacenter : Equinix Paris
ssl : Disable
algorithm : client-ip
Vhosts :
Backends :
\tBackend with ip address 195.142.160.181 no longer exists.
\tYou should remove it.
\tip : 195.142.160.181
\tport : 80
\tstate : running
Probe :
\tstate : Enabled
\thost :
\tinterval :
\tmethod :
\tresponse :
\tthreshold :
\ttimeout :
\turl :
\twindow :
""")
self.assertEqual(result.exit_code, 0)
def test_create(self):
args = ['webacc2', '--datacenter', 'FR-SD3']
result = self.invoke_with_exceptions(webacc.create, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Creating your webaccelerator webacc2
\rProgress: [###] 100.00% 00:00:00 \
\nYour webaccelerator have been created""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.rproxy.create'][0][0]
self.assertEqual(params['name'], 'webacc2')
self.assertEqual(params['datacenter_id'], 4)
self.assertEqual(params['ssl_enable'], False)
self.assertEqual(params['lb'], {'algorithm': 'client-ip'})
self.assertEqual(params['zone_alter'], False)
self.assertEqual(params['override'], True)
def test_create_datacenter_limited(self):
args = ['webacc2', '--datacenter', 'FR-SD2']
result = self.invoke_with_exceptions(webacc.create, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
/!\ Datacenter FR-SD2 will be closed on 25/12/2017, please consider using \
another datacenter.
Creating your webaccelerator webacc2
\rProgress: [###] 100.00% 00:00:00 \
\nYour webaccelerator have been created""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.rproxy.create'][0][0]
self.assertEqual(params['name'], 'webacc2')
self.assertEqual(params['datacenter_id'], 1)
self.assertEqual(params['ssl_enable'], False)
self.assertEqual(params['lb'], {'algorithm': 'client-ip'})
self.assertEqual(params['zone_alter'], False)
self.assertEqual(params['override'], True)
def test_create_datacenter_closed(self):
args = ['webacc2', '--datacenter', 'US-BA1']
result = self.invoke_with_exceptions(webacc.create, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Error: /!\ Datacenter US-BA1 is closed, please choose another datacenter.""")
self.assertEqual(result.exit_code, 1)
def test_create_ssl_ok(self):
args = ['webacc2', '--datacenter', 'FR-SD3', '--vhost',
'pouet.lol.cat', '--ssl']
result = self.invoke_with_exceptions(webacc.create, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Please give the private key for certificate id 710 (CN: lol.cat)""")
self.assertEqual(result.exit_code, 0)
def test_create_backend_prompt(self):
args = ['webacc2', '--datacenter', 'FR-SD3',
'--backend', '195.142.160.181']
result = self.invoke_with_exceptions(webacc.create, args,
input='8080\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Please set a port for backends. \
If you want to set different port for each backend, use `-b ip:port`: 8080
Creating your webaccelerator webacc2
\rProgress: [###] 100.00% 00:00:00 \
\nYour webaccelerator have been created""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.rproxy.create'][0][0]
self.assertEqual(params['name'], 'webacc2')
self.assertEqual(params['datacenter_id'], 4)
self.assertEqual(params['ssl_enable'], False)
self.assertEqual(params['lb'], {'algorithm': 'client-ip'})
self.assertEqual(params['zone_alter'], False)
self.assertEqual(params['override'], True)
self.assertEqual(params['servers'], ({'ip': u'195.142.160.181',
'port': 8080},))
def test_create_backend_port_ok(self):
args = ['webacc2', '--datacenter', 'FR-SD3',
'--backend', '195.142.160.181', '--port', 9000]
result = self.invoke_with_exceptions(webacc.create, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Creating your webaccelerator webacc2
\rProgress: [###] 100.00% 00:00:00 \
\nYour webaccelerator have been created""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.rproxy.create'][0][0]
self.assertEqual(params['name'], 'webacc2')
self.assertEqual(params['datacenter_id'], 4)
self.assertEqual(params['ssl_enable'], False)
self.assertEqual(params['lb'], {'algorithm': 'client-ip'})
self.assertEqual(params['zone_alter'], False)
self.assertEqual(params['override'], True)
self.assertEqual(params['servers'], ({'ip': u'195.142.160.181',
'port': 9000},))
def test_update(self):
args = ['testwebacc', '-n', 'testwebacc2',
'--ssl-enable', '--algorithm', 'round-robin']
result = self.invoke_with_exceptions(webacc.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your webaccelerator
\rProgress: [###] 100.00% 00:00:00 \
\nThe webaccelerator have been udated""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.rproxy.update'][0][1]
self.assertEqual(params['name'], 'testwebacc2')
self.assertEqual(params['ssl_enable'], True)
self.assertEqual(params['lb'], {'algorithm': 'round-robin'})
def test_delete_webacc(self):
args = ['-w', 'webacc01']
result = self.invoke_with_exceptions(webacc.delete, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Deleting your webaccelerator named webacc01
\rProgress: [###] 100.00% 00:00:00 \
\nWebaccelerator have been deleted""")
self.assertEqual(result.exit_code, 0)
def test_delete_vhost(self):
args = ['-v', 'pouet.iheartcli.com']
result = self.invoke_with_exceptions(webacc.delete, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Deleting your virtual host pouet.iheartcli.com
\rProgress: [###] 100.00% 00:00:00 \
\nYour virtual host have been removed""")
self.assertEqual(result.exit_code, 0)
def test_delete_backend_prompt(self):
args = ['--backend', '195.142.160.181']
result = self.invoke_with_exceptions(webacc.delete, args,
input='8080\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Please set a port for backends. \
If you want to different port for each backend, use `-b ip:port`: 8080
Removing backend 195.142.160.181:8080 into webaccelerator
\rProgress: [###] 100.00% 00:00:00 \
\nYour backend have been removed""")
self.assertEqual(result.exit_code, 0)
def test_delete_backend_port_ok(self):
args = ['--backend', '195.142.160.181', '--port', 9000]
result = self.invoke_with_exceptions(webacc.delete, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Removing backend 195.142.160.181:9000 into webaccelerator
\rProgress: [###] 100.00% 00:00:00 \
\nYour backend have been removed""")
self.assertEqual(result.exit_code, 0)
def test_enable_probe(self):
args = ['webacc01', '-p']
result = self.invoke_with_exceptions(webacc.enable, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Activating probe on webacc01
\rProgress: [###] 100.00% 00:00:00 \
\nThe probe have been activated""")
self.assertEqual(result.exit_code, 0)
def test_enable_probe_no_resource(self):
args = ['-p']
result = self.invoke_with_exceptions(webacc.enable, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
You need to indicate the Webaccelerator name""")
self.assertEqual(result.exit_code, 0)
def test_enable_backend_prompt(self):
args = ['webacc01', '--backend', '195.142.160.181']
result = self.invoke_with_exceptions(webacc.enable, args,
input='8080\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Please set a port for backends. \
If you want to different port for each backend, use `-b ip:port`: 8080
Activating backend 195.142.160.181
\rProgress: [###] 100.00% 00:00:00 \
\nBackend activated""")
self.assertEqual(result.exit_code, 0)
def test_enable_backend_port_ok(self):
args = ['webacc01', '--backend', '195.142.160.181',
'--port', 9000]
result = self.invoke_with_exceptions(webacc.enable, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Activating backend 195.142.160.181
\rProgress: [###] 100.00% 00:00:00 \
\nBackend activated""")
self.assertEqual(result.exit_code, 0)
def test_disable_probe(self):
args = ['webacc01', '-p']
result = self.invoke_with_exceptions(webacc.disable, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Desactivating probe on webacc01
\rProgress: [###] 100.00% 00:00:00 \
\nThe probe have been desactivated""")
self.assertEqual(result.exit_code, 0)
def test_disable_probe_no_resource(self):
args = ['-p']
result = self.invoke_with_exceptions(webacc.disable, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
You need to indicate the Webaccelerator name""")
self.assertEqual(result.exit_code, 0)
def test_disable_backend_port_ok(self):
args = ['webacc01', '--backend', '195.142.160.181',
'--port', 9000]
result = self.invoke_with_exceptions(webacc.disable, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Desactivating backend on server 195.142.160.181
\rProgress: [###] 100.00% 00:00:00 \
\nBackend desactivated""")
self.assertEqual(result.exit_code, 0)
def test_disable_backend_prompt(self):
args = ['webacc01', '--backend', '195.142.160.181']
result = self.invoke_with_exceptions(webacc.disable, args,
input='8080\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Please set a port for backends. \
If you want to different port for each backend, use `-b ip:port`: 8080
Desactivating backend on server 195.142.160.181
\rProgress: [###] 100.00% 00:00:00 \
\nBackend desactivated""")
self.assertEqual(result.exit_code, 0)
def test_add_vhost(self):
args = ['webacc01', '-v', 'pouet.iheartcli.com', '--zone-alter',
'--ssl']
result = self.invoke_with_exceptions(webacc.add, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
There is no certificate for pouet.iheartcli.com.
Create the certificate with (for exemple) :
$ gandi certificate create --cn pouet.iheartcli.com --type std \
\nOr relaunch the current command with --poll-cert option
Adding your virtual host (pouet.iheartcli.com) into webacc01
\rProgress: [###] 100.00% 00:00:00 \
\nYour virtual host habe been added""")
self.assertEqual(result.exit_code, 0)
params = self.api_calls['hosting.rproxy.vhost.create'][0][1]
self.assertEqual(params['vhost'], 'pouet.iheartcli.com')
self.assertEqual(params['zone_alter'], True)
def test_add_vhost_ssl_ok(self):
args = ['webacc01', '-v', 'pouet.lol.cat', '--zone-alter',
'--ssl']
result = self.invoke_with_exceptions(webacc.add, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Please give the private key for certificate id 710 (CN: lol.cat)""")
self.assertEqual(result.exit_code, 0)
def test_add_backend_prompt(self):
args = ['webacc01', '-b', '195.142.160.181']
result = self.invoke_with_exceptions(webacc.add, args,
input='80\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Please set a port for backends. \
If you want to different port for each backend, use `-b ip:port`: 80
Adding backend 195.142.160.181:80 into webaccelerator
\rProgress: [###] 100.00% 00:00:00 \
\nBackend added""")
self.assertEqual(result.exit_code, 0)
def test_add_backend_port_ok(self):
args = ['webacc01', '-b', '195.142.160.181', '--port', 9000]
result = self.invoke_with_exceptions(webacc.add, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Adding backend 195.142.160.181:9000 into webaccelerator
\rProgress: [###] 100.00% 00:00:00 \
\nBackend added""")
self.assertEqual(result.exit_code, 0)
def test_probe_test(self):
args = ['webacc01', '--window', 5,
'--timeout', 5, '--threshold', 3, '--interval', 10,
'--host', '95.142.160.181', '--url', '/',
'--http-method', 'GET', '--http-response', 200,
'--test']
result = self.invoke_with_exceptions(webacc.probe, args)
self.assertEqual(result.output.strip(), """\
status : 200
timeout : 1.0""")
self.assertEqual(result.exit_code, 0)
def test_probe_update(self):
args = ['webacc01', '--window', 5,
'--timeout', 5, '--threshold', 3, '--interval', 10,
'--host', '95.142.160.181', '--url', '/',
'--http-method', 'GET', '--http-response', 200]
result = self.invoke_with_exceptions(webacc.probe, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Progress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_vm.py 0000644 0001750 0001750 00000120671 13227371252 022724 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import re
import socket
from ..compat import mock
from ..fixtures.mocks import MockObject
from .base import CommandTestCase
from gandi.cli.commands import vm
from gandi.cli.core.base import GandiContextHelper
class VmTestCase(CommandTestCase):
mocks = [('gandi.cli.core.base.GandiModule.exec_output',
MockObject.exec_output)]
def test_list(self):
result = self.invoke_with_exceptions(vm.list, [])
self.assertEqual(result.output, """hostname : vm1426759833
state : running
----------
hostname : vm1426759844
state : running
----------
hostname : server01
state : running
----------
hostname : server02
state : halted
""")
self.assertEqual(result.exit_code, 0)
def test_list_id(self):
result = self.invoke_with_exceptions(vm.list, ['--id'])
self.assertEqual(result.output, """hostname : vm1426759833
state : running
id : 152966
----------
hostname : vm1426759844
state : running
id : 152964
----------
hostname : server01
state : running
id : 152967
----------
hostname : server02
state : halted
id : 152968
""")
self.assertEqual(result.exit_code, 0)
def test_list_filter_state(self):
result = self.invoke_with_exceptions(vm.list, ['--state', 'halted'])
self.assertEqual(result.output, """hostname : server02
state : halted
""")
self.assertEqual(result.exit_code, 0)
def test_list_filter_datacenter(self):
result = self.invoke_with_exceptions(vm.list, ['--datacenter', 'FR'])
self.assertEqual(result.output, """hostname : server01
state : running
----------
hostname : server02
state : halted
""")
self.assertEqual(result.exit_code, 0)
def test_info_ko_resource(self):
result = self.invoke_with_exceptions(vm.info, [])
self.assertEqual(result.exit_code, 2)
def test_info_ok_one_resource(self):
result = self.invoke_with_exceptions(vm.info, ['server01'])
self.assertEqual(result.output, """hostname : server01
state : running
cores : 1
memory : 256
console :
datacenter : FR-SD2
----------
bandwidth : 102400.0
ip4 : 95.142.160.181
ip6 : 2001:4b98:dc0:47:216:3eff:feb2:3862
label : Debian 7 64 bits (HVM)
kernel_version: 3.12-x86_64 (hvm)
name : sys_server01
size : 3072
""")
self.assertEqual(result.exit_code, 0)
def test_info_ok_multiple_resources(self):
args = ['server01', 'vm1426759833']
result = self.invoke_with_exceptions(vm.info, args)
self.assertEqual(result.output, """hostname : server01
state : running
cores : 1
memory : 256
console :
datacenter : FR-SD2
----------
bandwidth : 102400.0
ip4 : 95.142.160.181
ip6 : 2001:4b98:dc0:47:216:3eff:feb2:3862
label : Debian 7 64 bits (HVM)
kernel_version: 3.12-x86_64 (hvm)
name : sys_server01
size : 3072
----------
hostname : vm1426759833
state : running
cores : 1
memory : 256
console :
datacenter : LU-BI1
----------
bandwidth : 102400.0
ip6 : 2001:4b98:dc2:43:216:3eff:fece:e25f
label : Debian 7 64 bits (HVM)
kernel_version: 3.12-x86_64 (hvm)
name : sys_1426759833
size : 3072
""")
self.assertEqual(result.exit_code, 0)
def test_info_stat(self):
result = self.invoke_with_exceptions(vm.info, ['server01', '--stat'])
expected = u"""hostname : server01
state : running
cores : 1
memory : 256
console :
datacenter : FR-SD2
----------
bandwidth : 102400.0
ip4 : 95.142.160.181
ip6 : 2001:4b98:dc0:47:216:3eff:feb2:3862
label : Debian 7 64 bits (HVM)
kernel_version: 3.12-x86_64 (hvm)
name : sys_server01
size : 3072
vm network stats
in : ▁▁ ▂▁ ▁▁▁▂▂▁▁ ▉▃▁
out : ▁▃ ▉▂ ▆▆▁▃▄▁▁ ▁ ▁▉▅▇
disk network stats
read : ▁ ▁▁ ▁ ▉ ▁▃▁
write : ▁ ▁ ▂ ▉▁▁
"""
self.assertEqual(result.output, expected)
self.assertEqual(result.exit_code, 0)
def test_datacenters(self):
result = self.invoke_with_exceptions(vm.datacenters, [])
self.assertEqual(result.output, """\
iso : FR
name : Equinix Paris
country : France
dc_code : FR-SD2
closing on: 25/12/2017
----------
iso : US
name : Level3 Baltimore
country : United States of America
dc_code : US-BA1
closing on: 25/12/2016
closed for: vm, paas
----------
iso : LU
name : Bissen
country : Luxembourg
dc_code : LU-BI1
----------
iso : FR
name : France, Paris
country : France
dc_code : FR-SD3
closed for: paas
----------
iso : FR
name : France, Paris
country : France
dc_code : FR-SD5
closed for: paas
""")
self.assertEqual(result.exit_code, 0)
def test_datacenters_id(self):
result = self.invoke_with_exceptions(vm.datacenters, ['--id'])
self.assertEqual(result.output, """\
iso : FR
name : Equinix Paris
country : France
dc_code : FR-SD2
id : 1
closing on: 25/12/2017
----------
iso : US
name : Level3 Baltimore
country : United States of America
dc_code : US-BA1
id : 2
closing on: 25/12/2016
closed for: vm, paas
----------
iso : LU
name : Bissen
country : Luxembourg
dc_code : LU-BI1
id : 3
----------
iso : FR
name : France, Paris
country : France
dc_code : FR-SD3
id : 4
closed for: paas
----------
iso : FR
name : France, Paris
country : France
dc_code : FR-SD5
id : 5
closed for: paas
""")
self.assertEqual(result.exit_code, 0)
def test_kernels(self):
result = self.invoke_with_exceptions(vm.kernels, [])
linux_hvm_out = """\
flavor : linux-hvm
version : 3.12-x86_64 (hvm)
version : grub
version : raw
"""
linux_out_dc1 = """\
flavor : linux
version : 2.6.18 (deprecated)
version : 2.6.27-compat-sysfs (deprecated)
version : 2.6.32
version : 2.6.27 (deprecated)
version : 2.6.32-x86_64
version : 2.6.36 (deprecated)
version : 2.6.32-x86_64-grsec
version : 2.6.36-x86_64 (deprecated)
version : 3.2-i386
version : 3.2-x86_64
version : 3.2-x86_64-grsec
version : 3.10-x86_64
version : 3.10-i386
"""
linux_out_dc2 = """\
flavor : linux
version : 2.6.18 (deprecated)
version : 2.6.27-compat-sysfs (deprecated)
version : 2.6.32
version : 2.6.27 (deprecated)
version : 2.6.32-x86_64
version : 2.6.36 (deprecated)
version : 2.6.32-x86_64-grsec
version : 2.6.36-x86_64 (deprecated)
version : 3.2-i386
version : 3.2-x86_64
version : 3.2-x86_64-grsec
version : 3.10-x86_64
version : 3.10-i386
"""
linux_out_dc3 = """\
flavor : linux
version : 2.6.32
version : 2.6.27 (deprecated)
version : 2.6.32-x86_64
version : 2.6.32-x86_64-grsec
version : 3.2-i386
version : 3.2-x86_64
version : 3.2-x86_64-grsec
version : 3.10-x86_64
version : 3.10-i386
"""
self.assertTrue(linux_hvm_out in result.output)
self.assertTrue(linux_out_dc1 in result.output)
self.assertTrue(linux_out_dc2 in result.output)
self.assertTrue(linux_out_dc3 in result.output)
self.assertTrue('datacenter : Bissen' in result.output)
self.assertTrue('datacenter : Level3 Baltimore' in result.output)
self.assertTrue('datacenter : Equinix Paris' in result.output)
self.assertEqual(result.exit_code, 0)
def test_kernels_match(self):
result = self.invoke_with_exceptions(vm.kernels, ['3.10'])
linux_out = """\
----------
flavor : linux
version : 3.10-x86_64
version : 3.10-i386
"""
self.assertTrue(linux_out in result.output)
self.assertTrue('datacenter : Bissen' in result.output)
self.assertTrue('datacenter : Level3 Baltimore' in result.output)
self.assertTrue('datacenter : Equinix Paris' in result.output)
self.assertEqual(result.exit_code, 0)
def test_kernels_flavor(self):
args = ['--flavor', 'linux-hvm']
result = self.invoke_with_exceptions(vm.kernels, args)
self.assertEqual(result.output, """\
datacenter : Equinix Paris
----------
flavor : linux-hvm
version : 3.12-x86_64 (hvm)
version : grub
version : raw
datacenter : Level3 Baltimore
----------
flavor : linux-hvm
version : 3.12-x86_64 (hvm)
version : grub
version : raw
datacenter : Bissen
----------
flavor : linux-hvm
version : 3.12-x86_64 (hvm)
version : grub
version : raw
datacenter : France, Paris
----------
flavor : linux-hvm
version : 3.12-x86_64 (hvm)
version : grub
version : raw
datacenter : France, Paris
----------
flavor : linux-hvm
version : 3.12-x86_64 (hvm)
version : grub
version : raw
""")
self.assertEqual(result.exit_code, 0)
def test_kernels_datacenter(self):
args = ['--datacenter', 'LU']
result = self.invoke_with_exceptions(vm.kernels, args)
linux_out = """\
flavor : linux
version : 2.6.32
version : 2.6.27 (deprecated)
version : 2.6.32-x86_64
version : 2.6.32-x86_64-grsec
version : 3.2-i386
version : 3.2-x86_64
version : 3.2-x86_64-grsec
version : 3.10-x86_64
version : 3.10-i386
"""
linux_hvm_out = """\
flavor : linux-hvm
version : 3.12-x86_64 (hvm)
version : grub
version : raw
"""
self.assertTrue(linux_out in result.output)
self.assertTrue(linux_hvm_out in result.output)
self.assertTrue('datacenter : Bissen' in result.output)
self.assertEqual(result.exit_code, 0)
def test_kernels_vm(self):
result = self.invoke_with_exceptions(vm.kernels, ['--vm', 'server01'])
linux_out = """\
flavor : linux
version : 2.6.18 (deprecated)
version : 2.6.27-compat-sysfs (deprecated)
version : 2.6.32
version : 2.6.27 (deprecated)
version : 2.6.32-x86_64
version : 2.6.36 (deprecated)
version : 2.6.32-x86_64-grsec
version : 2.6.36-x86_64 (deprecated)
version : 3.2-i386
version : 3.2-x86_64
version : 3.2-x86_64-grsec
version : 3.10-x86_64
version : 3.10-i386
"""
linux_hvm_out = """\
flavor : linux-hvm
version : 3.12-x86_64 (hvm)
version : grub
version : raw
"""
self.assertTrue(linux_out in result.output)
self.assertTrue(linux_hvm_out in result.output)
self.assertTrue('datacenter : Equinix Paris' in result.output)
self.assertEqual(result.exit_code, 0)
def test_kernels_all(self):
args = ['--vm', 'server01', '--datacenter', 'FR',
'--flavor', 'linux-hvm', '3.12']
result = self.invoke_with_exceptions(vm.kernels, args)
self.assertEqual(result.output, """\
datacenter : Equinix Paris
----------
flavor : linux-hvm
version : 3.12-x86_64 (hvm)
""")
self.assertEqual(result.exit_code, 0)
def test_images(self):
self.maxDiff = None
result = self.invoke_with_exceptions(vm.images, [])
self.assertEqual(result.output, """\
label : Fedora 17 32 bits
os_arch : x86-32
kernel_version: 3.2-i386
disk_id : 527489
datacenter : LU-BI1
----------
label : Fedora 17 64 bits
os_arch : x86-64
kernel_version: 3.2-x86_64
disk_id : 527490
datacenter : LU-BI1
----------
label : OpenSUSE 12.2 32 bits
os_arch : x86-32
kernel_version: 3.2-i386
disk_id : 527491
datacenter : LU-BI1
----------
label : OpenSUSE 12.2 64 bits
os_arch : x86-64
kernel_version: 3.2-x86_64
disk_id : 527494
datacenter : LU-BI1
----------
label : CentOS 5 32 bits
os_arch : x86-32
kernel_version: 2.6.32
disk_id : 726224
datacenter : LU-BI1
----------
label : CentOS 5 64 bits
os_arch : x86-64
kernel_version: 2.6.32-x86_64
disk_id : 726225
datacenter : LU-BI1
----------
label : ArchLinux 32 bits
os_arch : x86-32
kernel_version: 3.2-i386
disk_id : 726230
datacenter : LU-BI1
----------
label : ArchLinux 64 bits
os_arch : x86-64
kernel_version: 3.2-x86_64
disk_id : 726233
datacenter : LU-BI1
----------
label : Debian 7 64 bits (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 1401491
datacenter : US-BA1
----------
label : Debian 7 64 bits (HVM) /!\ DEPRECATED
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 1349810
datacenter : FR-SD2
----------
label : Debian 7 64 bits (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 1401327
datacenter : LU-BI1
----------
label : Debian 8 (testing) 64 bits (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 3315704
datacenter : FR-SD2
----------
label : Debian 8 (testing) 64 bits (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 3315992
datacenter : US-BA1
----------
label : Debian 8
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 3316070
datacenter : FR-SD2
----------
label : Debian 8
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 3316070
datacenter : LU-BI1
----------
label : Debian 8
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 3316070
datacenter : FR-SD3
----------
label : Debian 8
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 3316070
datacenter : FR-SD5
----------
label : Debian 8 (testing) 64 bits (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 3316076
datacenter : LU-BI1
----------
label : Ubuntu 14.04 64 bits LTS (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 3315748
datacenter : FR-SD2
----------
label : Ubuntu 14.04 64 bits LTS (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 3316144
datacenter : US-BA1
----------
label : Ubuntu 14.04 64 bits LTS (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 3316160
datacenter : LU-BI1
----------
label : CentOS 7 64 bits (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 2876292
datacenter : FR-SD2
----------
label : CentOS 7 64 bits (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 4744388
datacenter : US-BA1
----------
label : CentOS 7 64 bits (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 4744392
datacenter : LU-BI1
----------
label : Debian 7 64 bits (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 1401492
datacenter : FR-SD3
----------
label : Debian 7 64 bits (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 1401492
datacenter : FR-SD5
----------
label : Debian 7 64 bits (HVM)
kernel_version: 3.12-x86_64 (hvm)
name : sys_1426759833
datacenter : LU-BI1
----------
label : Debian 7 64 bits (HVM)
kernel_version: 3.12-x86_64 (hvm)
name : sys_server01
datacenter : FR-SD2
----------
label :
kernel_version:
name : data
datacenter : FR-SD2
----------
label : Debian 7 64 bits
kernel_version: 3.2-x86_64
name : snaptest
datacenter : FR-SD2
----------
label : Debian 7 64 bits (HVM)
kernel_version: 3.12-x86_64 (hvm)
name : newdisk
datacenter : LU-BI1
""")
self.assertEqual(result.exit_code, 0)
def test_images_all(self):
args = ['Ubuntu 14.04', '--datacenter', 'LU']
result = self.invoke_with_exceptions(vm.images, args)
self.assertEqual(result.output, """\
label : Ubuntu 14.04 64 bits LTS (HVM)
os_arch : x86-64
kernel_version: 3.12-x86_64 (hvm)
disk_id : 3316160
datacenter : LU-BI1
""")
self.assertEqual(result.exit_code, 0)
def test_stop_one(self):
result = self.invoke_with_exceptions(vm.stop, ['server01'])
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Stopping your Virtual Machine(s) 'server01'.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_stop_multiple(self):
args = ['server01', 'vm1426759833']
result = self.invoke_with_exceptions(vm.stop, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Stopping your Virtual Machine(s) 'server01, vm1426759833'.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_stop_background(self):
result = self.invoke_with_exceptions(vm.stop, ['server01', '--bg'])
self.assertEqual(result.output, """\
id : 200
step : WAIT
""")
self.assertEqual(result.exit_code, 0)
def test_start_one(self):
result = self.invoke_with_exceptions(vm.start, ['server01'])
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Starting your Virtual Machine(s) 'server01'.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_start_multiple(self):
args = ['server01', 'vm1426759833']
result = self.invoke_with_exceptions(vm.start, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Starting your Virtual Machine(s) 'server01, vm1426759833'.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_start_background(self):
result = self.invoke_with_exceptions(vm.start, ['server01', '--bg'])
self.assertEqual(result.output, """\
id : 200
step : WAIT
""")
self.assertEqual(result.exit_code, 0)
def test_reboot_one(self):
result = self.invoke_with_exceptions(vm.reboot, ['server01'])
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Rebooting your Virtual Machine(s) 'server01'.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_reboot_multiple(self):
args = ['server01', 'vm1426759833']
result = self.invoke_with_exceptions(vm.reboot, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Rebooting your Virtual Machine(s) 'server01, vm1426759833'.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_reboot_background(self):
result = self.invoke_with_exceptions(vm.reboot, ['server01', '--bg'])
self.assertEqual(result.output, """\
id : 200
step : WAIT
""")
self.assertEqual(result.exit_code, 0)
def test_delete_prompt(self):
result = self.invoke_with_exceptions(vm.delete, ['server01'])
self.assertEqual(result.output.strip(), """\
Are you sure to delete Virtual Machine 'server01'? [y/N]:""")
self.assertEqual(result.exit_code, 0)
def test_delete_one(self):
result = self.invoke_with_exceptions(vm.delete, ['server01', '-f'])
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Stopping your Virtual Machine(s) 'server01'.
\rProgress: [###] 100.00% 00:00:00 \n\
Deleting your Virtual Machine(s) 'server01'.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_multiple(self):
args = ['server01', 'vm1426759833', '-f']
result = self.invoke_with_exceptions(vm.delete, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Stopping your Virtual Machine(s) 'server01'.
\rProgress: [###] 100.00% 00:00:00 \n\
Stopping your Virtual Machine(s) 'vm1426759833'.
\rProgress: [###] 100.00% 00:00:00 \n\
Deleting your Virtual Machine(s) 'server01, vm1426759833'.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_unknown(self):
result = self.invoke_with_exceptions(vm.delete, ['server100'])
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Sorry virtual machine server100 does not exist
Please use one of the following: ['vm1426759833', 'vm1426759844', 'server01', \
'server02', '152966', '152964', '152967', '152968']""")
self.assertEqual(result.exit_code, 0)
def test_delete_background_ko(self):
args = ['server01', '-f', '--bg']
result = self.invoke_with_exceptions(vm.delete, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Virtual machine not stopped, background option disabled
Stopping your Virtual Machine(s) 'server01'.
\rProgress: [###] 100.00% 00:00:00 \n\
Deleting your Virtual Machine(s) 'server01'.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_delete_background_ok(self):
args = ['server02', '-f', '--bg']
result = self.invoke_with_exceptions(vm.delete, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
id : 200
step : WAIT""")
self.assertEqual(result.exit_code, 0)
def test_update_ok(self):
args = ['server01', '--memory', '1024', '--cores', '4']
result = self.invoke_with_exceptions(vm.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your Virtual Machine server01.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_update_memory(self):
args = ['server01', '--memory', '10240', '--cores', '4']
result = self.invoke_with_exceptions(vm.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
memory update must be done offline.
reboot machine server01? [y/N]:""")
self.assertEqual(result.exit_code, 0)
def test_update_memory_reboot(self):
args = ['server01', '--memory', '10240', '--cores', '4', '--reboot']
result = self.invoke_with_exceptions(vm.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your Virtual Machine server01.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_update_background(self):
args = ['server01', '--memory', '1024', '--cores', '4', '--bg']
result = self.invoke_with_exceptions(vm.update, args)
self.assertEqual(result.output, """\
{'id': 200, 'step': 'WAIT'}
""")
self.assertEqual(result.exit_code, 0)
def test_update_password(self):
args = ['server01', '--password']
result = self.invoke_with_exceptions(vm.update, args,
input='plokiploki\nplokiploki\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
password: \nRepeat for confirmation: \nUpdating your Virtual Machine server01.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_update_console(self):
args = ['server01', '--console']
result = self.invoke_with_exceptions(vm.update, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Updating your Virtual Machine server01.
\rProgress: [###] 100.00% 00:00:00""")
self.assertEqual(result.exit_code, 0)
def test_console(self):
args = ['server01']
result = self.invoke_with_exceptions(vm.console, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
/!\\ Please be aware that if you didn\'t provide a password during creation, \
console service will be unavailable.
/!\\ You can use "gandi vm update" command to set a password.
/!\\ Use ~. ssh escape key to exit.
Updating your Virtual Machine server01.
\rProgress: [###] 100.00% 00:00:00 \n\
ssh 95.142.160.181@console.gandi.net""")
self.assertEqual(result.exit_code, 0)
def test_ssh(self):
args = ['admin@server01']
result = self.invoke_with_exceptions(vm.ssh, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Requesting access using: ssh admin@95.142.160.181 ...
ssh admin@95.142.160.181""")
self.assertEqual(result.exit_code, 0)
def test_ssh_wipe_key(self):
args = ['admin@server01', '--wipe-key']
with mock.patch('gandi.cli.modules.iaas.open',
create=True) as mock_open:
mock_open.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(vm.ssh, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Wiping old key and learning the new one
ssh-keygen -R "95.142.160.181"
Requesting access using: ssh admin@95.142.160.181 ...
ssh admin@95.142.160.181""")
self.assertEqual(result.exit_code, 0)
def test_ssh_wait(self):
args = ['server01', '--wait']
with mock.patch('gandi.cli.modules.iaas.socket',
create=True) as mock_socket:
mock_socket.return_value = mock.MagicMock(name='socket',
spec=socket.socket)
result = self.invoke_with_exceptions(vm.ssh, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Waiting for the vm to come online
Requesting access using: ssh root@95.142.160.181 ...
ssh root@95.142.160.181""")
self.assertEqual(result.exit_code, 0)
def test_ssh_login(self):
args = ['server01', '--login', 'joe']
result = self.invoke_with_exceptions(vm.ssh, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Requesting access using: ssh joe@95.142.160.181 ...
ssh joe@95.142.160.181""")
self.assertEqual(result.exit_code, 0)
def test_ssh_identity(self):
args = ['admin@server01', '-i', 'key.pub']
result = self.invoke_with_exceptions(vm.ssh, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Requesting access using: ssh -i key.pub admin@95.142.160.181 ...
ssh -i key.pub admin@95.142.160.181""")
self.assertEqual(result.exit_code, 0)
def test_ssh_args(self):
args = ['server01', 'sudo reboot']
result = self.invoke_with_exceptions(vm.ssh, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Requesting access using: ssh root@95.142.160.181 sudo reboot ...
ssh root@95.142.160.181 sudo reboot""")
self.assertEqual(result.exit_code, 0)
def test_create_default_hostname_ok(self):
args = ['--hostname', 'server500']
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper(),
input='plokiploki\nplokiploki\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
password: \nRepeat for confirmation: \n* root user will be created.
* Configuration used: 1 cores, 256Mb memory, ip v6, image Debian 8\
, hostname: server500, datacenter: FR-SD5
Creating your Virtual Machine server500.
\rProgress: [###] 100.00% 00:00:00 \n\
Your Virtual Machine server500 has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_default_ok(self):
args = []
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper(),
input='plokiploki\nplokiploki\n')
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(re.sub(r'vm\d+', 'vm', output), """\
password: \nRepeat for confirmation: \n* root user will be created.
* Configuration used: 1 cores, 256Mb memory, ip v6, image Debian 8\
, hostname: vm, datacenter: FR-SD5
Creating your Virtual Machine vm.
\rProgress: [###] 100.00% 00:00:00 \n\
Your Virtual Machine vm has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_ip_not_vlan_ko(self):
args = ['--hostname', 'server500', '--ip', '10.50.10.10']
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper(),
input='plokiploki\nplokiploki\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
password: \nRepeat for confirmation: \n\
--ip can't be used without --vlan.""")
self.assertEqual(result.exit_code, 0)
def test_create_vlan_ip_ok(self):
args = ['--hostname', 'server400', '--vlan', 'vlantest',
'--ip', '10.50.10.10']
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper(),
input='plokiploki\nplokiploki\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
password: \nRepeat for confirmation: \n* Private only ip vm (can't enable \
emergency web console access).
* root user will be created.
Creating your iface.
\rProgress: [###] 100.00% 00:00:00 \n\
Your iface has been created with the following IP addresses:
ip4:\t10.50.10.10
* Configuration used: 1 cores, 256Mb memory, ip private, image Debian 8\
, hostname: server400, datacenter: FR-SD5
Creating your Virtual Machine server400.
\rProgress: [###] 100.00% 00:00:00 \n\
Your Virtual Machine server400 has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_login_ok(self):
args = ['--login', 'administrator']
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper(),
input='plokiploki\nplokiploki\n')
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(re.sub(r'vm\d+', 'vm', output), """\
password: \nRepeat for confirmation: \n\
* root and administrator users will be created.
* Configuration used: 1 cores, 256Mb memory, ip v6, image Debian 8\
, hostname: vm, datacenter: FR-SD5
Creating your Virtual Machine vm.
\rProgress: [###] 100.00% 00:00:00 \n\
Your Virtual Machine vm has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_background_ok(self):
args = ['--hostname', 'server500', '--background']
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper(),
input='plokiploki\nplokiploki\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
password: \nRepeat for confirmation: \n* root user will be created.
* Configuration used: 1 cores, 256Mb memory, ip v6, image Debian 8\
, hostname: server500, datacenter: FR-SD5
* IAAS backend is now creating your VM and its associated resources in the \
background.""")
self.assertEqual(result.exit_code, 0)
def test_create_sshkey_ok(self):
args = ['--sshkey', 'mysecretkey']
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper())
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(re.sub(r'vm\d+', 'vm', output), """\
* root user will be created.
* SSH key authorization will be used.
* No password supplied for vm (required to enable emergency web console \
access).
* Configuration used: 1 cores, 256Mb memory, ip v6, image Debian 8\
, hostname: vm, datacenter: FR-SD5
Creating your Virtual Machine vm.
\rProgress: [###] 100.00% 00:00:00 \n\
Your Virtual Machine vm has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_gen_password_root_ok(self):
args = ['--gen-password']
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper())
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
output = re.sub(r'vm\d+', 'vm', output)
output = re.sub(r'with password .*', 'with password FAKEPASSWORD',
output)
self.assertEqual(output, """\
* root user will be created.
* User root setup with password FAKEPASSWORD
* Configuration used: 1 cores, 256Mb memory, ip v6, image Debian 8\
, hostname: vm, datacenter: FR-SD5
Creating your Virtual Machine vm.
\rProgress: [###] 100.00% 00:00:00 \n\
Your Virtual Machine vm has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_gen_password_user_ok(self):
args = ['--gen-password', '--login', 'myuser']
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper())
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
output = re.sub(r'vm\d+', 'vm', output)
output = re.sub(r'with password .*', 'with password FAKEPASSWORD',
output)
self.assertEqual(output, """\
* root and myuser users will be created.
* Users root and myuser setup with password FAKEPASSWORD
* Configuration used: 1 cores, 256Mb memory, ip v6, image Debian 8\
, hostname: vm, datacenter: FR-SD5
Creating your Virtual Machine vm.
\rProgress: [###] 100.00% 00:00:00 \n\
Your Virtual Machine vm has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_image_deprecated(self):
args = ['--image', 'Debian 7 64 bits (HVM)',
'--sshkey', 'mysecretkey',
'--datacenter', 'FR-SD2']
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper())
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(re.sub(r'vm\d+', 'vm', output), """\
/!\ Datacenter FR-SD2 will be closed on 25/12/2017, please consider \
using another datacenter.
/!\ Image Debian 7 64 bits (HVM) is deprecated and will soon be unavailable.
* root user will be created.
* SSH key authorization will be used.
* No password supplied for vm (required to enable emergency web console \
access).
* Configuration used: 1 cores, 256Mb memory, ip v6, image Debian 7 64 bits \
(HVM), hostname: vm, datacenter: FR-SD2
Creating your Virtual Machine vm.
\rProgress: [###] 100.00% 00:00:00 \n\
Your Virtual Machine vm has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_dc_code_ok(self):
args = ['--datacenter', 'FR-SD3']
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper(),
input='plokiploki\nplokiploki\n')
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(re.sub(r'vm\d+', 'vm', output), """\
password: \nRepeat for confirmation: \n* root user will be created.
* Configuration used: 1 cores, 256Mb memory, ip v6, image Debian 8\
, hostname: vm, datacenter: FR-SD3
Creating your Virtual Machine vm.
\rProgress: [###] 100.00% 00:00:00 \n\
Your Virtual Machine vm has been created.""")
self.assertEqual(result.exit_code, 0)
def test_create_datacenter_closed(self):
args = ['--datacenter', 'US-BA1']
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper(),
input='plokiploki\nplokiploki\n')
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(re.sub(r'vm\d+', 'vm', output), """\
Error: /!\ Datacenter US-BA1 is closed, please choose another datacenter.""")
self.assertEqual(result.exit_code, 1)
def test_create_datacenter_limited(self):
args = ['--datacenter', 'FR-SD2']
result = self.invoke_with_exceptions(vm.create, args,
obj=GandiContextHelper(),
input='plokiploki\nplokiploki\n')
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(re.sub(r'vm\d+', 'vm', output), """\
/!\ Datacenter FR-SD2 will be closed on 25/12/2017, please consider \
using another datacenter.
password: \nRepeat for confirmation: \n\
* root user will be created.
* Configuration used: 1 cores, 256Mb memory, ip v6, image Debian 8\
, hostname: vm, datacenter: FR-SD2
Creating your Virtual Machine vm.
\rProgress: [###] 100.00% 00:00:00 \n\
Your Virtual Machine vm has been created.""")
self.assertEqual(result.exit_code, 0)
def test_migrate_not_available(self):
args = ['vm1426759844']
result = self.invoke_with_exceptions(vm.migrate, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Your VM vm1426759844 cannot be migrated yet. \
Migration will be available when datacenter FR-SD5 is opened.""")
self.assertEqual(result.exit_code, 0)
def test_migrate_ok(self):
args = ['server02', '-f']
result = self.invoke_with_exceptions(vm.migrate, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
* Starting the migration of VM server02 from datacenter FR-SD2 to LU-BI1
VM migration in progress.
\rProgress: [###] 100.00% 00:00:00 \n\
Your VM server02 has been migrated.""")
self.assertEqual(result.exit_code, 0)
def test_migrate_noconfirm(self):
args = ['server02']
result = self.invoke_with_exceptions(vm.migrate, args, input='\n')
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Are you sure you want to migrate VM server02 ? [y/N]:""")
self.assertEqual(result.exit_code, 0)
def test_migrate_background(self):
args = ['server02', '--bg', '-f']
result = self.invoke_with_exceptions(vm.migrate, args)
self.assertEqual(result.output.strip(), """\
* Starting the migration of VM server02 from datacenter FR-SD2 to LU-BI1
id : 9900
step : WAIT""")
self.assertEqual(result.exit_code, 0)
def test_migrate_finalize_not_found(self):
args = ['server02', '-f', '--finalize']
result = self.invoke_with_exceptions(vm.migrate, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Error: Cannot find VM server02 migration operation.""")
self.assertEqual(result.exit_code, 1)
def test_migrate_finalize_not_needed(self):
args = ['server01', '-f', '--finalize']
result = self.invoke_with_exceptions(vm.migrate, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
Error: VM server01 migration does not need finalization.""")
self.assertEqual(result.exit_code, 1)
def test_migrate_finalize_ok(self):
args = ['vm1426759833', '-f', '--finalize']
result = self.invoke_with_exceptions(vm.migrate, args)
self.assertEqual(re.sub(r'\[#+\]', '[###]',
result.output.strip()), """\
* Finalizing the migration of VM vm1426759833 from datacenter FR-SD2 to LU-BI1
VM migration in progress.
\rProgress: [###] 100.00% 00:00:00 \n\
Your VM vm1426759833 has been migrated.""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_certificate.py 0000644 0001750 0001750 00000071107 13120224646 024557 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
import re
from ..compat import mock
from .base import CommandTestCase
from gandi.cli.commands import certificate
class CertTestCase(CommandTestCase):
def test_packages(self):
result = self.invoke_with_exceptions(certificate.packages, [])
wanted = ("""/!\ "gandi certificate packages" is deprecated.
Please use "gandi certificate plans".
Description | Name | Max altnames | Type
-----------------------+--------------------+--------------+-----
Standard Single Domain | cert_std_1_0_0 | 1 | std \
\nStandard Wildcard | cert_std_w_0_0 | 1 | std \
\nStandard Multi Domain | cert_std_3_0_0 | 3 | std \
\nStandard Multi Domain | cert_std_5_0_0 | 5 | std \
\nStandard Multi Domain | cert_std_10_0_0 | 10 | std \
\nStandard Multi Domain | cert_std_20_0_0 | 20 | std \
\nPro Single Domain | cert_pro_1_10_0 | 1 | pro \
\nPro Single Domain | cert_pro_1_100_0 | 1 | pro \
\nPro Single Domain | cert_pro_1_100_SGC | 1 | pro \
\nPro Single Domain | cert_pro_1_250_0 | 1 | pro \
\nPro Wildcard | cert_pro_w_250_0 | 1 | pro \
\nPro Wildcard | cert_pro_w_250_SGC | 1 | pro \
\nBusiness Single Domain | cert_bus_1_250_0 | 1 | bus \
\nBusiness Single Domain | cert_bus_1_250_SGC | 1 | bus \
\nBusiness Multi Domain | cert_bus_3_250_0 | 3 | bus \
\nBusiness Multi Domain | cert_bus_5_250_0 | 5 | bus \
\nBusiness Multi Domain | cert_bus_10_250_0 | 10 | bus \
\nBusiness Multi Domain | cert_bus_20_250_0 | 20 | bus \
\n""") # noqa
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_plans(self):
result = self.invoke_with_exceptions(certificate.plans, [])
wanted = ("""\
Description | Max altnames | Type | Warranty
-----------------------+--------------+------+---------
Standard Single Domain | 1 | std | 0 \
\nStandard Wildcard | 1 | std | 0 \
\nStandard Multi Domain | 3 | std | 0 \
\nStandard Multi Domain | 5 | std | 0 \
\nStandard Multi Domain | 10 | std | 0 \
\nStandard Multi Domain | 20 | std | 0 \
\nPro Single Domain | 1 | pro | 10,000 \
\nPro Single Domain | 1 | pro | 100,000 \
\nPro Single Domain | 1 | pro | 100,000 \
\nPro Single Domain | 1 | pro | 250,000 \
\nPro Wildcard | 1 | pro | 250,000 \
\nPro Wildcard | 1 | pro | 250,000 \
\nBusiness Single Domain | 1 | bus | 250,000 \
\nBusiness Single Domain | 1 | bus | 250,000 \
\nBusiness Multi Domain | 3 | bus | 250,000 \
\nBusiness Multi Domain | 5 | bus | 250,000 \
\nBusiness Multi Domain | 10 | bus | 250,000 \
\nBusiness Multi Domain | 20 | bus | 250,000 \
\n""") # noqa
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_list(self):
result = self.invoke_with_exceptions(certificate.list, [])
self.assertEqual(result.output, """\
cn : mydomain.name
plan : Standard Single Domain
----------
cn : bew.web
plan : Standard Single Domain
----------
cn : iheartcli.com
plan : Pro Single Domain
----------
cn : cat.lol
plan : Pro Single Domain
----------
cn : iheartcli.com
plan : Business Single Domain
----------
cn : inter.net
plan : Business Multi Domain
----------
cn : lol.cat
plan : Standard Single Domain
""")
self.assertEqual(result.exit_code, 0)
def test_list_all(self):
args = ['--id', '--status', '--dates', '--altnames', '--csr',
'--cert']
result = self.invoke_with_exceptions(certificate.list, args)
self.assertEqual(result.output, """\
cn : mydomain.name
plan : Standard Single Domain
id : 701
status : pending
date_created : 20140904T14:06:26
date_end :
csr : -----BEGIN CERTIFICATE REQUEST-----
MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX...K+I=
-----END CERTIFICATE REQUEST-----
----------
cn : bew.web
plan : Standard Single Domain
id : 771
status : pending
date_created : 20140904T14:06:26
date_end :
csr : -----BEGIN CERTIFICATE REQUEST-----
MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX...K+I=
-----END CERTIFICATE REQUEST-----
----------
cn : iheartcli.com
plan : Pro Single Domain
id : 709
status : valid
date_created : 20140904T14:06:26
date_end :
csr : -----BEGIN CERTIFICATE REQUEST-----
MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX...K+I=
-----END CERTIFICATE REQUEST-----
----------
cn : cat.lol
plan : Pro Single Domain
id : 709
status : valid
date_created : 20140904T14:06:26
date_end :
csr : -----BEGIN CERTIFICATE REQUEST-----
MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX...K+I=
-----END CERTIFICATE REQUEST-----
----------
cn : iheartcli.com
plan : Business Single Domain
id : 769
status : valid
date_created : 20140904T14:06:26
date_end :
csr : -----BEGIN CERTIFICATE REQUEST-----
MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX...K+I=
-----END CERTIFICATE REQUEST-----
----------
cn : inter.net
plan : Business Multi Domain
id : 706
status : valid
date_created : 20140904T14:06:26
date_end :
csr : -----BEGIN CERTIFICATE REQUEST-----
MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX...K+I=
-----END CERTIFICATE REQUEST-----
----------
cn : lol.cat
plan : Standard Single Domain
id : 710
status : valid
date_created : 20150318T00:00:00
date_end : 20160318T00:00:00
csr : -----BEGIN CERTIFICATE REQUEST-----
MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX...K+I=
-----END CERTIFICATE REQUEST-----
cert : \
\n-----BEGIN CERTIFICATE-----
MIIE5zCCA8+gAwIBAgIQAkC4TU9JG8wqhf4FCrsNGTANBgkqhkiG9w0BAQsFADBf
MQswCQYDVQQGEwJGUjEOMAwGA1UECBMFUGFyaXMxDj...tU6XzbS6/s2D1/N1wWO
OCD/V3XAROtKr1a0mtJ8n7SZyzr0j3weRbN7nV24RDQ6d4+GHy5zZstKyDrTknlu
yyZuDAAYAQJ+nrL5p1gxVNwj1f5XKFk=
-----END CERTIFICATE-----
altname : pouet.lol.cat
""")
self.assertEqual(result.exit_code, 0)
def test_info(self):
args = ['inter.net', 'bew.web']
result = self.invoke_with_exceptions(certificate.info, args)
self.assertEqual(result.output, """cn : inter.net
date_created : 20140904T14:06:26
date_end :
plan : Business Multi Domain
status : valid
----------
cn : bew.web
date_created : 20140904T14:06:26
date_end :
plan : Standard Single Domain
status : pending
""")
self.assertEqual(result.exit_code, 0)
def test_info_all(self):
args = ['inter.net', '--id', '--altnames', '--csr', '--cert']
result = self.invoke_with_exceptions(certificate.info, args)
self.assertEqual(result.output, """cn : inter.net
date_created : 20140904T14:06:26
date_end :
plan : Business Multi Domain
status : valid
id : 706
csr : -----BEGIN CERTIFICATE REQUEST-----
MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX...K+I=
-----END CERTIFICATE REQUEST-----
""")
self.assertEqual(result.exit_code, 0)
def test_create(self):
csr = '''-----BEGIN CERTIFICATE REQUEST-----
MIICWjCCAUICAQAwFTETMBEGA1UEAwwKZG9tYWluLnRsZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAKYPfDoiWuWDwJb+fZhOHA++9yYy1BbxnY729hSd
/P12kw1HeIL5CGIhZLpJrwRQmLPTlJ0VttFaqpNm7mEISr+GMJzEWBTyD8750hbW
bXwZBcsWi8AsOsnT+sh/cTKGlJctA346HKU3tLlZsvI4ecfnlIZk5Yefgf+78abz
SzSV47gPDUNQvGIzP9QPE4bEFu5NjdxPg3ylaQ5cv8iiWHn4iUCRXlxxNfHmH7xE
ysFlsD6KnKjR5eYLKBcATeqopGPi72KlcDn5lmtdWsd9aGSl5KlkKQC497buqjbr
H31lMAGAC7At6S7AF5GIT5KGjN6KyPrzUOn7FrhNUcnpUQMCAwEAAaAAMA0GCSqG
SIb3DQEBCwUAA4IBAQCBM6wc9DfsI1htRhAz7/RfOIn7kb6LygOSEgfb757My+60
N/WP9ndpmob0PW18B1vXBloZEkO/aNTXCGAIPJXRkeTYVhEE2B7K3pc9IiNmLxXC
3b2cwUjgmNw9wmFZ4AuHqzWHevqix3m7Acpkl5ugcCsTVOX3mx84MSguSC+5AWfm
DG0VmOWZ0tWjyZuKgtoXgHnH3whEac+pM7M3J+z94/msO9hnpUOQNt4XALEoONrv
+xE1FDGhRJAx9AYOtTBQSFLqKB4D6W2hhDVLirxQuJ/lC/l8tyEu96ggfDRrMXE4
v0L9Vc0443fop+UbFCabF0NWM6rJ31Nlv7s3mQIA
-----END CERTIFICATE REQUEST-----'''
result = self.invoke_with_exceptions(certificate.create,
['--csr', csr,
'--duration', 2,
'--max-altname', '5',
])
wanted = '''The certificate create operation is 1
You can follow it with:
$ gandi certificate follow 1
When the operation is DONE, you can retrieve the .crt with:
$ gandi certificate export "domain.tld"
'''
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_create_package_deprecated(self):
csr = '''-----BEGIN CERTIFICATE REQUEST-----
MIICWjCCAUICAQAwFTETMBEGA1UEAwwKZG9tYWluLnRsZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAKYPfDoiWuWDwJb+fZhOHA++9yYy1BbxnY729hSd
/P12kw1HeIL5CGIhZLpJrwRQmLPTlJ0VttFaqpNm7mEISr+GMJzEWBTyD8750hbW
bXwZBcsWi8AsOsnT+sh/cTKGlJctA346HKU3tLlZsvI4ecfnlIZk5Yefgf+78abz
SzSV47gPDUNQvGIzP9QPE4bEFu5NjdxPg3ylaQ5cv8iiWHn4iUCRXlxxNfHmH7xE
ysFlsD6KnKjR5eYLKBcATeqopGPi72KlcDn5lmtdWsd9aGSl5KlkKQC497buqjbr
H31lMAGAC7At6S7AF5GIT5KGjN6KyPrzUOn7FrhNUcnpUQMCAwEAAaAAMA0GCSqG
SIb3DQEBCwUAA4IBAQCBM6wc9DfsI1htRhAz7/RfOIn7kb6LygOSEgfb757My+60
N/WP9ndpmob0PW18B1vXBloZEkO/aNTXCGAIPJXRkeTYVhEE2B7K3pc9IiNmLxXC
3b2cwUjgmNw9wmFZ4AuHqzWHevqix3m7Acpkl5ugcCsTVOX3mx84MSguSC+5AWfm
DG0VmOWZ0tWjyZuKgtoXgHnH3whEac+pM7M3J+z94/msO9hnpUOQNt4XALEoONrv
+xE1FDGhRJAx9AYOtTBQSFLqKB4D6W2hhDVLirxQuJ/lC/l8tyEu96ggfDRrMXE4
v0L9Vc0443fop+UbFCabF0NWM6rJ31Nlv7s3mQIA
-----END CERTIFICATE REQUEST-----'''
result = self.invoke_with_exceptions(certificate.create,
['--csr', csr,
'--duration', 2,
'--package', 'cert_std_1_0_0',
])
wanted = '''\
/!\\ Using --package is deprecated, please replace it by --type (in std, pro \
or bus) and --max-altname to set the max number of altnames.
The certificate create operation is 1
You can follow it with:
$ gandi certificate follow 1
When the operation is DONE, you can retrieve the .crt with:
$ gandi certificate export "domain.tld"
'''
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_create_no_csr(self):
result = self.invoke_with_exceptions(certificate.create,
['--duration', 2,
'--max-altname', '5',
])
wanted = """You need a CSR or a CN to create a certificate.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_create_bad_csr(self):
args = ['--csr', 'badpath.csr', '--pk', 'badpath.key',
'--package', 'cert_std_3_0_0', '--dcv-method', 'email']
result = self.invoke_with_exceptions(certificate.create, args)
wanted = """Unable to parse provided csr: badpath.csr
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_create_no_package_and_option(self):
csr = '''-----BEGIN CERTIFICATE REQUEST-----
MIICWjCCAUICAQAwFTETMBEGA1UEAwwKZG9tYWluLnRsZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAKYPfDoiWuWDwJb+fZhOHA++9yYy1BbxnY729hSd
/P12kw1HeIL5CGIhZLpJrwRQmLPTlJ0VttFaqpNm7mEISr+GMJzEWBTyD8750hbW
bXwZBcsWi8AsOsnT+sh/cTKGlJctA346HKU3tLlZsvI4ecfnlIZk5Yefgf+78abz
SzSV47gPDUNQvGIzP9QPE4bEFu5NjdxPg3ylaQ5cv8iiWHn4iUCRXlxxNfHmH7xE
ysFlsD6KnKjR5eYLKBcATeqopGPi72KlcDn5lmtdWsd9aGSl5KlkKQC497buqjbr
H31lMAGAC7At6S7AF5GIT5KGjN6KyPrzUOn7FrhNUcnpUQMCAwEAAaAAMA0GCSqG
SIb3DQEBCwUAA4IBAQCBM6wc9DfsI1htRhAz7/RfOIn7kb6LygOSEgfb757My+60
N/WP9ndpmob0PW18B1vXBloZEkO/aNTXCGAIPJXRkeTYVhEE2B7K3pc9IiNmLxXC
3b2cwUjgmNw9wmFZ4AuHqzWHevqix3m7Acpkl5ugcCsTVOX3mx84MSguSC+5AWfm
DG0VmOWZ0tWjyZuKgtoXgHnH3whEac+pM7M3J+z94/msO9hnpUOQNt4XALEoONrv
+xE1FDGhRJAx9AYOtTBQSFLqKB4D6W2hhDVLirxQuJ/lC/l8tyEu96ggfDRrMXE4
v0L9Vc0443fop+UbFCabF0NWM6rJ31Nlv7s3mQIA
-----END CERTIFICATE REQUEST-----'''
result = self.invoke_with_exceptions(certificate.create,
['--csr', csr,
'--duration', 2,
'--max-altname', '5',
'--package', 'cert_std_1_0_0',
])
wanted = """Please do not use --package at the same time you use \
--type, --max-altname or --warranty.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_create_warranty(self):
csr = '''-----BEGIN CERTIFICATE REQUEST-----
MIICWjCCAUICAQAwFTETMBEGA1UEAwwKZG9tYWluLnRsZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAKYPfDoiWuWDwJb+fZhOHA++9yYy1BbxnY729hSd
/P12kw1HeIL5CGIhZLpJrwRQmLPTlJ0VttFaqpNm7mEISr+GMJzEWBTyD8750hbW
bXwZBcsWi8AsOsnT+sh/cTKGlJctA346HKU3tLlZsvI4ecfnlIZk5Yefgf+78abz
SzSV47gPDUNQvGIzP9QPE4bEFu5NjdxPg3ylaQ5cv8iiWHn4iUCRXlxxNfHmH7xE
ysFlsD6KnKjR5eYLKBcATeqopGPi72KlcDn5lmtdWsd9aGSl5KlkKQC497buqjbr
H31lMAGAC7At6S7AF5GIT5KGjN6KyPrzUOn7FrhNUcnpUQMCAwEAAaAAMA0GCSqG
SIb3DQEBCwUAA4IBAQCBM6wc9DfsI1htRhAz7/RfOIn7kb6LygOSEgfb757My+60
N/WP9ndpmob0PW18B1vXBloZEkO/aNTXCGAIPJXRkeTYVhEE2B7K3pc9IiNmLxXC
3b2cwUjgmNw9wmFZ4AuHqzWHevqix3m7Acpkl5ugcCsTVOX3mx84MSguSC+5AWfm
DG0VmOWZ0tWjyZuKgtoXgHnH3whEac+pM7M3J+z94/msO9hnpUOQNt4XALEoONrv
+xE1FDGhRJAx9AYOtTBQSFLqKB4D6W2hhDVLirxQuJ/lC/l8tyEu96ggfDRrMXE4
v0L9Vc0443fop+UbFCabF0NWM6rJ31Nlv7s3mQIA
-----END CERTIFICATE REQUEST-----'''
result = self.invoke_with_exceptions(certificate.create,
['--csr', csr,
'--duration', 2,
'--max-altname', '5',
'--type', 'std',
'--warranty', '250',
])
wanted = """The warranty can only be specified for pro certificates.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_create_no_package(self):
csr = '''-----BEGIN CERTIFICATE REQUEST-----
MIICWjCCAUICAQAwFTETMBEGA1UEAwwKZG9tYWluLnRsZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAKYPfDoiWuWDwJb+fZhOHA++9yYy1BbxnY729hSd
/P12kw1HeIL5CGIhZLpJrwRQmLPTlJ0VttFaqpNm7mEISr+GMJzEWBTyD8750hbW
bXwZBcsWi8AsOsnT+sh/cTKGlJctA346HKU3tLlZsvI4ecfnlIZk5Yefgf+78abz
SzSV47gPDUNQvGIzP9QPE4bEFu5NjdxPg3ylaQ5cv8iiWHn4iUCRXlxxNfHmH7xE
ysFlsD6KnKjR5eYLKBcATeqopGPi72KlcDn5lmtdWsd9aGSl5KlkKQC497buqjbr
H31lMAGAC7At6S7AF5GIT5KGjN6KyPrzUOn7FrhNUcnpUQMCAwEAAaAAMA0GCSqG
SIb3DQEBCwUAA4IBAQCBM6wc9DfsI1htRhAz7/RfOIn7kb6LygOSEgfb757My+60
N/WP9ndpmob0PW18B1vXBloZEkO/aNTXCGAIPJXRkeTYVhEE2B7K3pc9IiNmLxXC
3b2cwUjgmNw9wmFZ4AuHqzWHevqix3m7Acpkl5ugcCsTVOX3mx84MSguSC+5AWfm
DG0VmOWZ0tWjyZuKgtoXgHnH3whEac+pM7M3J+z94/msO9hnpUOQNt4XALEoONrv
+xE1FDGhRJAx9AYOtTBQSFLqKB4D6W2hhDVLirxQuJ/lC/l8tyEu96ggfDRrMXE4
v0L9Vc0443fop+UbFCabF0NWM6rJ31Nlv7s3mQIA
-----END CERTIFICATE REQUEST-----'''
result = self.invoke_with_exceptions(certificate.create,
['--csr', csr,
'--duration', 2,
'--max-altname', '5',
'--cn', '*.lol.cat',
'--altnames', 'pouet.lol.cat',
'--altnames', 'grumpf.lol.cat',
])
wanted = """\
You can't have a wildcard with multidomain certificate.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_create_csr_empty(self):
args = ['--csr', 'sandbox/example.txt',
'--duration', 2,
'--max-altname', '5',
'--cn', '*.lol.cat',
'--altnames', 'pouet.lol.cat',
'--altnames', 'grumpf.lol.cat',
]
result = self.isolated_invoke_with_exceptions(certificate.create,
args,
temp_content='')
self.assertEqual(result.output, '')
self.assertEqual(result.exit_code, 0)
def test_create_wild_altnames(self):
csr = '''-----BEGIN CERTIFICATE REQUEST-----
MIICWjCCAUICAQAwFTETMBEGA1UEAwwKZG9tYWluLnRsZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAKYPfDoiWuWDwJb+fZhOHA++9yYy1BbxnY729hSd
/P12kw1HeIL5CGIhZLpJrwRQmLPTlJ0VttFaqpNm7mEISr+GMJzEWBTyD8750hbW
bXwZBcsWi8AsOsnT+sh/cTKGlJctA346HKU3tLlZsvI4ecfnlIZk5Yefgf+78abz
SzSV47gPDUNQvGIzP9QPE4bEFu5NjdxPg3ylaQ5cv8iiWHn4iUCRXlxxNfHmH7xE
ysFlsD6KnKjR5eYLKBcATeqopGPi72KlcDn5lmtdWsd9aGSl5KlkKQC497buqjbr
H31lMAGAC7At6S7AF5GIT5KGjN6KyPrzUOn7FrhNUcnpUQMCAwEAAaAAMA0GCSqG
SIb3DQEBCwUAA4IBAQCBM6wc9DfsI1htRhAz7/RfOIn7kb6LygOSEgfb757My+60
N/WP9ndpmob0PW18B1vXBloZEkO/aNTXCGAIPJXRkeTYVhEE2B7K3pc9IiNmLxXC
3b2cwUjgmNw9wmFZ4AuHqzWHevqix3m7Acpkl5ugcCsTVOX3mx84MSguSC+5AWfm
DG0VmOWZ0tWjyZuKgtoXgHnH3whEac+pM7M3J+z94/msO9hnpUOQNt4XALEoONrv
+xE1FDGhRJAx9AYOtTBQSFLqKB4D6W2hhDVLirxQuJ/lC/l8tyEu96ggfDRrMXE4
v0L9Vc0443fop+UbFCabF0NWM6rJ31Nlv7s3mQIA
-----END CERTIFICATE REQUEST-----'''
result = self.invoke_with_exceptions(certificate.create,
['--csr', csr,
'--duration', 2,
'--max-altname', '5',
'--type', 'pro',
'--warranty', '250',
])
wanted = """\
Can't find any plan with your params.
Please call : "gandi certificate plans".
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_export_ok(self):
args = ['lol.cat']
with mock.patch('gandi.cli.commands.certificate.open',
create=True) as mock_open:
mock_open.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(certificate.export, args)
wanted = """wrote lol.cat.crt
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_export_no_cert(self):
args = ['bew.web']
with mock.patch('gandi.cli.commands.certificate.open',
create=True) as mock_open:
mock_open.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(certificate.export, args)
self.assertEqual(result.output, '')
self.assertEqual(result.exit_code, 0)
def test_export_invalid(self):
args = ['mydomain.name']
with mock.patch('gandi.cli.commands.certificate.open',
create=True) as mock_open:
mock_open.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(certificate.export, args)
self.assertEqual(result.output, """\
The certificate must be in valid status to be exported (701).
""")
self.assertEqual(result.exit_code, 0)
def test_export_multiple_ok(self):
args = ['lol.cat', 'inter.net', '-o', 'pouet.crt']
with mock.patch('gandi.cli.commands.certificate.open',
create=True) as mock_open:
mock_open.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(certificate.export, args)
wanted = """\
Too many certs found, you must specify which cert you want to export
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_export_exists(self):
args = ['lol.cat']
with mock.patch('gandi.cli.commands.certificate.os.path.isfile',
create=True) as mock_isfile:
mock_isfile.return_value = True
result = self.invoke_with_exceptions(certificate.export, args)
wanted = """The file lol.cat.crt already exists.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_export_nothing(self):
args = ['inter.net']
result = self.invoke_with_exceptions(certificate.export, args)
self.assertEqual(result.output, '')
self.assertEqual(result.exit_code, 0)
def test_export_intermediate_business_ok(self):
args = ['inter.net', '-i']
with mock.patch('gandi.cli.commands.certificate.open',
create=True) as mock_open:
mock_open.return_value = mock.MagicMock()
with mock.patch('gandi.cli.commands.certificate.requests.get',
create=True) as mock_get:
mock_get.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(certificate.export, args)
wanted = """Business certs do not need intermediates.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_export_intermediate_ok(self):
args = ['lol.cat', '-i']
with mock.patch('gandi.cli.commands.certificate.open',
create=True) as mock_open:
mock_open.return_value = mock.MagicMock()
with mock.patch('gandi.cli.commands.certificate.requests.get',
create=True) as mock_get:
mock_get.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(certificate.export, args)
wanted = """wrote lol.cat.crt
wrote lol.cat.inter.crt
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_export_intermediate_sgc_ok(self):
args = ['cat.lol', '-i']
with mock.patch('gandi.cli.commands.certificate.open',
create=True) as mock_open:
mock_open.return_value = mock.MagicMock()
with mock.patch('gandi.cli.commands.certificate.requests.get',
create=True) as mock_get:
mock_get.return_value = mock.MagicMock()
result = self.invoke_with_exceptions(certificate.export, args)
wanted = """wrote cat.lol.inter.crt
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_update(self):
args = ['inter.net']
result = self.invoke_with_exceptions(certificate.update, args)
wanted = """\
openssl req -new -newkey rsa:2048 -sha256 -nodes -out inter.net.csr \
-keyout inter.net.key -subj "/CN=inter.net"
The certificate update operation is 400
You can follow it with:
$ gandi certificate follow 400
When the operation is DONE, you can retrieve the .crt with:
$ gandi certificate export "inter.net"
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_update_multi(self):
args = ['iheartcli.com']
result = self.invoke_with_exceptions(certificate.update, args)
wanted = """\
Will not update, iheartcli.com is not precise enough.
* cert : 709
* cert : 769
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_follow(self):
args = ['600']
result = self.invoke_with_exceptions(certificate.follow, args)
wanted = """\
type : certificate_update
step : DONE
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_change_dcv(self):
args = ['lol.cat', '--dcv-method', 'dns']
result = self.invoke_with_exceptions(certificate.change_dcv, args)
wanted = """\
You have to add these records in your domain zone :
920F78CCE11DA7D9554.lol.cat. 10800 IN CNAME AD6A9D35FF5BD9FB03A41.comodoca.com.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_change_dcv_not_valid(self):
args = ['mydomain.name', '--dcv-method', 'dns']
result = self.invoke_with_exceptions(certificate.change_dcv, args)
wanted = """\
This certificate operation is not in the good step to update the DCV method.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_change_dcv_multi(self):
args = ['iheartcli.com', '--dcv-method', 'dns']
result = self.invoke_with_exceptions(certificate.change_dcv, args)
wanted = """\
Will not update, iheartcli.com is not precise enough.
* cert : 709
* cert : 769
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_change_dcv_no_oper(self):
args = ['cat.lol', '--dcv-method', 'dns']
result = self.invoke_with_exceptions(certificate.change_dcv, args)
wanted = """\
Can not find any operation for this certificate.
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_resend_dcv(self):
args = ['lol.cat']
result = self.invoke_with_exceptions(certificate.resend_dcv, args)
self.assertEqual(result.output, '')
self.assertEqual(result.exit_code, 0)
def test_resend_dcv_multi(self):
args = ['iheartcli.com']
result = self.invoke_with_exceptions(certificate.resend_dcv, args)
wanted = """\
Will not update, iheartcli.com is not precise enough.
* cert : 709
* cert : 769
"""
self.assertEqual(result.output, wanted)
self.assertEqual(result.exit_code, 0)
def test_resend_dcv_no_oper(self):
args = ['cat.lol']
result = self.invoke_with_exceptions(certificate.resend_dcv, args)
self.assertEqual(result.output, """\
Can not find any operation for this certificate.
""")
self.assertEqual(result.exit_code, 0)
def test_resend_dcv_not_email(self):
args = ['inter.net']
result = self.invoke_with_exceptions(certificate.resend_dcv, args)
self.assertEqual(result.output, """\
This certificate operation is not in email DCV.
""")
self.assertEqual(result.exit_code, 0)
def test_resend_dcv_not_valid(self):
args = ['mydomain.name']
result = self.invoke_with_exceptions(certificate.resend_dcv, args)
self.assertEqual(result.output, """\
This certificate operation is not in the good step to resend the DCV.
""")
self.assertEqual(result.exit_code, 0)
def test_delete_force(self):
args = ['lol.cat', '--force']
result = self.invoke_with_exceptions(certificate.delete, args)
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
Deleting your certificate.
\rProgress: [###] 100.00% 00:00:00 \
\nYour certificate 710 has been deleted.""")
self.assertEqual(result.exit_code, 0)
def test_delete_multiple(self):
args = ['iheartcli.com', '--force']
result = self.invoke_with_exceptions(certificate.delete, args)
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
Will not delete, iheartcli.com is not precise enough.
* cert : 709
* cert : 769""")
self.assertEqual(result.exit_code, 0)
def test_delete_prompt_ok(self):
args = ['lol.cat']
result = self.invoke_with_exceptions(certificate.delete, args,
input='y\n')
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
Are you sure to delete the certificate lol.cat? [y/N]: y
Deleting your certificate.
\rProgress: [###] 100.00% 00:00:00 \
\nYour certificate 710 has been deleted.""")
self.assertEqual(result.exit_code, 0)
def test_delete_prompt_ko(self):
args = ['lol.cat']
result = self.invoke_with_exceptions(certificate.delete, args,
input='N\n')
output = re.sub(r'\[#+\]', '[###]', result.output.strip())
self.assertEqual(output, """\
Are you sure to delete the certificate lol.cat? [y/N]: N""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/commands/test_account.py 0000644 0001750 0001750 00000001012 12746404125 023722 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
from .base import CommandTestCase
from gandi.cli.commands import account
class AccountTestCase(CommandTestCase):
def test_info(self):
result = self.invoke_with_exceptions(account.info, [])
self.assertEqual(result.output, """\
handle : PXP561-GANDI
prepaid : 1337.42 EUR
credits :
available: 2335360
usage : 633/h
time left: 0 year(s) 4 month(s) 29 day(s) 17 hour(s)
""")
self.assertEqual(result.exit_code, 0)
gandi.cli-1.2/gandi/cli/tests/fixtures/ 0000755 0001750 0001750 00000000000 13227415174 020734 5 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/gandi/cli/tests/fixtures/mocks.py 0000644 0001750 0001750 00000001644 12623566662 022437 0 ustar sayoun sayoun 0000000 0000000 from __future__ import print_function
from datetime import datetime
class MockObject(object):
@classmethod
def blank_func(cls, *args, **kwargs):
pass
@classmethod
def execute(cls, command, shell=True):
""" Execute a shell command. """
if not shell:
print(' '.join(command))
else:
print(command)
return True
@classmethod
def exec_output(cls, command, shell=True, encoding='utf-8'):
""" Return execution output
:param encoding: charset used to decode the stdout
:type encoding: str
:return: the return of the command
:rtype: unicode string
"""
if not shell:
return ' '.join(command)
return command
@classmethod
def now(cls, *args, **kwargs):
return datetime(2020, 12, 25, 0, 0, 0)
@classmethod
def deprecated(cls, message):
pass
gandi.cli-1.2/gandi/cli/tests/fixtures/api.py 0000644 0001750 0001750 00000001570 12623134755 022064 0 ustar sayoun sayoun 0000000 0000000 import logging
import importlib
log = logging.getLogger(__name__)
class Api(object):
_calls = {}
def request(self, method, apikey, *args, **kwargs):
log.info('Calling %s%r %r' % (method, args, kwargs))
modname, func = method.split('.', 1)
modname = 'gandi.cli.tests.fixtures._' + modname
module = importlib.import_module(modname)
func = func.replace('.', '_')
if (kwargs.get('dry_run', False)
and kwargs.get('return_dry_run', False)):
func = func + '_dry_run'
try:
self._calls.setdefault(method, []).append(args)
return getattr(module, func)(*args)
except Exception as exc:
log.exception('Unexpected Exception %s while calling %s' % (exc,
method)
)
gandi.cli-1.2/gandi/cli/tests/fixtures/_cert.py 0000644 0001750 0001750 00000047065 12753315354 022420 0 ustar sayoun sayoun 0000000 0000000 try:
# python3
from xmlrpc.client import DateTime
except ImportError:
# python2
from xmlrpclib import DateTime
type_list = list
def package_list(options):
return [{'category': {'id': 1, 'name': 'standard'},
'comodo_id': 287,
'id': 1,
'max_domains': 1,
'min_domains': 1,
'name': 'cert_std_1_0_0',
'sgc': 0,
'trustlogo': 0,
'warranty': 0,
'wildcard': 0},
{'category': {'id': 1, 'name': 'standard'},
'comodo_id': 279,
'id': 2,
'max_domains': 3,
'min_domains': 1,
'name': 'cert_std_3_0_0',
'sgc': 0,
'trustlogo': 0,
'warranty': 0,
'wildcard': 0},
{'category': {'id': 1, 'name': 'standard'},
'comodo_id': 289,
'id': 3,
'max_domains': 1,
'min_domains': 1,
'name': 'cert_std_w_0_0',
'sgc': 0,
'trustlogo': 0,
'warranty': 0,
'wildcard': 1},
{'category': {'id': 2, 'name': 'pro'},
'comodo_id': 24,
'id': 4,
'max_domains': 1,
'min_domains': 1,
'name': 'cert_pro_1_10_0',
'sgc': 0,
'trustlogo': 1,
'warranty': 10000,
'wildcard': 0},
{'category': {'id': 2, 'name': 'pro'},
'comodo_id': 34,
'id': 5,
'max_domains': 1,
'min_domains': 1,
'name': 'cert_pro_1_100_0',
'sgc': 0,
'trustlogo': 1,
'warranty': 100000,
'wildcard': 0},
{'category': {'id': 2, 'name': 'pro'},
'comodo_id': 317,
'id': 6,
'max_domains': 1,
'min_domains': 1,
'name': 'cert_pro_1_100_SGC',
'sgc': 1,
'trustlogo': 1,
'warranty': 100000,
'wildcard': 0},
{'category': {'id': 2, 'name': 'pro'},
'comodo_id': 7,
'id': 7,
'max_domains': 1,
'min_domains': 1,
'name': 'cert_pro_1_250_0',
'sgc': 0,
'trustlogo': 1,
'warranty': 250000,
'wildcard': 0},
{'category': {'id': 2, 'name': 'pro'},
'comodo_id': 35,
'id': 8,
'max_domains': 1,
'min_domains': 1,
'name': 'cert_pro_w_250_0',
'sgc': 0,
'trustlogo': 1,
'warranty': 250000,
'wildcard': 1},
{'category': {'id': 2, 'name': 'pro'},
'comodo_id': 323,
'id': 9,
'max_domains': 1,
'min_domains': 1,
'name': 'cert_pro_w_250_SGC',
'sgc': 1,
'trustlogo': 1,
'warranty': 250000,
'wildcard': 1},
{'category': {'id': 3, 'name': 'business'},
'comodo_id': 337,
'id': 10,
'max_domains': 1,
'min_domains': 1,
'name': 'cert_bus_1_250_0',
'sgc': 0,
'trustlogo': 0,
'warranty': 250000,
'wildcard': 0},
{'category': {'id': 3, 'name': 'business'},
'comodo_id': 338,
'id': 11,
'max_domains': 1,
'min_domains': 1,
'name': 'cert_bus_1_250_SGC',
'sgc': 1,
'trustlogo': 0,
'warranty': 250000,
'wildcard': 0},
{'category': {'id': 1, 'name': 'standard'},
'comodo_id': 279,
'id': 12,
'max_domains': 5,
'min_domains': 1,
'name': 'cert_std_5_0_0',
'sgc': 0,
'trustlogo': 0,
'warranty': 0,
'wildcard': 0},
{'category': {'id': 1, 'name': 'standard'},
'comodo_id': 279,
'id': 13,
'max_domains': 10,
'min_domains': 1,
'name': 'cert_std_10_0_0',
'sgc': 0,
'trustlogo': 0,
'warranty': 0,
'wildcard': 0},
{'category': {'id': 1, 'name': 'standard'},
'comodo_id': 279,
'id': 14,
'max_domains': 20,
'min_domains': 1,
'name': 'cert_std_20_0_0',
'sgc': 0,
'trustlogo': 0,
'warranty': 0,
'wildcard': 0},
{'category': {'id': 3, 'name': 'business'},
'comodo_id': 410,
'id': 15,
'max_domains': 3,
'min_domains': 1,
'name': 'cert_bus_3_250_0',
'sgc': 0,
'trustlogo': 0,
'warranty': 250000,
'wildcard': 0},
{'category': {'id': 3, 'name': 'business'},
'comodo_id': 410,
'id': 16,
'max_domains': 5,
'min_domains': 1,
'name': 'cert_bus_5_250_0',
'sgc': 0,
'trustlogo': 0,
'warranty': 250000,
'wildcard': 0},
{'category': {'id': 3, 'name': 'business'},
'comodo_id': 410,
'id': 17,
'max_domains': 10,
'min_domains': 1,
'name': 'cert_bus_10_250_0',
'sgc': 0,
'trustlogo': 0,
'warranty': 250000,
'wildcard': 0},
{'category': {'id': 3, 'name': 'business'},
'comodo_id': 410,
'id': 18,
'max_domains': 20,
'min_domains': 1,
'name': 'cert_bus_20_250_0',
'sgc': 0,
'trustlogo': 0,
'warranty': 250000,
'wildcard': 0}
]
def list(options):
ret = [{'trustlogo': False,
'assumed_name': None,
'package': 'cert_std_1_0_0',
'order_number': None,
'altnames': [],
'trustlogo_token': {'mydomain.name': 'adadadadad'},
'date_incorporation': None,
'card_pay_trustlogo': False,
'contact': 'TEST1-GANDI',
'date_start': None,
'ida_email': None,
'business_category': None,
'cert': None,
'date_end': None,
'status': 'pending',
'csr': '-----BEGIN CERTIFICATE REQUEST-----\n'
'MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX'
'...'
'K+I=\n-----END CERTIFICATE REQUEST-----',
'date_updated': DateTime('20140904T14:06:26'),
'software': 2,
'id': 701,
'joi_locality': None,
'date_created': DateTime('20140904T14:06:26'),
'cn': 'mydomain.name',
'altname': [],
'sha_version': 1,
'middleman': '',
'ida_tel': None,
'ida_fax': None,
'comodo_id': None,
'joi_country': None,
'joi_state': None},
{'trustlogo': False,
'assumed_name': None,
'package': 'cert_std_1_0_0',
'order_number': None,
'altnames': [],
'trustlogo_token': {'bew.web': 'adadadadad'},
'date_incorporation': None,
'card_pay_trustlogo': False,
'contact': 'TEST1-GANDI',
'date_start': None,
'ida_email': None,
'business_category': None,
'date_end': None,
'status': 'pending',
'csr': '-----BEGIN CERTIFICATE REQUEST-----\n'
'MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX'
'...'
'K+I=\n-----END CERTIFICATE REQUEST-----',
'date_updated': DateTime('20140904T14:06:26'),
'software': 2,
'id': 771,
'joi_locality': None,
'date_created': DateTime('20140904T14:06:26'),
'cn': 'bew.web',
'altname': [],
'sha_version': 1,
'middleman': '',
'ida_tel': None,
'ida_fax': None,
'comodo_id': None,
'joi_country': None,
'joi_state': None},
{'trustlogo': False,
'assumed_name': None,
'package': 'cert_pro_1_100_SGC',
'order_number': None,
'altnames': [],
'trustlogo_token': {'iheartcli.com': 'adadadadad'},
'date_incorporation': None,
'card_pay_trustlogo': False,
'contact': 'TEST1-GANDI',
'date_start': None,
'ida_email': None,
'business_category': None,
'cert': None,
'date_end': None,
'status': 'valid',
'csr': '-----BEGIN CERTIFICATE REQUEST-----\n'
'MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX'
'...'
'K+I=\n-----END CERTIFICATE REQUEST-----',
'date_updated': DateTime('20140904T14:06:26'),
'software': 2,
'id': 709,
'joi_locality': None,
'date_created': DateTime('20140904T14:06:26'),
'cn': 'iheartcli.com',
'altname': [],
'sha_version': 1,
'middleman': '',
'ida_tel': None,
'ida_fax': None,
'comodo_id': None,
'joi_country': None,
'joi_state': None},
{'trustlogo': False,
'assumed_name': None,
'package': 'cert_pro_1_100_SGC',
'order_number': None,
'altnames': [],
'trustlogo_token': {'cat.lol': 'adadadadad'},
'date_incorporation': None,
'card_pay_trustlogo': False,
'contact': 'TEST1-GANDI',
'date_start': None,
'ida_email': None,
'business_category': None,
'cert': None,
'date_end': None,
'status': 'valid',
'csr': '-----BEGIN CERTIFICATE REQUEST-----\n'
'MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX'
'...'
'K+I=\n-----END CERTIFICATE REQUEST-----',
'date_updated': DateTime('20140904T14:06:26'),
'software': 2,
'id': 709,
'joi_locality': None,
'date_created': DateTime('20140904T14:06:26'),
'cn': 'cat.lol',
'altname': [],
'sha_version': 1,
'middleman': '',
'ida_tel': None,
'ida_fax': None,
'comodo_id': None,
'joi_country': None,
'joi_state': None},
{'trustlogo': False,
'assumed_name': None,
'package': 'cert_bus_1_250_0',
'order_number': None,
'altnames': [],
'trustlogo_token': {'iheartcli.com': 'adadadadad'},
'date_incorporation': None,
'card_pay_trustlogo': False,
'contact': 'TEST1-GANDI',
'date_start': None,
'ida_email': None,
'business_category': None,
'cert': None,
'date_end': None,
'status': 'valid',
'csr': '-----BEGIN CERTIFICATE REQUEST-----\n'
'MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX'
'...'
'K+I=\n-----END CERTIFICATE REQUEST-----',
'date_updated': DateTime('20140904T14:06:26'),
'software': 2,
'id': 769,
'joi_locality': None,
'date_created': DateTime('20140904T14:06:26'),
'cn': 'iheartcli.com',
'altname': [],
'sha_version': 1,
'middleman': '',
'ida_tel': None,
'ida_fax': None,
'comodo_id': None,
'joi_country': None,
'joi_state': None},
{'trustlogo': False,
'assumed_name': None,
'package': 'cert_bus_20_250_0',
'order_number': None,
'altnames': [],
'trustlogo_token': {'inter.net': 'adadadadad'},
'date_incorporation': None,
'card_pay_trustlogo': False,
'contact': 'TEST1-GANDI',
'date_start': None,
'ida_email': None,
'business_category': None,
'cert': None,
'date_end': None,
'status': 'valid',
'csr': '-----BEGIN CERTIFICATE REQUEST-----\n'
'MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX'
'...'
'K+I=\n-----END CERTIFICATE REQUEST-----',
'date_updated': DateTime('20140904T14:06:26'),
'software': 2,
'id': 706,
'joi_locality': None,
'date_created': DateTime('20140904T14:06:26'),
'cn': 'inter.net',
'altname': [],
'sha_version': 1,
'middleman': '',
'ida_tel': None,
'ida_fax': None,
'comodo_id': None,
'joi_country': None,
'joi_state': None},
{'altnames': ['pouet.lol.cat'],
'assumed_name': None,
'business_category': None,
'card_pay_trustlogo': False,
'cert': 'MIIE5zCCA8+gAwIBAgIQAkC4TU9JG8wqhf4FCrsNGTANBgkqhkiG9'
'w0BAQsFADBfMQswCQYDVQQGEwJGUjEOMAwGA1UECBMFUGFyaXMxDj'
'...'
'tU6XzbS6/s2D1/N1wWOOCD/V3XAROtKr1a0mtJ8n7SZyzr0j3weRbN'
'7nV24RDQ6d4+GHy5zZstKyDrTknluyyZuDAAYAQJ+nrL5p1gxVNwj1'
'f5XKFk=',
'cn': 'lol.cat',
'altname': [],
'comodo_id': 1777348256,
'contact': 'DF2975-GANDI',
'csr': '-----BEGIN CERTIFICATE REQUEST-----\n'
'MIICgzCCAWsCAQAwPjERMA8GA1UEAwwIZ2F1dnIuaX'
'...'
'K+I=\n-----END CERTIFICATE REQUEST-----',
'date_created': DateTime('20150318T00:00:00'),
'date_end': DateTime('20160318T00:00:00'),
'date_incorporation': None,
'date_start': DateTime('20150318T00:00:00'),
'date_updated': DateTime('20150318T00:00:00'),
'id': 710,
'ida_email': None,
'ida_fax': None,
'ida_tel': None,
'joi_country': None,
'joi_locality': None,
'joi_state': None,
'middleman': '',
'order_number': 12345678,
'package': 'cert_std_1_0_0',
'sha_version': 2,
'software': 2,
'status': 'valid',
'trustlogo': False,
'trustlogo_token': {'lol.cat': 'ababababa'}}]
options.pop('items_per_page', None)
def compare(hc, option):
if isinstance(option, (type_list, tuple)):
return hc in option
return hc == option
for fkey in options:
ret = [hc for hc in ret if compare(hc[fkey], options[fkey])]
return ret
def info(id):
cert = dict([(cert['id'], cert) for cert in list({})])
return cert[id]
def create(*args):
return {'id': 1}
def update(*args):
return {'id': 400}
def change_dcv(oper_id, dcv_method):
return True
def resend_dcv(oper_id):
return True
def get_dcv_params(params):
return {
'sha1': 'AD6A9D35FF5BD9FB03A41F4F82CAA4B77',
'dcv_method': 'dns',
'fqdns': ['lol.cat'],
'message': ['920F78CCE11DA7D9554.lol.cat. 10800 IN '
'CNAME AD6A9D35FF5BD9FB03A41.comodoca.com.'],
'dns_records': ['920F78CCE11DA7D9554.lol.cat. 10800 IN '
'CNAME AD6A9D35FF5BD9FB03A41.comodoca.com.'],
'md5': '920F78CCE11DA7ADAD9554',
}
def delete(cert_id):
return {'id': 200, 'step': 'WAIT'}
def hosted_list(options):
fqdns_id = {'test1.domain.fr': [1, 2],
'test2.domain.fr': [3],
'test3.domain.fr': [4],
'test4.domain.fr': [5],
'*.domain.fr': [6]}
ret = [{'date_created': DateTime('20150407T00:00:00'),
'date_expire': DateTime('20160316T00:00:00'),
'id': 1,
'state': u'created',
'subject': u'/OU=Domain Control Validated/OU=Gandi Standard '
'SSL/CN=test1.domain.fr'},
{'date_created': DateTime('20150407T00:00:00'),
'date_expire': DateTime('20160316T00:00:00'),
'id': 2,
'state': u'created',
'subject': u'/OU=Domain Control Validated/OU=Gandi Standard '
'SSL/CN=test1.domain.fr'},
{'date_created': DateTime('20150408T00:00:00'),
'date_expire': DateTime('20160408T00:00:00'),
'id': 3,
'state': u'created',
'subject': u'/OU=Domain Control Validated/OU=Gandi Standard '
'SSL/CN=test2.domain.fr'},
{'date_created': DateTime('20150408T00:00:00'),
'date_expire': DateTime('20160408T00:00:00'),
'id': 4,
'state': u'created',
'subject': u'/OU=Domain Control Validated/OU=Gandi Standard '
'SSL/CN=test3.domain.fr'},
{'date_created': DateTime('20150408T00:00:00'),
'date_expire': DateTime('20160408T00:00:00'),
'id': 5,
'state': u'created',
'subject': u'/OU=Domain Control Validated/OU=Gandi Standard '
'SSL/CN=test4.domain.fr'},
{'date_created': DateTime('20150409T00:00:00'),
'date_expire': DateTime('20160409T00:00:00'),
'id': 6,
'state': u'created',
'subject': u'/OU=Domain Control Validated/OU=Gandi Standard '
'Wildcard SSL/CN=*.domain.fr'}]
options.pop('items_per_page', None)
fqdns = options.pop('fqdns', None)
if fqdns:
if not isinstance(fqdns, (type_list, tuple)):
fqdns = [fqdns]
for fqdn in fqdns:
options.setdefault('id', []).extend(fqdns_id.get(fqdn, []))
def compare(hc, option):
if isinstance(option, (type_list, tuple)):
return hc in option
return hc == option
for fkey in options:
ret = [hc for hc in ret if compare(hc[fkey], options[fkey])]
return ret
def hosted_info(id):
additionals = {
1: {'fqdns': [{'type': u'cn', 'name': u'test1.domain.fr'}],
'related_vhosts': [{'service_id': 1,
'type': 'paas',
'id': 1,
'name': 'test1.domain.fr'}]},
2: {'fqdns': [{'type': u'cn', 'name': u'test1.domain.fr'}],
'related_vhosts': [{'service_id': 1,
'type': 'paas',
'id': 1,
'name': 'test1.domain.fr'}]},
3: {'fqdns': [{'type': u'cn', 'name': u'test2.domain.fr'}],
'related_vhosts': []},
4: {'fqdns': [{'type': u'cn', 'name': u'test3.domain.fr'}],
'related_vhosts': []},
5: {'fqdns': [{'type': u'cn', 'name': u'test4.domain.fr'}],
'related_vhosts': []},
6: {'fqdns': [{'type': u'cn', 'name': u'*.domain.fr'}],
'related_vhosts': [{'service_id': 2,
'type': 'paas',
'id': 2,
'name': '*.domain.fr'}]}}
def additional(hc):
hc.update(additionals[hc['id']])
return hc
hc = dict([(hc['id'], additional(hc)) for hc in hosted_list({})])
return hc.get(id)
def hosted_create(params):
return hosted_info(5)
def hosted_delete(id):
return
gandi.cli-1.2/gandi/cli/tests/fixtures/_paas.py 0000644 0001750 0001750 00000024003 12663631621 022370 0 ustar sayoun sayoun 0000000 0000000 try:
# python3
from xmlrpc.client import DateTime
except ImportError:
# python2
from xmlrpclib import DateTime
def snapshotprofile_list(options):
ret = [{'id': 7,
'kept_total': 3,
'name': 'paas_normal',
'quota_factor': 1.3,
'schedules': [{'kept_version': 1, 'name': 'daily'},
{'kept_version': 1, 'name': 'weekly'},
{'kept_version': 1, 'name': 'weekly4'}]}]
for fkey in options:
ret = [snp for snp in ret if snp[fkey] == options[fkey]]
return ret
def list(options):
ret = [{'catalog_name': 'phpmysql_s',
'console': '1656411@console.dc0.gpaas.net',
'data_disk_additional_size': 0,
'datacenter_id': 1,
'date_end': DateTime('20160408T00:00:00'),
'date_end_commitment': None,
'date_start': DateTime('20130903T22:14:13'),
'id': 126276,
'name': 'paas_owncloud',
'need_upgrade': False,
'servers': [{'id': 126273}],
'size': 's',
'snapshot_profile': None,
'state': 'halted',
'type': 'phpmysql'},
{'catalog_name': 'nodejsmongodb_s',
'console': '185290@console.dc2.gpaas.net',
'data_disk_additional_size': 0,
'datacenter_id': 3,
'date_end': DateTime('20161125T15:52:56'),
'date_end_commitment': DateTime('20151118T18:00:00'),
'date_start': DateTime('20141025T15:52:56'),
'id': 163744,
'name': 'paas_cozycloud',
'need_upgrade': False,
'servers': [{'id': 163728}],
'size': 's',
'snapshot_profile': None,
'state': 'running',
'type': 'nodejsmongodb'}]
options.pop('items_per_page', None)
for fkey in options:
ret = [paas for paas in ret if paas[fkey] == options[fkey]]
return ret
def info(paas_id):
ret = [{'autorenew': None,
'catalog_name': 'nodejsmongodb_s',
'console': '185290@console.dc2.gpaas.net',
'data_disk_additional_size': 0,
'datacenter': {'country': 'Luxembourg',
'dc_code': 'LU-BI1',
'id': 3,
'iso': 'LU',
'name': 'Bissen'},
'datadisk_total_size': 10.0,
'date_end': DateTime('20161125T15:52:56'),
'date_end_commitment': DateTime('20151118T00:00:00'),
'date_start': DateTime('20141025T15:52:56'),
'ftp_server': 'sftp.dc2.gpaas.net',
'git_server': 'git.dc2.gpaas.net',
'id': 163744,
'name': 'paas_cozycloud',
'need_upgrade': False,
'owner': {'handle': 'PXP561-GANDI', 'id': 2920674},
'servers': [{'graph_urls': {'vcpu': [''], 'vdi': [''],
'vif': ['']},
'id': 163728,
'uuid': 19254}],
'size': 's',
'snapshot_profile': None,
'state': 'running',
'type': 'nodejsmongodb',
'user': 185290,
'vhosts': [{'date_creation': DateTime('20141025T15:50:54'),
'id': 1177216,
'name': '187832c2b34.testurl.ws',
'state': 'running'},
{'date_creation': DateTime('20141025T15:50:54'),
'id': 1177220,
'name': 'cloud.iheartcli.com',
'state': 'running'},
{'date_creation': DateTime('20150728T17:50:56'),
'id': 1365951,
'name': 'cli.sexy',
'state': 'running'}]},
{'autorenew': None,
'catalog_name': 'phpmysql_s',
'console': '1656411@console.dc0.gpaas.net',
'data_disk_additional_size': 0,
'datacenter': {'country': 'France',
'dc_code': 'FR-SD2',
'id': 1,
'iso': 'FR',
'name': 'Equinix Paris'},
'datadisk_total_size': 10.0,
'date_end': DateTime('20160408T00:00:00'),
'date_end_commitment': None,
'date_start': DateTime('20130903T22:14:13'),
'ftp_server': 'sftp.dc0.gpaas.net',
'git_server': 'git.dc0.gpaas.net',
'id': 126276,
'name': 'sap',
'need_upgrade': False,
'owner': {'handle': 'PXP561-GANDI', 'id': 2920674},
'servers': [{'graph_urls': {'vcpu': [''], 'vdi': [''],
'vif': ['']},
'id': 126273,
'uuid': 195339}],
'size': 's',
'snapshot_profile': None,
'state': 'halted',
'type': 'phpmysql',
'user': 1656411,
'vhosts': [{'date_creation': DateTime('20130903T22:11:54'),
'id': 160126,
'name': 'aa3e0e26f8.url-de-test.ws',
'state': 'running'},
{'date_creation': DateTime('20130903T22:24:06'),
'id': 160127,
'name': 'cloud.cat.lol',
'state': 'running'}]},
{'autorenew': None,
'catalog_name': 'pythonpgsql_s',
'console': '1185290@console.dc2.gpaas.net',
'data_disk_additional_size': 0,
'datacenter': {'country': 'Luxembourg',
'dc_code': 'LU-BI1',
'id': 3,
'iso': 'LU',
'name': 'Bissen'},
'datadisk_total_size': 10.0,
'date_end': DateTime('20161125T15:52:56'),
'date_end_commitment': DateTime('20151118T00:00:00'),
'date_start': DateTime('20141025T15:52:56'),
'ftp_server': 'sftp.dc2.gpaas.net',
'git_server': 'git.dc2.gpaas.net',
'id': 123456,
'name': '123456',
'need_upgrade': False,
'owner': {'handle': 'PXP561-GANDI', 'id': 2920674},
'servers': [{'graph_urls': {'vcpu': [''], 'vdi': [''],
'vif': ['']},
'id': 1123456,
'uuid': 119254}],
'size': 's',
'snapshot_profile': None,
'state': 'running',
'type': 'pythonpgsql',
'user': 1185290,
'vhosts': [{'date_creation': DateTime('20141025T15:50:54'),
'id': 2177216,
'name': '987832c2b34.testurl.ws',
'state': 'running'}]}]
instances = dict([(paas['id'], paas) for paas in ret])
return instances[paas_id]
def vhost_list(options):
ret = [{'cert_id': None,
'date_creation': DateTime('20130903T22:11:54'),
'id': 160126,
'name': 'aa3e0e26f8.url-de-test.ws',
'paas_id': 126276,
'state': 'running'},
{'cert_id': None,
'date_creation': DateTime('20130903T22:24:06'),
'id': 160127,
'name': 'cloud.cat.lol',
'paas_id': 126276,
'state': 'running'},
{'cert_id': None,
'date_creation': DateTime('20141025T15:50:54'),
'id': 1177216,
'name': '187832c2b34.testurl.ws',
'paas_id': 163744,
'state': 'running'},
{'cert_id': None,
'date_creation': DateTime('20141025T15:50:54'),
'id': 1177220,
'name': 'cloud.iheartcli.com',
'paas_id': 163744,
'state': 'running'},
{'cert_id': None,
'date_creation': DateTime('20150728T17:50:56'),
'id': 1365951,
'name': 'cli.sexy',
'paas_id': 163744,
'state': 'running'}]
options.pop('items_per_page', None)
for fkey in options:
ret = [paas for paas in ret if paas[fkey] == options[fkey]]
return ret
def vhost_info(name):
vhosts = vhost_list({})
vhosts = dict([(vhost['name'], vhost) for vhost in vhosts])
return vhosts[name]
def vhost_delete(name):
return {'id': 200, 'step': 'WAIT', 'name': 'rproxy_update',
'paas_id': 1177220,
'date_creation': DateTime('20150728T17:50:56')}
def type_list(options):
ret = [{'database': 'MySQL',
'language': 'PHP',
'name': 'phpmysql'},
{'database': 'PostgreSQL',
'language': 'PHP',
'name': 'phppgsql'},
{'database': 'PostgreSQL',
'language': 'Node.js',
'name': 'nodejspgsql'},
{'database': 'MongoDB',
'language': 'Node.js',
'name': 'nodejsmongodb'},
{'database': 'MySQL',
'language': 'Node.js',
'name': 'nodejsmysql'},
{'database': 'MongoDB',
'language': 'PHP',
'name': 'phpmongodb'},
{'database': 'MySQL',
'language': 'Python',
'name': 'pythonmysql'},
{'database': 'PostgreSQL',
'language': 'Python',
'name': 'pythonpgsql'},
{'database': 'MongoDB',
'language': 'Python',
'name': 'pythonmongodb'},
{'database': 'MySQL',
'language': 'Ruby',
'name': 'rubymysql'},
{'database': 'PostgreSQL',
'language': 'Ruby',
'name': 'rubypgsql'},
{'database': 'MongoDB',
'language': 'Ruby',
'name': 'rubymongodb'}]
return ret
def update(paas_id, options):
return {'id': 200, 'step': 'WAIT'}
def vhost_create(options):
return {'id': 200, 'step': 'WAIT'}
def delete(paas_id):
return {'id': 200, 'step': 'WAIT'}
def create(options):
return {'id': 200, 'step': 'WAIT'}
def restart(options):
return {'id': 200, 'step': 'WAIT'}
gandi.cli-1.2/gandi/cli/tests/fixtures/_hosting.py 0000644 0001750 0001750 00000167377 13227142754 023146 0 ustar sayoun sayoun 0000000 0000000 from datetime import datetime
try:
# python3
from xmlrpc.client import DateTime
except ImportError:
# python2
from xmlrpclib import DateTime
def image_list(options):
ret = [{'author_id': 248842,
'datacenter_id': 3,
'date_created': DateTime('20130902T15:04:18'),
'date_updated': DateTime('20130903T12:14:30'),
'disk_id': 527489,
'id': 131,
'kernel_version': '3.2-i386',
'label': 'Fedora 17 32 bits',
'os_arch': 'x86-32',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 3,
'date_created': DateTime('20130902T15:04:18'),
'date_updated': DateTime('20130903T12:14:30'),
'disk_id': 527490,
'id': 132,
'kernel_version': '3.2-x86_64',
'label': 'Fedora 17 64 bits',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 3,
'date_created': DateTime('20130902T15:04:18'),
'date_updated': DateTime('20130903T12:14:30'),
'disk_id': 527491,
'id': 133,
'kernel_version': '3.2-i386',
'label': 'OpenSUSE 12.2 32 bits',
'os_arch': 'x86-32',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 3,
'date_created': DateTime('20130902T15:04:18'),
'date_updated': DateTime('20130903T12:14:30'),
'disk_id': 527494,
'id': 134,
'kernel_version': '3.2-x86_64',
'label': 'OpenSUSE 12.2 64 bits',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 3,
'date_created': DateTime('20130902T15:04:18'),
'date_updated': DateTime('20130903T12:14:30'),
'disk_id': 726224,
'id': 149,
'kernel_version': '2.6.32',
'label': 'CentOS 5 32 bits',
'os_arch': 'x86-32',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 3,
'date_created': DateTime('20130902T15:04:18'),
'date_updated': DateTime('20130903T12:14:30'),
'disk_id': 726225,
'id': 150,
'kernel_version': '2.6.32-x86_64',
'label': 'CentOS 5 64 bits',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 3,
'date_created': DateTime('20130902T15:04:18'),
'date_updated': DateTime('20130903T12:14:30'),
'disk_id': 726230,
'id': 151,
'kernel_version': '3.2-i386',
'label': 'ArchLinux 32 bits',
'os_arch': 'x86-32',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 3,
'date_created': DateTime('20130902T15:04:18'),
'date_updated': DateTime('20130903T12:14:30'),
'disk_id': 726233,
'id': 152,
'kernel_version': '3.2-x86_64',
'label': 'ArchLinux 64 bits',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 2,
'date_created': DateTime('20140417T18:38:53'),
'date_updated': DateTime('20141030T10:38:45'),
'disk_id': 1401491,
'id': 161,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 7 64 bits (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 1,
'date_created': DateTime('20140417T18:38:53'),
'date_updated': DateTime('20141030T18:06:44'),
'disk_id': 1349810,
'id': 162,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 7 64 bits (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'deprecated'},
{'author_id': 248842,
'datacenter_id': 3,
'date_created': DateTime('20140417T18:38:53'),
'date_updated': DateTime('20141030T10:38:45'),
'disk_id': 1401327,
'id': 167,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 7 64 bits (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 1,
'date_created': DateTime('20141203T14:15:28'),
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 3315704,
'id': 172,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 8 (testing) 64 bits (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 2,
'date_created': DateTime('20141203T14:15:28'),
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 3315992,
'id': 176,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 8 (testing) 64 bits (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 1,
'date_created': DateTime('20141203T14:15:28'),
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 3316070,
'id': 178,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 8',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 3,
'date_created': DateTime('20141203T14:15:28'),
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 3316070,
'id': 178,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 8',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 4,
'date_created': DateTime('20141203T14:15:28'),
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 3316070,
'id': 178,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 8',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 5,
'date_created': DateTime('20141203T14:15:28'),
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 3316070,
'id': 178,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 8',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 3,
'date_created': DateTime('20141203T14:15:28'),
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 3316076,
'id': 180,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 8 (testing) 64 bits (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 1,
'date_created': DateTime('20141203T14:15:28'),
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 3315748,
'id': 184,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Ubuntu 14.04 64 bits LTS (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 2,
'date_created': DateTime('20141203T14:15:28'),
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 3316144,
'id': 188,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Ubuntu 14.04 64 bits LTS (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 3,
'date_created': DateTime('20141203T14:15:28'),
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 3316160,
'id': 192,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Ubuntu 14.04 64 bits LTS (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 1,
'date_created': None,
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 2876292,
'id': 196,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'CentOS 7 64 bits (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 2,
'date_created': None,
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 4744388,
'id': 200,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'CentOS 7 64 bits (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 3,
'date_created': None,
'date_updated': DateTime('20150116T11:24:56'),
'disk_id': 4744392,
'id': 204,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'CentOS 7 64 bits (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 4,
'date_created': DateTime('20140417T18:38:53'),
'date_updated': DateTime('20141030T10:38:45'),
'disk_id': 1401492,
'id': 163,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 7 64 bits (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
{'author_id': 248842,
'datacenter_id': 5,
'date_created': DateTime('20140417T18:38:53'),
'date_updated': DateTime('20141030T10:38:45'),
'disk_id': 1401492,
'id': 163,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 7 64 bits (HVM)',
'os_arch': 'x86-64',
'size': 3072,
'visibility': 'all'},
]
for fkey in options:
ret = [dc for dc in ret if dc[fkey] == options[fkey]]
return ret
def datacenter_list(options):
ret = [{'iso': 'FR',
'name': 'Equinix Paris',
'id': 1,
'can_migrate_to': [4],
'country': 'France',
'deactivate_at': datetime(2017, 12, 25, 0, 0, 0),
'iaas_closed_for': 'NEW',
'paas_closed_for': 'NEW',
'dc_code': 'FR-SD2'},
{'iso': 'US',
'name': 'Level3 Baltimore',
'id': 2,
'can_migrate_to': [],
'country': 'United States of America',
'deactivate_at': datetime(2016, 12, 25, 0, 0, 0),
'iaas_closed_for': 'ALL',
'paas_closed_for': 'ALL',
'dc_code': 'US-BA1'},
{'iso': 'LU',
'name': 'Bissen',
'id': 3,
'can_migrate_to': [],
'country': 'Luxembourg',
'deactivate_at': None,
'iaas_closed_for': 'NONE',
'paas_closed_for': 'NONE',
'dc_code': 'LU-BI1'},
{'iso': 'FR',
'name': 'France, Paris',
'id': 4,
'can_migrate_to': [],
'country': 'France',
'deactivate_at': None,
'iaas_closed_for': 'NONE',
'paas_closed_for': 'ALL',
'dc_code': 'FR-SD3'},
{'iso': 'FR',
'name': 'France, Paris',
'id': 5,
'can_migrate_to': [],
'country': 'France',
'deactivate_at': None,
'iaas_closed_for': 'NONE',
'paas_closed_for': 'ALL',
'dc_code': 'FR-SD5'}]
options.pop('sort_by', None)
for fkey in options:
if (fkey == 'iaas_opened') or (fkey == 'paas_opened'):
fkey = '%s_closed_for' % fkey[:4]
ret = [dc for dc in ret if dc[fkey] in ['NONE', 'NEW']]
else:
ret = [dc for dc in ret if dc[fkey] == options[fkey]]
return ret
def disk_list(options):
disks = [{'can_snapshot': True,
'datacenter_id': 3,
'date_created': DateTime('20150319T11:10:34'),
'date_updated': DateTime('20150319T11:10:58'),
'id': 4969232,
'is_boot_disk': True,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 7 64 bits (HVM)',
'name': 'sys_1426759833',
'size': 3072,
'snapshot_profile_id': None,
'snapshots_id': [],
'source': 1401327,
'state': 'created',
'total_size': 3072,
'type': 'data',
'visibility': 'private',
'vms_id': [152966]},
{'can_snapshot': True,
'datacenter_id': 1,
'date_created': DateTime('20150319T11:14:13'),
'date_updated': DateTime('20150319T11:14:29'),
'id': 4969249,
'is_boot_disk': True,
'kernel_cmdline': {'console': 'ttyS0',
'nosep': True,
'ro': True,
'root': '/dev/sda'},
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 7 64 bits (HVM)',
'name': 'sys_server01',
'size': 3072,
'snapshot_profile_id': None,
'snapshots_id': [],
'source': 1349810,
'state': 'created',
'total_size': 3072,
'type': 'data',
'visibility': 'private',
'vms_id': [152967]},
{'can_snapshot': True,
'datacenter_id': 1,
'date_created': DateTime('20150319T15:39:54'),
'date_updated': DateTime('20150319T15:40:24'),
'id': 4970079,
'is_boot_disk': False,
'kernel_version': None,
'label': None,
'name': 'data',
'size': 3072,
'snapshot_profile_id': 1,
'snapshots_id': [663497],
'source': None,
'state': 'created',
'total_size': 3072,
'type': 'data',
'visibility': 'private',
'vms_id': [152967]},
{'can_snapshot': False,
'datacenter_id': 1,
'date_created': DateTime('20140826T00:00:00'),
'date_updated': DateTime('20140826T00:00:00'),
'id': 663497,
'is_boot_disk': False,
'kernel_version': '3.2-x86_64',
'label': 'Debian 7 64 bits',
'name': 'snaptest',
'size': 3072,
'snapshot_profile_id': None,
'snapshots_id': [],
'source': 4970079,
'state': 'created',
'total_size': 3072,
'type': 'snapshot',
'visibility': 'private',
'vms_id': []},
{'can_snapshot': True,
'datacenter_id': 3,
'date_created': DateTime('20150319T11:10:34'),
'date_updated': DateTime('20150319T11:10:58'),
'id': 4969233,
'is_boot_disk': True,
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 7 64 bits (HVM)',
'name': 'newdisk',
'size': 3072,
'snapshot_profile_id': None,
'snapshots_id': [],
'source': 1401327,
'state': 'created',
'total_size': 3072,
'type': 'data',
'visibility': 'private',
'vms_id': []}]
options.pop('items_per_page', None)
for fkey in options:
ret = []
for disk in disks:
if isinstance(options[fkey], list):
if disk[fkey] in options[fkey]:
ret.append(disk)
elif disk[fkey] == options[fkey]:
ret.append(disk)
disks = ret
return disks
def disk_info(id):
disks = disk_list({})
disks = dict([(disk['id'], disk) for disk in disks])
return disks[id]
def disk_update(disk_id, options):
return {'id': 200, 'step': 'WAIT'}
def disk_delete(disk_id):
return {'id': 200, 'step': 'WAIT'}
def disk_rollback_from(disk_id):
return {'id': 200, 'step': 'WAIT'}
def disk_migrate(disk_id, datacenter_id):
return {'id': 200, 'step': 'WAIT'}
def disk_create_from(options, disk_id):
return {'id': 200, 'step': 'WAIT'}
def disk_create(options):
return {'id': 200, 'step': 'WAIT', 'disk_id': 9000}
def vm_migrate(vm_id, finalize=False):
return {'id': 9900, 'step': 'WAIT'}
def vm_can_migrate(vm_id):
if vm_id == 152964:
return {'can_migrate': False,
'matched': ['FR-SD5'],
'can_migrate_to': []}
return {'can_migrate': True,
'matched': ['LU-BI1'],
'can_migrate_to': ['LU-BI1']}
def vm_list(options):
ret = [{'ai_active': 0,
'console': 0,
'cores': 1,
'datacenter_id': 3,
'date_created': DateTime('20141008T16:13:59'),
'date_updated': DateTime('20150319T11:11:31'),
'description': None,
'disks_id': [4969232],
'flex_shares': 0,
'hostname': 'vm1426759833',
'id': 152966,
'ifaces_id': [156572],
'memory': 256,
'state': 'running',
'vm_max_memory': 2048},
{'ai_active': 0,
'console': 0,
'cores': 1,
'datacenter_id': 3,
'date_created': DateTime('20141008T16:13:59'),
'date_updated': DateTime('20150319T11:11:31'),
'description': None,
'disks_id': [4969232],
'flex_shares': 0,
'hostname': 'vm1426759844',
'id': 152964,
'ifaces_id': [156572],
'memory': 256,
'state': 'running',
'vm_max_memory': 2048},
{'ai_active': 0,
'console': 0,
'cores': 1,
'datacenter_id': 1,
'date_created': DateTime('20150319T11:14:13'),
'date_updated': DateTime('20150319T11:14:55'),
'description': None,
'disks_id': [4969249],
'flex_shares': 0,
'hostname': 'server01',
'id': 152967,
'ifaces_id': [156573],
'memory': 256,
'state': 'running',
'vm_max_memory': 2048},
{'ai_active': 0,
'console': 0,
'cores': 1,
'datacenter_id': 1,
'date_created': DateTime('20150319T11:14:13'),
'date_updated': DateTime('20150319T11:14:55'),
'description': None,
'disks_id': [4969250],
'flex_shares': 0,
'hostname': 'server02',
'id': 152968,
'ifaces_id': [156574],
'memory': 256,
'state': 'halted',
'vm_max_memory': 2048}]
options.pop('items_per_page', None)
for fkey in options:
ret = [vm for vm in ret if vm[fkey] == options[fkey]]
return ret
def vm_info(id):
ret = [{'ai_active': 0,
'console': 0,
'console_url': 'console.gandi.net',
'cores': 1,
'datacenter_id': 3,
'date_created': DateTime('20150319T11:10:34'),
'date_updated': DateTime('20150319T11:11:31'),
'description': None,
'disks': [{'can_snapshot': True,
'datacenter_id': 3,
'date_created': DateTime('20150319T11:10:34'),
'date_updated': DateTime('20150319T11:10:58'),
'id': 4969232,
'is_boot_disk': True,
'kernel_cmdline': {'console': 'ttyS0',
'nosep': True,
'ro': True,
'root': '/dev/sda'},
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 7 64 bits (HVM)',
'name': 'sys_1426759833',
'size': 3072,
'snapshot_profile': None,
'snapshots_id': [],
'source': 1401327,
'state': 'created',
'total_size': 3072,
'type': 'data',
'visibility': 'private',
'vms_id': [152966]}],
'disks_id': [4969232],
'flex_shares': 0,
'graph_urls': {'vcpu': [''], 'vdi': [''], 'vif': ['']},
'hostname': 'vm1426759833',
'id': 152966,
'ifaces': [{'bandwidth': 102400.0,
'datacenter_id': 3,
'date_created': DateTime('20150319T11:10:34'),
'date_updated': DateTime('20150319T11:10:35'),
'id': 156572,
'ips': [{'datacenter_id': 3,
'date_created': DateTime('20150319T11:10:34'),
'date_updated': DateTime('20150319T11:10:36'),
'id': 204557,
'iface_id': 156572,
'ip': '2001:4b98:dc2:43:216:3eff:fece:e25f',
'num': 0,
'reverse': 'xvm6-dc2-fece-e25f.ghst.net',
'state': 'created',
'version': 6}],
'ips_id': [204557],
'num': 0,
'state': 'used',
'type': 'public',
'vlan': None,
'vm_id': 152966}],
'ifaces_id': [156572],
'memory': 256,
'probes': [],
'state': 'running',
'triggers': [],
'vm_max_memory': 2048},
{'ai_active': 0,
'console': 0,
'console_url': 'console.gandi.net',
'cores': 1,
'datacenter_id': 1,
'date_created': DateTime('20150319T11:14:13'),
'date_updated': DateTime('20150319T11:14:55'),
'description': None,
'disks': [{'can_snapshot': True,
'datacenter_id': 1,
'date_created': DateTime('20150319T11:14:13'),
'date_updated': DateTime('20150319T11:14:29'),
'id': 4969249,
'is_boot_disk': True,
'kernel_cmdline': {'console': 'ttyS0',
'nosep': True,
'ro': True,
'root': '/dev/sda'},
'kernel_version': '3.12-x86_64 (hvm)',
'label': 'Debian 7 64 bits (HVM)',
'name': 'sys_server01',
'size': 3072,
'snapshot_profile': None,
'snapshots_id': [],
'source': 1349810,
'state': 'created',
'total_size': 3072,
'type': 'data',
'visibility': 'private',
'vms_id': [152967]}],
'disks_id': [4969249],
'flex_shares': 0,
'graph_urls': {'vcpu': [''], 'vdi': [''], 'vif': ['']},
'hostname': 'server01',
'id': 152967,
'ifaces': [{'bandwidth': 102400.0,
'datacenter_id': 1,
'date_created': DateTime('20150319T11:14:13'),
'date_updated': DateTime('20150319T11:14:16'),
'id': 156573,
'ips': [{'datacenter_id': 1,
'date_created': DateTime('20150317T16:20:10'),
'date_updated': DateTime('20150319T11:14:13'),
'id': 203968,
'iface_id': 156573,
'ip': '95.142.160.181',
'num': 0,
'reverse': 'xvm-160-181.dc0.ghst.net',
'state': 'created',
'version': 4},
{'datacenter_id': 1,
'date_created': DateTime('20150319T11:14:16'),
'date_updated': DateTime('20150319T11:14:16'),
'id': 204558,
'iface_id': 156573,
'ip': '2001:4b98:dc0:47:216:3eff:feb2:3862',
'num': 1,
'reverse': 'xvm6-dc0-feb2-3862.ghst.net',
'state': 'created',
'version': 6}],
'ips_id': [203968, 204558],
'num': 0,
'state': 'used',
'type': 'public',
'vlan': None,
'vm_id': 152967}],
'ifaces_id': [156573],
'memory': 256,
'probes': [],
'state': 'running',
'triggers': [],
'vm_max_memory': 2048},
{'ai_active': 0,
'console': 0,
'console_url': 'console.gandi.net',
'cores': 1,
'datacenter_id': 4,
'date_created': DateTime('20160115T162658'),
'date_updated': DateTime('20160115T162658'),
'description': None,
'disks': [],
'disks_id': [4969250],
'flex_shares': 0,
'graph_urls': {'vcpu': [''], 'vdi': [''], 'vif': ['', '']},
'hostname': 'server02',
'hvm_state': 'unknown',
'id': 152968,
'ifaces': [{'bandwidth': 102400.0,
'datacenter_id': 4,
'date_created': DateTime('20160115T162658'),
'date_updated': DateTime('20160115T162658'),
'id': 1274919,
'ips': [{'datacenter_id': 4,
'date_created': DateTime('20160115T162658'),
'date_updated': DateTime('20160115T162658'),
'id': 351155,
'iface_id': 1274919,
'ip': '213.167.231.3',
'num': 0,
'reverse': 'xvm-231-3.sd3.ghst.net',
'state': 'created',
'version': 4},
{'datacenter_id': 4,
'date_created': DateTime('20160115T162658'),
'date_updated': DateTime('20160115T162658'),
'id': 352862,
'iface_id': 1274919,
'ip': '2001:4b98:c001:1:216:3eff:fec5:c104',
'num': 1,
'reverse': 'xvm6-c001-fec5-c104.ghst.net',
'state': 'created',
'version': 6}],
'ips_id': [351155, 352862],
'num': 0,
'state': 'used',
'type': 'public',
'vlan': {'id': 717, 'name': 'pouet'},
'vm_id': 227627},
{'bandwidth': 102400.0,
'datacenter_id': 4,
'date_created': DateTime('20160115T162658'),
'date_updated': DateTime('20160115T162658'),
'id': 1416,
'ips': [{'datacenter_id': 1,
'date_created': DateTime('20160115T162658'),
'date_updated': DateTime('20160115T162702'),
'id': 2361,
'iface_id': 1416,
'ip': '192.168.232.252',
'num': 0,
'reverse': '',
'state': 'created',
'version': 4}],
'ips_id': [2361],
'num': 1,
'state': 'used',
'type': 'private',
'vlan': {'id': 717, 'name': 'pouet'},
'vm_id': 227627}],
'ifaces_id': [1274919, 1416],
'memory': 236,
'probes': [],
'state': 'halted',
'triggers': [],
'vm_max_memory': 2048}]
vms = dict([(vm['id'], vm) for vm in ret])
return vms[id]
def metric_query(query):
vif_bytes_all = [
{'direction': ['in'],
'metric': 'vif.bytes',
'points': [{'timestamp': '2015-03-18T10:00:00', 'value': 24420.0},
{'timestamp': '2015-03-18T11:00:00', 'value': 22370.0},
{'timestamp': '2015-03-18T12:00:00', 'value': 46680.0},
{'timestamp': '2015-03-18T13:00:00', 'value': 61664.0},
{'timestamp': '2015-03-18T14:00:00', 'value': 142789.0},
{'timestamp': '2015-03-18T15:00:00', 'value': 35633.0},
{'timestamp': '2015-03-18T16:00:00', 'value': 213987.0},
{'timestamp': '2015-03-18T17:00:00', 'value': 80055.0},
{'timestamp': '2015-03-18T18:00:00', 'value': 57690.0},
{'timestamp': '2015-03-18T19:00:00', 'value': 83508.0},
{'timestamp': '2015-03-18T20:00:00', 'value': 115038.0},
{'timestamp': '2015-03-18T21:00:00', 'value': 71923.0},
{'timestamp': '2015-03-18T22:00:00', 'value': 259466.0},
{'timestamp': '2015-03-18T23:00:00', 'value': 301198.0},
{'timestamp': '2015-03-19T00:00:00', 'value': 69579.0},
{'timestamp': '2015-03-19T01:00:00', 'value': 99998.0},
{'timestamp': '2015-03-19T02:00:00', 'value': 53706.0},
{'timestamp': '2015-03-19T03:00:00', 'value': 55539.0},
{'timestamp': '2015-03-19T04:00:00', 'value': 60018.0},
{'timestamp': '2015-03-19T05:00:00', 'value': 23000.0},
{'timestamp': '2015-03-19T06:00:00', 'value': 57812.0},
{'timestamp': '2015-03-19T07:00:00', 'value': 984992.0},
{'timestamp': '2015-03-19T08:00:00', 'value': 315608.0},
{'timestamp': '2015-03-19T09:00:00', 'value': 77852.0}],
'resource_id': 152967,
'resource_type': 'vm',
'type': ['public']},
{'direction': ['out'],
'metric': 'vif.bytes',
'points': [{'timestamp': '2015-03-18T10:00:00', 'value': 5335.0},
{'timestamp': '2015-03-18T11:00:00', 'value': 8763.0},
{'timestamp': '2015-03-18T12:00:00', 'value': 43790.0},
{'timestamp': '2015-03-18T13:00:00', 'value': 73345.0},
{'timestamp': '2015-03-18T14:00:00', 'value': 259536.0},
{'timestamp': '2015-03-18T15:00:00', 'value': 18595.0},
{'timestamp': '2015-03-18T16:00:00', 'value': 751379.0},
{'timestamp': '2015-03-18T17:00:00', 'value': 150840.0},
{'timestamp': '2015-03-18T18:00:00', 'value': 43115.0},
{'timestamp': '2015-03-18T19:00:00', 'value': 593737.0},
{'timestamp': '2015-03-18T20:00:00', 'value': 619675.0},
{'timestamp': '2015-03-18T21:00:00', 'value': 67605.0},
{'timestamp': '2015-03-18T22:00:00', 'value': 300711.0},
{'timestamp': '2015-03-18T23:00:00', 'value': 380400.0},
{'timestamp': '2015-03-19T00:00:00', 'value': 62705.0},
{'timestamp': '2015-03-19T01:00:00', 'value': 100512.0},
{'timestamp': '2015-03-19T02:00:00', 'value': 47963.0},
{'timestamp': '2015-03-19T03:00:00', 'value': 50301.0},
{'timestamp': '2015-03-19T04:00:00', 'value': 48572.0},
{'timestamp': '2015-03-19T05:00:00', 'value': 6263.0},
{'timestamp': '2015-03-19T06:00:00', 'value': 67014.0},
{'timestamp': '2015-03-19T07:00:00', 'value': 777215.0},
{'timestamp': '2015-03-19T08:00:00', 'value': 495497.0},
{'timestamp': '2015-03-19T09:00:00', 'value': 660825.0}],
'resource_id': 152967,
'resource_type': 'vm',
'type': ['public']}]
vbd_bytes_all = [
{'direction': ['read'],
'metric': 'vbd.bytes',
'points': [{'timestamp': '2015-03-18T10:00:00', 'value': 13824000.0},
{'timestamp': '2015-03-18T11:00:00', 'value': 5644288.0},
{'timestamp': '2015-03-18T12:00:00', 'value': 0.0},
{'timestamp': '2015-03-18T13:00:00', 'value': 13516800.0},
{'timestamp': '2015-03-18T14:00:00', 'value': 27918336.0},
{'timestamp': '2015-03-18T15:00:00', 'value': 9150464.0},
{'timestamp': '2015-03-18T16:00:00', 'value': 64323584.0},
{'timestamp': '2015-03-18T17:00:00', 'value': 29974528.0},
{'timestamp': '2015-03-18T18:00:00', 'value': 761856.0},
{'timestamp': '2015-03-18T19:00:00', 'value': 41775104.0},
{'timestamp': '2015-03-18T20:00:00', 'value': 14286848.0},
{'timestamp': '2015-03-18T21:00:00', 'value': 1073152.0},
{'timestamp': '2015-03-18T22:00:00', 'value': 387248128.0},
{'timestamp': '2015-03-18T23:00:00', 'value': 13754368.0},
{'timestamp': '2015-03-19T00:00:00', 'value': 2056192.0},
{'timestamp': '2015-03-19T01:00:00', 'value': 9990144.0},
{'timestamp': '2015-03-19T02:00:00', 'value': 643072.0},
{'timestamp': '2015-03-19T03:00:00', 'value': 6148096.0},
{'timestamp': '2015-03-19T04:00:00', 'value': 8974336.0},
{'timestamp': '2015-03-19T05:00:00', 'value': 782336.0},
{'timestamp': '2015-03-19T06:00:00', 'value': 12214272.0},
{'timestamp': '2015-03-19T07:00:00', 'value': 29261824.0},
{'timestamp': '2015-03-19T08:00:00', 'value': 144080896.0},
{'timestamp': '2015-03-19T09:00:00', 'value': 39198720.0}],
'resource_id': 152967,
'resource_type': 'vm'},
{'direction': ['write'],
'metric': 'vbd.bytes',
'points': [{'timestamp': '2015-03-18T10:00:00', 'value': 217088.0},
{'timestamp': '2015-03-18T11:00:00', 'value': 229376.0},
{'timestamp': '2015-03-18T12:00:00', 'value': 401408.0},
{'timestamp': '2015-03-18T13:00:00', 'value': 577536.0},
{'timestamp': '2015-03-18T14:00:00', 'value': 3862528.0},
{'timestamp': '2015-03-18T15:00:00', 'value': 217088.0},
{'timestamp': '2015-03-18T16:00:00', 'value': 2363392.0},
{'timestamp': '2015-03-18T17:00:00', 'value': 1773568.0},
{'timestamp': '2015-03-18T18:00:00', 'value': 217088.0},
{'timestamp': '2015-03-18T19:00:00', 'value': 3153920.0},
{'timestamp': '2015-03-18T20:00:00', 'value': 2039808.0},
{'timestamp': '2015-03-18T21:00:00', 'value': 606208.0},
{'timestamp': '2015-03-18T22:00:00', 'value': 12505088.0},
{'timestamp': '2015-03-18T23:00:00', 'value': 675840.0},
{'timestamp': '2015-03-19T00:00:00', 'value': 602112.0},
{'timestamp': '2015-03-19T01:00:00', 'value': 598016.0},
{'timestamp': '2015-03-19T02:00:00', 'value': 483328.0},
{'timestamp': '2015-03-19T03:00:00', 'value': 462848.0},
{'timestamp': '2015-03-19T04:00:00', 'value': 471040.0},
{'timestamp': '2015-03-19T05:00:00', 'value': 487424.0},
{'timestamp': '2015-03-19T06:00:00', 'value': 499712.0},
{'timestamp': '2015-03-19T07:00:00', 'value': 42958848.0},
{'timestamp': '2015-03-19T08:00:00', 'value': 6299648.0},
{'timestamp': '2015-03-19T09:00:00', 'value': 3862528.0}],
'resource_id': 152967,
'resource_type': 'vm'}]
vfs_df_bytes_all = [
{'metric': 'vfs.df.bytes',
'points': [{'timestamp': '2015-11-18T07:19:00',
'value': 10679488512.0},
{'timestamp': '2015-11-18T07:20:00'}],
'resource_id': 163744,
'resource_type': 'paas',
'size': ['free']},
{'metric': 'vfs.df.bytes',
'points': [{'timestamp': '2015-11-18T07:19:00',
'value': 57929728.0},
{'timestamp': '2015-11-18T07:20:00'}],
'resource_id': 163744,
'resource_type': 'paas',
'size': ['used']}]
webacc_requests_cache_all = [
{'cache': ['miss'],
'metric': 'webacc.requests',
'points': [{'timestamp': '2015-11-17T00:00:00', 'value': 2.0},
{'timestamp': '2015-11-18T00:00:00'}],
'resource_id': 163744,
'resource_type': 'paas',
'status': ['2xx']}]
metrics = {'vif.bytes.all': vif_bytes_all,
'vbd.bytes.all': vbd_bytes_all,
'vfs.df.bytes.all': vfs_df_bytes_all,
'webacc.requests.cache.all': webacc_requests_cache_all}
metrics = [item for item in metrics[query['query']]
if item['resource_id'] == query['resource_id'][0]]
return metrics
def disk_list_kernels(dc_id):
ret = {
1: {'linux': ['2.6.18 (deprecated)',
'2.6.27-compat-sysfs (deprecated)',
'2.6.32',
'2.6.27 (deprecated)',
'2.6.32-x86_64',
'2.6.36 (deprecated)',
'2.6.32-x86_64-grsec',
'2.6.36-x86_64 (deprecated)',
'3.2-i386',
'3.2-x86_64',
'3.2-x86_64-grsec',
'3.10-x86_64',
'3.10-i386'],
'linux-hvm': ['3.12-x86_64 (hvm)', 'grub', 'raw']},
2: {'linux': ['2.6.18 (deprecated)',
'2.6.27-compat-sysfs (deprecated)',
'2.6.32',
'2.6.27 (deprecated)',
'2.6.32-x86_64',
'2.6.36 (deprecated)',
'2.6.32-x86_64-grsec',
'2.6.36-x86_64 (deprecated)',
'3.2-i386',
'3.2-x86_64',
'3.2-x86_64-grsec',
'3.10-x86_64',
'3.10-i386'],
'linux-hvm': ['3.12-x86_64 (hvm)', 'grub', 'raw']},
3: {'linux': ['2.6.32',
'2.6.27 (deprecated)',
'2.6.32-x86_64',
'2.6.32-x86_64-grsec',
'3.2-i386',
'3.2-x86_64',
'3.2-x86_64-grsec',
'3.10-x86_64',
'3.10-i386'],
'linux-hvm': ['3.12-x86_64 (hvm)', 'grub', 'raw']},
4: {'linux': ['2.6.32',
'2.6.27 (deprecated)',
'2.6.32-x86_64',
'2.6.32-x86_64-grsec',
'3.2-i386',
'3.2-x86_64',
'3.2-x86_64-grsec',
'3.10-x86_64',
'3.10-i386',
'3.12-x86_64'],
'linux-hvm': ['3.12-x86_64 (hvm)', 'grub', 'raw']}}
return ret.get(dc_id, ret[4])
def account_info():
return {'average_credit_cost': 0.0,
'credits': 2335360,
'cycle_day': 23,
'date_credits_expiration': DateTime('20160319T10:07:24'),
'fullname': 'Peter Parker',
'handle': 'PXP561-GANDI',
'id': 2920674,
'products': None,
'rating_enabled': True,
'resources': {'available': None,
'expired': None,
'granted': None,
'used': None},
'share_definition': None}
def rating_list():
return [{'bw_out': None,
'cpu': {'default': 168},
'disk_data': {'default': 135},
'disk_snapshot': None,
'disk_snapshot_auto': None,
'instance': {'default': 0},
'ip': {'v4_public': 210, 'v6': 0},
'ram': {'default': 120},
'rproxy': None,
'rproxy_server': None,
'rproxy_ssl': None,
'timestamp': DateTime('20150319T15:07:24')}]
def vm_disk_detach(vm_id, disk_id):
if vm_id == 152967 and disk_id == 4970079:
return {'id': 200, 'step': 'WAIT'}
def vm_iface_detach(vm_id, iface_id):
if vm_id == 152967 and iface_id == 156573:
return {'id': 200, 'step': 'WAIT'}
def vm_iface_attach(vm_id, iface_id):
if vm_id == 152966 and iface_id == 156573:
return {'id': 200, 'step': 'WAIT'}
if vm_id == 152967 and iface_id == 156572:
return {'id': 200, 'step': 'WAIT'}
if vm_id == 152967 and iface_id == 156573:
return {'id': 200, 'step': 'WAIT', 'iface_id': 156573}
def vm_disk_attach(vm_id, disk_id, options):
if vm_id == 152967 and disk_id == 663497:
return {'id': 200, 'step': 'WAIT'}
if vm_id == 152966 and disk_id == 4970079:
return {'id': 200, 'step': 'WAIT'}
if vm_id == 152967 and disk_id == 9000:
return {'id': 200, 'step': 'WAIT'}
def vm_stop(vm_id):
if vm_id in (152967, 152966):
return {'id': 200, 'step': 'WAIT'}
def vm_start(vm_id):
if vm_id in (152967, 152966):
return {'id': 200, 'step': 'WAIT'}
def vm_reboot(vm_id):
if vm_id in (152967, 152966):
return {'id': 200, 'step': 'WAIT'}
def vm_delete(vm_id):
if vm_id in (152968, 152967, 152966):
return {'id': 200, 'step': 'WAIT'}
def vm_update(vm_id, options):
if vm_id in (152967, 152966):
return {'id': 200, 'step': 'WAIT'}
def vm_create_from(vm_spec, disk_spec, src_disk_id):
return [{'id': 300, 'step': 'WAIT'}]
def vlan_list(options):
ret = [{'datacenter_id': 1,
'gateway': '10.7.13.254',
'id': 123,
'name': 'vlantest',
'state': 'created',
'subnet': '10.7.13.0/24',
'uuid': 321},
{'datacenter_id': 1,
'gateway': '192.168.232.254',
'id': 717,
'name': 'pouet',
'state': 'created',
'subnet': '192.168.232.0/24',
'uuid': 720},
{'datacenter_id': 4,
'gateway': '10.7.242.254',
'id': 999,
'name': 'intranet',
'state': 'created',
'subnet': '10.7.242.0/24',
'uuid': 421}]
options.pop('items_per_page', None)
for fkey in options:
ret = [vlan for vlan in ret if vlan[fkey] == options[fkey]]
return ret
def vlan_info(id):
vlans = vlan_list({})
vlans = dict([(vlan['id'], vlan) for vlan in vlans])
return vlans[id]
def vlan_delete(vlan_id):
return {'id': 200, 'step': 'WAIT'}
def vlan_create(options):
return {'id': 200, 'step': 'WAIT'}
def vlan_update(vlan_id, options):
return {'id': 200, 'step': 'WAIT'}
def iface_create(options):
if 'ip' in options:
return {'id': 200, 'step': 'WAIT', 'iface_id': 156572}
return {'id': 200, 'step': 'WAIT', 'iface_id': 156573}
def iface_delete(ip_id):
return {'id': 200, 'step': 'WAIT'}
def iface_list(options):
ret = [{'bandwidth': 102400.0,
'datacenter_id': 1,
'date_created': DateTime('20140423T00:00:00'),
'date_updated': DateTime('20140423T00:00:00'),
'id': 156573,
'ips_id': [203968, 204558],
'ips': [{'datacenter_id': 1,
'date_created': DateTime('20150317T16:20:10'),
'date_updated': DateTime('20150319T11:14:13'),
'id': 203968,
'iface_id': 156573,
'ip': '95.142.160.181',
'num': 0,
'reverse': 'xvm-160-181.dc0.ghst.net',
'state': 'created',
'version': 4},
{'datacenter_id': 1,
'date_created': DateTime('20150319T11:14:16'),
'date_updated': DateTime('20150319T11:14:16'),
'id': 204558,
'iface_id': 156573,
'ip': '2001:4b98:dc0:47:216:3eff:feb2:3862',
'num': 1,
'reverse': 'xvm6-dc0-feb2-3862.ghst.net',
'state': 'created',
'version': 6}],
'num': 0,
'state': 'used',
'type': 'public',
'vlan': None,
'vm_id': 152967},
{'bandwidth': 102400.0,
'datacenter_id': 1,
'date_created': DateTime('20141009T00:00:00'),
'date_updated': DateTime('20141105T00:00:00'),
'id': 1416,
'ips_id': [2361],
'ips': [{'datacenter_id': 1,
'date_created': DateTime('20160115T162658'),
'date_updated': DateTime('20160115T162702'),
'id': 2361,
'iface_id': 1416,
'ip': '192.168.232.252',
'num': 0,
'reverse': '',
'state': 'created',
'version': 4}],
'num': None,
'state': 'used',
'type': 'private',
'vlan': {'id': 717, 'name': 'pouet'},
'vm_id': 152968},
{'bandwidth': 204800.0,
'datacenter_id': 1,
'date_created': DateTime('20150105T00:00:00'),
'date_updated': DateTime('20150105T00:00:00'),
'id': 1914,
'ips': [{'datacenter_id': 1,
'date_created': DateTime('20160115T162658'),
'date_updated': DateTime('20160115T162702'),
'id': 2361,
'iface_id': 1914,
'ip': '192.168.232.253',
'num': 0,
'reverse': '',
'state': 'created',
'version': 4}],
'ips_id': [2361],
'num': None,
'state': 'used',
'type': 'private',
'vlan': {'id': 717, 'name': 'pouet'},
'vm_id': 152968},
{'bandwidth': 204800.0,
'datacenter_id': 1,
'date_created': DateTime('20150105T00:00:00'),
'date_updated': DateTime('20150105T00:00:00'),
'id': 156572,
'ips_id': [204557],
'ips': [{'datacenter_id': 3,
'date_created': DateTime('20150319T11:10:34'),
'date_updated': DateTime('20150319T11:10:36'),
'id': 204557,
'iface_id': 156572,
'ip': '10.50.10.10',
'num': 0,
'reverse': 'xvm6-dc2-fece-e25f.ghst.net',
'state': 'created',
'version': 4}],
'num': None,
'state': 'free',
'type': 'private',
'vlan': None,
'vm_id': None}]
options.pop('items_per_page', None)
for fkey in options:
if fkey == 'vlan':
ret_ = []
for iface in ret:
if iface['vlan'] and iface['vlan']['name'] == options['vlan']:
ret_.append(iface)
ret = ret_
elif fkey == 'vlan_id':
ret_ = []
for iface in ret:
if iface['vlan'] and iface['vlan']['id'] == options['vlan_id']:
ret_.append(iface)
ret = ret_
else:
ret = [iface for iface in ret if iface[fkey] == options[fkey]]
return ret
def iface_info(iface_id):
ifaces = iface_list({})
ifaces = dict([(iface['id'], iface) for iface in ifaces])
return ifaces[iface_id]
def ip_list(options):
ips = [{'datacenter_id': 1,
'date_created': DateTime('20150317T16:20:10'),
'date_updated': DateTime('20150319T11:14:13'),
'id': 203968,
'iface_id': 156573,
'ip': '95.142.160.181',
'num': 0,
'reverse': 'xvm-160-181.dc0.ghst.net',
'state': 'created',
'version': 4},
{'datacenter_id': 3,
'date_created': DateTime('20150319T11:10:34'),
'date_updated': DateTime('20150319T11:10:36'),
'id': 204557,
'iface_id': 156572,
'ip': '2001:4b98:dc2:43:216:3eff:fece:e25f',
'num': 0,
'reverse': 'xvm6-dc2-fece-e25f.ghst.net',
'state': 'created',
'version': 6},
{'datacenter_id': 1,
'date_created': DateTime('20150319T11:14:16'),
'date_updated': DateTime('20150319T11:14:16'),
'id': 204558,
'iface_id': 156573,
'ip': '2001:4b98:dc0:47:216:3eff:feb2:3862',
'num': 1,
'reverse': 'xvm6-dc0-feb2-3862.ghst.net',
'state': 'created',
'version': 6},
{'datacenter_id': 1,
'date_created': DateTime('20160115T162658'),
'date_updated': DateTime('20160115T162702'),
'id': 2361,
'iface_id': 1914,
'ip': '192.168.232.253',
'num': 0,
'reverse': '',
'state': 'created',
'version': 4},
{'datacenter_id': 1,
'date_created': DateTime('20160115T162658'),
'date_updated': DateTime('20160115T162702'),
'id': 2361,
'iface_id': 1416,
'ip': '192.168.232.252',
'num': 0,
'reverse': '',
'state': 'created',
'version': 4}]
options.pop('items_per_page', None)
for fkey in options:
ret = []
for ip in ips:
if isinstance(options[fkey], list):
if ip[fkey] in options[fkey]:
ret.append(ip)
elif ip[fkey] == options[fkey]:
ret.append(ip)
ips = ret
return ips
def ip_info(ip_id):
ips = ip_list({})
ips = dict([(ip['id'], ip) for ip in ips])
return ips[ip_id]
def ip_update(ip_id, options):
return {'id': 200, 'step': 'WAIT'}
def ssh_list(options):
ret = [{'fingerprint': 'b3:11:67:10:2e:1b:a5:66:ed:16:24:98:3e:2e:ed:f5',
'id': 134,
'name': 'default',
'value': 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC63QZAW3tusdv+JuyzOoXTND9/wxKogMwZbxBPPtoN7Hjnyn0kUUHMJ6ji5xpbatRYKOeGAoZDW2TXojvbJdQj7tWsRr7ES0qB9qhDGVSDIJWRQ6f9MQCCLjV5tpBTAwb unknown@lol.cat'}, # noqa
{'fingerprint': '09:11:21:e3:90:3c:7d:d5:06:d9:6f:f9:36:e1:99:a6',
'id': 141,
'name': 'mysecretkey'}]
options.pop('items_per_page', None)
for fkey in options:
ret = [vm for vm in ret if vm[fkey] == options[fkey]]
return ret
def ssh_info(key_id):
keys = ssh_list({})
keys = dict([(key['id'], key) for key in keys])
return keys[key_id]
def ssh_delete(key_id):
return {'id': 200, 'step': 'WAIT'}
def ssh_create(params):
return {'fingerprint': 'b3:11:67:10:2e:1b:a5:55:ed:16:24:98:3e:2e:ed:f5',
'id': 145,
'name': params['name'],
'value': params['value']}
def snapshotprofile_list(options):
ret = [{'id': 1,
'kept_total': 2,
'name': 'minimal',
'quota_factor': 1.2,
'schedules': [{'kept_version': 2, 'name': 'daily'}]},
{'id': 2,
'kept_total': 7,
'name': 'full_week',
'quota_factor': 1.7,
'schedules': [{'kept_version': 7, 'name': 'daily'}]},
{'id': 3,
'kept_total': 10,
'name': 'security',
'quota_factor': 2.0,
'schedules': [{'kept_version': 3, 'name': 'hourly6'},
{'kept_version': 6, 'name': 'daily'},
{'kept_version': 1, 'name': 'weekly4'}]}]
for fkey in options:
ret = [snp for snp in ret if snp[fkey] == options[fkey]]
return ret
def rproxy_list(options):
ret = [{'datacenter_id': 3,
'date_created': DateTime('20160115T162658'),
'id': 12138,
'name': 'webacc01',
'probe': {'enable': True,
'host': None,
'interval': None,
'method': None,
'response': None,
'threshold': None,
'timeout': None,
'url': None,
'window': None},
'servers': [{'fallback': False,
'id': 14988,
'ip': '195.142.160.181',
'port': 80,
'rproxy_id': 132691,
'state': 'running'}],
'ssl_enable': False,
'state': 'running',
'uuid': 12138,
'vhosts': []},
{'datacenter_id': 1,
'date_created': DateTime('20160115T162658'),
'id': 13263,
'name': 'testwebacc',
'probe': {'enable': True,
'host': '95.142.160.181',
'interval': 10,
'method': 'GET',
'response': 200,
'threshold': 3,
'timeout': 5,
'url': '/',
'window': 5},
'servers': [{'fallback': False,
'id': 4988,
'ip': '95.142.160.181',
'port': 80,
'rproxy_id': 13269,
'state': 'running'}],
'ssl_enable': False,
'state': 'running',
'uuid': 13263,
'vhosts': [{'cert_id': None,
'id': 5171,
'name': 'pouet.iheartcli.com',
'rproxy_id': 13263,
'state': 'running'}]}]
options.pop('items_per_page', None)
for fkey in options:
ret = [rpx for rpx in ret if rpx[fkey] == options[fkey]]
return ret
def rproxy_delete(rproxy_id):
return {'id': 200, 'step': 'WAIT'}
def rproxy_info(rproxy_id):
ret = [{'datacenter': {'country': 'France',
'dc_code': 'FR-SD2',
'id': 1,
'iso': 'FR',
'name': 'Equinix Paris'},
'date_created': DateTime('20160115T162658'),
'id': 13263,
'lb': {'algorithm': 'client-ip'},
'name': 'testwebacc',
'probe': {'enable': True,
'host': '95.142.160.181',
'interval': 10,
'method': 'GET',
'response': 200,
'threshold': 3,
'timeout': 5,
'url': '/',
'window': 5},
'servers': [{'fallback': False,
'id': 4988,
'ip': '95.142.160.181',
'port': 80,
'rproxy_id': 13269,
'state': 'running'}],
'ssl_enable': False,
'state': 'running',
'uuid': 13263,
'vhosts': [{'cert_id': None,
'id': 5171,
'name': 'pouet.iheartcli.com',
'rproxy_id': 13263,
'state': 'running'}]},
{'datacenter': {'country': 'France',
'dc_code': 'FR-SD2',
'id': 1,
'iso': 'FR',
'name': 'Equinix Paris'},
'date_created': DateTime('20160115T162658'),
'id': 12138,
'lb': {'algorithm': 'client-ip'},
'name': 'webacc01',
'probe': {'enable': True,
'host': None,
'interval': None,
'method': None,
'response': None,
'threshold': None,
'timeout': None,
'url': None,
'window': None},
'servers': [{'fallback': False,
'id': 14988,
'ip': '195.142.160.181',
'port': 80,
'rproxy_id': 132691,
'state': 'running'}],
'ssl_enable': False,
'state': 'running',
'uuid': 12138,
'vhosts': []}]
rpx = dict([(rpx['id'], rpx) for rpx in ret])
return rpx[rproxy_id]
def rproxy_update(rproxy_id, params):
return {'id': 200, 'step': 'WAIT'}
def rproxy_create(params):
return {'id': 200, 'step': 'WAIT'}
def rproxy_probe_disable(rproxy_id):
return {'id': 200, 'step': 'WAIT'}
def rproxy_probe_enable(rproxy_id):
return {'id': 200, 'step': 'WAIT'}
def rproxy_vhost_list():
ret = [{'cert_id': None,
'id': 5177,
'name': 'pouet.iheartcli.com',
'rproxy_id': 13269,
'state': 'running'}]
return ret
def rproxy_vhost_delete(vhost):
return {'id': 200, 'step': 'WAIT'}
def rproxy_vhost_create(rproxy_id, vhost):
return {'id': 200, 'step': 'WAIT'}
def rproxy_probe_test(rproxy_id, params):
return {'servers': [{'server': 4988, 'status': 200, 'timeout': 1.0}],
'status': 200,
'timeout': 1.0}
def rproxy_probe_update(rproxy_id, params):
return {'id': 200, 'step': 'WAIT'}
def rproxy_server_create(rproxy_id, params):
return {'id': 200, 'step': 'WAIT'}
def rproxy_server_list(params):
return [{'fallback': False,
'id': 14988,
'ip': '195.142.160.181',
'port': 80,
'rproxy_id': 132691,
'state': 'running'}]
def rproxy_server_delete(server_id):
return {'id': 200, 'step': 'WAIT'}
def rproxy_server_enable(server_id):
return {'id': 200, 'step': 'WAIT'}
def rproxy_server_disable(server_id):
return {'id': 200, 'step': 'WAIT'}
gandi.cli-1.2/gandi/cli/tests/fixtures/_domain.py 0000644 0001750 0001750 00000015526 12656121545 022726 0 ustar sayoun sayoun 0000000 0000000 from datetime import datetime
try:
# python3
from xmlrpc.client import DateTime
except ImportError:
# python2
from xmlrpclib import DateTime
type_list = list
def list(options):
return [{'authinfo': 'abcdef0001',
'autorenew': None,
'zone_id': 424242,
'tags': 'bla',
'contacts': {'owner': {'handle': 'AA1-GANDI'},
'admin': {'handle': 'AA2-GANDI'},
'bill': {'handle': 'AA3-GANDI'},
'reseller': {'handle': 'AA4-GANDI'},
'tech': {'handle': 'AA5-GANDI'}},
'date_created': datetime(2010, 9, 22, 15, 6, 18),
'date_delete': datetime(2015, 10, 19, 19, 14, 0),
'date_hold_begin': datetime(2015, 9, 22, 22, 0, 0),
'date_registry_creation': datetime(2010, 9, 22, 13, 6, 16),
'date_registry_end': datetime(2015, 9, 22, 0, 0, 0),
'date_updated': datetime(2014, 9, 21, 3, 10, 7),
'nameservers': ['a.dns.gandi.net', 'b.dns.gandi.net',
'c.dns.gandi.net'],
'services': ['gandidns'],
'fqdn': 'iheartcli.com',
'id': 236816922,
'status': [],
'tld': 'com'},
{'authinfo': 'abcdef0002',
'autorenew': None,
'contacts': {'admin': {'handle': 'PXP561-GANDI', 'id': 2920674},
'bill': {'handle': 'PXP561-GANDI', 'id': 2920674},
'owner': {'handle': 'PXP561-GANDI', 'id': 2920674},
'reseller': None,
'tech': {'handle': 'PXP561-GANDI', 'id': 2920674}},
'date_created': DateTime('20130410T12:46:05'),
'date_delete': DateTime('20160507T07:14:00'),
'date_hold_begin': DateTime('20160410T00:00:00'),
'date_registry_creation': DateTime('20140410T10:46:04'),
'date_registry_end': DateTime('20140410T00:00:00'),
'date_updated': DateTime('20150313T10:30:05'),
'date_hold_end': DateTime('20151020T20:00:00'),
'date_pending_delete_end': DateTime('20151119T00:00:00'),
'date_renew_begin': DateTime('20120101T00:00:00'),
'date_restore_end': DateTime('20151119T00:00:00'),
'fqdn': 'cli.sexy',
'id': 3412062241,
'nameservers': ['a.dns.gandi.net', 'b.dns.gandi.net',
'c.dns.gandi.net'],
'services': ['gandidns', 'gandimail', 'paas'],
'status': [],
'tags': [],
'tld': 'sexy',
'zone_id': None}]
def info(id):
domain = dict([(domain['fqdn'], domain) for domain in list({})])
return domain[id]
def available(domains):
ret = {}
for domain in domains:
if 'unavailable' in domain:
ret[domain] = 'unavailable'
elif 'pending' in domain:
ret[domain] = 'pending'
else:
ret[domain] = 'available'
return ret
def create(domain, params):
return {'id': 400, 'step': 'WAIT'}
def renew(domain, params):
return {'id': 400, 'step': 'WAIT'}
def mailbox_list(domain, options):
return [{'login': 'admin',
'responder': {'active': False},
'quota': {'granted': 0, 'used': 233}}]
def mailbox_info(domain, login):
ret = {'aliases': [],
'login': 'admin',
'responder': {'active': False, 'text': None},
'fallback_email': '',
'quota': {'granted': 0, 'used': 233}}
return ret
def mailbox_create(domain, login, params):
return {'id': 400, 'step': 'WAIT'}
def mailbox_delete(domain, login):
return {'id': 400, 'step': 'WAIT'}
def mailbox_update(domain, login, params):
return {'id': 400, 'step': 'WAIT'}
def mailbox_alias_set(domain, login, aliases):
return {'id': 400, 'step': 'WAIT'}
def mailbox_purge(domain, login):
return {'id': 400, 'step': 'WAIT'}
def forward_list(domain, options):
return [{'source': 'admin',
'destinations': ['admin@cli.sexy', 'grumpy@cat.lol']},
{'source': 'contact',
'destinations': ['contact@cli.sexy']}]
def forward_create(domain, source, options):
return [{'source': source,
'destinations': options['destinations']}]
def forward_update(domain, source, options):
return [{'source': source,
'destinations': options['destinations']}]
def forward_delete(domain, source):
return True
def zone_record_list(zone_id, version, options=None):
ret = [{'id': 337085079,
'name': '*',
'ttl': 10800,
'type': 'A',
'value': '73.246.104.110'},
{'id': 337085078,
'name': '@',
'ttl': 10800,
'type': 'A',
'value': '73.246.104.110'},
{'id': 337085081,
'name': 'much',
'ttl': 10800,
'type': 'A',
'value': '192.243.24.132'},
{'id': 337085072,
'name': 'blog',
'ttl': 10800,
'type': 'CNAME',
'value': 'blogs.vip.gandi.net.'},
{'id': 337085082,
'name': 'cloud',
'ttl': 10800,
'type': 'CNAME',
'value': 'gpaas6.dc0.gandi.net.'},
{'id': 337085075,
'name': 'imap',
'ttl': 10800,
'type': 'CNAME',
'value': 'access.mail.gandi.net.'},
{'id': 337085071,
'name': 'pop',
'ttl': 10800,
'type': 'CNAME',
'value': 'access.mail.gandi.net.'},
{'id': 337085074,
'name': 'smtp',
'ttl': 10800,
'type': 'CNAME',
'value': 'relay.mail.gandi.net.'},
{'id': 337085073,
'name': 'webmail',
'ttl': 10800,
'type': 'CNAME',
'value': 'agent.mail.gandi.net.'},
{'id': 337085077,
'name': '@',
'ttl': 10800,
'type': 'MX',
'value': '50 fb.mail.gandi.net.'},
{'id': 337085076,
'name': '@',
'ttl': 10800,
'type': 'MX',
'value': '10 spool.mail.gandi.net.'}]
options = options or {}
options.pop('items_per_page', None)
def match(zone, options):
for fkey in options:
if zone[fkey] != options[fkey]:
return
return zone
ret = [zone for zone in ret if match(zone, options)]
return ret
def zone_version_new(zone_id):
return 242424
def zone_record_add(zone_id, version, data):
return
def zone_version_set(zone_id, version):
return
def zone_record_delete(zone_id, version, data):
return
def zone_record_update(zone_id, version, opts, data):
return
def zone_record_set(zone_id, version, data):
return
gandi.cli-1.2/gandi/cli/tests/fixtures/_contact.py 0000644 0001750 0001750 00000003251 12746404125 023100 0 ustar sayoun sayoun 0000000 0000000
def list(options):
return [{'handle': 'AA1-GANDI'},
{'handle': 'AA2-GANDI'},
{'handle': 'AA3-GANDI'},
{'handle': 'AA4-GANDI'},
{'handle': 'AA5-GANDI'},
{'handle': 'TEST1-GANDI'},
{'handle': 'PXP561-GANDI', 'id': 2920674,
'prepaid': {'amount': '1337.42',
'currency': 'EUR'}}
]
def info(id='PXP561-GANDI'):
contact = dict([(contact['handle'], contact) for contact in list({})])
return contact[id]
def create(params):
return {'handle': 'PP0000-GANDI'}
def create_dry_run(params):
errors = []
if params['phone'] == '555-123-456':
errors.append({'attr': None,
'error': '!EC_STRMATCH',
'field': 'phone',
'field_type': 'String',
'reason': "phone: string '555-123-456' does not "
"match '^\\+\\d{1,3}\\.\\d+$'"})
if params['email'] == 'green.goblin@spiderman.org':
# add an unknown error
errors.append({'attr': ['Sun, Mercury, Venus, Earth, Mars, Jupiter,'
'Saturn, Uranus, Neptune'],
'error': '!EC_ENUMIN',
'field': 'planet',
'field_type': 'Enum',
'reason': 'planet: Pluto not in list Sun, Mercury, '
'Venus, Earth, Mars, Jupiter, '
'Saturn, Uranus, Neptune'})
return errors
def balance(id='PXP561-GANDI'):
contact = dict([(contact['handle'], contact) for contact in list({})])
return contact[id]
gandi.cli-1.2/gandi/cli/tests/fixtures/__init__.py 0000644 0001750 0001750 00000000000 12453203306 023023 0 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/gandi/cli/tests/fixtures/_version.py 0000644 0001750 0001750 00000000063 12507007717 023131 0 ustar sayoun sayoun 0000000 0000000
def info():
return {'api_version': '3.3.42'}
gandi.cli-1.2/gandi/cli/tests/fixtures/_operation.py 0000644 0001750 0001750 00000013232 13164644453 023452 0 ustar sayoun sayoun 0000000 0000000 try:
# python3
from xmlrpc.client import DateTime
except ImportError:
# python2
from xmlrpclib import DateTime
type_list = list
def list(options):
ret = [{'date_created': DateTime('20150915T18:29:16'),
'date_start': None,
'date_updated': DateTime('20150915T18:29:17'),
'errortype': None,
'eta': -1863666,
'id': 100100,
'cert_id': None,
'infos': {'extras': {},
'id': '',
'label': 'iheartcli.com',
'product_action': 'renew',
'product_name': 'com',
'product_type': 'domain',
'quantity': ''},
'last_error': None,
'params': {'auth_id': 99999999,
'current_year': 2015,
'domain': 'iheartcli.com',
'domain_id': 1234567,
'duration': 1,
'param_type': 'domain',
'remote_addr': '127.0.0.1',
'session_id': 2920674,
'tld': 'com',
'tracker_id': '621cb9f4-472d-4cc1-b4b9-b18cc61e2914'},
'session_id': 2920674,
'source': 'PXP561-GANDI',
'step': 'BILL',
'type': 'domain_renew'},
{'date_created': DateTime('20150505T00:00:00'),
'date_start': None,
'date_updated': DateTime('20150505T00:00:00'),
'errortype': None,
'eta': 0,
'id': 100200,
'cert_id': None,
'infos': {'extras': {},
'id': '',
'label': '',
'product_action': 'billing_prepaid_add_money',
'product_name': '',
'product_type': 'corporate',
'quantity': ''},
'last_error': None,
'params': {'amount': 50.0,
'auth_id': 99999999,
'param_type': 'prepaid_add_money',
'prepaid_id': 100000,
'remote_addr': '127.0.0.1',
'tracker_id': 'ab0e5e67-6ca7-4afc-8311-f20080f15cf1'},
'session_id': 9844958,
'source': 'PXP561-GANDI',
'step': 'BILL',
'type': 'billing_prepaid_add_money'},
{'step': 'RUN',
'cert_id': 710,
'id': 100300,
'type': 'certificate_update',
'params': {'cert_id': 710,
'param_type': 'certificate_update',
'prepaid_id': 100000,
'inner_step': 'comodo_oper_updated',
'dcv_method': 'email',
'csr': '-----BEGIN CERTIFICATE REQUEST-----'
'MIICxjCCAa4CAQAwgYAxCzAJBgNVBAYTAkZSMQsw'
'0eWfyJJTOypoToCtdGoye507GOsgIysfRWaExay5'
'-----END CERTIFICATE REQUEST-----',
'remote_addr': '127.0.0.1'}},
{'step': 'RUN',
'cert_id': 706,
'id': 100302,
'type': 'certificate_update',
'params': {'cert_id': 706,
'param_type': 'certificate_update',
'prepaid_id': 100000,
'inner_step': 'comodo_oper_updated',
'dcv_method': 'dns',
'csr': '-----BEGIN CERTIFICATE REQUEST-----'
'MIICxjCCAa4CAQAwgYAxCzAJBgNVBAYTAkZSMQsw'
'0eWfyJJTOypoToCtdGoye507GOsgIysfRWaExay5'
'-----END CERTIFICATE REQUEST-----',
'remote_addr': '127.0.0.1'}},
{'step': 'WAIT',
'cert_id': 701,
'id': 100303,
'type': 'certificate_update',
'params': {'cert_id': 706,
'param_type': 'certificate_update',
'prepaid_id': 100000,
'inner_step': 'check_email_sent',
'dcv_method': 'dns',
'remote_addr': '127.0.0.1'}},
{'step': 'RUN',
'id': 99001,
'vm_id': 152967,
'type': 'hosting_migration_vm',
'params': {'inner_step': 'wait_sync'}},
{'step': 'RUN',
'id': 99002,
'vm_id': 152966,
'type': 'hosting_migration_vm',
'params': {'inner_step': 'wait_finalize'}},
]
options.pop('sort_by', None)
options.pop('items_per_page', None)
def compare(op, option):
if isinstance(option, (type_list, tuple)):
return op in option
return op == option
for fkey in options:
ret = [op for op in ret if compare(op.get(fkey), options[fkey])]
return ret
def info(id):
if id == 200:
return {'step': 'DONE'}
if id == 300:
return {'step': 'DONE', 'vm_id': 9000}
if id == 400:
return {'step': 'DONE'}
if id == 600:
return {'step': 'DONE', 'type': 'certificate_update',
'params': {'cert_id': 710,
'param_type': 'certificate_update',
'prepaid_id': 100000,
'remote_addr': '127.0.0.1'}}
if id == 9900:
return {'step': 'DONE',
'type': 'hosting_migration_disk',
'params': {'to_dc_id': 3,
'from_dc_id': 1,
'inner_step': 'wait_finalize'},
'id': 9900}
return [oper for oper in list({}) if oper['id'] == id][0]
gandi.cli-1.2/gandi/cli/tests/compat.py 0000644 0001750 0001750 00000000717 13227142762 020725 0 ustar sayoun sayoun 0000000 0000000
try:
from unittest import mock
except ImportError:
import mock
try:
import unittest2 as unittest
except ImportError:
import unittest
try:
import configparser as ConfigParser
except ImportError:
import ConfigParser
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
try:
from cStringIO import StringIO as ReasonableBytesIO
except ImportError:
from io import BytesIO as ReasonableBytesIO
gandi.cli-1.2/gandi/cli/tests/test_main.py 0000644 0001750 0001750 00000000472 12501555263 021421 0 ustar sayoun sayoun 0000000 0000000 from .compat import unittest
from .compat import mock
class TestCase(unittest.TestCase):
def test_main(self):
cli = mock.Mock()
with mock.patch('gandi.cli.core.cli.cli', cli):
from gandi.cli.__main__ import main
main()
cli.assert_called_once_with(obj={})
gandi.cli-1.2/gandi/cli/core/ 0000755 0001750 0001750 00000000000 13227415174 016651 5 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/gandi/cli/core/cli.py 0000644 0001750 0001750 00000012435 13227374032 017774 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
""" GandiCLI class declaration and initialization. """
import os
import os.path
import inspect
import platform
from functools import update_wrapper
import click
from .base import GandiContextHelper
from gandi.cli import __version__
try:
use_man_epilog = platform.system() == 'Linux'
except:
pass
# XXX: dirty hack of click help command to allow short help -h
def add_help_option(self):
"""Add a help option to the command."""
click.help_option(*('--help', '-h'))(self)
click.Command.add_help_option = add_help_option
# XXX: patch each command with an epilog
if use_man_epilog:
def format_epilog(self, ctx, formatter):
"""Writes the epilog into the formatter if it exists."""
self.epilog = 'For detailed documentation, use `man gandi`.'
formatter.write_paragraph()
with formatter.indentation():
formatter.write_text(self.epilog)
click.Command.format_epilog = format_epilog
def compatcallback(f):
""" Compatibility callback decorator for older click version.
Click 1.0 does not have a version string stored, so we need to
use getattr here to be safe.
"""
if getattr(click, '__version__', '0.0') >= '2.0':
return f
return update_wrapper(lambda ctx, value: f(ctx, None, value), f)
class GandiCLI(click.Group):
""" Gandi command line utility.
All CLI commands have a documented help
$ gandi --help
"""
def __init__(self, help=None):
""" Initialize CLI command line."""
@compatcallback
def set_debug(ctx, param, value):
ctx.obj['verbose'] = value
@compatcallback
def get_version(ctx, param, value):
if value:
print(('Gandi CLI %s\n\n'
'Copyright: © 2014-2018 Gandi S.A.S.\n'
'License: GPL-3' % __version__))
ctx.exit()
if help is None:
help = inspect.getdoc(self)
click.Group.__init__(self, help=help, params=[
click.Option(['-v'],
help='Enable or disable verbose mode. Use multiple '
'time for higher level of verbosity: -v, -vv',
count=True, metavar='',
default=0, callback=set_debug),
click.Option(['--version'],
help='Display version.',
is_flag=True,
default=False, callback=get_version)
])
def format_commands(self, ctx, formatter):
"""Extra format methods for multi methods that adds all the commands
after the options.
Display custom help for all subcommands.
"""
rows = []
all_cmds = self.list_all_commands(ctx)
for cmd_name in sorted(all_cmds):
cmd = all_cmds[cmd_name]
help = cmd.short_help or ''
rows.append((cmd_name, help))
if rows:
with formatter.section('Commands'):
formatter.write_dl(rows)
def list_sub_commmands(self, cmd_name, cmd):
"""Return all commands for a group"""
ret = {}
if isinstance(cmd, click.core.Group):
for sub_cmd_name in cmd.commands:
sub_cmd = cmd.commands[sub_cmd_name]
sub = self.list_sub_commmands(sub_cmd_name, sub_cmd)
if sub:
if isinstance(sub, dict):
for n, c in sub.items():
ret['%s %s' % (cmd_name, n)] = c
else:
ret['%s %s' % (cmd_name, sub[0])] = sub[1]
elif isinstance(cmd, click.core.Command):
return (cmd.name, cmd)
return ret
def list_all_commands(self, ctx):
ret = {}
for cmd_name in self.commands:
cmd = self.commands[cmd_name]
sub = self.list_sub_commmands(cmd_name, cmd)
if sub:
if isinstance(sub, tuple):
ret[sub[0]] = sub[1]
else:
ret.update(sub)
return ret
def load_commands(self):
""" Load cli commands from submodules. """
command_folder = os.path.join(os.path.dirname(__file__),
'..', 'commands')
command_dirs = {
'gandi.cli': command_folder
}
if 'GANDICLI_PATH' in os.environ:
for _path in os.environ.get('GANDICLI_PATH').split(':'):
# remove trailing separator if any
path = _path.rstrip(os.sep)
command_dirs[os.path.basename(path)] = os.path.join(path,
'commands')
for module_basename, dir in list(command_dirs.items()):
for filename in sorted(os.listdir(dir)):
if filename.endswith('.py') and '__init__' not in filename:
submod = filename[:-3]
module_name = module_basename + '.commands.' + submod
__import__(module_name, fromlist=[module_name])
def invoke(self, ctx):
""" Invoke command in context. """
ctx.obj = GandiContextHelper(verbose=ctx.obj['verbose'])
click.Group.invoke(self, ctx)
cli = GandiCLI()
cli.load_commands()
gandi.cli-1.2/gandi/cli/core/base.py 0000644 0001750 0001750 00000032134 13227142736 020141 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
""" Contains GandiModule and GandiContextHelper classes.
GandiModule class is used by commands modules.
GandiContextHelper class is used as click context for commands.
"""
from __future__ import print_function
import os
import sys
import time
import os.path
from datetime import datetime
from subprocess import check_call, Popen, PIPE, CalledProcessError
import click
from click.exceptions import UsageError
from .client import XMLRPCClient, APICallFailed, DryRunException, JsonClient
from .conf import GandiConfig
class MissingConfiguration(Exception):
""" Raise when no configuration was found. """
class GandiModule(GandiConfig):
""" Base class for modules.
Manage
- initializing xmlrpc connection
- execute remote api calls
"""
_op_scores = {'BILL': 0, 'WAIT': 1, 'RUN': 2, 'DONE': 3}
verbose = 0
_api = None
# frequency of api calls when polling for operation progress
_poll_freq = 1
@classmethod
def get_api_connector(cls):
""" Initialize an api connector for future use."""
if cls._api is None: # pragma: no cover
cls.load_config()
cls.debug('initialize connection to remote server')
apihost = cls.get('api.host')
if not apihost:
raise MissingConfiguration()
apienv = cls.get('api.env')
if apienv and apienv in cls.apienvs:
apihost = cls.apienvs[apienv]
cls._api = XMLRPCClient(host=apihost, debug=cls.verbose)
return cls._api
@classmethod
def call(cls, method, *args, **kwargs):
""" Call a remote api method and return the result."""
api = None
empty_key = kwargs.pop('empty_key', False)
try:
api = cls.get_api_connector()
apikey = cls.get('api.key')
if not apikey and not empty_key:
cls.echo("No apikey found, please use 'gandi setup' "
"command")
sys.exit(1)
except MissingConfiguration:
if api and empty_key:
apikey = ''
elif not kwargs.get('safe'):
cls.echo("No configuration found, please use 'gandi setup' "
"command")
sys.exit(1)
else:
return []
# make the call
cls.debug('calling method: %s' % method)
for arg in args:
cls.debug('with params: %r' % arg)
try:
resp = api.request(method, apikey, *args,
**{'dry_run': kwargs.get('dry_run', False),
'return_dry_run':
kwargs.get('return_dry_run', False)})
cls.dump('responded: %r' % resp)
return resp
except APICallFailed as err:
if kwargs.get('safe'):
return []
if err.code == 530040:
cls.echo("Error: It appears you haven't purchased any credits "
"yet.\n"
"Please visit https://www.gandi.net/credit/buy to "
"learn more and buy credits.")
sys.exit(1)
if err.code == 510150:
cls.echo("Invalid API key, please use 'gandi setup' command.")
sys.exit(1)
if isinstance(err, DryRunException):
if kwargs.get('return_dry_run', False):
return err.dry_run
else:
for msg in err.dry_run:
# TODO use trads with %s
cls.echo(msg['reason'])
cls.echo('\t' + ' '.join(msg['attr']))
sys.exit(1)
error = UsageError(err.errors)
setattr(error, 'code', err.code)
raise error
@classmethod
def safe_call(cls, method, *args):
""" Call a remote api method but don't raise if an error occurred."""
return cls.call(method, *args, safe=True)
@classmethod
def json_call(cls, method, url, **kwargs):
""" Call a remote api using json format """
# retrieve api key if needed
empty_key = kwargs.pop('empty_key', False)
send_key = kwargs.pop('send_key', True)
return_header = kwargs.pop('return_header', False)
try:
apikey = cls.get('apirest.key')
if not apikey and not empty_key:
cls.echo("No apikey found for REST API, please use "
"'gandi setup' command")
sys.exit(1)
if send_key:
if 'headers' in kwargs:
kwargs['headers'].update({'X-Api-Key': apikey})
else:
kwargs['headers'] = {'X-Api-Key': apikey}
except MissingConfiguration:
if not empty_key:
return []
# make the call
cls.debug('calling url: %s %s' % (method, url))
cls.debug('with params: %r' % kwargs)
try:
resp, resp_headers = JsonClient.request(method, url, **kwargs)
cls.dump('responded: %r' % resp)
if return_header:
return resp, resp_headers
return resp
except APICallFailed as err:
cls.echo('An error occured during call: %s' % err.errors)
sys.exit(1)
@classmethod
def json_get(cls, url, **kwargs):
""" Helper for GET json request """
return cls.json_call('GET', url, **kwargs)
@classmethod
def json_post(cls, url, **kwargs):
""" Helper for POST json request """
return cls.json_call('POST', url, **kwargs)
@classmethod
def json_put(cls, url, **kwargs):
""" Helper for PUT json request """
return cls.json_call('PUT', url, **kwargs)
@classmethod
def json_delete(cls, url, **kwargs):
""" Helper for DELETE json request """
return cls.json_call('DELETE', url, **kwargs)
@classmethod
def intty(cls):
""" Check if we are in a tty. """
# XXX: temporary hack until we can detect if we are in a pipe or not
return True
if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty():
return True
return False
@classmethod
def echo(cls, message):
""" Display message. """
if cls.intty():
if message is not None:
print(message)
@classmethod
def pretty_echo(cls, message):
""" Display message using pretty print formatting. """
if cls.intty():
if message:
from pprint import pprint
pprint(message)
@classmethod
def separator_line(cls, sep='-', size=10):
""" Display a separator line. """
if cls.intty():
cls.echo(sep * size)
@classmethod
def separator_sub_line(cls, sep='-', size=10):
""" Display a separator line. """
if cls.intty():
cls.echo("\t" + sep * size)
@classmethod
def dump(cls, message):
""" Display dump message if verbose level allows it. """
if cls.verbose > 2:
msg = '[DUMP] %s' % message
cls.echo(msg)
@classmethod
def debug(cls, message):
""" Display debug message if verbose level allows it. """
if cls.verbose > 1:
msg = '[DEBUG] %s' % message
cls.echo(msg)
@classmethod
def log(cls, message):
""" Display info message if verbose level allows it. """
if cls.verbose > 0:
msg = '[INFO] %s' % message
cls.echo(msg)
@classmethod
def deprecated(cls, message):
print('[deprecated]: %s' % message, file=sys.stderr)
@classmethod
def error(cls, msg):
""" Raise click UsageError exception using msg. """
raise UsageError(msg)
@classmethod
def execute(cls, command, shell=True):
""" Execute a shell command. """
cls.debug('execute command (shell flag:%r): %r ' % (shell, command))
try:
check_call(command, shell=shell)
return True
except CalledProcessError:
return False
@classmethod
def exec_output(cls, command, shell=True, encoding='utf-8'):
""" Return execution output
:param encoding: charset used to decode the stdout
:type encoding: str
:return: the return of the command
:rtype: unicode string
"""
proc = Popen(command, shell=shell, stdout=PIPE)
stdout, _stderr = proc.communicate()
if proc.returncode == 0:
return stdout.decode(encoding)
return ''
@classmethod
def update_progress(cls, progress, starttime):
""" Display an ascii progress bar while processing operation. """
width, _height = click.get_terminal_size()
if not width:
return
duration = datetime.utcnow() - starttime
hours, remainder = divmod(duration.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
size = int(width * .6)
status = ""
if isinstance(progress, int):
progress = float(progress)
if not isinstance(progress, float):
progress = 0
status = 'error: progress var must be float\n'
cls.echo(type(progress))
if progress < 0:
progress = 0
status = 'Halt...\n'
if progress >= 1:
progress = 1
# status = 'Done...\n'
block = int(round(size * progress))
text = ('\rProgress: [{0}] {1:.2%} {2} {3:0>2}:{4:0>2}:{5:0>2} '
''.format('#' * block + '-' * (size - block), progress,
status, hours, minutes, seconds))
sys.stdout.write(text)
sys.stdout.flush()
@classmethod
def display_progress(cls, operations):
""" Display progress of Gandi operations.
polls API every 1 seconds to retrieve status.
"""
start_crea = datetime.utcnow()
# count number of operations, 3 steps per operation
if not isinstance(operations, (list, tuple)):
operations = [operations]
count_operations = len(operations) * 3
updating_done = False
while not updating_done:
op_score = 0
for oper in operations:
op_ret = cls.call('operation.info', oper['id'])
op_step = op_ret['step']
if op_step in cls._op_scores:
op_score += cls._op_scores[op_step]
else:
cls.echo('')
msg = 'step %s unknown, exiting' % op_step
if op_step == 'ERROR':
msg = ('An error has occured during operation '
'processing: %s' % op_ret['last_error'])
elif op_step == 'SUPPORT':
msg = ('An error has occured during operation '
'processing, you must contact Gandi support.')
cls.echo(msg)
sys.exit(1)
cls.update_progress(float(op_score) / count_operations,
start_crea)
if op_score == count_operations:
updating_done = True
time.sleep(cls._poll_freq)
cls.echo('\r')
class GandiContextHelper(GandiModule):
""" Gandi context helper.
Import module classes from modules directory upon initialization.
"""
_modules = {}
def __init__(self, verbose=-1):
""" Initialize variables and api connection. """
GandiModule.verbose = verbose
GandiModule.load_config()
# only load modules once
if not self._modules:
self.load_modules()
def __getattribute__(self, item):
""" Return module from internal imported modules dict. """
if item in object.__getattribute__(self, '_modules'):
return object.__getattribute__(self, '_modules')[item]
return object.__getattribute__(self, item)
def load_modules(self):
""" Import CLI commands modules. """
module_folder = os.path.join(os.path.dirname(__file__),
'..', 'modules')
module_dirs = {
'gandi.cli': module_folder
}
if 'GANDICLI_PATH' in os.environ:
for _path in os.environ.get('GANDICLI_PATH').split(':'):
# remove trailing separator if any
path = _path.rstrip(os.sep)
module_dirs[os.path.basename(path)] = os.path.join(path,
'modules')
for module_basename, dir in list(module_dirs.items()):
for filename in sorted(os.listdir(dir)):
if filename.endswith('.py') and '__init__' not in filename:
submod = filename[:-3]
module_name = module_basename + '.modules.' + submod
__import__(module_name, fromlist=[module_name])
# save internal map of loaded module classes
for subclass in GandiModule.__subclasses__():
self._modules[subclass.__name__.lower()] = subclass
gandi.cli-1.2/gandi/cli/core/utils/ 0000755 0001750 0001750 00000000000 13227415174 020011 5 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/gandi/cli/core/utils/unixpipe.py 0000644 0001750 0001750 00000013617 12764262127 022237 0 ustar sayoun sayoun 0000000 0000000 #!/usr/bin/python
import os
import socket
import select
import subprocess
import sys
import time
class FdPipe:
"""Connect two pairs of file objects"""
def __init__(self, in0, out0, in1, out1):
self.poller = select.poll()
self.fd_map = {
out0.fileno(): in1.fileno(),
out1.fileno(): in0.fileno()
}
for fd in self.fd_map:
self.select_for_read(fd)
self.out_buf = {}
def select_for_flush(self, fd):
self.poller.register(fd, select.POLLERR | select.POLLHUP |
select.POLLIN | select.POLLOUT)
def select_for_write(self, fd):
self.poller.register(fd, select.POLLERR | select.POLLHUP |
select.POLLIN | select.POLLOUT)
def select_for_read(self, fd):
self.poller.register(fd, select.POLLERR | select.POLLHUP |
select.POLLIN)
def do_write(self, outfd):
if not self.out_buf[outfd]:
return
(rl, wl, el) = select.select([], [outfd], [outfd])
if outfd in wl:
length = os.write(outfd, self.out_buf[outfd])
self.out_buf[outfd] = self.out_buf[outfd][length:]
if not self.out_buf[outfd]:
self.select_for_read(outfd)
del self.out_buf[outfd]
elif outfd in el:
raise Exception('could not flush fd')
def queue_write(self, outfd, data):
self.out_buf[outfd] = self.out_buf.setdefault(outfd, '') + data
self.select_for_write(outfd)
def flush_outputs(self):
while self.out_buf:
for outfd in self.out_buf:
try:
self.do_write(outfd)
except OSError:
return
def one_loop(self):
ret = True
for (fd, ev) in self.poller.poll(1000):
if ev & (select.POLLERR | select.POLLHUP):
self.flush_outputs()
self.poller.unregister(fd)
ret = False
if ev & select.POLLIN:
data = os.read(fd, 4096)
if not data:
os.close(self.fd_map[fd])
ret = False
self.queue_write(self.fd_map[fd], data)
if ev & select.POLLOUT:
self.do_write(fd)
return ret
def scp(addr, user, local_path, remote_path, local_key=None):
scp_call = ['scp', local_path,
'%s@[%s]:%s' % (user, addr, remote_path)]
if local_key:
scp_call.insert(1, local_key)
scp_call.insert(1, '-i')
subprocess.call(scp_call)
def tcp4_to_unix(local_port, unix_path):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM,
socket.IPPROTO_TCP)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
server.bind(('127.0.0.1', local_port))
except socket.error as e:
sys.stderr.write('remote cant grab port %d\n' % local_port)
# let other end time to connect to maintain ssh up
time.sleep(10)
sys.exit(0)
server.listen(32)
while True:
(rl, wl, el) = select.select([server], [], [server], 1)
if server in rl:
(client, _) = server.accept()
if not os.fork():
unix = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
unix.connect(unix_path)
except socket.error as e:
print('Unable to grab %s: %s' % (unix_path, e))
pipe = FdPipe(client, client, unix, unix)
while pipe.one_loop():
pass
return
client.close()
try:
os.waitpid(-1, os.WNOHANG)
except OSError:
pass
def find_port(addr, user):
"""Find local port in existing tunnels"""
import pwd
home = pwd.getpwuid(os.getuid()).pw_dir
for name in os.listdir('%s/.ssh/' % home):
if name.startswith('unixpipe_%s@%s_' % (user, addr,)):
return int(name.split('_')[2])
def new_port():
"""Find a free local port and allocate it"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
for i in range(12042, 16042):
try:
s.bind(('127.0.0.1', i))
s.close()
return i
except socket.error:
pass
raise Exception('No local port available')
def _ssh_master_cmd(addr, user, command, local_key=None):
"""Exit or check ssh mux"""
ssh_call = ['ssh', '-qNfL%d:127.0.0.1:12042' % find_port(addr, user),
'-o', 'ControlPath=~/.ssh/unixpipe_%%r@%%h_%d' %
find_port(addr, user),
'-O', command, '%s@%s' % (user, addr,)]
if local_key:
ssh_call.insert(1, local_key)
ssh_call.insert(1, '-i')
return subprocess.call(ssh_call)
def is_alive(addr, user):
"""Check wether a tunnel is alive"""
return _ssh_master_cmd(addr, user, 'check') == 0
def setup(addr, user, remote_path, local_key=None):
"""Setup the tunnel"""
port = find_port(addr, user)
if not port or not is_alive(addr, user):
port = new_port()
scp(addr, user, __file__, '~/unixpipe', local_key)
ssh_call = ['ssh', '-fL%d:127.0.0.1:12042' % port,
'-o', 'ExitOnForwardFailure=yes',
'-o', 'ControlPath=~/.ssh/unixpipe_%%r@%%h_%d' % port,
'-o', 'ControlMaster=auto',
'%s@%s' % (user, addr,), 'python', '~/unixpipe',
'server', remote_path]
if local_key:
ssh_call.insert(1, local_key)
ssh_call.insert(1, '-i')
subprocess.call(ssh_call)
# XXX Sleep is a bad way to wait for the tunnel endpoint
time.sleep(1)
return port
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'server':
tcp4_to_unix(12042, sys.argv[2])
gandi.cli-1.2/gandi/cli/core/utils/xmlrpc.py 0000644 0001750 0001750 00000004030 12506727670 021673 0 ustar sayoun sayoun 0000000 0000000 """
Security enhancements for xmlrpc.
"""
from __future__ import print_function
import sys
try:
import xmlrpc.client as xmlrpclib
except ImportError:
import xmlrpclib
try:
import requests
except ImportError:
print('python requests is required, please reinstall.', file=sys.stderr)
sys.exit(1)
class RequestsTransport(xmlrpclib.Transport):
"""
Drop in Transport for xmlrpclib that uses Requests instead of httplib
# https://gist.github.com/chrisguitarguy/2354951
# https://github.com/mardiros/pyshop/blob/master/pyshop/helpers/pypi.py
"""
use_https = True
def __init__(self, use_datetime=0, host=None):
xmlrpclib.Transport.__init__(self, use_datetime)
if host:
self.use_https = 'https' in host
def request(self, host, handler, request_body, verbose):
"""
Make an xmlrpc request.
"""
headers = {'User-Agent': self.user_agent,
'Accept': 'text/xml',
'Content-Type': 'text/xml'}
url = self._build_url(host, handler)
try:
resp = requests.post(url, data=request_body, headers=headers)
except ValueError:
raise
except Exception:
raise # something went wrong
else:
try:
resp.raise_for_status()
except requests.RequestException as e:
raise xmlrpclib.ProtocolError(url, resp.status_code,
str(e), resp.headers)
else:
return self.parse_response(resp)
def parse_response(self, resp):
"""
Parse the xmlrpc response.
"""
p, u = self.getparser()
p.feed(resp.content)
p.close()
return u.close()
def _build_url(self, host, handler):
"""
Build a url for our request based on the host, handler and use_https
property
"""
scheme = 'https' if self.use_https else 'http'
return '%s://%s%s' % (scheme, host, handler)
gandi.cli-1.2/gandi/cli/core/utils/password.py 0000644 0001750 0001750 00000002316 13227414340 022221 0 ustar sayoun sayoun 0000000 0000000 # coding: utf-8
"""Contains methods to generate a random password."""
import random
import string
# remove backslash from generated password to avoid
# general escape issues while transmitting it
PUNCTUATION = string.punctuation.replace(chr(0x5c), '')
def mkpassword(length=16, chars=None, punctuation=None):
"""Generates a random ascii string - useful to generate authinfos
:param length: string wanted length
:type length: ``int``
:param chars: character population,
defaults to alphabet (lower & upper) + numbers
:type chars: ``str``, ``list``, ``set`` (sequence)
:param punctuation: number of punctuation signs to include in string
:type punctuation: ``int``
:rtype: ``str``
"""
if chars is None:
chars = string.ascii_letters + string.digits
# Generate string from population
data = [random.choice(chars) for _ in range(length)]
# If punctuation:
# - remove n chars from string
# - add random punctuation
# - shuffle chars :)
if punctuation:
data = data[:-punctuation]
for _ in range(punctuation):
data.append(random.choice(PUNCTUATION))
random.shuffle(data)
return ''.join(data)
gandi.cli-1.2/gandi/cli/core/utils/__init__.py 0000644 0001750 0001750 00000045717 13164644614 022143 0 ustar sayoun sayoun 0000000 0000000 """ Contains output methods used by commands.
Also custom exceptions and method to generate a random string.
"""
import sys
import time
from datetime import datetime
import json
from click.formatting import measure_table
from click import ClickException
from .ascii_sparks import sparks
class MissingConfiguration(Exception):
""" Raise when configuration if missing. """
def __init__(self, errors):
""" Initialize exception."""
self.errors = errors
class DuplicateResults(Exception):
""" Raise when multiple results are found."""
def __init__(self, errors):
""" Initialize exception."""
self.errors = errors
class DomainNotAvailable(Exception):
""" Raise when domain is not available. """
def __init__(self, errors):
""" Initialize exception."""
self.errors = errors
class DatacenterClosed(ClickException):
"""Raise when datacenter is closed: ALL"""
def __init__(self, message):
""" Initialize exception."""
self.message = message
class DatacenterLimited(Exception):
"""Raise when datacenter will soon be closed: NEW"""
def __init__(self, date):
""" Initialize exception."""
self.date = date
class MigrationNotFinalized(ClickException):
"""Raise when VM migration does not need to be finalized."""
def __init__(self, message):
""" Initialize exception."""
self.message = message
def format_list(data):
""" Remove useless characters to output a clean list."""
if isinstance(data, (list, tuple)):
to_clean = ['[', ']', '(', ')', "'"]
for item in to_clean:
data = str(data).replace("u\"", "\"").replace("u\'", "\'")
data = str(data).replace(item, '')
return data
def display_rows(gandi, rows, has_header=True):
col_len = measure_table(rows)
formatting = ' | '.join(['%-' + str(l) + 's' for l in col_len])
if has_header:
header = rows.pop(0)
gandi.echo(formatting % tuple(header))
gandi.echo('-+-'.join(['-' * l for l in col_len]))
for row in rows:
gandi.echo(formatting % tuple(row))
def output_line(gandi, key, val, justify):
""" Base helper to output a key value using left justify."""
msg = ('%%-%ds:%%s' % justify) % (key, (' %s' % val) if val else '')
gandi.echo(msg)
def output_generic(gandi, data, output_keys, justify=10):
""" Generic helper to output info from a data dict."""
for key in output_keys:
if key in data:
output_line(gandi, key, data[key], justify)
def output_account(gandi, account, output_keys, justify=17):
""" Helper to output an account information."""
output_generic(gandi, account, output_keys, justify)
if 'prepaid' in output_keys:
prepaid = '%s %s' % (account['prepaid_info']['amount'],
account['prepaid_info']['currency'])
output_line(gandi, 'prepaid', prepaid, justify)
if 'credit' in output_keys:
output_line(gandi, 'credits', None, justify)
available = account.get('credits')
output_line(gandi, ' available', available, justify)
# sometimes rating is returning nothing
usage_str = left_str = 'not available'
usage = account.get('credit_usage', 0)
left = account.get('left')
if usage:
usage_str = '%d/h' % usage
years, months, days, hours = left
left_str = ('%d year(s) %d month(s) %d day(s) %d hour(s)' %
(years, months, days, hours))
output_line(gandi, ' usage', usage_str, justify)
output_line(gandi, ' time left', left_str, justify)
def output_vm(gandi, vm, datacenters, output_keys, justify=10):
""" Helper to output a vm information."""
output_generic(gandi, vm, output_keys, justify)
if 'datacenter' in output_keys:
for dc in datacenters:
if dc['id'] == vm['datacenter_id']:
dc_name = dc.get('dc_code', dc.get('iso', ''))
break
output_line(gandi, 'datacenter', dc_name, justify)
if 'ip' in output_keys:
for iface in vm['ifaces']:
gandi.separator_line()
output_line(gandi, 'bandwidth', iface['bandwidth'], justify)
for ip in iface['ips']:
ip_addr = ip['ip']
output_line(gandi, 'ip%s' % ip['version'], ip_addr, justify)
def output_metric(gandi, metrics, key, justify=10):
""" Helper to output metrics."""
for metric in metrics:
key_name = metric[key].pop()
values = [point.get('value', 0) for point in metric['points']]
graph = sparks(values) if max(values) else ''
# need to encode in utf-8 to work in python2.X
if sys.version_info < (2, 8):
graph = graph.encode('utf-8')
output_line(gandi, key_name, graph, justify)
def output_vhost(gandi, vhost, paas, output_keys, justify=14):
""" Helper to output a vhost information."""
output_generic(gandi, vhost, output_keys, justify)
if 'paas_name' in output_keys:
output_line(gandi, 'paas_name', paas, justify)
def output_paas(gandi, paas, datacenters, vhosts, output_keys, justify=11):
""" Helper to output a paas information."""
output_generic(gandi, paas, output_keys, justify)
if 'sftp_server' in output_keys:
output_line(gandi, 'sftp_server', paas['ftp_server'], justify)
if 'vhost' in output_keys:
for entry in vhosts:
output_line(gandi, 'vhost', entry, justify)
if 'dc' in output_keys:
dc_name = paas['datacenter'].get('dc_code',
paas['datacenter'].get('iso', ''))
output_line(gandi, 'datacenter', dc_name, justify)
if 'df' in paas:
df = paas['df']
total = df['free'] + df['used']
if total:
disk_used = '%.1f%%' % (df['used'] * 100 / total)
output_line(gandi, 'quota used', disk_used, justify)
if 'snapshot' in output_keys:
val = None
if paas['snapshot_profile']:
val = paas['snapshot_profile']['name']
output_line(gandi, 'snapshot', val, justify)
if 'cache' in paas:
cache = paas['cache']
total = cache['hit'] + cache['miss'] + cache['not'] + cache['pass']
if total:
output_line(gandi, 'cache', None, justify)
for key in sorted(cache):
str_value = '%.1f%%' % (cache[key] * 100 / total)
output_sub_line(gandi, key, str_value, 5)
def output_image(gandi, image, datacenters, output_keys, justify=14,
warn_deprecated=True):
""" Helper to output a disk image."""
for key in output_keys:
if key in image:
if (key == 'label' and image['visibility'] == 'deprecated' and
warn_deprecated):
image[key] = '%s /!\ DEPRECATED' % image[key]
output_line(gandi, key, image[key], justify)
dc_name = 'Nowhere'
if 'dc' in output_keys:
for dc in datacenters:
if dc['id'] == image['datacenter_id']:
dc_name = dc.get('dc_code', dc.get('iso', ''))
break
output_line(gandi, 'datacenter', dc_name, justify)
def output_kernels(gandi, flavor, name_list, justify=14):
""" Helper to output kernel flavor versions."""
output_line(gandi, 'flavor', flavor, justify)
for name in name_list:
output_line(gandi, 'version', name, justify)
def output_datacenter(gandi, datacenter, output_keys, justify=14):
""" Helper to output datacenter information."""
output_generic(gandi, datacenter, output_keys, justify)
if 'dc_name' in output_keys:
output_line(gandi, 'datacenter', datacenter['name'], justify)
if 'status' in output_keys:
deactivate_at = datacenter.get('deactivate_at')
if deactivate_at:
output_line(gandi, 'closing on',
deactivate_at.strftime('%d/%m/%Y'), justify)
closing = []
iaas_closed_for = datacenter.get('iaas_closed_for')
if iaas_closed_for == 'ALL':
closing.append('vm')
paas_closed_for = datacenter.get('paas_closed_for')
if paas_closed_for == 'ALL':
closing.append('paas')
if closing:
output_line(gandi, 'closed for', ', '.join(closing), justify)
def output_cmdline(gandi, cmdline, justify=14):
args = []
for key in sorted(cmdline, reverse=True):
if isinstance(cmdline[key], bool):
args.append(key)
else:
args.append('%s=%s' % (key, cmdline[key]))
output_line(gandi, 'cmdline', ' '.join(args), justify)
def output_disk(gandi, disk, datacenters, vms, profiles, output_keys,
justify=10):
""" Helper to output a disk."""
output_generic(gandi, disk, output_keys, justify)
if 'kernel' in output_keys and disk.get('kernel_version'):
output_line(gandi, 'kernel', disk['kernel_version'], justify)
if 'cmdline' in output_keys and disk.get('kernel_cmdline'):
output_cmdline(gandi, disk.get('kernel_cmdline'), justify)
if 'dc' in output_keys:
dc_name = None
for dc in datacenters:
if dc['id'] == disk['datacenter_id']:
dc_name = dc.get('dc_code', dc.get('iso', ''))
break
if dc_name:
output_line(gandi, 'datacenter', dc_name, justify)
if 'vm' in output_keys:
for vm_id in disk['vms_id']:
vm_name = vms.get(vm_id, {}).get('hostname')
if vm_name:
output_line(gandi, 'vm', vm_name, justify)
if 'profile' in output_keys and disk.get('snapshot_profile'):
output_line(gandi, 'profile', disk['snapshot_profile']['name'],
justify)
elif 'profile' in output_keys and disk.get('snapshot_profile_id'):
for profile in profiles:
if profile['id'] == disk['snapshot_profile_id']:
output_line(gandi, 'profile', profile['name'], justify)
break
def output_sshkey(gandi, sshkey, output_keys, justify=12):
""" Helper to output an ssh key information."""
output_generic(gandi, sshkey, output_keys, justify)
def output_snapshot_profile(gandi, profile, output_keys, justify=13):
""" Helper to output a snapshot_profile."""
schedules = 'schedules' in output_keys
if schedules:
output_keys.remove('schedules')
output_generic(gandi, profile, output_keys, justify)
if schedules:
schedule_keys = ['name', 'kept_version']
for schedule in profile['schedules']:
gandi.separator_line()
output_generic(gandi, schedule, schedule_keys, justify)
def output_contact_info(gandi, data, output_keys, justify=10):
"""Helper to output chosen contacts info."""
for key in output_keys:
if data[key]:
output_line(gandi, key, data[key]['handle'], justify)
def output_cert_oper(gandi, oper, justify=12):
output_generic(gandi, oper, ['type', 'step'], justify)
params = dict(oper['params'])
params['fqdns'] = ', '.join(params.get('fqdns', []))
output = ['inner_step', 'package_name', 'dcv_method']
if params['fqdns']:
output.append('fqdns')
output_generic(gandi, params, output, justify)
def output_cert(gandi, cert, output_keys, justify=13):
"""Helper to output a certificate information."""
output = list(output_keys)
display_altnames = False
if 'altnames' in output:
display_altnames = True
output.remove('altnames')
display_output = False
if 'cert' in output:
display_output = True
output.remove('cert')
output_generic(gandi, cert, output, justify)
if display_output:
crt = gandi.certificate.pretty_format_cert(cert)
if crt:
output_line(gandi, 'cert', '\n' + crt, justify)
if display_altnames:
for altname in cert['altnames']:
output_line(gandi, 'altname', altname, justify)
def output_vlan(gandi, vlan, datacenters, output_keys, justify=10):
""" Helper to output a vlan information."""
output_generic(gandi, vlan, output_keys, justify)
if 'dc' in output_keys:
for dc in datacenters:
if dc['id'] == vlan.get('datacenter_id',
vlan.get('datacenter', {}).get('id')):
dc_name = dc.get('dc_code', dc.get('iso', ''))
break
output_line(gandi, 'datacenter', dc_name, justify)
def output_iface(gandi, iface, datacenters, vms, output_keys, justify=10):
""" Helper to output an iface information."""
output_generic(gandi, iface, output_keys, justify)
if 'vm' in output_keys:
vm_name = vms.get(iface['vm_id'], {}).get('hostname')
if vm_name:
output_line(gandi, 'vm', vm_name, justify)
if 'dc' in output_keys:
for dc in datacenters:
if dc['id'] == iface.get('datacenter_id',
iface.get('datacenter', {}).get('id')):
dc_name = dc.get('dc_code', dc.get('iso', ''))
break
output_line(gandi, 'datacenter', dc_name, justify)
if 'vlan_' in output_keys:
vlan = iface.get('vlan') or {}
output_line(gandi, 'vlan', vlan.get('name', '-'), justify)
def output_ip(gandi, ip, datacenters, vms, ifaces, output_keys, justify=11):
""" Helper to output an ip information."""
output_generic(gandi, ip, output_keys, justify)
if 'type' in output_keys:
iface = ifaces.get(ip['iface_id'])
type_ = 'private' if iface.get('vlan') else 'public'
output_line(gandi, 'type', type_, justify)
if type_ == 'private':
output_line(gandi, 'vlan', iface['vlan']['name'], justify)
if 'vm' in output_keys:
iface = ifaces.get(ip['iface_id'])
vm_id = iface.get('vm_id')
if vm_id:
vm_name = vms.get(vm_id, {}).get('hostname')
if vm_name:
output_line(gandi, 'vm', vm_name, justify)
if 'dc' in output_keys:
for dc in datacenters:
if dc['id'] == ip.get('datacenter_id',
ip.get('datacenter', {}).get('id')):
dc_name = dc.get('dc_code', dc.get('iso', ''))
break
output_line(gandi, 'datacenter', dc_name, justify)
def randomstring(prefix=None):
""" Helper to generate a random string, used for temporary hostnames."""
if not prefix:
prefix = 'tmp'
return '%s%s' % (prefix, str(int(time.time())))
def output_list(gandi, val):
"""Helper to generate a beautiful list."""
for element in val:
gandi.echo(element)
def date_handler(obj):
""" Serialize date for json output """
return obj.isoformat() if hasattr(obj, 'isoformat') else str(obj)
def output_json(gandi, format, value):
""" Helper to show json output """
if format == 'json':
gandi.echo(json.dumps(value, default=date_handler, sort_keys=True))
elif format == 'pretty-json':
gandi.echo(json.dumps(value, default=date_handler, sort_keys=True,
indent=2, separators=(',', ': ')))
def output_sub_line(gandi, key, val, justify):
""" Base helper to output a key value using left justify."""
msg = ('\t%%-%ds:%%s' % justify) % (key, (' %s' % val) if val else '')
gandi.echo(msg)
def output_sub_generic(gandi, data, output_keys, justify=10):
""" Generic helper to output info from a data dict."""
for key in output_keys:
if key in data:
output_sub_line(gandi, key, data[key], justify)
def output_service(gandi, service, status, justify=10):
""" Helper to output a status service information."""
output_line(gandi, service, status, justify)
def output_hostedcert(gandi, hcert, output_keys, justify=12):
output_keys = list(output_keys)
fqdns = 'fqdns' in output_keys
vhosts = 'vhosts' in output_keys
if fqdns:
output_keys.pop(output_keys.index('fqdns'))
if vhosts:
output_keys.pop(output_keys.index('vhosts'))
output_generic(gandi, hcert, output_keys, justify)
if fqdns:
for fqdn in hcert['fqdns']:
gandi.separator_sub_line()
output_sub_line(gandi, 'fqdn', fqdn['name'], 10)
if vhosts:
for vhost in hcert['related_vhosts']:
gandi.separator_sub_line()
output_sub_line(gandi, 'vhost', vhost['name'], 10)
output_sub_line(gandi, 'type', vhost['type'], 10)
def output_domain(gandi, domain, output_keys, justify=12):
""" Helper to output a domain information."""
if 'nameservers' in domain:
domain['nameservers'] = format_list(domain['nameservers'])
if 'services' in domain:
domain['services'] = format_list(domain['services'])
if 'tags' in domain:
domain['tags'] = format_list(domain['tags'])
output_generic(gandi, domain, output_keys, justify)
if 'created' in output_keys:
output_line(gandi, 'created', domain['date_created'], justify)
if 'expires' in output_keys:
date_end = domain.get('date_registry_end')
if date_end:
days_left = (date_end - datetime.now()).days
output_line(gandi, 'expires',
'%s (in %d days)' % (date_end, days_left),
justify)
if 'updated' in output_keys:
output_line(gandi, 'updated', domain['date_updated'], justify)
def output_mailbox(gandi, mailbox, output_keys, justify=16):
""" Helper to output a mailbox information."""
quota = 'quota' in output_keys
responder = 'responder' in output_keys
if quota:
output_keys.pop(output_keys.index('quota'))
if responder:
output_keys.pop(output_keys.index('responder'))
if 'aliases' in output_keys:
mailbox['aliases'] = sorted(mailbox['aliases'])
output_generic(gandi, mailbox, output_keys, justify)
if 'fallback' in output_keys:
output_line(gandi, 'fallback email', mailbox['fallback_email'],
justify)
if quota:
granted = mailbox['quota']['granted']
if mailbox['quota']['granted'] == 0:
granted = 'unlimited'
output_line(gandi, 'quota usage',
'%s KiB / %s' % (mailbox['quota']['used'], granted),
justify)
if responder:
responder_status = 'yes' if mailbox['responder']['active'] else 'no'
output_line(gandi, 'responder active', responder_status, justify)
output_line(gandi, 'responder text', mailbox['responder']['text'],
justify)
def output_forward(gandi, domain, forward, justify=14):
""" Helper to output a mail forward information."""
for dest in forward['destinations']:
output_line(gandi, forward['source'], dest, justify)
def output_dns_records(gandi, records, output_keys, justify=12):
""" Helper to output a dns records information."""
for key in output_keys:
real_key = 'rrset_%s' % key
if real_key in records:
val = records[real_key]
if key == 'values':
val = format_list(records[real_key])
output_line(gandi, key, val, justify)
gandi.cli-1.2/gandi/cli/core/utils/ascii_sparks.py 0000644 0001750 0001750 00000000433 12506776076 023050 0 ustar sayoun sayoun 0000000 0000000 # coding: utf-8
# Author: Rory McCann
# https://pypi.python.org/pypi/ascii_sparks/0.0.3
# License: Unknown
parts = u' ▁▂▃▄▅▆▇▉'
def sparks(nums):
fraction = max(nums) / float(len(parts) - 1)
return ''.join([parts[int(round(x / fraction))] for x in nums])
gandi.cli-1.2/gandi/cli/core/utils/size.py 0000644 0001750 0001750 00000000721 12656121545 021336 0 ustar sayoun sayoun 0000000 0000000 """Size related methods."""
import click
from gandi.cli.core.cli import compatcallback
@compatcallback
def disk_check_size(ctx, param, value):
""" Validation callback for disk size parameter."""
if value:
# if we've got a prefix
if isinstance(value, tuple):
val = value[1]
else:
val = value
if val % 1024:
raise click.ClickException('Size must be a multiple of 1024.')
return value
gandi.cli-1.2/gandi/cli/core/client.py 0000644 0001750 0001750 00000010077 13164644614 020511 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
""" XML-RPC connection helper. """
from __future__ import print_function
import sys
import socket
try:
import xmlrpclib
except ImportError:
import xmlrpc.client as xmlrpclib
try:
import requests
except ImportError:
print('python requests is required, please reinstall.', file=sys.stderr)
sys.exit(1)
from gandi.cli import __version__
from gandi.cli.core.utils.xmlrpc import RequestsTransport
class APICallFailed(Exception):
""" Raise when an error occurred during an API call. """
def __init__(self, errors, code=None):
""" Initialize exception. """
self.errors = errors
self.code = code
class GandiTransport(RequestsTransport):
""" Mixin to send custom User-Agent in requests."""
user_agent = 'gandi.cli/%s' % __version__
class DryRunException(APICallFailed):
dry_run = None
def __init__(self, message, code, dry_run):
super(DryRunException, self).__init__(message, code)
self.dry_run = dry_run
class XMLRPCClient(object):
""" Class wrapper for xmlrpc calls to Gandi public API. """
def __init__(self, host, debug=False):
""" Initialize xml-rpc endpoint connector. """
self.debug = debug
self.host = host
self.endpoint = xmlrpclib.ServerProxy(
host, allow_none=True, use_datetime=True,
transport=GandiTransport(use_datetime=True, host=host))
def request(self, method, apikey, *args, **kwargs):
""" Make a xml-rpc call to remote API. """
dry_run = kwargs.get('dry_run', False)
return_dry_run = kwargs.get('return_dry_run', False)
if return_dry_run:
args[-1]['--dry-run'] = True
try:
func = getattr(self.endpoint, method)
return func(apikey, *args)
except (socket.error, requests.exceptions.ConnectionError):
msg = 'Gandi API service is unreachable'
raise APICallFailed(msg)
except xmlrpclib.Fault as err:
msg = 'Gandi API has returned an error: %s' % err
if dry_run:
args[-1]['--dry-run'] = True
ret = func(apikey, *args)
raise DryRunException(msg, err.faultCode, ret)
raise APICallFailed(msg, err.faultCode)
except TypeError as err:
msg = 'An unknown error has occurred: %s' % err
raise APICallFailed(msg)
class JsonClient(object):
""" Class wrapper for JSON calls. """
@classmethod
def format_errors(cls, errors):
return '\n'.join([err['description'] for err in errors])
@classmethod
def request(cls, method, url, **kwargs):
"""Make a http call to a remote API and return a json response."""
user_agent = 'gandi.cli/%s' % __version__
headers = {'User-Agent': user_agent,
'Content-Type': 'application/json; charset=utf-8'}
if kwargs.get('headers'):
headers.update(kwargs.pop('headers'))
try:
response = requests.request(method, url, headers=headers,
**kwargs)
response.raise_for_status()
try:
return response.json(), response.headers
except ValueError as err:
return response.text, response.headers
except (socket.error, requests.exceptions.ConnectionError):
msg = 'Remote API service is unreachable'
raise APICallFailed(msg)
except Exception as err:
if isinstance(err, requests.HTTPError):
try:
resp = response.json()
except:
msg = 'An unknown error has occurred: %s' % err
raise APICallFailed(msg)
if resp.get('message'):
error = resp.get('message')
if resp.get('errors'):
error = cls.format_errors(resp.get('errors'))
msg = '%s: %s' % (err, error)
else:
msg = 'An unknown error has occurred: %s' % err
raise APICallFailed(msg)
gandi.cli-1.2/gandi/cli/core/__init__.py 0000644 0001750 0001750 00000000313 12441335654 020760 0 ustar sayoun sayoun 0000000 0000000 """ Gandi CLI core modules.
Contains:
- XML-RPC connection handler
- Configuration handler
- Base class, which load modules and commands
- Custom command line validation parameters
- Output methods
"""
gandi.cli-1.2/gandi/cli/core/params.py 0000644 0001750 0001750 00000043451 13214476173 020517 0 ustar sayoun sayoun 0000000 0000000 """ Custom command line validation parameters. """
import re
import sys
import click
from click.decorators import _param_memo
from gandi.cli.core.base import GandiContextHelper
class GandiChoice(click.Choice):
""" Base class for custom Choice parameters. """
gandi = None
def __init__(self):
""" Initialize choices list. """
self._choices = []
def _get_choices(self, gandi):
""" Internal method to get choices list """
raise NotImplementedError
@property
def choices(self):
""" Retrieve choices from API if possible"""
if not self._choices:
gandi = self.gandi or GandiContextHelper()
self._choices = self._get_choices(gandi)
if not self._choices:
api = gandi.get_api_connector()
gandi.echo('Please check that you are connecting to the good '
"api '%s' and that it's running." % (api.host))
sys.exit(1)
return self._choices
def convert(self, value, param, ctx):
""" Internal method to use correct context. """
self.gandi = ctx.obj
return click.Choice.convert(self, value, param, ctx)
def convert_deprecated_value(self, value):
""" To override when needed """
return value
class DatacenterParamType(GandiChoice):
""" Choice parameter to select an available datacenter. """
name = 'datacenter'
def _get_choices(self, gandi):
""" Internal method to get choices list """
iso_codes = []
dc_codes = []
for item in gandi.datacenter.list():
iso_codes.append(item['iso'])
if item.get('dc_code'):
dc_codes.append(item['dc_code'])
return dc_codes + iso_codes
def convert(self, value, param, ctx):
""" Convert value to uppercase. """
self.gandi = ctx.obj
value = value.upper()
return click.Choice.convert(self, value, param, ctx)
def convert_deprecated_value(self, value):
""" To update the configuration with the new datacenter naming """
convert = {
'FR': 'FR-SD2',
'LU': 'LU-BI1',
'US': 'US-BA1',
}
return convert.get(value, value)
class PaasTypeParamType(GandiChoice):
""" Choice parameter to select an available PaaS instance type. """
name = 'paas type'
def _get_choices(self, gandi):
""" Internal method to get choices list """
return [item['name'] for item in gandi.paas.type_list()]
class IntChoice(click.Choice):
""" Choice parameter to select an integer value in a set of int values."""
name = 'integer choice'
def convert(self, value, param, ctx):
""" Convert value to int. """
self.gandi = ctx.obj
try:
value = str(value)
except Exception:
pass
value = click.Choice.convert(self, value, param, ctx)
return int(value)
class DiskImageParamType(GandiChoice):
""" Choice parameter to select an available disk image. """
name = 'images'
def _get_choices(self, gandi):
""" Internal method to get choices list """
image_list = []
for item in gandi.image.list():
label = item['label']
if item['visibility'] == 'deprecated':
label = '*%s' % label
image_list.append(label)
disk_list = [item['name'] for item in gandi.disk.list_create()]
return sorted(tuple(set(image_list))) + disk_list
def convert(self, value, param, ctx):
""" Try to find correct disk image regarding version. """
self.gandi = ctx.obj
# remove deprecated * prefix
choices = [choice.replace('*', '') for choice in self.choices]
value = value.replace('*', '')
# Exact match
if value in choices:
return value
# Try to find 64 bits version
new_value = '%s 64 bits' % value
if new_value in choices:
return new_value
# Try to find without specific bits version
p = re.compile(' (64|32) bits')
new_value = p.sub('', value)
if new_value in choices:
return new_value
self.fail('invalid choice: %s. (choose from %s)' %
(value, ', '.join(self.choices)), param, ctx)
class KernelParamType(GandiChoice):
""" Choice parameter to select an available kernel. """
name = 'kernels'
def _get_choices(self, gandi):
""" Internal method to get choices list """
kernel_families = list(gandi.kernel.list().values())
return [kernel for klist in kernel_families for kernel in klist]
def convert(self, value, param, ctx):
""" Try to find correct kernel regarding version. """
self.gandi = ctx.obj
# Exact match first
if value in self.choices:
return value
# Also try with x86-64 suffix
new_value = '%s-x86_64' % value
if new_value in self.choices:
return new_value
self.fail('invalid choice: %s. (choose from %s)' %
(value, ', '.join(self.choices)), param, ctx)
class SnapshotParamType(GandiChoice):
""" Choice parameter to select an available snapshot profile. """
name = 'snapshot profile'
target = None
def __init__(self, target=None):
""" Initialize choices list. """
self._choices = []
self.target = target
def _get_choices(self, gandi):
""" Internal method to get choices list """
return [str(item['id'])
for item in gandi.snapshotprofile.list(target=self.target)]
def convert(self, value, param, ctx):
""" Convert value to int. """
self.gandi = ctx.obj
value = click.Choice.convert(self, value, param, ctx)
return int(value)
class CertificatePackage(GandiChoice):
""" Choice parameter to select an available certificate package. """
name = 'certificate package'
def _get_choices(self, gandi):
""" Internal method to get choices list """
return [item['name'] for item in gandi.certificate.package_list()]
class CertificatePackageType(CertificatePackage):
""" Choice parameter to select an available certificate package type. """
name = 'certificate package type'
def _get_choices(self, gandi):
""" Internal method to get choices list """
packages = super(CertificatePackageType, self)._get_choices(gandi)
return list(set([pack.split('_')[1] for pack in packages]))
class CertificatePackageMax(CertificatePackage):
"""
Choice parameter to select an available certificate package max altname.
"""
name = 'certificate package max'
def _get_choices(self, gandi):
""" Internal method to get choices list """
packages = super(CertificatePackageMax, self)._get_choices(gandi)
ret = list(set([pack.split('_')[2] for pack in packages]))
if 'w' in ret:
ret.remove('w')
return ret
def convert(self, value, param, ctx):
""" Convert value to int. """
self.gandi = ctx.obj
value = click.Choice.convert(self, value, param, ctx)
return int(value)
class CertificatePackageWarranty(CertificatePackage):
""" Choice parameter to select an available certificate warranty. """
name = 'certificate package warranty'
def _get_choices(self, gandi):
""" Internal method to get choices list """
packages = super(CertificatePackageWarranty, self)._get_choices(gandi)
return list(set([pack.split('_')[3] for pack in packages]))
def convert(self, value, param, ctx):
""" Convert value to int. """
self.gandi = ctx.obj
value = click.Choice.convert(self, value, param, ctx)
return int(value)
class CertificateDcvMethod(click.Choice):
""" Choice parameter to select a certificate dcv method.
* 'email' will send you an email to check domain ownership
* 'dns' will require you to add a TXT record in your domain zone
* 'file' will require you to add a file on you server
* 'auto' can only be used when your domain and its zone are on the
same gandi account you are currently using (gandi will add the TXT
dns record).
"""
name = 'certificate dcv method'
choices = ['email', 'dns', 'file', 'auto']
def __init__(self):
""" Initialize choices list. """
pass
class IpType(click.Choice):
""" Choice parameter to filter on ip types.
* 'private' will only retrieve private ips
* 'public' will only retrieve public ips
"""
name = 'ip type'
choices = ['private', 'public']
def __init__(self):
""" Initialize choices list. """
pass
class StringConstraint(click.types.StringParamType):
""" Check that provided string matches constraints."""
name = 'string constraints'
def __init__(self, minlen=None, maxlen=None):
self.min = minlen
self.max = maxlen
def convert(self, value, param, ctx):
value = click.types.StringParamType.convert(self, value, param, ctx)
rv = len(value)
if self.min is not None and rv < self.min or \
self.max is not None and rv > self.max:
if self.min is None:
self.fail('%s is longer than the maximum valid length '
'%s.' % (rv, self.max), param, ctx)
elif self.max is None:
self.fail('%s is shorter than the minimum valid length '
'%s.' % (rv, self.min), param, ctx)
else:
self.fail('%s is not in the valid length range of %s to %s.'
% (rv, self.min, self.max), param, ctx)
return value
def __repr__(self):
return 'StringConstraint(%r, %r)' % (self.min, self.max)
class EmailParamType(click.ParamType):
"""Check the email value and return a list ['login', 'domain']. """
name = 'email'
def convert(self, value, param, ctx):
""" Validate value using regexp. """
rxp = '^[^@]+?@[-.a-z0-9]+$'
regex = re.compile(rxp, re.I)
try:
if regex.match(value):
value = value.split("@")
return value
else:
self.fail('%s is not a valid email address' % value, param,
ctx)
except ValueError:
self.fail('%s is not a valid email address' % value, param, ctx)
class SizeParamType(click.ParamType):
name = 'size'
suffixes = {'M': 0,
'G': 1,
'T': 2}
prefixes = ['+']
def convert(self, value, param, ctx):
prefix = ''
suffix = ''
for i, c in enumerate(value):
if not c.isdigit():
if c in self.prefixes:
prefix = c
continue
suffix = value[i:]
value = value[:i]
break
try:
mul = self.suffixes[suffix] if suffix else 0
return prefix, int(value) * (1 << (mul * 10))
except ValueError:
self.fail("%r is not an integer" % (value))
except KeyError:
self.fail("%r is not a supported suffix" % (suffix))
class BackendParamType(click.ParamType):
"""
Check the validity of the server ip and port and return a dict
['ip', 'port'].
"""
name = 'backend'
def convert(self, value, param, ctx):
""" Validate value using regexp. """
rxp = "^(((([1]?\d)?\d|2[0-4]\d|25[0-5])\.){3}(([1]?\d)?\d|2[0-4]\d|"\
"25[0-5]))|([\da-fA-F]{1,4}(\:[\da-fA-F]{1,4}){7})|(([\da-fA-F]"\
"{1,4}:){0,5}::([\da-fA-F]{1,4}:)"\
"{0,5}[\da-fA-F]{1,4})$"
regex = re.compile(rxp, re.I)
backend = {}
if value.count(':') == 0:
# port is not set
backend['ip'] = value
elif value.count(':') == 7:
# it's an ipv6 without port
backend['ip'] = value
elif value.count(':') == 8:
# it's an ipv6 with port
backend['ip'] = value.rsplit(':', 1)[0]
backend['port'] = int(value.rsplit(':', 1)[1])
else:
backend['ip'] = value.split(':')[0]
backend['port'] = int(value.split(':')[1])
try:
if regex.match(backend['ip']):
return backend
else:
self.fail('%s is not a valid ip address' %
backend['ip'], param, ctx)
except ValueError:
self.fail('%s is not a valid ip address' % backend['ip'], param,
ctx)
class WebAccNameParamType(GandiChoice):
""" Choice a webaccelerator """
name = 'webacc list'
def _get_choices(self, gandi):
""" Internal method to get choice list """
return [str(item['name']) for item in gandi.webacc.list()]
class WebAccVhostParamType(GandiChoice):
""" Retrieve vhost on a webaccelerator """
name = 'webacc vhost list'
def _get_choices(self, gandi):
""" Internal method to get choice list """
return [str(item['name']) for item in gandi.webacc.vhost_list()]
class OperStepParamType(click.Choice):
""" Choice parameter to filter on operation step """
name = 'oper step'
choices = ['BILL', 'WAIT', 'RUN', 'ERROR']
def __init__(self):
""" Initialize choices list. """
pass
class DNSRecordsParamType(GandiChoice):
""" Choice parameter to select a DNS record type. """
name = 'dns record'
def _get_choices(self, gandi):
""" Internal method to get choices list """
return [item for item in gandi.dns.type_list()]
def convert(self, value, param, ctx):
""" Convert value to uppercase. """
self.gandi = ctx.obj
value = value.upper()
return click.Choice.convert(self, value, param, ctx)
DATACENTER = DatacenterParamType()
PAAS_TYPE = PaasTypeParamType()
DISK_IMAGE = DiskImageParamType()
DISK_MAXLIST = 500
KERNEL = KernelParamType()
SNAPSHOTPROFILE_PAAS = SnapshotParamType('paas')
SNAPSHOTPROFILE_VM = SnapshotParamType('vm')
CERTIFICATE_PACKAGE = CertificatePackage()
CERTIFICATE_PACKAGE_TYPE = CertificatePackageType()
CERTIFICATE_PACKAGE_MAX = CertificatePackageMax()
CERTIFICATE_PACKAGE_WARRANTY = CertificatePackageWarranty()
CERTIFICATE_DCV_METHOD = CertificateDcvMethod()
EMAIL_TYPE = EmailParamType()
IP_TYPE = IpType()
SIZE = SizeParamType()
BACKEND = BackendParamType()
WEBACC_NAME = WebAccNameParamType()
WEBACC_VHOST_NAME = WebAccVhostParamType()
OPER_STEP = OperStepParamType()
DNS_RECORDS = DNSRecordsParamType()
class GandiOption(click.Option):
""" Custom command option class for handling configuration files.
When no value was found on command line, try to pull it from configuration
Display default or configuration value when needed
"""
def display_value(self, ctx, value):
""" Display value to be used for this parameter. """
gandi = ctx.obj
gandi.log('%s: %s' % (self.name, (value if value is not None
else 'Not found')))
def get_default(self, ctx):
""" Retrieve default value and display it when prompt disabled. """
value = click.Option.get_default(self, ctx)
if not self.prompt:
# value found in default display it
self.display_value(ctx, value)
return value
def consume_value(self, ctx, opts):
""" Retrieve default value and display it when prompt is disabled. """
value = click.Option.consume_value(self, ctx, opts)
if not value:
# value not found by click on command line
# now check using our context helper in order into
# local configuration
# global configuration
gandi = ctx.obj
value = gandi.get(self.name)
if value is not None:
# value found in configuration display it
self.display_value(ctx, value)
else:
if self.default is None and self.required:
metavar = ''
if self.type.name not in ['integer', 'text']:
metavar = self.make_metavar()
prompt = '%s %s' % (self.help, metavar)
gandi.echo(prompt)
return value
def handle_parse_result(self, ctx, opts, args):
""" Save value for this option in configuration
if key/value pair doesn't already exist.
Or old value in config was deprecated
it needs to be updated to the new value format
but the value keeps the same "meaning"
"""
gandi = ctx.obj
needs_update = False
value, args = click.Option.handle_parse_result(self, ctx, opts, args)
if value is not None:
previous_value = gandi.get(global_=True, key=self.name)
if isinstance(self.type, GandiChoice):
if value == previous_value:
needs_update = True
value = self.type.convert_deprecated_value(value)
if not previous_value or needs_update:
gandi.configure(global_=True, key=self.name, val=value)
opts[self.name] = value
value, args = click.Option.handle_parse_result(self, ctx, opts, args)
return value, args
def option(*param_decls, **attrs):
"""Attach an option to the command.
All positional arguments are passed as parameter declarations
to :class:`Option`, all keyword arguments are forwarded unchanged.
This is equivalent to creating an :class:`Option` instance manually and
attaching it to the :attr:`Command.params` list.
"""
def decorator(f):
_param_memo(f, GandiOption(param_decls, **attrs))
return f
return decorator
# create a decorator to pass the Gandi object as context to click calls
pass_gandi = click.make_pass_decorator(GandiContextHelper, ensure=True)
gandi.cli-1.2/gandi/cli/core/conf.py 0000644 0001750 0001750 00000021331 13160664756 020160 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
""" Configuration handler class declaration. """
import os
import sys
import yaml
import os.path
from distutils.dir_util import mkpath
try:
from yaml import CSafeLoader as YAMLLoader
except ImportError:
from yaml import SafeLoader as YAMLLoader
import click
class GandiConfig(object):
""" Base class for yaml configuration.
Manage
- read/write configuration files/values
- handle two scopes : global/local
"""
_conffiles = {}
home_config = os.environ.get('GANDI_CONFIG',
'~/.config/gandi/config.yaml')
local_config = '.gandi.config.yaml'
apienvs = {
'ote': 'https://rpc.ote.gandi.net/xmlrpc/',
'production': 'https://rpc.gandi.net/xmlrpc/',
}
default_apienv = 'production'
@classmethod
def load_config(cls):
""" Load global and local configuration files and update if needed."""
config_file = os.path.expanduser(cls.home_config)
global_conf = cls.load(config_file, 'global')
cls.load(cls.local_config, 'local')
# update global configuration if needed
cls.update_config(config_file, global_conf)
@classmethod
def update_config(cls, config_file, config):
""" Update configuration if needed. """
need_save = False
# delete old env key
if 'api' in config and 'env' in config['api']:
del config['api']['env']
need_save = True
# convert old ssh_key configuration entry
ssh_key = config.get('ssh_key')
sshkeys = config.get('sshkey')
if ssh_key and not sshkeys:
config.update({'sshkey': [ssh_key]})
need_save = True
elif ssh_key and sshkeys:
config.update({'sshkey': sshkeys.append(ssh_key)})
need_save = True
# remove old value
if ssh_key:
del config['ssh_key']
need_save = True
# save to disk
if need_save:
cls.save(config_file, config)
@classmethod
def load(cls, filename, name=None):
""" Load yaml configuration from filename. """
if not os.path.exists(filename):
return {}
name = name or filename
if name not in cls._conffiles:
with open(filename) as fdesc:
content = yaml.load(fdesc, YAMLLoader)
# in case the file is empty
if content is None:
content = {}
cls._conffiles[name] = content
return cls._conffiles[name]
@classmethod
def save(cls, filename, config):
""" Save configuration to yaml file. """
mode = os.O_WRONLY | os.O_TRUNC | os.O_CREAT
with os.fdopen(os.open(filename, mode, 0o600), 'w') as fname:
yaml.safe_dump(config, fname, indent=4, default_flow_style=False)
@classmethod
def _del(cls, scope, key, separator='.', conf=None):
orig_key = key
key = key.split(separator)
if not conf:
conf = cls._conffiles.get(scope, {})
if separator not in orig_key:
if orig_key in conf:
del conf[orig_key]
return
for k in key:
if k not in conf:
return
else:
cls._del(scope, separator.join([k1 for k1 in key if k1 != k]),
conf=conf[k])
return
@classmethod
def delete(cls, global_, key):
""" Delete key/value pair from configuration file. """
# first retrieve current configuration
scope = 'global' if global_ else 'local'
config = cls._conffiles.get(scope, {})
cls._del(scope, key)
conf_file = cls.home_config if global_ else cls.local_config
# save configuration to file
cls.save(os.path.expanduser(conf_file), config)
@classmethod
def _set(cls, scope, key, val, separator='.'):
orig_key = key
key = key.split(separator)
value = cls._conffiles.get(scope, {})
if separator not in orig_key:
value[orig_key] = val
return
for k in key:
if k not in value:
value[k] = {}
last_val = value
value = value[k]
else:
last_val = value
value = value[k]
last_val[k] = val
@classmethod
def _get(cls, scope, key, default=None, separator='.'):
key = key.split(separator)
value = cls._conffiles.get(scope, {})
if not value:
return default
try:
for k in key:
value = value[k]
return value
except KeyError:
return default
@classmethod
def get(cls, key, default=None, separator='.', global_=False):
""" Retrieve a key value from loaded configuration.
Order of search if global_=False:
1/ environnment variables
2/ local configuration
3/ global configuration
"""
# first check environnment variables
# if we're not in global scope
if not global_:
ret = os.environ.get(key.upper().replace('.', '_'))
if ret is not None:
return ret
# then check in local and global configuration unless global_=True
scopes = ['global'] if global_ else ['local', 'global']
for scope in scopes:
ret = cls._get(scope, key, default, separator)
if ret is not None and ret != default:
return ret
if ret is None or ret == default:
return default
@classmethod
def configure(cls, global_, key, val):
""" Update and save configuration value to file. """
# first retrieve current configuration
scope = 'global' if global_ else 'local'
if scope not in cls._conffiles:
cls._conffiles[scope] = {}
config = cls._conffiles.get(scope, {})
# apply modification to fields
cls._set(scope, key, val)
conf_file = cls.home_config if global_ else cls.local_config
# save configuration to file
cls.save(os.path.expanduser(conf_file), config)
@classmethod
def list(cls, global_):
""" Return configuration file content. """
scope = 'global' if global_ else 'local'
return cls._conffiles.get(scope, {})
@classmethod
def init_config(cls):
""" Initialize Gandi CLI configuration.
Create global configuration directory with API credentials
"""
try:
# first load current conf and only overwrite needed params
# we don't want to reset everything
config_file = os.path.expanduser(cls.home_config)
config = cls.load(config_file, 'global')
cls._del('global', 'api.env')
hidden_apikey = '%s...' % cls.get('api.key', '')[:6]
apikey = click.prompt('Api key (xmlrpc)',
default=hidden_apikey)
if apikey == hidden_apikey:
# if default value then use actual value not hidden one
apikey = cls.get('api.key')
env_choice = click.Choice(list(cls.apienvs.keys()))
apienv = click.prompt('Environnment [production]/ote',
default=cls.default_apienv,
type=env_choice,
show_default=False)
sshkey = click.prompt('SSH keyfile',
default='~/.ssh/id_rsa.pub')
hidden_apikeyrest = '%s...' % cls.get('apirest.key', '')[:6]
apikeyrest = click.prompt('Api key (REST)',
default=hidden_apikeyrest)
if apikeyrest == hidden_apikeyrest:
# if default value then use actual value not hidden one
apikeyrest = cls.get('apirest.key')
config.update({
'api': {'key': apikey,
'host': cls.apienvs[apienv]},
})
if apikeyrest:
config.update({
'apirest': {'key': apikeyrest},
})
if sshkey is not None:
sshkey_file = os.path.expanduser(sshkey)
if os.path.exists(sshkey_file):
config['sshkey'] = [sshkey_file]
directory = os.path.expanduser(os.path.dirname(config_file))
if not os.path.exists(directory):
mkpath(directory, 0o700)
# save to disk
cls.save(config_file, config)
# load in memory
cls.load(config_file, 'global')
except (KeyboardInterrupt, click.exceptions.Abort):
cls.echo('Aborted.')
sys.exit(1)
gandi.cli-1.2/gandi/cli/__main__.py 0000644 0001750 0001750 00000000224 12441335654 020012 0 ustar sayoun sayoun 0000000 0000000 #!/usr/bin/python
# -*- coding: utf-8 -*-
from gandi.cli.core.cli import cli
def main():
cli(obj={})
if __name__ == "__main__":
main()
gandi.cli-1.2/gandi/cli/__init__.py 0000644 0001750 0001750 00000000055 13227414723 020031 0 ustar sayoun sayoun 0000000 0000000 # -*- coding: utf-8 -*-
__version__ = '1.2'
gandi.cli-1.2/gandi/cli/commands/ 0000755 0001750 0001750 00000000000 13227415174 017522 5 ustar sayoun sayoun 0000000 0000000 gandi.cli-1.2/gandi/cli/commands/snapshotprofile.py 0000644 0001750 0001750 00000002656 13164644514 023327 0 ustar sayoun sayoun 0000000 0000000 """ Snapshot profiles namespace commands. """
import click
from gandi.cli.core.cli import cli
from gandi.cli.core.utils import output_snapshot_profile
from gandi.cli.core.params import pass_gandi
@cli.group(name='snapshotprofile')
@pass_gandi
def snapshotprofile(gandi):
"""Commands related to hosting snapshot profiles."""
@snapshotprofile.command()
@click.option('--only-paas', help='Only display PaaS profiles.', is_flag=True)
@click.option('--only-vm', help='Only display vm profile.s', is_flag=True)
@pass_gandi
def list(gandi, only_paas, only_vm):
""" List snapshot profiles. """
target = None
if only_paas and not only_vm:
target = 'paas'
if only_vm and not only_paas:
target = 'vm'
output_keys = ['id', 'name', 'kept_total', 'target']
result = gandi.snapshotprofile.list({}, target=target)
for num, profile in enumerate(result):
if num:
gandi.separator_line()
output_snapshot_profile(gandi, profile, output_keys)
return result
@snapshotprofile.command()
@click.argument('resource')
@pass_gandi
def info(gandi, resource):
""" Display information about a snapshot profile.
Resource can be a profile name or ID
"""
output_keys = ['id', 'name', 'kept_total', 'target', 'quota_factor',
'schedules']
result = gandi.snapshotprofile.info(resource)
output_snapshot_profile(gandi, result, output_keys)
return result
gandi.cli-1.2/gandi/cli/commands/disk.py 0000644 0001750 0001750 00000034612 13227142745 021035 0 ustar sayoun sayoun 0000000 0000000 """ Disk namespace commands. """
import click
from click.exceptions import UsageError
from gandi.cli.core.cli import cli
from gandi.cli.core.utils import (
output_disk, output_generic, randomstring,
DatacenterLimited
)
from gandi.cli.core.utils.size import disk_check_size
from gandi.cli.core.params import (pass_gandi, DATACENTER, SNAPSHOTPROFILE_VM,
KERNEL, SIZE, option, DISK_IMAGE)
@cli.group(name='disk')
@pass_gandi
def disk(gandi):
"""Commands related to hosting disks."""
@disk.command()
@click.option('--only-data', help='Only display data disks.', is_flag=True)
@click.option('--only-snapshot', help='Only display snapshots.', is_flag=True)
@click.option('--attached', help='Only display disks attached to a VM',
is_flag=True)
@click.option('--detached', help='Only display detached disks', is_flag=True)
@click.option('--type', help='Display types.', is_flag=True)
@click.option('--id', help='Display ids.', is_flag=True)
@click.option('--vm', help='Display vms.', is_flag=True)
@click.option('--snapshotprofile', help='Display snapshot profile.',
is_flag=True)
@click.option('--datacenter', default=None, type=DATACENTER,
help='Filter results by datacenter.')
@click.option('--limit', help='Limit number of results.', default=100,
show_default=True)
@pass_gandi
def list(gandi, only_data, only_snapshot, attached, detached, type, id, vm,
snapshotprofile, datacenter, limit):
""" List disks. """
options = {
'items_per_page': limit,
}
if attached and detached:
raise UsageError('You cannot use both --attached and --detached.')
if only_data:
options.setdefault('type', []).append('data')
if only_snapshot:
options.setdefault('type', []).append('snapshot')
if datacenter:
options['datacenter_id'] = gandi.datacenter.usable_id(datacenter)
output_keys = ['name', 'state', 'size']
if type:
output_keys.append('type')
if id:
output_keys.append('id')
if vm:
output_keys.append('vm')
profiles = []
if snapshotprofile:
output_keys.append('profile')
profiles = gandi.snapshotprofile.list()
result = gandi.disk.list(options)
vms = dict([(vm_['id'], vm_) for vm_ in gandi.iaas.list()])
# filter results per attached/detached
disks = []
for disk in result:
if attached and not disk['vms_id']:
continue
if detached and disk['vms_id']:
continue
disks.append(disk)
for num, disk in enumerate(disks):
if num:
gandi.separator_line()
output_disk(gandi, disk, [], vms, profiles, output_keys)
return result
@disk.command()
@click.argument('resource', nargs=-1, required=True)
@pass_gandi
def info(gandi, resource):
""" Display information about a disk.
Resource can be a disk name or ID
"""
output_keys = ['name', 'state', 'size', 'type', 'id', 'dc', 'vm',
'profile', 'kernel', 'cmdline']
resource = sorted(tuple(set(resource)))
vms = dict([(vm['id'], vm) for vm in gandi.iaas.list()])
datacenters = gandi.datacenter.list()
result = []
for num, item in enumerate(resource):
if num:
gandi.separator_line()
disk = gandi.disk.info(item)
output_disk(gandi, disk, datacenters, vms, [], output_keys)
result.append(disk)
return result
@disk.command()
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.option('--force', '-f', is_flag=True,
help='This is a dangerous option that will cause CLI to continue'
' without prompting. (default=False).')
@pass_gandi
@click.argument('resource', nargs=-1, required=True)
def detach(gandi, resource, background, force):
""" Detach disks from currectly attached vm.
Resource can be a disk name, or ID
"""
resource = sorted(tuple(set(resource)))
if not force:
proceed = click.confirm('Are you sure you want to detach %s?' %
', '.join(resource))
if not proceed:
return
result = gandi.disk.detach(resource, background)
if background:
gandi.pretty_echo(result)
return result
@disk.command()
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.option('-r', '--read-only', default=False, is_flag=True,
help='Attach disk as read-only')
@click.option('--position', '-p', type=click.INT,
help='Position where disk should be attached: 0 for system disk.'
' If there is already a disk attached at the specified'
' position, it will be swapped.')
@click.option('--force', '-f', is_flag=True,
help='This is a dangerous option that will cause CLI to continue'
' without prompting. (default=False).')
@pass_gandi
@click.argument('disk', nargs=1, required=True)
@click.argument('vm', nargs=1, required=True)
def attach(gandi, disk, vm, position, read_only, background, force):
""" Attach disk to vm.
disk can be a disk name, or ID
vm can be a vm name, or ID
"""
if not force:
proceed = click.confirm("Are you sure you want to attach disk '%s'"
" to vm '%s'?" % (disk, vm))
if not proceed:
return
disk_info = gandi.disk.info(disk)
attached = disk_info.get('vms_id', False)
if attached and not force:
gandi.echo('This disk is still attached')
proceed = click.confirm('Are you sure you want to detach %s?' % disk)
if not proceed:
return
result = gandi.disk.attach(disk, vm, background, position, read_only)
if background and result:
gandi.pretty_echo(result)
return result
@disk.command()
@click.option('--cmdline', type=click.STRING, default=None,
help='Kernel cmdline.')
@click.option('--kernel', type=KERNEL, default=None, help='Kernel for disk.')
@click.option('--name', type=click.STRING, default=None, help='Disk name.')
@click.option('--size', default=None, metavar='[+]SIZE[M|G|T]', type=SIZE,
help=('Disk size. A size suffix (M for megabytes up to T for '
'terabytes) is optional, megabytes is the default if no '
'suffix is present. A prefix + is optionnal, if provided '
'size value will be added to current disk size, default '
'is to set directly new disk size.'),
callback=disk_check_size)
@click.option('--snapshotprofile', help='Selected snapshot profile.',
default=None, type=SNAPSHOTPROFILE_VM)
@click.option('--delete-snapshotprofile', default=False, is_flag=True,
help='Remove snapshot profile associated to this disk.')
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@pass_gandi
@click.argument('resource')
def update(gandi, resource, cmdline, kernel, name, size,
snapshotprofile, delete_snapshotprofile, background):
""" Update a disk.
Resource can be a disk name, or ID
"""
if snapshotprofile and delete_snapshotprofile:
raise UsageError('You must not set snapshotprofile and '
'delete-snapshotprofile.')
if delete_snapshotprofile:
snapshotprofile = ''
if kernel:
source_info = gandi.disk.info(resource)
available = gandi.kernel.is_available(source_info, kernel)
if not available:
raise UsageError('Kernel %s is not available for disk %s' %
(kernel, resource))
result = gandi.disk.update(resource, name, size, snapshotprofile,
background, cmdline, kernel)
if background:
gandi.pretty_echo(result)
return result
@disk.command()
@click.option('--force', '-f', is_flag=True,
help='This is a dangerous option that will cause CLI to continue'
' without prompting. (default=False).')
@click.option('--bg', '--background', default=False, is_flag=True,
help='run command in background mode (default=False).')
@click.argument('resource', nargs=-1, required=True)
@pass_gandi
def delete(gandi, resource, force, background):
""" Delete a disk. """
output_keys = ['id', 'type', 'step']
resource = sorted(tuple(set(resource)))
if not force:
disk_info = "'%s'" % ', '.join(resource)
proceed = click.confirm('Are you sure you want to delete disk %s?'
% disk_info)
if not proceed:
return
opers = gandi.disk.delete(resource, background)
if background:
for oper in opers:
output_generic(gandi, oper, output_keys)
return opers
@disk.command()
@click.option('--name', type=click.STRING, default=None,
help='Disk name, will be generated if not provided.')
@click.option('--vm', default=None, type=click.STRING,
help='Attach the newly created disk to the vm.')
@click.option('--size', default='3072', metavar='SIZE[M|G|T]', type=SIZE,
help=('Disk size. A size suffix (M for megabytes up to T for '
'terabytes) is optional, megabytes is the default if no '
'suffix is present.'),
callback=disk_check_size)
@click.option('--snapshotprofile', help='Selected snapshot profile.',
default=None, type=SNAPSHOTPROFILE_VM)
@click.option('--source', default=None, type=DISK_IMAGE,
help='Create a disk from a disk or a snapshot.')
@option('--datacenter', type=DATACENTER, default='FR-SD5',
help='Datacenter where the disk will be created.')
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@pass_gandi
def create(gandi, name, vm, size, snapshotprofile, datacenter, source,
background):
""" Create a new disk. """
try:
gandi.datacenter.is_opened(datacenter, 'iaas')
except DatacenterLimited as exc:
gandi.echo('/!\ Datacenter %s will be closed on %s, '
'please consider using another datacenter.' %
(datacenter, exc.date))
if vm:
vm_dc = gandi.iaas.info(vm)
vm_dc_id = vm_dc['datacenter_id']
dc_id = int(gandi.datacenter.usable_id(datacenter))
if vm_dc_id != dc_id:
gandi.echo('/!\ VM %s datacenter will be used instead of %s.'
% (vm, datacenter))
datacenter = vm_dc_id
output_keys = ['id', 'type', 'step']
name = name or randomstring('vdi')
disk_type = 'data'
oper = gandi.disk.create(name, vm, size, snapshotprofile, datacenter,
source, disk_type, background)
if background:
output_generic(gandi, oper, output_keys)
return oper
@disk.command()
@click.option('--name', type=click.STRING, default=None,
help='Snapshot name, will be generated if not provided.')
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.argument('resource')
@pass_gandi
def snapshot(gandi, name, resource, background):
""" Create a snapshot on the fly. """
name = name or randomstring('snp')
source_info = gandi.disk.info(resource)
datacenter = source_info['datacenter_id']
result = gandi.disk.create(name, None, None, None, datacenter, resource,
'snapshot', background)
if background:
gandi.pretty_echo(result)
return result
@disk.command()
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.argument('resource', required=True)
@pass_gandi
def rollback(gandi, resource, background):
""" Rollback a disk from a snapshot. """
result = gandi.disk.rollback(resource, background)
if background:
gandi.pretty_echo(result)
return result
@disk.command()
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.option('--force', '-f', is_flag=True,
help='This is a dangerous option that will cause CLI to continue'
' without prompting. (default=False).')
@click.argument('resource', required=True)
@pass_gandi
def migrate(gandi, resource, force, background):
""" Migrate a disk to another datacenter. """
# check it's not attached
source_info = gandi.disk.info(resource)
if source_info['vms_id']:
click.echo('Cannot start the migration: disk %s is attached. '
'Please detach the disk before starting the migration.'
% resource)
return
disk_datacenter = source_info['datacenter_id']
dc_choices = gandi.datacenter.list_migration_choice(disk_datacenter)
if not dc_choices:
click.echo('No datacenter is available for migration')
return
elif len(dc_choices) == 1:
# use the only one available
datacenter_id = dc_choices[0]['id']
else:
choice_list = [dc['dc_code'] for dc in dc_choices]
dc_choice = click.Choice(choice_list)
dc_chosen = click.prompt('Select a datacenter [%s]' % '|'.join(choice_list), # noqa
type=dc_choice,
show_default=True)
datacenter_id = [dc['id'] for dc in dc_choices
if dc['dc_code'] == dc_chosen][0]
if not force:
proceed = click.confirm('Are you sure you want to migrate disk %s ?'
% resource)
if not proceed:
return
datacenters = gandi.datacenter.list()
dc_from = [dc['dc_code']
for dc in datacenters if dc['id'] == disk_datacenter][0]
dc_to = [dc['dc_code']
for dc in datacenters if dc['id'] == datacenter_id][0]
migration_msg = ('* Starting the migration of disk %s from datacenter %s '
'to %s' % (resource, dc_from, dc_to))
gandi.echo(migration_msg)
output_keys = ['id', 'type', 'step']
oper = gandi.disk.migrate(resource, datacenter_id, background)
if background:
output_generic(gandi, oper, output_keys)
return oper
gandi.cli-1.2/gandi/cli/commands/docker.py 0000644 0001750 0001750 00000002374 13164644514 021353 0 ustar sayoun sayoun 0000000 0000000 """ Docker namespace commands. """
import os
import click
from gandi.cli.core.cli import cli
from gandi.cli.core.params import pass_gandi
@cli.command()
@click.option('--vm', help='Use given VM for docker connection')
@click.argument('args', nargs=-1)
@pass_gandi
def docker(gandi, vm, args):
"""
Manage docker instance
"""
if not [basedir for basedir in os.getenv('PATH', '.:/usr/bin').split(':')
if os.path.exists('%s/docker' % basedir)]:
gandi.echo("""'docker' not found in $PATH, required for this command \
to work
See https://docs.docker.com/installation/#installation to install, or use:
# curl https://get.docker.io/ | sh""")
return
if vm:
gandi.configure(True, 'dockervm', vm)
else:
vm = gandi.get('dockervm')
if not vm:
gandi.echo("""
No docker vm specified. You can create one:
$ gandi vm create --hostname docker --image "Ubuntu 14.04 64 bits LTS (HVM)" \\
--run 'wget -O - https://get.docker.io/ | sh'
Then configure it using:
$ gandi docker --vm docker ps
Or to both change target vm and spawn a process (note the -- separator):
$ gandi docker --vm myvm -- run -i -t debian bash
""") # noqa
return
return gandi.docker.handle(vm, args)
gandi.cli-1.2/gandi/cli/commands/vm.py 0000644 0001750 0001750 00000045031 13227414355 020521 0 ustar sayoun sayoun 0000000 0000000 """ Virtual machines namespace commands. """
import click
from gandi.cli.core.cli import cli
from gandi.cli.core.utils import (
output_vm, output_image, output_generic, output_datacenter,
output_kernels, output_metric,
DatacenterLimited
)
from gandi.cli.core.utils.size import disk_check_size
from gandi.cli.core.utils.password import mkpassword
from gandi.cli.core.params import (
pass_gandi, option, IntChoice, DATACENTER, DISK_IMAGE, SIZE
)
@cli.group(name='vm')
@pass_gandi
def vm(gandi):
"""Commands related to hosting virtual machines."""
@vm.command()
@click.option('--state', default=None, help='Filter results by state.')
@click.option('--datacenter', default=None, type=DATACENTER,
help='Filter results by datacenter.')
@click.option('--id', help='Display ids.', is_flag=True)
@click.option('--limit', help='Limit number of results.', default=100,
show_default=True)
@pass_gandi
def list(gandi, state, id, limit, datacenter):
"""List virtual machines."""
options = {
'items_per_page': limit,
}
if state:
options['state'] = state
if datacenter:
options['datacenter_id'] = gandi.datacenter.usable_id(datacenter)
output_keys = ['hostname', 'state']
if id:
output_keys.append('id')
result = gandi.iaas.list(options)
for num, vm in enumerate(result):
if num:
gandi.separator_line()
output_vm(gandi, vm, [], output_keys)
return result
@vm.command()
@click.argument('resource', nargs=-1, required=True)
@click.option('--stat', default=False, is_flag=True,
help='Display general vm statistic')
@pass_gandi
def info(gandi, resource, stat):
"""Display information about a virtual machine.
Resource can be a Hostname or an ID
"""
output_keys = ['hostname', 'state', 'cores', 'memory', 'console',
'datacenter', 'ip']
justify = 14
if stat is True:
sampler = {'unit': 'hours', 'value': 1, 'function': 'max'}
time_range = 3600 * 24
query_vif = 'vif.bytes.all'
query_vbd = 'vbd.bytes.all'
resource = sorted(tuple(set(resource)))
datacenters = gandi.datacenter.list()
ret = []
for num, item in enumerate(resource):
if num:
gandi.separator_line()
vm = gandi.iaas.info(item)
output_vm(gandi, vm, datacenters, output_keys, justify)
ret.append(vm)
for num, disk in enumerate(vm['disks']):
gandi.echo('')
disk_out_keys = ['label', 'kernel_version', 'name', 'size']
output_image(gandi, disk, datacenters, disk_out_keys, justify,
warn_deprecated=False)
if stat is True:
metrics_vif = gandi.metric.query(vm['id'], time_range, query_vif,
'vm', sampler)
metrics_vbd = gandi.metric.query(vm['id'], time_range, query_vbd,
'vm', sampler)
gandi.echo('')
gandi.echo('vm network stats')
output_metric(gandi, metrics_vif, 'direction', justify)
gandi.echo('disk network stats')
output_metric(gandi, metrics_vbd, 'direction', justify)
return ret
@vm.command()
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.argument('resource', nargs=-1, required=True)
@pass_gandi
def stop(gandi, background, resource):
"""Stop a virtual machine.
Resource can be a Hostname or an ID
"""
output_keys = ['id', 'type', 'step']
resource = sorted(tuple(set(resource)))
opers = gandi.iaas.stop(resource, background)
if background:
for oper in opers:
output_generic(gandi, oper, output_keys)
return opers
@vm.command()
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.argument('resource', nargs=-1, required=True)
@pass_gandi
def start(gandi, background, resource):
"""Start a virtual machine.
Resource can be a Hostname or an ID
"""
output_keys = ['id', 'type', 'step']
resource = sorted(tuple(set(resource)))
opers = gandi.iaas.start(resource, background)
if background:
for oper in opers:
output_generic(gandi, oper, output_keys)
return opers
@vm.command()
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.argument('resource', nargs=-1, required=True)
@pass_gandi
def reboot(gandi, background, resource):
"""Reboot a virtual machine.
Resource can be a Hostname or an ID
"""
output_keys = ['id', 'type', 'step']
resource = sorted(tuple(set(resource)))
opers = gandi.iaas.reboot(resource, background)
if background:
for oper in opers:
output_generic(gandi, oper, output_keys)
return opers
@vm.command()
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.option('--force', '-f', is_flag=True,
help='This is a dangerous option that will cause CLI to continue'
' without prompting. (default=False).')
@click.argument('resource', nargs=-1, required=True)
@pass_gandi
def delete(gandi, background, force, resource):
"""Delete a virtual machine.
Resource can be a Hostname or an ID
"""
output_keys = ['id', 'type', 'step']
resource = sorted(tuple(set(resource)))
possible_resources = gandi.iaas.resource_list()
for item in resource:
if item not in possible_resources:
gandi.echo('Sorry virtual machine %s does not exist' % item)
gandi.echo('Please use one of the following: %s' %
possible_resources)
return
if not force:
instance_info = "'%s'" % ', '.join(resource)
proceed = click.confirm("Are you sure to delete Virtual Machine %s?" %
instance_info)
if not proceed:
return
iaas_list = gandi.iaas.list()
stop_opers = []
for item in resource:
vm = next((vm for (index, vm) in enumerate(iaas_list)
if vm['hostname'] == item), gandi.iaas.info(item))
if vm['state'] == 'running':
if background:
gandi.echo('Virtual machine not stopped, background option '
'disabled')
background = False
oper = gandi.iaas.stop(item, background)
if not background:
stop_opers.append(oper)
opers = gandi.iaas.delete(resource, background)
if background:
for oper in stop_opers + opers:
output_generic(gandi, oper, output_keys)
return opers
@vm.command()
@option('--datacenter', type=DATACENTER, default='FR-SD5',
help='Datacenter where the VM will be spawned.')
@option('--memory', type=click.INT, default=256,
help='Quantity of RAM in Megabytes to allocate.')
@option('--cores', type=click.INT, default=1,
help='Number of cpu.')
@click.option('--ip-version', type=IntChoice(['4', '6']), default=None,
help='Version of created IP.')
@option('--bandwidth', type=click.INT, default=102400,
help="Network bandwidth in kbit/s used to create the VM's first "
"network interface.")
@click.option('--login', default=None,
help='Login to create on the VM.')
@click.option('--password', default=False, is_flag=True,
help='Will ask for a password to be set for the root account '
'and the created login.')
@click.option('--hostname', default=None,
help='Hostname of the VM, will be generated if not provided.')
@option('--image', type=DISK_IMAGE, default='Debian 8',
help='Disk image used to boot the VM. A * prefix means the image is '
'deprecated and will soon be unavailable.')
@click.option('--run', default=None,
help='Shell command that will run at the first startup of a VM.'
'This command will run with root privileges in the ``/`` '
'directory at the end of its boot: network interfaces and '
'disks are mounted.')
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@option('--sshkey', multiple=True,
help='Authorize ssh authentication for the given ssh key.')
@click.option('--size', default=None, metavar='SIZE[M|G|T]', type=SIZE,
help=('Disk size. A size suffix (M for megabytes up to T for '
'terabytes) is optional, megabytes is the default if no '
'suffix is present.'),
callback=disk_check_size)
@click.option('--vlan', default=None, help='A vlan to use with this vm.')
@click.option('--ip', default=None, help='An ip in the vlan for this vm.')
@click.option('--script', default=None,
help='Local script to upload and run on the VM after creation.')
@click.option('--script-args', default=None,
help='Local script argument line.')
@click.option('--ssh', default=False, is_flag=True,
help='Open a SSH session to the machine after creation '
'(default=False).')
@click.option('--gen-password', default=False, is_flag=True,
help='Generate a random password to be set for the root '
'account and the created login(default=False).')
@pass_gandi
def create(gandi, datacenter, memory, cores, ip_version, bandwidth, login,
password, hostname, image, run, background, sshkey, size, vlan, ip,
script, script_args, ssh, gen_password):
"""Create a new virtual machine.
you can specify a configuration entry named 'sshkey' containing
path to your sshkey file
$ gandi config set [-g] sshkey ~/.ssh/id_rsa.pub
or getting the sshkey "my_key" from your gandi ssh keyring
$ gandi config set [-g] sshkey my_key
to know which disk image label (or id) to use as image
$ gandi vm images
"""
try:
gandi.datacenter.is_opened(datacenter, 'iaas')
except DatacenterLimited as exc:
if exc.date:
gandi.echo('/!\ Datacenter %s will be closed on %s, '
'please consider using another datacenter.' %
(datacenter, exc.date))
if gandi.image.is_deprecated(image, datacenter):
gandi.echo('/!\ Image %s is deprecated and will soon be unavailable.'
% image)
pwd = None
if gen_password:
pwd = mkpassword()
if password or (not pwd and not sshkey):
pwd = click.prompt('password', hide_input=True,
confirmation_prompt=True)
if ip and not vlan:
gandi.echo("--ip can't be used without --vlan.")
return
if not vlan and not ip_version:
ip_version = 6
if not ip_version:
gandi.echo("* Private only ip vm (can't enable emergency web console "
'access).')
# Display a short summary for creation
if login:
user_summary = 'root and %s users' % login
password_summary = 'Users root and %s' % login
else:
user_summary = 'root user'
password_summary = 'User root'
gandi.echo('* %s will be created.' % user_summary)
if sshkey:
gandi.echo('* SSH key authorization will be used.')
if pwd and gen_password:
gandi.echo('* %s setup with password %s' %
(password_summary, pwd))
if not pwd:
gandi.echo('* No password supplied for vm (required to enable '
'emergency web console access).')
result = gandi.iaas.create(datacenter, memory, cores, ip_version,
bandwidth, login, pwd, hostname,
image, run,
background,
sshkey, size, vlan, ip, script, script_args, ssh)
if background:
gandi.echo('* IAAS backend is now creating your VM and its '
'associated resources in the background.')
return result
@vm.command()
@click.option('--memory', type=click.INT, default=None,
help='Quantity of RAM in Megabytes to allocate.')
@click.option('--cores', type=click.INT, default=None,
help='Number of cpu.')
@click.option('--console', default=None, is_flag=True,
help='Activate the emergency console.')
@click.option('--password', default=False, is_flag=True,
help='Will ask for a password to be set for the root account '
'and the created login.')
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.option('--reboot', default=False, is_flag=True,
help='Accept a VM reboot for non-live updates')
@click.argument('resource')
@pass_gandi
def update(gandi, resource, memory, cores, console, password, background,
reboot):
"""Update a virtual machine.
Resource can be a Hostname or an ID
"""
pwd = None
if password:
pwd = click.prompt('password', hide_input=True,
confirmation_prompt=True)
max_memory = None
if memory:
max_memory = gandi.iaas.required_max_memory(resource, memory)
if max_memory and not reboot:
gandi.echo('memory update must be done offline.')
if not click.confirm("reboot machine %s?" % resource):
return
result = gandi.iaas.update(resource, memory, cores, console, pwd,
background, max_memory)
if background:
gandi.pretty_echo(result)
return result
@vm.command()
@click.argument('resource')
@pass_gandi
def console(gandi, resource):
"""Open a console to virtual machine.
Resource can be a Hostname or an ID
"""
gandi.echo('/!\ Please be aware that if you didn\'t provide a password '
'during creation, console service will be unavailable.')
gandi.echo('/!\ You can use "gandi vm update" command to set a password.')
gandi.echo('/!\ Use ~. ssh escape key to exit.')
gandi.iaas.console(resource)
@vm.command()
@click.option('--wait', default=False, is_flag=True,
help='Wait for virtual machine sshd to come up (timeout 2min).')
@click.option('--wipe-key', default=False, is_flag=True,
help='Wipe SSH known host entry first.')
@click.option('--login', '-l', default='root',
help='Use given login for ssh call')
@click.option('--identity', '-i', default=None,
help='Use specified path for ssh key')
@click.argument('resource')
@click.argument('args', nargs=-1)
@pass_gandi
def ssh(gandi, resource, login, identity, wipe_key, wait, args):
"""Spawn an SSH session to virtual machine.
Resource can be a Hostname or an ID
"""
if '@' in resource:
(login, resource) = resource.split('@', 1)
if wipe_key:
gandi.iaas.ssh_keyscan(resource)
if wait:
gandi.iaas.wait_for_sshd(resource)
gandi.iaas.ssh(resource, login, identity, args)
@vm.command()
@click.option('--datacenter', type=DATACENTER, default=None,
help='Filter by datacenter.')
@click.argument('label', required=False)
@pass_gandi
def images(gandi, label, datacenter):
"""List available system images for virtual machines.
You can also filter results using label, by example:
$ gandi vm images Ubuntu --datacenter LU
or
$ gandi vm images 'Ubuntu 10.04' --datacenter LU
"""
output_keys = ['label', 'os_arch', 'kernel_version', 'disk_id',
'dc', 'name']
datacenters = gandi.datacenter.list()
result = gandi.image.list(datacenter, label)
for num, image in enumerate(result):
if num:
gandi.separator_line()
output_image(gandi, image, datacenters, output_keys)
# also display usable disks
result = gandi.disk.list_create(datacenter, label)
for disk in result:
gandi.separator_line()
output_image(gandi, disk, datacenters, output_keys)
return result
@vm.command()
@click.option('--vm', default=None,
help='Output available kernels for given vm.')
@click.option('--datacenter', type=DATACENTER, default=None,
help='Filter by datacenter.')
@click.option('--flavor', default=None,
help='Filter by kernel flavor.')
@click.argument('match', default='', required=False, metavar='pattern')
@pass_gandi
def kernels(gandi, vm, datacenter, flavor, match):
"""List available kernels."""
if vm:
vm = gandi.iaas.info(vm)
dc_list = gandi.datacenter.filtered_list(datacenter, vm)
for num, dc in enumerate(dc_list):
if num:
gandi.echo('\n')
output_datacenter(gandi, dc, ['dc_name'])
kmap = gandi.kernel.list(dc['id'], flavor, match)
for _flavor in kmap:
gandi.separator_line()
output_kernels(gandi, _flavor, kmap[_flavor])
@cli.command()
@click.option('--id', help='Display ids.', is_flag=True)
@pass_gandi
def datacenters(gandi, id):
"""List available datacenters."""
output_keys = ['iso', 'name', 'country', 'dc_code', 'status']
if id:
output_keys.append('id')
result = gandi.datacenter.list()
for num, dc in enumerate(result):
if num:
gandi.separator_line()
output_datacenter(gandi, dc, output_keys, justify=10)
return result
@vm.command()
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.option('--force', '-f', is_flag=True,
help='This is a dangerous option that will cause CLI to continue'
' without prompting. (default=False).')
@click.option('--finalize', default=False, is_flag=True,
help='Finalize migration.')
@click.argument('resource', required=True)
@pass_gandi
def migrate(gandi, resource, force, background, finalize):
""" Migrate a virtual machine to another datacenter. """
if not gandi.iaas.check_can_migrate(resource):
return
if not force:
proceed = click.confirm('Are you sure you want to migrate VM %s ?'
% resource)
if not proceed:
return
if finalize:
gandi.iaas.need_finalize(resource)
output_keys = ['id', 'type', 'step']
oper = gandi.iaas.migrate(resource, background, finalize=finalize)
if background:
output_generic(gandi, oper, output_keys)
return oper
gandi.cli-1.2/gandi/cli/commands/record.py 0000644 0001750 0001750 00000017274 13227414205 021357 0 ustar sayoun sayoun 0000000 0000000 """ Record namespace commands. """
import os
import click
import json
from gandi.cli.core.cli import cli
from gandi.cli.core.utils import (
output_generic,
)
from gandi.cli.core.params import pass_gandi, StringConstraint
@cli.group(name='record')
@pass_gandi
def record(gandi):
"""Commands related to DNS zone records (xmlrpc)."""
@record.command()
@click.option('--zone-id', '-z', default=None, type=click.INT,
help='Zone ID to use, if not set default zone will be used.')
@click.option('--output', '-o', is_flag=True,
help='Write the records into a file.')
@click.option('--format', '-f', type=click.Choice(['text', 'json']),
help='Choose the output format', required=False)
@click.option('--limit', help='Limit number of results.', default=100,
show_default=True)
@click.argument('domain', required=True)
@pass_gandi
def list(gandi, domain, zone_id, output, format, limit):
"""List DNS zone records for a domain."""
options = {
'items_per_page': limit,
}
output_keys = ['name', 'type', 'value', 'ttl']
if not zone_id:
result = gandi.domain.info(domain)
zone_id = result['zone_id']
if not zone_id:
gandi.echo('No zone records found, domain %s doesn\'t seems to be '
'managed at Gandi.' % domain)
return
records = gandi.record.list(zone_id, options)
if not output and not format:
for num, rec in enumerate(records):
if num:
gandi.separator_line()
output_generic(gandi, rec, output_keys, justify=12)
elif output:
zone_filename = domain + "_" + str(zone_id)
if os.path.isfile(zone_filename):
open(zone_filename, 'w').close()
for record in records:
format_record = ('%s %s IN %s %s' %
(record['name'], record['ttl'],
record['type'], record['value']))
with open(zone_filename, 'ab') as zone_file:
zone_file.write(format_record + '\n')
gandi.echo('Your zone file have been writen in %s' % zone_filename)
elif format:
if format == 'text':
for record in records:
format_record = ('%s %s IN %s %s' %
(record['name'], record['ttl'],
record['type'], record['value']))
gandi.echo(format_record)
if format == 'json':
format_record = json.dumps(records, sort_keys=True,
indent=4, separators=(',', ': '))
gandi.echo(format_record)
return records
@record.command()
@click.option('--zone-id', '-z', default=None, type=click.INT,
help='Zone ID to use, if not set, default zone will be used.')
@click.option('--name', default=None, required=True,
help='Relative name, may contain leading wildcard. '
'`@` for empty name')
@click.option('--type', default=None, required=True,
type=click.Choice(['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT',
'WKS', 'SRV', 'LOC', 'SPF']),
help='DNS record type')
@click.option('--value', default=None, required=True,
type=StringConstraint(minlen=1, maxlen=1024),
help='Value for record. Semantics depends on the record type.'
'Currently limited to 1024 ASCII characters.'
'In case of TXT, each part between quotes is limited to 255'
' characters')
@click.option('--ttl', default=None, required=False,
type=click.IntRange(min=300, max=2592000),
help='Time to live, in seconds, between 5 minutes and 30 days')
@click.argument('domain', required=True)
@pass_gandi
def create(gandi, domain, zone_id, name, type, value, ttl):
"""Create new DNS zone record entry for a domain."""
if not zone_id:
result = gandi.domain.info(domain)
zone_id = result['zone_id']
if not zone_id:
gandi.echo('No zone records found, domain %s doesn\'t seems to be '
'managed at Gandi.' % domain)
return
record = {'type': type, 'name': name, 'value': value}
if ttl:
record['ttl'] = ttl
result = gandi.record.create(zone_id, record)
return result
@record.command()
@click.option('--zone-id', '-z', default=None, type=click.INT,
help='Zone ID to use, if not set, default zone will be used.')
@click.option('--name', default=None,
help='Relative name of the record to delete.')
@click.option('--type', default=None,
type=click.Choice(['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT',
'WKS', 'SRV', 'LOC', 'SPF']),
help='DNS record type')
@click.option('--value', default=None,
type=StringConstraint(minlen=1, maxlen=1024),
help='Value for record. Semantics depends on the record type.'
'Currently limited to 1024 ASCII characters.'
'In case of TXT, each part between quotes is limited to 255'
' characters')
@click.argument('domain', required=True)
@pass_gandi
def delete(gandi, domain, zone_id, name, type, value):
"""Delete a record entry for a domain"""
if not zone_id:
result = gandi.domain.info(domain)
zone_id = result['zone_id']
if not zone_id:
gandi.echo('No zone records found, domain %s doesn\'t seems to be '
'managed at Gandi.' % domain)
return
if not name and not type and not value:
proceed = click.confirm('This command without parameters --type, '
'--name or --value will remove all records'
' in this zone file. Are you sur to '
'perform this action ?')
if not proceed:
return
record = {'name': name, 'type': type, 'value': value}
result = gandi.record.delete(zone_id, record)
return result
@record.command()
@click.option('--zone-id', '-z', default=None, type=click.INT,
help='Zone ID to use, if not set, default zone will be used.')
@click.option('--file', '-f', type=click.File('r'),
required=False, help='Filename of the zone file.')
@click.option('--record', '-r', default=None, required=False,
help="'name TTL IN TYPE [A, AAAA, MX, TXT, SPF] value' \n"
"Note that you can specify only a name, but in case of "
"multiple entries with the same name, only the first one will "
"be updated")
@click.option('--new-record', default=None, required=False,
help="'name TTL IN TYPE [A, AAAA, MX, TXT, SPF] value'")
@click.argument('domain', required=True)
@pass_gandi
def update(gandi, domain, zone_id, file, record, new_record):
"""Update records entries for a domain.
You can update an individual record using --record and --new-record
parameters
Or you can use a plaintext file to update all records of a DNS zone at
once with --file parameter.
"""
if not zone_id:
result = gandi.domain.info(domain)
zone_id = result['zone_id']
if not zone_id:
gandi.echo('No zone records found, domain %s doesn\'t seems to be'
' managed at Gandi.' % domain)
return
if file:
records = file.read()
result = gandi.record.zone_update(zone_id, records)
return result
elif record and new_record:
result = gandi.record.update(zone_id, record, new_record)
return result
else:
gandi.echo('You must indicate a zone file or a record.'
' Use `gandi record update --help` for more information')
gandi.cli-1.2/gandi/cli/commands/oper.py 0000644 0001750 0001750 00000002443 13164644514 021046 0 ustar sayoun sayoun 0000000 0000000 """ Operation namespace commands. """
import click
from gandi.cli.core.cli import cli
from gandi.cli.core.utils import output_generic
from gandi.cli.core.params import pass_gandi, OPER_STEP
@cli.group(name='oper')
@pass_gandi
def oper(gandi):
"""Commands related to Gandi operations."""
@oper.command()
@click.option('--limit', help='Limit number of results.', default=100,
show_default=True)
@click.option('--step', '-s', type=OPER_STEP, help='Filter the result by step',
default=['BILL', 'WAIT', 'RUN'], multiple=True,
show_default=True)
@pass_gandi
def list(gandi, limit, step):
"""List operations."""
output_keys = ['id', 'type', 'step']
options = {
'step': step,
'items_per_page': limit,
'sort_by': 'date_created DESC'
}
result = gandi.oper.list(options)
for num, oper in enumerate(reversed(result)):
if num:
gandi.separator_line()
output_generic(gandi, oper, output_keys)
return result
@oper.command()
@click.argument('id', type=click.INT)
@pass_gandi
def info(gandi, id):
"""Display information about an operation."""
output_keys = ['id', 'type', 'step', 'last_error']
oper = gandi.oper.info(id)
output_generic(gandi, oper, output_keys)
return oper
gandi.cli-1.2/gandi/cli/commands/root.py 0000644 0001750 0001750 00000005732 13155727500 021065 0 ustar sayoun sayoun 0000000 0000000 """ Main namespace commands. """
import click
from gandi.cli.core.cli import cli
from gandi.cli.core.utils import output_generic, output_service
from gandi.cli.core.params import pass_gandi
@cli.command()
@pass_gandi
def setup(gandi):
""" Initialize Gandi CLI configuration.
Create global configuration directory with API credentials
"""
intro = """Welcome to GandiCLI, let's configure a few things before we \
start.
"""
outro = """
Setup completed. You can now:
* use 'gandi' to see all command.
* use 'gandi vm create' to create and access a Virtual Machine.
* use 'gandi paas create' to create and access a SimpleHosting instance.
"""
gandi.echo(intro)
gandi.init_config()
gandi.echo(outro)
@cli.command()
@pass_gandi
def api(gandi):
"""Display information about API used."""
key_name = 'API version'
result = gandi.api.info()
result[key_name] = result.pop('api_version')
output_generic(gandi, result, [key_name])
return result
@cli.command()
@click.argument('command', required=False, nargs=-1)
@click.pass_context
def help(ctx, command):
"""Display help for a command."""
command = ' '.join(command)
if not command:
click.echo(cli.get_help(ctx))
return
cmd = cli.get_command(ctx, command)
if cmd:
click.echo(cmd.get_help(ctx))
else:
click.echo(cli.get_help(ctx))
@cli.command()
@click.argument('service', required=False)
@pass_gandi
def status(gandi, service):
"""Display current status from status.gandi.net."""
if not service:
global_status = gandi.status.status()
if global_status['status'] == 'FOGGY':
# something is going on but not affecting services
filters = {
'category': 'Incident',
'current': True,
}
events = gandi.status.events(filters)
for event in events:
if event['services']:
# do not process services
continue
event_url = gandi.status.event_timeline(event)
service_detail = '%s - %s' % (event['title'], event_url)
gandi.echo(service_detail)
# then check other services
descs = gandi.status.descriptions()
needed = services = gandi.status.services()
if service:
needed = [serv for serv in services
if serv['name'].lower() == service.lower()]
for serv in needed:
if serv['status'] != 'STORMY':
output_service(gandi, serv['name'], descs[serv['status']])
continue
filters = {
'category': 'Incident',
'services': serv['name'],
'current': True,
}
events = gandi.status.events(filters)
for event in events:
event_url = gandi.status.event_timeline(event)
service_detail = '%s - %s' % (event['title'], event_url)
output_service(gandi, serv['name'], service_detail)
return services
gandi.cli-1.2/gandi/cli/commands/domain.py 0000644 0001750 0001750 00000006764 13164644514 021362 0 ustar sayoun sayoun 0000000 0000000 """ Domain namespace commands. """
import click
from gandi.cli.core.cli import cli
from gandi.cli.core.utils import output_contact_info, output_domain
from gandi.cli.core.params import pass_gandi
@cli.group(name='domain')
@pass_gandi
def domain(gandi):
"""Commands related to domains."""
@domain.command()
@click.option('--limit', help='Limit number of results.', default=100,
show_default=True)
@pass_gandi
def list(gandi, limit):
"""List domains."""
options = {'items_per_page': limit}
domains = gandi.domain.list(options)
for domain in domains:
gandi.echo(domain['fqdn'])
return domains
@domain.command()
@click.argument('resource')
@pass_gandi
def info(gandi, resource):
"""Display information about a domain."""
output_keys = ['fqdn', 'nameservers', 'services', 'zone_id', 'tags',
'created', 'expires', 'updated']
contact_field = ['owner', 'admin', 'bill', 'tech', 'reseller']
result = gandi.domain.info(resource)
output_contact_info(gandi, result['contacts'], contact_field, justify=12)
output_domain(gandi, result, output_keys, justify=12)
return result
@domain.command()
@click.option('--domain', default=None, help='Name of the domain.')
@click.option('--duration', default=1, prompt=True,
type=click.IntRange(min=1, max=10),
help='Registration period in years, between 1 and 10.')
@click.option('--owner', default=None,
help='Registrant handle.')
@click.option('--admin', default=None,
help='Administrative contact handle.')
@click.option('--tech', default=None,
help='Technical contact handle.')
@click.option('--bill', default=None,
help='Billing contact handle.')
@click.option('--nameserver', default=None,
help='Nameserver', multiple=True)
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.argument('resource', metavar='DOMAIN', required=False)
@pass_gandi
def create(gandi, resource, domain, duration, owner, admin, tech, bill,
nameserver, background):
"""Buy a domain."""
if domain:
gandi.echo('/!\ --domain option is deprecated and will be removed '
'upon next release.')
gandi.echo("You should use 'gandi domain create %s' instead." % domain)
if (domain and resource) and (domain != resource):
gandi.echo('/!\ You specified both an option and an argument which '
'are different, please choose only one between: %s and %s.'
% (domain, resource))
return
_domain = domain or resource
if not _domain:
_domain = click.prompt('Name of the domain')
result = gandi.domain.create(_domain, duration, owner, admin, tech, bill,
nameserver, background)
if background:
gandi.pretty_echo(result)
return result
@domain.command()
@click.option('--duration', default=1, prompt=True,
type=click.IntRange(min=1, max=10),
help='Registration period in years, between 1 and 10.')
@click.option('--bg', '--background', default=False, is_flag=True,
help='Run command in background mode (default=False).')
@click.argument('domain')
@pass_gandi
def renew(gandi, domain, duration, background):
"""Renew a domain."""
result = gandi.domain.renew(domain, duration, background)
if background:
gandi.pretty_echo(result)
return result
gandi.cli-1.2/gandi/cli/commands/forward.py 0000644 0001750 0001750 00000005020 13164644514 021537 0 ustar sayoun sayoun 0000000 0000000 """ Forward namespace commands. """
import click
from gandi.cli.core.cli import cli
from gandi.cli.core.utils import output_forward
from gandi.cli.core.params import pass_gandi, EMAIL_TYPE
@cli.group(name='forward')
@pass_gandi
def forward(gandi):
"""Commands related to domain mail forwards."""
@forward.command()
@click.option('--limit', help='Limit number of results.',
default=100, show_default=True)
@click.argument('domain', metavar='domain.tld')
@pass_gandi
def list(gandi, domain, limit):
"""List mail forwards for a domain."""
options = {'items_per_page': limit}
result = gandi.forward.list(domain, options)
for forward in result:
output_forward(gandi, domain, forward)
return result
@forward.command()
@click.option('--destination', '-d', help='Add forward destination.',
multiple=True, required=True)
@click.argument('address', type=EMAIL_TYPE, metavar='address@domain.tld')
@pass_gandi
def create(gandi, address, destination):
"""Create a domain mail forward."""
source, domain = address
result = gandi.forward.create(domain, source, destination)
return result
@forward.command()
@click.option('--dest-add', '-a', help='Add forward destination.',
multiple=True, required=False)
@click.option('--dest-del', '-d', help='Delete forward destination.',
multiple=True, required=False)
@click.argument('address', type=EMAIL_TYPE, metavar='address@domain.tld')
@pass_gandi
def update(gandi, address, dest_add, dest_del):
"""Update a domain mail forward."""
source, domain = address
if not dest_add and not dest_del:
gandi.echo('Nothing to update: you must provide destinations to '
'update, use --dest-add/-a or -dest-del/-d parameters.')
return
result = gandi.forward.update(domain, source, dest_add, dest_del)
return result
@forward.command()
@click.option('--force', '-f', is_flag=True,
help='This is a dangerous option that will cause CLI to continue'
' without prompting. (default=False).')
@click.argument('address', type=EMAIL_TYPE, metavar='address@domain.tld')
@pass_gandi
def delete(gandi, address, force):
"""Delete a domain mail forward."""
source, domain = address
if not force:
proceed = click.confirm('Are you sure to delete the domain '
'mail forward %s@%s ?' % (source, domain))
if not proceed:
return
result = gandi.forward.delete(domain, source)
return result
gandi.cli-1.2/gandi/cli/commands/contact.py 0000644 0001750 0001750 00000007644 13164644514 021544 0 ustar sayoun sayoun 0000000 0000000 """ Contact namespace commands. """
import click
import time
import webbrowser
# define unicode for python3
try:
unicode
except NameError:
unicode = str
from gandi.cli.core.cli import cli
from gandi.cli.core.utils import randomstring
from gandi.cli.core.params import pass_gandi
FIELDS = (('type', 'Choose your contact type',
{'valid': ((0, 'individual'),
(1, 'company'),
(2, 'association'),
(3, 'public body'),
(4, 'reseller')),
'convert': int}),
('given', 'What is your first name', None),
('family', 'What is your last name', None),
('orgname', 'What is your company name',
{'display': lambda contact_: contact_['type'] != 0}),
('email', 'What is your email address', None),
('streetaddr', 'What is your street address', None),
('zip', 'What is your zipcode', None),
('city', 'Which city', None),
('country', 'Which country', None),
('phone', 'What is your telephone number', None))
FIELDS_POSITION = dict([(j, i) for i, j in enumerate(
[field[0] for field in FIELDS])])
def ask_field(gandi, contact, field, label, checks):
valid = display = None
convert = unicode
if checks:
valid = checks.get('valid')
convert = checks.get('convert', unicode)
display = checks.get('display')
if display and not display(contact):
return
if not valid:
contact[field] = convert(click.prompt(label))
elif isinstance(valid, (tuple, list)):
valid_keys = [unicode(val) for val, _ in valid]
value = None
gandi.echo(label)
while value not in valid_keys:
for key, val in valid:
gandi.echo('%s- %s' % (key, val))
value = click.prompt('')
contact[field] = convert(value)
@cli.group(name='contact')
@pass_gandi
def contact(gandi):
"""Commands related to contacts."""
@contact.command()
@pass_gandi
def create(gandi):
""" Create a new contact.
"""
contact = {}
for field, label, checks in FIELDS:
ask_field(gandi, contact, field, label, checks)
default_pwd = randomstring(16)
contact['password'] = click.prompt('Please enter your password',
hide_input=True,
confirmation_prompt=True,
default=default_pwd)
result = True
while result:
result = gandi.contact.create_dry_run(contact)
# display errors
for err in result:
gandi.echo(err['reason'])
field = err['field']
if field not in FIELDS_POSITION:
return
desc = FIELDS[FIELDS_POSITION.get(field)]
ask_field(gandi, contact, *desc)
result = gandi.contact.create(contact)
handle = result['handle']
gandi.echo('Please activate you public api access from gandi website, and '
'get the apikey.')
gandi.echo('Your handle is %s, and the password is the one you defined.' %
handle)
# open new browser window
webbrowser.open('https://www.gandi.net/admin/api_key')
# just to avoid missing the next question in webbrowser stderr
time.sleep(1)
# get the apikey from shell
apikey = None
while not apikey:
apikey = click.prompt('What is your production apikey')
caller = gandi.get('api.key')
# save apikey in the conf if none defined else display an help on how to
# use it
if caller:
gandi.echo('You already have an apikey defined, if you want to use the'
' newly created contact, use the env var : ')
gandi.echo('export API_KEY=%s' % apikey)
else:
gandi.echo('Will save your apikey into the config file.')
gandi.configure(True, 'api.key', apikey)
return handle
gandi.cli-1.2/gandi/cli/commands/certificate.py 0000644 0001750 0001750 00000044026 13164644514 022366 0 ustar sayoun sayoun 0000000 0000000 """ Certificate namespace commands. """
import os
import click
import requests
# define basestring for python3
try:
basestring
except NameError:
basestring = (str, bytes)
type_list = list
from gandi.cli.core.cli import cli
from gandi.cli.core.utils import output_cert, output_cert_oper, display_rows
from gandi.cli.core.params import (pass_gandi, IntChoice,
CERTIFICATE_PACKAGE, CERTIFICATE_DCV_METHOD,
CERTIFICATE_PACKAGE_TYPE,
CERTIFICATE_PACKAGE_MAX,
CERTIFICATE_PACKAGE_WARRANTY)
@cli.group(name='certificate')
@pass_gandi
def certificate(gandi):
"""Commands related to certificates."""
@certificate.command()
@pass_gandi
def packages(gandi):
""" List certificate packages.
/!\\ deprecated call.
"""
gandi.echo('/!\ "gandi certificate packages" is deprecated.')
gandi.echo('Please use "gandi certificate plans".')
return _plans(gandi, with_name=True)
@certificate.command()
@pass_gandi
def plans(gandi):
""" List certificate plans. """
return _plans(gandi)
def package_desc(gandi, package):
if isinstance(package, basestring):
package = gandi.certificate.package_get(package)
if not package:
return ''
type_ = package['category']['name']
if package['wildcard']:
desc = '%s wildcard' % type_
elif package['max_domains'] > 1:
desc = '%s multi domain' % type_
else:
desc = '%s single domain' % type_
return ' '.join([word.capitalize() for word in desc.split(' ')])
def _plans(gandi, with_name=False):
packages = gandi.certificate.package_list()
def keyfunc(item):
return (item['category']['id'],
item['max_domains'],
item['warranty'],
item['name'])
packages.sort(key=keyfunc)
labels = ['Description', 'Max altnames', 'Type']
if with_name:
labels.insert(1, 'Name')
else:
labels.append('Warranty')
ret = [labels]
for package in packages:
params = package['name'].split('_')
cat = package['name'].split('_')[1]
warranty = str(int(package['name'].split('_')[3]) * 1000)
if len(warranty) > 3:
warranty = type_list(warranty)
warranty.insert(len(warranty) - 3, ',')
warranty = ''.join(warranty)
desc = package_desc(gandi, package)
line = [desc, str(package['max_domains']), cat]
if with_name:
line.insert(1, package['name'])
else:
line.append(warranty)
ret.append(line)
display_rows(gandi, ret)
return ret
@certificate.command()
@click.option('--id', help='Display ids.', is_flag=True)
@click.option('--altnames', help='Display altnames.', is_flag=True)
@click.option('--csr', help='Display CSR.', is_flag=True)
@click.option('--cert', help='Display CRT.', is_flag=True)
@click.option('--all-status', is_flag=True,
help='Filter the certificate without regard to its status.')
@click.option('--status', help='Display status.', is_flag=True)
@click.option('--dates', help='Display dates.', is_flag=True)
@click.option('--limit', help='Limit number of results.', default=100,
show_default=True)
@pass_gandi
def list(gandi, id, altnames, csr, cert, all_status, status, dates, limit):
""" List certificates. """
options = {'items_per_page': limit}
if not all_status:
options['status'] = ['valid', 'pending']
output_keys = ['cn', 'plan']
if id:
output_keys.append('id')
if status:
output_keys.append('status')
if dates:
output_keys.extend(['date_created', 'date_end'])
if altnames:
output_keys.append('altnames')
if csr:
output_keys.append('csr')
if cert:
output_keys.append('cert')
result = gandi.certificate.list(options)
for num, cert in enumerate(result):
if num:
gandi.separator_line()
cert['plan'] = package_desc(gandi, cert['package'])
output_cert(gandi, cert, output_keys)
return result
@certificate.command()
@click.argument('resource', nargs=-1, required=True)
@click.option('--id', help='Display ids.', is_flag=True)
@click.option('--altnames', help='Display altnames.', is_flag=True)
@click.option('--csr', help='Display CSR.', is_flag=True)
@click.option('--cert', help='Display CRT.', is_flag=True)
@click.option('--all-status', help='Show all certificates.', is_flag=True)
@pass_gandi
def info(gandi, resource, id, altnames, csr, cert, all_status):
""" Display information about a certificate.
Resource can be a CN or an ID
"""
output_keys = ['cn', 'date_created', 'date_end', 'plan', 'status']
if id:
output_keys.append('id')
if altnames:
output_keys.append('altnames')
if csr:
output_keys.append('csr')
if cert:
output_keys.append('cert')
ids = []
for res in resource:
ids.extend(gandi.certificate.usable_ids(res))
result = []
for num, id_ in enumerate(set(ids)):
cert = gandi.certificate.info(id_)
if not all_status and cert['status'] not in ['valid', 'pending']:
continue
if num:
gandi.separator_line()
cert['plan'] = package_desc(gandi, cert['package'])
output_cert(gandi, cert, output_keys)
result.append(cert)
return result
@certificate.command()
@click.argument('resource', nargs=-1, required=True)
@click.option('-o', '--output', help='The file to write the cert.')
@click.option('--force', '-f', is_flag=True,
help='Overwrite the crt file if it exists.')
@click.option('-i', '--intermediate', is_flag=True,
help='Retrieve gandi intermediate certs.')
@pass_gandi
def export(gandi, resource, output, force, intermediate):
""" Write the certificate to