Init: mediaserver

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

View File

@@ -0,0 +1,172 @@
===========================================
NetApp StorageGRID Collection Release Notes
===========================================
.. contents:: Topics
v21.11.1
========
Bugfixes
--------
- na_sg_org_container - fix versioning not enabled on initial bucket creation.
v21.11.0
========
Minor Changes
-------------
- na_sg_org_container - supports versioning configuration for S3 buckets available in StorageGRID 11.6+.
New Modules
-----------
- netapp.storagegrid.na_sg_grid_client_certificate - Manage Client Certificates on StorageGRID.
v21.10.0
========
Minor Changes
-------------
- na_sg_grid_gateway - supports specifying HA Groups by name or UUID.
Bugfixes
--------
- na_sg_org_group - fixed behaviour where update to ``s3_policy`` is ignored if ``management_policy`` is set.
New Modules
-----------
- netapp.storagegrid.na_sg_grid_ha_group - Manage high availability (HA) group configuration on StorageGRID.
- netapp.storagegrid.na_sg_grid_traffic_classes - Manage Traffic Classification Policy configuration on StorageGRID.
v21.9.0
=======
Minor Changes
-------------
- PR2 - allow usage of Ansible module group defaults - for Ansible 2.12+.
- na_sg_grid_gateway - supports load balancer endpoint binding available in StorageGRID 11.5+.
- na_sg_org_container - supports creation of S3 Object Lock buckets available in StorageGRID 11.5+.
Bugfixes
--------
- na_sg_grid_account - minor documentation fix.
- na_sg_grid_gateway - existing endpoints matched by ``name`` and ``port``.
v21.8.0
=======
Minor Changes
-------------
- PR2 - allow usage of Ansible module group defaults - for Ansible 2.12+.
v21.7.0
=======
Minor Changes
-------------
- Updated documentation - added RETURN block for each module
New Modules
-----------
- netapp.storagegrid.na_sg_grid_gateway - Manage Load balancer (gateway) endpoints on StorageGRID.
v21.6.0
=======
Minor Changes
-------------
- na_sg_org_container - supports deletion of buckets when ``state`` is set to ``absent``.
Bugfixes
--------
- na_sg_org_container - fix issue with applying compliance settings on buckets.
New Modules
-----------
- netapp.storagegrid.na_sg_grid_certificate - Manage the Storage API and Grid Management certificates on StorageGRID.
- netapp.storagegrid.na_sg_grid_identity_federation - NetApp StorageGRID manage Grid identity federation.
- netapp.storagegrid.na_sg_org_identity_federation - NetApp StorageGRID manage Tenant identity federation.
v20.11.0
========
Minor Changes
-------------
- na_sg_grid_account - New option ``root_access_account`` for granting initial root access permissions for the tenant to an existing federated group
New Modules
-----------
- netapp.storagegrid.na_sg_grid_info - NetApp StorageGRID Grid information gatherer
- netapp.storagegrid.na_sg_org_info - NetApp StorageGRID Org information gatherer
v20.10.0
========
Minor Changes
-------------
- na_sg_grid_account - new option ``update_password`` for managing Tenant Account root password changes.
- na_sg_grid_user - new option ``password`` and ``update_password`` for setting or updating Grid Admin User passwords.
- na_sg_org_user - new option ``password`` and ``update_password`` for setting or updating Tenant User passwords.
Breaking Changes / Porting Guide
--------------------------------
- This version introduces a breaking change.
All modules have been renamed from ``nac_sg_*`` to ``na_sg_*``.
Playbooks and Roles must be updated to match.
Bugfixes
--------
- na_sg_grid_account - added ``no_log`` flag to password fields.
- na_sg_grid_account - fixed documentation issue.
- na_sg_grid_group - fixed group name parsing.
- na_sg_org_group - fixed group name parsing.
v20.6.1
=======
Minor Changes
-------------
- Fixed documentation issue in README.md
Bugfixes
--------
- nac_sg_org_container - fixed documentation issue.
v20.6.0
=======
New Modules
-----------
- netapp.storagegrid.nac_sg_grid_account - NetApp StorageGRID Manage Tenant account.
- netapp.storagegrid.nac_sg_grid_dns - NetApp StorageGRID Manage Grid DNS servers.
- netapp.storagegrid.nac_sg_grid_group - NetApp StorageGRID Manage Grid admin group.
- netapp.storagegrid.nac_sg_grid_ntp - NetApp StorageGRID Manage Grid NTP servers.
- netapp.storagegrid.nac_sg_grid_regions - NetApp StorageGRID Manage Grid Regions.
- netapp.storagegrid.nac_sg_grid_user - NetApp StorageGRID Manage Grid admin user.
- netapp.storagegrid.nac_sg_org_container - NetApp StorageGRID Manage S3 bucket.
- netapp.storagegrid.nac_sg_org_group - NetApp StorageGRID Manage Tenant group.
- netapp.storagegrid.nac_sg_org_user - NetApp StorageGRID Manage Tenant user.
- netapp.storagegrid.nac_sg_org_user_s3_key - NetApp StorageGRID Manage S3 key.

View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -0,0 +1,572 @@
{
"files": [
{
"name": ".",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "requirements.txt",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "7cc4959877dbe6b6c63a8eb1bfe3bfb545fa8fe5b28b1b2c13e4a7c1c0d1c4d4",
"format": 1
},
{
"name": "plugins",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "plugins/doc_fragments",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "plugins/doc_fragments/netapp.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "e790e69d7116516a69110a233da28e21442e5fee8805b3b6f985854f27f26449",
"format": 1
},
{
"name": "plugins/module_utils",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "plugins/module_utils/netapp.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "9f7a966f44fca740564887beea1232f8e89bad232cde62d9de9a12428ec442c4",
"format": 1
},
{
"name": "plugins/module_utils/netapp_module.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "b1a4b77fd27fd5bf2810c0db1d4692093ae5b310992fb183e3817e2e3903891a",
"format": 1
},
{
"name": "plugins/modules",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_client_certificate.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "e0df90a4c30e50481afe5f508798187ecfbe31b1f5bb7e29e3d724d41203373f",
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_traffic_classes.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "e27c214bb0ff1e9022a575647d1c05d7e0f7bf5c2b35fa49b576f7beb64ce79c",
"format": 1
},
{
"name": "plugins/modules/na_sg_org_user_s3_key.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "d71cb0c2c53424b5ce28ead1b06f9c3b3106c709e4d2f0b74879b0eef2d668b6",
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_certificate.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "6b10f9a744d3f78024f3b82f082338aed13f31b5aa71efa86f4ba8ed8cce3088",
"format": 1
},
{
"name": "plugins/modules/na_sg_org_identity_federation.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "93bf629ba06db59b1c74bfda8422471a64d3804776207dc925b175ed3378ed9c",
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_account.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "a20270da62abd5c22c977081e1c4a11b9f96e39d56254711edb21f7c5cbe7f5b",
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_user.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "bc4be89bd9b2977d59fa51e1ef72b0522d2aad116e7dcb531ba0b214854d878b",
"format": 1
},
{
"name": "plugins/modules/na_sg_org_info.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "a088c55bad0b8b4a1c5e19024ed922ff4388ab92b3edcdcd91353b27cdf3d05a",
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_ha_group.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "48017730eff1e3a04c47c9187488eb915b18ef9293ca06b9dd49832eb2877856",
"format": 1
},
{
"name": "plugins/modules/na_sg_org_group.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "6b6e5bff028d0bd999f8b7a6998e59451c7bd4e5c0de63953ddd19ee13fafad5",
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_ntp.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "9e3659af27a8a5b26b5ff95eb407a1d0b393a74f22b8e9a66a762b75565dab69",
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_identity_federation.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "279f42f668b563bf22aa79be77fd3b90de7dab0a8bccbfe4773900acf820e64c",
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_group.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "0fe8a0e6ce5aefad01638e493dda3964019360e0c04cd6aa3b1e432d586b00d9",
"format": 1
},
{
"name": "plugins/modules/na_sg_org_user.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "5766c6c0c639b848c6ce3007533059d570f66f9c48f477e0e915b3f5c90a96ae",
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_info.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "324308668d15dbc88894b4aea9f9838093d3dd38e09307c94038cff4d289a614",
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_gateway.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "036c8706d7ff3ca3a5b970e0fa4d10ba4d33f7cfcfa160acf868d5bb9dc51eaf",
"format": 1
},
{
"name": "plugins/modules/na_sg_org_container.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "70b7ca86ed2853ca6fa99a5dc712770e342d1d5b95fea154a67d7c7db3df3c8c",
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_dns.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "ccc1e086f899279cce569309c183b358bce0620446fbc8602c94f3be1f5b793f",
"format": 1
},
{
"name": "plugins/modules/na_sg_grid_regions.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "7aa2472c25390e20db5437cb23e2824546854c18033b7e92e77eb627b92981a7",
"format": 1
},
{
"name": "tests",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "tests/unit",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "tests/unit/compat",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "tests/unit/compat/unittest.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "cba95d18c5b39c6f49714eacf1ac77452c2e32fa087c03cf01aacd19ae597b0f",
"format": 1
},
{
"name": "tests/unit/compat/builtins.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "ba13a350ade8ef804336f888d5883b8e54f8bddfb9d0fadc10277a8ca6540f4e",
"format": 1
},
{
"name": "tests/unit/compat/__init__.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"format": 1
},
{
"name": "tests/unit/compat/mock.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "d18988875cf9d824a5089d062935b2750d96e99e47925a2eb62c527d92c9cc13",
"format": 1
},
{
"name": "tests/unit/requirements.txt",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "68a61b1d58a722f4ffabaa28da01c9837c93a582ea41c1bfb1c1fd54ea2d8fab",
"format": 1
},
{
"name": "tests/unit/plugins",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "tests/unit/plugins/modules",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_dns.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "a96a6d0218e6e7764786af0bf8dc8fbb24c3b492d57a627a7cf827bb33b2c327",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_regions.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "870928ef29b01babed679fb5d207eb997ed724aa97135528f45cfda0314705c3",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_org_group.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "ff42467a461b8a69a2a2895242852444c89e0471bc0101ae10f99bbcf54528cb",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_info.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "8b713f3a28e2948c42996d06cc69f316ed2576999d2372ed354c474e0a52472e",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_org_user.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "c7fa1a8212a01dd81411129c7a31048d3e154c9729a0ac28a8016ace1fbd6e87",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_ntp.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "7299330b0ddfa005c4976c10b3c564b4c84c5f3cc620d566f9baa0ebe63460b1",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_group.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "15ad7caefc2c6fc3c9e473d176e032473f2065dfb249f3524263ec6129b61e6c",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_identity_federation.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "3943d740fc467010966238cce4d10aa484ea70d6e689704043cee7344e1916b5",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_gateway.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "c87e92555fb3aafaa82e197910b8fa61976ce358e220da2e0968469ea0df505c",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_client_certificate.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "1ab3f4d2b408d5943dfb334ed64729ba5b9a2974c78ef1c2cb48622289181e52",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_certificate.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "d046329c9ba078773c442a8bc47b00c76b49eac762f05f1b43789689056e295a",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_org_info.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "3d167cae9bf3aebf2b3b8b6b2736701a37e0c86ebae8ad89223deca373d4e4dd",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_account.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "eebfa82a09b7b3413d77749c1afc10a69cde0d90e940b0d454af807cebc476be",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_org_identity_federation.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "cda86d8e2632609873d7ccda496ca69e0884ba82712418e23148025f0f3a5482",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_traffic_classes.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "4ec3ad3f3716e69cd39a3dddbbffc1796d415b4a8a729f5d61289a3f39967b18",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_ha_group.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "a0782c6e1a6a9185cf4724da8d7e4363f6af13934efd4152779fcbe9aa05c2b7",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_org_container.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "9593869384f8f9f0f0de69e63f72a90de414e858dbfead681966e54b9ebd4b23",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_org_user_s3_key.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "d39947658ff5fdbc740275959f4fc82fcbdfd84553b771d78632d461e1638b4f",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_sg_grid_user.py",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "614c50a7ef9b7fe296025441e0350b78cb5e381f04d336f1cb49512d60404605",
"format": 1
},
{
"name": "meta",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "meta/runtime.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "fe82353b93dc1bedb4d4f8695b23d6a33537fd0401c406547d132d4497ff7ca2",
"format": 1
},
{
"name": "changelogs",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "changelogs/fragments",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "changelogs/fragments/github-66.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "49dfee10361876355f72737bd6f8d86ce6cea258bac589e20ec31125d6d4023a",
"format": 1
},
{
"name": "changelogs/fragments/github-10.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "82584dd77f4f48618a3ae519fdfe9347774691201e4fd98868d21cc782820ecd",
"format": 1
},
{
"name": "changelogs/fragments/20.10.0.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "2567ca0de5c5aa2b12772cf077003ea361b46b046e7fff165fbfb901dc86ff24",
"format": 1
},
{
"name": "changelogs/fragments/21.9.0.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "7b2062ed359ef484360534a91969fdb39e59f741cd5aa96b18e08de79bef81f1",
"format": 1
},
{
"name": "changelogs/fragments/20.7.0.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "e197a5a1f5b3a2e38dfd7456429e59a3a01a8f80e86e445ff1b7d5b5acf7dc3e",
"format": 1
},
{
"name": "changelogs/fragments/github-8.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "e99536aa20608eaee59a0f0bc586425750a4561b39b78fa50722be660f4333a3",
"format": 1
},
{
"name": "changelogs/fragments/21.11.1.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "c392f498059c8b2db2e7756553dde51d153bb7003f22c379641746e0bcf26188",
"format": 1
},
{
"name": "changelogs/fragments/20.6.1.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "54144fc957437d09f4efd29f0dbfd18cfe40e21d7196e1c0d9dca35acc644a70",
"format": 1
},
{
"name": "changelogs/fragments/21.6.0.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "1f058dcc3961d0dc37d26b6a3ea6aefd477378bb51f8bdbe0595c2bf1c145b73",
"format": 1
},
{
"name": "changelogs/fragments/21.11.0.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "b05d16dd6f1c82d0b43033d464d48f2c487cc90195fd1f0a8d79c4b8f97560a5",
"format": 1
},
{
"name": "changelogs/fragments/DEVOPS-4416.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "6742492ce83786ffcddc12818ef3771ef915123fbe3b0518a101044435701af3",
"format": 1
},
{
"name": "changelogs/config.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "3c330af70628d6a33029dadb8c4e4aac81eb5e82946651f804cff46bd0736cbe",
"format": 1
},
{
"name": "changelogs/changelog.yaml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "2d6b62b96b1a730bbb09d926e37a0dc4f1d3cf9f4218e8a2feb4a00c30c66171",
"format": 1
},
{
"name": "README.md",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "1fb8b745f8becd6ea2348808e1979a31486ab9357ec1197cb3136c2727d712b2",
"format": 1
},
{
"name": "COPYING",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "c53a65c2fd561c87eaabf1072ef5dcab8653042bc15308465f52413585eb6271",
"format": 1
},
{
"name": ".github",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": ".github/workflows",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": ".github/workflows/coverage.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "faf067634d432b31207f4ad48bdc037d2ec518a7df4377cb0533126fe9a50a21",
"format": 1
},
{
"name": ".github/workflows/main.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "07c19a69adcb2c9c482cadf8785c3bc0bf621ff161a592b48db9458e0673b1c5",
"format": 1
},
{
"name": "CHANGELOG.rst",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "131e7c4ca2ddb153d23af2174d262f4b0ae62079e892ecf36f062e929d43f729",
"format": 1
}
],
"format": 1
}

View File

@@ -0,0 +1,32 @@
{
"collection_info": {
"namespace": "netapp",
"name": "storagegrid",
"version": "21.11.1",
"authors": [
"NetApp Ansible Team <ng-ansibleteam@netapp.com>"
],
"readme": "README.md",
"tags": [
"storage",
"netapp",
"storagegrid"
],
"description": "NetApp StorageGRID Collection",
"license": [],
"license_file": "COPYING",
"dependencies": {},
"repository": "https://github.com/ansible-collections/netapp.storagegrid",
"documentation": null,
"homepage": "https://netapp.io/configuration-management-and-automation/",
"issues": null
},
"file_manifest_file": {
"name": "FILES.json",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "c9021a8a169b205830d9914712b6c039123456973f905ab635b85e37192f132c",
"format": 1
},
"format": 1
}

View File

@@ -0,0 +1,199 @@
![example workflow](https://github.com/ansible-collections/netapp.storagegrid/actions/workflows/main.yml/badge.svg)
[![codecov](https://codecov.io/gh/ansible-collections/netapp.storagegrid/branch/main/graph/badge.svg?token=weBYkksxSi)](https://codecov.io/gh/ansible-collections/netapp.storagegrid)
[![Discord](https://img.shields.io/discord/855068651522490400)](https://discord.gg/NetApp)
=============================================================
netapp.storagegrid
NetApp StorageGRID Collection
Copyright (c) 2020 NetApp, Inc. All rights reserved.
Specifications subject to change without notice.
=============================================================
# Installation
```bash
ansible-galaxy collection install netapp.storagegrid
```
To use this collection add the following to the top of your playbook.
```
collections:
- netapp.storagegrid
```
# Usage
Each of the StorageGRID modules require an `auth_token` parameter to be specified. This can be obtained by executing a `uri` task against the StorageGRID Authorization API endpoint and registering the output as the first item in a Playbook.
If you are performing a Tenant operation, ensure that the `accountId` parameter is also specified in the URI body and set to the Tenant Account ID. For example, `"accountId": "01234567890123456789"`
```yaml
- name: Get Grid Authorization token
uri:
url: "https://sgadmin.example.com/api/v3/authorize"
method: POST
body: {
"username": "root",
"password": "storagegrid123",
"cookie": false,
"csrfToken": false
}
body_format: json
validate_certs: false
register: auth
```
Subsequent tasks can leverage the registered auth token.
```yaml
- name: Create a StorageGRID Tenant Account
netapp.storagegrid.na_sg_grid_account:
api_url: "https://sgadmin.example.com"
auth_token: "{{ auth.json.data }}"
validate_certs: false
state: present
name: AnsibleTenant
protocol: s3
management: true
use_own_identity_source: true
allow_platform_services: true
password: "mytenantrootpassword"
quota_size: 10
```
# Versioning
[Releasing, Versioning and Deprecation](https://github.com/ansible-collections/netapp/issues/93)
# Need help
Join our [Discord](https://discord.gg/NetApp)
# Code of Conduct
This collection follows the [Ansible project's Code of Conduct](https://docs.ansible.com/ansible/devel/community/code_of_conduct.html).
# Release Notes
## 21.11.1
### Bug Fixes
- na_sg_org_container - fix versioning not enabled on initial bucket creation.
## 21.11.0
### Minor Changes
- na_sg_org_container - supports versioning configuration for S3 buckets available in StorageGRID 11.6+.
### New Modules
- na_sg_grid_client_certificate - Manage Client Certificates on StorageGRID.
## 21.10.0
### Minor Changes
- na_sg_grid_gateway - supports specifying HA Groups by name or UUID.
### Bug Fixes
- na_sg_org_group - fixed behaviour where update to ``s3_policy`` is ignored if ``management_policy`` is set.
### New Modules
- na_sg_grid_ha_group - Manage high availability (HA) group configuration on StorageGRID.
- na_sg_grid_traffic_classes - Manage Traffic Classification Policy configuration on StorageGRID.
## 21.9.0
### Minor Changes
- na_sg_grid_gateway - supports load balancer endpoint binding available in StorageGRID 11.5+.
- na_sg_org_container - supports creation of S3 Object Lock buckets available in StorageGRID 11.5+.
### Bug Fixes
- na_sg_grid_gateway - existing endpoints matched by ``name`` and ``port``.
- na_sg_grid_account - minor documentation fix.
## 21.8.0
### Minor Changes
- all modules - enable usage of Ansible module group defaults - for Ansible 2.12+.
## 21.7.0
### New Modules
- na_sg_grid_gateway: Manage Load balancer (gateway) endpoints
### Minor Changes
- Updated documentation - added RETURN block for each module
## 21.6.0
### New Modules
- na_sg_grid_certificate: Manage the Storage API and Grid Management certificates on StorageGRID.
- na_sg_grid_identity_federation: Manage Grid identity federation.
- na_sg_org_identity_federation: Manage Tenant identity federation.
### Minor Changes
- na_sg_org_container - supports deletion of buckets when `state` is set to `absent`.
### Bug Fixes
- na_sg_org_container - fix issue with applying compliance settings on buckets.
## 20.11.0
### New Modules
- na_sg_grid_info: Gather StorageGRID Grig subset information
- na_sg_org_info: Gather StorageGRID Org subset information
### Minor Changes
- na_sg_grid_account: new option `root_access_account` for granting initial root access permissions for the tenant to an existing federated group
## 20.10.0
### Breaking Changes
This version introduces a breaking change. All modules have been renamed from `nac_sg_*` to `na_sg_*`. Playbooks and Roles must be updated to match.
### Bug Fixes
- na_sg_grid_account: fixed documentation issue.
- na_sg_grid_account: added `no_log` flag to password fields
- na_sg_grid_group: fixed group name parsing
- na_sg_org_group: fixed group name parsing
### New Options
- na_sg_grid_account: new option `update_password` for managing Tenant Account root password changes
- na_sg_org_user: new option `password` and `update_password` for setting or updating Tenant User passwords
- na_sg_grid_user: new option `password` and `update_password` for setting or updating Grid Admin User passwords
## 20.6.1
### Minor Changes
- Fixed documentation issue in README.md
### Bug Fixes
- nac_sg_org_container: fixed documentation issue.
## 20.6.0
Initial release of NetApp StorageGRID Ansible modules
### New Modules
- nac_sg_grid_account: create/modify/delete Tenant account
- nac_sg_grid_dns: set Grid DNS servers
- nac_sg_grid_group: create/modify/delete Grid admin group
- nac_sg_grid_ntp: set Grid NTP servers
- nac_sg_grid_regions: set Grid Regions
- nac_sg_grid_user: create/modify/delete Grid admin user
- nac_sg_org_container: create S3 bucket
- nac_sg_org_group: create/modify/delete Tenant group
- nac_sg_org_user: create/modify/delete Tenant user
- nac_sg_org_user_s3_key: create/delete S3 key

View File

@@ -0,0 +1,171 @@
ancestor: null
releases:
20.10.0:
changes:
breaking_changes:
- 'This version introduces a breaking change.
All modules have been renamed from ``nac_sg_*`` to ``na_sg_*``.
Playbooks and Roles must be updated to match.'
bugfixes:
- na_sg_grid_account - added ``no_log`` flag to password fields.
- na_sg_grid_account - fixed documentation issue.
- na_sg_grid_group - fixed group name parsing.
- na_sg_org_group - fixed group name parsing.
minor_changes:
- na_sg_grid_account - new option ``update_password`` for managing Tenant Account
root password changes.
- na_sg_grid_user - new option ``password`` and ``update_password`` for setting
or updating Grid Admin User passwords.
- na_sg_org_user - new option ``password`` and ``update_password`` for setting
or updating Tenant User passwords.
fragments:
- 20.10.0.yaml
release_date: '2020-10-15'
20.11.0:
changes:
minor_changes:
- na_sg_grid_account - New option ``root_access_account`` for granting initial
root access permissions for the tenant to an existing federated group
fragments:
- github-66.yaml
modules:
- description: NetApp StorageGRID Grid information gatherer
name: na_sg_grid_info
namespace: ''
- description: NetApp StorageGRID Org information gatherer
name: na_sg_org_info
namespace: ''
release_date: '2020-11-18'
20.6.0:
modules:
- description: NetApp StorageGRID Manage Tenant account.
name: nac_sg_grid_account
namespace: ''
- description: NetApp StorageGRID Manage Grid DNS servers.
name: nac_sg_grid_dns
namespace: ''
- description: NetApp StorageGRID Manage Grid admin group.
name: nac_sg_grid_group
namespace: ''
- description: NetApp StorageGRID Manage Grid NTP servers.
name: nac_sg_grid_ntp
namespace: ''
- description: NetApp StorageGRID Manage Grid Regions.
name: nac_sg_grid_regions
namespace: ''
- description: NetApp StorageGRID Manage Grid admin user.
name: nac_sg_grid_user
namespace: ''
- description: NetApp StorageGRID Manage S3 bucket.
name: nac_sg_org_container
namespace: ''
- description: NetApp StorageGRID Manage Tenant group.
name: nac_sg_org_group
namespace: ''
- description: NetApp StorageGRID Manage Tenant user.
name: nac_sg_org_user
namespace: ''
- description: NetApp StorageGRID Manage S3 key.
name: nac_sg_org_user_s3_key
namespace: ''
release_date: '2020-06-09'
20.6.1:
changes:
bugfixes:
- nac_sg_org_container - fixed documentation issue.
minor_changes:
- Fixed documentation issue in README.md
fragments:
- 20.6.1.yaml
release_date: '2020-06-09'
21.10.0:
changes:
bugfixes:
- na_sg_org_group - fixed behaviour where update to ``s3_policy`` is ignored
if ``management_policy`` is set.
minor_changes:
- na_sg_grid_gateway - supports specifying HA Groups by name or UUID.
fragments:
- github-10.yaml
- github-8.yaml
modules:
- description: Manage high availability (HA) group configuration on StorageGRID.
name: na_sg_grid_ha_group
namespace: ''
- description: Manage Traffic Classification Policy configuration on StorageGRID.
name: na_sg_grid_traffic_classes
namespace: ''
release_date: '2022-03-17'
21.11.0:
changes:
minor_changes:
- na_sg_org_container - supports versioning configuration for S3 buckets available
in StorageGRID 11.6+.
fragments:
- 21.11.0.yaml
modules:
- description: Manage Client Certificates on StorageGRID.
name: na_sg_grid_client_certificate
namespace: ''
release_date: '2022-09-06'
21.11.1:
changes:
bugfixes:
- na_sg_org_container - fix versioning not enabled on initial bucket creation.
fragments:
- 21.11.1.yaml
release_date: '2022-09-23'
21.6.0:
changes:
bugfixes:
- na_sg_org_container - fix issue with applying compliance settings on buckets.
minor_changes:
- na_sg_org_container - supports deletion of buckets when ``state`` is set to
``absent``.
fragments:
- 21.6.0.yaml
modules:
- description: Manage the Storage API and Grid Management certificates on StorageGRID.
name: na_sg_grid_certificate
namespace: ''
- description: NetApp StorageGRID manage Grid identity federation.
name: na_sg_grid_identity_federation
namespace: ''
- description: NetApp StorageGRID manage Tenant identity federation.
name: na_sg_org_identity_federation
namespace: ''
release_date: '2021-06-16'
21.7.0:
changes:
minor_changes:
- Updated documentation - added RETURN block for each module
fragments:
- 20.7.0.yaml
modules:
- description: Manage Load balancer (gateway) endpoints on StorageGRID.
name: na_sg_grid_gateway
namespace: ''
release_date: '2021-10-05'
21.8.0:
changes:
minor_changes:
- PR2 - allow usage of Ansible module group defaults - for Ansible 2.12+.
fragments:
- DEVOPS-4416.yaml
release_date: '2021-11-11'
21.9.0:
changes:
bugfixes:
- na_sg_grid_account - minor documentation fix.
- na_sg_grid_gateway - existing endpoints matched by ``name`` and ``port``.
minor_changes:
- PR2 - allow usage of Ansible module group defaults - for Ansible 2.12+.
- na_sg_grid_gateway - supports load balancer endpoint binding available in
StorageGRID 11.5+.
- na_sg_org_container - supports creation of S3 Object Lock buckets available
in StorageGRID 11.5+.
fragments:
- 21.9.0.yaml
release_date: '2021-12-17'

View File

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

View File

@@ -0,0 +1,14 @@
breaking_changes:
- |
This version introduces a breaking change.
All modules have been renamed from ``nac_sg_*`` to ``na_sg_*``.
Playbooks and Roles must be updated to match.
minor_changes:
- na_sg_grid_account - new option ``update_password`` for managing Tenant Account root password changes.
- na_sg_org_user - new option ``password`` and ``update_password`` for setting or updating Tenant User passwords.
- na_sg_grid_user - new option ``password`` and ``update_password`` for setting or updating Grid Admin User passwords.
bugfixes:
- na_sg_grid_account - fixed documentation issue.
- na_sg_grid_account - added ``no_log`` flag to password fields.
- na_sg_grid_group - fixed group name parsing.
- na_sg_org_group - fixed group name parsing.

View File

@@ -0,0 +1,4 @@
minor_changes:
- Fixed documentation issue in README.md
bugfixes:
- nac_sg_org_container - fixed documentation issue.

View File

@@ -0,0 +1,2 @@
minor_changes:
- Updated documentation - added RETURN block for each module

View File

@@ -0,0 +1,2 @@
minor_changes:
- na_sg_org_container - supports versioning configuration for S3 buckets available in StorageGRID 11.6+.

View File

@@ -0,0 +1,2 @@
bugfixes:
- na_sg_org_container - fix versioning not enabled on initial bucket creation.

View File

@@ -0,0 +1,4 @@
minor_changes:
- na_sg_org_container - supports deletion of buckets when ``state`` is set to ``absent``.
bugfixes:
- na_sg_org_container - fix issue with applying compliance settings on buckets.

View File

@@ -0,0 +1,6 @@
minor_changes:
- na_sg_grid_gateway - supports load balancer endpoint binding available in StorageGRID 11.5+.
- na_sg_org_container - supports creation of S3 Object Lock buckets available in StorageGRID 11.5+.
bugfixes:
- na_sg_grid_gateway - existing endpoints matched by ``name`` and ``port``.
- na_sg_grid_account - minor documentation fix.

View File

@@ -0,0 +1,2 @@
minor_changes:
- PR2 - allow usage of Ansible module group defaults - for Ansible 2.12+.

View File

@@ -0,0 +1,2 @@
minor_changes:
- na_sg_grid_gateway - supports specifying HA Groups by name or UUID.

View File

@@ -0,0 +1,2 @@
minor_changes:
- na_sg_grid_account - New option ``root_access_account`` for granting initial root access permissions for the tenant to an existing federated group

View File

@@ -0,0 +1,2 @@
bugfixes:
- na_sg_org_group - fixed behaviour where update to ``s3_policy`` is ignored if ``management_policy`` is set.

View File

@@ -0,0 +1,23 @@
---
requires_ansible: ">=2.9.10"
action_groups:
netapp_storagegrid:
- na_sg_grid_account
- na_sg_grid_certificate
- na_sg_grid_client_certificate
- na_sg_grid_dns
- na_sg_grid_gateway
- na_sg_grid_group
- na_sg_grid_ha_group
- na_sg_grid_identity_federation
- na_sg_grid_info
- na_sg_grid_ntp
- na_sg_grid_regions
- na_sg_grid_traffic_classes
- na_sg_grid_user
- na_sg_org_container
- na_sg_org_group
- na_sg_org_identity_federation
- na_sg_org_info
- na_sg_org_user
- na_sg_org_user_s3_key

View File

@@ -0,0 +1,41 @@
# Copyright: (c) 2019, NetApp Ansible Team <ng-ansibleteam@netapp.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = r"""
options:
- See respective platform section for more details
requirements:
- See respective platform section for more details
notes:
- This is documentation for NetApp's StorageGRID modules.
"""
# Documentation fragment for StorageGRID
SG = """
options:
auth_token:
required: true
type: str
description:
- The authorization token for the API request
api_url:
required: true
type: str
description:
- The url to the StorageGRID Admin Node REST API.
validate_certs:
required: false
default: true
description:
- Should https certificates be validated?
type: bool
notes:
- The modules prefixed with C(na_sg) are built to manage NetApp StorageGRID.
"""

View File

@@ -0,0 +1,211 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# Copyright (c) 2020, NetApp Ansible Team <ng-ansibleteam@netapp.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
import mimetypes
import os
import random
from pprint import pformat
from ansible.module_utils import six
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
from ansible.module_utils.urls import open_url
from ansible.module_utils.api import basic_auth_argument_spec
from ansible.module_utils._text import to_native
COLLECTION_VERSION = "21.11.1"
try:
import requests
HAS_REQUESTS = True
except ImportError:
HAS_REQUESTS = False
import ssl
try:
from urlparse import urlparse, urlunparse
except ImportError:
from urllib.parse import urlparse, urlunparse
POW2_BYTE_MAP = dict(
# Here, 1 kb = 1024
bytes=1,
b=1,
kb=1024,
mb=1024**2,
gb=1024**3,
tb=1024**4,
pb=1024**5,
eb=1024**6,
zb=1024**7,
yb=1024**8,
)
def na_storagegrid_host_argument_spec():
return dict(
api_url=dict(required=True, type="str"),
validate_certs=dict(required=False, type="bool", default=True),
auth_token=dict(required=True, type="str", no_log=True),
)
class SGRestAPI(object):
def __init__(self, module, timeout=60):
self.module = module
self.auth_token = self.module.params["auth_token"]
self.api_url = self.module.params["api_url"]
self.verify = self.module.params["validate_certs"]
self.timeout = timeout
self.check_required_library()
self.sg_version = dict(major=-1, minor=-1, full="", valid=False)
def check_required_library(self):
if not HAS_REQUESTS:
self.module.fail_json(msg=missing_required_lib("requests"))
def send_request(self, method, api, params, json=None):
"""send http request and process reponse, including error conditions"""
url = "%s/%s" % (self.api_url, api)
status_code = None
content = None
json_dict = None
json_error = None
error_details = None
headers = {
"Content-type": "application/json",
"Authorization": self.auth_token,
"Cache-Control": "no-cache",
}
def get_json(response):
"""extract json, and error message if present"""
try:
json = response.json()
except ValueError:
return None, None
success_code = [200, 201, 202, 204]
if response.status_code not in success_code:
error = json.get("message")
else:
error = None
return json, error
try:
response = requests.request(
method,
url,
headers=headers,
timeout=self.timeout,
json=json,
verify=self.verify,
params=params,
)
status_code = response.status_code
# If the response was successful, no Exception will be raised
json_dict, json_error = get_json(response)
except requests.exceptions.HTTPError as err:
__, json_error = get_json(response)
if json_error is None:
error_details = str(err)
except requests.exceptions.ConnectionError as err:
error_details = str(err)
except Exception as err:
error_details = str(err)
if json_error is not None:
error_details = json_error
return json_dict, error_details
# If an error was reported in the json payload, it is handled below
def get(self, api, params=None):
method = "GET"
return self.send_request(method, api, params)
def post(self, api, data, params=None):
method = "POST"
return self.send_request(method, api, params, json=data)
def patch(self, api, data, params=None):
method = "PATCH"
return self.send_request(method, api, params, json=data)
def put(self, api, data, params=None):
method = "PUT"
return self.send_request(method, api, params, json=data)
def delete(self, api, data, params=None):
method = "DELETE"
return self.send_request(method, api, params, json=data)
def get_sg_product_version(self, api_root="grid"):
method = "GET"
api = "api/v3/%s/config/product-version" % api_root
message, error = self.send_request(method, api, params={})
if error:
self.module.fail_json(msg=error)
self.set_version(message)
def set_version(self, message):
try:
product_version = message.get("data", "not found").get("productVersion", "not_found")
except AttributeError:
self.sg_version["valid"] = False
return
self.sg_version["major"], self.sg_version["minor"] = list(map(int, product_version.split(".")[0:2]))
self.sg_version["full"] = product_version
self.sg_version["valid"] = True
def get_sg_version(self):
if self.sg_version["valid"]:
return self.sg_version["major"], self.sg_version["minor"]
return -1, -1
def meets_sg_minimum_version(self, minimum_major, minimum_minor):
return self.get_sg_version() >= (minimum_major, minimum_minor)
def requires_sg_version(self, module_or_option, version):
return "%s requires StorageGRID %s or later." % (module_or_option, version)
def fail_if_not_sg_minimum_version(self, module_or_option, minimum_major, minimum_minor):
version = self.get_sg_version()
if version < (minimum_major, minimum_minor):
msg = "Error: " + self.requires_sg_version(module_or_option, "%d.%d" % (minimum_major, minimum_minor))
msg += " Found: %s.%s." % version
self.module.fail_json(msg=msg)

View File

@@ -0,0 +1,237 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# Copyright (c) 2018, Laurent Nicolas <laurentn@netapp.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
""" Support class for NetApp ansible modules """
from __future__ import absolute_import, division, print_function
from copy import deepcopy
__metaclass__ = type
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
def cmp(obj1, obj2):
"""
Python 3 does not have a cmp function, this will do the cmp.
:param obj1: first object to check
:param obj2: second object to check
:return:
"""
# convert to lower case for string comparison.
if obj1 is None:
return -1
if isinstance(obj1, str) and isinstance(obj2, str):
obj1 = obj1.lower()
obj2 = obj2.lower()
# if list has string element, convert string to lower case.
if isinstance(obj1, list) and isinstance(obj2, list):
obj1 = [x.lower() if isinstance(x, str) else x for x in obj1]
obj2 = [x.lower() if isinstance(x, str) else x for x in obj2]
obj1.sort()
obj2.sort()
return (obj1 > obj2) - (obj1 < obj2)
class NetAppModule(object):
"""
Common class for NetApp modules
set of support functions to derive actions based
on the current state of the system, and a desired state
"""
def __init__(self):
self.log = list()
self.changed = False
self.parameters = {"name": "not initialized"}
def set_parameters(self, ansible_params):
self.parameters = dict()
for param in ansible_params:
if ansible_params[param] is not None:
self.parameters[param] = ansible_params[param]
return self.parameters
def get_cd_action(self, current, desired):
""" takes a desired state and a current state, and return an action:
create, delete, None
eg:
is_present = 'absent'
some_object = self.get_object(source)
if some_object is not None:
is_present = 'present'
action = cd_action(current=is_present, desired = self.desired.state())
"""
if "state" in desired:
desired_state = desired["state"]
else:
desired_state = "present"
if current is None and desired_state == "absent":
return None
if current is not None and desired_state == "present":
return None
# change in state
self.changed = True
if current is not None:
return "delete"
return "create"
def compare_and_update_values(self, current, desired, keys_to_compare):
updated_values = dict()
is_changed = False
for key in keys_to_compare:
if key in current:
if key in desired and desired[key] is not None:
if current[key] != desired[key]:
updated_values[key] = desired[key]
is_changed = True
else:
updated_values[key] = current[key]
else:
updated_values[key] = current[key]
return updated_values, is_changed
@staticmethod
def check_keys(current, desired):
''' TODO: raise an error if keys do not match
with the exception of:
new_name, state in desired
'''
def is_rename_action(self, source, target):
""" takes a source and target object, and returns True
if a rename is required
eg:
source = self.get_object(source_name)
target = self.get_object(target_name)
action = is_rename_action(source, target)
:return: None for error, True for rename action, False otherwise
"""
if source is None and target is None:
# error, do nothing
# cannot rename an non existent resource
# alternatively we could create B
return None
if source is not None and target is not None:
# error, do nothing
# idempotency (or) new_name_is_already_in_use
# alternatively we could delete B and rename A to B
return False
if source is None and target is not None:
# do nothing, maybe the rename was already done
return False
# source is not None and target is None:
# rename is in order
self.changed = True
return True
@staticmethod
def compare_lists(current, desired, get_list_diff):
''' compares two lists and return a list of elements that are either the desired elements or elements that are
modified from the current state depending on the get_list_diff flag
:param: current: current item attribute in ONTAP
:param: desired: attributes from playbook
:param: get_list_diff: specifies whether to have a diff of desired list w.r.t current list for an attribute
:return: list of attributes to be modified
:rtype: list
'''
current_copy = deepcopy(current)
desired_copy = deepcopy(desired)
# get what in desired and not in current
desired_diff_list = list()
for item in desired:
if item in current_copy:
current_copy.remove(item)
else:
desired_diff_list.append(item)
# get what in current but not in desired
current_diff_list = []
for item in current:
if item in desired_copy:
desired_copy.remove(item)
else:
current_diff_list.append(item)
if desired_diff_list or current_diff_list:
# there are changes
if get_list_diff:
return desired_diff_list
else:
return desired
else:
return None
def get_modified_attributes(self, current, desired, get_list_diff=False):
''' takes two dicts of attributes and return a dict of attributes that are
not in the current state
It is expected that all attributes of interest are listed in current and
desired.
:param: current: current attributes on StorageGRID
:param: desired: attributes from playbook
:param: get_list_diff: specifies whether to have a diff of desired list w.r.t current list for an attribute
:return: dict of attributes to be modified
:rtype: dict
NOTE: depending on the attribute, the caller may need to do a modify or a
different operation (eg move volume if the modified attribute is an
aggregate name)
'''
# if the object does not exist, we can't modify it
modified = {}
if current is None:
return modified
# error out if keys do not match
self.check_keys(current, desired)
# collect changed attributes
for key, value in current.items():
if key in desired and desired[key] is not None:
if isinstance(value, list):
modified_list = self.compare_lists(value, desired[key], get_list_diff) # get modified list from current and desired
if modified_list is not None:
modified[key] = modified_list
elif isinstance(value, dict):
modified_dict = self.get_modified_attributes(value, desired[key])
if modified_dict:
modified[key] = modified_dict
else:
try:
result = cmp(value, desired[key])
except TypeError as exc:
raise TypeError("%s, key: %s, value: %s, desired: %s" % (repr(exc), key, repr(value), repr(desired[key])))
else:
if result != 0:
modified[key] = desired[key]
if modified:
self.changed = True
return modified

View File

@@ -0,0 +1,458 @@
#!/usr/bin/python
# (c) 2020, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Accounts"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = """
module: na_sg_grid_account
short_description: NetApp StorageGRID manage accounts.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '20.6.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Create, Update, Delete Tenant Accounts on NetApp StorageGRID.
options:
state:
description:
- Whether the specified account should exist or not.
- Required for all operations.
type: str
choices: ['present', 'absent']
default: present
name:
description:
- Name of the tenant.
- Required for create or modify operation.
type: str
account_id:
description:
- Account Id of the tenant.
- May be used for modify or delete operation.
type: str
protocol:
description:
- Object Storage protocol used by the tenancy.
- Required for create operation.
type: str
choices: ['s3', 'swift']
management:
description:
- Whether the tenant can login to the StorageGRID tenant portal.
type: bool
default: true
use_own_identity_source:
description:
- Whether the tenant account should configure its own identity source.
type: bool
allow_platform_services:
description:
- Allows tenant to use platform services features such as CloudMirror.
type: bool
root_access_group:
description:
- Existing federated group to have initial Root Access permissions for the tenant.
- Must begin with C(federated-group/)
type: str
version_added: 20.11.0
quota_size:
description:
- Quota to apply to the tenant specified in I(quota_size_unit).
- If you intend to have no limits, assign C(0).
type: int
default: 0
quota_size_unit:
description:
- The unit used to interpret the size parameter.
choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
type: str
default: 'gb'
password:
description:
- Root password for tenant account.
- Requires root privilege.
type: str
update_password:
description:
- Choose when to update the password.
- When set to C(always), the password will always be updated.
- When set to C(on_create) the password will only be set upon a new user creation.
default: on_create
choices:
- on_create
- always
type: str
"""
EXAMPLES = """
- name: create a tenant account
netapp.storagegrid.na_sg_grid_account:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
name: storagegrid-tenant-1
protocol: s3
management: true
use_own_identity_source: false
allow_platform_services: false
password: "tenant-password"
quota_size: 0
- name: update a tenant account
netapp.storagegrid.na_sg_grid_account:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
name: storagegrid-tenant-1
protocol: s3
management: true
use_own_identity_source: false
allow_platform_services: true
password: "tenant-password"
quota_size: 10240
- name: delete a tenant account
netapp.storagegrid.na_sg_grid_account:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: absent
name: storagegrid-tenant-1
protocol: s3
"""
RETURN = """
resp:
description: Returns information about the StorageGRID tenant account.
returned: success
type: dict
sample: {
"name": "Example Account",
"capabilities": ["management", "s3"],
"policy": {
"useAccountIdentitySource": true,
"allowPlatformServices": false,
"quotaObjectBytes": 100000000000
},
"id": "12345678901234567890"
}
"""
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import (
NetAppModule,
)
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import (
SGRestAPI,
)
class SgGridAccount(object):
"""
Create, modify and delete StorageGRID Tenant Account
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
name=dict(required=False, type="str"),
account_id=dict(required=False, type="str"),
protocol=dict(required=False, choices=["s3", "swift"]),
management=dict(required=False, type="bool", default=True),
use_own_identity_source=dict(required=False, type="bool"),
allow_platform_services=dict(required=False, type="bool"),
root_access_group=dict(required=False, type="str"),
quota_size=dict(required=False, type="int", default=0),
quota_size_unit=dict(
default="gb",
choices=[
"bytes",
"b",
"kb",
"mb",
"gb",
"tb",
"pb",
"eb",
"zb",
"yb",
],
type="str",
),
password=dict(required=False, type="str", no_log=True),
update_password=dict(
default="on_create", choices=["on_create", "always"]
),
)
)
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
(
"state",
"present",
[
"name",
"protocol",
"use_own_identity_source",
"allow_platform_services",
],
)
],
supports_check_mode=True,
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = {}
self.data["name"] = self.parameters["name"]
self.data["capabilities"] = [self.parameters["protocol"]]
if self.parameters.get("password") is not None:
self.data["password"] = self.parameters["password"]
# Append "management" to the capability list only if parameter is True
if self.parameters.get("management"):
self.data["capabilities"].append("management")
self.data["policy"] = {}
if "use_own_identity_source" in self.parameters:
self.data["policy"]["useAccountIdentitySource"] = self.parameters[
"use_own_identity_source"
]
if "allow_platform_services" in self.parameters:
self.data["policy"]["allowPlatformServices"] = self.parameters[
"allow_platform_services"
]
if self.parameters.get("root_access_group") is not None:
self.data["grantRootAccessToGroup"] = self.parameters["root_access_group"]
if self.parameters["quota_size"] > 0:
self.parameters["quota_size"] = (
self.parameters["quota_size"]
* netapp_utils.POW2_BYTE_MAP[
self.parameters["quota_size_unit"]
]
)
self.data["policy"]["quotaObjectBytes"] = self.parameters[
"quota_size"
]
elif self.parameters["quota_size"] == 0:
self.data["policy"]["quotaObjectBytes"] = None
self.pw_change = {}
if self.parameters.get("password") is not None:
self.pw_change["password"] = self.parameters["password"]
def get_tenant_account_id(self):
# Check if tenant account exists
# Return tenant account info if found, or None
api = "api/v3/grid/accounts?limit=350"
list_accounts, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
for account in list_accounts.get("data"):
if account["name"] == self.parameters["name"]:
return account["id"]
return None
def get_tenant_account(self, account_id):
api = "api/v3/grid/accounts/%s" % account_id
account, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
else:
return account["data"]
return None
def create_tenant_account(self):
api = "api/v3/grid/accounts"
response, error = self.rest_api.post(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def delete_tenant_account(self, account_id):
api = "api/v3/grid/accounts/" + account_id
self.data = None
response, error = self.rest_api.delete(api, self.data)
if error:
self.module.fail_json(msg=error)
def update_tenant_account(self, account_id):
api = "api/v3/grid/accounts/" + account_id
if "password" in self.data:
del self.data["password"]
if "grantRootAccessToGroup" in self.data:
del self.data["grantRootAccessToGroup"]
response, error = self.rest_api.put(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def set_tenant_root_password(self, account_id):
api = "api/v3/grid/accounts/%s/change-password" % account_id
response, error = self.rest_api.post(api, self.pw_change)
if error:
self.module.fail_json(msg=error["text"])
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
tenant_account = None
if self.parameters.get("account_id"):
tenant_account = self.get_tenant_account(
self.parameters["account_id"]
)
else:
tenant_account_id = self.get_tenant_account_id()
if tenant_account_id:
tenant_account = self.get_tenant_account(tenant_account_id)
cd_action = self.na_helper.get_cd_action(
tenant_account, self.parameters
)
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
update = False
capability_diff = [
i
for i in self.data["capabilities"]
+ tenant_account["capabilities"]
if i not in self.data["capabilities"]
or i not in tenant_account["capabilities"]
]
if self.parameters["quota_size"] > 0:
if (
tenant_account["policy"]["quotaObjectBytes"]
!= self.parameters["quota_size"]
):
update = True
elif (
self.parameters["quota_size"] == 0
and tenant_account["policy"]["quotaObjectBytes"] is not None
):
update = True
if (
"use_own_identity_source" in self.parameters
and tenant_account["policy"]["useAccountIdentitySource"]
!= self.parameters["use_own_identity_source"]
):
update = True
elif (
"allow_platform_services" in self.parameters
and tenant_account["policy"]["allowPlatformServices"]
!= self.parameters["allow_platform_services"]
):
update = True
elif capability_diff:
update = True
if update:
self.na_helper.changed = True
result_message = ""
resp_data = tenant_account
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == "delete":
self.delete_tenant_account(tenant_account["id"])
result_message = "Tenant Account deleted"
resp_data = None
elif cd_action == "create":
resp_data = self.create_tenant_account()
result_message = "Tenant Account created"
else:
resp_data = self.update_tenant_account(tenant_account["id"])
result_message = "Tenant Account updated"
# If a password has been set
if self.pw_change:
if self.module.check_mode:
pass
else:
# Only update the password if update_password is always
# On a create action, the password is set directly by the POST /grid/accounts method
if self.parameters["update_password"] == "always" and cd_action != "create":
self.set_tenant_root_password(tenant_account["id"])
self.na_helper.changed = True
results = [result_message, "Tenant Account root password updated"]
result_message = "; ".join(filter(None, results))
self.module.exit_json(
changed=self.na_helper.changed, msg=result_message, resp=resp_data
)
def main():
"""
Main function
"""
na_sg_grid_account = SgGridAccount()
na_sg_grid_account.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,226 @@
#!/usr/bin/python
# (c) 2021, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Certificates"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: na_sg_grid_certificate
short_description: Manage the Storage API and Grid Management certificates on StorageGRID.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '21.6.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Set and update the Storage API and Grid Management certificates on NetApp StorageGRID.
options:
state:
description:
- Whether the specified certificate should be set.
type: str
choices: ['present', 'absent']
default: present
type:
description:
- Which certificate to update.
type: str
choices: ['storage-api', 'management']
required: true
server_certificate:
description:
- X.509 server certificate in PEM-encoding.
type: str
ca_bundle:
description:
- Intermediate CA certificate bundle in concatenated PEM-encoding.
- Omit if there is no intermediate CA.
type: str
private_key:
description:
- Certificate private key in PEM-encoding.
- Required if I(server_certificate) is specified.
type: str
"""
EXAMPLES = """
- name: set storage API certificate
netapp.storagegrid.na_sg_grid_certificate:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
type: storage-api
server_certificate: |
-----BEGIN CERTIFICATE-----
MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGEwJB
BAMMHnNnYW4wMS5kZXYubWljcm9icmV3Lm5ldGFwcC5hdTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMvjm9I35lmKcC7ITVL8+QiZ/klvdkbfZCUQrfdy
71inP+XmPjs0rnkhICA9ItODteRcVlO+t7nDTfm7HgG0mJFkcJm0ffyEYrcx24qu
S7gXYQjRsJmrep1awoaCa20BMGuqK2WKI3IvZ7YiT22qkBqKJD+hIFffX6u3Jy+B
77pR6YcATtpMHW/AaOx+OX9l80dIRsRZKMDxYQ==
-----END CERTIFICATE-----
private_key: |
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDL45vSN+ZZinAu
L25W0+cz1Oi69AKkI7d9nbFics2ay5+7o+4rKqf3en2R4MSxiJvy+iDlOmATib5O
x8TN5pJ9AgMBAAECggEADDLM8tHXXUoUFihzv+BUwff8p8YcbHcXFcSes+xTd5li
po8lNsx/v2pQx4ByBkuaYLZGIEXOWS6gkp44xhIXgQKBgQD4Hq7862u5HLbmhrV3
vs8nC69b3QKBgQDacCD8d8JpwPbg8t2VjXM3UvdmgAaLUfU7O1DWV+W3jqzmDOoN
zWVgPbPNj0UmzvLDbgxLoxe77wjn2BHsAJVAfJ9VeQKBgGqFAegYO+wHR8lJUoa5
ZEe8Upy2oBtvND/0dnwO2ym2FGsBJN0Gr4NKdG5vkzLsthKkcwRm0ikwEUOUZQKE
K8J5yEVeo9K2v3wggtq8fYn6
-----END PRIVATE KEY-----
"""
RETURN = """
resp:
description: Returns information about the StorageGRID server certificates.
returned: success
type: dict
sample: {
"serverCertificateEncoded": "-----BEGIN CERTIFICATE-----MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGE...-----END CERTIFICATE-----",
"caBundleEncoded": "-----BEGIN CERTIFICATE-----MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELM...-----END CERTIFICATE-----"
}
"""
import json
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgGridCertificate:
"""
Update StorageGRID certificates
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
type=dict(required=True, type="str", choices=["storage-api", "management"]),
server_certificate=dict(required=False, type="str"),
ca_bundle=dict(required=False, type="str"),
private_key=dict(required=False, type="str", no_log=True),
)
)
parameter_map = {
"server_certificate": "serverCertificateEncoded",
"ca_bundle": "caBundleEncoded",
"private_key": "privateKeyEncoded",
}
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[("state", "present", ["server_certificate", "private_key"])],
supports_check_mode=True,
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = {}
if self.parameters["state"] == "present":
for k in parameter_map.keys():
if self.parameters.get(k) is not None:
self.data[parameter_map[k]] = self.parameters[k]
self.module.fail_json
def get_grid_certificate(self, cert_type):
api = "api/v3/grid/%s" % cert_type
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
return response["data"]
def update_grid_certificate(self, cert_type):
api = "api/v3/grid/%s/update" % cert_type
response, error = self.rest_api.post(api, self.data)
if error:
self.module.fail_json(msg=error)
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
cert_type = ""
cd_action = None
if self.parameters.get("type") == "storage-api":
cert_type = "storage-api-certificate"
elif self.parameters.get("type") == "management":
cert_type = "management-certificate"
cert_data = self.get_grid_certificate(cert_type)
if cert_data["serverCertificateEncoded"] is None and cert_data["caBundleEncoded"] is None:
cd_action = self.na_helper.get_cd_action(None, self.parameters)
else:
cd_action = self.na_helper.get_cd_action(cert_data, self.parameters)
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
update = False
if self.data.get("serverCertificateEncoded") is not None and self.data.get("privateKeyEncoded") is not None:
for item in ["serverCertificateEncoded", "caBundleEncoded"]:
if self.data.get(item) != cert_data.get(item):
update = True
if update:
self.na_helper.changed = True
result_message = ""
resp_data = cert_data
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == "delete":
self.update_grid_certificate(cert_type)
resp_data = self.get_grid_certificate(cert_type)
result_message = "Grid %s removed" % cert_type
else:
self.update_grid_certificate(cert_type)
resp_data = self.get_grid_certificate(cert_type)
result_message = "Grid %s updated" % cert_type
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_grid_certificate = SgGridCertificate()
na_sg_grid_certificate.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,265 @@
#!/usr/bin/python
# (c) 2022, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Certificates"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: na_sg_grid_client_certificate
short_description: Manage Client Certificates on StorageGRID
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '21.11.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Create, Update, Delete Client Certificates on NetApp StorageGRID.
options:
state:
description:
- Whether the specified certificate should exist.
type: str
choices: ['present', 'absent']
default: present
certificate_id:
description:
- ID of the client certificate.
type: str
display_name:
description:
- A display name for the client certificate configuration.
- This parameter can be modified if I(certificate_id) is also specified.
type: str
public_key:
description:
- X.509 client certificate in PEM-encoding.
type: str
allow_prometheus:
description:
- Whether the external monitoring tool can access Prometheus metrics.
type: bool
"""
EXAMPLES = """
- name: create client certificate
netapp.storagegrid.na_sg_grid_client_certificate:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
display_name: client-cert1
public_key: |
-----BEGIN CERTIFICATE-----
MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2..swCQYDVQQGEwJB
BAMMHnNnYW4wMS5kZXYubWljcm9icmV3Lm5ldGFwcC5hdTCC..IwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMvjm9I35lmKcC7ITVL8+QiZ..lvdkbfZCUQrfdy
71inP+XmPjs0rnkhICA9ItODteRcVlO+t7nDTfm7HgG0mJFk..m0ffyEYrcx24qu
S7gXYQjRsJmrep1awoaCa20BMGuqK2WKI3IvZ7YiT22qkBqK..+hIFffX6u3Jy+B
77pR6YcATtpMHW/AaOx+OX9l80dIRsRZKMDxYQ==
-----END CERTIFICATE-----
allow_prometheus: true
- name: rename client certificate
netapp.storagegrid.na_sg_grid_client_certificate:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
certificate_id: 00000000-0000-0000-0000-000000000000
display_name: client-cert1-rename
public_key: |
-----BEGIN CERTIFICATE-----
MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2..swCQYDVQQGEwJB
BAMMHnNnYW4wMS5kZXYubWljcm9icmV3Lm5ldGFwcC5hdTCC..IwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMvjm9I35lmKcC7ITVL8+QiZ..lvdkbfZCUQrfdy
71inP+XmPjs0rnkhICA9ItODteRcVlO+t7nDTfm7HgG0mJFk..m0ffyEYrcx24qu
S7gXYQjRsJmrep1awoaCa20BMGuqK2WKI3IvZ7YiT22qkBqK..+hIFffX6u3Jy+B
77pR6YcATtpMHW/AaOx+OX9l80dIRsRZKMDxYQ==
-----END CERTIFICATE-----
allow_prometheus: true
- name: delete client certificate
netapp.storagegrid.na_sg_grid_client_certificate:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: absent
display_name: client-cert1-rename
"""
RETURN = """
resp:
description: Returns information about the StorageGRID server certificates.
returned: success
type: dict
sample: {
"id": "abcABC_01234-0123456789abcABCabc0123456789==",
"displayName": "client-cert1",
"expiryDate": "2024-01-01T00:00:00.000Z",
"publicKey": "-----BEGIN CERTIFICATE-----MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGE...-----END CERTIFICATE-----",
"allowPrometheus": true
}
"""
import json
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgGridClientCertificate:
"""
Update StorageGRID client certificates
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
certificate_id=dict(required=False, type="str"),
display_name=dict(required=False, type="str"),
public_key=dict(required=False, type="str"),
allow_prometheus=dict(required=False, type="bool"),
)
)
parameter_map = {
"display_name": "displayName",
"public_key": "publicKey",
"allow_prometheus": "allowPrometheus",
}
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[("state", "present", ["display_name", "public_key"])],
required_one_of=[("display_name", "certificate_id")],
supports_check_mode=True,
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = {}
if self.parameters["state"] == "present":
for k in parameter_map.keys():
if self.parameters.get(k) is not None:
self.data[parameter_map[k]] = self.parameters[k]
self.module.fail_json
def get_grid_client_certificate_id(self):
# Check if certificate with name exists
# Return certificate ID if found, or None
api = "api/v3/grid/client-certificates"
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
for cert in response.get("data"):
if cert["displayName"] == self.parameters["display_name"]:
return cert["id"]
return None
def get_grid_client_certificate(self, cert_id):
api = "api/v3/grid/client-certificates/%s" % cert_id
account, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
else:
return account["data"]
return None
def create_grid_client_certificate(self):
api = "api/v3/grid/client-certificates"
response, error = self.rest_api.post(api, self.data)
if error:
self.module.fail_json(msg=error["text"])
return response["data"]
def delete_grid_client_certificate(self, cert_id):
api = "api/v3/grid/client-certificates/" + cert_id
self.data = None
response, error = self.rest_api.delete(api, self.data)
if error:
self.module.fail_json(msg=error)
def update_grid_client_certificate(self, cert_id):
api = "api/v3/grid/client-certificates/" + cert_id
response, error = self.rest_api.put(api, self.data)
if error:
self.module.fail_json(msg=error["text"])
return response["data"]
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
client_certificate = None
if self.parameters.get("certificate_id"):
client_certificate = self.get_grid_client_certificate(self.parameters["certificate_id"])
else:
client_cert_id = self.get_grid_client_certificate_id()
if client_cert_id:
client_certificate = self.get_grid_client_certificate(client_cert_id)
cd_action = self.na_helper.get_cd_action(client_certificate, self.parameters)
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
modify = self.na_helper.get_modified_attributes(client_certificate, self.data)
result_message = ""
resp_data = client_certificate
if self.na_helper.changed and not self.module.check_mode:
if cd_action == "delete":
self.delete_grid_client_certificate(client_certificate["id"])
result_message = "Client Certificate deleted"
elif cd_action == "create":
resp_data = self.create_grid_client_certificate()
result_message = "Client Certificate created"
elif modify:
resp_data = self.update_grid_client_certificate(client_certificate["id"])
result_message = "Client Certificate updated"
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_grid_certificate = SgGridClientCertificate()
na_sg_grid_certificate.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,163 @@
#!/usr/bin/python
# (c) 2020, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Grid DNS Servers"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = """
module: na_sg_grid_dns
short_description: NetApp StorageGRID manage external DNS servers for the grid.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '20.6.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Update NetApp StorageGRID DNS addresses.
options:
state:
description:
- Whether the specified DNS address should exist or not.
- Required for all operations.
type: str
choices: ['present']
default: present
dns_servers:
description:
- List of comma separated DNS Addresses to be updated or delete.
type: list
elements: str
required: true
"""
EXAMPLES = """
- name: update DNS servers on StorageGRID
netapp.storagegrid.na_sg_grid_dns:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
dns_servers: "x.x.x.x,xxx.xxx.xxx.xxx"
"""
RETURN = """
resp:
description: Returns information about the configured DNS servers.
returned: success
type: list
elements: str
sample: ["8.8.8.8", "8.8.4.4"]
"""
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgGridDns(object):
"""
Create, modify and delete DNS entries for StorageGRID
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present"], default="present"),
dns_servers=dict(required=True, type="list", elements="str"),
)
)
self.module = AnsibleModule(
argument_spec=self.argument_spec,
# required_if=[("state", "present", ["state", "name", "protocol"])],
supports_check_mode=True,
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = self.parameters["dns_servers"]
def get_grid_dns(self):
# Check if tenant account exists
# Return tenant account info if found, or None
api = "api/v3/grid/dns-servers"
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
return response["data"]
def update_grid_dns(self):
api = "api/v3/grid/dns-servers"
response, error = self.rest_api.put(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
grid_dns = self.get_grid_dns()
cd_action = self.na_helper.get_cd_action(grid_dns, self.parameters["dns_servers"])
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
update = False
dns_diff = [i for i in self.data + grid_dns if i not in self.data or i not in grid_dns]
if dns_diff:
update = True
if update:
self.na_helper.changed = True
result_message = ""
resp_data = grid_dns
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
resp_data = self.update_grid_dns()
result_message = "Grid DNS updated"
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_grid_dns = SgGridDns()
na_sg_grid_dns.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,532 @@
#!/usr/bin/python
# (c) 2021, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Load Balancer Endpoints"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: na_sg_grid_gateway
short_description: Manage Load balancer (gateway) endpoints on StorageGRID.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '21.7.0'
author: NetApp Ansible Team (@jkandati) <ng-sg-ansibleteam@netapp.com>
description:
- Create or Update Load Balancer Endpoints on StorageGRID.
- This module is idempotent if I(private_key) is not specified.
- The module will match an existing config based on I(port) and I(display_name).
- If multiple load balancer endpoints exist utilizing the same port and display name, use I(gateway_id) to select the intended endpoint.
options:
state:
description:
- Whether the specified load balancer endpoint should be configured.
type: str
choices: ['present', 'absent']
default: present
gateway_id:
description:
- ID of the load balancer endpoint.
type: str
version_added: '21.9.0'
display_name:
description:
- A display name for the configuration.
- This parameter can be modified if I(gateway_id) is also specified.
type: str
port:
description:
- The TCP port to serve traffic on.
- This parameter cannot be modified after the load balancer endpoint has been created.
type: int
required: true
secure:
description:
- Whether the load balancer endpoint serves HTTP or HTTPS traffic.
- This parameter cannot be modified after the load balancer endpoint has been created.
type: bool
default: true
enable_ipv4:
description:
- Indicates whether to listen for connections on IPv4.
type: bool
default: true
enable_ipv6:
description:
- Indicates whether to listen for connections on IPv6.
type: bool
default: true
binding_mode:
description:
- Binding mode to restrict accessibility of the load balancer endpoint.
- A binding mode other than I(global) requires StorageGRID 11.5 or greater.
type: str
choices: ['global', 'ha-groups', 'node-interfaces']
default: 'global'
version_added: '21.9.0'
ha_groups:
description:
- A set of StorageGRID HA Groups by name or UUID to bind the load balancer endpoint to.
- Option is ignored unless I(binding_mode=ha-groups).
type: list
elements: str
version_added: '21.9.0'
node_interfaces:
description:
- A set of StorageGRID node interfaces to bind the load balancer endpoint to.
type: list
elements: dict
suboptions:
node:
description:
- Name of the StorageGRID node.
type: str
interface:
description:
- The interface to bind to. eth0 corresponds to the Grid Network, eth1 to the Admin Network, and eth2 to the Client Network.
type: str
version_added: '21.9.0'
default_service_type:
description:
- The type of service to proxy through the load balancer.
type: str
choices: ['s3', 'swift']
default: 's3'
server_certificate:
description:
- X.509 server certificate in PEM-encoding.
- Omit if using default certificates.
type: str
required: false
private_key:
description:
- Certficate private key in PEM-encoding.
- Required if I(server_certificate) is not empty.
type: str
required: false
ca_bundle:
description:
- Intermediate CA certificate bundle in concatenated PEM-encoding.
- Omit when there is no intermediate CA.
type: str
required: false
"""
EXAMPLES = """
- name: Create and Upload Certificate to a Gateway Endpoint with global binding
netapp.storagegrid.na_sg_grid_gateway:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
displayName: "FabricPool Endpoint"
port: 10443
secure: True
enable_ipv4: True
enable_ipv6: True
default_service_type: "s3"
server_certificate: |
-----BEGIN CERTIFICATE-----
MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2..swCQYDVQQGEwJB
BAMMHnNnYW4wMS5kZXYubWljcm9icmV3Lm5ldGFwcC5hdTCC..IwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMvjm9I35lmKcC7ITVL8+QiZ..lvdkbfZCUQrfdy
71inP+XmPjs0rnkhICA9ItODteRcVlO+t7nDTfm7HgG0mJFk..m0ffyEYrcx24qu
S7gXYQjRsJmrep1awoaCa20BMGuqK2WKI3IvZ7YiT22qkBqK..+hIFffX6u3Jy+B
77pR6YcATtpMHW/AaOx+OX9l80dIRsRZKMDxYQ==
-----END CERTIFICATE-----
private_key: |
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIB..DL45vSN+ZZinAu
L25W0+cz1Oi69AKkI7d9nbFics2ay5+7o+4rKqf3en2R4MSx..vy+iDlOmATib5O
x8TN5pJ9AgMBAAECggEADDLM8tHXXUoUFihzv+BUwff8p8Yc..cXFcSes+xTd5li
po8lNsx/v2pQx4ByBkuaYLZGIEXOWS6gkp44xhIXgQKBgQD4..7862u5HLbmhrV3
vs8nC69b3QKBgQDacCD8d8JpwPbg8t2VjXM3UvdmgAaLUfU7..DWV+W3jqzmDOoN
zWVgPbPNj0UmzvLDbgxLoxe77wjn2BHsAJVAfJ9VeQKBgGqF..gYO+wHR8lJUoa5
ZEe8Upy2oBtvND/0dnwO2ym2FGsBJN0Gr4NKdG5vkzLsthKk..Rm0ikwEUOUZQKE
K8J5yEVeo9K2v3wggtq8fYn6
-----END PRIVATE KEY-----
validate_certs: false
- name: Create a HTTP Gateway Endpoint with HA Group Binding
netapp.storagegrid.na_sg_grid_gateway:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
displayName: "App Endpoint 1"
port: 10501
secure: false
enable_ipv4: True
enable_ipv6: True
default_service_type: "s3"
binding_mode: ha-groups
ha_groups: site1_ha_group
validate_certs: false
- name: Create a HTTP Gateway Endpoint with Node Interface Binding
netapp.storagegrid.na_sg_grid_gateway:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
displayName: "App Endpoint 2"
port: 10502
secure: false
enable_ipv4: True
enable_ipv6: True
default_service_type: "s3"
binding_mode: node-interfaces
node_interfaecs:
- node: SITE1_ADM1
interface: eth2
- node: SITE2_ADM1
interface: eth2
validate_certs: false
- name: Delete Gateway Endpoint
netapp.storagegrid.na_sg_grid_gateway:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
displayName: "App Endpoint 2"
port: 10502
default_service_type: "s3"
validate_certs: false
"""
RETURN = """
resp:
description: Returns information about the StorageGRID Load Balancer Endpoint.
returned: success
type: dict
sample: {
"id": "ffffffff-ffff-ffff-ffff-ffffffffffff",
"displayName": "ansibletest-secure",
"enableIPv4": True,
"enableIPv6": True,
"port": 10443,
"secure": True,
"accountId": "0",
"defaultServiceType": "s3",
"certSource": "plaintext",
"plaintextCertData": {
"serverCertificateEncoded": "-----BEGIN CERTIFICATE-----MIIC6DCCAdACCQC7l4WukhKD0zANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGE...-----END CERTIFICATE-----",
"caBundleEncoded": "-----BEGIN CERTIFICATE-----MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELM...-----END CERTIFICATE-----",
"metadata": {...}
}
}
"""
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgGridGateway:
"""
Create, modify and delete Gateway entries for StorageGRID
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
# Arguments for Creating Gateway Port
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
gateway_id=dict(required=False, type="str"),
display_name=dict(required=False, type="str"),
port=dict(required=True, type="int"),
secure=dict(required=False, type="bool", default=True),
enable_ipv4=dict(required=False, type="bool", default=True),
enable_ipv6=dict(required=False, type="bool", default=True),
binding_mode=dict(
required=False, type="str", choices=["global", "ha-groups", "node-interfaces"], default="global"
),
ha_groups=dict(required=False, type="list", elements="str"),
node_interfaces=dict(
required=False,
type="list",
elements="dict",
options=dict(
node=dict(required=False, type="str"),
interface=dict(required=False, type="str"),
),
),
# Arguments for setting Gateway Virtual Server
default_service_type=dict(required=False, type="str", choices=["s3", "swift"], default="s3"),
server_certificate=dict(required=False, type="str"),
ca_bundle=dict(required=False, type="str"),
private_key=dict(required=False, type="str", no_log=True),
)
)
parameter_map_gateway = {
"gateway_id": "id",
"display_name": "displayName",
"port": "port",
"secure": "secure",
"enable_ipv4": "enableIPv4",
"enable_ipv6": "enableIPv6",
}
parameter_map_server = {
"server_certificate": "serverCertificateEncoded",
"ca_bundle": "caBundleEncoded",
"private_key": "privateKeyEncoded",
}
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[("state", "present", ["display_name"])],
supports_check_mode=True,
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Get API version
self.rest_api.get_sg_product_version()
# Checking for the parameters passed and create new parameters list
# Parameters for creating a new gateway port configuration
self.data_gateway = {}
self.data_gateway["accountId"] = "0"
for k in parameter_map_gateway.keys():
if self.parameters.get(k) is not None:
self.data_gateway[parameter_map_gateway[k]] = self.parameters[k]
# Parameters for setting a gateway virtual server configuration for a gateway port
self.data_server = {}
self.data_server["defaultServiceType"] = self.parameters["default_service_type"]
if self.parameters["secure"]:
self.data_server["plaintextCertData"] = {}
self.data_server["certSource"] = "plaintext"
for k in parameter_map_server.keys():
if self.parameters.get(k) is not None:
self.data_server["plaintextCertData"][parameter_map_server[k]] = self.parameters[k]
if self.parameters["binding_mode"] != "global":
self.rest_api.fail_if_not_sg_minimum_version("non-global binding mode", 11, 5)
if self.parameters["binding_mode"] == "ha-groups":
self.data_gateway["pinTargets"] = {}
self.data_gateway["pinTargets"]["haGroups"] = self.build_ha_group_list()
self.data_gateway["pinTargets"]["nodeInterfaces"] = []
elif self.parameters["binding_mode"] == "node-interfaces":
self.data_gateway["pinTargets"] = {}
self.data_gateway["pinTargets"]["nodeInterfaces"] = self.build_node_interface_list()
self.data_gateway["pinTargets"]["haGroups"] = []
else:
self.data_gateway["pinTargets"] = {}
self.data_gateway["pinTargets"]["haGroups"] = []
self.data_gateway["pinTargets"]["nodeInterfaces"] = []
def build_ha_group_list(self):
ha_group_ids = []
api = "api/v3/private/ha-groups"
ha_groups, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
for param in self.parameters["ha_groups"]:
ha_group = next(
(item for item in ha_groups["data"] if (item["name"] == param or item["id"] == param)), None
)
if ha_group is not None:
ha_group_ids.append(ha_group["id"])
else:
self.module.fail_json(msg="HA Group '%s' is invalid" % param)
return ha_group_ids
def build_node_interface_list(self):
node_interfaces = []
api = "api/v3/grid/node-health"
nodes, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
for node_interface in self.parameters["node_interfaces"]:
node_dict = {}
node = next((item for item in nodes["data"] if item["name"] == node_interface["node"]), None)
if node is not None:
node_dict["nodeId"] = node["id"]
node_dict["interface"] = node_interface["interface"]
node_interfaces.append(node_dict)
else:
self.module.fail_json(msg="Node '%s' is invalid" % node_interface["node"])
return node_interfaces
def get_grid_gateway_config(self, gateway_id):
api = "api/v3/private/gateway-configs/%s" % gateway_id
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
gateway = response["data"]
gateway_config = self.get_grid_gateway_server_config(gateway["id"])
return gateway, gateway_config
def get_grid_gateway_server_config(self, gateway_id):
api = "api/v3/private/gateway-configs/%s/server-config" % gateway_id
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
return response["data"]
def get_grid_gateway_ports(self, target_port):
configured_ports = []
gateway = {}
gateway_config = {}
api = "api/v3/private/gateway-configs"
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
grid_gateway_ports = response["data"]
# Get only a list of used ports
configured_ports = [data["port"] for data in grid_gateway_ports]
for index, port in enumerate(configured_ports):
# if port already exists then get gateway ID and get the gateway port server configs
if target_port == port and grid_gateway_ports[index]["displayName"] == self.parameters["display_name"]:
gateway = grid_gateway_ports[index]
gateway_config = self.get_grid_gateway_server_config(gateway["id"])
break
return gateway, gateway_config
def create_grid_gateway(self):
api = "api/v3/private/gateway-configs"
response, error = self.rest_api.post(api, self.data_gateway)
if error:
self.module.fail_json(msg=error)
return response["data"]
def delete_grid_gateway(self, gateway_id):
api = "api/v3/private/gateway-configs/" + gateway_id
self.data = None
response, error = self.rest_api.delete(api, self.data)
if error:
self.module.fail_json(msg=error)
def update_grid_gateway(self, gateway_id):
api = "api/v3/private/gateway-configs/%s" % gateway_id
response, error = self.rest_api.put(api, self.data_gateway)
if error:
self.module.fail_json(msg=error)
return response["data"]
def update_grid_gateway_server(self, gateway_id):
api = "api/v3/private/gateway-configs/%s/server-config" % gateway_id
response, error = self.rest_api.put(api, self.data_server)
if error:
self.module.fail_json(msg=error)
return response["data"]
def apply(self):
gateway = None
gateway_config = None
update_gateway = False
update_gateway_server = False
if self.parameters.get("gateway_id"):
gateway, gateway_config = self.get_grid_gateway_config(self.parameters["gateway_id"])
else:
# Get list of all gateway port configurations
gateway, gateway_config = self.get_grid_gateway_ports(self.data_gateway["port"])
cd_action = self.na_helper.get_cd_action(gateway.get("id"), self.parameters)
if cd_action is None and self.parameters["state"] == "present":
update = False
if self.data_server.get("plaintextCertData"):
if self.data_server["plaintextCertData"].get("privateKeyEncoded") is not None:
update = True
self.module.warn("This module is not idempotent when private_key is present.")
if gateway_config.get("plaintextCertData"):
# If certificate private key supplied, update
if gateway_config["plaintextCertData"].get("metadata"):
# remove metadata because we can't compare that
del gateway_config["plaintextCertData"]["metadata"]
# compare current and desired state
# gateway config cannot be modified until StorageGRID 11.5
if self.rest_api.meets_sg_minimum_version(11, 5):
update_gateway = self.na_helper.get_modified_attributes(gateway, self.data_gateway)
update_gateway_server = self.na_helper.get_modified_attributes(gateway_config, self.data_server)
if update:
self.na_helper.changed = True
result_message = ""
resp_data = {}
if self.na_helper.changed and not self.module.check_mode:
if cd_action == "delete":
self.delete_grid_gateway(gateway["id"])
result_message = "Load Balancer Gateway Port Deleted"
elif cd_action == "create":
resp_data = self.create_grid_gateway()
gateway["id"] = resp_data["id"]
resp_data_server = self.update_grid_gateway_server(gateway["id"])
resp_data.update(resp_data_server)
result_message = "Load Balancer Gateway Port Created"
else:
resp_data = gateway
if update_gateway:
resp_data = self.update_grid_gateway(gateway["id"])
resp_data.update(gateway_config)
if update_gateway_server:
resp_data_server = self.update_grid_gateway_server(gateway["id"])
resp_data.update(resp_data_server)
result_message = "Load Balancer Gateway Port Updated"
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_grid_gateway = SgGridGateway()
na_sg_grid_gateway.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,341 @@
#!/usr/bin/python
# (c) 2020, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Grid Groups"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = """
module: na_sg_grid_group
short_description: NetApp StorageGRID manage groups.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '20.6.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Create, Update, Delete Administration Groups within NetApp StorageGRID.
options:
state:
description:
- Whether the specified group should exist or not.
type: str
choices: ['present', 'absent']
default: present
display_name:
description:
- Name of the group.
- Required for create operation
type: str
unique_name:
description:
- Unique Name for the group. Must begin with C(group/) or C(federated-group/)
- Required for create, modify or delete operation.
type: str
required: true
management_policy:
description:
- Management access controls granted to the group within the tenancy.
type: dict
suboptions:
alarm_acknowledgement:
description:
- Group members can have permission to acknowledge alarms.
required: false
type: bool
other_grid_configuration:
description:
- Need to investigate.
required: false
type: bool
grid_topology_page_configuration:
description:
- Users in this group will have permissions to change grid topology.
required: false
type: bool
tenant_accounts:
description:
- Users in this group will have permissions to manage tenant accounts.
required: false
type: bool
change_tenant_root_password:
description:
- Users in this group will have permissions to change tenant password.
required: false
type: bool
maintenance:
description:
- Users in this group will have permissions to run maintenance tasks on StorageGRID.
required: false
type: bool
metrics_query:
description:
- Users in this group will have permissions to query metrics on StorageGRID.
required: false
type: bool
activate_features:
description:
- Users in this group will have permissions to reactivate features.
required: false
type: bool
ilm:
description:
- Users in this group will have permissions to manage ILM rules on StorageGRID.
required: false
type: bool
object_metadata:
description:
- Users in this group will have permissions to manage object metadata.
required: false
type: bool
root_access:
description:
- Users in this group will have root access.
required: false
type: bool
"""
EXAMPLES = """
- name: create a StorageGRID group
netapp.storagegrid.na_sg_grid_group:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
display_name: ansiblegroup100
unique_name: group/ansiblegroup100
management_policy:
tenant_accounts: true
maintenance: true
root_access: false
"""
RETURN = """
resp:
description: Returns information about the StorageGRID group attributes.
returned: success
type: dict
sample: {
"displayName": "Example Group",
"policies": {
"management": {
"alarmAcknowledgment": true,
"manageAlerts": true,
"otherGridConfiguration": true,
"gridTopologyPageConfiguration": true,
"tenantAccounts": true,
"changeTenantRootPassword": true,
"maintenance": true,
"metricsQuery": true,
"activateFeatures": false,
"ilm": true,
"objectMetadata": true,
"storageAdmin": true,
"rootAccess": true
}
},
"uniqueName": "group/examplegroup",
"accountId": "12345678901234567890",
"id": "00000000-0000-0000-0000-000000000000",
"federated": false,
"groupURN": "urn:sgws:identity::12345678901234567890:group/examplegroup"
}
"""
import json
import re
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgGridGroup(object):
"""
Create, modify and delete StorageGRID Grid-administration Group
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
display_name=dict(required=False, type="str"),
unique_name=dict(required=True, type="str"),
management_policy=dict(
required=False,
type="dict",
options=dict(
alarm_acknowledgement=dict(required=False, type="bool"),
other_grid_configuration=dict(required=False, type="bool"),
grid_topology_page_configuration=dict(required=False, type="bool"),
tenant_accounts=dict(required=False, type="bool"),
change_tenant_root_password=dict(required=False, type="bool"),
maintenance=dict(required=False, type="bool"),
metrics_query=dict(required=False, type="bool"),
activate_features=dict(required=False, type="bool"),
ilm=dict(required=False, type="bool"),
object_metadata=dict(required=False, type="bool"),
root_access=dict(required=False, type="bool"),
),
),
)
)
parameter_map = {
"alarm_acknowledgement": "alarmAcknowledgement",
"other_grid_configuration": "otherGridConfiguration",
"grid_topology_page_configuration": "gridTopologyPageConfiguration",
"tenant_accounts": "tenantAccounts",
"change_tenant_root_password": "changeTenantRootPassword",
"maintenance": "maintenance",
"metrics_query": "metricsQuery",
"activate_features": "activateFeatures",
"ilm": "ilm",
"object_metadata": "objectMetadata",
"root_access": "rootAccess",
}
self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True,)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = {}
self.data["displayName"] = self.parameters.get("display_name")
self.data["uniqueName"] = self.parameters["unique_name"]
# Only add the parameter if value is True, as JSON response does not include non-true objects
self.data["policies"] = {}
if self.parameters.get("management_policy"):
self.data["policies"] = {
"management": dict(
(parameter_map[k], v) for (k, v) in self.parameters["management_policy"].items() if v
)
}
if not self.data["policies"].get("management"):
self.data["policies"]["management"] = None
self.re_local_group = re.compile("^group/")
self.re_fed_group = re.compile("^federated-group/")
if (
self.re_local_group.match(self.parameters["unique_name"]) is None
and self.re_fed_group.match(self.parameters["unique_name"]) is None
):
self.module.fail_json(msg="unique_name must begin with 'group/' or 'federated-group/'")
def get_grid_group(self, unique_name):
# Use the unique name to check if the group exists
api = "api/v3/grid/groups/%s" % unique_name
response, error = self.rest_api.get(api)
if error:
if response["code"] != 404:
self.module.fail_json(msg=error)
else:
return response["data"]
return None
def create_grid_group(self):
api = "api/v3/grid/groups"
response, error = self.rest_api.post(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def delete_grid_group(self, group_id):
api = "api/v3/grid/groups/" + group_id
self.data = None
response, error = self.rest_api.delete(api, self.data)
if error:
self.module.fail_json(msg=error)
def update_grid_group(self, group_id):
api = "api/v3/grid/groups/" + group_id
response, error = self.rest_api.put(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
grid_group = self.get_grid_group(self.parameters["unique_name"])
cd_action = self.na_helper.get_cd_action(grid_group, self.parameters)
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
update = False
if self.parameters.get("management_policy"):
if (
grid_group.get("policies") is None
or grid_group.get("policies", {}).get("management") != self.data["policies"]["management"]
):
update = True
if update:
self.na_helper.changed = True
result_message = ""
resp_data = grid_group
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == "delete":
self.delete_grid_group(grid_group["id"])
result_message = "Grid Group deleted"
elif cd_action == "create":
resp_data = self.create_grid_group()
result_message = "Grid Group created"
else:
# for a federated group, the displayName parameter needs to be specified
# and must match the existing displayName
if self.re_fed_group.match(self.parameters["unique_name"]):
self.data["displayName"] = grid_group["displayName"]
resp_data = self.update_grid_group(grid_group["id"])
result_message = "Grid Group updated"
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_grid_group = SgGridGroup()
na_sg_grid_group.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,334 @@
#!/usr/bin/python
# (c) 2022, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage HA Groups"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: na_sg_grid_ha_group
short_description: Manage high availability (HA) group configuration on StorageGRID.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '21.10.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Create, Update, Delete HA Groups on NetApp StorageGRID.
options:
state:
description:
- Whether the specified HA Group should exist.
type: str
choices: ['present', 'absent']
default: present
name:
description:
- Name of the HA Group.
type: str
ha_group_id:
description:
- HA Group ID.
- May be used for modify or delete operation.
type: str
description:
description:
- Description of the HA Group.
type: str
gateway_cidr:
description:
- CIDR for the gateway IP and VIP subnet.
type: str
virtual_ips:
description:
- A list of virtual IP addresses.
type: list
elements: str
interfaces:
description:
- A set of StorageGRID node interface pairs.
- The primary interface is specified first, followed by the other interface pairs in failover order.
type: list
elements: dict
suboptions:
node:
description:
- Name of the StorageGRID node.
type: str
interface:
description:
- The interface to bind to. eth0 corresponds to the Grid Network, eth1 to the Admin Network, and eth2 to the Client Network.
type: str
"""
EXAMPLES = """
- name: create HA Group
netapp.storagegrid.na_sg_grid_ha_group:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
name: Site1-HA-Group
description: "Site 1 HA Group"
gateway_cidr: 192.168.50.1/24
virtual_ips: 192.168.50.5
interfaces:
- node: SITE1-ADM1
interface: eth2
- node: SITE1-G1
interface: eth2
- name: add VIP to HA Group
netapp.storagegrid.na_sg_grid_ha_group:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
name: Site1-HA-Group
description: "Site 1 HA Group"
gateway_cidr: 192.168.50.1/24
virtual_ips: 192.168.50.5,192.168.50.6
interfaces:
- node: SITE1-ADM1
interface: eth2
- node: SITE1-G1
interface: eth2
- name: rename HA Group
netapp.storagegrid.na_sg_grid_ha_group:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
ha_group_id: 00000000-0000-0000-0000-000000000000
name: Site1-HA-Group-New-Name
description: "Site 1 HA Group"
gateway_cidr: 192.168.50.1/24
virtual_ips: 192.168.50.5
interfaces:
- node: SITE1-ADM1
interface: eth2
- node: SITE1-G1
interface: eth2
- name: delete HA Group
netapp.storagegrid.na_sg_grid_ha_group:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: absent
name: Site1-HA-Group
"""
RETURN = """
resp:
description: Returns information about the StorageGRID HA Group.
returned: success
type: dict
sample: {
"description": "Site 1 HA Group",
"gatewayCidr": "192.168.50.1/24",
"id": "bb386f30-805d-4fec-a2c5-85790b460db0",
"interfaces": [
{
"interface": "eth2",
"nodeId": "0b1866ed-d6e7-41b4-815f-bf867348b76b"
},
{
"interface": "eth2",
"nodeId": "7bb5bf05-a04c-4344-8abd-08c5c4048666"
}
],
"name": "Site1-HA-Group",
"virtualIps": [
"192.168.50.5",
"192.168.50.6"
]
}
"""
import json
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgGridHaGroup:
"""
Create, modify and delete HA Group configurations for StorageGRID
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
name=dict(required=False, type="str"),
ha_group_id=dict(required=False, type="str"),
description=dict(required=False, type="str"),
gateway_cidr=dict(required=False, type="str"),
virtual_ips=dict(required=False, type="list", elements="str"),
interfaces=dict(
required=False,
type="list",
elements="dict",
options=dict(
node=dict(required=False, type="str"),
interface=dict(required=False, type="str"),
),
),
)
)
parameter_map = {
"name": "name",
"description": "description",
"gateway_cidr": "gatewayCidr",
"virtual_ips": "virtualIps",
}
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[("state", "present", ["name", "gateway_cidr", "virtual_ips", "interfaces"])],
required_one_of=[("name", "ha_group_id")],
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = {}
if self.parameters["state"] == "present":
for k in parameter_map.keys():
if self.parameters.get(k) is not None:
self.data[parameter_map[k]] = self.parameters[k]
if self.parameters.get("interfaces") is not None:
self.data["interfaces"] = self.build_node_interface_list()
def build_node_interface_list(self):
node_interfaces = []
api = "api/v3/grid/node-health"
nodes, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
for node_interface in self.parameters["interfaces"]:
node_dict = {}
node = next((item for item in nodes["data"] if item["name"] == node_interface["node"]), None)
if node is not None:
node_dict["nodeId"] = node["id"]
node_dict["interface"] = node_interface["interface"]
node_interfaces.append(node_dict)
else:
self.module.fail_json(msg="Node '%s' is invalid" % node_interface["node"])
return node_interfaces
def get_ha_group_id(self):
# Check if HA Group exists
# Return HA Group info if found, or None
api = "api/v3/private/ha-groups"
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
return next((item["id"] for item in response.get("data") if item["name"] == self.parameters["name"]), None)
def get_ha_group(self, ha_group_id):
api = "api/v3/private/ha-groups/%s" % ha_group_id
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
return response["data"]
def create_ha_group(self):
api = "api/v3/private/ha-groups"
response, error = self.rest_api.post(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def delete_ha_group(self, ha_group_id):
api = "api/v3/private/ha-groups/%s" % ha_group_id
dummy, error = self.rest_api.delete(api, self.data)
if error:
self.module.fail_json(msg=error)
def update_ha_group(self, ha_group_id):
api = "api/v3/private/ha-groups/%s" % ha_group_id
response, error = self.rest_api.put(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
ha_group = None
if self.parameters.get("ha_group_id"):
ha_group = self.get_ha_group(self.parameters["ha_group_id"])
else:
ha_group_id = self.get_ha_group_id()
if ha_group_id:
ha_group = self.get_ha_group(ha_group_id)
cd_action = self.na_helper.get_cd_action(ha_group, self.parameters)
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
modify = self.na_helper.get_modified_attributes(ha_group, self.data)
result_message = ""
resp_data = {}
if self.na_helper.changed and not self.module.check_mode:
if cd_action == "delete":
self.delete_ha_group(ha_group["id"])
result_message = "HA Group deleted"
elif cd_action == "create":
resp_data = self.create_ha_group()
result_message = "HA Group created"
elif modify:
resp_data = self.update_ha_group(ha_group["id"])
result_message = "HA Group updated"
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_grid_ha_group = SgGridHaGroup()
na_sg_grid_ha_group.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,335 @@
#!/usr/bin/python
# (c) 2021, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Grid Identity Federation"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: na_sg_grid_identity_federation
short_description: NetApp StorageGRID manage Grid identity federation.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '21.6.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Configure Grid Identity Federation within NetApp StorageGRID.
- If module is run with I(check_mode), a connectivity test will be performed using the supplied values without changing the configuration.
- This module is idempotent if I(password) is not specified.
options:
state:
description:
- Whether identity federation should be enabled or not.
type: str
choices: ['present', 'absent']
default: present
username:
description:
- The username to bind to the LDAP server.
type: str
password:
description:
- The password associated with the username.
type: str
hostname:
description:
- The hostname or IP address of the LDAP server.
type: str
port:
description:
- The port used to connect to the LDAP server. Typically 389 for LDAP, or 636 for LDAPS.
type: int
base_group_dn:
description:
- The Distinguished Name of the LDAP subtree to search for groups.
type: str
base_user_dn:
description:
- The Distinguished Name of the LDAP subtree to search for users.
type: str
ldap_service_type:
description:
- The type of LDAP server.
choices: ['Active Directory', 'OpenLDAP', 'Other']
type: str
type:
description:
- The type of identity source.
- Default is C(ldap).
type: str
default: ldap
ldap_user_id_attribute:
description:
- The LDAP attribute which contains the unique user name of a user.
- Should be configured if I(ldap_service_type=Other).
type: str
ldap_user_uuid_attribute:
description:
- The LDAP attribute which contains the permanent unique identity of a user.
- Should be configured if I(ldap_service_type=Other).
type: str
ldap_group_id_attribute:
description:
- The LDAP attribute which contains the group for a user.
- Should be configured if I(ldap_service_type=Other).
type: str
ldap_group_uuid_attribute:
description:
- The LDAP attribute which contains the group's permanent unique identity.
- Should be configured if I(ldap_service_type=Other).
type: str
tls:
description:
- Whether Transport Layer Security is used to connect to the LDAP server.
choices: ['STARTTLS', 'LDAPS', 'Disabled']
type: str
default: STARTTLS
ca_cert:
description:
- Custom certificate used to connect to the LDAP server.
- If a custom certificate is not supplied, the operating system CA certificate will be used.
type: str
"""
EXAMPLES = """
- name: test identity federation configuration
netapp.storagegrid.na_sg_grid_identity_federation:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
ldap_service_type: "Active Directory"
hostname: "ad.example.com"
port: 389
username: "binduser"
password: "bindpass"
base_group_dn: "DC=example,DC=com"
base_user_dn: "DC=example,DC=com"
tls: "Disabled"
check_mode: yes
- name: configure identity federation with AD and TLS
netapp.storagegrid.na_sg_grid_identity_federation:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
ldap_service_type: "Active Directory"
hostname: "ad.example.com"
port: 636
username: "binduser"
password: "bindpass"
base_group_dn: "DC=example,DC=com"
base_user_dn: "DC=example,DC=com"
tls: "LDAPS"
ca_cert: |
-----BEGIN CERTIFICATE-----
MIIC+jCCAeICCQDmn9Gow08LTzANBgkqhkiG9w0BAQsFADA/..swCQYDVQQGEwJV
bXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB..JFzNIXQEGnsgjV
JGU4giuvOLOZ8Q3gyuUbkSUQDjmjpMR8PliwJ6iW2Ity89Dv..dl1TaIYI/ansyZ
Uxk4YXeN6kUkrDtNxCg1McALzXVAfxMTtj2SFlLxne4Z6rX2..UyftQrfM13F1vY
gK8dBPz+l+X/Uozo/xNm7gxe68p9le9/pcULst1CQn5/sPqq..kgWcSvlKUItu82
lq3B2169rovdIaNdcvaQjMPhrDGo5rvLfMN35U3Hgbz41PL5..x2BcUE6/0ab5T4
qKBxKa3t9twj+zpUqOzyL0PFfCE+SK5fEXAS1ow4eAcLN+eB..gR/PuvGAyIPCtE
1+X4GrECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAFpO+04Ra..FMJPH6dBmzfb7l
k04BWTvSlur6HiQdXY+oFQMJZzyI7MQ8v9HBIzS0ZAzYWLp4..VZhHmRxnrWyxVs
u783V5YfQH2L4QnBDoiDefgxyfDs2PcoF5C+X9CGXmPqzst2..y/6tdOVJzdiA==
-----END CERTIFICATE-----
"""
RETURN = """
resp:
description: Returns information about the StorageGRID management identity source configuration.
returned: success
type: dict
sample: {
"id": "00000000-0000-0000-0000-000000000000",
"disable": false,
"hostname": "10.1.2.3",
"port": 389,
"username": "MYDOMAIN\\\\Administrator",
"password": "********",
"baseGroupDn": "DC=example,DC=com",
"baseUserDn": "DC=example,DC=com",
"ldapServiceType": "Active Directory",
"type": "ldap",
"disableTLS": false,
"enableLDAPS": false,
"caCert": "-----BEGIN CERTIFICATE----- abcdefghijkl123456780ABCDEFGHIJKL 123456/7890ABCDEFabcdefghijklABCD -----END CERTIFICATE-----\n"
}
"""
import json
import re
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgGridIdentityFederation:
"""
Configure and modify StorageGRID Grid Identity Federation
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
username=dict(required=False, type="str"),
password=dict(required=False, type="str", no_log=True),
hostname=dict(required=False, type="str"),
port=dict(required=False, type="int"),
base_group_dn=dict(required=False, type="str"),
base_user_dn=dict(required=False, type="str"),
ldap_service_type=dict(required=False, type="str", choices=["OpenLDAP", "Active Directory", "Other"]),
type=dict(required=False, type="str", default="ldap"),
ldap_user_id_attribute=dict(required=False, type="str"),
ldap_user_uuid_attribute=dict(required=False, type="str"),
ldap_group_id_attribute=dict(required=False, type="str"),
ldap_group_uuid_attribute=dict(required=False, type="str"),
tls=dict(required=False, type="str", choices=["STARTTLS", "LDAPS", "Disabled"], default="STARTTLS"),
ca_cert=dict(required=False, type="str"),
),
)
parameter_map = {
"username": "username",
"password": "password",
"hostname": "hostname",
"port": "port",
"base_group_dn": "baseGroupDn",
"base_user_dn": "baseUserDn",
"ldap_service_type": "ldapServiceType",
"ldap_user_id_attribute": "ldapUserIdAttribute",
"ldap_user_uuid_attribute": "ldapUserUUIDAttribute",
"ldap_group_id_attribute": "ldapGroupIdAttribute",
"ldap_group_uuid_attribute": "ldapGroupUUIDAttribute",
"ca_cert": "caCert",
}
self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True,)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = {}
if self.parameters["state"] == "present":
self.data["disable"] = False
for k in parameter_map.keys():
if self.parameters.get(k) is not None:
self.data[parameter_map[k]] = self.parameters[k]
if self.parameters.get("tls") == "STARTTLS":
self.data["disableTLS"] = False
self.data["enableLDAPS"] = False
elif self.parameters.get("tls") == "LDAPS":
self.data["disableTLS"] = False
self.data["enableLDAPS"] = True
else:
self.data["disableTLS"] = True
self.data["enableLDAPS"] = False
def get_grid_identity_source(self):
api = "api/v3/grid/identity-source"
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
else:
return response["data"]
return None
def update_identity_federation(self, test=False):
api = "api/v3/grid/identity-source"
params = {}
if test:
params["test"] = True
response, error = self.rest_api.put(api, self.data, params=params)
if error:
self.module.fail_json(msg=error, payload=self.data)
if response is not None:
return response["data"]
else:
return None
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
grid_identity_source = self.get_grid_identity_source()
cd_action = self.na_helper.get_cd_action(grid_identity_source, self.parameters)
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
update = False
for k in (i for i in self.data.keys() if i != "password"):
if self.data[k] != grid_identity_source.get(k):
update = True
break
# if a password has been specified we need to update it
if self.data.get("password") and self.parameters["state"] == "present":
update = True
self.module.warn("Password attribute has been specified. Task is not idempotent.")
if update:
self.na_helper.changed = True
if cd_action == "delete":
# if identity federation is already in a disable state
if grid_identity_source.get("disable"):
self.na_helper.changed = False
result_message = ""
resp_data = grid_identity_source
if self.na_helper.changed and not self.module.check_mode:
if cd_action == "delete":
self.data = dict(disable=True)
resp_data = self.update_identity_federation()
result_message = "Grid identity federation disabled"
else:
resp_data = self.update_identity_federation()
result_message = "Grid identity federation updated"
if self.module.check_mode:
self.update_identity_federation(test=True)
# if no error, connection test successful
self.module.exit_json(changed=self.na_helper.changed, msg="Connection test successful")
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_grid_identity_federation = SgGridIdentityFederation()
na_sg_grid_identity_federation.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,405 @@
#!/usr/bin/python
# (c) 2020, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
""" NetApp StorageGRID Grid Info using REST APIs """
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
module: na_sg_grid_info
author: NetApp Ansible Team (@jasonl4) <ng-ansibleteam@netapp.com>
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
short_description: NetApp StorageGRID Grid information gatherer.
description:
- This module allows you to gather various information about StorageGRID Grid configuration.
version_added: 20.11.0
options:
gather_subset:
type: list
elements: str
description:
- When supplied, this argument will restrict the information collected to a given subset.
- Either the info name or the REST API can be given.
- Possible values for this argument include
- C(grid_accounts_info) or C(grid/accounts)
- C(grid_alarms_info) or C(grid/alarms)
- C(grid_audit_info) or C(grid/audit)
- C(grid_compliance_global_info) or C(grid/compliance-global)
- C(grid_config_info) or C(grid/config)
- C(grid_config_management_info) or C(grid/config/management)
- C(grid_config_product_version_info) or C(grid/config/product-version)
- C(grid_deactivated_features_info) or C(grid/deactivated-features)
- C(grid_dns_servers_info) or C(grid/dns-servers)
- C(grid_domain_names_info) or C(grid/domain-names)
- C(grid_ec_profiles_info) or C(grid/ec-profiles)
- C(grid_expansion_info) or C(grid/expansion)
- C(grid_expansion_nodes_info) or C(grid/expansion/nodes)
- C(grid_expansion_sites_info) or C(grid/expansion/sites)
- C(grid_grid_networks_info) or C(grid/grid-networks)
- C(grid_groups_info) or C(grid/groups)
- C(grid_health_info) or C(grid/health)
- C(grid_health_topology_info) or C(grid/health/topology)
- C(grid_identity_source_info) or C(grid/identity-source)
- C(grid_ilm_criteria_info) or C(grid/ilm-criteria)
- C(grid_ilm_policies_info) or C(grid/ilm-policies)
- C(grid_ilm_rules_info) or C(grid/ilm-rules)
- C(grid_license_info) or C(grid/license)
- C(grid_management_certificate_info) or C(grid/management-certificate)
- C(grid_ntp_servers_info) or C(grid/ntp-servers)
- C(grid_recovery_available_nodes_info) or C(grid/recovery/available-nodes)
- C(grid_recovery_info) or C(grid/recovery)
- C(grid_regions_info) or C(grid/regions)
- C(grid_schemes_info) or C(grid/schemes)
- C(grid_snmp_info) or C(grid/snmp)
- C(grid_storage_api_certificate_info) or C(grid/storage-api-certificate)
- C(grid_untrusted_client_network_info) or C(grid/untrusted-client-network)
- C(grid_users_info) or C(grid/users)
- C(grid_users_root_info) or C(grid/users/root)
- C(versions_info) or C(versions)
- Can specify a list of values to include a larger subset.
default: all
parameters:
description:
- Allows for any rest option to be passed in.
type: dict
"""
EXAMPLES = """
- name: Gather StorageGRID Grid info
netapp.storagegrid.na_sg_grid_info:
api_url: "https://1.2.3.4/"
auth_token: "storagegrid-auth-token"
validate_certs: false
register: sg_grid_info
- name: Gather StorageGRID Grid info for grid/accounts and grid/config subsets
netapp.storagegrid.na_sg_grid_info:
api_url: "https://1.2.3.4/"
auth_token: "storagegrid-auth-token"
validate_certs: false
gather_subset:
- grid_accounts_info
- grid/config
register: sg_grid_info
- name: Gather StorageGRID Grid info for all subsets
netapp.storagegrid.na_sg_grid_info:
api_url: "https://1.2.3.4/"
auth_token: "storagegrid-auth-token"
validate_certs: false
gather_subset:
- all
register: sg_grid_info
- name: Gather StorageGRID Grid info for grid/accounts and grid/users subsets, limit to 5 results for each subset
netapp.storagegrid.na_sg_grid_info:
api_url: "https://1.2.3.4/"
auth_token: "storagegrid-auth-token"
validate_certs: false
gather_subset:
- grid/accounts
- grid/users
parameters:
limit: 5
register: sg_grid_info
"""
RETURN = """
sg_info:
description: Returns various information about the StorageGRID Grid configuration.
returned: always
type: dict
sample: {
"grid/accounts": {...},
"grid/alarms": {...},
"grid/audit": {...},
"grid/compliance-global": {...},
"grid/config": {...},
"grid/config/management": {...},
"grid/config/product-version": {...},
"grid/deactivated-features": {...},
"grid/dns-servers": {...},
"grid/domain-names": {...},
"grid/ec-profiles": {...},
"grid/expansion": {...},
"grid/expansion/nodes": {...},
"grid/expansion/sites": {...},
"grid/networks": {...},
"grid/groups": {...},
"grid/health": {...},
"grid/health/topology": {...},
"grid/identity-source": {...},
"grid/ilm-criteria": {...},
"grid/ilm-policies": {...},
"grid/ilm-rules": {...},
"grid/license": {...},
"grid/management-certificate": {...},
"grid/ntp-servers": {...},
"grid/recovery/available-nodes": {...},
"grid/recovery": {...},
"grid/regions": {...},
"grid/schemes": {...},
"grid/snmp": {...},
"grid/storage-api-certificate": {...},
"grid/untrusted-client-network": {...},
"grid/users": {...},
"grid/users/root": {...},
"grid/versions": {...}
}
"""
from ansible.module_utils.basic import AnsibleModule
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class NetAppSgGatherInfo(object):
""" Class with gather info methods """
def __init__(self):
"""
Parse arguments, setup variables, check parameters and ensure
request module is installed.
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(dict(
gather_subset=dict(default=['all'], type='list', elements='str', required=False),
parameters=dict(type='dict', required=False)
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
# set up variables
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
self.rest_api = SGRestAPI(self.module)
def get_subset_info(self, gather_subset_info):
"""
Gather StorageGRID information for the given subset using REST APIs
Input for REST APIs call : (api, data)
return gathered_sg_info
"""
api = gather_subset_info['api_call']
data = {}
# allow for passing in any additional rest api parameters
if self.parameters.get('parameters'):
for each in self.parameters['parameters']:
data[each] = self.parameters['parameters'][each]
gathered_sg_info, error = self.rest_api.get(api, data)
if error:
self.module.fail_json(msg=error)
else:
return gathered_sg_info
return None
def convert_subsets(self):
""" Convert an info to the REST API """
info_to_rest_mapping = {
'grid_accounts_info': 'grid/accounts',
'grid_alarms_info': 'grid/alarms',
'grid_audit_info': 'grid/audit',
'grid_compliance_global_info': 'grid/compliance-global',
'grid_config_info': 'grid/config',
'grid_config_management_info': 'grid/config/management',
'grid_config_product_version_info': 'grid/config/product-version',
'grid_deactivated_features_info': 'grid/deactivated-features',
'grid_dns_servers_info': 'grid/dns-servers',
'grid_domain_names_info': 'grid/domain-names',
'grid_ec_profiles_info': 'grid/ec-profiles',
'grid_expansion_info': 'grid/expansion',
'grid_expansion_nodes_info': 'grid/expansion/nodes',
'grid_expansion_sites_info': 'grid/expansion/sites',
'grid_grid_networks_info': 'grid/grid-networks',
'grid_groups_info': 'grid/groups',
'grid_health_info': 'grid/health',
'grid_health_topology_info': 'grid/health/topology',
'grid_identity_source_info': 'grid/identity-source',
'grid_ilm_criteria_info': 'grid/ilm-criteria',
'grid_ilm_policies_info': 'grid/ilm-policies',
'grid_ilm_rules_info': 'grid/ilm-rules',
'grid_license_info': 'grid/license',
'grid_management_certificate_info': 'grid/management-certificate',
'grid_ntp_servers_info': 'grid/ntp-servers',
'grid_recovery_available_nodes_info': 'grid/recovery/available-nodes',
'grid_recovery_info': 'grid/recovery',
'grid_regions_info': 'grid/regions',
'grid_schemes_info': 'grid/schemes',
'grid_snmp_info': 'grid/snmp',
'grid_storage_api_certificate_info': 'grid/storage-api-certificate',
'grid_untrusted_client_network_info': 'grid/untrusted-client-network',
'grid_users_info': 'grid/users',
'grid_users_root_info': 'grid/users/root',
'versions_info': 'versions',
}
# Add rest API names as there info version, also make sure we don't add a duplicate
subsets = []
for subset in self.parameters['gather_subset']:
if subset in info_to_rest_mapping:
if info_to_rest_mapping[subset] not in subsets:
subsets.append(info_to_rest_mapping[subset])
else:
if subset not in subsets:
subsets.append(subset)
return subsets
def apply(self):
""" Perform pre-checks, call functions and exit """
result_message = dict()
# Defining gather_subset and appropriate api_call
get_sg_subset_info = {
'grid/accounts': {
'api_call': 'api/v3/grid/accounts',
},
'grid/alarms': {
'api_call': 'api/v3/grid/alarms',
},
'grid/audit': {
'api_call': 'api/v3/grid/audit',
},
'grid/compliance-global': {
'api_call': 'api/v3/grid/compliance-global',
},
'grid/config': {
'api_call': 'api/v3/grid/config',
},
'grid/config/management': {
'api_call': 'api/v3/grid/config/management',
},
'grid/config/product-version': {
'api_call': 'api/v3/grid/config/product-version',
},
'grid/deactivated-features': {
'api_call': 'api/v3/grid/deactivated-features',
},
'grid/dns-servers': {
'api_call': 'api/v3/grid/dns-servers',
},
'grid/domain-names': {
'api_call': 'api/v3/grid/domain-names',
},
'grid/ec-profiles': {
'api_call': 'api/v3/grid/ec-profiles',
},
'grid/expansion': {
'api_call': 'api/v3/grid/expansion',
},
'grid/expansion/nodes': {
'api_call': 'api/v3/grid/expansion/nodes',
},
'grid/expansion/sites': {
'api_call': 'api/v3/grid/expansion/sites',
},
'grid/grid-networks': {
'api_call': 'api/v3/grid/grid-networks',
},
'grid/groups': {
'api_call': 'api/v3/grid/groups',
},
'grid/health': {
'api_call': 'api/v3/grid/health',
},
'grid/health/topology': {
'api_call': 'api/v3/grid/health/topology',
},
'grid/identity-source': {
'api_call': 'api/v3/grid/identity-source',
},
'grid/ilm-criteria': {
'api_call': 'api/v3/grid/ilm-criteria',
},
'grid/ilm-policies': {
'api_call': 'api/v3/grid/ilm-policies',
},
'grid/ilm-rules': {
'api_call': 'api/v3/grid/ilm-rules',
},
'grid/license': {
'api_call': 'api/v3/grid/license',
},
'grid/management-certificate': {
'api_call': 'api/v3/grid/management-certificate',
},
'grid/ntp-servers': {
'api_call': 'api/v3/grid/ntp-servers',
},
'grid/recovery/available-nodes': {
'api_call': 'api/v3/grid/recovery/available-nodes',
},
'grid/recovery': {
'api_call': 'api/v3/grid/recovery',
},
'grid/regions': {
'api_call': 'api/v3/grid/regions',
},
'grid/schemes': {
'api_call': 'api/v3/grid/schemes',
},
'grid/snmp': {
'api_call': 'api/v3/grid/snmp',
},
'grid/storage-api-certificate': {
'api_call': 'api/v3/grid/storage-api-certificate',
},
'grid/untrusted-client-network': {
'api_call': 'api/v3/grid/untrusted-client-network',
},
'grid/users': {
'api_call': 'api/v3/grid/users',
},
'grid/users/root': {
'api_call': 'api/v3/grid/users/root',
},
'versions': {
'api_call': 'api/v3/versions',
},
}
if 'all' in self.parameters['gather_subset']:
# If all in subset list, get the information of all subsets
self.parameters['gather_subset'] = sorted(get_sg_subset_info.keys())
converted_subsets = self.convert_subsets()
for subset in converted_subsets:
try:
# Verify whether the supported subset passed
specified_subset = get_sg_subset_info[subset]
except KeyError:
self.module.fail_json(msg="Specified subset %s not found, supported subsets are %s" %
(subset, list(get_sg_subset_info.keys())))
result_message[subset] = self.get_subset_info(specified_subset)
self.module.exit_json(changed='False', sg_info=result_message)
def main():
""" Main function """
obj = NetAppSgGatherInfo()
obj.apply()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,173 @@
#!/usr/bin/python
# (c) 2020, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Grid NTP Servers"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = """
module: na_sg_grid_ntp
short_description: NetApp StorageGRID manage external NTP servers for the grid.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '20.6.0'
author: NetApp Ansible Team (@jkandati) <ng-sg-ansibleteam@netapp.com>
description:
- Update NTP server on NetApp StorageGRID.
options:
state:
description:
- Whether the specified user should exist or not.
type: str
choices: ['present']
default: present
ntp_servers:
description:
- List of comma separated NTP server address.
type: list
elements: str
required: true
passphrase:
description:
- passphrase for GRID.
type: str
required: true
"""
EXAMPLES = """
- name: update NTP servers
netapp.storagegrid.na_sg_grid_ntp:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
passphrase: "{{ grid_pass }}"
ntp_servers: "x.x.x.x,xx.x.xx.x"
"""
RETURN = """
resp:
description: Returns information about the configured NTP servers.
returned: success
type: list
elements: str
sample: ["10.0.0.1", "10.0.0.2", "10.0.0.3", "10.0.0.4"]
"""
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgGridNtp(object):
"""
Create, modify and delete NTP entries for StorageGRID
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present"], default="present"),
ntp_servers=dict(required=True, type="list", elements="str"),
passphrase=dict(required=True, type="str", no_log=True),
)
)
self.module = AnsibleModule(
argument_spec=self.argument_spec,
# required_if=[("state", "present", ["state", "name", "protocol"])],
supports_check_mode=True,
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = self.parameters["ntp_servers"]
self.passphrase = self.parameters["passphrase"]
self.ntp_input = {"passphrase": self.passphrase, "servers": self.data}
def get_grid_ntp(self):
# Check if tenant account exists
# Return tenant account info if found, or None
api = "api/v3/grid/ntp-servers"
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
return response["data"]
def update_grid_ntp(self):
api = "api/v3/grid/ntp-servers/update"
response, error = self.rest_api.post(api, self.ntp_input)
if error:
self.module.fail_json(msg=error)
return response["data"]
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
grid_ntp = self.get_grid_ntp()
cd_action = self.na_helper.get_cd_action(grid_ntp, self.parameters["ntp_servers"])
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
update = False
ntp_diff = [i for i in self.data + grid_ntp if i not in self.data or i not in grid_ntp]
if ntp_diff:
update = True
if update:
self.na_helper.changed = True
result_message = ""
resp_data = grid_ntp
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
resp_data = self.update_grid_ntp()
result_message = "Grid NTP updated"
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_grid_ntp = SgGridNtp()
na_sg_grid_ntp.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,163 @@
#!/usr/bin/python
# (c) 2020, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Grid Regions"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = """
module: na_sg_grid_regions
short_description: NetApp StorageGRID manage Regions for the grid.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '20.6.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Create, Update, Delete Users within a NetApp StorageGRID tenant.
options:
state:
description:
- Whether the specified user should exist or not.
type: str
choices: ['present']
default: present
regions:
description:
- List of regions
required: true
type: list
elements: str
"""
EXAMPLES = """
- name: update Regions
netapp.storagegrid.na_sg_grid_regions:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
regions: "us-east-1"
"""
RETURN = """
resp:
description: Returns information about the configured regions.
returned: success
type: list
elements: str
sample: ["us-east-1", "us-central-1"]
"""
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgGridRegions(object):
"""
Create, modify and delete Regions for StorageGRID
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present"], default="present"),
regions=dict(required=True, type="list", elements="str"),
)
)
self.module = AnsibleModule(
argument_spec=self.argument_spec,
# required_if=[("state", "present", ["state", "name", "protocol"])],
supports_check_mode=True,
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = self.parameters["regions"]
def get_grid_regions(self):
# Check if tenant account exists
# Return tenant account info if found, or None
api = "api/v3/grid/regions"
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
return response["data"]
def update_grid_regions(self):
api = "api/v3/grid/regions"
response, error = self.rest_api.put(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
grid_regions = self.get_grid_regions()
cd_action = self.na_helper.get_cd_action(grid_regions, self.parameters["regions"])
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
update = False
regions_diff = [i for i in self.data + grid_regions if i not in self.data or i not in grid_regions]
if regions_diff:
update = True
if update:
self.na_helper.changed = True
result_message = ""
resp_data = grid_regions
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
resp_data = self.update_grid_regions()
result_message = "Grid Regions updated"
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_grid_regions = SgGridRegions()
na_sg_grid_regions.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,375 @@
#!/usr/bin/python
# (c) 2022, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Traffic Classification Policies"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: na_sg_grid_traffic_classes
short_description: Manage Traffic Classification Policy configuration on StorageGRID.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '21.10.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Create, Update, Delete Traffic Classification Policies on NetApp StorageGRID.
options:
state:
description:
- Whether the specified Traffic Classification Policy should exist.
type: str
choices: ['present', 'absent']
default: present
name:
description:
- Name of the Traffic Classification Policy.
type: str
policy_id:
description:
- Traffic Classification Policy ID.
- May be used for modify or delete operation.
type: str
description:
description:
- Description of the Traffic Classification Policy.
type: str
matchers:
description:
- A set of parameters to match.
- The traffic class will match requests where any of these matchers match.
type: list
elements: dict
suboptions:
type:
description:
- The attribute of the request to match.
- C(bucket) - The S3 bucket (or Swift container) being accessed.
- C(bucket-regex) - A regular expression to evaluate against the S3 bucket (or Swift container) being accessed.
- C(cidr) - Matches if the client request source IP is in the specified IPv4 CIDR (RFC4632).
- C(tenant) - Matches if the S3 bucket (or Swift container) is owned by the tenant account with this ID.
choices: ['bucket', 'bucket-regex', 'cidr', 'endpoint', 'tenant']
type: str
required: true
inverse:
description:
- If I(true), entities that match the value are excluded.
type: bool
default: false
members:
description:
- A list of members to match on.
type: list
elements: str
required: true
limits:
description:
- Optional limits to impose on client requests matched by this traffic class.
- Only one of each limit type can be specified.
type: list
elements: dict
suboptions:
type:
description:
- The type of limit to apply.
- C(aggregateBandwidthIn) - The maximum combined upload bandwidth in bytes/second of all concurrent requests that match this policy.
- C(aggregateBandwidthOut) - The maximum combined download bandwidth in bytes/second of all concurrent requests that match this policy.
- C(concurrentReadRequests) - The maximum number of download requests that can be in progress at the same time.
- C(concurrentWriteRequests) - The maximum number of upload requests that can be in progress at the same time.
- C(readRequestRate) - The maximum number of download requests that can be started each second.
- C(writeRequestRate) - The maximum number of download requests that can be started each second.
- C(perRequestBandwidthIn) - The maximum upload bandwidth in bytes/second allowed for each request that matches this policy.
- C(perRequestBandwidthOut) - The maximum download bandwidth in bytes/second allowed for each request that matches this policy.
choices: [
'aggregateBandwidthIn',
'aggregateBandwidthOut',
'concurrentReadRequests',
'concurrentWriteRequests',
'readRequestRate',
'writeRequestRate',
'perRequestBandwidthIn',
'perRequestBandwidthOut'
]
type: str
required: true
value:
description:
- The limit to apply.
- Limit values are type specific.
type: int
required: true
"""
EXAMPLES = """
- name: create Traffic Classification Policy with bandwidth limit on buckets
netapp.storagegrid.na_sg_grid_traffic_classes:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
name: Traffic-Policy1
matchers:
- type: bucket
members: bucket1,anotherbucket
limits:
- type: aggregateBandwidthOut
value: 100000000
- name: create Traffic Classification Policy with bandwidth limits except for specific tenant account
netapp.storagegrid.na_sg_grid_traffic_classes:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
name: Fabricpool-Policy
description: "Limit all to 500MB/s except FabricPool tenant"
matchers:
- type: tenant
inverse: True
members: 12345678901234567890
limits:
- type: aggregateBandwidthIn
value: 50000000
- type: aggregateBandwidthOut
value: 50000000
- name: rename Traffic Classification Policy
netapp.storagegrid.na_sg_grid_traffic_classes:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
policy_id: 00000000-0000-0000-0000-000000000000
name: Traffic-Policy1-New-Name
matchers:
- type: bucket
members: bucket1,anotherbucket
limits:
- type: aggregateBandwidthOut
value: 100000000
- name: delete Traffic Classification Policy
netapp.storagegrid.na_sg_grid_traffic_classes:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: absent
name: Traffic-Policy1
"""
RETURN = """
resp:
description: Returns information about the StorageGRID Traffic Classification Policy.
returned: success
type: dict
sample: {
"id": "6b2946e6-7fed-40d0-9262-8e922580aba7",
"name": "Traffic-Policy1",
"description": "Traffic Classification Policy 1",
"matchers": [
{
"type": "cidr",
"inverse": False,
"members": [
"192.168.50.0/24"
]
},
{
"type": "bucket",
"inverse": False,
"members": [
"mybucket1",
"mybucket2"
]
},
],
"limits": [
{
"type": "aggregateBandwidthOut",
"value": 100000000
}
],
}
"""
import json
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgGridTrafficClasses:
"""
Create, modify and delete Traffic Classification Policies for StorageGRID
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
name=dict(required=False, type="str"),
policy_id=dict(required=False, type="str"),
description=dict(required=False, type="str"),
matchers=dict(
required=False,
type="list",
elements="dict",
options=dict(
type=dict(
required=True,
type="str",
choices=["bucket", "bucket-regex", "cidr", "endpoint", "tenant"],
),
inverse=dict(required=False, type="bool", default="false"),
members=dict(required=True, type="list", elements="str"),
),
),
limits=dict(
required=False,
type="list",
elements="dict",
options=dict(
type=dict(
required=True,
type="str",
choices=[
"aggregateBandwidthIn",
"aggregateBandwidthOut",
"concurrentReadRequests",
"concurrentWriteRequests",
"readRequestRate",
"writeRequestRate",
"perRequestBandwidthIn",
"perRequestBandwidthOut",
],
),
value=dict(required=True, type="int"),
),
),
)
)
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[("state", "present", ["name"])],
required_one_of=[("name", "policy_id")],
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = {}
if self.parameters["state"] == "present":
for k in ["name", "description", "matchers", "limits"]:
if self.parameters.get(k) is not None:
self.data[k] = self.parameters[k]
def get_traffic_class_policy_id(self):
# Check if Traffic Classification Policy exists
# Return policy ID if found, or None
api = "api/v3/grid/traffic-classes/policies"
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
return next((item["id"] for item in response.get("data") if item["name"] == self.parameters["name"]), None)
def get_traffic_class_policy(self, policy_id):
api = "api/v3/grid/traffic-classes/policies/%s" % policy_id
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
return response["data"]
def create_traffic_class_policy(self):
api = "api/v3/grid/traffic-classes/policies"
# self.module.fail_json(msg=self.data)
response, error = self.rest_api.post(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def delete_traffic_class_policy(self, policy_id):
api = "api/v3/grid/traffic-classes/policies/%s" % policy_id
dummy, error = self.rest_api.delete(api, self.data)
if error:
self.module.fail_json(msg=error)
def update_traffic_class_policy(self, policy_id):
api = "api/v3/grid/traffic-classes/policies/%s" % policy_id
response, error = self.rest_api.put(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
traffic_class_policy = None
if self.parameters.get("policy_id"):
traffic_class_policy = self.get_traffic_class_policy(self.parameters["policy_id"])
else:
policy_id = self.get_traffic_class_policy_id()
if policy_id:
traffic_class_policy = self.get_traffic_class_policy(policy_id)
cd_action = self.na_helper.get_cd_action(traffic_class_policy, self.parameters)
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
modify = self.na_helper.get_modified_attributes(traffic_class_policy, self.data)
result_message = ""
resp_data = {}
if self.na_helper.changed and not self.module.check_mode:
if cd_action == "delete":
self.delete_traffic_class_policy(traffic_class_policy["id"])
result_message = "Traffic Classification Policy deleted"
elif cd_action == "create":
resp_data = self.create_traffic_class_policy()
result_message = "Traffic Classification Policy created"
elif modify:
resp_data = self.update_traffic_class_policy(traffic_class_policy["id"])
result_message = "Traffic Classification Policy updated"
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_grid_traffic_classes = SgGridTrafficClasses()
na_sg_grid_traffic_classes.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,316 @@
#!/usr/bin/python
# (c) 2020, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Grid-administration Users"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = """
module: na_sg_grid_user
short_description: NetApp StorageGRID manage users.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '20.6.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Create, Update, Delete Administrative Users within NetApp StorageGRID.
options:
state:
description:
- Whether the specified user should exist or not.
type: str
choices: ['present', 'absent']
default: present
full_name:
description:
- Full Name of the user.
- Required for create operation
type: str
unique_name:
description:
- Unique Name for the user. Must begin with C(user/) or C(federated-user/)
- Required for create, modify or delete operation.
type: str
required: true
member_of:
description:
- List of C(unique_groups) that the user is a member of.
type: list
elements: str
password:
description:
- Set a password for a local user. Does not apply to federated users.
- Requires root privilege.
required: false
type: str
update_password:
description:
- Choose when to update the password.
- When set to C(always), the password will always be updated.
- When set to C(on_create), the password will only be set upon a new user creation.
default: on_create
choices:
- on_create
- always
type: str
disable:
description:
- Disable the user from signing in. Does not apply to federated users.
type: bool
"""
EXAMPLES = """
- name: create a user
netapp.storagegrid.na_sg_grid_user:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
full_name: ansibleuser100
unique_name: user/ansibleuser100
member_of: "group/ansiblegroup100"
disable: false
"""
RETURN = """
resp:
description: Returns information about the StorageGRID Grid user.
returned: always
type: dict
sample: {
"fullName": "Example User",
"memberOf": ["00000000-0000-0000-0000-000000000000"],
"disable": false,
"uniqueName": "user/Example",
"accountId": "0",
"id": "00000000-0000-0000-0000-000000000000",
"federated": false,
"userURN": "urn:sgws:identity::0:user/Example"
}
"""
import json
import re
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgGridUser(object):
"""
Create, modify and delete user within a StorageGRID Tenant Account
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
full_name=dict(required=False, type="str"),
unique_name=dict(required=True, type="str"),
member_of=dict(required=False, type="list", elements="str"),
disable=dict(required=False, type="bool"),
password=dict(required=False, type="str", no_log=True),
update_password=dict(default="on_create", choices=["on_create", "always"]),
)
)
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[("state", "present", ["full_name", "unique_name"])],
supports_check_mode=True,
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = {}
self.data["memberOf"] = []
if self.parameters.get("full_name"):
self.data["fullName"] = self.parameters["full_name"]
if self.parameters.get("unique_name"):
self.data["uniqueName"] = self.parameters["unique_name"]
if self.parameters.get("disable") is not None:
self.data["disable"] = self.parameters["disable"]
re_local_user = re.compile("^user/")
re_fed_user = re.compile("^federated-user/")
if (
re_local_user.match(self.parameters["unique_name"]) is None
and re_fed_user.match(self.parameters["unique_name"]) is None
):
self.module.fail_json(msg="unique_name must begin with 'user/' or 'federated-user/'")
self.pw_change = {}
if self.parameters.get("password") is not None:
if re_fed_user.match(self.parameters["unique_name"]):
self.module.fail_json(msg="password cannot be set for a federated user")
self.pw_change["password"] = self.parameters["password"]
def get_grid_groups(self):
# Get list of admin groups
# Retrun mapping of uniqueName to ids if found, or None
api = "api/v3/grid/groups?limit=350"
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
if response["data"]:
name_to_id_map = dict(zip([i["uniqueName"] for i in response["data"]], [j["id"] for j in response["data"]]))
return name_to_id_map
return None
def get_grid_user(self, unique_name):
# Use the unique name to check if the user exists
api = "api/v3/grid/users/%s" % unique_name
response, error = self.rest_api.get(api)
if error:
if response["code"] != 404:
self.module.fail_json(msg=error["text"])
else:
return response["data"]
return None
def create_grid_user(self):
api = "api/v3/grid/users"
response, error = self.rest_api.post(api, self.data)
if error:
self.module.fail_json(msg=error["text"])
return response["data"]
def delete_grid_user(self, user_id):
api = "api/v3/grid/users/" + user_id
self.data = None
response, error = self.rest_api.delete(api, self.data)
if error:
self.module.fail_json(msg=error)
def update_grid_user(self, user_id):
api = "api/v3/grid/users/" + user_id
response, error = self.rest_api.put(api, self.data)
if error:
self.module.fail_json(msg=error["text"])
return response["data"]
def set_grid_user_password(self, unique_name):
api = "api/v3/grid/users/%s/change-password" % unique_name
response, error = self.rest_api.post(api, self.pw_change)
if error:
self.module.fail_json(msg=error["text"])
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
grid_user = self.get_grid_user(self.parameters["unique_name"])
if self.parameters.get("member_of"):
grid_groups = self.get_grid_groups()
try:
self.data["memberOf"] = [grid_groups[x] for x in self.parameters["member_of"]]
except KeyError as e:
self.module.fail_json(msg="Invalid unique_group supplied: '%s' not found" % e.args[0])
cd_action = self.na_helper.get_cd_action(grid_user, self.parameters)
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
update = False
if grid_user["memberOf"] is None:
member_of_diff = []
else:
member_of_diff = [
i
for i in self.data["memberOf"] + grid_user["memberOf"]
if i not in self.data["memberOf"] or i not in grid_user["memberOf"]
]
if member_of_diff:
update = True
if self.parameters.get("disable") is not None and self.parameters["disable"] != grid_user.get("disable"):
update = True
if update:
self.na_helper.changed = True
result_message = ""
resp_data = grid_user
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == "delete":
self.delete_grid_user(grid_user["id"])
result_message = "Grid User deleted"
elif cd_action == "create":
resp_data = self.create_grid_user()
result_message = "Grid User created"
else:
resp_data = self.update_grid_user(grid_user["id"])
result_message = "Grid User updated"
# If a password has been set
if self.pw_change:
if self.module.check_mode:
pass
else:
# Only update the password if update_password is always, or a create activity has occurred
if cd_action == "create" or self.parameters["update_password"] == "always":
self.set_grid_user_password(self.parameters["unique_name"])
self.na_helper.changed = True
results = [result_message, "Grid User password updated"]
result_message = "; ".join(filter(None, results))
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_grid_user = SgGridUser()
na_sg_grid_user.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,352 @@
#!/usr/bin/python
# (c) 2020, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Buckets"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = """
module: na_sg_org_container
short_description: Manage buckets on StorageGRID.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '20.6.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Create S3 buckets on NetApp StorageGRID.
options:
state:
description:
- Whether the specified bucket should exist or not.
type: str
choices: ['present', 'absent']
default: present
name:
description:
- Name of the bucket.
required: true
type: str
region:
description:
- Set a region for the bucket.
type: str
compliance:
description:
- Configure compliance settings for an S3 bucket.
- Cannot be specified along with I(s3_object_lock_enabled).
type: dict
suboptions:
auto_delete:
description:
- If enabled, objects will be deleted automatically when its retention period expires, unless the bucket is under a legal hold.
type: bool
legal_hold:
description:
- If enabled, objects in this bucket cannot be deleted, even if their retention period has expired.
type: bool
retention_period_minutes:
description:
- specify the length of the retention period for objects added to this bucket, in minutes.
type: int
s3_object_lock_enabled:
description:
- Enable S3 Object Lock on the bucket.
- S3 Object Lock requires StorageGRID 11.5 or greater.
type: bool
version_added: '21.9.0'
bucket_versioning_enabled:
description:
- Enable versioning on the bucket.
- This API requires StorageGRID 11.6 or greater.
type: bool
version_added: '21.11.0'
"""
EXAMPLES = """
- name: create a s3 bucket
netapp.storagegrid.na_sg_org_container:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
name: ansiblebucket1
- name: delete a s3 bucket
netapp.storagegrid.na_sg_org_container:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: absent
name: ansiblebucket1
- name: create a s3 bucket with Object Lock
netapp.storagegrid.na_sg_org_container:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
name: objectlock-bucket1
s3_object_lock_enabled: true
- name: create a s3 bucket with versioning enabled
netapp.storagegrid.na_sg_org_container:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
name: ansiblebucket1
bucket_versioning_enabled: true
"""
RETURN = """
resp:
description: Returns information about the StorageGRID bucket.
returned: always
type: dict
sample: {
"name": "example-bucket",
"creationTime": "2021-01-01T00:00:00.000Z",
"region": "us-east-1",
"compliance": {
"autoDelete": false,
"legalHold": false,
"retentionPeriodMinutes": 2629800
},
"s3ObjectLock": {
"enabled": false
}
}
"""
import json
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgOrgContainer(object):
"""
Create, modify and delete StorageGRID Tenant Account
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
name=dict(required=True, type="str"),
region=dict(required=False, type="str"),
compliance=dict(
required=False,
type="dict",
options=dict(
auto_delete=dict(required=False, type="bool"),
legal_hold=dict(required=False, type="bool"),
retention_period_minutes=dict(required=False, type="int"),
),
),
s3_object_lock_enabled=dict(required=False, type="bool"),
bucket_versioning_enabled=dict(required=False, type="bool"),
)
)
parameter_map = {
"auto_delete": "autoDelete",
"legal_hold": "legalHold",
"retention_period_minutes": "retentionPeriodMinutes",
}
self.module = AnsibleModule(
argument_spec=self.argument_spec,
mutually_exclusive=[("compliance", "s3_object_lock_enabled")],
supports_check_mode=True,
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Get API version
self.rest_api.get_sg_product_version(api_root="org")
# Checking for the parameters passed and create new parameters list
self.data_versioning = {}
self.data_versioning["versioningSuspended"] = True
self.data = {}
self.data["name"] = self.parameters["name"]
self.data["region"] = self.parameters.get("region")
if self.parameters.get("compliance"):
self.data["compliance"] = dict(
(parameter_map[k], v) for (k, v) in self.parameters["compliance"].items() if v is not None
)
if self.parameters.get("s3_object_lock_enabled") is not None:
self.rest_api.fail_if_not_sg_minimum_version("S3 Object Lock", 11, 5)
self.data["s3ObjectLock"] = dict(enabled=self.parameters["s3_object_lock_enabled"])
if self.parameters.get("bucket_versioning_enabled") is not None:
self.rest_api.fail_if_not_sg_minimum_version("Bucket versioning configuration", 11, 6)
self.data_versioning["versioningEnabled"] = self.parameters["bucket_versioning_enabled"]
if self.data_versioning["versioningEnabled"]:
self.data_versioning["versioningSuspended"] = False
def get_org_container(self):
# Check if bucket/container exists
# Return info if found, or None
params = {"include": "compliance,region"}
response, error = self.rest_api.get("api/v3/org/containers", params=params)
if error:
self.module.fail_json(msg=error)
for container in response["data"]:
if container["name"] == self.parameters["name"]:
return container
return None
def create_org_container(self):
api = "api/v3/org/containers"
response, error = self.rest_api.post(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def get_org_container_versioning(self):
api = "api/v3/org/containers/%s/versioning" % self.parameters["name"]
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
return response["data"]
def update_org_container_versioning(self):
api = "api/v3/org/containers/%s/versioning" % self.parameters["name"]
response, error = self.rest_api.put(api, self.data_versioning)
if error:
self.module.fail_json(msg=error)
return response["data"]
def fail_if_global_object_lock_disabled(self):
api = "api/v3/org/compliance-global"
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
if not response["data"]["complianceEnabled"]:
self.module.fail_json(msg="Error: Global S3 Object Lock setting is not enabled.")
def update_org_container_compliance(self):
api = "api/v3/org/containers/%s/compliance" % self.parameters["name"]
response, error = self.rest_api.put(api, self.data["compliance"])
if error:
self.module.fail_json(msg=error)
return response["data"]
def delete_org_container(self):
api = "api/v3/org/containers/%s" % self.parameters["name"]
response, error = self.rest_api.delete(api, None)
if error:
self.module.fail_json(msg=error["text"])
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
versioning_config = None
update_versioning = False
org_container = self.get_org_container()
if org_container and self.parameters.get("bucket_versioning_enabled") is not None:
versioning_config = self.get_org_container_versioning()
cd_action = self.na_helper.get_cd_action(org_container, self.parameters)
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
update_compliance = False
if self.parameters.get("compliance") and org_container.get("compliance") != self.data["compliance"]:
update_compliance = True
self.na_helper.changed = True
if (
versioning_config
and versioning_config["versioningEnabled"] != self.data_versioning["versioningEnabled"]
):
update_versioning = True
self.na_helper.changed = True
result_message = ""
resp_data = org_container
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == "delete":
self.delete_org_container()
resp_data = None
result_message = "Org Container deleted"
elif cd_action == "create":
if self.parameters.get("s3_object_lock_enabled"): # if it is set and true
self.fail_if_global_object_lock_disabled()
resp_data = self.create_org_container()
if self.parameters.get("bucket_versioning_enabled") is not None:
self.update_org_container_versioning()
result_message = "Org Container created"
else:
if update_compliance:
resp_data = self.update_org_container_compliance()
if update_versioning:
self.update_org_container_versioning()
result_message = "Org Container updated"
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_org_container = SgOrgContainer()
na_sg_org_container.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,301 @@
#!/usr/bin/python
# (c) 2020, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage tenant Groups"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = """
module: na_sg_org_group
short_description: NetApp StorageGRID manage groups within a tenancy.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '20.6.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Create, Update, Delete Groups within NetApp StorageGRID tenant.
options:
state:
description:
- Whether the specified group should exist or not.
type: str
choices: ['present', 'absent']
default: present
unique_name:
description:
- Unique Name for the group. Must begin with C(group/) or C(federated-group/).
- Required for create, modify or delete operation.
type: str
required: true
display_name:
description:
- Name of the group.
- Required for create operation.
type: str
management_policy:
description:
- Management access controls granted to the group within the tenancy.
type: dict
suboptions:
manage_all_containers:
description:
- Allows users to manage the settings for all S3 buckets in the tenant account, regardless of S3 bucket or group policies.
type: bool
manage_endpoints:
description:
- Allows users to use the Tenant Manager or the Tenant Management API to create or edit endpoints.
- Endpoints are used as the destination for StorageGRID platform services.
type: bool
manage_own_s3_credentials:
description:
- Allows users to create and remove their own S3 access keys.
- Users who do not have this permission do not see the S3 > My Credentials menu option.
type: bool
root_access:
description:
- Provides full access to the Tenant Manager and the Tenant Management API.
type: bool
s3_policy:
description:
- StorageGRID S3 Group Policy.
default: ""
type: json
"""
EXAMPLES = """
- name: create a group
netapp.storagegrid.na_sg_org_group:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
display_name: ansiblegroup1
unique_name: group/ansiblegroup1
management_policy:
manage_all_containers: true
manage_endpoints: true
manage_own_s3_credentials: false
root_access: false
s3_policy: {"Statement":[{"Effect":"Deny","Action":"s3:*","Resource":"arn:aws:s3:::*"}]}
"""
RETURN = """
resp:
description: Returns information about the StorageGRID tenant group attributes.
returned: success
type: dict
sample: {
"displayName": "Example Group",
"policies": {
"management": {
"manageAllContainers": true,
"manageEndpoints": true,
"manageOwnS3Credentials": true,
"rootAccess": true
},
"s3": {...},
"swift": {...}
},
"uniqueName": "group/examplegroup",
"accountId": "12345678901234567890",
"id": "00000000-0000-0000-0000-000000000000",
"federated": false,
"groupURN": "urn:sgws:identity::12345678901234567890:group/examplegroup"
}
"""
import json
import re
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgOrgGroup(object):
"""
Create, modify and delete StorageGRID Tenant Account
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
display_name=dict(required=False, type="str"),
unique_name=dict(required=True, type="str"),
management_policy=dict(
required=False,
type="dict",
options=dict(
manage_all_containers=dict(required=False, type="bool"),
manage_endpoints=dict(required=False, type="bool"),
manage_own_s3_credentials=dict(required=False, type="bool"),
root_access=dict(required=False, type="bool"),
),
),
s3_policy=dict(required=False, type="json"),
)
)
parameter_map = {
"manage_all_containers": "manageAllContainers",
"manage_endpoints": "manageEndpoints",
"manage_own_s3_credentials": "manageOwnS3Credentials",
"root_access": "rootAccess",
}
self.module = AnsibleModule(
argument_spec=self.argument_spec,
# required_if=[("state", "present", ["display_name"])],
supports_check_mode=True,
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = {}
self.data["displayName"] = self.parameters.get("display_name")
self.data["uniqueName"] = self.parameters["unique_name"]
# Only add the parameter if value is True, as JSON response does not include non-true objects
self.data["policies"] = {}
if self.parameters.get("management_policy"):
self.data["policies"] = {
"management": dict(
(parameter_map[k], v) for (k, v) in self.parameters["management_policy"].items() if v
)
}
if not self.data["policies"].get("management"):
self.data["policies"]["management"] = None
if self.parameters.get("s3_policy"):
try:
self.data["policies"]["s3"] = json.loads(self.parameters["s3_policy"])
except ValueError:
self.module.fail_json(msg="Failed to decode s3_policy. Invalid JSON.")
self.re_local_group = re.compile("^group/")
self.re_fed_group = re.compile("^federated-group/")
if (
self.re_local_group.match(self.parameters["unique_name"]) is None
and self.re_fed_group.match(self.parameters["unique_name"]) is None
):
self.module.fail_json(msg="unique_name must begin with 'group/' or 'federated-group/'")
def get_org_group(self, unique_name):
# Use the unique name to check if the group exists
api = "api/v3/org/groups/%s" % unique_name
response, error = self.rest_api.get(api)
if error:
if response["code"] != 404:
self.module.fail_json(msg=error)
else:
return response["data"]
return None
def create_org_group(self):
api = "api/v3/org/groups"
response, error = self.rest_api.post(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def delete_org_group(self, group_id):
api = "api/v3/org/groups/" + group_id
self.data = None
response, error = self.rest_api.delete(api, self.data)
if error:
self.module.fail_json(msg=error)
def update_org_group(self, group_id):
api = "api/v3/org/groups/" + group_id
response, error = self.rest_api.put(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
org_group = self.get_org_group(self.parameters["unique_name"])
cd_action = self.na_helper.get_cd_action(org_group, self.parameters)
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
update = False
if self.parameters.get("management_policy"):
if org_group.get("policies") is None or org_group.get("policies", {}).get("management") != self.data["policies"]["management"]:
update = True
if self.parameters.get("s3_policy"):
if org_group.get("policies") is None or org_group.get("policies", {}).get("s3") != self.data["policies"]["s3"]:
update = True
if update:
self.na_helper.changed = True
result_message = ""
resp_data = org_group
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == "delete":
self.delete_org_group(org_group["id"])
result_message = "Org Group deleted"
elif cd_action == "create":
resp_data = self.create_org_group()
result_message = "Org Group created"
else:
# for a federated group, the displayName parameter needs to be specified
# and must match the existing displayName
if self.re_fed_group.match(self.parameters["unique_name"]):
self.data["displayName"] = org_group["displayName"]
resp_data = self.update_org_group(org_group["id"])
result_message = "Org Group updated"
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_org_group = SgOrgGroup()
na_sg_org_group.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,335 @@
#!/usr/bin/python
# (c) 2021, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Tenant Identity Federation"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: na_sg_org_identity_federation
short_description: NetApp StorageGRID manage Tenant identity federation.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '21.6.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Configure Tenant Identity Federation within NetApp StorageGRID.
- If module is run with C(check_mode), a connectivity test will be performed using the supplied values without changing the configuration.
- This module is idempotent if I(password) is not specified.
options:
state:
description:
- Whether identity federation should be enabled or not.
type: str
choices: ['present', 'absent']
default: present
username:
description:
- The username to bind to the LDAP server.
type: str
password:
description:
- The password associated with the username.
type: str
hostname:
description:
- The hostname or IP address of the LDAP server.
type: str
port:
description:
- The port used to connect to the LDAP server. Typically 389 for LDAP, or 636 for LDAPS.
type: int
base_group_dn:
description:
- The Distinguished Name of the LDAP subtree to search for groups.
type: str
base_user_dn:
description:
- The Distinguished Name of the LDAP subtree to search for users.
type: str
ldap_service_type:
description:
- The type of LDAP server.
choices: ['Active Directory', 'OpenLDAP', 'Other']
type: str
type:
description:
- The type of identity source.
- Default is 'ldap'.
type: str
default: ldap
ldap_user_id_attribute:
description:
- The LDAP attribute which contains the unique user name of a user.
- Should be configured if I(ldap_service_type=Other).
type: str
ldap_user_uuid_attribute:
description:
- The LDAP attribute which contains the permanent unique identity of a user.
- Should be configured if I(ldap_service_type=Other).
type: str
ldap_group_id_attribute:
description:
- The LDAP attribute which contains the group for a user.
- Should be configured if I(ldap_service_type=Other).
type: str
ldap_group_uuid_attribute:
description:
- The LDAP attribute which contains the group's permanent unique identity.
- Should be configured if I(ldap_service_type=Other).
type: str
tls:
description:
- Whether Transport Layer Security is used to connect to the LDAP server.
choices: ['STARTTLS', 'LDAPS', 'Disabled']
type: str
default: STARTTLS
ca_cert:
description:
- Custom certificate used to connect to the LDAP server.
- If a custom certificate is not supplied, the operating system CA certificate will be used.
type: str
"""
EXAMPLES = """
- name: test identity federation configuration
netapp.storagegrid.na_sg_org_identity_federation:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
ldap_service_type: "Active Directory"
hostname: "ad.example.com"
port: 389
username: "binduser"
password: "bindpass"
base_group_dn: "DC=example,DC=com"
base_user_dn: "DC=example,DC=com"
tls: "Disabled"
check_mode: yes
- name: configure identity federation with AD and TLS
netapp.storagegrid.na_sg_org_identity_federation:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
ldap_service_type: "Active Directory"
hostname: "ad.example.com"
port: 636,
username: "binduser"
password: "bindpass"
base_group_dn: "DC=example,DC=com"
base_user_dn: "DC=example,DC=com"
tls: "LDAPS"
ca_cert: |
-----BEGIN CERTIFICATE-----
MIIC+jCCAeICCQDmn9Gow08LTzANBgkqhkiG9w0BAQsFADA/..swCQYDVQQGEwJV
bXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB..JFzNIXQEGnsgjV
JGU4giuvOLOZ8Q3gyuUbkSUQDjmjpMR8PliwJ6iW2Ity89Dv..dl1TaIYI/ansyZ
Uxk4YXeN6kUkrDtNxCg1McALzXVAfxMTtj2SFlLxne4Z6rX2..UyftQrfM13F1vY
gK8dBPz+l+X/Uozo/xNm7gxe68p9le9/pcULst1CQn5/sPqq..kgWcSvlKUItu82
lq3B2169rovdIaNdcvaQjMPhrDGo5rvLfMN35U3Hgbz41PL5..x2BcUE6/0ab5T4
qKBxKa3t9twj+zpUqOzyL0PFfCE+SK5fEXAS1ow4eAcLN+eB..gR/PuvGAyIPCtE
1+X4GrECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAFpO+04Ra..FMJPH6dBmzfb7l
k04BWTvSlur6HiQdXY+oFQMJZzyI7MQ8v9HBIzS0ZAzYWLp4..VZhHmRxnrWyxVs
u783V5YfQH2L4QnBDoiDefgxyfDs2PcoF5C+X9CGXmPqzst2..y/6tdOVJzdiA==
-----END CERTIFICATE-----
"""
RETURN = """
resp:
description: Returns information about the StorageGRID tenant account identity source configuration.
returned: success
type: dict
sample: {
"id": "00000000-0000-0000-0000-000000000000",
"disable": false,
"hostname": "10.1.2.3",
"port": 389,
"username": "MYDOMAIN\\\\Administrator",
"password": "********",
"baseGroupDn": "DC=example,DC=com",
"baseUserDn": "DC=example,DC=com",
"ldapServiceType": "Active Directory",
"type": "ldap",
"disableTLS": false,
"enableLDAPS": false,
"caCert": "-----BEGIN CERTIFICATE----- abcdefghijkl123456780ABCDEFGHIJKL 123456/7890ABCDEFabcdefghijklABCD -----END CERTIFICATE-----\n"
}
"""
import json
import re
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgOrgIdentityFederation:
"""
Configure and modify StorageGRID Tenant Identity Federation
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
username=dict(required=False, type="str"),
password=dict(required=False, type="str", no_log=True),
hostname=dict(required=False, type="str"),
port=dict(required=False, type="int"),
base_group_dn=dict(required=False, type="str"),
base_user_dn=dict(required=False, type="str"),
ldap_service_type=dict(required=False, type="str", choices=["OpenLDAP", "Active Directory", "Other"]),
type=dict(required=False, type="str", default="ldap"),
ldap_user_id_attribute=dict(required=False, type="str"),
ldap_user_uuid_attribute=dict(required=False, type="str"),
ldap_group_id_attribute=dict(required=False, type="str"),
ldap_group_uuid_attribute=dict(required=False, type="str"),
tls=dict(required=False, type="str", choices=["STARTTLS", "LDAPS", "Disabled"], default="STARTTLS"),
ca_cert=dict(required=False, type="str"),
),
)
parameter_map = {
"username": "username",
"password": "password",
"hostname": "hostname",
"port": "port",
"base_group_dn": "baseGroupDn",
"base_user_dn": "baseUserDn",
"ldap_service_type": "ldapServiceType",
"ldap_user_id_attribute": "ldapUserIdAttribute",
"ldap_user_uuid_attribute": "ldapUserUUIDAttribute",
"ldap_group_id_attribute": "ldapGroupIdAttribute",
"ldap_group_uuid_attribute": "ldapGroupUUIDAttribute",
"ca_cert": "caCert",
}
self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True,)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = {}
if self.parameters["state"] == "present":
self.data["disable"] = False
for k in parameter_map.keys():
if self.parameters.get(k) is not None:
self.data[parameter_map[k]] = self.parameters[k]
if self.parameters.get("tls") == "STARTTLS":
self.data["disableTLS"] = False
self.data["enableLDAPS"] = False
elif self.parameters.get("tls") == "LDAPS":
self.data["disableTLS"] = False
self.data["enableLDAPS"] = True
else:
self.data["disableTLS"] = True
self.data["enableLDAPS"] = False
def get_org_identity_source(self):
api = "api/v3/org/identity-source"
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
else:
return response["data"]
return None
def update_identity_federation(self, test=False):
api = "api/v3/org/identity-source"
params = {}
if test:
params["test"] = True
response, error = self.rest_api.put(api, self.data, params=params)
if error:
self.module.fail_json(msg=error, payload=self.data)
if response is not None:
return response["data"]
else:
return None
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
org_identity_source = self.get_org_identity_source()
cd_action = self.na_helper.get_cd_action(org_identity_source, self.parameters)
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
update = False
for k in (i for i in self.data.keys() if i != "password"):
if self.data[k] != org_identity_source.get(k):
update = True
break
# if a password has been specified we need to update it
if self.data.get("password") and self.parameters["state"] == "present":
update = True
self.module.warn("Password attribute has been specified. Task is not idempotent.")
if update:
self.na_helper.changed = True
if cd_action == "delete":
# if identity federation is already in a disable state
if org_identity_source.get("disable"):
self.na_helper.changed = False
result_message = ""
resp_data = org_identity_source
if self.na_helper.changed and not self.module.check_mode:
if cd_action == "delete":
self.data = dict(disable=True)
resp_data = self.update_identity_federation()
result_message = "Tenant identity federation disabled"
else:
resp_data = self.update_identity_federation()
result_message = "Tenant identity federation updated"
if self.module.check_mode:
self.update_identity_federation(test=True)
# if no error, connection test successful
self.module.exit_json(changed=self.na_helper.changed, msg="Connection test successful")
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_org_identity_federation = SgOrgIdentityFederation()
na_sg_org_identity_federation.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,279 @@
#!/usr/bin/python
# (c) 2020, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
""" NetApp StorageGRID Org Info using REST APIs """
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
module: na_sg_org_info
author: NetApp Ansible Team (@jasonl4) <ng-ansibleteam@netapp.com>
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
short_description: NetApp StorageGRID Org information gatherer.
description:
- This module allows you to gather various information about StorageGRID Org configuration.
version_added: 20.11.0
options:
gather_subset:
type: list
elements: str
description:
- When supplied, this argument will restrict the information collected to a given subset.
- Either the info name or the Rest API can be given.
- Possible values for this argument include
- C(org_compliance_global_info) or C(org/compliance-global)
- C(org_config_info) or C(org/config)
- C(org_config_product_version_info) or C(org/config/product-version)
- C(org_containers_info) or C(org/containers)
- C(org_deactivated_features_info) or C(org/deactivated-features)
- C(org_endpoints_info) or C(org/endpoints)
- C(org_groups_info) or C(org/groups)
- C(org_identity_source_info) or C(org/identity-source)
- C(org_regions_info) or C(org/regions)
- C(org_users_current_user_s3_access_keys_info) or C(org/users/current-user/s3-access-keys)
- C(org_usage_info) or C(org/usage)
- C(org_users_info) or C(org/users)
- C(org_users_root_info) or C(org/users/root)
- C(versions_info) or C(versions)
- Can specify a list of values to include a larger subset.
default: "all"
parameters:
description:
- Allows for any rest option to be passed in.
type: dict
"""
EXAMPLES = """
- name: Gather StorageGRID Org info
netapp.storagegrid.na_sg_org_info:
api_url: "https://1.2.3.4/"
auth_token: "storagegrid-auth-token"
validate_certs: false
register: sg_org_info
- name: Gather StorageGRID Org info for org/containers and org/config subsets
netapp.storagegrid.na_sg_org_info:
api_url: "https://1.2.3.4/"
auth_token: "storagegrid-auth-token"
validate_certs: false
gather_subset:
- org_containers_info
- org/config
register: sg_org_info
- name: Gather StorageGRID Org info for all subsets
netapp.storagegrid.na_sg_org_info:
api_url: "https://1.2.3.4/"
auth_token: "storagegrid-auth-token"
validate_certs: false
gather_subset:
- all
register: sg_org_info
- name: Gather StorageGRID Org info for org/containers and org/users subsets, limit to 5 results for each subset
netapp.storagegrid.na_sg_org_info:
api_url: "https://1.2.3.4/"
auth_token: "storagegrid-auth-token"
validate_certs: false
gather_subset:
- org/containers
- org/users
parameters:
limit: 5
register: sg_org_info
"""
RETURN = """
sg_info:
description: Returns various information about the StorageGRID Grid configuration.
returned: always
type: dict
sample: {
"org/compliance-global": {...},
"org/config": {...},
"org/config/product-version": {...},
"org/containers": {...},
"org/deactivated-features": {...},
"org/endpoints": {...},
"org/groups": {...},
"org/identity-source": {...},
"org/regions": {...},
"org/users/current-user/s3-access-keys": {...},
"org/usage": {...},
"org/users": {...},
"org/users/root": {...},
"org/versions": {...}
}
"""
from ansible.module_utils.basic import AnsibleModule
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class NetAppSgGatherInfo(object):
""" Class with gather info methods """
def __init__(self):
"""
Parse arguments, setup variables, check parameters and ensure
request module is installed.
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(dict(
gather_subset=dict(default=['all'], type='list', elements='str', required=False),
parameters=dict(type='dict', required=False)
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
# set up variables
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
self.rest_api = SGRestAPI(self.module)
def get_subset_info(self, gather_subset_info):
"""
Gather StorageGRID information for the given subset using REST APIs
Input for REST APIs call : (api, data)
return gathered_sg_info
"""
api = gather_subset_info['api_call']
data = {}
# allow for passing in any additional rest api parameters
if self.parameters.get('parameters'):
for each in self.parameters['parameters']:
data[each] = self.parameters['parameters'][each]
gathered_sg_info, error = self.rest_api.get(api, data)
if error:
self.module.fail_json(msg=error)
else:
return gathered_sg_info
return None
def convert_subsets(self):
""" Convert an info to the REST API """
info_to_rest_mapping = {
'org_compliance_global_info': 'org/compliance-global',
'org_config_info': 'org/config',
'org_config_product_version_info': 'org/config/product-version',
'org_containers_info': 'org/containers',
'org_deactivated_features_info': 'org/deactivated-features',
'org_endpoints_info': 'org/endpoints',
'org_groups_info': 'org/groups',
'org_identity_source_info': 'org/identity-source',
'org_regions_info': 'org/regions',
'org_users_current_user_s3_access_keys_info': 'org/users/current-user/s3-access-keys',
'org_usage_info': 'org/usage',
'org_users_info': 'org/users',
'org_users_root_info': 'org/users/root',
'versions_info': 'versions'
}
# Add rest API names as there info version, also make sure we don't add a duplicate
subsets = []
for subset in self.parameters['gather_subset']:
if subset in info_to_rest_mapping:
if info_to_rest_mapping[subset] not in subsets:
subsets.append(info_to_rest_mapping[subset])
else:
if subset not in subsets:
subsets.append(subset)
return subsets
def apply(self):
""" Perform pre-checks, call functions and exit """
result_message = dict()
# Defining gather_subset and appropriate api_call
get_sg_subset_info = {
'org/compliance-global': {
'api_call': 'api/v3/org/compliance-global',
},
'org/config': {
'api_call': 'api/v3/org/config',
},
'org/config/product-version': {
'api_call': 'api/v3/org/config/product-version',
},
'org/containers': {
'api_call': 'api/v3/org/containers',
},
'org/deactivated-features': {
'api_call': 'api/v3/org/deactivated-features',
},
'org/endpoints': {
'api_call': 'api/v3/org/endpoints',
},
'org/groups': {
'api_call': 'api/v3/org/groups',
},
'org/identity-source': {
'api_call': 'api/v3/org/identity-source',
},
'org/regions': {
'api_call': 'api/v3/org/regions',
},
'org/users/current-user/s3-access-keys': {
'api_call': 'api/v3/org/users/current-user/s3-access-keys',
},
'org/usage': {
'api_call': 'api/v3/org/usage',
},
'org/users': {
'api_call': 'api/v3/org/users',
},
'org/users/root': {
'api_call': 'api/v3/org/users/root',
},
'versions': {
'api_call': 'api/v3/versions',
},
}
if 'all' in self.parameters['gather_subset']:
# If all in subset list, get the information of all subsets
self.parameters['gather_subset'] = sorted(get_sg_subset_info.keys())
converted_subsets = self.convert_subsets()
for subset in converted_subsets:
try:
# Verify whether the supported subset passed
specified_subset = get_sg_subset_info[subset]
except KeyError:
self.module.fail_json(msg="Specified subset %s not found, supported subsets are %s" %
(subset, list(get_sg_subset_info.keys())))
result_message[subset] = self.get_subset_info(specified_subset)
self.module.exit_json(changed='False', sg_info=result_message)
def main():
""" Main function """
obj = NetAppSgGatherInfo()
obj.apply()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,335 @@
#!/usr/bin/python
# (c) 2020, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage Tenant Users"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = """
module: na_sg_org_user
short_description: NetApp StorageGRID manage users within a tenancy.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '20.6.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Create, Update, Delete Users within a NetApp StorageGRID tenant.
options:
state:
description:
- Whether the specified user should exist or not.
type: str
choices: ['present', 'absent']
default: present
full_name:
description:
- Full Name of the user.
- Required for create operation
type: str
unique_name:
description:
- Unique Name for the user. Must begin with C(user/) or C(federated-user/).
- Required for create, modify or delete operation.
type: str
required: true
member_of:
description:
- List of unique_groups that the user is a member of.
type: list
elements: str
password:
description:
- Set a password for a local user. Does not apply to federated users.
- Requires root privilege.
required: false
type: str
update_password:
description:
- Choose when to update the password.
- When set to C(always), the password will always be updated.
- When set to C(on_create), the password will only be set upon a new user creation.
default: on_create
choices:
- on_create
- always
type: str
disable:
description:
- Disable the user from signing in. Does not apply to federated users.
type: bool
"""
EXAMPLES = """
- name: create a tenant user
netapp.storagegrid.na_sg_org_user:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
full_name: ansibleuser1
unique_name: user/ansibleuser1
member_of: "group/ansiblegroup1"
disable: false
"""
RETURN = """
resp:
description: Returns information about the StorageGRID tenant user.
returned: always
type: dict
sample: {
"fullName": "Example User",
"memberOf": ["00000000-0000-0000-0000-000000000000"],
"disable": false,
"uniqueName": "user/Example",
"accountId": "0",
"id": "00000000-0000-0000-0000-000000000000",
"federated": false,
"userURN": "urn:sgws:identity::0:user/Example"
}
"""
import json
import re
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import (
NetAppModule,
)
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgOrgUser(object):
"""
Create, modify and delete user within a StorageGRID Tenant Account
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
full_name=dict(required=False, type="str"),
unique_name=dict(required=True, type="str"),
member_of=dict(required=False, type="list", elements="str"),
disable=dict(required=False, type="bool"),
password=dict(required=False, type="str", no_log=True),
update_password=dict(
default="on_create", choices=["on_create", "always"]
),
)
)
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[("state", "present", ["full_name", "unique_name"])],
supports_check_mode=True,
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = {}
self.data["memberOf"] = []
if self.parameters.get("full_name"):
self.data["fullName"] = self.parameters["full_name"]
if self.parameters.get("unique_name"):
self.data["uniqueName"] = self.parameters["unique_name"]
if self.parameters.get("disable") is not None:
self.data["disable"] = self.parameters["disable"]
re_local_user = re.compile("^user/")
re_fed_user = re.compile("^federated-user/")
if (
re_local_user.match(self.parameters["unique_name"]) is None
and re_fed_user.match(self.parameters["unique_name"]) is None
):
self.module.fail_json(
msg="unique_name must begin with 'user/' or 'federated-user/'"
)
self.pw_change = {}
if self.parameters.get("password") is not None:
if re_fed_user.match(self.parameters["unique_name"]):
self.module.fail_json(msg="password cannot be set for a federated user")
self.pw_change["password"] = self.parameters["password"]
def get_org_groups(self):
# Get list of groups
# Retrun mapping of uniqueName to ids if found, or None
api = "api/v3/org/groups?limit=350"
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
if response["data"]:
name_to_id_map = dict(
zip(
[i["uniqueName"] for i in response["data"]],
[j["id"] for j in response["data"]],
)
)
return name_to_id_map
return None
def get_org_user(self, unique_name):
# Use the unique name to check if the user exists
api = "api/v3/org/users/%s" % unique_name
response, error = self.rest_api.get(api)
if error:
if response["code"] != 404:
self.module.fail_json(msg=error)
else:
return response["data"]
return None
def create_org_user(self):
api = "api/v3/org/users"
response, error = self.rest_api.post(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def delete_org_user(self, user_id):
api = "api/v3/org/users/" + user_id
self.data = None
response, error = self.rest_api.delete(api, self.data)
if error:
self.module.fail_json(msg=error)
def update_org_user(self, user_id):
api = "api/v3/org/users/" + user_id
response, error = self.rest_api.put(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def set_org_user_password(self, unique_name):
api = "api/v3/org/users/%s/change-password" % unique_name
response, error = self.rest_api.post(api, self.pw_change)
if error:
self.module.fail_json(msg=error["text"])
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
org_user = self.get_org_user(self.parameters["unique_name"])
if self.parameters.get("member_of"):
org_groups = self.get_org_groups()
try:
self.data["memberOf"] = [
org_groups[x] for x in self.parameters["member_of"]
]
except KeyError as e:
self.module.fail_json(
msg="Invalid unique_group supplied: '%s' not found" % e.args[0]
)
cd_action = self.na_helper.get_cd_action(org_user, self.parameters)
if cd_action is None and self.parameters["state"] == "present":
# let's see if we need to update parameters
update = False
if org_user["memberOf"] is None:
member_of_diff = []
else:
member_of_diff = [
i
for i in self.data["memberOf"] + org_user["memberOf"]
if i not in self.data["memberOf"] or i not in org_user["memberOf"]
]
if member_of_diff:
update = True
if self.parameters.get("disable") is not None and self.parameters[
"disable"
] != org_user.get("disable"):
update = True
if update:
self.na_helper.changed = True
result_message = ""
resp_data = org_user
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == "delete":
self.delete_org_user(org_user["id"])
result_message = "Org User deleted"
elif cd_action == "create":
resp_data = self.create_org_user()
result_message = "Org User created"
else:
resp_data = self.update_org_user(org_user["id"])
result_message = "Org User updated"
# If a password has been set
if self.pw_change:
if self.module.check_mode:
pass
else:
# Only update the password if update_password is always, or a create activity has occurred
if cd_action == "create" or self.parameters["update_password"] == "always":
self.set_org_user_password(self.parameters["unique_name"])
self.na_helper.changed = True
results = [result_message, "Org User password updated"]
result_message = "; ".join(filter(None, results))
self.module.exit_json(
changed=self.na_helper.changed, msg=result_message, resp=resp_data
)
def main():
"""
Main function
"""
na_sg_org_user = SgOrgUser()
na_sg_org_user.apply()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,210 @@
#!/usr/bin/python
# (c) 2020, NetApp Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""NetApp StorageGRID - Manage User S3 keys"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = """
module: na_sg_org_user_s3_key
short_description: Creates NetApp StorageGRID User S3 keys.
extends_documentation_fragment:
- netapp.storagegrid.netapp.sg
version_added: '20.6.0'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Create, Delete Users S3 keys on NetApp StorageGRID.
options:
state:
description:
- Whether the specified account should exist or not.
type: str
choices: ['present', 'absent']
default: present
unique_user_name:
description:
- Unique user name owning the S3 Key.
required: true
type: str
expires:
description:
- Date-Time string for the key to expire.
type: str
access_key:
description:
- Access Key or S3 credential pair identifier.
- Required for delete operation.
type: str
"""
EXAMPLES = """
- name: create a s3 key
netapp.storagegrid.na_sg_org_user_s3_key:
api_url: "https://<storagegrid-endpoint-url>"
auth_token: "storagegrid-auth-token"
validate_certs: false
state: present
unique_user_name: user/ansibleuser1
"""
RETURN = """
resp:
description: Returns information about an S3 access key for the user.
returned: always
type: dict
sample: {
"id": "abcABC_01234-0123456789abcABCabc0123456789==",
"accountId": 12345678901234567000,
"displayName": "****************AB12",
"userURN": "urn:sgws:identity::12345678901234567000:root",
"userUUID": "00000000-0000-0000-0000-000000000000",
"expires": "2020-09-04T00:00:00.000Z"
}
"""
import json
import ansible_collections.netapp.storagegrid.plugins.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.storagegrid.plugins.module_utils.netapp import SGRestAPI
class SgOrgUserS3Key(object):
"""
Create, modify and delete StorageGRID Tenant Account
"""
def __init__(self):
"""
Parse arguments, setup state variables,
check parameters and ensure request module is installed
"""
self.argument_spec = netapp_utils.na_storagegrid_host_argument_spec()
self.argument_spec.update(
dict(
state=dict(required=False, type="str", choices=["present", "absent"], default="present"),
unique_user_name=dict(required=True, type="str"),
expires=dict(required=False, type="str"),
access_key=dict(required=False, type="str", no_log=False),
)
)
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[("state", "absent", ["access_key"])],
supports_check_mode=False,
)
self.na_helper = NetAppModule()
# set up state variables
self.parameters = self.na_helper.set_parameters(self.module.params)
# Calling generic SG rest_api class
self.rest_api = SGRestAPI(self.module)
# Checking for the parameters passed and create new parameters list
self.data = {}
self.data["expires"] = self.parameters.get("expires")
def get_org_user_id(self, unique_name):
# Use the unique name to check if the user exists
api = "api/v3/org/users/%s" % unique_name
response, error = self.rest_api.get(api)
if error:
if response["code"] != 404:
self.module.fail_json(msg=error)
else:
return response["data"]["id"]
return None
def get_org_user_s3_key(self, user_id, access_key):
# Use the unique name to check if the user exists
api = "api/v3/org/users/current-user/s3-access-keys/%s" % access_key
if user_id:
api = "api/v3/org/users/%s/s3-access-keys/%s" % (user_id, access_key,)
response, error = self.rest_api.get(api)
if error:
self.module.fail_json(msg=error)
else:
return response["data"]
return None
def create_org_user_s3_key(self, user_id):
api = "api/v3/org/users/current-user/s3-access-keys"
if user_id:
api = "api/v3/org/users/%s/s3-access-keys" % user_id
response, error = self.rest_api.post(api, self.data)
if error:
self.module.fail_json(msg=error)
return response["data"]
def delete_org_user_s3_key(self, user_id, access_key):
api = "api/v3/org/users/current-user/s3-access-keys"
if user_id:
api = "api/v3/org/users/%s/s3-access-keys/%s" % (user_id, access_key,)
self.data = None
response, error = self.rest_api.delete(api, self.data)
if error:
self.module.fail_json(msg=error)
def apply(self):
"""
Perform pre-checks, call functions and exit
"""
result_message = ""
resp_data = {}
user_id = None
if self.parameters.get("unique_user_name"):
user_id = self.get_org_user_id(self.parameters["unique_user_name"])
if self.parameters["state"] == "present":
org_user_s3_key = None
if self.parameters.get("access_key"):
org_user_s3_key = self.get_org_user_s3_key(user_id, self.parameters["access_key"])
resp_data = org_user_s3_key
if not org_user_s3_key: # create
resp_data = self.create_org_user_s3_key(user_id)
self.na_helper.changed = True
if self.parameters["state"] == "absent":
self.delete_org_user_s3_key(user_id, self.parameters["access_key"])
self.na_helper.changed = True
result_message = "Org User S3 key deleted"
self.module.exit_json(changed=self.na_helper.changed, msg=result_message, resp=resp_data)
def main():
"""
Main function
"""
na_sg_org_user_s3_key = SgOrgUserS3Key()
na_sg_org_user_s3_key.apply()
if __name__ == "__main__":
main()