File salt-formulas-3.0.4.obscpio of Package infrastructure-formulas
07070100000000000081A400000000000000000000000168EB80BB0000003A000000000000000000000000000000000000001F00000000salt-formulas-3.0.4/.gitignore.vagrant/
__pycache__/
.scullery_*
*.egg-info
dist/
venv/
07070100000001000081A400000000000000000000000168EB80BB0000894D000000000000000000000000000000000000001C00000000salt-formulas-3.0.4/COPYING GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://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 <https://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
<https://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
<https://www.gnu.org/licenses/why-not-lgpl.html>.
07070100000002000081A400000000000000000000000168EB80BB000002B8000000000000000000000000000000000000001E00000000salt-formulas-3.0.4/README.mdThis repository houses Salt states used in the openSUSE and SUSE infrastructures.
Most of the code is specific to SUSE based operating systems and gets tested on the latest openSUSE Leap or Tumbleweed.
These formulas can be installed using the `infrastructure-formulas` package which is currently available in the `isv:SUSEInfra:Tools` and `openSUSE:infrastructure` projects and provides subpackages for each formula. Of course, any other installation routine for Git based Salt states can be applied as well.
Files are licensed under the GNU General Public License v3 unless a designated COPYING, LICENCE or LICENSE file in the respective formulas' subdirectory declares a different license.
07070100000003000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002900000000salt-formulas-3.0.4/apache_httpd-formula07070100000004000081A400000000000000000000000168EB80BB00000129000000000000000000000000000000000000003300000000salt-formulas-3.0.4/apache_httpd-formula/README.md# Salt states for the Apache HTTP server
## Available states
`apache_httpd`
Installs and configures `apache2`.
`apache_httpd.purge`
Removes configuration files neither managed by RPM packages, nor by Salt.
Included in `apache_httpd` by default, unless `apache_httpd:purge` is set to `False`.
07070100000005000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003600000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd07070100000006000081A400000000000000000000000168EB80BB00000243000000000000000000000000000000000000004400000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/defaults.json{
"directories": {
"base": "/etc/apache2",
"configs": "/etc/apache2/conf.d",
"vhosts": "/etc/apache2/vhosts.d",
"logs": "/var/log/apache2",
"htdocs": "/srv/www/htdocs"
},
"purge": true,
"sysconfig": {
"APACHE_CONF_INCLUDE_FILES": "",
"APACHE_CONF_INCLUDE_DIRS": "",
"APACHE_SERVER_FLAGS": "",
"APACHE_HTTPD_CONF": "",
"APACHE_MPM": "event",
"APACHE_SERVERADMIN": "",
"APACHE_SERVERNAME": ""
},
"internal": {
"repetitive_options": [
"Alias",
"RemoteIPTrustedProxy",
"RewriteCond",
"RewriteMap",
"RewriteRule",
"SetEnvIf"
]
}
}
07070100000007000081A400000000000000000000000168EB80BB00000FB8000000000000000000000000000000000000003F00000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/init.sls{#-
Salt state file for managing the Apache HTTP server on openSUSE and SLES
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'apache_httpd/map.jinja' import httpd, modules, mpm, places, sysconfig, cmd_kwargs -%}
{%- from 'apache_httpd/macros.jinja' import config_file_common, watch_in_restart -%}
{%- if salt['file.access']('/usr/sbin/a2enmod', 'x') %}
{%- set have_a2enmod = True %}
{%- set try_a2enmod = True %}
{%- else %}
{%- set have_a2enmod = False %}
{#- if apache2 is not yet installed, states using a2enmod fail in test mode #}
{%- set try_a2enmod = not opts['test'] %}
{%- endif %}
include:
- .packages
- .services
{%- if httpd.purge %}
- .purge
{%- endif %}
apache_httpd_sysconfig:
suse_sysconfig.sysconfig:
- name: apache2
- header_pillar: managed_by_salt_formula_sysconfig
- key_values:
{%- for key, value in sysconfig.items() %}
{{ key }}: '"{{ value }}"'
{%- endfor %}
- require:
- pkg: apache_httpd_packages
- watch_in:
- service: apache_httpd_service
{%- if try_a2enmod %}
{%- for module in modules %}
apache_httpd_load_module-{{ module }}:
module.run:
- apache.a2enmod:
- mod: {{ module }}
- require:
- pkg: apache_httpd_packages
- require_in:
{%- for place in places %}
{%- if httpd.get(place) %}
- file: apache_httpd_{{ place }}
{%- endif %}
{%- endfor %}
- unless:
- fun: apache.check_mod_enabled
mod: {{ module }}
{{ watch_in_restart() }}
{%- endfor %} {#- close configured modules loop #}
{%- endif %} {#- close a2enmod check #}
{%- if have_a2enmod %}
{%- for module in salt['cmd.run_stdout']('/usr/sbin/a2enmod -l', **cmd_kwargs).split() %}
{%- if module not in modules %}
apache_httpd_unload_module-{{ module }}:
module.run:
- apache.a2dismod:
- mod: {{ module }}
- require_in:
{%- for place in places %}
{%- if httpd.get(place) %}
- file: apache_httpd_{{ place }}
{%- endif %}
{%- endfor %}
{{ watch_in_restart() }}
{%- endif %}
{%- endfor %} {#- close enabled modules loop #}
{%- endif %} {#- close a2enmod check #}
apache_httpd_listen:
file.managed:
- name: {{ httpd.directories['base'] }}/listen.conf
- source: salt://apache_httpd/templates/listen_config.jinja
{{ config_file_common() }}
- require:
- pkg: apache_httpd_packages
- watch_in:
- service: apache_httpd_service
{%- for place in places %}
{%- set directory = httpd.directories[place] %}
{%- set config_pillar = httpd.get(place, {}) %}
{%- if config_pillar %}
apache_httpd_{{ place }}:
file.managed:
- names:
{%- for config in config_pillar.keys() %}
- {{ directory }}/{{ config }}.conf:
- context:
name: {{ config }}
type: {{ place }}
repetitive_options: {{ httpd.internal.repetitive_options }}
logdir: {{ httpd.directories.logs }}
wwwdir: {{ httpd.directories.htdocs }}
{%- endfor %}
- source: salt://apache_httpd/templates/config.jinja
{{ config_file_common() }}
- require:
- pkg: apache_httpd_packages
- watch_in:
- service: apache_httpd_service
{%- endif %} {#- close pillar check #}
{%- endfor %} {#- close places loop #}
07070100000008000081A400000000000000000000000168EB80BB000008C5000000000000000000000000000000000000004300000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/macros.jinja{#-
Jinja macros file for Apache HTTP server Salt states
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'apache_httpd/map.jinja' import cmd_kwargs -%}
{%- macro config_file_common() %}
- template: jinja
- check_cmd: apachectl -tf
{#- setting this tmp_dir is marginally better than the default - the minion users home directory (usually /root/)
a mktemp like directory would be ideal, but creating one using modules.temp in Jinja would leave it behind after the state run
#}
- tmp_dir: /dev/shm
{%- endmacro %}
{#- no native is-active implementation in modules.systemd_service,
this was deemed better than reading the (potentially big) service.get_running list
#}
{%- if salt['cmd.retcode']('/usr/bin/systemctl is-active apache2', **cmd_kwargs) == 0 %}
{%- macro watch_in_restart() %}
- watch_in:
- module: apache_httpd_service_restart
{%- endmacro %}
{%- else %}
{%- macro watch_in_restart() %}
{#- noop #}
{%- endmacro %}
{%- endif %}
{%- macro expandOrJoin(option, values, repetitive, indent_i) -%}
{%- if repetitive and values[0] is not mapping -%}
{%- for value in values %}
{{ ( option ~ ' ' ~ value ) | indent(indent_i, True) }}
{%- endfor %}
{%- else -%}
{%- if values[0] is mapping -%}
{%- for entry in values -%}
{%- for k, v in entry.items() %}
{{ ( option ~ ' ' ~ k ~ ' ' ~ v ) | indent(indent_i, True) }}
{%- endfor %}
{%- endfor -%}
{%- else %}
{{ ( option ~ ' ' ~ ' '.join(values) ) | indent(indent_i, True) }}
{%- endif -%}
{%- endif -%}
{%- endmacro -%}
07070100000009000081A400000000000000000000000168EB80BB00000A4C000000000000000000000000000000000000004000000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/map.jinja{#-
Jinja variables file for Apache HTTP server Salt states
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set base = 'apache_httpd' -%}
{%- import_json base ~ '/defaults.json' as defaults -%}
{%- set httpd = salt.pillar.get('apache_httpd', default=defaults, merge=True) -%}
{%- set places = ['configs', 'vhosts'] -%}
{%- import_json base ~ '/modules/os/' ~ grains.oscodename.replace(' ', '_') ~ '.json' as modules_os -%}
{%- import_json base ~ '/modules/map.json' as modules_map -%}
{%- set sysconfig = httpd.sysconfig -%}
{%- set mpm = sysconfig.APACHE_MPM -%}
{%- do httpd.internal.update(
{
'modules': {
'base': modules_os.get('base', {}),
'default': modules_os.get('default', {}),
'map': modules_map
}
}
) -%}
{%- do salt.log.debug('apache_httpd internal: ' ~ httpd.internal) -%}
{%- set modules = httpd.get('modules', []) + httpd.internal.modules.default -%}
{%- set options = [] %}
{%- for place in places %}
{%- for config, settings in httpd.get(place, {}).items() %}
{%- for option, low_settings in settings.items() %}
{%- if option not in options %}
{%- do options.append(option) %}
{%- endif %}
{%- if low_settings is mapping %}
{%- for low_option in low_settings.keys() %}
{%- if low_option not in options %}
{%- do options.append(low_option) %}
{%- endif %}
{%- endfor %}
{%- endif %}
{%- endfor %}
{%- endfor %}
{%- endfor %}
{%- for option in options %}
{%- for module_option, module in httpd.internal.modules.map.items() %}
{%- if option == module_option and module not in modules %}
{%- do modules.append(module) %}
{%- endif %}
{%- endfor %}
{%- endfor %}
{%- do salt.log.debug('apache_httpd modules: ' ~ modules) -%}
{%- set cmd_kwargs = {
'clean_env': True,
'ignore_retcode': True,
'python_shell': False,
'shell': '/bin/sh',
}
-%}
0707010000000A000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003E00000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/modules0707010000000B000081A400000000000000000000000168EB80BB0000014B000000000000000000000000000000000000004700000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/modules/map.json{
"Header": "headers",
"ProxyPass": "proxy",
"ProxyPassReverse": "proxy",
"RemoteIPTrustedProxy": "remoteip",
"RequestHeader": "headers",
"RewriteCond": "rewrite",
"RewriteEngine": "rewrite",
"RewriteMap": "rewrite",
"RewriteOptions": "rewrite",
"RewriteRule": "rewrite",
"SSLCertificateFile": "ssl",
"SetEnv": "env"
}
0707010000000C000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004100000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/modules/os0707010000000D000081A400000000000000000000000168EB80BB00000BCF000000000000000000000000000000000000006A00000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/modules/os/SUSE_Linux_Enterprise_Server_15_SP5.json{
"base": [
"access_compat",
"actions",
"alias",
"allowmethods",
"asis",
"auth_basic",
"auth_digest",
"auth_form",
"authn_anon",
"authn_core",
"authn_dbd",
"authn_dbm",
"authn_file",
"authn_socache",
"authnz_fcgi",
"authnz_ldap",
"authz_core",
"authz_dbd",
"authz_dbm",
"authz_groupfile",
"authz_host",
"authz_owner",
"authz_user",
"autoindex",
"brotli",
"bucketeer",
"buffer",
"cache",
"cache_disk",
"cache_socache",
"case_filter",
"case_filter_in",
"charset_lite",
"data",
"dav",
"dav_fs",
"dav_lock",
"dbd",
"deflate",
"dialup",
"dir",
"dumpio",
"echo",
"env",
"expires",
"ext_filter",
"file_cache",
"filter",
"headers",
"heartmonitor",
"http2",
"imagemap",
"include",
"info",
"lbmethod_bybusyness",
"lbmethod_byrequests",
"lbmethod_bytraffic",
"lbmethod_heartbeat",
"ldap",
"log_config",
"log_debug",
"log_forensic",
"logio",
"lua",
"macro",
"mime",
"mime_magic",
"negotiation",
"optional_fn_export",
"optional_fn_import",
"optional_hook_export",
"optional_hook_import",
"proxy",
"proxy_ajp",
"proxy_balancer",
"proxy_connect",
"proxy_express",
"proxy_fcgi",
"proxy_fdpass",
"proxy_ftp",
"proxy_hcheck",
"proxy_html",
"proxy_http",
"proxy_http2",
"proxy_scgi",
"proxy_uwsgi",
"proxy_wstunnel",
"ratelimit",
"reflector",
"remoteip",
"reqtimeout",
"request",
"rewrite",
"sed",
"session",
"session_cookie",
"session_crypto",
"session_dbd",
"setenvif",
"slotmem_plain",
"slotmem_shm",
"socache_dbm",
"socache_memcache",
"socache_redis",
"socache_shmcb",
"speling",
"ssl",
"status",
"substitute",
"suexec",
"unique_id",
"userdir",
"usertrack",
"version",
"vhost_alias",
"watchdog",
"xml2enc"
],
"default": [
"actions",
"alias",
"auth_basic",
"authn_core",
"authn_file",
"authz_host",
"authz_groupfile",
"authz_core",
"authz_user",
"autoindex",
"cgi",
"dir",
"env",
"expires",
"include",
"log_config",
"mime",
"negotiation",
"setenvif",
"ssl",
"socache_shmcb",
"userdir",
"reqtimeout"
]
}
0707010000000E000081A400000000000000000000000168EB80BB00000BEE000000000000000000000000000000000000006A00000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/modules/os/SUSE_Linux_Enterprise_Server_15_SP6.json{
"base": [
"access_compat",
"actions",
"alias",
"allowmethods",
"asis",
"auth_basic",
"auth_digest",
"auth_form",
"authn_anon",
"authn_core",
"authn_dbd",
"authn_dbm",
"authn_file",
"authn_socache",
"authnz_fcgi",
"authnz_ldap",
"authz_core",
"authz_dbd",
"authz_dbm",
"authz_groupfile",
"authz_host",
"authz_owner",
"authz_user",
"autoindex",
"brotli",
"bucketeer",
"buffer",
"cache",
"cache_disk",
"cache_socache",
"case_filter",
"case_filter_in",
"cgi",
"cgid",
"charset_lite",
"data",
"dav",
"dav_fs",
"dav_lock",
"dbd",
"deflate",
"dialup",
"dir",
"dumpio",
"echo",
"env",
"expires",
"ext_filter",
"file_cache",
"filter",
"headers",
"heartmonitor",
"http2",
"imagemap",
"include",
"info",
"lbmethod_bybusyness",
"lbmethod_byrequests",
"lbmethod_bytraffic",
"lbmethod_heartbeat",
"ldap",
"log_config",
"log_debug",
"log_forensic",
"logio",
"lua",
"macro",
"mime",
"mime_magic",
"negotiation",
"optional_fn_export",
"optional_fn_import",
"optional_hook_export",
"optional_hook_import",
"proxy",
"proxy_ajp",
"proxy_balancer",
"proxy_connect",
"proxy_express",
"proxy_fcgi",
"proxy_fdpass",
"proxy_ftp",
"proxy_hcheck",
"proxy_html",
"proxy_http",
"proxy_http2",
"proxy_scgi",
"proxy_uwsgi",
"proxy_wstunnel",
"ratelimit",
"reflector",
"remoteip",
"reqtimeout",
"request",
"rewrite",
"sed",
"session",
"session_cookie",
"session_crypto",
"session_dbd",
"setenvif",
"slotmem_plain",
"slotmem_shm",
"socache_dbm",
"socache_memcache",
"socache_redis",
"socache_shmcb",
"speling",
"ssl",
"status",
"substitute",
"suexec",
"unique_id",
"userdir",
"usertrack",
"version",
"vhost_alias",
"watchdog",
"xml2enc"
],
"default": [
"actions",
"alias",
"auth_basic",
"authn_core",
"authn_file",
"authz_host",
"authz_groupfile",
"authz_core",
"authz_user",
"autoindex",
"cgi",
"dir",
"env",
"expires",
"include",
"log_config",
"mime",
"negotiation",
"setenvif",
"ssl",
"socache_shmcb",
"userdir",
"reqtimeout"
]
}
0707010000000F0000A1FF00000000000000000000000168EB80BB00000028000000000000000000000000000000000000005900000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/modules/os/openSUSE_Leap_15.5.jsonSUSE_Linux_Enterprise_Server_15_SP5.json070701000000100000A1FF00000000000000000000000168EB80BB00000028000000000000000000000000000000000000005900000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/modules/os/openSUSE_Leap_15.6.jsonSUSE_Linux_Enterprise_Server_15_SP6.json07070100000011000081A400000000000000000000000168EB80BB000004C0000000000000000000000000000000000000004300000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/packages.sls{#-
Salt state file for managing the Apache HTTP server packages
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'apache_httpd/map.jinja' import httpd, modules, mpm -%}
apache_httpd_packages:
pkg.installed:
- pkgs:
- apache2-{{ mpm }}
{%- for module in modules %}
{%- if module not in httpd.internal.modules.base and module != 'cgi' %}
- apache2-mod_{{ module }}
{%- endif %}
{%- endfor %}
- apache2-utils
# https://bugzilla.opensuse.org/show_bug.cgi?id=1226379
- apache2
07070100000012000081A400000000000000000000000168EB80BB0000063D000000000000000000000000000000000000004000000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/purge.sls{#-
Salt state file for removing unmanaged httpd configuration files
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'apache_httpd/map.jinja' import httpd, places, cmd_kwargs -%}
include:
- .services
{%- for place in places %}
{%- set directory = httpd.directories[place] %}
{%- set config_pillar = httpd.get(place, {}) %}
{%- for file in salt['file.find'](directory, print='name', type='f') %}
{%- set path = directory ~ '/' ~ file %}
{%- do salt.log.debug('apache_httpd.purge: ' ~ path) %}
{#- RPM file query is not implemented in modules.rpm_lowpkg #}
{%- if
salt['cmd.retcode']('/usr/bin/rpm -fq --quiet ' ~ path, **cmd_kwargs) == 1
and
file.replace('.conf', '') not in config_pillar
%}
apache_httpd_remove_{{ place }}-{{ file }}:
file.absent:
- name: {{ path }}
- watch_in:
- service: apache_httpd_service
{%- endif %}
{%- endfor %}
{%- endfor %}
07070100000013000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003F00000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/services07070100000014000081A400000000000000000000000168EB80BB00000403000000000000000000000000000000000000004800000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/services/init.sls{#-
Salt state file for managing the Apache HTTP server service
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
include:
- apache_httpd.packages
- .watch
apache_httpd_service:
service.running:
- name: apache2
- enable: True
- reload: True
- require:
- pkg: apache_httpd_packages
- require_in:
- module: apache_httpd_service_restart
07070100000015000081A400000000000000000000000168EB80BB000003B8000000000000000000000000000000000000004900000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/services/watch.sls{#-
Salt state file for managing Apache HTTP server service restarts
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
include:
- apache_httpd.packages
apache_httpd_service_restart:
module.wait:
- name: service.restart
- m_name: apache2
- require:
- pkg: apache_httpd_packages
07070100000016000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004000000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/templates07070100000017000081A400000000000000000000000168EB80BB00000E23000000000000000000000000000000000000004D00000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/templates/config.jinja{%- from 'apache_httpd/macros.jinja' import expandOrJoin -%}
{{ pillar.get('managed_by_salt_formula', '# Managed by the apache_httpd formula') }}
{%- set config = salt['pillar.get']('apache_httpd:' ~ type, {}).get(name, {}) %}
{%- if type == 'vhosts' %}
{%- set i = 4 %}
{%- if 'listen' in config %}
{%- set listen = config.pop('listen') %}
{%- else %}
{%- set listen = ['*:80'] %}
{%- endif %}
{%- if listen is string %}
{%- set listen = [listen] %}
{%- endif %}
{%- set defaults = {
'customlog': {
'location': '{0}/{1}-access_log'.format(logdir, name),
'format': 'combined',
'env': None,
},
}
%}
{%- if 'CustomLog' in config and config['CustomLog'] is mapping %}
{%- set customlog = config.pop('CustomLog') %}
{%- do salt.log.debug(customlog) %}
{%- for customlog_option in defaults['customlog'].keys() %}
{%- do salt.log.debug(customlog_option) %}
{%- if not customlog_option in customlog %}
{%- do customlog.update({customlog_option: defaults['customlog'][customlog_option]}) %}
{%- endif %}
{%- endfor %}
{%- else %}
{%- set customlog = defaults['customlog'] %}
{%- endif %}
{#- if CustomLog is in config and is not a mapping, assume a full CustomLog line to write as-is #}
<VirtualHost {%- for listener in listen %} {{ listener }}{% endfor -%}>
{%- if not 'ServerName' in config %}
ServerName {{ name }}
{%- endif %}
{%- if not 'CustomLog' in config %}
CustomLog {{ customlog['location'] }} {{ customlog['format'] }}{{ ' env' ~ customlog['env'] if customlog['env'] else '' }}
{%- endif %}
{%- if not 'ErrorLog' in config %}
ErrorLog {{ '{0}/{1}-error_log'.format(logdir, name) }}
{%- endif %}
{%- elif type == 'configs' %}
{%- set i = 0 %}
{%- endif %} {#- close config type check #}
{%- for option, value in config.items() %}
{%- if value is sameas true %}
{%- set value = 'on' %}
{%- elif value is sameas false %}
{%- set value = 'off' %}
{%- endif %}
{%- if value is iterable and value is not mapping %}
{%- if value is string %}
{%- set value = [value] %}
{%- endif -%}
{{ expandOrJoin(option, value, option in repetitive_options, i) }}
{%- elif value is mapping %}
{%- for low_option, low_value in value.items() %}
{%- if low_value is mapping %}
{%- if option not in repetitive_options %}
{{ ( '<' ~ option ~ ' "' ~ low_option ~ '">' ) | indent(i, True) }}
{%- endif %} {#- close first inner repetitive_option check #}
{%- for low_low_option, low_low_values in low_value.items() %}
{%- if low_low_values is string %}
{%- set low_low_values = [low_low_values] %}
{%- endif %}
{%- if option in repetitive_options -%}
{{ expandOrJoin(option ~ ' ' ~ low_option ~ ' ' ~ low_low_option, low_low_values, True, i) }}
{%- else -%}
{{ expandOrJoin(low_low_option, low_low_values, False, i+4) }}
{%- endif %} {#- close second inner repetitive_option check #}
{%- endfor %} {#- close low_value iteration #}
{%- if option not in repetitive_options %}
{{ ( '</' ~ option ~ '>' ) | indent(i, True) }}
{%- endif %} {#- close third inner repetitive_option check #}
{%- elif low_value is string %}
{{ ( option ~ ' ' ~ low_option ~ ' ' ~ low_value ) | indent(i, True) }}
{%- endif %} {#- close low_value type check #}
{%- endfor %} {#- close value iteration #}
{%- endif %} {#- close value type check #}
{%- endfor %} {#- close config iteration #}
{%- if type == 'vhosts' %}
</VirtualHost>
{%- endif %} {#- close vhost check #}
07070100000018000081A400000000000000000000000168EB80BB00000599000000000000000000000000000000000000005400000000salt-formulas-3.0.4/apache_httpd-formula/apache_httpd/templates/listen_config.jinja{{ pillar.get('managed_by_salt_formula', '# Managed by the apache_httpd formula') }}
{%- set config = salt['pillar.get']('apache_httpd:vhosts', {}) %}
{%- if config %}
{%- set listeners = [] %}
{%- set wildcard_ports = [] %}
{%- for vhost_name, vhost_config in config.items() %}
{%- set listen = vhost_config.get('listen', '*:80') %}
{%- if listen is string %}
{%- set listen = [listen] %}
{%- endif %}
{%- for listener in listen %}
{%- if listener not in listeners %}
{%- if ':' in listener %}
{%- do listeners.append(listener) %}
{%- else %}
{%- do salt.log.error('apache_httpd: invalid listener: ' ~ listener) %}
{%- endif %}
{%- endif %}
{%- endfor %}
{%- endfor %}
{%- for listener in listeners %}
{%- set listener_split = listener.split(':') %}
{%- if listener_split[0] == '*' %}
{%- set listener_port = listener_split[1] %}
{%- if listener_port not in wildcard_ports %}
{%- do wildcard_ports.append(listener_port) %}
{%- endif %}
{%- endif %}
{%- endfor %}
{%- for listener in listeners %}
{%- if not listener.split(':')[1] in wildcard_ports %}
Listen {{ listener }}
{%- endif %}
{%- endfor %}
{%- for port in wildcard_ports %}
Listen *:{{ port }}
{%- endfor %}
{%- else %}
Listen 80
<IfModule mod_ssl.c>
Listen 443
</IfModule>
{%- endif %} {#- close config check #}
07070100000019000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002D00000000salt-formulas-3.0.4/apache_httpd-formula/bin0707010000001A000081ED00000000000000000000000168EB80BB0000093E000000000000000000000000000000000000003800000000salt-formulas-3.0.4/apache_httpd-formula/bin/modules.sh#!/bin/sh -Cefu
# This compiles the modules shipped on an openSUSE system into a JSON file,
# which is then used by the formula to differentiate between modules requiring package
# installation and ones bundled with the core apache2 packages.
# Should be run on a machine which does NOT have any additional apache2-mod_* package installed.
#
# Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
#
# 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 <https://www.gnu.org/licenses/>.
# Check if a usable apache2 is installed
libdir='/usr/lib64/apache2/'
if [ ! -d "$libdir" ]
then
echo 'Missing libdir.'
exit 1
fi
# Define output directory - if not run in the salt-formulas.git root, OUTDIR can be specified
outdir="${OUTDIR?:apache_httpd-formula/apache_httpd/modules/os}"
if [ ! -d "$outdir" ]
then
echo 'Missing output directory.'
exit 1
fi
# Define output file name - if OS is not specified, the current OS name converted into the "oscodename" grain format will be used
set +u
if [ -z "$OS" ]
then
. /etc/os-release
OS="${PRETTY_NAME// /_}"
fi
set -u
outfile="$outdir/$OS.json"
# Start JSON file with "base" section
printf '{\n "base": [\n' >| "$outfile"
# Gather installed modules
find "$libdir" \
-name 'mod_*.so' -type l \
-execdir basename {} \; \
| LC_ALL=C sort \
| sed -E \
-e 's/mod_([a-z0-9_]+)\.so/ "\1"/' \
-e '$ ! s/$/,/' \
>> "$outfile"
# End "base" and start "default" section
printf ' ],\n "default": [\n' >> "$outfile"
# Gather modules enabled by default
awk -F= \
'/^APACHE_MODULES=".*"$/{ gsub("\"", "", $2) ; gsub(" ", "\",\n \"", $2) ; print " \"" $2 "\"" }' \
/usr/share/fillup-templates/sysconfig.apache2 \
>> "$outfile"
# End JSON file
printf ' ]\n}\n' >> "$outfile"
0707010000001B000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003200000000salt-formulas-3.0.4/apache_httpd-formula/metadata0707010000001C000081A400000000000000000000000168EB80BB000000B8000000000000000000000000000000000000003F00000000salt-formulas-3.0.4/apache_httpd-formula/metadata/metadata.yml---
summary:
Salt states for managing the Apache httpd
description:
Salt states for installing and configuring the Apache HTTP server on SUSE distributions.
require:
- sysconfig
0707010000001D000081A400000000000000000000000168EB80BB0000132E000000000000000000000000000000000000003800000000salt-formulas-3.0.4/apache_httpd-formula/pillar.exampleapache_httpd:
directories:
# defaults
base: /etc/apache2
configs: /etc/apache2/conf.d
vhosts: /etc/apache2/vhosts.d
logs: /var/log/apache2
htdocs: /srv/www/htdocs
# whether to remove files neither managed by Salt nor by packages, True by default
purge: True
# which modules to enable (and to install through packages, in case of modules not contained in the base installation)
# the modules list will be automatically extended with:
# - the modules enabled by default on a stock apache2 installation on openSUSE,
# in order to keep them enabled - see internal:default_modules at the end of this file
# - modules needed for certain options - see internal:module_map
modules:
# example
- status
# sysconfig will be written into /etc/sysconfig/apache2
sysconfig:
# the event mpm is used by default
apache_mpm: event
# any additional sysconfig options can be declared
# configs will be placed in directories.configs
configs:
myconfig:
# example
RemoteIPHeader: X-Forwarded-For
# all options should be supported - feature-wise identical with vhosts, reference more examples below
# vhosts will be placed in directories.vhosts
vhosts:
# just an example
mysite:
# again, all options should be supported
# some general rules with pseudo-code:
# options with a single parameter:
foo: bar
# options with multiple parameters:
foo:
- bar
- baz
# options using a <foo "bar"> baz: boo </foo> block:
foo:
bar:
baz: boo
# below are some more practical examples:
# "listen" is special as it is not only written to the vhost configuration, but to listen.conf as well
listen: localhost:80
# "ServerName" is always written, if not specified in the pillar, it defaults to the vhost name
ServerName: something.example.com
# options which take multiple values can be specified either as a list, or as a string
ServerAlias:
- example.com
- example.net
Protocols:
- h2
- http/1.1
# OR
Protocols: h2
# declare repeated options as a mapping
Alias:
/mypath: /usr/share/mypath
/myotherpath: /usr/share/myotherpath
# mappings such as Directory, Location or LocationMatch are all feature identical
Directory:
/srv/www/example:
# again, options taking multiple values can be written as a list or as a string
Options:
- Indexes
Require: all granted
RewriteCond:
# mappings with the same key can be provided as a list
- '%{REQUEST_FILENAME}': '!-f'
- '%{REQUEST_FILENAME}': '!-d'
# options which aren't blocks but which take multiple arguments can be provided as mappings as well
SetEnvIf:
Request_URI:
^/example$: myvariable
# boolean values are rewritten to on/off
RewriteEngine: true
# the following options are always written if not specified in the pillar, but the values can be overwritten
CustomLog: {{ directories.logs }}/{{ name of vhost }}-access.log combined
ErrorLog: {{ directories.logs }}/{{ name of vhost }}-error.log
# customize CustomLog parameters
CustomLog:
location: /somewhere/else/foo.log
format: notCombined
env: =!baz
# the "internal" section is altering behavior in the formula logic
# whilst it is possible to set and overwrite these setting in the pillar, doing so is not commonly tested
# this example section merely explains the behavior, reference the JSON files in the formula sources for the long list of default values
internal:
modules:
# "modules:default" defines the modules enabled by default on the given distribution
# if overwriting of the distribution enabled modules in favor of exclusively the ones in apache_httpd:modules is
# desired, this can be set to an empty list in the pillar
default: []
# "modules:base" defines the modules shipped as part of the main apache2 package on the given distribution
# modules not part of the base require a apache2-mod_<module> package to be available, hence this should _not_ be cleared
# in the pillar
base: []
# the bin/modules.sh script should be used to generate the default and base maps to introduce distribution changes or
# support for new distributions
# "modules:map" defines options which require certain modules
# this allows the formula to automatically enable modules based on the provided configuration and
# avoids Salt failing to start the service
map: {}
# options which must be declared multiple times instead of supporting multiple values as a parameter
repetitive_options: []
0707010000001E000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002F00000000salt-formulas-3.0.4/apache_httpd-formula/tests0707010000001F000081A400000000000000000000000168EB80BB0000068D000000000000000000000000000000000000003B00000000salt-formulas-3.0.4/apache_httpd-formula/tests/conftest.py"""
Helpers for testing the apache_httpd formula
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
This program is free software: you can redminetribute 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 <https://www.gnu.org/licenses/>.
"""
from json import loads
from yaml import safe_load
from shlex import quote
import pytest
def salt(host, command):
result = host.run(f'salt-call --local --out json {command}')
#print(result)
output = loads(result.stdout)['local']
return output, result.stderr, result.rc
@pytest.fixture
def pillar(request):
with open('apache_httpd-formula/tests/pillar.sls') as fh:
pillar = safe_load(fh)
if hasattr(request, 'param'):
print(request.param)
pillar['apache_httpd'].update(request.param)
return pillar
@pytest.fixture
def salt_apply(host, pillar, test):
print(f'sa pillar: {pillar}')
print(f'sa test: {test}')
pillar = quote(str(pillar))
yield salt(host, f'state.apply apache_httpd pillar={pillar} test={test}')
host.run('zypper -n rm -u apache2*')
host.run('rm -fr /etc/apache2 /etc/sysconfig/apache2 /var/cache/apache2 /var/lib/apache2 /var/log/apache2')
07070100000020000081A400000000000000000000000168EB80BB00000524000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/apache_httpd-formula/tests/pillar.slsapache_httpd:
modules:
- status
sysconfig:
apache_servername: ipv6-localhost
configs:
log:
SetEnvIf:
Request_URI:
^/health$: donotlog
remote:
RemoteIPHeader: X-Forwarded-For
RemoteIPTrustedProxy:
- 2001:db8::1
- 2001:db8::2
vhosts:
status:
listen: ipv6-localhost:8181
Location:
/server-status:
SetHandler: server-status
mysite1:
RewriteEngine: on
Directory:
/srv/www/htdocs:
Require: all granted
RewriteCond:
- '%{REQUEST_FILENAME}': '!-f'
- '%{REQUEST_FILENAME}': '!-d'
RewriteRule:
- '^(foo.html/)?(.+)$ foo.php?bar=$2 [L]'
CustomLog:
env: =!donotlog
mysite2:
ServerName: mysite2.example.com
RewriteEngine: off
Protocols:
- h2
- http/1.1
Alias:
/static: /srv/www/static
Directory:
/srv/www/static:
Options:
- Indexes
- FollowSymLinks
AllowOverride: None
Require: all granted
mysite3:
RewriteEngine: on
RewriteCond:
- '%{REQUEST_FILENAME}': '!-f'
- '%{REQUEST_FILENAME}': '!-d'
RewriteRule:
- '^(foo.html/)?(.+)$ foo.php?bar=$2 [QSA]'
07070100000021000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003900000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference07070100000022000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003D00000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc07070100000023000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004500000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache207070100000024000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004C00000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/conf.d07070100000025000081A400000000000000000000000168EB80BB0000004E000000000000000000000000000000000000005500000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/conf.d/log.conf# Managed by the apache_httpd formula
SetEnvIf Request_URI ^/health$ donotlog
07070100000026000081A400000000000000000000000168EB80BB0000002B000000000000000000000000000000000000005900000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/conf.d/log.conf.md5f36f2adb9df5b321b6b2383a339de780 log.conf
07070100000027000081A400000000000000000000000168EB80BB00000087000000000000000000000000000000000000005800000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/conf.d/remote.conf# Managed by the apache_httpd formula
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 2001:db8::1
RemoteIPTrustedProxy 2001:db8::2
07070100000028000081A400000000000000000000000168EB80BB0000002E000000000000000000000000000000000000005C00000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/conf.d/remote.conf.md52d4e69a65a3c77743f8504af4ae2415a remote.conf
07070100000029000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004E00000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d0707010000002A000081A400000000000000000000000168EB80BB000001CF000000000000000000000000000000000000005B00000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d/mysite1.conf# Managed by the apache_httpd formula
<VirtualHost *:80>
ServerName mysite1
CustomLog /var/log/apache2/mysite1-access_log combined env=!donotlog
ErrorLog /var/log/apache2/mysite1-error_log
RewriteEngine on
<Directory "/srv/www/htdocs">
Require all granted
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(foo.html/)?(.+)$ foo.php?bar=$2 [L]
</Directory>
</VirtualHost>
0707010000002B000081A400000000000000000000000168EB80BB0000002F000000000000000000000000000000000000005F00000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d/mysite1.conf.md59a121be8a4196294722e1707fc975e44 mysite1.conf
0707010000002C000081A400000000000000000000000168EB80BB000001BA000000000000000000000000000000000000005B00000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d/mysite2.conf# Managed by the apache_httpd formula
<VirtualHost *:80>
CustomLog /var/log/apache2/mysite2-access_log combined
ErrorLog /var/log/apache2/mysite2-error_log
ServerName mysite2.example.com
RewriteEngine off
Protocols h2 http/1.1
Alias /static /srv/www/static
<Directory "/srv/www/static">
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
</VirtualHost>
0707010000002D000081A400000000000000000000000168EB80BB0000002F000000000000000000000000000000000000005F00000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d/mysite2.conf.md5d0004f7ee286d5402c55fa2f187f0bab mysite2.conf
0707010000002E000081A400000000000000000000000168EB80BB00000168000000000000000000000000000000000000005B00000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d/mysite3.conf# Managed by the apache_httpd formula
<VirtualHost *:80>
ServerName mysite3
CustomLog /var/log/apache2/mysite3-access_log combined
ErrorLog /var/log/apache2/mysite3-error_log
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(foo.html/)?(.+)$ foo.php?bar=$2 [QSA]
</VirtualHost>
0707010000002F000081A400000000000000000000000168EB80BB0000002F000000000000000000000000000000000000005F00000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d/mysite3.conf.md5a030e822bc10043b9d10558d1558ee1a mysite3.conf
07070100000030000081A400000000000000000000000168EB80BB00000128000000000000000000000000000000000000005A00000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d/status.conf# Managed by the apache_httpd formula
<VirtualHost ipv6-localhost:8181>
ServerName status
CustomLog /var/log/apache2/status-access_log combined
ErrorLog /var/log/apache2/status-error_log
<Location "/server-status">
SetHandler server-status
</Location>
</VirtualHost>
07070100000031000081A400000000000000000000000168EB80BB0000002E000000000000000000000000000000000000005E00000000salt-formulas-3.0.4/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d/status.conf.md5dda3e0dd18b99d5c3525dd1b43e35081 status.conf
07070100000032000081ED00000000000000000000000168EB80BB000000A1000000000000000000000000000000000000003600000000salt-formulas-3.0.4/apache_httpd-formula/tests/run.sh#!/bin/sh
pytest --pdb --pdbcls=IPython.terminal.debugger:Pdb --disable-warnings -v -rx -x --hosts=test --ssh-config=ssh_config apache_httpd-formula/tests/ "$@"
07070100000033000081A400000000000000000000000168EB80BB00000FD2000000000000000000000000000000000000003D00000000salt-formulas-3.0.4/apache_httpd-formula/tests/test_httpd.py"""
Test suite for assessing the apache_httpd formula
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
This program is free software: you can redminetribute 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 <https://www.gnu.org/licenses/>.
"""
import pytest
@pytest.mark.parametrize(
'pillar, package', [
({}, 'apache2-event'),
({'sysconfig': {'APACHE_MPM': 'prefork'}}, 'apache2-prefork')
],
indirect=['pillar']
)
@pytest.mark.parametrize('test', [True, False])
def test_httpd_package(host, package, pillar, salt_apply, test):
result = salt_apply
assert len(result) > 0
output = result[0]
state = 'pkg_|-apache_httpd_packages_|-apache_httpd_packages_|-installed'
assert state in output
assert output[state].get('name') == 'apache_httpd_packages'
changes = output[state].get('changes')
if test:
assert changes == {package: {'new': 'installed', 'old': ''}, 'apache2-utils': {'new': 'installed', 'old': ''}, 'apache2': {'new': 'installed', 'old': ''}}
else:
assert package in changes
assert 'apache2' in changes
# in non-test runs, "new" will return the freshly installed version, such as 2.4.51-150400.6.14.1
assert '2.4' in changes[package]['new']
print(output)
assert host.package('apache2').is_installed is not test
assert host.package(package).is_installed is not test
@pytest.mark.parametrize('test', [True, False])
def test_httpd_config(host, salt_apply, test):
result = salt_apply
assert len(result) > 0
output = result[0]
for file, checksum in {
'conf.d/log.conf': 'f36f2adb9df5b321b6b2383a339de780',
'conf.d/remote.conf': '2d4e69a65a3c77743f8504af4ae2415a',
'vhosts.d/mysite1.conf': '9a121be8a4196294722e1707fc975e44',
'vhosts.d/mysite2.conf': 'd0004f7ee286d5402c55fa2f187f0bab',
'vhosts.d/mysite3.conf': 'a030e822bc10043b9d10558d1558ee1a',
'vhosts.d/status.conf': 'dda3e0dd18b99d5c3525dd1b43e35081',
}.items():
if file.startswith('conf.d'):
place = 'configs'
elif file.startswith('vhosts.d'):
place = 'vhosts'
file = f'/etc/apache2/{file}'
state = f'file_|-apache_httpd_{place}_|-{file}_|-managed'
assert state in output
assert output[state].get('name') == file
changes = output[state].get('changes')
if test:
assert changes == {'newfile': file}
assert output[state]['result'] is None
else:
assert changes == {'diff': 'New file', 'mode': '0644'}
file = host.file(file)
assert file.is_file
assert file.uid == 0
assert file.gid == 0
assert file.md5sum == checksum
@pytest.mark.parametrize('test', [True, False])
def test_httpd_sysconfig(host, salt_apply, test):
result = salt_apply
assert len(result) > 0
output = result[0]
state = 'suse_sysconfig_|-apache_httpd_sysconfig_|-apache2_|-sysconfig'
assert state in output
file = '/etc/sysconfig/apache2'
changes = output[state].get('changes')
assert output[state].get('name') == file
assert output[state]['result'] is True
if test:
assert changes == {}
assert 'unable to open' in output[state]['comment']
else:
assert 'diff_config' in changes
assert 'diff_header' in changes
file = host.file(file)
assert file.contains('^APACHE_MPM="event"$')
assert file.contains('^APACHE_SERVERADMIN=""$')
assert file.contains('^APACHE_SERVERNAME="ipv6-localhost"$')
@pytest.mark.parametrize('test', [False])
def test_httpd_service(host, salt_apply, test):
assert host.service('apache2').is_enabled
assert host.service('apache2').is_running
07070100000034000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002900000000salt-formulas-3.0.4/backupscript-formula07070100000035000081A400000000000000000000000168EB80BB0000020A000000000000000000000000000000000000003300000000salt-formulas-3.0.4/backupscript-formula/README.md# Salt states for SUSE backup scripts
## Available states
`backupscript`
Depending on the pillar configuration, installs and configures:
- [influxdb-backupscript](https://build.opensuse.org/package/show/home:lrupp/influxdb-backupscript)
- [mysql-backupscript](https://build.opensuse.org/package/show/home:lrupp/mysql-backupscript)
- [postgresql-backupscript](https://build.opensuse.org/package/show/home:lrupp/postgresql-backupscript)
Backupscripts which are installed but not defined in the pillar will be disabled.
07070100000036000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003600000000salt-formulas-3.0.4/backupscript-formula/backupscript07070100000037000081A400000000000000000000000168EB80BB00000AB3000000000000000000000000000000000000003F00000000salt-formulas-3.0.4/backupscript-formula/backupscript/init.sls{#-
Salt state file for managing SUSE backup scripts
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set mypillar = pillar.get('backupscript', {}) %}
{%- set backupscripts = {
'influxdb': mypillar.get('influxdb', False),
'mysql': mypillar.get('mysql', False),
'postgresql': mypillar.get('postgresql', False),
}
-%}
{%- if backupscripts['influxdb'] != False or backupscripts['mysql'] != False or backupscripts['postgresql'] != False %}
backupscript_packages:
pkg.installed:
- pkgs:
{%- for bs_short in backupscripts.keys() %}
{%- if backupscripts[bs_short] != False %}
- {{ bs_short }}-backupscript
{%- endif %}
{%- endfor %}
{%- for bs_short, config in backupscripts.items() %}
{%- set bs = bs_short ~ '-backupscript' %}
{%- if config %}
{%- set file = '/etc/sysconfig/' ~ bs %}
{{ bs }}_sysconfig:
suse_sysconfig.sysconfig:
- name: {{ file }}
- header_pillar: managed_by_salt_formula_sysconfig
- key_values:
{%- for k, v in config.items() %}
{{ k }}: {{ v }}
{%- endfor %}
- require:
- pkg: backupscript_packages
{%- endif %} {#- close config check #}
{%- if backupscripts[bs_short] != False %}
{{ bs }}_timer:
service.running:
- name: {{ bs }}.timer
- enable: true
- require:
- pkg: backupscript_packages
{%- if config %}
- suse_sysconfig: {{ bs }}_sysconfig
{%- endif %}
{%- endif %}
{%- endfor %} {#- close backupscripts loop #}
{%- endif %} {#- close backupscripts not false check #}
{%- for bs in backupscripts.keys() %}
{%- if backupscripts[bs] == False %}
{%- set bs = bs ~ '-backupscript' %}
{%- if salt['service.available'](bs) %}
{{ bs }}_service:
service.dead:
- names:
{%- if not opts['test'] %}
{#- not idempotent in test mode #}
- {{ bs }}.service
{%- endif %}
- {{ bs }}.timer
- enable: false
{%- endif %} {#- close backupscript service check #}
{%- endif %} {#- close backupscript false check #}
{%- endfor %} {#- close backupscripts loop #}
07070100000038000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003200000000salt-formulas-3.0.4/backupscript-formula/metadata07070100000039000081A400000000000000000000000168EB80BB000000BF000000000000000000000000000000000000003F00000000salt-formulas-3.0.4/backupscript-formula/metadata/metadata.yml---
summary:
Salt states for managing SUSE backup scripts
description:
Salt states for installing and configuring the SUSE backup scripts for MySQL and PostgreSQL.
require:
- sysconfig
0707010000003A000081A400000000000000000000000168EB80BB00000175000000000000000000000000000000000000003800000000salt-formulas-3.0.4/backupscript-formula/pillar.examplebackupscript:
# writes /etc/sysconfig/influxdb-backupscript
influxdb: {}
# writes /etc/sysconfig/mysql-backupscript
mysql:
# example for adjusting some defaults
backupdir: /my/backup/directory
retention: 7
# writes /etc/sysconfig/postgresql-backupscript
# use an empty dictionary to install and enable without changing any defaults
postgresql: {}
0707010000003B000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002700000000salt-formulas-3.0.4/bootloader-formula0707010000003C000081A400000000000000000000000168EB80BB000001A6000000000000000000000000000000000000003100000000salt-formulas-3.0.4/bootloader-formula/README.md# Salt states for bootloader configuration
## Available states
`bootloader`
Runs both of the below.
`bootloader.bootloader`
Configures general settings (`/etc/sysconfig/bootloader`).
Unless disabled, changes will trigger a bootloader re-installation.
`bootloader.grub`
Configures GRUB specific settings (`/etc/default/grub`).
Unless disabled, changes will trigger the generation of a new GRUB configuration file.
0707010000003D000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003200000000salt-formulas-3.0.4/bootloader-formula/bootloader0707010000003E000081A400000000000000000000000168EB80BB0000054F000000000000000000000000000000000000004100000000salt-formulas-3.0.4/bootloader-formula/bootloader/bootloader.sls{#-
Salt state file for managing generic bootloader configuration
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'bootloader/map.jinja' import bootloader_data -%}
{%- if 'config' in bootloader_data %}
bootloader_sysconfig:
suse_sysconfig.sysconfig:
- name: bootloader
- header_pillar: managed_by_salt_formula_sysconfig
- append_if_not_found: True
- quote_booleans: False
- key_values: {{ bootloader_data['config'] }}
{%- if bootloader_data.get('update', True) %}
bootloader_update:
cmd.run:
{#- on 15.4 pbl is not yet available under /usr #}
- name: /sbin/pbl --install
- onchanges:
- suse_sysconfig: bootloader_sysconfig
{%- endif %}
{%- endif %}
0707010000003F000081A400000000000000000000000168EB80BB000008CD000000000000000000000000000000000000003B00000000salt-formulas-3.0.4/bootloader-formula/bootloader/grub.sls{#-
Salt state file for managing GRUB configuration
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'bootloader/map.jinja' import grub_data -%}
{%- if 'config' in grub_data %}
{%- set file = '/etc/default/grub' %}
grub_header:
file.prepend:
- name: {{ file }}
- text: {{ pillar.get('managed_by_salt_formula', '# Managed by the bootloader formula') | yaml_encode }}
{%- set boolmap = {true: 'true', false: 'false'} %}
grub_default:
file.keyvalue:
- name: {{ file }}
- key_values:
{%- for k, v in grub_data['config'].items() %}
{%- if v is sameas True or v is sameas False %}
{%- set value = boolmap[v] %}
{%- else %}
{%- set value = v %}
{%- endif %}
{{ k | upper }}: '"{{ value }}"'
{%- endfor %}
- ignore_if_missing: {{ opts['test'] }}
- append_if_not_found: True
- uncomment: '#'
{%- if grub_data.get('update', True) %}
{%- set files = grub_data.get('grub_configuration', '/boot/grub2/grub.cfg') %}
{%- if files is string %}
{%- set files = [files] %}
{%- endif %}
{%- set main = files.pop(0) %}
grub_update:
cmd.run:
- name: /usr/sbin/grub2-mkconfig -o {{ main }}
- onchanges:
- file: grub_default
{%- if files | length %}
grub_update_copies:
file.copy:
- source: {{ main }}
- names:
{%- for file in files %}
- {{ file }}
{%- endfor %}
- onchanges:
- file: grub_default
- require:
- cmd: grub_update
{%- endif %} {#- close files check -#}
{%- endif %} {#- close update check -#}
{%- endif %} {#- close config check -#}
07070100000040000081A400000000000000000000000168EB80BB0000032A000000000000000000000000000000000000003B00000000salt-formulas-3.0.4/bootloader-formula/bootloader/init.sls{#-
Salt state file for managing configuration related to bootloaders
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
include:
- .bootloader
- .grub
07070100000041000081A400000000000000000000000168EB80BB000003A7000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/bootloader-formula/bootloader/map.jinja{#-
Jinja variables file for the Bootloader Salt states
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set bootloader = salt.pillar.get('bootloader', {}) -%}
{%- set bootloader_data = bootloader.get('bootloader', {}) -%}
{%- set grub_data = bootloader.get('grub', {}) -%}
07070100000042000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003000000000salt-formulas-3.0.4/bootloader-formula/metadata07070100000043000081A400000000000000000000000168EB80BB000000A3000000000000000000000000000000000000003D00000000salt-formulas-3.0.4/bootloader-formula/metadata/metadata.yml---
summary:
Salt states for managing the bootloader
description:
Salt states for managing the bootloader setup and GRUB configuration.
require:
- sysconfig
07070100000044000081A400000000000000000000000168EB80BB000002BB000000000000000000000000000000000000003600000000salt-formulas-3.0.4/bootloader-formula/pillar.examplebootloader:
bootloader:
# whether relevant configuration changes should trigger a bootloader update - this is the default
update: true
# key-value pairs written to /etc/sysconfig/bootloader, none by default
config:
secure_boot: false
grub:
# whether relevant configuration changes should trigger a rebuild of the GRUB configuration - this is the default
update: true
# location to write built GRUB configuraton to, can be a list if a copy should be stored in other locations - the following is the default:
grub_configuration: /boot/grub2/grub.cfg
# key-value pairs written to /etc/default/grub, none by default
config:
grub_terminal: console
07070100000045000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002500000000salt-formulas-3.0.4/doofetch-formula07070100000046000081A400000000000000000000000168EB80BB0000009C000000000000000000000000000000000000002F00000000salt-formulas-3.0.4/doofetch-formula/README.md# Salt states for doofetch
## Available states
`doofetch`
Installs and configures [doofetch](https://github.com/tacerus/doofetch) and enables its timer.
07070100000047000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/doofetch-formula/doofetch07070100000048000081A400000000000000000000000168EB80BB00000664000000000000000000000000000000000000003700000000salt-formulas-3.0.4/doofetch-formula/doofetch/init.sls{#-
Salt state file for managing doofetch
Copyright (C) 2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set mypillar = salt['pillar.get']('doofetch', {}) %}
{%- set mykey = mypillar.get('gpg', {}).get('key', {}) %}
{%- if mykey and 'url' in mykey and 'fingerprint' in mykey %}
doofetch_repository_key:
cmd.run:
- name: gpg --import < <(curl -sS {{ mykey['url'] }})
- shell: /bin/bash
- unless:
- gpg --list-key {{ mykey['fingerprint'] }}
- '! gpg --list-key {{ mykey['fingerprint'] }} | grep expired'
{%- endif %}
doofetch_package:
pkg.installed:
- name: doofetch
{%- if 'sysconfig' in mypillar and mypillar['sysconfig'] is mapping %}
doofetch_sysconfig:
suse_sysconfig.sysconfig:
- name: /etc/sysconfig/doofetch
- quote_char: "'"
- key_values: {{ mypillar['sysconfig'] }}
- require:
- pkg: doofetch_package
{%- endif %}
doofetch_timer:
service.running:
- name: doofetch.timer
- enable: true
- require:
- pkg: doofetch_package
07070100000049000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/doofetch-formula/metadata0707010000004A000081A400000000000000000000000168EB80BB0000008C000000000000000000000000000000000000003B00000000salt-formulas-3.0.4/doofetch-formula/metadata/metadata.yml---
summary:
Salt states for managing doofetch
description:
Salt states for installing and configuring doofetch.
require:
- sysconfig
0707010000004B000081A400000000000000000000000168EB80BB0000022B000000000000000000000000000000000000003400000000salt-formulas-3.0.4/doofetch-formula/pillar.exampledoofetch:
# optional PGP key to import into the keyring of the user executing the Salt Minion
gpg:
key:
url: http://download.infra.opensuse.org/repositories/openSUSE%3A/infrastructure/15.6/repodata/repomd.xml.key
fingerprint: 034EB7A6E7506D45DE9CCEC68E01781420F13AAC
# settings to write into /etc/sysconfig/doofetch
sysconfig:
url: https://download.opensuse.org/repositories/openSUSE:/infrastructure:/Images:/15.6/images/admin-openSUSE-Leap-15.6.x86_64-qcow.qcow2
targetdir: /srv/my-images
targetlink: my-latest-image
0707010000004C000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002200000000salt-formulas-3.0.4/gitea-formula0707010000004D000081A400000000000000000000000168EB80BB00000072000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/gitea-formula/README.md# Salt states for Gitea
## Available states
`gitea`
Installs and configures [Gitea](https://about.gitea.com/).
0707010000004E000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002800000000salt-formulas-3.0.4/gitea-formula/gitea0707010000004F000081A400000000000000000000000168EB80BB000002AF000000000000000000000000000000000000003600000000salt-formulas-3.0.4/gitea-formula/gitea/defaults.yaml---
config:
# config defaults have been taken from
# devel:tools:scm/gitea/gitea.app.ini.patch
default:
work_path: /var/lib/gitea
run_user: gitea
server:
static_root_path: /usr/share/gitea
app_data_path: /var/lib/gitea/data
pprof_data_path: /var/lib/gitea/data/tmp/pprof
log:
root_path: /var/log/gitea
repository:
root: /var/lib/gitea/repositories
repository.upload:
temp_path: /var/lib/gitea/data/tmp/uploads
queue:
datadir: /var/lib/gitea/queues
picture:
avatar_upload_path: /var/lib/gitea/data/avatars
repository_avatar_upload_path: /var/lib/gitea/data/repo-avatars
attachment:
path: /var/lib/gitea/data/attachments
07070100000050000081A400000000000000000000000168EB80BB00000570000000000000000000000000000000000000003100000000salt-formulas-3.0.4/gitea-formula/gitea/init.sls{#-
Salt state file for managing Gitea
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'gitea/map.jinja' import config %}
gitea_package:
pkg.installed:
- name: gitea
gitea_configuration:
ini.options_present:
- name: /etc/gitea/conf/app.ini
- strict: True
- sections:
{%- for section in config.keys() %}
{{ section | upper if section == 'default' else section }}:
{%- for option, value in config[section].items() %}
{{ option | upper }}: {{ value }}
{%- endfor %}
{%- endfor %}
- require:
- pkg: gitea_package
gitea_service:
service.running:
- name: gitea
- enable: True
- require:
- pkg: gitea_package
- watch:
- ini: gitea_configuration
07070100000051000081A400000000000000000000000168EB80BB00000389000000000000000000000000000000000000003200000000salt-formulas-3.0.4/gitea-formula/gitea/map.jinja{#-
Jinja variables file for the Gitea Salt states
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- import_yaml './defaults.yaml' as defaults -%}
{%- set gitea = salt.pillar.get('gitea', default=defaults, merge=True) -%}
{%- set config = gitea.get('config', {}) -%}
07070100000052000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002B00000000salt-formulas-3.0.4/gitea-formula/metadata07070100000053000081A400000000000000000000000168EB80BB0000002E000000000000000000000000000000000000003800000000salt-formulas-3.0.4/gitea-formula/metadata/metadata.yml---
summary:
Salt states for managing Gitea
07070100000054000081A400000000000000000000000168EB80BB0000015D000000000000000000000000000000000000003100000000salt-formulas-3.0.4/gitea-formula/pillar.example# The "gitea" pillar allows for all Gitea configuration options:
# https://docs.gitea.com/administration/config-cheat-sheet
#
# Some defaults will be written (unless specified otherwise) to accomodate the default configuration shipped with the package.
gitea:
config:
default:
app_name: GitCoffee
server:
http_addr: 127.0.0.1
07070100000055000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002300000000salt-formulas-3.0.4/grains-formula07070100000056000081A400000000000000000000000168EB80BB00000288000000000000000000000000000000000000002B00000000salt-formulas-3.0.4/grains-formula/LICENSE Copyright (c) 2013-2017 Salt Stack Formulas
Copyright (c) 2018-2024 openSUSE contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
07070100000057000081A400000000000000000000000168EB80BB0000029C000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/grains-formula/README.rst==============
grains-formula
==============
A formula that handles /etc/salt/grains (in order to create custom grains), and
populates its content based on pillars. For more information on custom grains
(and grains in general) please consult the `grains documentation
<https://docs.saltstack.com/en/latest/topics/grains/#grains-in-etc-salt-grains>`_.
.. note::
See the full `Salt Formulas installation and usage instructions
<http://docs.saltstack.com/en/latest/topics/development/conventions/formulas.html>`_.
Available states
================
.. contents::
:local:
``grains``
----------
Installs the /etc/salt/grains file and manages its content.
07070100000058000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002A00000000salt-formulas-3.0.4/grains-formula/grains07070100000059000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003000000000salt-formulas-3.0.4/grains-formula/grains/files0707010000005A000081A400000000000000000000000168EB80BB00000078000000000000000000000000000000000000003700000000salt-formulas-3.0.4/grains-formula/grains/files/grains{% set cfg_grains = salt['pillar.get']('grains', {}) -%}
{% if cfg_grains -%}
{{ cfg_grains|yaml(False) }}
{% endif -%}
0707010000005B000081A400000000000000000000000168EB80BB000000A1000000000000000000000000000000000000003300000000salt-formulas-3.0.4/grains-formula/grains/init.sls/etc/salt/grains:
file.managed:
- source:
- salt://grains/files/grains
- user: root
- group: root
- mode: '0644'
- template: jinja
0707010000005C000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/grains-formula/metadata0707010000005D000081A400000000000000000000000168EB80BB00000044000000000000000000000000000000000000003900000000salt-formulas-3.0.4/grains-formula/metadata/metadata.yml---
summary:
Salt state for managing grains
license:
Apache-2.0
0707010000005E000081A400000000000000000000000168EB80BB00000067000000000000000000000000000000000000003200000000salt-formulas-3.0.4/grains-formula/pillar.examplegrains:
roles:
- webserver
- memcache
deployment: datacenter4
cabinet: 13
cab_u: 14-15
0707010000005F000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002900000000salt-formulas-3.0.4/grains-formula/tests07070100000060000081A400000000000000000000000168EB80BB0000003F000000000000000000000000000000000000003400000000salt-formulas-3.0.4/grains-formula/tests/pillar.slsgrains:
snack: peanuts
treats:
- chocolate
- candy
07070100000061000081A400000000000000000000000168EB80BB000002D4000000000000000000000000000000000000003800000000salt-formulas-3.0.4/grains-formula/tests/test_grains.pyimport pytest
import yaml
@pytest.fixture
def file():
return '/etc/salt/grains'
def test_grains_file_exists(host, file):
with host.sudo():
exists = host.file(file).exists
assert exists is True
def test_grains_file_contents(host, file):
with host.sudo():
struct = host.file(file).content.decode('UTF-8')
data = yaml.safe_load(struct)
assert data['snack'] == 'peanuts' and data['treats'][0] == 'chocolate'
def test_grains_file_ownership(host, file):
with host.sudo():
user = host.file(file).user
assert user == 'root'
def test_grains_salt(host, file):
with host.sudo():
snack = host.salt('grains.get', 'snack', local=True)
assert snack == 'peanuts'
07070100000062000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002200000000salt-formulas-3.0.4/hosts-formula07070100000063000081A400000000000000000000000168EB80BB00000077000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/hosts-formula/README.md# Salt states for managing `hosts(5)`
## Available states
`hosts`
Enforces and writes the contents of `/etc/hosts`.
07070100000064000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002800000000salt-formulas-3.0.4/hosts-formula/hosts07070100000065000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/hosts-formula/hosts/files07070100000066000081A400000000000000000000000168EB80BB00000118000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/hosts-formula/hosts/files/additional.j2# Additional mappings
{%- for address, hosts in pillar.get('hosts', {}).items() -%}
{%- if hosts is string %}
{%- set hosts = [hosts] %}
{%- endif %}
{%- do salt.log.error(hosts) %}
{{ address }} {{ ' '.join(hosts) | indent(30 - address | length, true) }}
{%- endfor %}
07070100000067000081A400000000000000000000000168EB80BB00000186000000000000000000000000000000000000003900000000salt-formulas-3.0.4/hosts-formula/hosts/files/default.j2# Default IPv6 mappings
::1 localhost ipv6-localhost ipv6-loopback
fe00::0 ipv6-localnet
ff00::0 ipv6-mcastprefix
ff02::1 ipv6-allnodes
ff02::2 ipv6-allrouters
ff02::3 ipv6-allhosts
# Default IPv4 mappings
127.0.0.1 localhost
07070100000068000081A400000000000000000000000168EB80BB000000A9000000000000000000000000000000000000003700000000salt-formulas-3.0.4/hosts-formula/hosts/files/hosts.j2{{ salt['pillar.get']('managed_by_salt_formula', '# Managed by the hosts formula') }}
{% include 'hosts/files/default.j2' %}
{% include 'hosts/files/additional.j2' %}
07070100000069000081A400000000000000000000000168EB80BB00000403000000000000000000000000000000000000003100000000salt-formulas-3.0.4/hosts-formula/hosts/init.sls{#-
Salt state file for managing /etc/hosts
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
Copyright (C) 2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
/etc/hosts:
file.managed:
- source: salt://{{ 'infrastructure/' if salt['pillar.get']('infrastructure:hosts') else '' }}hosts/files/hosts.j2
- template: jinja
- mode: '0644'
- user: root
- group: root
0707010000006A000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002B00000000salt-formulas-3.0.4/hosts-formula/metadata0707010000006B000081A400000000000000000000000168EB80BB00000070000000000000000000000000000000000000003800000000salt-formulas-3.0.4/hosts-formula/metadata/metadata.yml---
summary:
Salt states for managing /etc/hosts
description:
Salt states for managing the /etc/hosts file.
0707010000006C000081A400000000000000000000000168EB80BB00000067000000000000000000000000000000000000003100000000salt-formulas-3.0.4/hosts-formula/pillar.examplehosts:
2001:0db8:100::10: example.com
'2001:0db8:f00:b43::':
- server.example.net
- server
0707010000006D000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002B00000000salt-formulas-3.0.4/infrastructure-formula0707010000006E000081A400000000000000000000000168EB80BB000005BF000000000000000000000000000000000000003500000000salt-formulas-3.0.4/infrastructure-formula/README.md# Infrastructure Salt states
The states in this directory extend other formulas using the `infrastructure` pillar.
The code under this directory is highly opinionated and specific to our infrastructure.
## Available states
### Libvirt:
`infrastructure.libvirt.domains`
Writes virtual machine domain definitions.
### SUSE HA:
`infrastructure.suse_ha.resources`
Configures virtual machine cluster resources.
### Salt:
The Salt states depend on the [Salt formula](https://github.com/saltstack-formulas/salt-formula) and a modified version of the [Podman formula](https://github.com/lkubb/salt-podman-formula). The latter is yet to be upstreamed.
`infrastructure.salt.master`
Configure a Salt master.
`infrastructure.salt.syndic`
Configure a Salt Syndic, which includes a Salt master.
`infrastructure.salt.minion`
Configures a Salt Minion.
`infrastructure.salt.proxy_master`
Extends a Salt Master with capabilities to manage proxy minions.
`infrastructure.salt.proxy_networkautomation`
Configures a container host to run Salt Proxy minions.
`infrastructure.salt.minion_networkautomation`
Extends a container host running Salt Proxy minions to run regular Salt Minions.
This is for managing devices using Salt states not implementing Salt Proxy operation and instead rely on modules on a regular minion to forward requests to an API.
`infrastructure.salt.scriptconfig`
Writes configuration used by Salt related scripts, currently only `salt-keydiff`.
0707010000006F000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003300000000salt-formulas-3.0.4/infrastructure-formula/_states07070100000070000081A400000000000000000000000168EB80BB00000FE3000000000000000000000000000000000000004100000000salt-formulas-3.0.4/infrastructure-formula/_states/racktables.py"""
Salt state module for managing RackTables entries
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
"""
import MySQLdb
import rtapi
import logging
from salt.exceptions import CommandExecutionError
log = logging.getLogger(__name__)
# to-do: consider moving some logic to an execution module
# to-do: update existing entry if VM specifications changed
# to-do: repair MAC address formatting bug
# won't fix: use an IPAM tool with a REST API ...
def _connect(db_host, db_user, db_secret, db):
try:
dbhandle = MySQLdb.connect(host=db_host, user=db_user, passwd=db_secret, db=db)
return(rtapi.RTObject(dbhandle))
except MySQLdb.Error as err:
log.critical("RackTables MySQL Error: %s" % str(err))
# it would be nicer to raise this without an ugly Salt exception stacktrace given Salt additionally prints the MySQL stacktrace beforehand
raise CommandExecutionError('RackTables MySQL connection failure')
def vm(db_host, db_user, db_secret, db, name, interfaces, ram, city, usage):
ret = {'name': name, 'result': True, 'changes': {}, 'comment': ''}
rt = _connect(db_host, db_user, db_secret, db)
if rt.ObjectExistName(name):
objid=rt.GetObjectId(name)
comment = 'Object exists:'
else:
# 1504 = VM
# unable to add actual NULL asset tag with AddObject
#objid = rt.AddObject(name, 1504, 'NULL', name)
objsql = "INSERT INTO Object (name,objtype_id,label) VALUES ('%s', %d, '%s')" % (name, 1504, name)
rt.db_insert(objsql)
objid = rt.GetObjectId(name)
rt.InsertLog(objid, 'Object created during SaltStack automation run')
log.debug('Configuring interfaces: %s' % str(interfaces))
for interface, ifconfig in interfaces.items():
mac = ifconfig['mac']
bridge = ifconfig['bridge']
ip4 = ifconfig.get('ip4', None)
ip6 = ifconfig.get('ip6', None)
portlabel = name[:8] + '_' + ifconfig['bridge'][:4]
rt.InterfaceAddIpv4IP(objid, interface, ip4)
rt.SetIPName(objid, ip4)
if ip6 != None:
rt.InterfaceAddIpv6IP(objid, interface, ip6)
#broken, "Column 'ip' cannot be null
#rt.SetIPName(objid, ip6)
# 24 = 1000Base-T
# Q: what is iif_id ?
portsql = "INSERT INTO Port (object_id, name, iif_id, type, l2address, label) VALUES (%d, '%s', 1, 24, '%s', '%s')" % (objid, interface, mac.replace(':', '').upper(), portlabel)
rt.db_insert(portsql)
# 11 = Server, 32 = Nuremberg, 34 = Prague, 44 = Production, 48 = Testing
tagmap = {'Nuremberg': 32, 'Prague': 34, 'Production': 44, 'Testing': 48}
tags = [11]
if city in tagmap:
tags.append(tagmap[city])
if usage in tagmap:
tags.append(tagmap[usage])
for tagid in tags:
tagsql = "INSERT INTO TagStorage (entity_realm, entity_id, tag_id, user, date) VALUES ('object', %d, %d, '%s', now())" % (objid, tagid, 'SaltStack Automation User')
rt.db_insert(tagsql)
comment = 'Object created:'
if objid:
url='https://racktables.suse.de/index.php?page=object&tab=default&object_id=' + str(objid)
ret['comment'] = comment + ' ' + url
else:
ret['comment'] = 'RackTables object creation failed'
ret['result'] = False
return(ret)
07070100000071000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure07070100000072000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004000000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/hosts07070100000073000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004600000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/hosts/files07070100000074000081A400000000000000000000000168EB80BB000002D9000000000000000000000000000000000000004F00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/hosts/files/hosts.j2{{ salt['pillar.get']('managed_by_salt_formula', '# Managed by the infrastructure formula') }}
{% include 'hosts/files/default.j2' %}
# Host mappings
{%- for interface, ifconfig in salt['pillar.get']('network:interfaces', {}).items() %}
{%- if not interface.endswith('-ur') and not '-ur-' in interface %}
{%- for address in ifconfig.get('addresses', []) %}
{%- set address = address.split('/')[0] %}
{%- if address.startswith('2a07') or salt['network.is_private'](address) %}
{{ address }} {{ ( salt['grains.get']('fqdn') ~ ' ' ~ salt['grains.get']('host') ) | indent(30 - address | length, true) }}
{%- endif %}
{%- endfor %}
{%- endif %}
{%- endfor %}
{% include 'hosts/files/additional.j2' %}
07070100000075000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004200000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/libvirt07070100000076000081A400000000000000000000000168EB80BB000000F6000000000000000000000000000000000000005200000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/libvirt/directories.slshypervisor_directories:
file.directory:
- names:
{%- for subdir in ['', 'agents', 'disks', 'domains', 'networks', 'os-images', 'nvram'] %}
- {{ salt['pillar.get']('infrastructure:kvm_topdir') }}/{{ subdir }}
{%- endfor %}
07070100000077000081A400000000000000000000000168EB80BB00001F01000000000000000000000000000000000000004E00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/libvirt/domains.sls{#-
Salt state file for managing libvirt domains
Copyright (C) 2023-2025 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set myid = grains['id'] -%}
{%- if pillar['do_vd'] | default(False) and 'delegated_orchestra' in pillar -%}
{%- do salt.log.debug('libvirt.domains: delegated from orchestration run') -%}
{%- set dopillar = pillar['delegated_orchestra'] -%}
{%- set lowpillar = dopillar['lowpillar'] -%}
{%- set domain = dopillar['domain'] -%}
{%- set cluster = dopillar['cluster'] -%}
{%- else -%}
{%- do salt.log.debug('libvirt.domains: running non-orchestrated') -%}
{%- set domain = grains['domain'] -%}
{%- set do_all_domains = salt['pillar.get']('infrastructure:libvirt:domains:do_all', false) -%}
{%- if 'virt_cluster' in grains %}
{%- set cluster = grains['virt_cluster'].replace('-bare','') -%}
{%- else %}
{%- set cluster = pillar.get('cluster') %}
{%- endif %}
{%- set lowpillar = salt['pillar.get']('infrastructure') -%}
{%- endif -%} {#- close do_vd check -#}
{%- if not 'domains' in lowpillar -%}
{%- do salt.log.error('Incomplete orchestration pillar - verify whether the orchestrator role is assigned.') -%}
{%- elif not domain in lowpillar['domains'] -%}
{%- do salt.log.error('Domain ' ~ domain ~ ' not correctly registered in pillar/domain or orchestrator role is not assigned!') -%}
{%- else -%}
{%- set clusterpillar = lowpillar['domains'][domain]['clusters'] -%}
{%- set topdir = lowpillar.get('kvm_topdir', '/kvm') -%}
{%- set domaindir = lowpillar.get('libvirt_domaindir', topdir ~ '/vm') -%}
{%- if not salt['file.file_exists']('/etc/uuidmap') %}
/etc/uuidmap:
file.touch
{%- endif %}
{%- if cluster in clusterpillar and ( not 'primary' in clusterpillar[cluster] or myid == clusterpillar[cluster]['primary'] ) %}
{%- for dname, dpillar in lowpillar['domains'].items() %}
{%- if dname == domain or do_all_domains %}
{%- for machine, config in dpillar['machines'].items() %}
{%- set machine = machine ~ '.' ~ dname %}
{%- if config['cluster'] == cluster and ( not 'node' in config or config['node'] == myid ) %}
{%- set domainxml = domaindir ~ '/' ~ machine ~ '.xml' %}
{%- if opts['test'] %}
{%- set alt_uuid = 'echo will-generate-a-new-uuid' %}
{%- else %}
{%- set alt_uuid = 'uuidgen' %}
{%- endif %}
{%- set uuid = salt['cmd.shell']('grep -oP "(?<=<uuid>).*(?=</uuid>)" ' ~ domainxml ~ ' 2>/dev/null ' ~ ' || ' ~ alt_uuid) %}
{%- do salt.log.debug('infrastructure.libvirt: uuid set to ' ~ uuid) %}
write_domainfile_{{ machine }}:
file.managed:
- template: jinja
- names:
- {{ domainxml }}:
- source: salt://files/libvirt/domains/{{ cluster }}.xml.j2
- context:
vm_uuid: {{ uuid }}
vm_name: {{ machine }}
vm_memory: {{ config['ram'] }}
vm_cores: {{ config['vcpu'] }}
vm_disks: {{ config['disks'] }}
vm_interfaces: {{ config['interfaces'] }}
vm_extra: {{ config.get('extra', {}) }}
letters: abcdefghijklmnopqrstuvwxyz
vm_uuid_map_{{ machine }}:
file.append:
- name: /etc/uuidmap
- text: '{{ machine }}: {{ uuid }}'
- unless: 'grep -q {{ machine }} /etc/uuidmap'
{%- if clusterpillar[cluster].get('storage') == 'local' and 'image' in config %}
define_domain_{{ machine }}:
module.run: {#- virt state does not support defining domains from custom XML files #}
- virt.define_xml_path:
- path: {{ domainxml }}
- onchanges:
- file: write_domainfile_{{ machine }}
{%- if 'root' in config['disks'] %}
{%- set root_disk = topdir ~ '/disks/' ~ machine ~ '_root.qcow2' %}
{%- set image = topdir ~ '/os-images/' ~ config['image'] %}
{%- set reinit = config.get('irreversibly_wipe_and_overwrite_vm_disk', False) %}
{%- if reinit is sameas true %}
destroy_machine_{{ machine }}:
virt.powered_off:
- name: {{ machine }}
{%- endif %}
write_vmdisk_{{ machine }}_root:
file.copy:
- name: {{ root_disk }}
- source: {{ image }}
{%- if reinit is sameas true %}
- force: true
{%- endif %}
{%- set disk_size = config['disks']['root'] %}
{%- set image_info = salt['cmd.run']('qemu-img info --out json ' ~ image) | load_json %}
{%- set image_size = image_info['virtual-size'] %}
{%- if disk_size.endswith('G') %}
{%- set converted_size = ( disk_size.rstrip('G') | int * 1073741824 ) | int %}
{%- else %}
{%- do salt.log.error('infrastructure.libvirt: sizes need to end with "G", illegal disk size ' ~ disk_size ~ ' for machine ' ~ machine) %}
{%- set converted_size = None %}
{%- endif %} {#- close suffix check #}
{%- if converted_size %}
{%- do salt.log.debug('infrastructure.libvirt: converted size is ' ~ converted_size) %}
{%- if converted_size > image_size %}
{%- if salt['file.file_exists'](root_disk) %}
{%- set disk_info = salt['cmd.run']('qemu-img info --out json -U ' ~ root_disk) | load_json %}
{%- set current_size = disk_info['virtual-size'] %}
{%- else %}
{%- set current_size = image_size %}
{%- endif %}
{%- do salt.log.debug('infrastructure.libvirt: current disk size is ' ~ current_size) %}
{%- if current_size < converted_size %}
resize_vmdisk_{{ machine }}_root:
cmd.run:
- name: |
{%- if machine in salt['virt.list_active_vms']() %}
virsh blockresize {{ machine }} {{ root_disk }} {{ disk_size }}
{%- else %}
qemu-img resize {{ root_disk }} {{ disk_size }}
{%- endif %}
- require:
- file: write_vmdisk_{{ machine }}_root
{%- endif %} {#- close current/converted size comparison check #}
{%- endif %} {#- close converted/image size comparison check #}
{%- endif %} {#- close converted size check #}
{%- endif %} {#- close root disk check #}
start_domain_{{ machine }}:
{%- if opts['test'] %} {#- ugly workaround to virt.running failing if the VM is not yet defined #}
test.succeed_without_changes:
- name: Will start {{ machine }} if it is not running already
{%- else %}
virt.running:
- name: {{ machine }}
- require:
- file: write_domainfile_{{ machine }}
- module: define_domain_{{ machine }}
{%- endif %}
{%- endif %} {#- close storage/image check #}
{%- endif %} {#- close cluster check #}
{%- endfor %} {#- close machine loop #}
{%- endif %} {#- close domain check #}
{%- endfor %} {#- close domain loop #}
{%- else %}
{%- do salt.log.warning('Libvirt: Skipping domain XML management due to non-primary minion ' ~ myid ~ ' in cluster ' ~ cluster) %}
{%- endif %}
{#- close domain pillar check -#}
{%- endif %}
07070100000078000081A400000000000000000000000168EB80BB00000035000000000000000000000000000000000000004B00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/libvirt/init.slsinclude:
- .packages
- .directories
- .domains
07070100000079000081A400000000000000000000000168EB80BB0000007A000000000000000000000000000000000000004F00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/libvirt/packages.slsinfrastructure_libvirt_packages:
pkg.installed:
- pkgs:
- python3-libvirt-python
- reload_modules: true
0707010000007A000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004400000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/orchestra0707010000007B000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004E00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/orchestra/deploy_vm0707010000007C000081A400000000000000000000000168EB80BB00000651000000000000000000000000000000000000005800000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/orchestra/deploy_vm/disks.sls{#-
Salt orchestration state file for managing virtual machine disks
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from slspath ~ '/map.jinja' import lockfile, ansiblegate, fqdn, disks, netapp_host, netapp_igroup_primary, netapp_vs_primary, netapp_vs_secondary, cluster -%}
check_lock:
lock.check:
- name: '{{ lockfile }}'
- failhard: True
lock:
lock.lock:
- name: '{{ lockfile }}'
- failhard: True
vm_disks:
salt.state:
- tgt: {{ ansiblegate }}
- sls:
- orchestra.vmdisks
- pillar:
delegated_orchestra:
deployhost: {{ fqdn }}
disks: {{ disks }}
netapp_host: {{ netapp_host }}
netapp_igroup_primary: {{ netapp_igroup_primary }}
netapp_vs_primary: {{ netapp_vs_primary }}
netapp_vs_secondary: {{ netapp_vs_secondary }}
cluster: {{ cluster }}
- saltenv: {{ saltenv }}
- pillarenv: {{ saltenv }}
unlock:
lock.unlock:
- name: '{{ lockfile }}'
0707010000007D000081A400000000000000000000000168EB80BB00000549000000000000000000000000000000000000005800000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/orchestra/deploy_vm/image.sls{#-
Salt orchestration state file for managing virtual machine base images
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from slspath ~ '/map.jinja' import lockfile, clusterprimary, fqdn, vmpillar -%}
check_lock:
lock.check:
- name: '{{ lockfile }}'
- failhard: True
lock:
lock.lock:
- name: '{{ lockfile }}'
- failhard: True
hypervisor_vm_disk_image:
salt.state:
- tgt: '{{ clusterprimary }}'
- sls:
- orchestra.vmimage
- pillar:
delegated_orchestra:
deployhost: {{ fqdn }}
image: {{ vmpillar['image'] }}
- saltenv: {{ saltenv }}
- pillarenv: {{ saltenv }}
- failhard: True
unlock:
lock.unlock:
- name: '{{ lockfile }}'
0707010000007E000081A400000000000000000000000168EB80BB0000081A000000000000000000000000000000000000005800000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/orchestra/deploy_vm/map.jinja{#-
Jinja variable mapping file
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set domain = pillar.domain | default(None) -%}
{%- set deployhost = pillar.target | default(None) -%}
{%- set fqdn = deployhost ~ '.' ~ domain -%}
{%- if deployhost is not none and domain is not none -%}
{%- set lockfile = 'lock_' ~ fqdn -%}
{%- set lowpillar = salt.pillar.get('orchestra') -%}
{%- set globalpillar = lowpillar['global'] -%}
{%- set racktables = lowpillar['global']['racktables'] -%}
{#- to-do: allow external deployhosts and domains to be inserted via cli pillar -#}
{%- set basepillar = lowpillar['domains'][domain] -%}
{%- set vmpillar = basepillar['machines'][deployhost] -%}
{%- set cluster = vmpillar['cluster'] -%}
{%- set clusterpillar = basepillar['clusters'][cluster] -%}
{%- set clusterprimary = clusterpillar['primary'] -%}
{%- set clustertype = clusterpillar['type'] -%}
{%- set netapppillar = clusterpillar['netapp'] -%}
{%- set netapp_host = netapppillar['host'] -%}
{%- set netapp_vs_primary = netapppillar['vs_primary'] -%}
{%- if 'vs_secondary' in netapppillar -%}
{%- set netapp_vs_secondary = netapppillar['vs_secondary'] -%}
{%- else -%}
{%- set netapp_vs_secondary = netapp_vs_primary -%}
{%- endif -%}
{%- set netapp_igroup_primary = netapppillar['igroup_primary'] -%}
{%- set disks = vmpillar['disks'] -%}
{#- to-do: fetch ansiblegate host from pillar -#}
{%- set ansiblegate = 'ansiblegate' -%}
{%- endif -%}
0707010000007F000081A400000000000000000000000168EB80BB000005EE000000000000000000000000000000000000005700000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/orchestra/deploy_vm/maps.sls{#-
Salt orchestration state file for managing LUN/multipath mapping files
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from slspath ~ '/map.jinja' import lockfile, cluster, ansiblegate, clusterprimary, netapp_host, fqdn, disks -%}
check_lock:
lock.check:
- name: '{{ lockfile }}'
- failhard: True
lock:
lock.lock:
- name: '{{ lockfile }}'
- failhard: True
hypervisor_lunmap:
salt.state:
- tgt: '{{ cluster }}*'
- sls:
- lunmap
hypervisor_mpathmap:
salt.state:
- tgt: {{ ansiblegate }}
- sls:
- orchestra.mpathmap
- pillar:
delegated_orchestra:
clusterprimary: {{ clusterprimary }}
netapphost: {{ netapp_host }}
deployhost: {{ fqdn }}
disks: {{ disks }}
- saltenv: {{ saltenv }}
- pillarenv: {{ saltenv }}
unlock:
lock.unlock:
- name: '{{ lockfile }}'
07070100000080000081A400000000000000000000000168EB80BB00000471000000000000000000000000000000000000005900000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/orchestra/deploy_vm/rescan.sls{#-
Salt orchestration state file for rescanning a SCSI bus
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from slspath ~ '/map.jinja' import lockfile, cluster -%}
check_lock:
lock.check:
- name: '{{ lockfile }}'
- failhard: True
lock:
lock.lock:
- name: '{{ lockfile }}'
- failhard: True
rescan_scsi_bus:
salt.function:
- name: cmd.run
- tgt: '{{ cluster }}*'
- arg:
- '/usr/bin/rescan-scsi-bus.sh'
unlock:
lock.unlock:
- name: '{{ lockfile }}'
07070100000081000081A400000000000000000000000168EB80BB000004DD000000000000000000000000000000000000006000000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/orchestra/deploy_vm/rescan_resize.sls{#-
Salt orchestration state file for rescanning a SCSI bus
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{#-
to-do: drop this file, add -s parameter based on pillar injected variable in rescan.sls instead
-#}
{%- from slspath ~ '/map.jinja' import lockfile, cluster -%}
check_lock:
lock.check:
- name: '{{ lockfile }}'
- failhard: True
lock:
lock.lock:
- name: '{{ lockfile }}'
- failhard: True
rescan_scsi_bus:
salt.function:
- name: cmd.run
- tgt: '{{ cluster }}*'
- arg:
- '/usr/bin/rescan-scsi-bus.sh -s'
unlock:
lock.unlock:
- name: '{{ lockfile }}'
07070100000082000081A400000000000000000000000168EB80BB000006FE000000000000000000000000000000000000005C00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/orchestra/deploy_vm/resources.sls{#-
Salt orchestration state file for managing virtual machine resources
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from slspath ~ '/map.jinja' import lockfile, clusterprimary, fqdn, lowpillar, domain, cluster, fqdn -%}
check_lock:
lock.check:
- name: '{{ lockfile }}'
- failhard: True
lock:
lock.lock:
- name: '{{ lockfile }}'
- failhard: True
hypervisor_vm_resources:
salt.state:
- tgt: '{{ clusterprimary }}'
- sls:
- infrastructure.libvirt.domains
- infrastructure.suse_ha.resources
- pillar:
do_vd: True
delegated_orchestra:
lowpillar: {{ lowpillar }}
domain: {{ domain }}
cluster: {{ cluster }}
deployhost: {{ fqdn }}
- saltenv: {{ saltenv }}
- pillarenv: {{ saltenv }}
hypervisor_vm_start:
salt.function:
- name: cmd.run
- tgt: '{{ clusterprimary }}'
- arg:
- 'crm resource status VM_{{ fqdn }} 2> >(grep -q "NOT running") && crm resource start VM_{{ fqdn }}'
- kwarg:
shell: /usr/bin/bash
- require:
- hypervisor_vm_resources
unlock:
lock.unlock:
- name: '{{ lockfile }}'
07070100000083000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003F00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt07070100000084000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004500000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files07070100000085000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004900000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/etc07070100000086000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004E00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/etc/salt07070100000087000081A400000000000000000000000168EB80BB00000196000000000000000000000000000000000000005F00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/etc/salt/schedule.conf.j2schedule:
__mine_interval: {enabled: true, function: mine.update, jid_include: true, maxrunning: 2,
minutes: 60, return_job: false, run_on_start: true}
{%- if proxy | default(False) %}
__proxy_keepalive:
enabled: true
function: status.proxy_reconnect
jid_include: true
kwargs: {proxy_name: napalm}
maxrunning: 1
minutes: 1
return_job: false
enabled: true
{%- endif %}
07070100000088000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000005100000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/etc/systemd07070100000089000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000005800000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/etc/systemd/system0707010000008A000081A400000000000000000000000168EB80BB00000537000000000000000000000000000000000000006C00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/etc/systemd/system/salt-git-gc.service# Service to clean up Git directories used by Salt.
# Copyright (C) 2023-2024 Georg Pfuetzenreuter <georg.pfuetzenreuter@suse.com>
#
# 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 <https://www.gnu.org/licenses/>.
[Unit]
Description=Git garbage collection for Salt
Documentation=https://github.com/openSUSE/salt-formulas
[Service]
Type=oneshot
User=salt
ExecStart=bash -c '\
for topdir in gitfs git_pillar ; do \
basedir=/var/cache/salt/master/$topdir ; \
if ! test -d $basedir; then exit 0; fi ; \
while read lowdir ; do echo $basedir/$lowdir ; \
git --git-dir=$basedir/$lowdir/.git gc ; done \
< <(awk "!/^#/{ print \\$1 }" $basedir/remote_map.txt) ; done'
SyslogIdentifier=git-gc
0707010000008B000081A400000000000000000000000168EB80BB000003BC000000000000000000000000000000000000006A00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/etc/systemd/system/salt-git-gc.timer# Timer to trigger the salt-git-gc service on a schedule.
# Copyright (C) 2023-2024 Georg Pfuetzenreuter <georg.pfuetzenreuter@suse.com>
#
# 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 <https://www.gnu.org/licenses/>.
[Unit]
Description=Run scheduled Git garbage collection for Salt
Documentation=https://github.com/openSUSE/salt-formulas
[Timer]
OnCalendar=daily
[Install]
WantedBy=timers.target
0707010000008C000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004900000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/srv0707010000008D000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000005100000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/srv/reactor0707010000008E000081A400000000000000000000000168EB80BB00000287000000000000000000000000000000000000006700000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/srv/reactor/update_fileserver.sls{%- set mypillar = salt['pillar.get']('infrastructure:salt:reactor', {}) -%}
{%- set target = mypillar.get('update_fileserver_ng', {}).get('target') -%}
{%- set deploy_password = mypillar.get('update_fileserver', {}).get('deploy_password', '') -%}
{%- raw -%}
{%- if data.data == "{% endraw %}{{ deploy_password }}{% raw %}" -%}
{%- endraw %}
update_fileserver:
runner.fileserver.update: []
runner.git_pillar.update: []
{%- if target %}
update_fileserver_ng:
local.state.apply:
- tgt: {{ target }}
- args:
- mods: profile.salt.git.base
{%- endif -%}
{%- raw %}
{%- endif -%}
{%- endraw -%}
0707010000008F000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004900000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/usr07070100000090000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004F00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/usr/local07070100000091000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000005400000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/usr/local/sbin07070100000092000081ED00000000000000000000000168EB80BB00000932000000000000000000000000000000000000007200000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/usr/local/sbin/create_salt_master_gpg_key.sh#!/usr/bin/bash
# Script to create a private GPG key for use with Salt
# Copyright (C) 2023-2025 SUSE LLC <ignacio.torres@suse.com>
# Copyright (C) 2025 SUSE LLC <georg.pfuetzenreuter@suse.com>
#
# 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 <https://www.gnu.org/licenses/>.
set -eu
mail="salt@${HOSTNAME}"
name="${HOSTNAME} (Salt Master Key)"
homedir="/etc/salt/gpgkeys"
help() {
echo "Generate a gpg secret key in a salt master (syndic)."
echo
echo "Options:"
echo "-m email address to identify key (default: $mail)"
echo "-n name associated to the key (default: $name)"
echo "-d GPG homedir (default: $homedir)"
}
while getopts :d:m:n:h arg; do
case ${arg} in
d) homedir="${OPTARG}" ;;
m) mail="${OPTARG}" ;;
n) name="${OPTARG}" ;;
h) help && exit ;;
*) help && exit 1 ;;
esac
done
if [ ! -d "$homedir" ]; then
mkdir -p "$homedir" && chown salt:salt "$homedir" && chmod 700 "$homedir"
fi
if sudo -u salt gpg2 --batch --homedir "$homedir" -k "$mail" >/dev/null; then
echo "A key for $mail already exists in $homedir. Aborting."
exit 1
fi
# cleanup gpg-agent
# We need the || true in case there are no processes. Check pgrep(1).
sudo -u salt pkill gpg-agent || true
sudo -u salt gpg2 --homedir "$homedir" --batch --passphrase '' --quick-generate-key "$name <$mail>" ed25519 cert 2y
fingerprint="$(sudo -u salt gpg2 --batch --homedir "$homedir" --list-options show-only-fpr-mbox --list-secret-keys "$mail" | awk '{print $1}')"
sudo -u salt gpg2 --homedir "$homedir" --batch --passphrase '' --quick-add-key "$fingerprint" cv25519 encrypt 2y
set -x
sudo -u salt gpg2 --batch --homedir "$homedir" --list-keys --keyid-format 0xlong
sudo -u salt gpg2 --batch --homedir "$homedir" --export --armor "$fingerprint"
07070100000093000081ED00000000000000000000000168EB80BB00000365000000000000000000000000000000000000005900000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/usr/local/sbin/qjid#!/bin/sh
# Script to get a Salt job output colored and paged
# Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
#
# 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 <https://www.gnu.org/licenses/>.
jid="${1?Pass a Salt job ID}"
salt-run --force-color jobs.lookup_jid "$jid" | less -R
07070100000094000081ED00000000000000000000000168EB80BB000007E9000000000000000000000000000000000000006E00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/usr/local/sbin/reset-proxy-containers.sh#!/bin/bash
# Script to reset and update all proxy containers
# Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
#
# 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 <https://www.gnu.org/licenses/>.
set -Ceu #x
POD_USER=autopod
UNITDIR=~autopod/.config/systemd/user
SRUN_ARGS=('--wait' '--user' "-M$POD_USER@" '-Pq')
SRUN="systemd-run ${SRUN_ARGS[@]}"
SCTL_ARGS=("-M$POD_USER@" '--user')
SCTL="systemctl ${SCTL_ARGS[@]}"
echo '==> Pulling images ...'
$SRUN sh -c 'podman images --format "{{.Repository}}" | sed "/<none>/d" | xargs podman pull -q'
echo '==> Fetching containers ...'
# https://github.com/containers/podman/issues/14888
CONTAINERS=($(printf '%s ' `$SRUN podman ps --no-trunc --format '{{.Names}}'`))
echo '==> Fetching services ...'
SERVICES=($(find "$UNITDIR" -type f -name '*.service' -printf '%P '))
if [ "${#SERVICES[@]}" -gt 0 ]
then
echo '==> Stopping and disabling services ...'
$SCTL disable --now ${SERVICES[@]}
fi
echo '==> Purging containers ...'
# containers should already be gracefully stopped using the systemd call above; stopping any remaining ones before removing all
$SRUN sh -c 'podman ps -aq | xargs -I ? sh -c "podman stop ? && podman rm ?" >/dev/null'
if [ "${#SERVICES[@]}" -gt 0 ]
then
echo '==> Purging services ...'
rm -r "$UNITDIR"/*
$SCTL daemon-reload
$SCTL --state not-found reset-failed
fi
echo '==> Applying highstate ...'
salt-call -lerror --state-output=mixed state.apply
echo '==> OK <=='
07070100000095000081A400000000000000000000000168EB80BB000003D8000000000000000000000000000000000000006900000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/usr/local/sbin/saltmaster-deploy.j2#!/bin/sh
# Script to call a Salt event
# Copyright (C) 2017-2024 openSUSE contributors
# Original author: Theo Chatzimichos <tampakrap@opensuse.org>
#
# 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 <https://www.gnu.org/licenses/>.
{%- set deploy_password = salt['pillar.get']('infrastructure:salt:reactor:update_fileserver:deploy_password', '') %}
salt-call event.fire_master {{ deploy_password }} salt/fileserver/gitfs/update
07070100000096000081ED00000000000000000000000168EB80BB00000635000000000000000000000000000000000000006F00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/files/usr/local/sbin/state-apply-super-async.sh#!/bin/sh
# This creates a single async state.apply job for each given host.
# Prevents congested jobs when targeting too many hosts. If that does not convince you, it makes it easier to look up results for a specific host.
#
# Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
#
# 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 <https://www.gnu.org/licenses/>.
state="$1"
input="$2"
log="$(mktemp -p /tmp superasync-XXXXX)"
count=0
if [ -z "$1" ]
then
echo 'Please specify a state to apply.'
exit 1
fi
if [ -z "$input" ]
then
echo 'No input file specified, reading from standard input, type "done" or press Ctrl+D when done ...'
input='/dev/stdin'
fi
while read host
do
if [ "$host" = 'done' ]
then
break
fi
printf 'Job for %s ...\t' "$host"
jid="$(salt --async --out=quiet --show-jid "$host" state.apply "$state" | cut -d':' -f2)"
count="$((count + 1))"
echo "$jid" >> "$log"
printf '%s\n' "$jid"
done < "$input"
sed -i -e 's/ //' "$log"
printf 'Created %s jobs, wrote JIDs to %s\n' "$count" "$log"
07070100000097000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004300000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/git07070100000098000081A400000000000000000000000168EB80BB0000026F000000000000000000000000000000000000005000000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/git/formulas.sls{%- from 'infrastructure/salt/map.jinja' import git, formulas -%}
{%- if 'repository' in formulas %}
{%- set branch = formulas.get('branch', git.branch) %}
salt_formula_clone:
git.latest:
- name: {{ formulas['repository'] }}
- target: {{ formulas.directory }}
- branch: {{ branch }}
- rev: {{ branch }}
{#- test apply fails if the user does not yet exist #}
{%- if not opts['test'] or salt['user.info'](git.user) %}
- user: {{ git.user }}
{%- endif %}
- force_checkout: true
- force_clone: true
- force_reset: true
- fetch_tags: false
- submodules: true
{%- endif %}
07070100000099000081A400000000000000000000000168EB80BB0000038C000000000000000000000000000000000000004C00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/git/init.sls{%- from 'infrastructure/salt/map.jinja' import git, formulas -%}
salt_git_user:
user.present:
- name: {{ git.user }}
- usergroup: false
- fullname: Git Cloney
- system: true
- home: /var/lib/{{ git.user }}
- createhome: false
- shell: /sbin/nologin
salt_git_directory:
file.directory:
- names:
- {{ git.directory }}
{%- if 'repository' in formulas %}
- {{ formulas.directory }}
{%- endif %}
- user: {{ git.user }}
- group: salt
- require:
- user: salt_git_user
{%- if 'repository' in formulas %}
- require_in:
- git: salt_formula_clone
{%- endif %}
{%- for l in ['salt', 'pillar'] %}
salt_git_link_{{ l }}:
file.symlink:
- name: /srv/{{ l }}
- target: {{ git.directory }}/{{ l }}
- force: true
- require:
- file: salt_git_directory
{%- endfor %}
include:
- .formulas
0707010000009A000081A400000000000000000000000168EB80BB0000045A000000000000000000000000000000000000004900000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/map.jinja{#-
Jinja variables file for the infrastructure.salt formula
Copyright (C) 2024 Georg Pfuetzenreuter <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- load_yaml as defaults -%}
git:
branch: production
user: cloneboy
directory: /srv/salt-git
formulas:
directory: /srv/formulas
{%- endload -%}
{%- set config = salt.pillar.get('infrastructure:salt', default=defaults, merge=True, merge_nested_lists=False) -%}
{%- set git = config.get('git', {}) -%}
{%- set formulas = git.get('formulas', {}) -%}
0707010000009B000081A400000000000000000000000168EB80BB0000098B000000000000000000000000000000000000004A00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/master.sls{%- set extrascriptdir = '/usr/local/sbin/' -%}
{%- set extrascripts = ['qjid', 'state-apply-super-async.sh'] -%}
{%- set extrapackages = ['salt-keydiff'] + salt['pillar.get']('infrastructure:salt:formulas', []) -%}
include:
- salt.master
salt_master_extra_scripts:
file.managed:
- names:
- {{ extrascriptdir }}saltmaster-deploy:
- source: salt://infrastructure/salt/files{{ extrascriptdir }}saltmaster-deploy.j2
- template: jinja
- mode: '0700'
{%- for file in extrascripts %}
- {{ extrascriptdir }}{{ file }}:
- source: salt://infrastructure/salt/files{{ extrascriptdir }}{{ file }}
- mode: '0755'
{%- endfor %}
salt_master_extra_packages:
pkg.installed:
- pkgs:
{%- for package in extrapackages %}
- {{ package }}
{%- endfor %}
/srv/reactor:
file.recurse:
- source: salt://infrastructure/salt/files/srv/reactor
- template: jinja
{%- if salt['pillar.get']('infrastructure:salt:master:git_gc', False) %}
salt_git_gc:
file.managed:
- names:
{%- for suffix in ['timer', 'service'] %}
{%- set unit = '/etc/systemd/system/salt-git-gc.' ~ suffix %}
- {{ unit }}:
- source: salt://{{ slspath }}/files/{{ unit }}
{%- endfor %}
service.running:
- name: salt-git-gc.timer
- enable: true
{%- endif %}
{%- set gpg_script = '/usr/local/sbin/create_salt_master_gpg_key.sh' %}
{%- if salt['pillar.get']('infrastructure:salt:master:gpg', True) %}
{%- set id = grains['id'] %}
/etc/salt/gpgkeys:
file.directory:
- user: salt
- group: salt
- dir_mode: '0700'
- file_mode: '0600'
- recurse:
- user
- group
- mode
install_gpg_bootstrap_script:
file.managed:
- name: {{ gpg_script }}
- source: salt://{{ slspath }}/files{{ gpg_script }}
- mode: '0750'
run_gpg_bootstrap_script:
cmd.run:
- name: {{ gpg_script }} -m salt@{{ id }} -n '{{ id }} (Salt Master Key)'
- unless: gpg2 --homedir /etc/salt/gpgkeys -k salt@{{ id }} 1>/dev/null 2>/dev/null
- require:
- file: install_gpg_bootstrap_script
- watch_in:
- file: /etc/salt/gpgkeys
{%- else %}
{%- if salt['file.directory_exists']('/etc/salt/gpgkeys') %}
salt_gpg_backup:
file.copy:
- name: /etc/salt/gpgkeys.bak
- source: /etc/salt/gpgkeys
salt_gpg_purge:
file.absent:
- names:
- {{ gpg_script }}
- /etc/salt/gpgkeys
{%- endif %}
{%- endif %}
0707010000009C000081A400000000000000000000000168EB80BB00000024000000000000000000000000000000000000004A00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/minion.slsinclude:
- grains
- salt.minion
0707010000009D000081A400000000000000000000000168EB80BB00000576000000000000000000000000000000000000005C00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/minion_networkautomation.sls{%- set highpillar = salt['pillar.get']('infrastructure:salt', {}) -%}
{%- set mypillar = highpillar.get('odd_proxy', {}) -%}
{%- if mypillar %}
include:
- .proxy_networkautomation
salt_odd_proxy_config:
file.managed:
- names:
- /etc/salt-pod/minion:
- contents: |
# Managed by Salt
{%- if 'master' in mypillar %}
master:
{%- for master in mypillar['master'] %}
- {{ master }}
{%- endfor %}
{%- endif %}
log_level: info
saltenv: {{ highpillar.get('saltenv', 'production_network') }}
grains:
odd_lobster: true
{%- if 'domain' in mypillar %}
domain: {{ mypillar['domain'] }}
{%- endif %}
- /etc/salt-pod/minion_schedule.conf:
- source: salt://{{ slspath }}/files/etc/salt/schedule.conf.j2
- template: jinja
{%- if 'minions' in mypillar %}
- watch_in:
{%- for minion in mypillar['minions'] %}
- user_service: Container {{ minion }} is running
- podman: Container {{ minion }} is present
{%- endfor %}
{%- endif %}
- require:
- file: salt_pod_dirs
{%- else %}
salt_odd_proxy_fail:
test.fail_without_changes:
- name: infrastructure:salt:odd_proxy pillar is empty, refusing to configure
{%- endif %} {#- close pillar check -#}
0707010000009E000081A400000000000000000000000168EB80BB00000243000000000000000000000000000000000000004A00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/podman.slssalt_pod_user:
group.present:
- name: autopod
- system: True
- gid: 900
user.present:
- name: autopod
- system: True
- home: /var/lib/autopod
- uid: 900
- gid: 900
- require:
- group: autopod
- require_in:
- User session for autopod is initialized at boot
cmd.run:
- name: 'usermod --add-subuids 100000-165535 --add-subgids 100000-165535 autopod'
- onchanges:
- group: autopod
- user: autopod
- require_in:
- User session for autopod is initialized at boot
include:
- podman.containers
0707010000009F000081A400000000000000000000000168EB80BB0000024F000000000000000000000000000000000000005000000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/proxy_master.sls{%- set domain = grains['domain'] %}
{%- set proxydomain = salt['pillar.get']('infrastructure:salt:proxy_domains:' ~ domain) -%}
{%- set pkidir = '/etc/salt/pki/master/minions/' %}
{%- if proxydomain | length and 'certificate' in proxydomain and 'minions' in proxydomain %}
salt_proxy_preseed:
file.managed:
- user: salt
- group: salt
- mode: '0644'
- names:
{%- for minion in proxydomain['minions'] %}
- {{ pkidir }}{{ minion }}:
- contents: |
{{ proxydomain['certificate'] | base64_decode | indent(12) }}
{%- endfor %}
{%- endif %}
070701000000A0000081A400000000000000000000000168EB80BB00000E90000000000000000000000000000000000000005B00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/proxy_networkautomation.sls{%- set highpillar = salt['pillar.get']('infrastructure:salt', {}) -%}
{%- set mypillar = highpillar.get('proxy', {}) -%}
{%- if mypillar %}
include:
- .podman
salt_pod_dirs:
file.directory:
- makedirs: True
- user: root
- group: autopod
- mode: '0750'
- names:
- /etc/salt-pod/pki/minion
- /etc/salt-pod/pki/proxy
- /etc/salt-pod/proxy.d
- require:
- user: salt_pod_user
salt_podpki_files:
file.managed:
- user: root
- group: autopod
- names:
{%- for mode in ['minion', 'proxy'] %}
- /etc/salt-pod/pki/{{ mode }}/minion_master.pub:
- contents_pillar: 'infrastructure:salt:proxy:podpki:master'
- mode: '0644'
- /etc/salt-pod/pki/{{ mode }}/minion.pub:
- contents_pillar: 'infrastructure:salt:proxy:podpki:crt'
- mode: '0644'
- /etc/salt-pod/pki/{{ mode }}/minion.pem:
- contents_pillar: 'infrastructure:salt:proxy:podpki:key'
- mode: '0440'
{%- endfor %}
- /etc/motd:
- contents:
- This machine runs multiple Salt minion and proxy instances in individual containers.
- Use `systemctl -Mautopod@ --user <start|stop|restart|status> [pattern]` to manage containers.
- Use `journalctl [-t pattern]` to inspect container logs.
- Do not modify Podman containers under the autopod@ user manually, they are managed by Salt.
- require:
- salt_pod_user
- file: salt_pod_dirs
salt_pod_proxy_config:
file.managed:
- names:
- /etc/salt-pod/proxy:
- contents: |
# Managed by Salt
{%- if 'master' in mypillar %}
master:
{%- for master in mypillar['master'] %}
- {{ master }}
{%- endfor %}
{%- endif %}
log_level: info
saltenv: {{ highpillar.get('saltenv', 'production_network') }}
grains:
lobster: true
{%- if 'domain' in mypillar %}
domain: {{ mypillar['domain'] }}
{%- endif %}
{%- if 'site' in mypillar %}
site: {{ mypillar['site'] }}
{%- endif %}
- /etc/salt-pod/proxy_schedule.conf:
- source: salt://{{ slspath }}/files/etc/salt/schedule.conf.j2
- template: jinja
- context:
proxy: True
{%- if 'minions' in mypillar %}
- watch_in:
{%- for minion in mypillar['minions'] %}
- user_service: {{ minion }}
- podman: Container {{ minion }} is present
{%- endfor %}
{%- endif %}
- require:
- file: salt_pod_dirs
{%- if 'minions' in mypillar %}
{%- for minion in mypillar['minions'] %}
salt_proxy_config_dir_{{ minion }}:
file.directory:
- name: /etc/salt-pod/proxy.d/{{ minion }}
- require:
- file: salt_pod_dirs
salt_proxy_config_{{ minion }}:
file.symlink:
- name: /etc/salt-pod/proxy.d/{{ minion }}/_schedule.conf
- target: /etc/salt/proxy_schedule.conf
- require:
- file: salt_pod_dirs
- file: salt_proxy_config_dir_{{ minion }}
{%- if 'minions' in mypillar %}
- watch_in:
{%- for minion in mypillar['minions'] %}
- user_service: {{ minion }}
- podman: Container {{ minion }} is present
{%- endfor %}
{%- endif %}
{%- endfor %}
{%- endif %}
salt_proxy_scripts:
file.managed:
- name: /usr/local/sbin/reset-proxy-containers.sh
- source: salt://{{ slspath }}/files/usr/local/sbin/reset-proxy-containers.sh
- mode: '0750'
{%- else %}
salt_proxy_nw_autom_fail:
test.fail_without_changes:
- name: infrastructure:salt:proxy pillar is empty, refusing to configure
{%- endif %} {#- close pillar check -#}
070701000000A1000081A400000000000000000000000168EB80BB0000023F000000000000000000000000000000000000005000000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/scriptconfig.sls{%- set file = '/etc/salt-scriptconfig' -%}
{%- set mypillar = salt['pillar.get']('infrastructure:salt:scriptconfig', {}) -%}
{%- if 'partner' in mypillar %}
{{ file }}_file:
file.managed:
- name: {{ file }}
- replace: false
{{ file }}_values:
file.keyvalue:
- name: {{ file }}
- append_if_not_found: true
- key_values:
{%- for option in ['partner', 'ssh_key'] %}
{%- if option in mypillar %}
{{ option }}: {{ mypillar[option] }}
{%- endif %}
{%- endfor %}
- require:
- {{ file }}_file
{%- endif %}
070701000000A2000081A400000000000000000000000168EB80BB00000025000000000000000000000000000000000000004A00000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/salt/syndic.slsinclude:
- salt.syndic
- .master
070701000000A3000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004200000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/suse_ha070701000000A4000081A400000000000000000000000168EB80BB000010FC000000000000000000000000000000000000005000000000salt-formulas-3.0.4/infrastructure-formula/infrastructure/suse_ha/resources.sls{#-
Salt state file for managing virtual machine resources in a SUSE HA cluster
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'suse_ha/macros.jinja' import ha_resource -%}
{#- virtual machine resources below are constructed if instructed by the deploy_vm orchestration state #}
{%- if pillar['do_vd'] | default(False) and 'delegated_orchestra' in pillar %}
{%- do salt.log.debug('suse_ha.resources: delegated from orchestration run') -%}
{%- set dopillar = pillar['delegated_orchestra'] -%}
{%- set lowpillar = dopillar['lowpillar'] -%}
{%- set domain = dopillar['domain'] -%}
{%- else %}
{%- do salt.log.debug('suse_ha.resources: running non-orchestrated') -%}
{%- set lowpillar = salt['pillar.get']('infrastructure') -%}
{%- set domain = grains['domain'] -%}
{%- set do_all_domains = salt['pillar.get']('infrastructure:libvirt:domains:do_all', false) -%}
{%- endif %}
{%- set myid = grains['id'] -%}
{%- if 'virt_cluster' in grains %}
{%- set cluster = grains['virt_cluster'].replace('-bare','') -%}
{%- else %}
{%- set cluster = pillar.get('cluster') %}
{%- endif %}
{%- if not 'domains' in lowpillar -%}
{%- do salt.log.error('Incomplete orchestration pillar - verify whether the orchestrator role is assigned.') -%}
{%- elif not domain in lowpillar['domains'] -%}
{%- do salt.log.error('Domain ' ~ domain ~ ' not correctly registered in pillar/domain or orchestrator role is not assigned!') -%}
{%- else -%}
{%- for dname in lowpillar['domains'] %}
{%- if dname == domain or do_all_domains %}
{%- set domainpillar = lowpillar['domains'][dname] -%}
{%- set clusterpillar = domainpillar['clusters'] -%}
{%- set machinepillar = domainpillar['machines'] -%}
{%- set topdir = lowpillar.get('kvm_topdir', '/kvm') -%}
{%- set domaindir = lowpillar.get('libvirt_domaindir', topdir ~ '/vm') -%}
{%- if cluster in clusterpillar and myid == clusterpillar[cluster]['primary'] %}
{%- if machinepillar is not none %}
{%- for machine, config in machinepillar.items() %}
{%- set machine = machine ~ '.' ~ dname %}
{%- do salt.log.debug(machine) %}
{%- if config['cluster'] == cluster %}
{%- set instance_attributes = {
'config': domaindir ~ '/' ~ machine ~ '.xml',
'hypervisor': 'qemu:///system',
'migrate_options': '--auto-converge',
'autoset_utilization_cpu': 'true',
'autoset_utilization_hv_memory': 'true',
'migration_transport': 'tcp',
'migrateport': 16509
} %}
{%- set operations = {
'monitor': {'timeout': 30, 'interval': 10},
'start': {'timeout': 90, 'interval': 0},
'stop': {'timeout': 90, 'interval': 0},
'migrate_to': {
'timeout': 600,
'interval': 0,
'on-fail': 'block',
},
'migrate_from': {
'timeout': 550,
'interval': 0,
'on-fail': 'block',
}
} %}
{%- set meta_attributes = {
'target-role': 'Started',
'priority': 0,
'migration-threshold': 0
} %}
{{ ha_resource('VM_' ~ machine, 'ocf', 'VirtualDomain', instance_attributes, operations, meta_attributes, 'heartbeat', requires=None) }}
{%- endif %}
{%- endfor %}
{%- endif %}
{%- endif %}
{%- endif %}
{%- endfor %}
{%- endif %}
070701000000A5000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003400000000salt-formulas-3.0.4/infrastructure-formula/metadata070701000000A6000081A400000000000000000000000168EB80BB0000009A000000000000000000000000000000000000004100000000salt-formulas-3.0.4/infrastructure-formula/metadata/metadata.yml---
summary:
Salt states specific to the openSUSE/SUSE infrastructures
description:
Custom Salt states specific to the openSUSE/SUSE infrastructures.
070701000000A7000081A400000000000000000000000168EB80BB00000A95000000000000000000000000000000000000003E00000000salt-formulas-3.0.4/infrastructure-formula/pillar.example.yml---
# yamllint disable rule:line-length
infrastructure:
# base directory for various VM related data, default is "/kvm"
kvm_topdir: /data/kvm
# directory for Libvirt VM definitions (XML files), default is "/kvm/vm"
libvirt_domaindir: /data/kvm/domains
libvirt:
domains:
# if set to false (the default), only domain definitions for machines under the domain matching the domain grain are managed
# if set to true, domain definitions for machines under all infrastructure:domains are managed
do_all: true
domains:
example.com:
clusters:
examplecluster:
# noop?
external: false
# in multi-node clusters with shared storage, VM operations will only be executed on the primary node - this needs to match its minion ID
primary: examplenode1
netapp:
host: 192.0.2.1:8080
vs_primary: examplessdvs
vs_secondary: examplehddvs
igroup_primary: exampleigroup
# *RT
city: Vienna
# "local" storage will cause VMs to use local qcow images for storage. otherwise FC (NetApp) storage is assumed.
storage: local
machines:
examplevm:
# *RT
usage: Testing
# cluster this VM should run on, needs to match one of the keys underneath "clusters"
cluster: examplecluster
interfaces:
eth0:
ip4: 192.0.2.10
ip6: '2001:DB8:2:10::'
mac: '00:00:5E:00:53:00'
bridge: examplebridge
disks:
# disk sizes need to end with "G"
# one disk named "root" is mandatory, others can use arbitrary names
root: 15G
data0: 20G
# memory size is currently only passed to downstream Libvirt domain templates
ram: 1024MB
vcpu: 1
# reference to a file or symlink in $kvm_topdir/os-images.
image: example.raw
hosts: true # if enabled, the hosts formula will use the infrastructure template for rendering /etc/hosts
salt:
git:
# default user and directory
user: cloneboy
directory: /srv/salt-git
formulas:
# default directory
directory: /srv/formulas
# example repository (no formulas will be cloned unless defined in the pillar)
repository: https://code.opensuse.org/heroes/salt-formulas-git.git
master:
# if set to true (the default), a GPG key will be managed for the Salt Master
# if set to false, no GPG key will be managed, and an existing /etc/salt/gpgkeys will be purged
gpg: true
# *RT: attribute is only used for updating RackTables
070701000000A8000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003200000000salt-formulas-3.0.4/infrastructure-formula/python070701000000A9000081A400000000000000000000000168EB80BB00000021000000000000000000000000000000000000003D00000000salt-formulas-3.0.4/infrastructure-formula/python/.gitignore*.egg-info
__pycache__
dist
venv
070701000000AA000081A400000000000000000000000168EB80BB000002DE000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/infrastructure-formula/python/README.md# Python pillar helpers
A Python library to be used in `#!py` pillar SLS files. It allows for rendering of formula pillars based off data in YAML datasets.
The logic is opinionated and specific to the architecture of our code based infrastructure.
## Usage
Example pillar file `network.sls`:
```
#!py
from opensuse_infrastructure_formula.pillar import network
def run():
return network.generate_network_pillar(
[
'iac_experts.example.com',
],
__grains__['domain'],
__grains__['host'],
)
```
All modules are aimed to be named by the top level pillar they generate and contain a generation function which directly returns the relevant Python data structure.
070701000000AB000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003700000000salt-formulas-3.0.4/infrastructure-formula/python/main070701000000AC000081A400000000000000000000000168EB80BB000002F4000000000000000000000000000000000000004300000000salt-formulas-3.0.4/infrastructure-formula/python/main/__init__.py"""
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
"""
from .__version__ import __version__ as __version__
070701000000AD000081A400000000000000000000000168EB80BB000002D4000000000000000000000000000000000000004600000000salt-formulas-3.0.4/infrastructure-formula/python/main/__version__.py"""
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
"""
__version__ = '1.0'
070701000000AE000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003900000000salt-formulas-3.0.4/infrastructure-formula/python/pillar070701000000AF000081A400000000000000000000000168EB80BB00000319000000000000000000000000000000000000004500000000salt-formulas-3.0.4/infrastructure-formula/python/pillar/__init__.py"""
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
"""
from opensuse_infrastructure_formula.__version__ import \
__version__ as __version__
070701000000B0000081A400000000000000000000000168EB80BB000014DA000000000000000000000000000000000000004B00000000salt-formulas-3.0.4/infrastructure-formula/python/pillar/infrastructure.py"""
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
"""
from logging import getLogger
from yaml import safe_load
root = '/srv/salt-git/pillar'
log = getLogger(__name__).debug
def generate_infrastructure_pillar(enabled_domains):
pillar = {
'infrastructure': {
'domains': {},
}
}
for domain in enabled_domains:
pillar['infrastructure']['domains'][domain] = {
'clusters': {},
'machines': {},
}
domainpillar = pillar['infrastructure']['domains'][domain]
domaindir = f'{root}/domain/'
mydomaindir = f'{domaindir}{domain.replace(".", "_")}'
msg = f'Parsing domain {domain}'
log(f'{msg} ...')
domaindata = {
'clusters': {},
'hosts': {},
'inherited_clusters': {},
}
for file in ['clusters', 'hosts']:
with open(f'{mydomaindir}/{file}.yaml') as fh:
domaindata[file] = safe_load(fh)
for cluster, clusterconfig in domaindata['clusters'].items():
log(f'{msg} => cluster {cluster} ...')
if 'delegate_to' in clusterconfig:
delegated_domain = clusterconfig['delegate_to']
with open(f'{domaindir}/{delegated_domain}/clusters.yaml') as fh:
domaindata['inherited_clusters'].update({
delegated_domain: safe_load(fh),
})
if cluster in domaindata['inherited_clusters']:
clusterconfig = domaindata['inherited_clusters'][cluster]
else:
log(f'Delegation of cluster {cluster} to {delegated_domain} is not possible!')
clusterpillar = {
'storage': clusterconfig['storage'],
}
if 'primary_node' in clusterconfig:
clusterpillar['primary'] = clusterconfig['primary_node']
if 'netapp' in clusterconfig:
clusterpillar.update({
'netapp': clusterconfig['netapp'],
})
log(clusterpillar)
domainpillar['clusters'][cluster] = clusterpillar
for host, hostconfig in domaindata['hosts'].items():
log(f'{msg} => host {host} ...')
hostpillar = {
'cluster': hostconfig['cluster'],
'disks': hostconfig.get('disks', {}),
'extra': {
'legacy': hostconfig.get('legacy_boot', False),
},
'image': hostconfig.get('image', 'admin-minimal-latest'),
'interfaces': {},
'ram': hostconfig['ram'],
'vcpu': hostconfig['vcpu'],
}
if 'node' in hostconfig:
node = hostconfig['node']
# the node key is compared against the hypervisor minion ID, which is always a FQDN in our infrastructure
if '.' in node:
hostpillar['node'] = node
else:
hostpillar['node'] = f'{node}.{domain}'
hostinterfaces = hostconfig.get('interfaces', {})
ip4 = hostconfig.get('ip4')
ip6 = hostconfig.get('ip6')
if not ip4 and not ip6 and hostinterfaces:
if 'primary_interface' in hostconfig:
interface = hostconfig['primary_interface']
elif len(hostinterfaces) == 1:
interface = next(iter(hostinterfaces))
else:
interface = 'eth0'
if interface in hostinterfaces:
ip4 = hostinterfaces[interface].get('ip4')
ip6 = hostinterfaces[interface].get('ip6')
hostpillar['ip4'] = ip4
hostpillar['ip6'] = ip6
for interface, ifconfig in hostinterfaces.items():
iftype = ifconfig.get('type', 'direct')
ifpillar = {
'mac': ifconfig['mac'],
'type': iftype,
'source': ifconfig['source'] if 'source' in ifconfig else f'x-{interface}',
}
if iftype == 'direct':
ifpillar['mode'] = ifconfig.get('mode', 'bridge')
for i in [4, 6]:
ipf = f'ip{i}'
if ipf in ifconfig:
ifpillar[ipf] = ifconfig[ipf]
hostpillar['interfaces'][interface] = ifpillar
log(hostpillar)
domainpillar['machines'][host] = hostpillar
return pillar
070701000000B1000081A400000000000000000000000168EB80BB00001F0C000000000000000000000000000000000000004A00000000salt-formulas-3.0.4/infrastructure-formula/python/pillar/juniper_junos.py"""
Copyright (C) 2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
"""
from bisect import insort
from ipaddress import ip_network
from logging import getLogger
from pathlib import PosixPath
import yaml
root = '/srv/salt-git/pillar'
log = getLogger(__name__)
def generate_juniper_junos_pillar(enabled_domains, minion_id, spacemap):
minion = minion_id.replace('LAB-', '')
data = {'networks': {}, 'switching': {}}
config = {}
log.debug('Starting juniper_junos pillar construction ...')
minion_s = minion.split('-')
if len(minion_s) != 4:
log.error('Cannot parse minion ID')
return {}
space = minion_s[1].lower()
log.debug(f'Minion space set to "{space}"')
for domain in enabled_domains:
domain_space = domain.split('.')[0]
if domain_space in spacemap:
domain_space = spacemap[domain_space]
log.debug(f'Domain space set to "{domain_space}"')
domain = domain.replace('.', '_')
for dataset in data.keys():
log.debug(f'Scanning domain {domain}, dataset {dataset} ...')
file = f'{root}/domain/{domain}/{dataset}.yaml'
if PosixPath(file).is_file():
with open(file) as fh:
if dataset == 'switching':
log.debug('Updating data ...')
data[dataset].update(yaml.safe_load(fh))
elif dataset == 'networks':
log.debug('Not updating data, scanning networks ...')
for network, nwconfig in yaml.safe_load(fh).items():
done = False
for existing_network, existing_nwconfig in data[dataset].items():
if network == existing_network or nwconfig.get('id') == existing_nwconfig.get('id'):
mynetwork = existing_network
log.debug(f'Mapping network {network} to existing network {mynetwork}')
if nwconfig.get('description') != data[dataset][mynetwork].get('description'):
log.warning(f'Conflicting descriptions in network {mynetwork}')
if nwconfig.get('id') != data[dataset][mynetwork].get('id'):
log.error(f'Conflicting ID: {network} != {mynetwork}, refusing to continue!')
return {}
if 'groups' not in data[dataset][mynetwork]:
data[dataset][mynetwork]['groups'] = []
for group in nwconfig.get('groups', []):
insort(data[dataset][mynetwork]['groups'], group)
done = True
break
if not done:
if space == domain_space:
log.debug(f'Creating new network {network}')
data[dataset][network] = nwconfig
else:
log.debug(f'Ignoring network {network}')
else:
log.warning(f'File {file} does not exist.')
if minion in data['switching']:
config.update(data['switching'][minion])
else:
return {}
log.debug(f'Constructing juniper_junos pillar for {minion}')
vlids = []
groups = {}
for interface, ifconfig in config.get('interfaces', {}).items():
log.debug(f'Parsing interface {interface} ...')
for vlid in ifconfig.get('vlan', {}).get('ids', []):
if vlid not in vlids:
vlids.append(vlid)
group = None
if 'group' in ifconfig:
group = ifconfig['group']
elif 'addresses' in ifconfig:
group = '__lonely'
elif 'vlan' in ifconfig and 'all' in ifconfig['vlan'].get('ids', []):
group = '__all'
if group:
if group not in groups:
groups.update({group: {'interfaces': [], 'networks': []}}) # noqa 206
log.debug(f'Appending interface {interface} to group {group}')
groups[group]['interfaces'].append(interface)
group_names = groups.keys()
for network, nwconfig in data['networks'].items():
matching_groups = [group for group in nwconfig.get('groups', []) if group in group_names]
if nwconfig['id'] in vlids or any(matching_groups) or network.startswith(('ICCL_', 'ICCP_')):
log.debug(f'Adding network {network} to config ...')
if 'vlans' not in config:
config.update({'vlans': {}})
if network not in config['vlans']:
config['vlans'].update({network: {}})
config['vlans'][network].update({'id': nwconfig['id']})
if 'description' in nwconfig:
config['vlans'][network].update({'description': nwconfig['description']})
for group in matching_groups:
groups[group]['networks'].append(network)
for group, members in groups.items():
for interface in members['interfaces']:
ifconfig = config['interfaces'][interface]
unit = 0
if '.' in interface:
ifsplit = interface.split('.')
ifname = ifsplit[0]
ifsuffix = ifsplit[1]
if ifsuffix.isdigit():
unit = int(ifsuffix)
if 'units' not in ifconfig:
ifconfig.update({'units': {}})
if unit not in ifconfig['units']:
ifconfig['units'].update({unit: {}})
if members['networks']:
ifconfig['units'][unit].update({'vlan': {'ids': [], 'type': ifconfig.get('vlan', {}).get('type', 'access')}}) # noqa 206
for network in members['networks']:
if config['vlans'][network]['id'] not in ifconfig['units'][unit]['vlan']['ids']:
insort(ifconfig['units'][unit]['vlan']['ids'], config['vlans'][network]['id'])
if 'addresses' in ifconfig:
for address in ifconfig['addresses']:
address_version = ip_network(address, False).version
if address_version == 4:
family = 'inet'
elif address_version == 6:
family = 'inet6'
else:
log.error(f'Illegal address: {address}')
if family not in ifconfig['units'][unit]:
ifconfig['units'][unit].update({family: {'addresses': []}}) # noqa 206
ifconfig['units'][unit][family]['addresses'].append(address)
del ifconfig['addresses']
elif group == '__all':
ifconfig['units'][unit].update({'vlan': {'ids': ['all'], 'type': ifconfig.get('vlan', {}).get('type', 'trunk')}}) # noqa 206
if unit > 0:
config['interfaces'][ifname] = config['interfaces'].pop(interface)
log.debug(f'Returning juniper_junos pillar for {minion}: {config}')
return {'juniper_junos': config}
070701000000B2000081A400000000000000000000000168EB80BB00001604000000000000000000000000000000000000004400000000salt-formulas-3.0.4/infrastructure-formula/python/pillar/network.py"""
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
"""
from logging import getLogger
from yaml import safe_load
root = '/srv/salt-git/pillar'
log = getLogger(__name__).debug
def generate_network_pillar(enabled_domains, domain, host):
if domain not in enabled_domains:
return {}
domaindir = f'{root}/domain/{domain.replace(".", "_")}'
msg = f'common.network, host {host}:'
domaindata = {
'hosts': {},
'networks': {},
}
for file in domaindata.keys():
with open(f'{domaindir}/{file}.yaml') as fh:
domaindata[file] = safe_load(fh)
# machine not in hosts, can be an error or expected (for example because the machine is bare metal or in an unsupported location)
if host not in domaindata['hosts']:
return {}
pillar = {}
hostconfig = domaindata['hosts'][host]
ifconfig = hostconfig.get('interfaces', {})
if 'primary_interface' in hostconfig:
primary_interface = hostconfig['primary_interface']
elif len(ifconfig) == 1:
primary_interface = next(iter(ifconfig))
else:
interface_candidates = [interface for interface in ifconfig.keys() if not interface.endswith('-ur')]
if len(interface_candidates) == 1:
primary_interface = next(iter(interface_candidates))
else:
primary_interface = 'eth0'
log(f'{msg} primary interface set to {primary_interface}')
if primary_interface in ifconfig and ( 'ip4' in ifconfig[primary_interface] or 'ip6' in ifconfig[primary_interface] ):
ip4 = ifconfig[primary_interface].get('ip4')
ip6 = ifconfig[primary_interface].get('ip6')
# if an interface name contains a hyphen, it can be assumed to match our short VLAN nameing convention
if '-' in primary_interface:
shortnet = primary_interface
# alternatively assess the network segment based off the source (hypervisor) interface
else:
shortnet = ifconfig[primary_interface].get('source', '').replace('x-', '')
log(f'{msg} shortnet set to {shortnet}')
# if no primary interface is available, find and apply addresses defined outside of an "interfaces" block (single interface hosts might use this)
else:
log(f'{msg} trying to use generic interface addresses')
ip4 = hostconfig.get('ip4')
ip6 = hostconfig.get('ip6')
ifconfig = {}
shortnet = None
ifconfig.pop(primary_interface, None)
log(f'{msg} primary IPv4 address set to {ip4}')
log(f'{msg} primary IPv6 address set to {ip6}')
if ip4 is not None or ip6 is not None or ifconfig:
pillar['network'] = {}
pillar['network']['interfaces'] = {}
if ip4 is not None or ip6 is not None:
pillar['network']['interfaces'][primary_interface] = {
'addresses': [],
}
addresses = pillar['network']['interfaces'][primary_interface]['addresses']
if ip4 is not None:
addresses.append(ip4)
if ip6 is not None:
addresses.append(ip6)
# firewall is managed through the firewalld pillar, avoid conflict with the wicked firewalld integration
pillar['network']['interfaces'][primary_interface]['firewall'] = False
for add_interface, add_ifconfig in ifconfig.items():
if 'ip4' in add_ifconfig or 'ip6' in add_ifconfig:
log(f'{msg} configuring additional interface {add_interface}')
pillar['network']['interfaces'][add_interface] = {
'addresses': [],
}
add_addresses = pillar['network']['interfaces'][add_interface]['addresses']
if 'ip4' in add_ifconfig:
add_addresses.append(add_ifconfig['ip4'])
if 'ip6' in add_ifconfig:
add_addresses.append(add_ifconfig['ip6'])
# explanation above
pillar['network']['interfaces'][add_interface]['firewall'] = False
if shortnet:
for network, nwconfig in domaindata['networks'].items():
if network == shortnet or nwconfig.get('short') == shortnet:
longnet = network
break
else:
longnet = None
log(f'{msg} network set to {longnet}')
if longnet:
nwconfig = domaindata['networks'][network]
if 'gw4' in nwconfig or 'gw6' in nwconfig:
pillar['network']['routes'] = {}
if ip4 is not None and 'gw4' in nwconfig:
pillar['network']['routes']['default4'] = {
'gateway': nwconfig['gw4'],
}
if ip6 is not None and 'gw6' in nwconfig:
pillar['network']['routes']['default6'] = {
'gateway': nwconfig['gw6'],
}
return pillar
070701000000B3000081A400000000000000000000000168EB80BB0000029D000000000000000000000000000000000000004100000000salt-formulas-3.0.4/infrastructure-formula/python/pyproject.toml[build-system]
requires = ['setuptools', 'wheel']
build-backend = 'setuptools.build_meta'
[project]
name = 'opensuse_infrastructure_formula'
authors = [
{ name='Georg Pfuetzenreuter', email='georg+opensuse@lysergic.dev' },
]
dynamic = ['version']
requires-python = '>=3.6'
dependencies = [
'salt',
]
[tool.setuptools.dynamic]
version = {attr = 'opensuse_infrastructure_formula.__version__'}
readme = {file = ['README.md']}
[tool.setuptools]
packages = [
'opensuse_infrastructure_formula',
'opensuse_infrastructure_formula.pillar',
]
[tool.setuptools.package-dir]
opensuse_infrastructure_formula = 'main'
'opensuse_infrastructure_formula.pillar' = 'pillar'
070701000000B4000081A400000000000000000000000168EB80BB00000197000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/infrastructure-formula/python/setup.cfg[metadata]
name = opensuse_infrastructure_formula
version = attr: opensuse_infrastructure_formula.__version__
author = Georg Pfuetzenreuter
author_email = georg+python@lysergic.dev
[options]
python_requires = >=3.6
packages =
opensuse_infrastructure_formula
opensuse_infrastructure_formula.pillar
package_dir =
opensuse_infrastructure_formula = main
opensuse_infrastructure_formula.pillar = pillar
070701000000B5000081A400000000000000000000000168EB80BB00003AEE000000000000000000000000000000000000003100000000salt-formulas-3.0.4/infrastructure-formulas.spec#
# spec file for package infrastructure-formulas
#
# Copyright (c) 2025 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
# Please submit bugfixes or comments via https://bugs.opensuse.org/
#
%define fdir %{_datadir}/salt-formulas
%define sdir %{fdir}/states
%define mdir %{fdir}/metadata
%define pythons python3
Name: infrastructure-formulas
Version: 3.0.4
Release: 0
Summary: Salt states for openSUSE and SLE
License: GPL-3.0-or-later
Group: System/Management
URL: https://github.com/openSUSE/salt-formulas
Source: _service
Requires: apache_httpd-formula
Requires: backupscript-formula
Requires: bootloader-formula
Requires: doofetch-formula
Requires: gitea-formula
Requires: grains-formula
Requires: hosts-formula
Requires: infrastructure-formula
Requires: jenkins-formula
Requires: juniper_junos-formula
Requires: kexec-formula
Requires: libvirt-formula
Requires: lldpd-formula
Requires: lock-formula
Requires: lunmap-formula
Requires: mtail-formula
Requires: multipath-formula
Requires: network-formula
Requires: orchestra-formula
Requires: os_update-formula
Requires: php_fpm-formula
Requires: rebootmgr-formula
Requires: redis-formula
Requires: redmine-formula
Requires: rsync-formula
Requires: security-formula
Requires: smartmontools-formula
Requires: status_mail-formula
Requires: suse_ha-formula
Requires: sysconfig-formula
Requires: tayga-formula
Requires: zypper-formula
BuildArch: noarch
%description
Salt states for managing applications running on openSUSE or SUSE Linux Enterprise based minions.
%package common
Summary: Files and directories shared by formulas
License: GPL-3.0-or-later
%description common
Files and directories shared by openSUSE/SUSE infrastructure formuas.
%package -n apache_httpd-formula
Summary: Salt states for managing the Apache httpd
License: GPL-3.0-or-later
Requires: %{name}-common
Requires: sysconfig-formula
%description -n apache_httpd-formula
Salt states for installing and configuring the Apache HTTP server on SUSE distributions.
%package -n backupscript-formula
Summary: Salt states for managing SUSE backup scripts
License: GPL-3.0-or-later
Requires: %{name}-common
Requires: sysconfig-formula
%description -n backupscript-formula
Salt states for installing and configuring the SUSE backup scripts for MySQL and PostgreSQL.
%package -n bootloader-formula
Summary: Salt states for managing the bootloader
License: GPL-3.0-or-later
Requires: %{name}-common
Requires: sysconfig-formula
%description -n bootloader-formula
Salt states for managing the bootloader setup and GRUB configuration.
%package -n doofetch-formula
Summary: Salt states for managing doofetch
License: GPL-3.0-or-later
Requires: %{name}-common
Requires: sysconfig-formula
%description -n doofetch-formula
Salt states for installing and configuring doofetch.
%package -n gitea-formula
Summary: Salt states for managing Gitea
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n gitea-formula
Salt states for managing Gitea.
%package -n grains-formula
Summary: Salt state for managing grains
License: Apache-2.0
Requires: %{name}-common
%description -n grains-formula
Salt state for managing grains.
%package -n hosts-formula
Summary: Salt states for managing %{_sysconfdir}/hosts
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n hosts-formula
Salt states for managing the %{_sysconfdir}/hosts file.
%package -n infrastructure-formula
Summary: Salt states specific to the openSUSE/SUSE infrastructures
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n infrastructure-formula
Custom Salt states specific to the openSUSE/SUSE infrastructures.
%package -n jenkins-formula
Summary: Salt states for managing Jenkins
License: GPL-3.0-or-later
Requires: %{name}-common
Requires: sysconfig-formula
%description -n jenkins-formula
Salt states for managing Jenkins Controller and Agent servers
%package -n juniper_junos-formula
Summary: Salt states for managing Junos
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n juniper_junos-formula
Salt states for managing Juniper Junos based network devices using pillars.
%package -n kexec-formula
Summary: Salt states for managing Kexec
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n kexec-formula
Salt states for managing Kexec using the kexec-load service
%package -n libvirt-formula
Summary: Salt states for managing libvirt
License: GPL-3.0-or-later
Requires: %{name}-common
Requires: sysconfig-formula
%description -n libvirt-formula
Salt states for managing libvirt servers.
%package -n lldpd-formula
Summary: Salt states for managing lldpd
License: GPL-3.0-or-later
Requires: %{name}-common
Requires: sysconfig-formula
%description -n lldpd-formula
Salt states for installing and configuring lldpd.
%package -n lock-formula
Summary: Salt state module for managing lockfiles
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n lock-formula
Salt state module allowing you to place a lock file prior to other states in order to prevent simultaneous executions.
%package -n lunmap-formula
Summary: Salt states for managing lunmap
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n lunmap-formula
Salt states for managing LUN mappings.
%package -n mtail-formula
Summary: Salt states for managing mtail
License: GPL-3.0-or-later
Requires: %{name}-common
Requires: sysconfig-formula
%description -n mtail-formula
Salt states for managing mtail.
%package -n multipath-formula
Summary: Salt states for managing multipath
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n multipath-formula
Salt states for installing multipath-tools and managing multipath/multipathd
%package -n network-formula
Summary: Salt states for managing the network
License: GPL-3.0-or-later
Requires: %{name}-common
Requires: sysconfig-formula
%description -n network-formula
Salt states for managing the network configuration using backends like Wicked.
%package -n orchestra-formula
Summary: Salt orchestration helper states
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n orchestra-formula
Salt helper states for the openSUSE/SUSE infrastructure orchestration states.
%package -n os_update-formula
Summary: Salt states for managing os-update
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n os_update-formula
Salt states for managing os-update.
%package -n php_fpm-formula
Summary: Salt states for managing PHP FPM
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n php_fpm-formula
Salt states for managing PHP FPM.
%package -n rebootmgr-formula
Summary: Salt states for managing rebootmgr
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n rebootmgr-formula
Salt states for managing rebootmgr.
%package -n redis-formula
Summary: Salt states for managing Redis
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n redis-formula
Salt states for managing Redis.
%package -n redmine-formula
Summary: Salt states for managing Redmine
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n redmine-formula
Salt states for managing Redmine.
%package -n rsync-formula
Summary: Salt states for managing rsyncd
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n rsync-formula
Salt states for managing rsyncd.
%package -n security-formula
Summary: Salt states for managing permissions
License: GPL-3.0-or-later
Requires: %{name}-common
Requires: sysconfig-formula
%description -n security-formula
Salt states for configuring permissions and capabilities.
%package -n smartmontools-formula
Summary: Salt states for managing smartmontools
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n smartmontools-formula
Salt states for installing smartmontools and configuring smartd
%package -n status_mail-formula
Summary: Salt states for managing systemd-status-mail
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n status_mail-formula
Salt states for managing systemd-status-mail.
%package -n suse_ha-formula
Summary: Salt states for managing SLE HA clusters
License: GPL-3.0-or-later
Requires: %{name}-common
Requires: sysconfig-formula
%description -n suse_ha-formula
Salt states for managing SUSE Linux Enterprise HA clusters.
%package -n sysconfig-formula
Summary: Salt helpers for sysconfig
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n sysconfig-formula
Library formula containing helper code for managing fillup/sysconfig files.
%package -n tayga-formula
Summary: Salt states for managing TAYGA
License: GPL-3.0-or-later
Requires: %{name}-common
%description -n tayga-formula
Salt states for managing the TAYGA NAT64 daemon
%package -n zypper-formula
Summary: Salt states for managing zypper
License: Apache-2.0
Requires: %{name}-common
%description -n zypper-formula
Salt states for configuring packages, repositories, and zypper itself.
%package -n infrastructure-formula-python
Summary: Infrastructure pillar helpers
License: GPL-3.0-or-later
BuildRequires: %{python_module pip}
BuildRequires: %{python_module setuptools}
BuildRequires: %{python_module wheel}
BuildRequires: %{pythons}
BuildRequires: python-rpm-macros
BuildArch: noarch
%description -n infrastructure-formula-python
Python libraries to help with rendering Salt formula pillars using YAML datasets found in the openSUSE infrastructure.
%prep
mv %{_sourcedir}/salt-formulas-%{version}/* .
%build
pushd infrastructure-formula/python
%pyproject_wheel
popd
%install
install -dm0755 %{buildroot}%{mdir} %{buildroot}%{sdir} %{buildroot}%{sdir}/_modules %{buildroot}%{sdir}/_states %{buildroot}%{_bindir}
dst_execumodules="%{sdir}/_modules"
dst_statemodules="%{sdir}/_states"
dst_bin='%{_bindir}'
for formula in $(find -mindepth 1 -maxdepth 1 -type d -name '*-formula' -printf '%%P\n')
do
echo "$formula"
fname="${formula%%-*}"
src_metadata="$formula/metadata"
dst_metadata="%{mdir}/$fname"
src_states="$formula/$fname"
dst_states="%{sdir}/$fname"
if [ ! -d "$src_states" ]
then
fname_sub="${fname//_/-}"
src_states="$formula/$fname_sub"
dst_states="%{sdir}/$fname_sub"
fi
src_execumodules="$formula/_modules"
src_statemodules="$formula/_states"
src_bin="$formula/bin"
if [ -d "$src_metadata" ]
then
cp -rv "$src_metadata" "%{buildroot}$dst_metadata"
echo "$dst_metadata" > "$fname.files"
fi
if [ -d "$src_states" ]
then
cp -rv "$src_states" "%{buildroot}$dst_states"
echo "$dst_states" >> "$fname.files"
else
echo "Warning: $formula does not ship with any states"
fi
for mod in execu state bin
do
mode=0644
case "$mod" in
'execu' ) src="$src_execumodules"
dst="$dst_execumodules"
;;
'state' ) src="$src_statemodules"
dst="$dst_statemodules"
;;
'bin' )
src="$src_bin"
dst="$dst_bin"
mode=0755
;;
esac
if [ -d "$src" ]
then
find "$src" -type f \
-execdir install -vm "$mode" {} "%{buildroot}$dst" \; \
-execdir sh -cx 'echo "$1/$(basename $2)" >> "$3"' prog "$dst" {} "../../$fname.files" \;
fi
done
for license in 'COPYING' 'LICENCE' 'LICENSE'
do
if [ -f "$formula/$license" ]
then
echo "%%license $formula/$license" >> "$fname.files"
break
fi
done
done
pushd infrastructure-formula/python
%pyproject_install
popd
%files
%files common
%license COPYING
%doc README.md
%dir %{fdir}
%dir %{mdir}
%dir %{sdir}
%dir %{sdir}/_{modules,states}
%files -n apache_httpd-formula -f apache_httpd.files
%files -n backupscript-formula -f backupscript.files
%files -n bootloader-formula -f bootloader.files
%files -n doofetch-formula -f doofetch.files
%files -n gitea-formula -f gitea.files
%files -n grains-formula -f grains.files
%files -n hosts-formula -f hosts.files
%files -n infrastructure-formula -f infrastructure.files
%files -n jenkins-formula -f jenkins.files
%files -n juniper_junos-formula -f juniper_junos.files
%files -n kexec-formula -f kexec.files
%files -n libvirt-formula -f libvirt.files
%files -n lldpd-formula -f lldpd.files
%files -n lock-formula -f lock.files
%files -n lunmap-formula -f lunmap.files
%files -n mtail-formula -f mtail.files
%files -n multipath-formula -f multipath.files
%files -n network-formula -f network.files
%files -n orchestra-formula -f orchestra.files
%files -n os_update-formula -f os_update.files
%files -n php_fpm-formula -f php_fpm.files
%files -n rebootmgr-formula -f rebootmgr.files
%files -n redis-formula -f redis.files
%files -n redmine-formula -f redmine.files
%files -n rsync-formula -f rsync.files
%files -n security-formula -f security.files
%files -n smartmontools-formula -f smartmontools.files
%files -n status_mail-formula -f status_mail.files
%files -n suse_ha-formula -f suse_ha.files
%files -n sysconfig-formula -f sysconfig.files
%files -n tayga-formula -f tayga.files
%files -n zypper-formula -f zypper.files
%files -n infrastructure-formula-python
%dir %{python_sitelib}/opensuse_infrastructure_formula
%pycache_only %{python_sitelib}/opensuse_infrastructure_formula/__pycache__
%{python_sitelib}/opensuse_infrastructure_formula/__{init,version}__.py
%dir %{python_sitelib}/opensuse_infrastructure_formula/pillar
%pycache_only %{python_sitelib}/opensuse_infrastructure_formula/pillar/__pycache__
%{python_sitelib}/opensuse_infrastructure_formula/pillar/*.py
%{python_sitelib}/opensuse_infrastructure_formula-*.dist-info
%changelog
070701000000B6000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002400000000salt-formulas-3.0.4/jenkins-formula070701000000B7000081A400000000000000000000000168EB80BB00000161000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/jenkins-formula/README.md# Salt states for [Jenkins](https://www.jenkins.io/)
Expects packages from `isv:SUSEInfra:CI:Jenkins` and `devel:tools:building`.
## Available states
`jenkins.controller`
Installs and configures a Jenkins controller using [JCasC](https://github.com/jenkinsci/configuration-as-code-plugin).
`jenkins.agent`
Installs and configures a Jenkins agent.
070701000000B8000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/jenkins-formula/jenkins070701000000B9000081A400000000000000000000000168EB80BB000005F5000000000000000000000000000000000000003600000000salt-formulas-3.0.4/jenkins-formula/jenkins/agent.sls{#-
Salt state file for managing Jenkins Agents
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'jenkins/map.jinja' import agent -%}
jenkins_agent_packages:
pkg.installed:
- name: jenkins-agent
{%- if 'sysconfig' in agent %}
jenkins_agent_sysconfig:
suse_sysconfig.sysconfig:
- name: jenkins-agent
- header_pillar: managed_by_salt_formula_sysconfig
- key_values:
{%- for k, v in agent.sysconfig.items() %}
{{ k }}: '{{ v }}'
{%- endfor %}
- append_if_not_found: True
- require:
- pkg: jenkins_agent_packages
jenkins_agent_service:
service.running:
- name: jenkins-agent
- enable: True
- watch:
- suse_sysconfig: jenkins_agent_sysconfig
- require:
- pkg: jenkins_agent_packages
{%- else %}
{%- do salt.log.warning('jenkins.agent: no sysconfig defined, skipping configuration') -%}
{%- endif %}
070701000000BA000081A400000000000000000000000168EB80BB0000084C000000000000000000000000000000000000003B00000000salt-formulas-3.0.4/jenkins-formula/jenkins/controller.sls{#-
Salt state file for managing Jenkins Controllers
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'jenkins/map.jinja' import controller -%}
jenkins_controller_packages:
pkg.installed:
- pkgs:
- jenkins
- jenkins-plugin-configuration-as-code
jenkins_controller_sysconfig:
suse_sysconfig.sysconfig:
- name: jenkins
- header_pillar: managed_by_salt_formula_sysconfig
- key_values:
{%- for k, v in controller.sysconfig.items() %}
{{ k }}: '{{ v }}'
{%- endfor %}
- append_if_not_found: True
- require:
- pkg: jenkins_controller_packages
jenkins_controller_config_directory:
file.directory:
- name: /etc/jenkins
- group: jenkins
- require:
- pkg: jenkins_controller_packages
{%- if 'config' in controller %}
jenkins_controller_config_file:
file.serialize:
- name: /etc/jenkins/salt.yaml
- serializer: yaml
- dataset: {{ controller.config }}
- group: jenkins
- mode: '0640'
- require:
- pkg: jenkins_controller_packages
- file: jenkins_controller_config_directory
{%- else %}
{%- do salt.log.warning('jenkins.controller: no JCasC configuration defined') -%}
{%- endif %}
jenkins_controller_service:
service.running:
- name: jenkins
- enable: True
- watch: # graceful reload possible ?
- suse_sysconfig: jenkins_controller_sysconfig
- file: jenkins_controller_config_file
- require:
- pkg: jenkins_controller_packages
070701000000BB000081A400000000000000000000000168EB80BB000000FE000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/jenkins-formula/jenkins/defaults.yaml---
# yamllint disable rule:line-length
jenkins:
controller:
sysconfig:
JENKINS_JAVA_OPTIONS: '-Djava.awt.headless=true -Xms256m -Xmx640m -Dhudson.DNSMultiCast.disabled=true -XX:+HeapDumpOnOutOfMemoryError -Dcasc.jenkins.config=/etc/jenkins'
070701000000BC000081A400000000000000000000000168EB80BB000003E1000000000000000000000000000000000000003600000000salt-formulas-3.0.4/jenkins-formula/jenkins/map.jinja{#-
Jinja variables file for the Jenkins Salt states
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- import_yaml './defaults.yaml' as defaults -%}
{%- set jenkins = salt.pillar.get('jenkins', default=defaults, merge=True, merge_nested_lists=False) -%}
{%- set controller = jenkins.get('controller', {}) -%}
{%- set agent = jenkins.get('agent', {}) -%}
070701000000BD000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002D00000000salt-formulas-3.0.4/jenkins-formula/metadata070701000000BE000081A400000000000000000000000168EB80BB00000094000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/jenkins-formula/metadata/metadata.yml---
summary:
Salt states for managing Jenkins
description:
Salt states for managing Jenkins Controller and Agent servers
require:
- sysconfig
070701000000BF000081A400000000000000000000000168EB80BB0000040F000000000000000000000000000000000000003300000000salt-formulas-3.0.4/jenkins-formula/pillar.examplejenkins:
controller:
# JCasC configuration below
config:
jenkins:
systemMessage: "Deployed with Salt!"
unclassified:
location:
url: https://example.com
# sysconfig configuration below, mostly used to pass additional JVM options to the controller
sysconfig:
# the following is added by default - this is the packaged default plus casc.jenkins.config
JENKINS_JAVA_OPTIONS: '-Djava.awt.headless=true -Xms256m -Xmx640m -Dhudson.DNSMultiCast.disabled=true -XX:+HeapDumpOnOutOfMemoryError -Dcasc.jenkins.config=/etc/jenkins'
agent:
# sysconfig configuration below, used to configure the agent wrapper
sysconfig:
# the first two are required, the third one might be if security is enabled in Jenkins
JENKINS_BASE: https://example.com
JENKINS_AGENT_JNLP_URL: https://example.com/computer/minion1/jenkins-agent.jnlp
# if -secret is part of the arguments, the value should be stored PGP encrypted
JENKINS_AGENT_ARGUMENTS: '-secret 12345'
070701000000C0000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002A00000000salt-formulas-3.0.4/jenkins-formula/tests070701000000C1000081A400000000000000000000000168EB80BB000006F5000000000000000000000000000000000000004800000000salt-formulas-3.0.4/jenkins-formula/tests/test_01_jenkins_controller.py"""
Test suite for assessing the Jenkins Controller configuration results
Copyright (C) 2023-2024 Georg Pfuetzenreuter <georg.pfuetzenreuter@suse.com>
This program is free software: you can jenkinstribute 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 <https://www.gnu.org/licenses/>.
"""
import yaml
confdir = '/etc/jenkins'
conffile = f'{confdir}/salt.yaml'
def test_jenkins_package(host):
assert host.package('jenkins').is_installed
def test_jenkins_config_file_exists(host):
with host.sudo():
assert host.file(conffile).exists
def test_jenkins_config_file_contents(host):
pillar = {'jenkins': {
'systemMessage': 'Deployed with Salt!'
},
'unclassified': {
'location': {
'url': 'https://example.com'
}
}
}
with host.sudo():
struct = yaml.safe_load(host.file(conffile).content_string)
assert pillar.items() == struct.items()
def test_jenkins_config_file_permissions(host):
with host.sudo():
file = host.file(conffile)
assert file.user == 'root'
assert file.group == 'jenkins'
assert oct(file.mode) == '0o640'
def test_jenkins_service(host):
assert host.service('jenkins').is_enabled
070701000000C2000081A400000000000000000000000168EB80BB0000047A000000000000000000000000000000000000004300000000salt-formulas-3.0.4/jenkins-formula/tests/test_02_jenkins_agent.py"""
Test suite for assessing the Jenkins Agent configuration results
Copyright (C) 2023-2024 Georg Pfuetzenreuter <georg.pfuetzenreuter@suse.com>
This program is free software: you can jenkinstribute 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 <https://www.gnu.org/licenses/>.
"""
sysconffile = '/etc/sysconfig/jenkins-agent'
def test_jenkins_agent_package(host):
assert host.package('jenkins-agent').is_installed
def test_jenkins_agent_sysconfig(host):
with host.sudo():
assert host.file(sysconffile).contains('^JENKINS_BASE="https://example.com"$')
def test_jenkins_agent_service(host):
assert host.service('jenkins-agent').is_enabled
070701000000C3000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002A00000000salt-formulas-3.0.4/juniper_junos-formula070701000000C4000081A400000000000000000000000168EB80BB00000A99000000000000000000000000000000000000003400000000salt-formulas-3.0.4/juniper_junos-formula/README.md# Salt states for Juniper Junos
This formula helps with configuring Juniper network devices using Salt pillars.
## Disclaimer
This formula is yet to be fully tested and hence considered a work in progress project. There are various "FIXME" remarks around the files which are intended to be revisited over time.
## Available states
`juniper_junos.firewall`
Manages a Juniper firewall configuration. Intended for use with Juniper SRX firewalls.
`juniper_junos.switch`
Manages a Juniper switch configuration. Intended for use with Juniper QFX switches, but can also be applied on SRX devices.
## History
This Salt formula was created in an effort to automate the network infrastructure in the SUSE datacenters.
The modules and conversion scripts have originally been developed for SUSE by DEVOQ TECHNOLOGY I.K.E. as part of a contracted network automation project before they were refactored and integrated with our formula ecosystem.
## Testing
The test suite currently only validates whether the configuration is applied as expected from the Salt end and does not assess the expected functionality of the network device. More importantly, the test pillar still needs to be expanded to cover the complete templating logic.
To run the test suite, a lab environment can be set up if the proprietary vSRX and vQFX images are provided in `/opt/images`.
#### Test dependencies
- Docker
- Libvirt + QEMU
- Scullery
- Pytest + Testinfra
Generally only Pytest and Testinfra are required, the other tools are helpers for setting up the needed environment. The Pytest suite expects a network device with the default vrnetlab admin credentials to be accessible at the address passed as `--target`. The minions in Salt should be called `vqfx-device1` and `vsrx-device1` respectively, as the `--model` argument will map `qfx` and `srx` to them.
#### Test steps
1. `juniper_junos-formula/bin/lab.sh` - this clones a forked vrnetlab repository, pulls our vrnetlab-base container image, re-builds it using the proprietary images, and runs the containers - these containers in return run the needed virtual machines and are hence started with additional privileges
2. `scullery --config test/scullery.ini --suite juniper_junos_formula.tumbleweed.one_master --env` - this instantiates a virtual machine running the Salt master and proxy minions
3. `pytest --disable-warnings -v -rx --hosts scullery-master0 --ssh-config .scullery_ssh --sudo --model=<model> --target=<target> juniper_junos-formula/tests/` - use your favourite arguments to customize the Pytest run, replace `<model>` with either `qfx` or `srx`, and `<target>` with the respective container address found in the `.devices` file created by `lab.sh`
070701000000C5000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003300000000salt-formulas-3.0.4/juniper_junos-formula/_modules070701000000C6000081A400000000000000000000000168EB80BB00001C2D000000000000000000000000000000000000004000000000salt-formulas-3.0.4/juniper_junos-formula/_modules/susejunos.py"""
Salt execution module with Juniper Junos related utilities
Copyright (C) 2023-2024 SUSE LLC
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 <https://www.gnu.org/licenses/>.
"""
'''
SUSE JUNOS
==========
:codeauthor: Adam Pavlidis <adampavlidis@gmail.com>
:maintainer: Georg Pfuetzenreuter <georg.pfuetzenreuter@suse.com>
:maturity: new
:depends: napalm
:platform: unix
Dependencies
------------
- :mod:`NAPALM proxy minion <salt.proxy.napalm>`
'''
import os
import re
import time
import ipaddress
import xml.etree.ElementTree as et
import logging
log = logging.getLogger(__file__)
# import NAPALM utils
import salt.utils.napalm
from salt.utils.napalm import proxy_napalm_wrap
# ----------------------------------------------------------------------------------------------------------------------
# module properties
ifname_regex = re.compile('set interfaces (\S+)\s+')
unit_regex = re.compile('set interfaces \S+\s+unit\s+(\S+)')
vlanid_regex = re.compile('set vlans (\S+)\s+vlan-id\s+(\d+)')
vlan_regex = re.compile('set vlans (\S+)')
# ----------------------------------------------------------------------------------------------------------------------
__virtualname__ = 'susejunos'
__proxyenabled__ = ['napalm']
# uses NAPALM-based proxy to interact with network devices
__virtual_aliases__ = ('susejunos',)
# ----------------------------------------------------------------------------------------------------------------------
# property functions
# ----------------------------------------------------------------------------------------------------------------------
def __virtual__():
'''
NAPALM library must be installed for this module to work and run in a (proxy) minion.
'''
return salt.utils.napalm.virtual(__opts__, __virtualname__, __file__)
# ----------------------------------------------------------------------------------------------------------------------
# callable functions
# ----------------------------------------------------------------------------------------------------------------------
@proxy_napalm_wrap
def get_active_interfaces(
include_ae=True,
include_xe=True,
include_et=True,
include_ge=True,
include_reth=True,
include_fxp=True,
parents_only=True
):
"""
:param include_ae:
:param include_xe:
:param include_et:
:param include_ge:
:param include_reth:
:param parents_only:
:return: list of currently installed interfaces
"""
command = 'show configuration interfaces | display set'
ret = __salt__['net.cli'](command,
inherit_napalm_device=napalm_device) # pylint: disable=undefined-variable
output = ret['out'][command]
filtered_output = []
unit_filtered_output = []
for ln in output.split('\n'):
match_ifname = ifname_regex.match(ln)
match_unit = unit_regex.match(ln)
if match_unit:
try:
unit = int(match_unit[1])
except ValueError:
unit = 0
else:
unit = 0
if match_ifname:
ifname = match_ifname[1]
if unit:
unitifname = f'{ifname}.{unit}'
else:
unitifname = ifname
if include_ae and ifname.startswith('ae'):
filtered_output.append(ifname)
unit_filtered_output.append(unitifname)
if include_xe and ifname.startswith('xe'):
filtered_output.append(ifname)
unit_filtered_output.append(unitifname)
if include_et and ifname.startswith('et'):
filtered_output.append(ifname)
unit_filtered_output.append(unitifname)
if include_reth and ifname.startswith('reth'):
filtered_output.append(ifname)
unit_filtered_output.append(unitifname)
if include_ge and ifname.startswith('ge'):
filtered_output.append(ifname)
unit_filtered_output.append(unitifname)
if include_fxp and ifname.startswith('fxp'):
filtered_output.append(ifname)
unit_filtered_output.append(unitifname)
if not parents_only:
filtered_output = unit_filtered_output
return sorted(list(set(filtered_output)))
@proxy_napalm_wrap
def get_active_vlans():
"""
parses the configuration of juniper to retrieve vlans parsed and unparsed
:return: {'parsed_vlan_dict': {}, 'unparsed_vlan_list': []}
"""
command = 'show configuration vlans | display set'
ret = __salt__['net.cli'](command,
inherit_napalm_device=napalm_device) # pylint: disable=undefined-variable
log.debug(f'Return output {ret}')
output = ret['out'][command]
vlan_noid = set()
parsed_vlans = {}
for ln in output.split('\n'):
match_vlan = vlan_regex.match(ln)
match_vlanid = vlanid_regex.match(ln)
if match_vlanid:
vlan_name = match_vlanid[1]
vlan_id = match_vlanid[2]
if vlan_id not in parsed_vlans:
parsed_vlans[int(vlan_id)] = vlan_name
if vlan_name in vlan_noid:
vlan_noid.remove(vlan_name)
else:
log.error('Something very wrong is happening here %s exists in %s', vlan_id, parsed_vlans)
else:
if match_vlan and match_vlan[1] not in parsed_vlans.values():
vlan_noid.add(match_vlan[1])
return {'parsed_vlan_dict': parsed_vlans, 'unparsed_vlan_list': list(vlan_noid)}
@proxy_napalm_wrap
def get_active_ntp():
"""
Parses the Junos configuration to find configured NTP servers
At the time of writing (2023/08/23), this function will not work with the latest release of Napalm (4.1.0) without this pending upstream patch:
https://github.com/napalm-automation/napalm/pull/1796
The openSUSE package has this patch applied downstream, allowing the function to operate on openSUSE Tumbleweed based Salt proxies.
:return: list of NTP servers
"""
command = 'show configuration system ntp | display xml rpc'
ret = __salt__['net.cli'](command,
inherit_napalm_device=napalm_device) # pylint: disable=undefined-variable
log.debug(f'Return output {ret}')
out = ret['out'][command]
servers = []
if 'configuration' in out:
xml = et.fromstring(out)
servers = [entry.text for entry in xml.findall('configuration/system/ntp/server/name')]
log.debug(f'Found NTP servers: {servers}')
return servers
070701000000C7000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/juniper_junos-formula/bin070701000000C8000081ED00000000000000000000000168EB80BB00003CE5000000000000000000000000000000000000004400000000salt-formulas-3.0.4/juniper_junos-formula/bin/compile_junos_data.py#!/usr/bin/python3
"""
Juniper Junos Salt pillar generator
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
"""
import argparse
import logging
import pathlib
import sys
import yaml
logger = logging.getLogger(__name__)
log_choices_converter = {
'debug': logging.DEBUG,
'info': logging.INFO
}
argparser = argparse.ArgumentParser()
argparser.add_argument('--log', type=str, default='info', choices=log_choices_converter.keys())
argparser.add_argument('--in-switching', type=str, default='switching.yaml')
argparser.add_argument('--in-backbone', type=str, default='backbone.yaml')
argparser.add_argument('--out', type=str, default='output')
args = argparser.parse_args()
infile_s = args.in_switching
infile_b = args.in_backbone
outdir = args.out
def _fail(msg):
logger.error(f'{msg}, bailing out.')
sys.exit(1)
# TODO: split the following into smaller functions
def generate_switch_pillars(data):
all_pillars = {}
global_port_groups = data.get('port_groups', {})
ignore_ports = data.get('ignore_ports', {})
global_ignore_ports = ignore_ports.get('global', [])
core = []
aggregation = []
access = []
for device in data.get('switches', []):
device_type = device.get('type')
device_role = device.get('role')
device_id = device.get('id')
if device_type == 'switch':
if device_role == 'core':
core.append(device_id)
elif device_role == 'aggregation':
aggregation.append(device_id)
elif device_role == 'access':
access.append(device_id)
else:
_fail(f'Illegal switch role "{device_role}" in device {device}')
logger.debug(f'Core switches: {core}')
logger.debug(f'Aggregation switches: {aggregation}')
logger.debug(f'Access switches: {access}')
switch_pillar = {
device: {
'vlans': {},
'vlan_set': set(),
'lacp': {},
'ports': {},
'ignore_ports': global_ignore_ports
} for device in core + aggregation + access
}
for device, config in switch_pillar.items():
if device in ignore_ports:
if ignore_ports[device] not in switch_pillar[device]['ignore_ports']:
switch_pillar[device]['ignore_ports'] += ignore_ports[device]
## Compatibility helper for the old, list based, input format
data_vlans = data.get('vlans')
if isinstance(data_vlans, list):
vlans = {}
for vlan in data_vlans:
vlan_id = vlan.get('id')
vlan_name = vlan.get('name')
vlan_description = vlan.get('description')
vlan_groups = vlan.get('groups')
vlans.update({
vlan_name: {
'id': vlan_id,
'description': vlan_description
}
})
if vlan_groups:
vlans[vlan_name].update({'groups': vlan_groups})
logger.debug(f'Converted legacy VLANs: {vlans}')
elif isinstance(data_vlans, dict):
vlans = data_vlans
for vlan, vconfig in vlans.items():
vlan_id = vconfig.get('id')
vlan_description = vconfig.get('description')
logger.debug(f'Processing VLAN {vlan} with ID {vlan_id}')
for group in vconfig.get('groups', []):
logger.debug(f'Processing group {group}')
if group in global_port_groups:
for group, gconfig in global_port_groups[group].items():
group_description = gconfig.get('description')
group_members = gconfig.get('members', [])
if vlan_id in switch_pillar[group]['vlan_set']:
logger.debug(f'VLAN {vlan} already exists in set.')
else:
# ??
switch_pillar[group]['vlan_set'] = switch_pillar[group]['vlan_set'].union({vlan_id})
switch_pillar[group]['vlans'].update({vlan: {'id': vlan_id, 'description': vlan_description}})
for port in group_members:
if not 'iface' in port:
logger.debug(f'Skipping {port}')
continue
interface = port['iface']
if interface in switch_pillar[group]['ignore_ports']:
_fail(f'Attempted to configure interface {interface}, but it is set to be ignored. This should not happen')
interface_description = port.get('description')
if interface in switch_pillar[group]['ports']:
logger.debug(f'Interface {interface} is already in pillar')
tagged = switch_pillar[group]['ports'][interface]['tagged']
if vlan_id not in tagged:
tagged.append(vlan_id)
else:
switch_pillar[group]['ports'][interface] = {
'interface': interface,
'untagged': None,
'tagged': [vlan_id],
'description': interface_description
}
lacp_backbone = data.get('lacp_backbone', {})
lacp_data = {}
for device, lacps in lacp_backbone.items():
for lacp, lconfig in lacps.items():
if lacp in switch_pillar[device]['ignore_ports']:
_fail(f'Attempted to configure LACP interface {lacp}, but it is set to be ignored. This should not happen')
for lacp_member in lconfig.get('members', []):
lacp_interface = lacp_member.get('interface')
logger.debug(f'Computing LACP member interface {lacp_interface}')
if interface in switch_pillar[device]['ignore_ports']:
_fail(f'Attempted to configure LACP member interface {interface}, but it is set to be ignored. This should not happen')
if not lacp_interface in lacp_data[device]:
lacp_data[device] = {lacp_interface: {}}
lacp_data[device][lacp_interface] = {
'parent': lacp_id,
'description': lconfig.get('description', f'member_of_lag_{lacp_id}')
}
logger.debug(f'LACP backbone data: {lacp_data}')
lacp_switch = data.get('lacp', {})
lacp_interfaces = {}
for device, interfaces in lacp_switch.items():
## Compatibility helper for old, list based, input data
if isinstance(interfaces, list):
interfaces = {}
for interface in interfaces:
if_lacp_id = interface.get('lacp_id')
if if_lacp_id:
lacp_interfaces[if_lacp_id] = {}
ifd = lacp_interfaces[if_lacp_id]
if_members = interface.get('members')
if isinstance(if_members, list):
ifd_members = {}
for member in if_members:
ifd_members.update({
member.get('iface'): {
'description': member.get('description')
}
})
elif isinstance(if_members, dict):
ifd_members = if_members
if_lacp_options = interface.get('lacp_options')
if_mclag_options = interface.get('mclag_options')
if if_members:
ifd.update({'members': ifd_members})
if if_lacp_options:
ifd.update({'lacp_options': if_lacp_options})
if if_mclag_options:
ifd.update({'mclag_options': if_mclag_options})
logger.debug(f'Converted legacy LACP interfaces: {lacp_interfaces}')
elif isinstance(lacp_interfaces, dict):
lacp_interfaces = interfaces
else:
_fail(f'Invalid LACP data structure')
for ae_interface, ifconfig in lacp_interfaces.items():
lacp_id = ifconfig.get('lacp_id')
if lacp_id in [elem.get('lacp_id') for elem in lacp_backbone.get(device, [])]:
logger.warning(f'Re-declared interface {device} {ae_interface}')
continue
if lacp_id in switch_pillar[device]['ignore_ports']:
logger.warning(f'Ignoring ignored interface {device} {ae_interface}')
continue
for member_interface, mconfig in ifconfig.get('members', {}).items():
if member_interface in lacp_data.get(device, {}):
logger.warning(f'Ignoring backbone interface {device} {member_interface}')
continue
else:
if member_interface in switch_pillar[device]['ignore_ports']:
logger.warning(f'Igoring ignored interface {member_interface}')
continue
if not device in lacp_data:
lacp_data[device] = {}
if not member_interface in lacp_data[device]:
lacp_data[device][member_interface] = {}
lacp_data[device][member_interface] = {
'parent': lacp_id,
'description': mconfig.get('description', f'member_of_lag_{lacp_id}')
}
for device, dconfig in lacp_data.items():
logger.debug('Processing LACP data {device} {dconfig}')
remove_ports = []
for member in dconfig.keys():
if device in switch_pillar and member in switch_pillar[device]['ports']:
remove_ports.append(member)
for port in remove_ports:
logger.warning(f'Popping backbone link {device} {member}')
dconfig.pop(member)
switch_pillar[device]['lacp'] = dconfig
for device, pconfig in data.get('ports', {}).items():
logger.debug(f'Processing port {device} {pconfig}')
for member in pconfig.keys():
if device in switch_pillar:
dpillar = switch_pillar[device]
if member in dpillar['ports']:
logger.warning(f'Ignoring backbone link {device} {member}')
continue
elif member in dpillar['lacp']:
logger.warning(f'Ignoring LACP slave {device} {member}')
continue
elif member in dpillar['ignore_ports']:
logger.warning(f'Ignoring ignored port {device} {member}')
continue
else:
dpillar['ports'][member] = pconfig[member]
for device in lacp_switch.keys():
for lacp, lconfig in lacp_interfaces.items():
interface = lconfig.get('lacp_id')
if interface in [elem.get('lacp_id') for elem in lacp_backbone.get(device, [])]:
logger.debug(f'Skipping LACP interface {interface}')
continue
lacp_options = lconfig.get('lacp', {})
mclag_options = lconfig.get('mclag', {})
mclag_options.setdefault('mc-ae-id', 1)
mclag_options.setdefault('redundancy-group', 1)
if device.endswith('1'):
mclag_options.setdefault('chassis-id', 0)
mclag_options.setdefault('status-control', 'active')
elif device.endswith('2'):
mclag_options.setdefault('chassis-id', 1)
mclag_options.setdefault('status-control', 'passive')
else:
logger.debug(f'Unable to determine chassis-id and status-control for interface {device} {interface}')
interface_pillar = switch_pillar[device]['ports'][interface]
if not 'lacp_options' in interface_pillar and 'mclag_options' in interface_pillar:
logger.warning(f'LACP interface {device} {interface} without switching configuration?')
switch_pillar[device]['ports'][interface] = {
'description': lconfig.get('description'),
'lacp_options': lacp_options,
'mclag_options': mclag_options
}
for device, lacps in lacp_backbone.items():
for lacp, lconfig in lacps.items():
interface = lconfig.get('lacp_id')
lacp_options = lconfig.get('lacp', {})
mclag_options = lconfig.get('mclag', {})
# FIXME, this is redundant with the lacp_switch loop above
mclag_options.setdefault('mc-ae-id', 1)
mclag_options.setdefault('redundancy-group', 1)
if device.endswith('1'):
mclag_options.setdefault('chassis-id', 0)
mclag_options.setdefault('status-control', 'active')
elif device.endswith('2'):
mclag_options.setdefault('chassis-id', 1)
mclag_options.setdefault('status-control', 'passive')
else:
logger.debug(f'Unable to determine chassis-id and status-control for interface {device} {interface}')
interface_pillar = switch_pillar[device]['ports'][interface]
if not 'lacp_options' in interface_pillar and 'mclag_options' in interface_pillar:
logger.warning(f'LACP interface {device} {interface} without switching configuration?')
switch_pillar[device]['ports'][interface] = {
'description': lconfig.get('description'),
'lacp_options': lacp_options,
'mclag_options': mclag_options
}
for device, config in switch_pillar.items():
config.pop('vlan_set')
all_pillars[device] = config
return all_pillars
def main():
for file in [infile_s, infile_b]:
if not pathlib.Path(file).is_file():
_fail(f'Unable to locate "{file}"')
if not pathlib.Path(outdir).is_dir():
_fail(f'Directory "{outdir}" does not exist')
with open(infile_s) as fh:
data_s = yaml.safe_load(fh)
with open(infile_b) as fh:
data_b = yaml.safe_load(fh)
data_s.update(data_b)
all_pillars = generate_switch_pillars(data_s)
for device, config in all_pillars.items():
with open(f'{outdir}/{device}.sls', 'w') as fh:
yaml.dump(config, fh)
logger.info('ok')
if __name__ == '__main__':
logger = logging.getLogger()
logger.setLevel(log_choices_converter[args.log])
logger.addHandler(logging.StreamHandler(sys.stdout))
logger.debug(args)
main()
070701000000C9000081ED00000000000000000000000168EB80BB00000BF9000000000000000000000000000000000000003500000000salt-formulas-3.0.4/juniper_junos-formula/bin/lab.sh#!/bin/sh
# Initialize virtual machines running vQFX and vSRX using vrnetlab containers
# Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
#
# 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 <https://www.gnu.org/licenses/>.
set -Ceux
# Git repository to fetch vrnetlab from ; to-do -> extract the needed files instead of cloning the whole tree
repository='https://github.com/tacerus/vrnetlab.git'
revision='SUSE-master'
docker pull registry.opensuse.org/isv/suseinfra/containers/containerfile/vrnetlab-base
wd="$PWD"
if [ ! -d ~/.cache ]
then
mkdir ~/.cache
fi
pushd ~/.cache
if [ -d vrnetlab ]
then
git --git-dir=$PWD/vrnetlab/.git pull origin "$revision"
else
git clone --no-tags --single-branch -b "$revision" "$repository"
fi
pushd vrnetlab
# to-do -> somehow automate the fetching of these proprietary images better
images=('junos-vsrx3-x86-64-20.2R1.10.qcow2' 'vqfx-20.2R1.10-re-qemu.qcow2' 'vqfx-20.2R1-2019010209-pfe-qemu.qcow2')
for image in ${images[@]}
do
test -f "$image" || cp "/opt/images/$image" .
done
pushd vsrx
mv ../junos-vsrx*.qcow2 .
make
popd
pushd vqfx
mv ../vqfx-*.qcow2 .
make
popd
container_srx='vsrx-device1'
container_qfx='vqfx-device1'
containers=("$container_srx" "$container_qfx")
for container in ${containers[@]}
do
if docker ps -a --format '{{.Names}}' | grep -q "$container"
then
echo 'Removing existing container'
docker stop "$container"
docker rm -v "$container" || true
fi
done
# to-do: map /dev/kvm instead of --privileged
# to-do: tag "latest" images and include run calls in loop above
docker_run=('docker' 'run' '-d' '--privileged' '--name')
${docker_run[@]} "$container_srx" vrnetlab/vr-vsrx:vsrx3-x86
${docker_run[@]} "$container_qfx" --device /dev/net/tun vrnetlab/vr-vqfx:20.2R1.10-re
popd >/dev/null
#echo "$address" > "$container-address"
popd >/dev/null
if echo "$wd" | grep -Fq 'formulas'
then
if [ -f ".devices" ]
then
echo 'Existing address file, overwriting'
rm ".devices"
fi
for container in ${containers[@]}
do
address="$(docker inspect -f '{{ range.NetworkSettings.Networks }}{{ .IPAddress }}{{ end }}' $container)"
if [ -z "$address" ]
then
echo 'Failed to fetch container address, aborting.'
exit 1
fi
echo "$container $address" >> ".devices"
done
fi
#if ! grep -Fqx "$address $container" /etc/hosts
#then
# if grep -Fq "$container" /etc/hosts
# then
# sed -Ei "s/^.*$container.*$/$address $container/" /etc/hosts
# else
# echo "$address $container" >> /etc/hosts
# fi
#fi
070701000000CA000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003800000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos070701000000CB000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003E00000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files070701000000CC000081A400000000000000000000000168EB80BB0000020F000000000000000000000000000000000000004B00000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/addresses.j2{%- set seta = 'set security address-book global address' %}
{{ seta }} dummy 127.0.0.1/32
delete security address-book global
{%- for address, prefix in salt['pillar.get']('juniper_junos:addresses', {}).items() %}
{{ seta }} {{ address }} {{ prefix }}
{%- endfor %}
{%- for addressset, addresses in salt['pillar.get']('juniper_junos:address-sets', {}).items() %}
{%- for address in addresses %}
{{ seta }}-set {{ addset }} address {{ addressset }} address {{ address }}
{%- endfor %}
{%- endfor %} {#- close address loop -#}
070701000000CD000081A400000000000000000000000168EB80BB000000D9000000000000000000000000000000000000004A00000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/baseline.j2{%- set base = 'juniper_junos/files/' -%}
{%- include base ~ 'vlans.j2' -%}
{%- include base ~ 'interfaces.j2' -%}
{%- include base ~ 'routes.j2' -%}
{%- include base ~ 'ntp.j2' -%}
{%- include base ~ 'syslog.j2' -%}
070701000000CE000081A400000000000000000000000168EB80BB00000124000000000000000000000000000000000000004A00000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/firewall.j2{%- set base = 'juniper_junos/files/' -%}
{%- include base ~ 'baseline.j2' -%}
{%- include base ~ 'redundancy.j2' -%}
{%- include base ~ 'addresses.j2' -%}
{%- include base ~ 'nat.j2' -%}
{%- include base ~ 'policies.j2' -%}
{%- include base ~ 'zones.j2' -%}
{%- include base ~ 'snmp.j2' -%}
070701000000CF000081A400000000000000000000000168EB80BB00000F60000000000000000000000000000000000000004C00000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/interfaces.j2{#- FIXME: move these to context variables #}
{%- set interfaces = salt['pillar.get']('juniper_junos:interfaces') -%}
{%- set present_interfaces = salt['susejunos.get_active_interfaces']() -%}
{%- set ignored_interfaces = salt['pillar.get']('juniper_junos:ignore', {}).get('interfaces', []) -%}
{%- set reth_ns = namespace(count=0) %}
{%- for interface in present_interfaces %}
{%- if interface not in ignored_interfaces %}
delete interfaces {{ interface }}
{%- endif %}
{%- endfor %}
{%- for ifname, ifconfig in interfaces.items() %}
{%- set setif = 'set interfaces ' ~ ifname -%}
{%- if ifname.startswith('reth') %}
{%- set reth_ns.count = reth_ns.count + 1 %}
{%- if 'redundancy-group' in ifconfig %}
{{ setif }} redundant-ether-options redundancy-group {{ ifconfig['redundancy-group'] }}
{%- endif %}
{%- endif %}
{%- if 'description' in ifconfig %}
{{ setif }} description "{{ ifconfig['description'] }}"
{%- endif %}
{%- if 'disable' in ifconfig and ifconfig['disable'] %}
{{ setif }} disable
{%- endif %}
{%- if 'speed' in ifconfig %}
{{ setif }} speed {{ ifconfig['speed'] | lower }}
{%- endif %}
{%- if not 'lacp' in ifconfig and not ifname.startswith(('em', 'fxp', 'vme')) %} {#- setting the MTU on a ae children or management interfaces is not allowed #}
{%- if pillar.get('simulation', False) %}
{%- set default_mtu = 1500 %}
{%- else %}
{%- set default_mtu = 9216 %}
{%- endif %}
{{ setif }} mtu {{ ifconfig.get('mtu', default_mtu) }}
{%- endif %}
{%- set units = ifconfig.get('units', {}) %}
{%- for unit, uconfig in units.items() %}
{%- if 'description' in uconfig %}
{{ setif }} unit {{ unit }} description "{{ uconfig['description'] }}"
{%- endif %}
{%- if 'inet' in uconfig %}
{%- for address in uconfig.inet.get('addresses', []) %}
{{ setif }} unit {{ unit }} family inet address {{ address }}
{%- endfor %}
{%- endif %}
{%- if 'inet6' in uconfig %}
{%- for address in uconfig.inet6.get('addresses', []) %}
{{ setif }} unit {{ unit }} family inet6 address {{ address }}
{%- endfor %}
{%- endif %}
{%- if 'vlan' in uconfig %}
{%- set vtype = uconfig['vlan'].get('type', None) %}
{%- if vtype in ['access', 'trunk'] %}
{{ setif }} unit {{ unit }} family ethernet-switching interface-mode {{ vtype }}
{{ setif }} unit {{ unit }} family ethernet-switching vlan members [ {{ uconfig['vlan']['ids'] | join(' ') }} ]
{%- endif %}
{%- endif %}
{%- endfor %} {#- close unit loop -#}
{%- if 'reth' in ifconfig %}
{{ setif }} ether-options redundant-parent {{ ifconfig['reth'] }}
{%- endif %}
{%- if 'lacp' in ifconfig %}
{{ setif }} ether-options 802.3ad {{ ifconfig['lacp'] }}
{%- endif %}
{%- if 'ae' in ifconfig %}
{%- set aec = ifconfig['ae'] -%}
{%- set setifae = setif ~ ' aggregated-ether-options ' -%}
{%- if 'lacp' in aec %}
{%- set aecl = aec['lacp'] -%}
{%- set setifael = setifae ~ 'lacp' -%}
{%- if aecl.get('force-up', False) %}
{{ setifael }} force-up
{%- endif %}
{%- if 'periodic' in aecl and aecl.periodic in ['fast', 'slow'] %}
{{ setifael }} periodic {{ aecl.periodic }}
{%- endif %}
{%- if 'mode' in aecl and aecl.mode in ['active', 'passive'] %}
{{ setifael }} {{ aecl.mode }}
{%- endif %}
{%- for aestr in ['system-id', 'admin-key'] %}
{%- if aestr in aecl and aecl[aestr] %}
{{ setifael }} {{ aestr }} {{ aecl[aestr] }}
{%- endif %}
{%- endfor %}
{%- endif %} {#- close lacp check -#}
{%- if 'mc' in aec %}
{%- set mclc = aec['mc'] -%}
{%- set setifaemc = setifae ~ 'mc-ae' -%}
{%- set mclo = ['mc-ae-id', 'redundancy-group', 'chassis-id', 'status-control', 'init-delay-time'] -%}
{%- for option in mclo %}
{%- if option in mclc %}
{{ setifaemc }} {{ option }} {{ mclc[option] }}
{%- endif %}
{%- endfor %}
{{ setifaemc }} mode {{ mclc.get('mode', 'active-active') }}
{%- endif %} {#- close mc check -#}
{%- endif %} {#- close ae check -#}
{%- endfor %} {#- close ifconfig loop -#}
{%- if reth_ns.count > 0 %}
set chassis cluster reth-count {{ reth_ns.count }}
{%- endif %}
070701000000D0000081A400000000000000000000000168EB80BB00000268000000000000000000000000000000000000004F00000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/multi_chassis.j2{%- set mc = salt['pillar.get']('juniper_junos:multi-chassis', {}) -%}
{%- set mcl = mc.get('mc-lag', {}) -%}
{%- set mcp = mc.get('multi-chassis-protection', {}) -%}
{#- todo: add delete statements -#}
{%- set setmc = 'set multi-chassis' -%}
{%- if 'consistency-check' in mcl and 'comparison-delay-time' in mcl['consistency-check'] %}
{{ setmc }} mc-lag consistency-check comparison-delay-time {{ mcl['consistency-check']['comparison-delay-time'] }}
{%- endif %}
{%- if 'name' in mcp and 'interface' in mcp %}
{{ setmc }} multi-chassis-protection {{ mcp['name'] }} interface {{ mcp['interface'] }}
{%- endif %}
070701000000D1000081A400000000000000000000000168EB80BB00000BDB000000000000000000000000000000000000004500000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/nat.j2{#- FIXME: move these to context variables #}
{%- set nat = salt['pillar.get']('juniper_junos:nat', {}) -%}
{%- set sets = 'set security nat' -%}
{%- set setsns = sets ~ ' static' %}
{{ setsns }} rule-set dummy description dummy
delete security nat static
{%- set setsnp = sets ~ ' proxy-arp' %}
{{ setsnp }} interface dummy address 127.0.0.99
delete security nat proxy-arp
{%- for type in ['source', 'destination', 'static'] %}
{%- if type == 'source' %}
{%- set setst = sets ~ ' source' -%}
{%- elif type == 'destination' %}
{%- set setst = sets ~ ' destination' -%}
{%- endif %}
{%- set myconfig = nat.get(type, {}) %}
{%- if myconfig %}
{%- for pool, pconfig in myconfig.get('pools', {}).items() %}
{%- if 'address' in pconfig %}
{{ setst }} pool {{ pool }} address {{ pconfig['address'] }}
{%- if 'port' in pconfig %}
{{ setst }} pool {{ pool }} address {{ pconfig['address'] }} port {{ pconfig['port'] }}
{%- endif %}
{%- endif %} {#- close address check -#}
{%- endfor %} {#- close pools loop -#}
{%- for ruleset, rsconfig in myconfig.get('rule-sets', {}).items() %}
{%- set setstrs = setst ~ ' rule-set ' ~ ruleset %}
{%- if 'description' in rsconfig %}
{{ setstrs }} description {{ rsconfig['description'] }}
{%- endif %}
{%- for scope in ['zone', 'interface'] %}
{%- for direction in ['from', 'to'] %}
{%- if direction in rsconfig %}
{%- if scope in rsconfig[direction] %}
{{ setstrs }} {{ direction }} {{ scope }} {{ rsconfig[direction][scope] }}
{%- endif %}
{%- endif %}
{%- endfor %} {#- close direction loop -#}
{%- endfor %} {#- close scope loop -#}
{%- for rule, rsrconfig in rsconfig.get('rules', {}).items() %}
{%- set setstrsr = setstrs ~ ' rule ' ~ rule %}
{{ setstrsr }}
{%- for plural, singular in {
'applications': 'application',
'destination-addresses': 'destination-address',
'destination-address-names': 'destination-address-name',
'source-addresses': 'source-address',
'source-address-names': 'source-address-name',
'source-ports': 'source-port',
'destination-ports': 'destination-port'
}.items() %}
{%- for entry in rsrconfig.get(plural, []) %}
{{ setstrsr }} match {{ singular }} {{ entry }}
{%- endfor %}
{%- endfor %}
{%- set then = rsconfig.get('then', {}) %}
{%- if then %}
{%- if type == 'source' %}
{%- if 'pool' in then %}
{{ setstrsr }} then source-nat pool {{ then['pool'] }}
{%- elif then.get('interface', true) %}
{{ setstrsr }} then source-nat interface
{%- endif %}
{%- elif type == 'destination' %}
{{ setstrsr }} then destination-nat pool {{ then['pool'] }}
{%- elif type == 'static' %}
{{ setstrsr }} then static-nat prefix {{ then['prefix'] }}
{%- endif %}
{%- endif %} {#- close then check -#}
{%- endfor %} {#- close rules loop -#}
{%- endfor %} {#- close rule-sets loop -#}
{%- endif %} {#- close myconfig check -#}
{%- endfor %} {#- close type loop -#}
{%- for interface, address in nat.get('proxy-arp', {}).items() %}
{{ setsnp }} interface {{ interface }} address {{ address }}
{%- endfor %}
070701000000D2000081A400000000000000000000000168EB80BB000001AD000000000000000000000000000000000000004500000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/ntp.j2{%- set ntp_servers = salt['pillar.get']('juniper_junos:ntp_servers', []) %}
{%- set present_ntp_servers = salt['susejunos.get_active_ntp']() -%}
{%- for server in present_ntp_servers %}
{%- if not server in ntp_servers %}
delete system ntp server {{ server }}
{%- endif %}
{%- endfor %}
{%- for server in ntp_servers %}
{%- if not server in present_ntp_servers %}
set system ntp server {{ server }}
{%- endif %}
{%- endfor %}
070701000000D3000081A400000000000000000000000168EB80BB000002ED000000000000000000000000000000000000004A00000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/policies.j2{#- FIXME: move these to context variables #}
{%- set policies = salt['pillar.get']('juniper_junos:policies', {}) -%}
{%- set setp = 'set security policies' -%}
{%- for policy, pconfig in policies.items() %}
{%- set setpp = setp ~ ' from-zone ' ~ pconfig['from-zone'] ~ ' to-zone ' ~ pconfig['to-zone'] ~ ' policy ' ~ policy %}
{{ setpp }}
{%- set options = {'sources': 'source-address', 'destinations': 'destination-address', 'applications': 'application'} %}
{%- for option, setting in options.items() %}
{%- for entry in pconfig.get(option, []) %}
{{ setpp }} match {{ setting }} {{ entry }}
{%- endfor %}
{%- endfor %} {#- close options loop -#}
{{ setpp }} then {{ pl.get('action', 'permit') }}
{%- endfor %} {#- close policies loop -#}
070701000000D4000081A400000000000000000000000168EB80BB000007C9000000000000000000000000000000000000004B00000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/protocols.j2{%- set protocols = salt['pillar.get']('juniper_junos:protocols', {}) -%}
{%- for protocol, pconfig in protocols.items() %}
{%- set setprotocol = 'set protocols ' ~ protocol %}
{%- if protocol in ['router-advertisement', 'lldp', 'lldp-med'] %}
{%- if 'interface' in pconfig %}
{%- set pinterfaces = pconfig['interface'] %}
{%- else %}
{%- set pinterfaces = pconfig.get('interfaces', []) %}
{%- endif %}
{%- if pinterfaces is string %}
{%- set pinterfaces = [pinterfaces] %}
{%- endif %}
{%- for pinterface in pinterfaces %}
{{ setprotocol }} interface {{ pinterface }}
{%- endfor %} {#- close interfaces loop #}
{%- elif protocol == 'iccp' %}
{%- for iccp_key, iccp_value in pconfig.items() %}
{%- if iccp_value is string %}
{{ setprotocol }} {{ iccp_key }} {{ iccp_value }}
{%- elif iccp_key == 'peers' %}
{%- for peer, peer_config in iccp_value.items() %}
{%- set seticcpp = setprotocol ~ ' peer ' ~ peer %}
{%- for peer_key, peer_value in peer_config.items() %}
{%- if peer_value is number or peer_value is string %}
{{ seticcpp }} {{ peer_key }} {{ peer_value }}
{%- elif peer_value is mapping %}
{%- for peer_key_low, peer_value_low in peer_value.items() %}
{%- set seticcplow = seticcpp ~ ' ' ~ peer_key ~ ' ' ~ peer_key_low %}
{%- if peer_value_low is number or peer_value_low is string %}
{{ seticcplow }} {{ peer_value_low }}
{%- elif peer_value_low is mapping %}
{%- for peer_key_very_low, peer_value_very_low in peer_value_low.items() %}
{{ seticcplow }} {{ peer_key_very_low }} {{ peer_value_very_low }}
{%- endfor %} {#- close very low peer_value loop #}
{%- endif %} {#- close peer_value_low check #}
{%- endfor %} {#- close low peer_value loop #}
{%- endif %} {#- close peer_value check #}
{%- endfor %} {#- close peer_config loop #}
{%- endfor %} {#- close iccp peers (iccp_value) loop #}
{%- endif %} {#- close iccp_key/value check #}
{%- endfor %} {#- close pconfig loop #}
{%- endif %} {#- close protocol check #}
{%- endfor %} {#- close protocols loop #}
070701000000D5000081A400000000000000000000000168EB80BB0000021B000000000000000000000000000000000000004C00000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/redundancy.j2{%- set rgs = salt['pillar.get']('juniper_junos:redundancy_groups', {}) -%}
{%- set setcrg = 'set chassis cluster redundancy-group' %}
{%- for group, gconfig in rgs.items() %}
{%- set setgroup = setcrg ~ ' ' ~ group %}
{%- for node, nconfig in gconfig.get('nodes', {}).items() %}
{%- set setgroup = setgroup ~ ' node ' ~ node %}
{%- if 'priority' in nconfig %}
{%- set setgroup = setgroup ~ ' priority ' ~ nconfig['priority'] %}
{%- endif %}
{{ setgroup }}
{%- endfor %} {#- close nodes loop -#}
{%- endfor %} {#- close rgs loop -#}
070701000000D6000081A400000000000000000000000168EB80BB0000021D000000000000000000000000000000000000004800000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/routes.j2{#- FIXME: move these to context variables #}
{%- set routes = salt['pillar.get']('juniper_junos:routes', {}) -%}
{%- set setro = 'set routing-options' %}
{%- for route, rconfig in routes.items() %}
{%- if rconfig['type'] == 'static' and 'next-hop' in rconfig %}
{%- if route | is_ipv6 %}
{{ setro }} rib inet6.0 static route {{ route }} next-hop {{ rconfig['next-hop'] }}
{%- elif route | is_ipv4 %}
{{ setro }} static route {{ route }} next-hop {{ rconfig['next-hop'] }}
{%- endif %}
{%- endif %}
{%- endfor %} {#- close routes loop -#}
070701000000D7000081A400000000000000000000000168EB80BB00000220000000000000000000000000000000000000004600000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/snmp.j2{#- FIXME: move these to context variables #}
{%- set snmp = salt['pillar.get']('juniper_junos:snmp', {}) %}
set snmp community dummy authorization read-only
delete snmp
{%- for community, cconfig in snmp.get('communities', {}).items() %}
{%- set setsc = 'set snmp community ' ~ community %}
{%- if 'authorization' in cconfig %}
{{ setsc }} authorization {{ cconfig['authorization'] }}
{%- endif %}
{%- for client in cconfig.get('clients', []) %}
{{ setsc }} clients {{ client }}
{%- endfor %}
{%- endfor %} {#- close communities loop -#}
070701000000D8000081A400000000000000000000000168EB80BB000000F1000000000000000000000000000000000000004800000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/switch.j2{%- set base = 'juniper_junos/files/' -%}
{%- include base ~ 'baseline.j2' -%}
{%- include base ~ 'redundancy.j2' -%}
{%- include base ~ 'protocols.j2' -%}
{%- include base ~ 'multi_chassis.j2' -%}
{%- include base ~ 'switch_options.j2' -%}
070701000000D9000081A400000000000000000000000168EB80BB000000F0000000000000000000000000000000000000005000000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/switch_options.j2{%- set so = salt['pillar.get']('juniper_junos:switch-options', {}) -%}
{#- todo: add delete statements -#}
{%- set setso = 'set switch-options' -%}
{%- if 'service-id' in so %}
{{ setso }} service-id {{ so['service-id'] }}
{%- endif %}
070701000000DA000081A400000000000000000000000168EB80BB000003F8000000000000000000000000000000000000004800000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/syslog.j2{%- set syslog = salt['pillar.get']('juniper_junos:syslog', {}) -%}
{%- set setsl = 'set system syslog' %}
{{ setsl }} user * any emergency
delete system syslog
{%- for user, userconfig in syslog.get('user', {}).items() %}
{%- for facility, level in userconfig.get('facilities', {}).items() %}
{{ setsl }} user {{ user }} {{ facility }} {{ level }}
{%- endfor %}
{%- endfor %}
{%- for file, fileconfig in syslog.get('file', {}).items() %}
{%- for facility, level in fileconfig.get('facilities', {}).items() %}
{{ setsl }} file {{ file }} {{ facility }} {{ level }}
{%- endfor %}
{%- endfor %}
{#-
{%- for type in ['user', 'file', 'server'] %}
{%- for object in syslog.get(type, {}) %}
{%- for facility, level in object.get('facilities', {}).items() %}
{{ setsl }} {{ type }} {{ facility }} {{ level }}
{%- endfor %}
{%- endfor %}
{%- endfor %}
#}
{#- FIXME: allow for syslog servers which are not "any any" #}
{%- for server in syslog.get('servers', []) %}
{{ setsl }} host {{ server }} any any
{%- endfor %}
070701000000DB000081A400000000000000000000000168EB80BB000004B8000000000000000000000000000000000000004700000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/vlans.j2{#- FIXME: move these to context variables #}
{%- set vlans = salt['pillar.get']('juniper_junos:vlans') -%}
{%- set present_vlans = salt['susejunos.get_active_vlans']() -%}
{%- set ignore_vlans = salt['pillar.get']('juniper_junos:ignore', {}).get('vlans', {}) %}
{%- set ignore_vlan_ids = ignore_vlans.get('ids', []) %}
{%- set ignore_vlan_names = ignore_vlans.get('names', []) %}
{%- for id, name in present_vlans.parsed_vlan_dict.items() %}
{%- if id not in ignore_vlan_ids and name not in ignore_vlan_names %}
delete vlans {{ name }}
{%- endif %}
{%- endfor %}
{#- to-do: what is this for ? #}
{%- for name in present_vlans.unparsed_vlan_list %}
{%- if name not in ignore_vlan_names %}
delete vlans {{ name }}
{%- endif %}
{%- endfor %}
{%- for vlan, vlconfig in vlans.items() %}
{%- set vlid = vlconfig['id'] %}
{%- set setvlan = 'set vlans ' ~ vlan %}
{%- if vlid not in ignore_vlan_ids and vlan not in ignore_vlan_names %}
{{ setvlan }} vlan-id {{ vlid }}
{%- if 'description' in vlconfig %}
{{ setvlan }} description "{{ vlconfig['description'] }}"
{%- endif %}
{%- if 'l3-interface' in vlconfig %}
{{ setvlan }} l3-interface {{ vlconfig['l3-interface'] }}
{%- endif %}
{%- endif %}
{%- endfor %}
070701000000DC000081A400000000000000000000000168EB80BB00000393000000000000000000000000000000000000004700000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/files/zones.j2{#- FIXME: move these to context variables #}
{%- set zones = salt['pillar.get']('juniper_junos:zones', {}) -%}
{%- set setz = 'set security zones security-zone' %}
{{ setz }} dummy
delete security zones security-zone dummy
{%- for zone, zconfig in zones.items() %}
{%- set setzz = setz ~ ' ' ~ zone %}
{{ setzz }}
{%- set options = ['protocols', 'system-services'] %}
{%- for option in options %}
{%- for entry in zconfig.get(option, []) %}
{{ setzz }} host-inbound-traffic {{ option }} {{ entry }}
{%- endfor %}
{%- endfor %}
{%- for interface, ifconfig in zconfig.get('interfaces', {}).items() %}
{%- set setzi = setzz ~ ' interfaces ' ~ interface %}
{{ setzi }}
{%- for option in options %}
{%- for entry in ifconfig.get(option, []) %}
{{ setzi }} host-inbound-traffic {{ option }} {{ entry }}
{%- endfor %}
{%- endfor %}
{%- endfor %} {#- close interfaces loop -#}
{%- endfor %} {#- close zones loop -#}
070701000000DD000081A400000000000000000000000168EB80BB00000399000000000000000000000000000000000000004500000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/firewall.sls{#-
Salt state file for managing Juniper Junos based network firewalls
Copyright (C) 2023-2024 SUSE LLC
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'juniper_junos/map.jinja' import config -%}
junos_firewall:
netconfig.managed:
- template_name: salt://{{ slspath }}/files/firewall.j2
- saltenv: {{ saltenv }}
- debug: true
070701000000DE000081A400000000000000000000000168EB80BB00000304000000000000000000000000000000000000004200000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/map.jinja{#-
Jinja variables file for Juniper Junos related Salt states
Copyright (C) 2023-2024 SUSE LLC
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 <https://www.gnu.org/licenses/>.
-#}
{%- set config = salt['pillar.get']('juniper_junos') -%}
070701000000DF000081A400000000000000000000000168EB80BB00000394000000000000000000000000000000000000004300000000salt-formulas-3.0.4/juniper_junos-formula/juniper_junos/switch.sls{#-
Salt state file for managing Juniper Junos based network switches
Copyright (C) 2023-2024 SUSE LLC
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'juniper_junos/map.jinja' import config -%}
junos_switch:
netconfig.managed:
- template_name: salt://{{ slspath }}/files/switch.j2
- saltenv: {{ saltenv }}
- debug: true
070701000000E0000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003300000000salt-formulas-3.0.4/juniper_junos-formula/metadata070701000000E1000081A400000000000000000000000168EB80BB00000089000000000000000000000000000000000000004000000000salt-formulas-3.0.4/juniper_junos-formula/metadata/metadata.yml---
summary:
Salt states for managing Junos
description:
Salt states for managing Juniper Junos based network devices using pillars.
070701000000E2000081A400000000000000000000000168EB80BB00000FA9000000000000000000000000000000000000003D00000000salt-formulas-3.0.4/juniper_junos-formula/pillar.example.yml---
# yamllint disable rule:line-length
# Configure the Salt proxy minion
proxy:
proxytype: napalm
driver: junos
username: geeko
passwd: it.is.recommended.to.store.the.passphrase.as.a.pgp.encrypted.secret
host: firewall1.example.com
# Configure the formula
#
# The same pillar structure is used for all available states, however some pillar options are not compatible with all device types.
# I hope to include more thorough examples about all possible options and their respective device compatibilities in the future.
#
juniper_junos:
interfaces:
ae0:
mtu: 9100
description: My aggregated interface
ae:
lacp:
force-up: true
system-id: ff:ff:ff:ff:ff:ff
admin-key: 65535
mc:
mc-ae-id: x
redundancy-group: 1
chassis-id: 12345
mode: active-active
status-control: asdf
init-delay-time: 300
ge-0/0/2:
description: foo
mtu: 9100
speed: 1G
# "native_vlan" cannot be combined with vlan:access, only with vlan:trunk
native_vlan: 2
units:
0:
description: bar
inet:
addresses:
- 192.168.99.1/29
inet6:
addresses:
- fd15:5695:f4b6:43d5::1/128
ge-0/0/3:
mtu: 9100
# "lacp" cannot be combined with any other interface options
lacp: ae0
ge-0/0/4:
mtu: 9000
units:
0:
vlan:
# "access" and "trunk" cannot co-exist
type: trunk
ids:
- 1
- 2
# - "reth*" interfaces will be counted to set the reth-count
# - "reth*" interfaces are not supported on QFX devices
reth0:
description: test
mtu: 9100
redundancy-group: 1
units:
0:
vlan:
type: access
ids:
- 1
ge-0/0/1:
mtu: 9100
# - ensure the specified reth interface exists in the pillar like in the example above
# the formula currently does not validate whether dependent interfaces exist
# - "reth" is not supported on QFX devices
reth: reth0
# if "disable" is falsy or not specified, the interface will be kept enabled
disable: false
multi-chassis:
mc-lag:
consistency-check:
comparison-delay-time: 600
multi-chassis-protection:
interface: ae0
name: 192.168.1.2
switch-options:
service-id: 1
# "redundancy_groups" are not supported on QFX devices
redundancy_groups:
1:
nodes:
1:
priority: 10
vlans:
vlan1:
id: 1
vlan2:
id: 2
vlan200:
id: 200
description: Baz
iccp:
id: 900
l3-interface: irb
ignore:
# these interface names will not be touched by the automation
# this is useful for the management interfaces Salt is connecting to
interfaces:
- em0
syslog:
user:
facilities:
any: emergency
file:
messages:
facilities:
any: notice
authorization: info
interactive-commands: any
zones:
myfirstzone:
interfaces:
ge-0/0/2:
protocols:
- ospf
mysecondzone:
interfaces:
ge-0/0/4:
system-services:
- dns
- ssh
routes:
192.168.100.0/24:
type: static
next-hop: 192.168.99.2
fd15:5695:f4b6:43d6::/64:
type: static
next-hop: fd15:5695:f4b6:43d5::1
ntp_servers:
- 192.168.100.1
protocols:
iccp:
local-ip-addr: 192.168.1.1
peers:
192.168.1.2:
session-establishment-hold-time: 340
redundancy-group-id-list: 1
backup-liveness-detection:
backup-peer-ip: 192.168.1.3
liveness-detection:
version: automatic
minimum-interval: 5000
transmit-interval:
minimum-interval: 1000
070701000000E3000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003000000000salt-formulas-3.0.4/juniper_junos-formula/tests070701000000E4000081A400000000000000000000000168EB80BB00000987000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/juniper_junos-formula/tests/conftest.py"""
Pytest helper functions for testing the Juniper Junos formula
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
"""
from jnpr.junos.utils.config import Config as JunosConfig
from lib import junos_device
import pytest
def pytest_addoption(parser):
parser.addoption('--model', action='store')
parser.addoption('--target', action='store')
def pytest_generate_tests(metafunc):
value = metafunc.config.option.target
if 'target' in metafunc.fixturenames and value is not None:
metafunc.parametrize('target', [value])
@pytest.fixture
def model(request):
modelarg = request.config.getoption('--model')
if modelarg in ['srx', 'vsrx']:
return 'vsrx-device1'
if modelarg in ['qfx', 'vqfx']:
return 'vqfx-device1'
@pytest.fixture
def device(target, model):
with junos_device(target) as jdevice:
jconfig = JunosConfig(jdevice, mode='exclusive')
rescue = jconfig.rescue(action='get')
if rescue is None:
jconfig.rescue(action='save')
else:
print('Existing rescue configuration, test suite may not behave correctly')
yield model
with junos_device(target) as jdevice:
jconfig = JunosConfig(jdevice, mode='exclusive')
jconfig.rescue(action='reload')
jconfig.commit()
jconfig.rescue(action='delete')
@pytest.fixture
def vlan(target):
with junos_device(target) as jdevice:
with JunosConfig(jdevice, mode='exclusive') as jconfig:
for cmdset in [
'set vlans pytest-vlan vlan-id 99',
'set vlans pytest-vlan description "VLAN fixture"'
]:
jconfig.load(cmdset)
jconfig.commit()
yield
jconfig.rollback(1)
jconfig.commit()
070701000000E5000081A400000000000000000000000168EB80BB000005D2000000000000000000000000000000000000003700000000salt-formulas-3.0.4/juniper_junos-formula/tests/lib.py"""
Helper functions for testing the Juniper Junos formula
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
"""
from jnpr.junos import Device as JunosDevice
import json
import requests
def api(target, path, params={}):
return requests.get(url=f'https://{target}/rpc/{path}', params=params, verify=False, auth=requests.auth.HTTPBasicAuth('vrnetlab', 'VR-netlab9')).json()
def junos_device(target):
return JunosDevice(host=target, user='vrnetlab', password='VR-netlab9')
def salt(host, device, command):
# use custom salt cli to skip deprecation warnings ...
result = host.run(f'/usr/local/bin/salt --out json {device} {command}')
output = json.loads(result.stdout)[device]
return output, result.stderr, result.rc
def salt_apply(host, device, state, test=False):
return salt(host, device, f'state.apply {state} test={test}')
070701000000E6000081A400000000000000000000000168EB80BB00000CE3000000000000000000000000000000000000003B00000000salt-formulas-3.0.4/juniper_junos-formula/tests/pillar.sls{%- set id = grains['id'] %}
include:
- devices.{{ id }}
proxy:
proxytype: napalm
driver: junos
username: vrnetlab
passwd: VR-netlab9
juniper_junos:
interfaces:
ae0:
mtu: 9100
description: Katze
ae:
lacp:
force-up: true
system-id: ff:ff:ff:ff:ff:ff
admin-key: 65535
# mc:
# mc-ae-id: asdf
# redundancy-group: bla
# chassis-id: 12345
# mode: active-active
# status-control: asdf
# init-delay-time: 300
irb:
# formula default MTU (9216) works in vQFX, but fails in vSRX (capped to 9192?)
mtu: 1500
units:
900:
inet:
addresses:
- 192.168.98.1/30
# reth* interfaces will be counted to set the reth-count
ge-0/0/2:
description: foo
speed: 1G
mtu: 9100
#reth: reth0
# cannot be combined with vlan:access, only vlan:trunk
native_vlan: 2
units:
0:
description: bar
inet:
addresses:
- 192.168.99.1/32
inet6:
addresses:
- fd15:5695:f4b6:43d5::1/128
ge-0/0/3:
mtu: 9100
# lacp cannot be combined with any other options
lacp: ae0
ge-0/0/4:
mtu: 9000
units:
0:
vlan:
# access/trunk cannot co-exist
type: trunk
ids:
- 1
- 2
ge-0/0/5:
disable: true
mtu: 1500
{%- if 'srx' in id %}
reth0:
description: test
mtu: 9100
redundancy-group: 1
units:
0:
vlan:
type: access
ids:
- 1
ge-0/0/1:
mtu: 9100
reth: reth0
redundancy_groups:
1:
nodes:
1:
priority: 10
{%- endif %}
{%- if 'qfx' in id %}
multi-chassis:
# not available in vQFX?
#mc-lag:
# consistency-check:
# comparison-delay-time: 600
multi-chassis-protection:
interface: ae0
name: 192.168.1.2
switch-options:
service-id: 1
{%- endif %}
vlans:
vlan1:
id: 1
vlan2:
id: 2
l3-interface: irb.900
vlan200:
id: 200
description: baz
ignore:
# these need to be ignored to prevent Salt from being disconnected during testing
interfaces:
- fxp0
- em0
- em1
syslog:
user:
facilities:
any: emergency
file:
messages:
facilities:
any: notice
authorization: info
interactive-commands: any
{%- if 'srx' in id %}
zones:
myfirstzone:
interfaces:
ge-0/0/2:
protocols:
- ospf
mysecondzone:
interfaces:
ge-0/0/4:
system-services:
- dns
- ssh
{%- endif %}
protocols:
iccp:
local-ip-addr: 192.168.1.1
peers:
192.168.1.2:
session-establishment-hold-time: 340
redundancy-group-id-list: 1
backup-liveness-detection:
backup-peer-ip: 192.168.1.3
liveness-detection:
version: automatic
minimum-interval: 5000
transmit-interval:
minimum-interval: 1000
070701000000E7000081A400000000000000000000000168EB80BB00000683000000000000000000000000000000000000004800000000salt-formulas-3.0.4/juniper_junos-formula/tests/test_110_mod_modules.py"""
Test suite for Salt execution modules in the Juniper Junos formula
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
"""
from lib import api, salt
import pytest
@pytest.mark.skip(reason="doesn't work consistently, unsure about the expected behavior (FIXME)")
@pytest.mark.parametrize('arguments', ['parents_only=False', ''])
def test_susejunos_get_active_interfaces(host, device, arguments):
rout, rerr, rc = salt(host, device, f'susejunos.get_active_interfaces {arguments}')
print(rout)
assert not rerr
if arguments == '':
assert not len(rout)
else:
assert len(rout)
assert 'fxp0' in rout
def test_susejunos_get_active_vlans(host, device, vlan):
rout, rerr, rc = salt(host, device, f'susejunos.get_active_vlans')
print(rout)
assert not rerr
assert 'parsed_vlan_dict' in rout
# does the VLAN ID really need to be string ?
assert '99' in rout['parsed_vlan_dict']
assert rout['parsed_vlan_dict']['99'] == 'pytest-vlan'
assert not rout['unparsed_vlan_list']
070701000000E8000081A400000000000000000000000168EB80BB00001920000000000000000000000000000000000000004300000000salt-formulas-3.0.4/juniper_junos-formula/tests/test_120_states.py"""
Test suite for Salt states in the Juniper Junos formula
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
"""
from lib import api, salt_apply
import pytest
import re
@pytest.mark.parametrize('state', ['firewall', 'switch'])
@pytest.mark.parametrize('test', [True, False])
def test_apply(host, device, state, test):
"""
Test to assess whether the device gets the expected configuration applied without any errors
"""
if state == 'firewall' and 'qfx' in device:
pytest.skip('Skipping firewall test on switch')
if state == 'switch' and 'srx' in device:
pytest.skip('Skipping switch test on firewall')
rout, rerr, rc = salt_apply(host, device, f'juniper_junos.{state}', test)
assert not rerr
stateout = rout[f'netconfig_|-junos_{state}_|-junos_{state}_|-managed']
assert stateout['name'] == f'junos_{state}'
assert bool(stateout['changes']) is not test
assert stateout['comment']
if test:
assert 'Configuration discarded.' in stateout['comment']
assert 'Configuration diff:' in stateout['comment']
assert 'Loaded config:' in stateout['comment']
else:
assert 'Configuration changed!\n' == stateout['comment']
assert stateout['changes']['loaded_config']
diffs_firewall = [
'+ any notice;',
'+ authorization info;',
'+ interactive-commands any;',
'+ vlans {',
'+ chassis {',
'+ cluster {',
'+ reth-count 1;',
'+ redundancy-group 1 {',
'+ node 1 priority 10;',
'+ ge-0/0/1 {',
'+ mtu 9100;',
'+ ether-options {',
'+ redundant-parent reth0;',
'+ reth0 {',
'+ description test;',
'+ mtu 9100;',
'+ redundant-ether-options {',
'+ redundancy-group 1;',
'+ unit 0 {',
'+ family ethernet-switching {',
'+ interface-mode access;',
'+ vlan {',
'+ members 1;'
]
diffs_switch = [
'- user \* {',
'- any emergency;',
'\[edit system syslog file messages\]\n\+ interactive-commands any;',
'- file interactive-commands {',
'- interactive-commands any;',
'- default {',
'- vlan-id 1;',
'\[edit\]\n\+ multi-chassis {',
'+ multi-chassis-protection 192.168.1.2 {',
'+ interface ae0;',
'\[edit\]\n\+ switch-options {',
'+ service-id 1;',
'\[edit protocols\]\n\+ iccp {',
'+ local-ip-addr 192.168.1.1;',
'+ local-ip-addr 192.168.1.1;',
'+ peer 192.168.1.2 {',
'+ session-establishment-hold-time 340;',
'+ redundancy-group-id-list 1;',
'+ backup-liveness-detection {',
'+ backup-peer-ip 192.168.1.3;',
'+ }',
'+ liveness-detection {',
'+ version automatic;',
'+ minimum-interval 5000;',
'+ transmit-interval {',
'+ minimum-interval 1000;',
'+ }',
'+ }',
'+ }',
'+ }',
]
diffs_shared = [
'+ ge-0/0/2 {',
'+ description foo;',
'+ speed 1g;',
'+ mtu 9100;',
'+ unit 0 {',
'+ description bar;',
'+ family inet {',
'+ address 192.168.99.1/32;',
'+ family inet6 {',
'+ address fd15:5695:f4b6:43d5::1/128;',
'+ ge-0/0/3 {',
'+ ether-options {',
'+ 802.3ad ae0;',
'+ ge-0/0/4 {',
'+ mtu 9000;',
'+ unit 0 {',
'+ family ethernet-switching {',
'+ interface-mode trunk;',
'+ vlan {',
'+ members 1-2;',
'+ ge-0/0/5 {\n\+ disable;\n\+ mtu 1500;',
'+ ae0 {',
'+ description Katze;',
'+ mtu 9100;',
'+ aggregated-ether-options {',
'+ lacp {',
'+ system-id ff:ff:ff:ff:ff:ff;',
'+ admin-key 65535;',
'+ force-up;',
'+ irb {',
'+ mtu 1500;',
'+ unit 900 {',
'+ family inet {',
'+ address 192.168.98.1/30;',
'+ }',
'+ }',
'+ }',
'+ \s+vlan1 {',
'+ \s+vlan-id 1;',
'+ \s+vlan2 {',
'+ \s+vlan-id 2;',
'+ \s+l3-interface irb.900;',
'+ \s+vlan200 {',
'+ \s+description baz;',
'+ \s+vlan-id 200;'
]
if 'srx' in device:
diffs = diffs_shared + diffs_firewall
else:
diffs = diffs_shared + diffs_switch
if test:
target = stateout['comment']
else:
target = stateout['changes']['diff']
for text in diffs:
if text.startswith('+'):
text = text.replace('+', '\+', 1)
assert bool(re.search(text, target))
070701000000E9000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002200000000salt-formulas-3.0.4/kexec-formula070701000000EA000081A400000000000000000000000168EB80BB000000AA000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/kexec-formula/README.md# Salt states for Kexec
## Available states
`kexec`
Enable `kexec-load` and execute it if needed.
This formula does not offer any pillar based configuration options.
070701000000EB000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002800000000salt-formulas-3.0.4/kexec-formula/kexec070701000000EC000081A400000000000000000000000168EB80BB0000047C000000000000000000000000000000000000003100000000salt-formulas-3.0.4/kexec-formula/kexec/init.sls{#-
Salt state file for managing Kexec
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
kexec_package:
pkg.installed:
- name: kexec-tools
kexec_service_enable:
service.enabled:
- name: kexec-load
- require:
- pkg: kexec_package
kexec_service_run:
module.run:
- name: service.start
- m_name: kexec-load
- unless:
- fun: sysfs.read
key: kernel/kexec_loaded
- require:
- pkg: kexec_package
- service: kexec_service_enable
070701000000ED000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002B00000000salt-formulas-3.0.4/kexec-formula/metadata070701000000EE000081A400000000000000000000000168EB80BB00000079000000000000000000000000000000000000003800000000salt-formulas-3.0.4/kexec-formula/metadata/metadata.yml---
summary:
Salt states for managing Kexec
description:
Salt states for managing Kexec using the kexec-load service
070701000000EF000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002400000000salt-formulas-3.0.4/libvirt-formula070701000000F0000081A400000000000000000000000168EB80BB00000065000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/libvirt-formula/README.md# Salt states for Libvirt
## Available states
`libvirt`
Installs and configures a Libvirt server.
070701000000F1000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/libvirt-formula/libvirt070701000000F2000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003500000000salt-formulas-3.0.4/libvirt-formula/libvirt/defaults070701000000F3000081A400000000000000000000000168EB80BB00000020000000000000000000000000000000000000004200000000salt-formulas-3.0.4/libvirt-formula/libvirt/defaults/libvirt.yaml---
uri_default: qemu:///system
070701000000F4000081A400000000000000000000000168EB80BB00000015000000000000000000000000000000000000004300000000salt-formulas-3.0.4/libvirt-formula/libvirt/defaults/libvirtd.yaml---
max_clients: 128
070701000000F5000081A400000000000000000000000168EB80BB00000080000000000000000000000000000000000000003F00000000salt-formulas-3.0.4/libvirt-formula/libvirt/defaults/qemu.yaml---
security_driver: apparmor
security_default_confined: 1
security_require_confined: 1
lock_manager: lockd
set_process_name: 1
070701000000F6000081A400000000000000000000000168EB80BB0000000E000000000000000000000000000000000000004200000000salt-formulas-3.0.4/libvirt-formula/libvirt/defaults/sockets.yaml---
tcp: true
070701000000F7000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003200000000salt-formulas-3.0.4/libvirt-formula/libvirt/files070701000000F8000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003600000000salt-formulas-3.0.4/libvirt-formula/libvirt/files/etc070701000000F9000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003E00000000salt-formulas-3.0.4/libvirt-formula/libvirt/files/etc/libvirt070701000000FA000081A400000000000000000000000168EB80BB000000F4000000000000000000000000000000000000004B00000000salt-formulas-3.0.4/libvirt-formula/libvirt/files/etc/libvirt/config.jinja{{ pillar.get('managed_by_salt_formula', '# Managed by the libvirt formula') }}
{%- for option, value in config.items() %}
{%- if value is not number %}
{%- set value = '"' ~ value ~ '"' %}
{%- endif %}
{{ option }} = {{ value }}
{%- endfor %}
070701000000FB000081A400000000000000000000000168EB80BB000006DE000000000000000000000000000000000000003700000000salt-formulas-3.0.4/libvirt-formula/libvirt/guests.sls{#-
Salt state file for managing libvirt-guests
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'libvirt/map.jinja' import config -%}
{%- set options = config.get('guests', {}) %}
{%- if 'enable' in options %}
{%- set enable = options.pop('enable') %}
{%- else %}
{%- set enable = True %}
{%- endif %}
{%- if options %}
libvirt_guests_sysconfig_file:
file.managed:
- name: /etc/sysconfig/libvirt-guests
- replace: false
libvirt_guests_sysconfig:
suse_sysconfig.sysconfig:
- name: libvirt-guests
- header_pillar: managed_by_salt_formula_sysconfig
- key_values:
{%- for key, value in options.items() %}
{{ key }}: {{ value }}
{%- endfor %}
- append_if_not_found: true
- require:
- file: libvirt_guests_sysconfig_file
{%- endif %}
libvirt_guests_service:
{%- if enable %}
service.running:
- name: libvirt-guests
- enable: true
{%- if options %}
- require:
- suse_sysconfig: libvirt_guests_sysconfig
{%- endif %}
{%- else %}
service.dead:
- name: libvirt-guests
- enable: false
{%- endif %}
070701000000FC000081A400000000000000000000000168EB80BB00000996000000000000000000000000000000000000003500000000salt-formulas-3.0.4/libvirt-formula/libvirt/init.sls{#-
Salt state file for managing libvirt
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set libvirt_configs = ['network'] -%}
{%- set libvirt_drivers = ['network', 'qemu', 'storage-disk', 'storage-mpath'] -%}
{%- set libvirt_components = ['libvirt', 'libvirtd', 'qemu', 'qemu-lockd', 'virtlockd', 'virtlogd'] -%}
{%- set libvirt_configpath = '/etc/libvirt/' -%}
{%- from 'libvirt/map.jinja' import config -%}
libvirt_packages:
pkg.installed:
- no_recommends: True
- pkgs:
- patterns-server-kvm_server
- libvirt-client
- libvirt-daemon
{%- for config in libvirt_configs %}
- libvirt-daemon-config-{{ config }}
{%- endfor %}
{%- for driver in libvirt_drivers %}
- libvirt-daemon-driver-{{ driver }}
{%- endfor %}
libvirt_files:
file.managed:
- template: jinja
- source: salt://{{ slspath }}/files{{ libvirt_configpath }}config.jinja
- names:
{%- for file in libvirt_components %}
- {{ libvirt_configpath }}{{ file ~ '.conf' }}:
- context:
config: {{ config.get(file, {}) }}
{%- endfor %}
# will restart itself through socket activation
libvirt_service_stop:
service.dead:
- name: libvirtd.service
- require:
- pkg: libvirt_packages
- onchanges:
- file: libvirt_files
{%- for socket, enable in config.sockets.items() %}
{%- if not socket.startswith('libvirtd') %}{%- set socket = 'libvirtd-' ~ socket -%}{%- endif %}
libvirt_{{ socket }}_socket:
{%- if enable %}
service.running:
- reload: False
{%- else %}
service.dead:
{%- endif %}
- name: {{ socket }}.socket
- enable: {{ enable }}
- require:
- pkg: libvirt_packages
- file: libvirt_files
{%- endfor %}
070701000000FD000081A400000000000000000000000168EB80BB00000575000000000000000000000000000000000000003600000000salt-formulas-3.0.4/libvirt-formula/libvirt/map.jinja{#-
Jinja variables file for libvirt related Salt states
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- import_yaml './defaults/libvirt.yaml' as defaults_libvirt -%}
{%- import_yaml './defaults/libvirtd.yaml' as defaults_libvirtd -%}
{%- import_yaml './defaults/qemu.yaml' as defaults_qemu -%}
{%- import_yaml './defaults/sockets.yaml' as defaults_sockets -%}
{%- set defaults = {'libvirt': {}, 'libvirtd': {}, 'qemu': {}, 'sockets': {}} -%}
{%- do defaults.libvirt.update(defaults_libvirt) -%}
{%- do defaults.libvirtd.update(defaults_libvirtd) -%}
{%- do defaults.qemu.update(defaults_qemu) -%}
{%- do defaults.sockets.update(defaults_sockets) -%}
{%- set config = salt.pillar.get('libvirt', default=defaults, merge=True, merge_nested_lists=False) -%}
070701000000FE000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002D00000000salt-formulas-3.0.4/libvirt-formula/metadata070701000000FF000081A400000000000000000000000168EB80BB00000080000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/libvirt-formula/metadata/metadata.yml---
summary:
Salt states for managing libvirt
description:
Salt states for managing libvirt servers.
require:
- sysconfig
07070100000100000081A400000000000000000000000168EB80BB0000061B000000000000000000000000000000000000003300000000salt-formulas-3.0.4/libvirt-formula/pillar.example# this optional libvirt pillar support arbitrary configuration options in the "libvirt", "libvirtd" and "qemu" sections
# the following examples reflect the formula defaults which can be extended or overwritten if needed
libvirt:
# everything under "libvirt" will be set in libvirt.conf
libvirt:
uri_default: "qemu:///system"
# everything under "libvirtd" will be set in libvirtd.conf
libvirtd:
max_clients: 128
# everything under "qemu" will be set in qemu.conf
qemu:
security_driver: apparmor
security_default_confined: 1
security_require_confined: 1
lock_manager: lockd
set_process_name: 1
# additionally, the keys "virtlockd" and "virtlogd" are supported to write their respective .conf files
# those do not have any defaults
# this defines which systemd sockets to enable (true) or disable (false)
# undefined sockets will not be changed (except tcp)
sockets:
# libvirtd-tcp.socket
tcp: true
# admin -> libvirtd-admin.socket, ro -> libvirtd-ro.socket
# libvirtd -> libvirtd.socket (will not get 'libvirtd-' prepended, unlike the other examples)
# contrary the examples above, the following does not reflect the formula defaults.
# everything under "guests" will be set in /etc/sysconfig/libvirt-guests - except for "enable", which defines whether the service should be enabled.
guests:
# "enable" is true by default - set to "false" if libvirt-guests should be disabled
enable: true
on_boot: ignore
on_shutdown: shutdown
parallel_shutdown: 2
start_delay: 5
07070100000101000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002200000000salt-formulas-3.0.4/lldpd-formula07070100000102000081A400000000000000000000000168EB80BB000000E6000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/lldpd-formula/README.md# Salt states for lldpd
## Available states
`lldpd`
Installs and configures [lldpd](https://lldpd.github.io/).
This does not support writing a lldpd configuration file, as we currently only utilize the command line arguments.
07070100000103000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002800000000salt-formulas-3.0.4/lldpd-formula/lldpd07070100000104000081A400000000000000000000000168EB80BB00000232000000000000000000000000000000000000003100000000salt-formulas-3.0.4/lldpd-formula/lldpd/init.slslldpd_package:
pkg.installed:
- name: lldpd
{%- if 'lldpd' in pillar and 'sysconfig' in pillar['lldpd'] %}
lldpd_sysconfig:
suse_sysconfig.sysconfig:
- name: lldpd
- key_values: {{ pillar['lldpd']['sysconfig'] }}
- require:
- pkg: lldpd_package
{%- endif %}
lldpd_service:
service.running:
- name: lldpd
- enable: true
- reload: false
- require:
- pkg: lldpd_package
{%- if 'lldpd' in pillar and 'sysconfig' in pillar['lldpd'] %}
- watch:
- suse_sysconfig: lldpd_sysconfig
{%- endif %}
07070100000105000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002B00000000salt-formulas-3.0.4/lldpd-formula/metadata07070100000106000081A400000000000000000000000168EB80BB00000086000000000000000000000000000000000000003800000000salt-formulas-3.0.4/lldpd-formula/metadata/metadata.yml---
summary:
Salt states for managing lldpd
description:
Salt states for installing and configuring lldpd.
require:
- sysconfig
07070100000107000081A400000000000000000000000168EB80BB00000050000000000000000000000000000000000000003100000000salt-formulas-3.0.4/lldpd-formula/pillar.examplelldpd:
# written to /etc/sysconfig/lldpd
sysconfig:
lldpd_options: -M 1
07070100000108000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002100000000salt-formulas-3.0.4/lock-formula07070100000109000081A400000000000000000000000168EB80BB000002E6000000000000000000000000000000000000002B00000000salt-formulas-3.0.4/lock-formula/README.md# Salt lock module
This only contains the `lock` state module which prevents simultaneous executions of states. Useful during orchestration runs.
Named like a formula for easier packaging.
## Available states
`lock(name, path=/var/lib/salt/)`
Write a lock file.
`unlock(name, path=/var/lib/salt/)`
Deletes a lock file.
`check(name, path=/var/lib/salt/)`
Checks whether a lock file is present (i.e. the operation is currently locked).
## Orchestration example
```
{%- set lock = 'my_important_operation' %}
check_lock:
lock.check:
- name: '{{ lock }}'
- failhard: True
lock:
lock.lock:
- name: '{{ lock }}'
- failhard: True
# some important states go here
unlock:
lock.unlock:
- name: '{{ lock }}'
```
0707010000010A000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002900000000salt-formulas-3.0.4/lock-formula/_states0707010000010B000081A400000000000000000000000168EB80BB00000DD3000000000000000000000000000000000000003100000000salt-formulas-3.0.4/lock-formula/_states/lock.py"""
Salt state module for managing lockfiles
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
"""
from pathlib import Path
def lock(name, path='/var/lib/salt/'):
ret = {'name': name, 'result': False, 'changes': {}, 'comment': ''}
lockfile = path + name
if Path(lockfile).exists():
if __opts__["test"]:
ret["comment"] = "Would have complained about {0} already existing".format(lockfile)
ret["result"] = None
else:
ret['comment'] = 'Lockfile {0} already exists'.format(lockfile)
return(ret)
if __opts__["test"]:
ret["comment"] = "Lockfile {0} would have been created".format(lockfile)
ret["result"] = None
return(ret)
try:
Path(lockfile).touch(exist_ok=False)
except FileExistsError as error:
ret['comment'] = 'Failed to create lockfile {0}, it already exists'.format(lockfile)
return(ret)
except Exception as error:
ret['comment'] = 'Failed to create lockfile {0}, error: {1}'.format(lockfile, error)
return(ret)
if Path(lockfile).exists():
ret['comment'] = 'Lockfile {0} created'.format(lockfile)
ret['result'] = True
else:
ret['comment'] = 'Failed to create lockfile {0}'.format(lockfile)
return(ret)
def unlock(name, path='/var/lib/salt/'):
ret = {'name': name, 'result': False, 'changes': {}, 'comment': ''}
lockfile = path + name
if not Path(lockfile).exists():
if __opts__["test"]:
ret['comment'] = 'Lockfile {0} would have been removed if it existed'.format(lockfile)
ret["result"] = None
else:
ret['comment'] = 'Lockfile {0} does not exist'.format(lockfile)
return(ret)
if __opts__["test"]:
ret["comment"] = "Lockfile {0} would have been removed".format(lockfile)
ret["result"] = None
return(ret)
try:
Path(lockfile).unlink()
except Exception as error:
ret['comment'] = 'Failed to delete lockfile {0}, error: {1}'.format(lockfile, error)
return(ret)
if not Path(lockfile).exists():
ret['comment'] = 'Lockfile {0} deleted'.format(lockfile)
ret['result'] = True
else:
ret['comment'] = 'Failed to delete lockfile {0}'.format(lockfile)
return(ret)
def check(name, path='/var/lib/salt/'):
ret = {'name': name, 'result': False, 'changes': {}, 'comment': ''}
lockfile = path + name
if __opts__["test"]:
ret["comment"] = "Would have checked for existence of lockfile {0}".format(lockfile)
ret["result"] = None
return(ret)
if Path(lockfile).exists():
ret['comment'] = 'Deployment of {0} is locked via {1} - maybe there is an existing execution'.format(name, lockfile)
else:
ret['comment'] = '{0} is not locked'.format(name)
ret['result'] = True
return(ret)
0707010000010C000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002A00000000salt-formulas-3.0.4/lock-formula/metadata0707010000010D000081A400000000000000000000000168EB80BB000000E9000000000000000000000000000000000000003700000000salt-formulas-3.0.4/lock-formula/metadata/metadata.yml---
summary:
Salt state module for managing lockfiles
description:
# yamllint disable-line rule:line-length
Salt state module allowing you to place a lock file prior to other states in order to prevent simultaneous executions.
0707010000010E000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002300000000salt-formulas-3.0.4/lunmap-formula0707010000010F000081A400000000000000000000000168EB80BB0000006F000000000000000000000000000000000000002D00000000salt-formulas-3.0.4/lunmap-formula/README.md# Salt state for managing a LUN mapping file
## Available states
`lunmap`
Creates or updates `/etc/lunmap`.
07070100000110000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002A00000000salt-formulas-3.0.4/lunmap-formula/lunmap07070100000111000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003000000000salt-formulas-3.0.4/lunmap-formula/lunmap/files07070100000112000081A400000000000000000000000168EB80BB00000495000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/lunmap-formula/lunmap/files/lunmap.j2{#-
Jinja template for a LUN mapping file
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set managed_by_salt = salt['pillar.get']('managed_by_salt') -%}
{{ managed_by_salt }}
{%- set mpathraw = salt['cmd.run']("multipathd show paths raw format '%w %i' | awk '!seen[$1]++'", python_shell=True) -%}
{%- set mpathall = mpathraw.splitlines() | sort -%}
{%- do salt.log.debug(mpathall) -%}
{%- for rawentry in mpathall %}
{%- set mpathsingle = rawentry.split(' ') %}
{{ mpathsingle[1].split(':')[-1] }},{{ mpathsingle[0] }}
{%- endfor %}
07070100000113000081A400000000000000000000000168EB80BB0000035A000000000000000000000000000000000000003300000000salt-formulas-3.0.4/lunmap-formula/lunmap/init.sls{#-
Salt state file for managing a lunmap file
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
write_lunmap:
file.managed:
- name: /etc/lunmap
- template: jinja
- source: salt://{{ slspath }}/files/lunmap.j2
07070100000114000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/lunmap-formula/metadata07070100000115000081A400000000000000000000000168EB80BB00000065000000000000000000000000000000000000003900000000salt-formulas-3.0.4/lunmap-formula/metadata/metadata.yml---
summary:
Salt states for managing lunmap
description:
Salt states for managing LUN mappings.
07070100000116000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002200000000salt-formulas-3.0.4/mtail-formula07070100000117000081A400000000000000000000000168EB80BB00000188000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/mtail-formula/README.md# Salt states for mtail
## Available states
`mtail`
Installs and configures [mtail](https://google.github.io/mtail/).
## Available programs
This formula additionally ships with mtail programs which can be enabled using the `mtail:programs` pillar.
The program files use different licenses, please reference their license headers!
TODO: document the provided programs and their metrics.
07070100000118000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002B00000000salt-formulas-3.0.4/mtail-formula/metadata07070100000119000081A400000000000000000000000168EB80BB00000045000000000000000000000000000000000000003800000000salt-formulas-3.0.4/mtail-formula/metadata/metadata.yml---
summary:
Salt states for managing mtail
require:
- sysconfig
0707010000011A000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002800000000salt-formulas-3.0.4/mtail-formula/mtail0707010000011B000081A400000000000000000000000168EB80BB0000008A000000000000000000000000000000000000003600000000salt-formulas-3.0.4/mtail-formula/mtail/defaults.yamlsysconfig:
args:
logs: /var/log/syslog
logtostderr: true
port: 3903
progs: /etc/mtail
syslog_use_current_year: true
0707010000011C000081A400000000000000000000000168EB80BB00000946000000000000000000000000000000000000003100000000salt-formulas-3.0.4/mtail-formula/mtail/init.sls{#-
Salt state file for managing mtail
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'mtail/map.jinja' import config %}
mtail_package:
pkg.installed:
- name: mtail
{%- set sysconfig = [] %}
{%- for key, value in config['sysconfig']['args'].items() %}
{%- if value == true %}
{%- do sysconfig.append('-' ~ key) %}
{%- elif value == false %}
{%- do salt.log.debug('mtail: ignoring ' ~ key) %}
{%- elif value is string or value is number %}
{%- do sysconfig.append('-' ~ key ~ ' ' ~ value) %}
{%- else %}
{%- do salt.log.error('mtail: illegal sysconfig value') %}
{%- endif %}
{%- endfor %}
mtail_sysconfig:
suse_sysconfig.sysconfig:
- name: mtail
- header_pillar: managed_by_salt_formula_sysconfig
- key_values:
ARGS: '{{ ' '.join(sysconfig) }}'
- require:
- pkg: mtail_package
{%- set programs = config.get('programs', []) %}
{%- if programs %}
{%- set programs_directory = config['sysconfig']['args']['progs'] %}
mtail_programs:
file.managed:
- names:
{%- for program in programs %}
{%- set program_file = program ~ '.mtail' %}
- {{ programs_directory }}/{{ program_file }}:
{#- prefer custom programs, default to formula provided ones #}
- source: salt://files/mtail/programs/{{ program_file }}
- source: salt://mtail/programs/{{ program_file }}
{%- endfor %}
- require:
- pkg: mtail_package
mtail_service:
service.running:
- name: mtail
- enable: true
- reload: true
- require:
- pkg: mtail_package
- watch:
- suse_sysconfig: mtail_sysconfig
- file: mtail_programs
{%- else %}
mtail_service:
service.dead:
- name: mtail
- enable: false
{%- endif %}
0707010000011D000081A400000000000000000000000168EB80BB0000038A000000000000000000000000000000000000003200000000salt-formulas-3.0.4/mtail-formula/mtail/map.jinja{#-
Jinja variable file for the mtail Salt states
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- import_yaml 'mtail/defaults.yaml' as defaults -%}
{%- set config = salt.pillar.get('mtail', default=defaults, merge=True, merge_nested_lists=False) -%}
0707010000011E000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003100000000salt-formulas-3.0.4/mtail-formula/mtail/programs0707010000011F000081A400000000000000000000000168EB80BB00001C8F000000000000000000000000000000000000003F00000000salt-formulas-3.0.4/mtail-formula/mtail/programs/postfix.mtail# vim:ts=2:sw=2:et:ai:sts=2:cinoptions=(0
# Syslog parser for Postfix, based on the parsing rules from:
# https://github.com/kumina/postfix_exporter
# Source 1: https://github.com/anarcat/puppet-mtail
# Source 2: https://github.com/google/mtail/blob/main/examples/postfix.mtail
# Copyright 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
# Multi-instance support Copyright 2019 <ale@incal.net>.
# Copyright 2017 MartÃn Ferrari <tincho@tincho.org>. All Rights Reserved.
# Copyright 2017 Kumina, https://kumina.nl/
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
const DELIVERY_DELAY_LINE /\sdelays=(?P<bqm>[0-9\.]+)\/(?P<qm>[0-9\.]+)\/(?P<cs>[0-9\.]+)\/(?P<tx>[0-9\.]+),\s/
const SMTP_TLS_LINE /(\S+) TLS connection established to \S+: (\S+) with cipher (\S+) \((\d+)\/(\d+) bits\)/
const SMTPD_TLS_LINE /(\S+) TLS connection established from \S+: (\S+) with cipher (\S+) \((\d+)\/(\d+) bits\)/
const QMGR_INSERT_LINE /:.*, size=(?P<size>\d+), nrcpt=(?P<nrcpt>\d+)/
const QMGR_REMOVE_LINE /: removed$/
def syslog {
/^(?P<rfc3339_date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+[-+]\d{2}:\d{2})/ { #+ /\s(?P<hostname>[-\w\.\/]+)\s/ {
len($rfc3339_date) > 0 {
strptime($rfc3339_date, "2006-01-02T15:04:05.000000-07:00")
}
next
}
}
# Total number of messages processed by cleanup.
counter postfix_cleanup_messages_processed_total by postfix_instance
# Total number of messages rejected by cleanup.
counter postfix_cleanup_messages_rejected_total by postfix_instance
# Total number of messages removed from mail queues.
counter postfix_qmgr_messages_removed_total by postfix_instance
# Total number of SMTP attempted deliveries by status.
counter postfix_smtp_deliveries by postfix_instance, status
# Total number of outgoing TLS connections.
counter postfix_smtp_tls_connections_total by postfix_instance, trust, protocol, cipher, secret_bits, algorithm_bits
# Total number of incoming connections.
counter postfix_smtpd_connects_total by postfix_instance
# Total number of incoming disconnections.
counter postfix_smtpd_disconnects_total by postfix_instance
# Total number of connections for which forward-confirmed DNS cannot be resolved.
counter postfix_smtpd_forward_confirmed_reverse_dns_errors_total by postfix_instance
# Total number of connections lost.
counter postfix_smtpd_connections_lost_total by postfix_instance, after_stage
# Total number of messages processed.
counter postfix_smtpd_messages_processed_total by postfix_instance
# Total number of rejects (NOQUEUE and others).
counter postfix_smtpd_messages_rejected_total by postfix_instance, code
# Total number of rejects due to rate limiting.
counter postfix_smtpd_messages_ratelimited_total by postfix_instance
# Total number of SASL authentication failures.
counter postfix_smtpd_sasl_authentication_failures_total by postfix_instance
# Total number of incoming TLS connections.
counter postfix_smtpd_tls_connections_total by postfix_instance, trust, protocol, cipher, secret_bits, algorithm_bits
# Total number of unrecognized log lines
counter postfix_unsupported_log_entries_total by postfix_instance, service
# Spamassassin classification counters (ham/spam).
counter spamassassin_ham_total
counter spamassassin_spam_total
# delays
counter postfix_smtp_delivery_delay_seconds by postfix_instance, delay
counter postfix_smtp_delivery_delay_seconds_count by postfix_instance
counter postfix_smtp_delivery_delay_seconds_sum by postfix_instance
# qmgr, TODO: refactor to histogram as per postfix.mtail in the Google mtail examples directory
counter postfix_qmgr_messages_inserted_recipients by postfix_instance
counter postfix_qmgr_messages_inserted_size_bytes by postfix_instance
@syslog {
/(?P<postfix_instance>postfix[-a-z]*)\/(?P<service>[-a-z\/]+)\[/ {
$service == "cleanup" {
/: message-id=</ {
postfix_cleanup_messages_processed_total[$postfix_instance]++
}
/: reject: / {
postfix_cleanup_messages_rejected_total[$postfix_instance]++
}
}
$service == "qmgr" {
// + QMGR_INSERT_LINE {
postfix_qmgr_messages_inserted_recipients[$postfix_instance] = $nrcpt
postfix_qmgr_messages_inserted_size_bytes[$postfix_instance] = $size
}
// + QMGR_REMOVE_LINE {
postfix_qmgr_messages_removed_total[$postfix_instance]++
}
}
$service =~ /smtp$/ {
// + DELIVERY_DELAY_LINE {
# 1st field: before_queue_manager
postfix_smtp_delivery_delay_seconds[$postfix_instance]["before_queue_manager"] = $bqm
# 2nd field: queue_manager
postfix_smtp_delivery_delay_seconds[$postfix_instance]["queue_manager"] = $qm
# 3rd field: connection_setup
postfix_smtp_delivery_delay_seconds[$postfix_instance]["connection_setup"] = $cs
# 4th field: transmission
postfix_smtp_delivery_delay_seconds[$postfix_instance]["transmission"] = $tx
# increase counter (used for average calculation)
postfix_smtp_delivery_delay_seconds_sum[$postfix_instance] = $bqm + $qm + $cs + $tx
postfix_smtp_delivery_delay_seconds_count[$postfix_instance]++
}
/status=(?P<status>\w+)/ {
postfix_smtp_deliveries[$postfix_instance][$status]++
}
// + SMTP_TLS_LINE {
postfix_smtp_tls_connections_total[$postfix_instance][$1][$2][$3][$4][$5]++
}
}
$service == "smtpd" {
/ connect from / {
postfix_smtpd_connects_total[$postfix_instance]++
}
/ disconnect from / {
postfix_smtpd_disconnects_total[$postfix_instance]++
}
/ warning: hostname \S+ does not resolve to address / {
postfix_smtpd_forward_confirmed_reverse_dns_errors_total[$postfix_instance]++
}
/ lost connection after (\w+) from / {
postfix_smtpd_connections_lost_total[$postfix_instance][$1]++
}
/: client=/ {
postfix_smtpd_messages_processed_total[$postfix_instance]++
}
/: reject: RCPT from \S+: (\d+) / {
postfix_smtpd_messages_rejected_total[$postfix_instance][$1]++
/ Rate limit / {
postfix_smtpd_messages_ratelimited_total[$postfix_instance]++
}
}
/warning: \S+: SASL \S+ authentication failed: / {
postfix_smtpd_sasl_authentication_failures_total[$postfix_instance]++
}
// + SMTPD_TLS_LINE {
postfix_smtpd_tls_connections_total[$postfix_instance][$1][$2][$3][$4][$5]++
}
}
otherwise {
postfix_unsupported_log_entries_total[$postfix_instance][$service]++
}
}
/spamd: clean message \([0-9.]+\/[0-9.]+\) for/ {
spamassassin_ham_total++
}
/spamd: identified spam \([0-9.]+\/[0-9.]+\) for/ {
spamassassin_spam_total++
}
}
07070100000120000081A400000000000000000000000168EB80BB00000324000000000000000000000000000000000000003100000000salt-formulas-3.0.4/mtail-formula/pillar.examplemtail:
sysconfig:
# startup parameters - if not set, the following defaults will apply
# these defaults are not the same as the ones shipped with the mtail package!
args:
logs: /var/log/syslog
logtostderr: true
port: 3903
progs: /etc/mtail
syslog_use_current_year: true
# which program files to install
# - custom programs can be provided in salt://files/mtail/, those will be attempted first
# - if no matching custom program is available, formula provided ones (salt://mtail/programs/) will be attempted
# - the .mtail suffix is implied
# - the mtail service will only be enabled if programs are listed in the pillar
# - by default, no programs will be installed - the following are the available formula provided ones
programs:
- postfix
07070100000121000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002600000000salt-formulas-3.0.4/multipath-formula07070100000122000081A400000000000000000000000168EB80BB00000063000000000000000000000000000000000000003000000000salt-formulas-3.0.4/multipath-formula/README.md# Salt states for multipath
## Available states
`multipath`
Installs and configures multipathd.
07070100000123000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002F00000000salt-formulas-3.0.4/multipath-formula/metadata07070100000124000081A400000000000000000000000168EB80BB0000008E000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/multipath-formula/metadata/metadata.yml---
summary:
Salt states for managing multipath
description:
Salt states for installing multipath-tools and managing multipath/multipathd
07070100000125000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003000000000salt-formulas-3.0.4/multipath-formula/multipath07070100000126000081A400000000000000000000000168EB80BB0000014E000000000000000000000000000000000000003E00000000salt-formulas-3.0.4/multipath-formula/multipath/defaults.yaml---
defaults:
checker_timeout: 60
no_path_retry: queue
path_checker: tur
path_grouping_policy: multibus
polling_interval: 15
devices:
- vendor: NETAPP
product: LUN
path_grouping_policy: group_by_prio
prio: ontap
- vendor: NETAPP
product: LUN C-Mode
path_grouping_policy: group_by_prio
prio: alua
07070100000127000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003600000000salt-formulas-3.0.4/multipath-formula/multipath/files07070100000128000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/multipath-formula/multipath/files/etc07070100000129000081A400000000000000000000000168EB80BB000002CB000000000000000000000000000000000000004F00000000salt-formulas-3.0.4/multipath-formula/multipath/files/etc/multipath.conf.jinja{%- from 'multipath/macros.jinja' import device -%}
{%- from 'multipath/map.jinja' import config -%}
{{ pillar.get('managed_by_salt_formula', '# Managed by the multipath formula') }}
{%- for section in config.keys() %}
{%- if config[section] %}
{{ section }} {
{%- if section == 'defaults' or section == 'blacklist' %}
{%- for option, value in config[section].items() %}
{%- if option == 'devices' %}
{%- for subsection in value %}
{{ device(subsection) }}
{%- endfor %}
{%- else %}
{{ option }} {{ value }}
{%- endif %}
{%- endfor %}
{%- elif section == 'devices' %}
{%- for subsection in config[section] %}
{{ device(subsection) }}
{%- endfor %}
{%- endif %}
}
{%- endif %}
{%- endfor %}
0707010000012A000081A400000000000000000000000168EB80BB000006D4000000000000000000000000000000000000003900000000salt-formulas-3.0.4/multipath-formula/multipath/init.sls{#-
Salt state file for managing multipath
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
include:
- .packages
multipath_config:
file.managed:
- name: /etc/multipath.conf
- source: salt://{{ slspath }}/files/etc/multipath.conf.jinja
- template: jinja
- require:
- pkg: multipath_packages
multipath_service_reload:
module.run:
- name: service.reload
- m_name: multipathd
- onchanges:
- file: multipath_config
- onlyif:
- fun: service.status
name: multipathd
- require:
- pkg: multipath_packages
{%- if grains['osrelease'] | float > 15.5 %}
multipath_service:
service.running:
- name: multipathd
- enable: true
- require:
- pkg: multipath_packages
- file: multipath_config
multipath_socket:
service.dead:
- name: multipathd.socket
{%- else %}
multipath_socket:
service.running:
- name: multipathd.socket
- enable: true
- require:
- pkg: multipath_packages
- file: multipath_config
{%- endif %}
0707010000012B000081A400000000000000000000000168EB80BB000003B4000000000000000000000000000000000000003D00000000salt-formulas-3.0.4/multipath-formula/multipath/macros.jinja{#-
Jinja macros file providing helper functions for multipath related Salt state files
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- macro device(config) -%}
device {
{%- for option, value in config.items() %}
{{ option }} "{{ value.replace('"', '""') }}"
{%- endfor %}
}
{%- endmacro -%}
0707010000012C000081A400000000000000000000000168EB80BB00000461000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/multipath-formula/multipath/map.jinja{#-
Jinja variables file for the multipath Salt states
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- import_yaml './defaults.yaml' as defaults -%}
{%- set multipath = salt.pillar.get('multipath', default=defaults, merge=True) -%}
{%- set defaults = multipath.get('defaults', {}) -%}
{%- set blacklist = multipath.get('blacklist', {}) -%}
{%- set devices = multipath.get('devices', []) -%}
{%- set config = {'defaults': defaults, 'blacklist': blacklist, 'devices': devices} -%}
0707010000012D000081A400000000000000000000000168EB80BB00000334000000000000000000000000000000000000003D00000000salt-formulas-3.0.4/multipath-formula/multipath/packages.sls{#-
Salt state file for managing packages related to multipath
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
multipath_packages:
pkg.installed:
- pkgs:
- multipath-tools
0707010000012E000081A400000000000000000000000168EB80BB000001A8000000000000000000000000000000000000003500000000salt-formulas-3.0.4/multipath-formula/pillar.example# this formula supports arbitrary configuration options under the defaults/blacklist/devices sections
multipath:
defaults:
verbosity: 2
blacklist:
wwid: 1234
devnode: '^(dm-raid|loop)[0-9]*'
devices:
- vendor: MAXTOR
product: ''
devices:
# this NetApp block is added by default
- vendor: NETAPP
product: LUN C-Mode
path_grouping_policy: group_by_prio
prio: alua
0707010000012F000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002400000000salt-formulas-3.0.4/network-formula07070100000130000081A400000000000000000000000168EB80BB00000492000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/network-formula/README.md# Salt states for managing the network
This formula manages the network configuration on a SLE/openSUSE based host.
The interface and routing configuration logic validates Salt Master connectivity after any network changes and reverts the configuration should connectivity be lost. Ideally, this should allow a Salt highstate to return even if harmful network configuration was provided in the pillar.
Operation on minions without a Salt master is currently not supported (`salt-call --local` will work as long as any Salt master is connected).
Currently only [Wicked](https://github.com/openSUSE/wicked) is supported, however the state layout is intended to facilitate other backends in the future.
## Available states
`network`
Configures all possible aspects using either the pillar specified or the default backend (Wicked).
`network.wicked`
Configures all aspects using Wicked.
`network.wicked.interfaces`
Configures interfaces using Wicked (`/etc/sysconfig/network/ifcfg-*`).
`network.wicked.routes`
Configures routes using Wicked (`/etc/sysconfig/network/routes`).
`network.wicked.netconfig`
Configures netconfig (`/etc/sysconfig/network/config`).
07070100000131000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002D00000000salt-formulas-3.0.4/network-formula/metadata07070100000132000081A400000000000000000000000168EB80BB000000A9000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/network-formula/metadata/metadata.yml---
summary:
Salt states for managing the network
description:
Salt states for managing the network configuration using backends like Wicked.
require:
- sysconfig
07070100000133000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/network-formula/network07070100000134000081A400000000000000000000000168EB80BB000003D8000000000000000000000000000000000000003500000000salt-formulas-3.0.4/network-formula/network/init.sls{#-
Salt state file for managing the network
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'network/map.jinja' import backend, legal_backends -%}
{%- if backend in legal_backends %}
include:
- .{{ backend }}
{%- else %}
{%- do salt.log.error('network: unsupported management backend: ' ~ backend) %}
{%- endif %}
07070100000135000081A400000000000000000000000168EB80BB00000482000000000000000000000000000000000000003600000000salt-formulas-3.0.4/network-formula/network/map.jinja{#-
Jinja variables file for the Network Salt states
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set network = salt.pillar.get('network', {}) -%}
{%- set backend = network.get('backend', 'wicked') -%}
{%- set config = network.get('config', {}) %}
{%- set routes = network.get('routes', {}) -%}
{%- set interfaces = network.get('interfaces', {}) -%}
{%- set control = network.get('control', {}) -%}
{%- set do_apply = control.get('apply', True) -%}
{%- set legal_backends = ['wicked'] -%}
07070100000136000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003300000000salt-formulas-3.0.4/network-formula/network/wicked07070100000137000081A400000000000000000000000168EB80BB0000051E000000000000000000000000000000000000003E00000000salt-formulas-3.0.4/network-formula/network/wicked/common.sls{#-
Salt state file for managing utilities for the Wicked Salt states
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'network/wicked/map.jinja' import base_backup, script -%}
network_wicked_backup_directory:
file.directory:
- name: {{ base_backup }}
- mode: '0750'
network_wicked_script:
file.managed:
- name: {{ script }}
- source: salt://{{ slspath }}/files{{ script }}
- mode: '0750'
network_wicked_script_links:
file.symlink:
- names:
- {{ script }}up:
- target: {{ script }}
- {{ script }}down:
- target: {{ script }}
- {{ script }}routes:
- target: {{ script }}
07070100000138000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003900000000salt-formulas-3.0.4/network-formula/network/wicked/files07070100000139000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003D00000000salt-formulas-3.0.4/network-formula/network/wicked/files/usr0707010000013A000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004300000000salt-formulas-3.0.4/network-formula/network/wicked/files/usr/local0707010000013B000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000004800000000salt-formulas-3.0.4/network-formula/network/wicked/files/usr/local/sbin0707010000013C000081ED00000000000000000000000168EB80BB00001B7A000000000000000000000000000000000000005400000000salt-formulas-3.0.4/network-formula/network/wicked/files/usr/local/sbin/saltsafe_if#!/bin/bash
# ifup/ifdown wrapper script ensuring safe operation if called remotely through Salt
# Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
#
# 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 <https://www.gnu.org/licenses/>.
set -Cu
extra="${2:-none}"
base='/etc/sysconfig/network'
base_backup="$base/salt-backup"
self="$(basename $0)"
call="${self##*_}"
logtool="$(command -v logger) -t saltsafe" || logtool=echo
fail() {
echo "$1"
exit 1
}
if [ ! \( "$call" == 'ifdown' -o "$call" == 'ifup' -o "$call" == 'ifroutes' \) ]
then
fail 'Invalid action. Call this script as `saltsafe_ifup` or `saltsafe_ifdown` or `saltsafe_ifroutes`.'
fi
if ! command -v wicked >/dev/null
then
fail 'Tool requires wicked.'
fi
log() {
local msg="$2"
case $1 in
0 )
$logtool "$msg"
if [ "$logtool" != 'echo' ]
then
echo "$msg"
fi ;;
1 )
if [ "$logtool" == 'echo' ]
then
local msg="saltsafe: $msg"
>&2 $logtool "$msg"
else
$logtool -s "$msg"
fi ;;
* )
fail 'Invalid function call' ;;
esac
}
quit() {
case "$1" in
0 ) result="$result result=True" ;;
1 ) result="$result result=False" ;;
esac
echo
log 0 "$result"
exit "$1"
}
check() {
if ! ping -c3 -w5 -q "$master_ip" >/dev/null
then
return 1
fi
if ! timeout 20 salt-call -t15 --out quiet test.ping
then
return 1
fi
}
rollback() {
rollback=yes
cp -v "$file_backup" "$file"
}
run() {
if [ "$call" == 'ifroutes' ]
then
# if possible, a call to reload only the routes would be better suited
local call='systemctl reload network'
else
local call="$call $interface"
fi
timeout --preserve-status -k 60 30 $call
}
backup() {
if [ -f "$file_backup" ] && command -v old >/dev/null
then
old "$file_backup" >/dev/null
fi
if [ -f "$file" ]
then
cp "$file" "$file_backup"
fi
}
run_test() {
if [ "$call" == 'ifroutes' ]
then
# Get routes from the configuration - currently not used
#desired_routes=$(awk '{ print $1 "_" $2 | "sort -u" }' "$file")
# Get routes passed by Salt on the command line
desired_routes="$(echo $routes | tr ',' '\n' | sort)"
# Get active routes, exclude non-administratively configured ones
existing_routes=$({ ip -br -4 r; ip -br -6 r; } | sort -u | awk '!/^(fe80::\/|::1)|scope link/{ print $1 "_" $3 }')
if [ "${desired_routes}" == "${existing_routes}" ]
then
result="changed=no comment='Routes are already correctly configured.'"
else
result="changed=yes comment='Would reload service to update routes.'"
fi
return
fi
comment1="Would have brought $interface"
comment2="$interface is already"
if [ "$call" == 'ifup' ]
then
if ifstatus "$interface" -o quiet
then
result="changed=no comment=\"$comment2 up\""
else
result="changed=yes comment=\"$comment1 up\""
fi
elif [ "$call" == 'ifdown' ]
then
if ifstatus "$interface" -o quiet
then
result="changed=yes comment=\"$comment1 down\""
else
result="changed=no comment=\"$comment2 down\""
fi
fi
}
run_cycle() {
if run
then
if [ "$call" == 'ifdown' ]
then
log 0 "Brought down interface $interface."
result="changed=yes comment=\"Brought down $interface.\""
quit 0
fi
if check
then
if [ "$rollback" == 'yes' ]
then
if [ "$call" == 'ifroutes' ]
then
log 1 'Routing configuration rollback successful.'
result='changed=yes comment="Routing configuration reverted."'
else
log 1 'Interface configuration rollback successful.'
result='changed=yes comment="Interface configuration reverted."'
fi
else
log 0 'Operation and validation successful.'
result='changed=yes comment="Operation and validation successful."'
backup
fi
quit 0
else
if [ "$call" == 'ifroutes' ]
then
log 1 'Reloaded service, but validation failed.'
result='changed=yes comment="New routing configuration applied but failed."'
else
log 1 "Brought up $interface, but validation failed."
result="changed=yes comment=\"New configuration for interface $interface applied but failed.\""
fi
if [ "$rollback" = 'yes' ]
then
log 1 'Rollback was not successful. Giving up.'
if [ "$call" == 'ifroutes' ]
then
result='changed=yes comment="Failed to revert routing configuration."'
else
result='changed=yes comment="Failed to revert interface configuration."'
fi
quit 1
fi
fi
else
result='changed=yes comment="Execution failed."'
return "$?"
fi
}
if [ "$call" == 'ifroutes' ]
then
filename='routes'
if [ "$extra" == 'test' ]
then
routes="${1?Cannot test without routes}"
fi
else
interface="${1?Cannot operate without an interface}"
filename="ifcfg-$interface"
if ! command -v "$call" >/dev/null
then
fail "Unable to locate $call."
fi
fi
file="$base/$filename"
file_backup="$base_backup/$filename"
if [ ! -f "$file_backup" ]
then
if [ "$extra" != 'test' ]
then
backup
fi
fi
# Get IP addresses of the Salt minion and master
read minion_ip master_ip < <(ss -HntA tcp dst :4505 | awk 'END { gsub(/\[|\]/,""); split($4, con_out, /:[[:digit:]]{4,5}$/); split($5, con_in, /:[[:digit:]]{4,5}$/); print con_out[1] " " con_in[1] }')
if [ -z "$minion_ip" -o -z "$master_ip" ]
then
fail 'Unable to determine Salt connection, refusing to operate.'
fi
# Get network interface the minion is using to connect to the master
out_interface="$(ip -br a sh | awk -v ip=$minion_ip '$0 ~ ip { print $1 }')"
danger=no
rollback=no
if [ "$call" == 'ifroutes' ]
then
# Assess whether the master is located in a remote network, requiring routing for the connection
if [ "$(ip -ts r g $master_ip | awk -v ip=$master_ip '$0 ~ ip { print $2 }')" == 'via' ]
then
danger=yes
fi
elif [ "$out_interface" == "$interface" ]
then
danger=yes
if [ "$call" == 'ifdown' ]
then
log 1 'Refusing to bring a potentially dangerous interface down.'
result='changed=no comment="Interface is used for Salt connectivity, refusing to bring it down."'
quit 1
fi
if ! check
then
log 1 'Failed to verify Salt master connectivity, refusing to operate on a potentially dangerous interface.'
result='changed=no comment="Interface is used for Salt connectivity, but functionality could not be validated. Refusing to bring it down."'
quit 1
fi
fi
if [ "$extra" == 'test' ]
then
run_test
result="$result result=None"
quit 0
else
run_cycle
fi
if [ "$danger" == 'yes' -o "$call" == 'ifroutes' ]
then
log 1 'Rolling back ...'
rollback
run_cycle
fi
quit 1
0707010000013D000081A400000000000000000000000168EB80BB0000032F000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/network-formula/network/wicked/init.sls{#-
Salt state file for managing the network using Wicked
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
include:
- .interfaces
- .routes
- .netconfig
0707010000013E000081A400000000000000000000000168EB80BB00001538000000000000000000000000000000000000004200000000salt-formulas-3.0.4/network-formula/network/wicked/interfaces.sls{#-
Salt state file for managing network interfaces using Wicked
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'network/wicked/map.jinja' import base, base_backup, interfaces, script, do_apply -%}
{%- set ifcfg_data = {} %}
{%- set enslaved = [] %}
{%- set startmode_ifcfg = {'auto': [], 'off': []} %}
include:
- .common
{%- for interface, config in interfaces.items() %}
{%- do ifcfg_data.update({ interface: {'addresses': [], 'startmode': 'auto'} }) %}
{%- if 'address' in config %}
{%- set addr = config['address'] %}
{%- elif 'addresses' in config %}
{%- set addr = config['addresses'] %}
{%- else %}
{%- set addr = None %}
{%- endif %}
{%- if addr is string %}
{%- do ifcfg_data[interface]['addresses'].append(addr) %}
{%- elif addr is iterable and addr is not mapping %}
{%- do ifcfg_data[interface]['addresses'].extend(addr) %}
{%- endif %}
{%- for option, value in config.items() %}
{%- set option = option | lower %}
{%- if value is sameas true %}
{%- set value = 'yes' %}
{%- elif value is sameas false %}
{%- if option == 'startmode' %}
{%- set value = 'off' %}
{%- else %}
{%- set value = 'no' %}
{%- endif %}
{%- elif option != 'ethtool_options' %}
{%- set value = value | lower %}
{%- endif %}
{%- if not option in ['address', 'addresses'] %}
{%- do ifcfg_data[interface].update({option: value}) %}
{%- endif %}
{%- endfor %}
{%- if ifcfg_data[interface]['startmode'] in startmode_ifcfg.keys() %}
{%- do startmode_ifcfg[ifcfg_data[interface]['startmode']].append(interface) %}
{%- endif %}
{%- if interface.startswith('br') and 'bridge_ports' in config %}
{%- if not 'bridge' in config %}
{%- do ifcfg_data[interface].update({'bridge': 'yes'}) %}
{%- endif %}
{%- do enslaved.extend(config['bridge_ports'].split()) %}
{%- endif %}
{%- endfor %}
{%- for interface, config in interfaces.items() %}
{%- if not 'bootproto' in config %}
{%- if interface in enslaved %}
{%- set bootproto = 'none' %}
{%- else %}
{%- set bootproto = 'static' %}
{%- endif %}
{%- do ifcfg_data[interface].update({'bootproto': bootproto}) %}
{%- endif %}
{%- endfor %}
{%- if ifcfg_data %}
{%- set interface_files = {} %}
{%- for interface in ifcfg_data.keys() %}
{%- set file = base ~ '/ifcfg-' ~ interface %}
{%- if salt['file.file_exists'](file) %}
{%- do interface_files.update({interface: file}) %}
{%- endif %}
{%- endfor %} {#- close interface loop #}
{%- if interface_files %}
network_wicked_ifcfg_backup:
file.copy:
- names:
{%- for interface, file in interface_files.items() %}
- {{ base_backup }}/ifcfg-{{ interface }}:
- source: {{ file }}
{%- endfor %}
- require:
- file: network_wicked_backup_directory
{%- endif %} {#- close interface_files check #}
network_wicked_ifcfg_settings:
file.managed:
- names:
{%- for interface, config in ifcfg_data.items() %}
- {{ base }}/ifcfg-{{ interface }}:
- contents:
- {{ pillar.get('managed_by_salt_formula', '# Managed by the network formula') | yaml_encode }}
{%- for address in config.pop('addresses') %}
- IPADDR_{{ loop.index }}='{{ address }}'
{%- endfor %}
{%- for key, value in config.items() %}
{%- if value is string %}
- {{ key | upper }}='{{ value }}'
{%- else %}
{%- do salt.log.warning('wicked: unsupported value for key ' ~ key) %}
{%- endif %}
{%- endfor %}
{%- endfor %}
- mode: '0640'
{%- if interface_files %}
- require:
- file: network_wicked_ifcfg_backup
{%- endif %}
{%- endif %}
{%- if do_apply and ( startmode_ifcfg['auto'] or startmode_ifcfg['off'] ) %}
network_wicked_interfaces:
cmd.run:
- names:
{%- for interface in startmode_ifcfg['auto'] %}
- {{ script }}up {{ interface }}:
- stateful:
- test_name: |
if test -x {{ script }}up
then
{{ script }}up {{ interface }} test
else
echo 'changed=True comment="Helper script is not available" result=None'
fi
{%- if salt['cmd.retcode'](cmd='ifstatus ' ~ interface ~ ' -o quiet', ignore_retcode=True) == 0 %}
- onchanges:
- file: {{ base }}/ifcfg-{{ interface }}
{%- endif %}
{%- endfor %}
{%- for interface in startmode_ifcfg['off'] %}
- {{ script }}down {{ interface }}:
- stateful:
- test_name: {{ script }}down {{ interface }} test
- onlyif: ifstatus {{ interface }} -o quiet
{%- endfor %}
- require:
- file: network_wicked_script
{%- if interface_files %}
- file: network_wicked_ifcfg_backup
{%- endif %}
- file: network_wicked_ifcfg_settings
- shell: /bin/sh
{%- endif %}
0707010000013F000081A400000000000000000000000168EB80BB0000044C000000000000000000000000000000000000003D00000000salt-formulas-3.0.4/network-formula/network/wicked/map.jinja{#-
Jinja variables file for the Wicked Salt states
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'network/map.jinja' import config, interfaces, routes, do_apply -%}
{%- set config = config -%}
{%- set interfaces = interfaces -%}
{%- set routes = routes -%}
{%- set do_apply = do_apply -%}
{%- set base = '/etc/sysconfig/network' -%}
{%- set base_backup = base ~ '/salt-backup' %}
{%- set script = '/usr/local/sbin/saltsafe_if' %}
07070100000140000081A400000000000000000000000168EB80BB00000602000000000000000000000000000000000000004100000000salt-formulas-3.0.4/network-formula/network/wicked/netconfig.sls{#-
Salt state file for managing the general network configuration
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'network/wicked/map.jinja' import base, config, do_apply -%}
{%- if config %}
network_wicked_config:
suse_sysconfig.sysconfig:
- name: {{ base }}/config
- header_pillar: managed_by_salt_formula_sysconfig
- quote_integers: true
- key_values:
{%- for key, value in config.items() %}
{%- if value is string %}
{%- set value = value | lower %}
{%- elif value is iterable %}
{%- set value = ' '.join(value) | lower -%}
{%- endif %}
{{ key }}: {{ value }}
{%- endfor %}
{%- if do_apply %}
network_wicked_netconfig_update:
cmd.run:
- name: netconfig update
- onchanges:
- suse_sysconfig: network_wicked_config
{%- endif %} {#- close do_apply check #}
{%- endif %}
07070100000141000081A400000000000000000000000168EB80BB000009A4000000000000000000000000000000000000003E00000000salt-formulas-3.0.4/network-formula/network/wicked/routes.sls{#-
Salt state file for managing network routes using Wicked
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'network/wicked/map.jinja' import base, base_backup, routes, script, do_apply -%}
include:
- .common
- .service
{%- set file = base ~ '/routes' %}
{%- if salt['file.file_exists'](file) %}
{%- set backup = True %}
network_wicked_routes_backup:
file.copy:
- names:
- {{ base_backup }}/routes:
- source: {{ file }}
{%- else %}
{%- set backup = False %}
{%- endif %}
{%- if routes %}
{%- set shell_routes = [] %}
network_wicked_routes:
file.managed:
- name: {{ file }}
- contents:
- {{ pillar.get('managed_by_salt_formula', '# Managed by the network formula') | yaml_encode }}
{%- for route, config in routes.items() %}
{%- if route in ['default4', 'default6'] %}
{%- set route = 'default' %}
{%- endif %}
{%- do shell_routes.append(route ~ '_' ~ config.get('gateway', '')) %}
{%- set options = config.get('options', []) %}
- '{{ route }} {{ config.get('gateway', '-') }} {{ config.get('netmask', '-') }} {{ config.get('interface', '-') }}{{ ' ' ~ ' '.join(options) if options else '' }}'
{%- endfor %}
- mode: '0640'
{%- if do_apply %}
network_wicked_routes_reload:
cmd.run:
- name: {{ script }}routes
{%- if salt['file.file_exists'](script ~ 'routes') %}
- stateful:
- test_name: {{ script }}routes '{{ ','.join(shell_routes) }}' test
{%- else %}
- stateful: true
{%- endif %}
- require:
- file: network_wicked_script
- file: network_wicked_script_links
{%- if backup %}
- file: network_wicked_routes_backup
{%- endif %}
- onchanges:
- file: network_wicked_routes
{%- endif %} {#- close do_apply check #}
{%- endif %}
07070100000142000081A400000000000000000000000168EB80BB00000377000000000000000000000000000000000000003F00000000salt-formulas-3.0.4/network-formula/network/wicked/service.sls{#-
Salt state file for managing the Wicked network service
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
include:
- .common
network_wicked_service:
service.running:
- name: wicked
- enable: true
- reload: true
07070100000143000081A400000000000000000000000168EB80BB00000821000000000000000000000000000000000000003300000000salt-formulas-3.0.4/network-formula/pillar.examplenetwork:
control:
# by default, changes will be applied after configuration files have been written
# this can be set to False if it's desired for no reload operations to be performed
apply: True
# settings written into /etc/sysconfig/network/config
config:
# keys/values are written as-is, but keys are uppercased and booleans values are converted automatically.
netconfig_dns_forwarder: resolver
# values provided in a list will be joined together.
netconfig_dns_static_servers:
- 192.168.120.1
- 192.168.120.2
netconfig_dns_resolver_options:
- attempts:1
- timeout:1
# each listed interface will generate an ifcfg- file
interfaces:
eth0:
# STARTMODE is "auto" by default, causing the interface to be started. if set to "off", it will be stopped.
# other startmodes will not trigger any action by Salt.
startmode: auto
# BOOTPROTO is "static" by default, unless the interfaces is enslaved in a bridge interface, then "none" is.
bootproto: static
# string with a single address or list with multiple addresses for generation of IPADDR fields.
# CIDR notiation for subnet masks is recommended - the IPADDR count number is generated and may not be deterministic, making it difficult to map them to NETMASK fields.
addresses:
- 192.168.120.110/24
# any other keys are written without special treatment. only string and integer values are supported. see ifcfg(5) for options. case is irrelevant.
mtu: 9000
# boolean values are converted automatically
firewall: false
dummy0:
addresses:
- 192.168.101.1/24
- fe80::1/64
mlx0:
ethtool_options: -K foo rxvlan off
# each listed route will be written into /etc/sysconfig/network/routes and applied
# "default4" and "default6" will be written as "default"
routes:
default4:
gateway: 192.168.120.1
default6:
gateway: fe80::1
interface: eth0
10.0.10.1/32:
gateway: 192.168.120.2
options:
- blackhole
07070100000144000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002600000000salt-formulas-3.0.4/orchestra-formula07070100000145000081A400000000000000000000000168EB80BB00000156000000000000000000000000000000000000003000000000salt-formulas-3.0.4/orchestra-formula/README.md# Salt orchestration helper states
These states are included from Orchestration states and not designed not be called directly.
`orchestra.mpathmap`
Manage multipath ID mapping files on hypervisors.
`orchestra.vmdisks`
Manage disks (LUN's) for virtual machines.
`orchestra.vmimage`
Writes an OS image to an empty virtual machine disk.
07070100000146000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002F00000000salt-formulas-3.0.4/orchestra-formula/_modules07070100000147000081A400000000000000000000000168EB80BB00000812000000000000000000000000000000000000003B00000000salt-formulas-3.0.4/orchestra-formula/_modules/vmhelper.py"""
Salt execution module for fetching LUN information related to virtual machines
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
"""
import re
# to-do: better Ansible output parsing
# to-do: make playbook, lunmap and mpathmap locations configurable
# Source: https://stackoverflow.com/a/7085715
def _get_trailing_number(s):
m = re.search(r'\d+$', s)
return int(m.group()) if m else None
def get_lun_by_comment(host, comment):
ansible_extravar = {'ontap_host': host, 'ontap_lun_comment': comment}
lun_out = __salt__['ansible.playbooks'](playbook='playbooks/fetch-lun-by-comment.yml', rundir='/srv/ansible', extra_vars=ansible_extravar)
lun_details = lun_out['plays'][0]['tasks'][0]['hosts']['localhost']['ontap_info']['storage/luns']['records'][0]
return(lun_details)
def get_lun_id(host, comment):
lun_details = get_lun_by_comment(host, comment)
lun_id = _get_trailing_number(lun_details['name'])
return(lun_id)
def get_lun_map():
import csv
mydict = {}
with open('/etc/lunmap', mode='r') as infile:
csv_reader = csv.reader(infile, delimiter=',')
for row in csv_reader:
if len(row) == 0 or row[0].startswith('#'):
continue
mydict.update({row[0]: row[1]})
return(mydict)
def get_mpath_map():
import yaml
with open('/etc/mpathmap', mode='r') as infile:
myyaml = yaml.safe_load(infile)
return(myyaml)
07070100000148000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/orchestra-formula/_states07070100000149000081A400000000000000000000000168EB80BB000023EC000000000000000000000000000000000000003800000000salt-formulas-3.0.4/orchestra-formula/_states/vmdisk.py"""
Salt state module for managing LUNs using the ONTAP Ansible collection
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
"""
import logging
import re
log = logging.getLogger(__name__)
# to-do: some of these function should go into an execution module ...
# to-do: make playbook/rundir configurable
# to-do: parse unexpected results better
# to-do: handle test=True
def _query_luns(host):
ansible_extravar = {'ontap_host': host}
lun_out = __salt__['ansible.playbooks'](playbook='playbooks/fetch-luns.yml', rundir='/srv/ansible', extra_vars=ansible_extravar)
all_luns = lun_out['plays'][0]['tasks'][0]['hosts']['localhost']['ontap_info']['storage/luns']
next_free_lun = lun_out['plays'][0]['tasks'][3]['hosts']['localhost']['ansible_facts']['lun_id']
return(all_luns, next_free_lun)
def _query_lun(host, uuid):
ansible_extravar = {'ontap_host': host, 'ontap_lun_uuid': uuid}
lun_out = __salt__['ansible.playbooks'](playbook='playbooks/fetch-lun.yml', rundir='/srv/ansible', extra_vars=ansible_extravar)
lun_details = lun_out['plays'][0]['tasks'][0]['hosts']['localhost']['ontap_info']['storage/luns']['records'][0]
return(lun_details)
def _create_lun(name, host, size, lunid, volume, vserver):
size = size.rstrip('GB')
ansible_extravar = {'ontap_lun_id': lunid, 'ontap_host': host, 'ontap_size': size, 'ontap_volume': volume, 'ontap_vserver': vserver, 'ontap_comment': name}
lun_out = __salt__['ansible.playbooks'](playbook='playbooks/deploy-lun.yml', rundir='/srv/ansible', extra_vars=ansible_extravar)
return(lun_out)
def _map_lun(host, lunid, volume, vserver, igroup, cluster):
ansible_extravar = {'ontap_lun_id': lunid, 'ontap_host': host, 'ontap_volume': volume, 'ontap_vserver': vserver, 'ontap_igroup': igroup}
map_out = __salt__['ansible.playbooks'](playbook='playbooks/map-lun.yml', rundir='/srv/ansible', extra_vars=ansible_extravar)
return(map_out)
# Source: https://stackoverflow.com/a/14996816
suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
def humansize(nbytes):
i = 0
while nbytes >= 1024 and i < len(suffixes)-1:
nbytes /= 1024.
i += 1
f = ('%.2f' % nbytes).rstrip('0').rstrip('.')
return '%s%s' % (f, suffixes[i])
# Source: https://stackoverflow.com/a/7085715
def get_trailing_number(s):
m = re.search(r'\d+$', s)
return int(m.group()) if m else None
def question_size(name, host, size):
ret = {'name': name, 'result': False, 'changes': {}, 'comment': ''}
lun_query = _query_luns(host)
all_luns = lun_query[0]
for lun in all_luns['records']:
if 'comment' in lun:
if lun['comment'] == name:
lun_details = _query_lun(host, lun['uuid'])
lun_size = humansize(lun_details['space']['size'])
lun_size_human = humansize(lun_details['space']['size'])
# instead of comparing two human sizes, it might be better to convert the input size to bytes in order to compare exact values
if size == lun_size_human:
ret['result'] = True
ret['comment'] = 'Disk for {0} matches size {1}'.format(name, size)
return(ret)
if size != lun_size:
ret['result'] = False
ret['comment'] = 'Found disk for {0}, but requested size {1} does not match {2}'.format(name, size, lun_size_human)
ret['changes'] = lun_details
return(ret)
ret['result'] = False
ret['comment'] = 'No matching LUN found'
ret['changes'] = all_luns
return(ret)
def question_mapping(name, host):
ret = {'name': name, 'result': False, 'changes': {}, 'comment': ''}
lun_query = _query_luns(host)
all_luns = lun_query[0]
for lun in all_luns['records']:
if 'comment' in lun:
if lun['comment'] == name:
lun_details = _query_lun(host, lun['uuid'])
lun_mapped = lun_details['status']['mapped']
if lun_mapped:
ret['result'] = True
ret['comment'] = 'LUN {0} is mapped'.format(name)
return(ret)
else:
ret['comment'] = 'Found LUN {0}, but it is not mapped'.format(name)
ret['changes'] = lun_details
return(ret)
ret['comment'] = 'No matching LUN found'
ret['changes'] = all_luns
return(ret)
def present(name, host, size, volume, vserver, igroup, cluster):
ret = {'name': name, 'result': False, 'changes': {}, 'comment': ''}
lun_query = _query_luns(host)
all_luns = lun_query[0]
for lun in all_luns['records']:
if 'comment' in lun:
if lun['comment'] == name:
lun_details = _query_lun(host, lun['uuid'])
lun_size = lun_details['space']['size']
lun_size_human = humansize(lun_size)
lun_mapped = lun_details['status']['mapped']
lun_id = get_trailing_number(lun_details['name'])
# instead of comparing two human sizes, it might be better to convert the input size to bytes in order to compare exact values
comment_base = 'Found existing disk for {0}'.format(name)
if size == lun_size_human:
comment_size = 'Size {0} matches'.format(lun_size_human)
elif size != lun_size:
_create_lun(name, host, size, lun_id, volume, vserver)
fetch_call = question_size(name, host, size)
ret['changes'] = fetch_call
if fetch_call['result']:
comment_size = 'Resized from {0} to {1}'.format(lun_size_human, size)
else:
ret['result'] = False
ret['comment'] = 'Disk resize failed!'
return(ret)
if lun_mapped:
comment_mapping = 'Already mapped'
else:
_map_lun(host, lun_id, volume, vserver, igroup, cluster)
fetch_call = question_mapping(name, host)
if fetch_call['result']:
comment_mapping = 'Mapped LUN to ID {0}'.format(lun_id)
else:
ret['result'] = False
ret['comment'] = 'LUN mapping failed!'
return(ret)
ret['result'] = True
ret['comment'] = comment_base + ' - ' + comment_size + ' - ' + comment_mapping
return(ret)
lun_id = lun_query[1]
_create_lun(name, host, size, lun_id, volume, vserver)
fetch_size_call = question_size(name, host, size)
ret['changes'] = fetch_size_call
if fetch_size_call['result']:
_map_lun(host, lun_id, volume, vserver, igroup, cluster)
fetch_mapping_call = question_mapping(name, host)
if fetch_mapping_call['result']:
ret['result'] = True
ret['comment'] = 'Disk for {0} with size {1} created and mapped to LUN ID {2}'.format(name, size, lun_id)
else:
ret['result'] = False
ret['comment'] = 'Disk for {0} with size {1} created, but mapping failed'.format(name, size)
else:
ret['comment'] = 'Disk creation went horribly wrong.'
return(ret)
def mpathmap(name, clusterhost, disks, netapphost, vm):
ret = {'name': name, 'result': False, 'changes': {}, 'comment': ''}
ansible_extravar = {'cluster_host': clusterhost, 'disks': disks, 'ontap_host': netapphost, 'vm': vm}
play_out = __salt__['ansible.playbooks'](playbook='playbooks/mpathmap.yml', rundir='/srv/ansible', extra_vars=ansible_extravar)
tasks = play_out['plays'][0]['tasks']
changed = None
# there are probably more efficient ways to do this
for taskid, task in enumerate(tasks):
if task['task']['name'] == 'Update block in mpathmap':
log.debug('Found matching task at position %d' % taskid)
changes = task['hosts']['localhost']
changed = changes['changed']
if changed == None:
ret['comment'] = 'Failed to generate mpathmap'
if changed == False:
ret['comment'] = 'Block in mpathmap for {0} on {1} already in its correct state'.format(vm, clusterhost)
ret['result'] = True
if changed == True:
ret['comment'] = 'Updated mpathmap block for {0} on {1}'.format(vm, clusterhost)
ret['result'] = changed
return(ret)
0707010000014A000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002F00000000salt-formulas-3.0.4/orchestra-formula/metadata0707010000014B000081A400000000000000000000000168EB80BB0000008D000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/orchestra-formula/metadata/metadata.yml---
summary:
Salt orchestration helper states
description:
Salt helper states for the openSUSE/SUSE infrastructure orchestration states.
0707010000014C000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003000000000salt-formulas-3.0.4/orchestra-formula/orchestra0707010000014D000081A400000000000000000000000168EB80BB00000402000000000000000000000000000000000000003D00000000salt-formulas-3.0.4/orchestra-formula/orchestra/mpathmap.sls{#-
Salt state file for managing multipath mappings
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set mypillar = pillar.delegated_orchestra -%}
hypervisor_mpathmap:
vmdisk.mpathmap:
- clusterhost: '{{ mypillar['clusterprimary'] }}'
- netapphost: '{{ mypillar['netapphost'] }}'
- vm: '{{ mypillar['deployhost'] }}'
- disks: {{ mypillar['disks'].keys() | list }}
0707010000014E000081A400000000000000000000000168EB80BB000005D0000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/orchestra-formula/orchestra/vmdisks.sls{#-
Salt state file for managing virtual machine disks
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set mypillar = pillar.delegated_orchestra -%}
{%- set volume_prefix = 'lun_kvm_' -%}
{%- for disk, size in mypillar['disks'].items() %}
{%- if disk == 'root' %}
{%- set volume = volume_prefix ~ 'system' %}
{%- set netapp_vs = mypillar['netapp_vs_primary'] %}
{%- else %}
{%- set volume = volume_prefix ~ 'data' %}
{%- set netapp_vs = mypillar['netapp_vs_secondary'] %}
{%- endif %}
disk_{{ disk }}:
vmdisk.present:
- name: '{{ mypillar['deployhost'] }}_{{ disk }}'
- host: '{{ mypillar['netapp_host'] }}'
- size: '{{ size }}'
- volume: '{{ volume }}'
- cluster: '{{ mypillar['cluster'] }}'
- vserver: '{{ netapp_vs }}'
- igroup: '{{ mypillar['netapp_igroup_primary'] }}'
- failhard: True
{%- endfor %}
0707010000014F000081A400000000000000000000000168EB80BB000008D5000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/orchestra-formula/orchestra/vmimage.sls#!py
"""
Salt state file for applying virtual machine disk images
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
"""
import os
import yaml
def run():
config = {}
mypillar = __pillar__['delegated_orchestra']
target_host = mypillar['deployhost']
target_part = 'root'
image = mypillar['image']
# to-do: set using pillar / default map
imagepath = '/kvm/images/'
imagefile = imagepath + mypillar['image']
if image is None:
__salt__['log.info']('No disk image specified')
return config
# to-do: set using pillar / default map
mpathmap_file = '/etc/mpathmap'
with open(mpathmap_file, 'r') as mpathmap_fh:
mpathmap = yaml.safe_load(mpathmap_fh)
try:
mpathid = mpathmap[target_host][target_part]
except KeyError:
__salt__['log.error']('Could not determine multipath device')
mpathdev = '/dev/disk/by-id/dm-uuid-mpath-' + mpathid
partquery = 'partx -rgoNR ' + mpathdev
partquery_retcode = __salt__['cmd.retcode'](partquery)
if partquery_retcode == 0:
# to-do: allow override using some sort of "force" argument
__salt__['log.debug']('Existing partitions found - skipping image copy.')
state = {'test.show_notification': [
{'text': 'Existing partitions found on {} - skipping image copy'.format(mpathdev)}
]
}
elif partquery_retcode == 1:
__salt__['log.debug']('No existing partitions found - writing disk image.')
# to-do: allow block size override
state = {'cmd.run': [
{'name': 'dd if={} of={} bs={}'.format(imagefile, mpathdev, '16M')}
]
}
config['write_image'] = state
return config
07070100000150000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002600000000salt-formulas-3.0.4/os_update-formula07070100000151000081A400000000000000000000000168EB80BB0000008B000000000000000000000000000000000000003000000000salt-formulas-3.0.4/os_update-formula/README.md# Salt states for os-update
## Available states
`os-update`
Installs and configures [os-update](https://github.com/openSUSE/os-update).
07070100000152000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002F00000000salt-formulas-3.0.4/os_update-formula/metadata07070100000153000081A400000000000000000000000168EB80BB00000032000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/os_update-formula/metadata/metadata.yml---
summary:
Salt states for managing os-update
07070100000154000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003000000000salt-formulas-3.0.4/os_update-formula/os-update07070100000155000081A400000000000000000000000168EB80BB00000ACB000000000000000000000000000000000000003900000000salt-formulas-3.0.4/os_update-formula/os-update/init.sls{#-
Salt state file for managing os-update
Copyright (C) 2023-2024 Georg Pfuetzenreuter
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 <https://www.gnu.org/licenses/>.
-#}
{%- from './map.jinja' import options, config -%}
os-update_package:
pkg.installed:
- name: os-update
{%- if 'os-update' in pillar %}
os-update_config_file:
file.managed:
- name: /etc/os-update.conf
- replace: false
os-update_config_values:
file.keyvalue:
- name: /etc/os-update.conf
- key_values:
{%- for option in options %}
{%- if config[option] is string %}
{%- set value = config[option] %}
{%- else %}
{%- set value = config[option] | join(' ') %}
{%- endif %}
{{ option | upper }}: '"{{ value }}"'
{%- endfor %}
{%- if opts['test'] %}
- ignore_if_missing: true
{%- endif %}
- append_if_not_found: true
- require:
- pkg: os-update_package
- file: os-update_config_file
os-update_config_header:
file.prepend:
- name: /etc/os-update.conf
- text: {{ pillar.get('managed_by_salt_formula', '# Managed by the os_update formula') | yaml_encode }}
{%- elif grains.osfullname == 'openSUSE Tumbleweed' %}
os-update_config_file:
file.absent:
- name: /etc/os-update.conf
{%- endif %}
{%- if config.time or 'accuracysec' in config or 'randomizeddelaysec' in config %}
os-update_timer_unit:
file.managed:
- name: /etc/systemd/system/os-update.timer.d/override.conf
- makedirs: True
- contents:
- {{ pillar.get('managed_by_salt_formula', '# Managed by the os_update formula') | yaml_encode }}
- '[Timer]'
{%- if config.time %}
- 'OnCalendar='
- 'OnCalendar={{ config.time }}'
{%- endif %}
{%- if 'accuracysec' in config %}
- 'AccuracySec={{ config.accuracysec }}'
{%- endif %}
{%- if 'randomizeddelaysec' in config %}
- 'RandomizedDelaySec={{ config.randomizeddelaysec }}'
{%- endif %}
- watch_in:
- service: os-update_timer_service
{%- endif %}
os-update_timer_service:
service.running:
- name: os-update.timer
- enable: {{ config.enable }}
- require:
- pkg: os-update_package
07070100000156000081A400000000000000000000000168EB80BB000004F1000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/os_update-formula/os-update/map.jinja{#-
Jinja variables file for the os-update Salt state
Copyright (C) 2023-2024 Georg Pfuetzenreuter
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 <https://www.gnu.org/licenses/>.
-#}
{%- set defaults = {} -%}
{%- load_yaml as defaults_timer -%}
enable: true
time: false
{%- endload -%}
{%- load_yaml as defaults_config -%}
update_cmd: auto
reboot_cmd: auto
restart_services: 'yes'
ignore_services_from_restart:
- dbus
- virtlockd
services_triggering_reboot:
- dbus
{%- endload -%}
{%- do defaults.update(defaults_timer) -%} {%- do defaults.update(defaults_config) -%}
{%- set config = salt.pillar.get('os-update', default=defaults, merge=True, merge_nested_lists=False) -%}
{%- set options = defaults_config.keys() -%}
07070100000157000081A400000000000000000000000168EB80BB0000027C000000000000000000000000000000000000003500000000salt-formulas-3.0.4/os_update-formula/pillar.exampleos-update:
# by default, the timer is enabled
# to keep it disabled, set this to false
enable: true
# by default, no timer override will be installed, and the packaged default will apply
# to install an override, use the syntax described in systemd.timer(5) - example:
# time: 'Thu *-*-01/4 02:00:00'
time: false
# additional, optional, overrides for the timer unit
accuracysec: 1us
randomizeddelaysec: 20
# options written to /etc/os-update.conf
update_cmd: auto
reboot_cmd: auto
restart_services: 'yes'
ignore_services_from_restart:
- dbus
- virtlockd
services_triggering_reboot:
- dbus
07070100000158000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000001E00000000salt-formulas-3.0.4/packaging07070100000159000081A400000000000000000000000168EB80BB00000D3B000000000000000000000000000000000000002900000000salt-formulas-3.0.4/packaging/genspec.py#!/usr/bin/python3
# Tool to generate an infrastructure-formulas.spec file
# Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+rpm@georg-pfuetzenreuter.net>
#
# 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 <https://www.gnu.org/licenses/>.
import logging
import sys
from argparse import ArgumentParser
pattern = '-formula'
infile = 'packaging/templates/infrastructure-formulas.spec.j2'
outfile = 'infrastructure-formulas.spec'
argp = ArgumentParser()
argg = argp.add_mutually_exclusive_group()
argg.add_argument('-v', help='Print verbose output', action='store_const', dest='loglevel', const=logging.INFO, default=logging.WARNING)
argg.add_argument('-d', help='Print very verbose output', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO)
args = argp.parse_args()
logging.basicConfig()
log = logging.getLogger('genspec')
log.setLevel(args.loglevel)
def abort(msg):
log.error(msg)
sys.exit(1)
try:
from jinja2 import Template
except ImportError as myerror:
abort('Missing jinja2 library')
from pathlib import Path
import yaml
if not Path.is_dir(Path('packaging')):
abort('Please call this program from the repository root')
directories = sorted(Path('.').glob('*{}/'.format(pattern)))
formulas = {}
for directory in directories:
formula = str(directory).removesuffix(pattern)
log.info('Formula: {}'.format(formula))
metadata = '{}/metadata/metadata.yml'.format(directory)
if Path.is_file(Path(metadata)):
with open(metadata) as yml:
meta = yaml.safe_load(yml)
summary = meta.get('summary', None)
description = meta.get('description', None)
lic = meta.get('license', None)
requires = meta.get('require', [])
else:
log.warning('No metadata file for {}'.format(formula))
summary = None
description = None
if summary is None:
abort('Cannot proceed without at least a summary in the metadata file for the {} formula'.format(formula))
log.info('Summary: {}'.format(str(summary)))
log.info('Description: {}'.format(str(description)))
log.info('License: {}'.format(str(lic)))
if any([Path.is_file(Path('{}/{}'.format(directory, file))) for file in ['COPYING', 'LICENCE', 'LICENSE']]) and lic is None:
log.warning('Formula {} ships a custom license, but does not declare it in its metadata. Make sure to update the generated spec file!'.format(formula))
lic = 'FIX-ME'
formulas.update({formula: {'summary': summary, 'description': description, 'license': lic, 'requires': requires}})
log.debug(formulas)
with open(infile, 'r') as j2:
template = Template(j2.read())
rendered = template.render(formulas=formulas)
log.debug(rendered)
with open(outfile, 'w') as spec:
spec.write(rendered)
log.info('Wrote {}'.format(outfile))
0707010000015A000081A400000000000000000000000168EB80BB00000241000000000000000000000000000000000000004000000000salt-formulas-3.0.4/packaging/infrastructure-formulas-rpmlintrc# yes, libvirt starts with "lib"...
addFilter('libvirt-formula.noarch: W: shlib-policy-missing-lib')
addFilter('infrastructure-formulas.noarch: W: explicit-lib-dependency libvirt-formula')
# this is a meta-package installing all the formula subpackages, it doesn't need any files
addFilter('infrastructure-formulas.noarch: W: suse-filelist-empty packages without any files are discouraged in SUSE')
# templated shell scripts are not meant to be directly executable
addFilter('[EW]: non-executable-script /usr/share/salt-formulas/states/.*\.j2 6\d\d (?:/usr)?/bin/(?:ba)?sh')
0707010000015B000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002800000000salt-formulas-3.0.4/packaging/templates0707010000015C000081A400000000000000000000000168EB80BB00001462000000000000000000000000000000000000004800000000salt-formulas-3.0.4/packaging/templates/infrastructure-formulas.spec.j2#
# spec file for package infrastructure-formulas
#
# Copyright (c) 2025 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
# Please submit bugfixes or comments via https://bugs.opensuse.org/
#
%define fdir %{_datadir}/salt-formulas
%define sdir %{fdir}/states
%define mdir %{fdir}/metadata
%define pythons python3
Name: infrastructure-formulas
Version: 0
Release: 0
Summary: Salt states for openSUSE and SLE
License: GPL-3.0-or-later
Group: System/Management
URL: https://github.com/openSUSE/salt-formulas
Source: _service
{%- for formula in formulas.keys() %}
Requires: {{ formula }}-formula
{%- endfor %}
BuildArch: noarch
%description
Salt states for managing applications running on openSUSE or SUSE Linux Enterprise based minions.
%package common
Summary: Files and directories shared by formulas
%description common
Files and directories shared by openSUSE/SUSE infrastructure formuas.
{%- for formula, config in formulas.items() %}
%package -n {{ formula }}-formula
Summary: {{ config.summary }}
Requires: %{name}-common
{%- for require in config.requires %}
Requires: {{ require }}-formula
{%- endfor %}
{%- if config.license is not none %}
License: {{ config.license }}
{%- endif %}
%description -n {{ formula }}-formula
{{ config.description if config.description else config.summary ~ '.' }}
{%- endfor %}
%package -n infrastructure-formula-python
Summary: Infrastructure pillar helpers
BuildRequires: %{python_module pip}
BuildRequires: %{python_module setuptools}
BuildRequires: %{python_module wheel}
BuildRequires: %{pythons}
BuildRequires: python-rpm-macros
BuildArch: noarch
%description -n infrastructure-formula-python
Python libraries to help with rendering Salt formula pillars using YAML datasets found in the openSUSE infrastructure.
%prep
mv %{_sourcedir}/salt-formulas-%{version}/* .
%build
pushd infrastructure-formula/python
%pyproject_wheel
popd
%install
install -dm0755 %{buildroot}%{mdir} %{buildroot}%{sdir} %{buildroot}%{sdir}/_modules %{buildroot}%{sdir}/_states %{buildroot}%{_bindir}
dst_execumodules="%{sdir}/_modules"
dst_statemodules="%{sdir}/_states"
dst_bin='%{_bindir}'
for formula in $(find -mindepth 1 -maxdepth 1 -type d -name '*-formula' -printf '%%P\n')
do
echo "$formula"
fname="${formula%%-*}"
src_metadata="$formula/metadata"
dst_metadata="%{mdir}/$fname"
src_states="$formula/$fname"
dst_states="%{sdir}/$fname"
if [ ! -d "$src_states" ]
then
fname_sub="${fname//_/-}"
src_states="$formula/$fname_sub"
dst_states="%{sdir}/$fname_sub"
fi
src_execumodules="$formula/_modules"
src_statemodules="$formula/_states"
src_bin="$formula/bin"
if [ -d "$src_metadata" ]
then
cp -rv "$src_metadata" "%{buildroot}$dst_metadata"
echo "$dst_metadata" > "$fname.files"
fi
if [ -d "$src_states" ]
then
cp -rv "$src_states" "%{buildroot}$dst_states"
echo "$dst_states" >> "$fname.files"
else
echo "Warning: $formula does not ship with any states"
fi
for mod in execu state bin
do
mode=0644
case "$mod" in
'execu' ) src="$src_execumodules"
dst="$dst_execumodules"
;;
'state' ) src="$src_statemodules"
dst="$dst_statemodules"
;;
'bin' )
src="$src_bin"
dst="$dst_bin"
mode=0755
;;
esac
if [ -d "$src" ]
then
find "$src" -type f \
-execdir install -vm "$mode" {} "%{buildroot}$dst" \; \
-execdir sh -cx 'echo "$1/$(basename $2)" >> "$3"' prog "$dst" {} "../../$fname.files" \;
fi
done
for license in 'COPYING' 'LICENCE' 'LICENSE'
do
if [ -f "$formula/$license" ]
then
echo "%%license $formula/$license" >> "$fname.files"
break
fi
done
done
pushd infrastructure-formula/python
%pyproject_install
popd
%files
%files common
%license COPYING
%doc README.md
%dir %{fdir}
%dir %{mdir}
%dir %{sdir}
%dir %{sdir}/_{modules,states}
{% for formula in formulas.keys() %}
%files -n {{ formula }}-formula -f {{ formula }}.files
{% endfor %}
%files -n infrastructure-formula-python
%dir %{python_sitelib}/opensuse_infrastructure_formula
%pycache_only %{python_sitelib}/opensuse_infrastructure_formula/__pycache__
%{python_sitelib}/opensuse_infrastructure_formula/__{init,version}__.py
%dir %{python_sitelib}/opensuse_infrastructure_formula/pillar
%pycache_only %{python_sitelib}/opensuse_infrastructure_formula/pillar/__pycache__
%{python_sitelib}/opensuse_infrastructure_formula/pillar/*.py
%{python_sitelib}/opensuse_infrastructure_formula-*.dist-info
%changelog
0707010000015D000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002400000000salt-formulas-3.0.4/php_fpm-formula0707010000015E000081A400000000000000000000000168EB80BB000001C1000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/php_fpm-formula/README.md# Salt states for the PHP FPM
(the PHP FastCGI Process Manager)
## Available states
- `php-fpm.manage`
Installs and configures [PHP FPM](https://www.php.net/manual/en/install.fpm.php).
Conflicts with `php-fpm.clean`.
- `php-fpm.clean`
Purges PHP FPM.
Conflicts with `php-fpm.manage`.
- `php-fpm`
Calls `php-fpm.manage` if a `php-fpm` pillar is defined, and `php-fpm.clean` otherwise.
This is the recommended state to include.
0707010000015F000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002D00000000salt-formulas-3.0.4/php_fpm-formula/metadata07070100000160000081A400000000000000000000000168EB80BB00000030000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/php_fpm-formula/metadata/metadata.yml---
summary:
Salt states for managing PHP FPM
07070100000161000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/php_fpm-formula/php-fpm07070100000162000081A400000000000000000000000168EB80BB000003BF000000000000000000000000000000000000003600000000salt-formulas-3.0.4/php_fpm-formula/php-fpm/clean.sls{#-
Salt state file for cleaning PHP FPM
Copyright (C) 2025 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
php-fpm_package:
pkg.removed:
- pkgs:
- php7-fpm
- php8-fpm
- php9-fpm
php-fpm_config:
file.absent:
- names:
- /etc/php7/fpm
- /etc/php8/fpm
- /etc/php9/fpm
07070100000163000081A400000000000000000000000168EB80BB00000370000000000000000000000000000000000000003500000000salt-formulas-3.0.4/php_fpm-formula/php-fpm/init.sls{#-
Salt state file for managing or cleaning PHP FPM
Copyright (C) 2025 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'php-fpm/map.jinja' import enable %}
include:
{%- if enable %}
- .manage
{%- else %}
- .clean
{%- endif %}
07070100000164000081A400000000000000000000000168EB80BB00000399000000000000000000000000000000000000003900000000salt-formulas-3.0.4/php_fpm-formula/php-fpm/macros.jinja{#-
Jinja macros file for the PHP FPM Salt states
Copyright (C) 2025 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set boolmap = {true: 'on', false: 'off'} -%}
{%- macro value(v) -%}
{%- if v in boolmap -%}
{%- set v = boolmap[v] -%}
{%- endif -%}
{{ v }}
{%- endmacro -%}
07070100000165000081A400000000000000000000000168EB80BB00000890000000000000000000000000000000000000003700000000salt-formulas-3.0.4/php_fpm-formula/php-fpm/manage.sls{#-
Salt state file for managing PHP FPM
Copyright (C) 2025 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'php-fpm/map.jinja' import etcfpm, php, pools %}
php-fpm_package:
pkg.installed:
- name: {{ php }}-fpm
php-fpm_config:
file.managed:
- names:
- {{ etcfpm }}php-fpm.conf:
- contents:
- {{ pillar.get('managed_by_salt_formula_ini', '; Managed by the PHP-FPM formula') | yaml_encode }}
- include={{ etcfpm }}php-fpm.d/salt.conf
- {{ etcfpm }}php-fpm.d/salt.conf:
- source: salt://php-fpm/templates/pool.conf.jinja
- template: jinja
- context:
pools: {{ pools }}
- require:
- pkg: php-fpm_package
{%- for file in salt['file.find'](etcfpm ~ 'php-fpm.d', mindepth=1, print='name') %}
{%- if
file not in ['salt.conf', 'www.conf.default']
and
file.replace('.conf', '') not in pools
%}
php-fpm_config_delete_{{ file }}:
file.absent:
- name: {{ etcfpm ~ 'php-fpm.d/' ~ file }}
- watch_in:
- service: php-fpm_service
{%- endif %}
{%- endfor %}
{%- for dir in salt['file.find']('/etc', maxdepth=1, mindepth=1, name='php*', print='name', type='d') %}
{%- if dir != php %}
php-fpm_config_delete_{{ dir }}:
file.absent:
- name: /etc/{{ dir }}/fpm
{%- endif %}
{%- endfor %}
php-fpm_service:
service.running:
- name: php-fpm
- enable: true
- reload: true
- watch:
- file: php-fpm_config
- require:
- pkg: php-fpm_package
07070100000166000081A400000000000000000000000168EB80BB0000043E000000000000000000000000000000000000003600000000salt-formulas-3.0.4/php_fpm-formula/php-fpm/map.jinja{#-
Jinja map file for the PHP FPM Salt states
Copyright (C) 2025 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set mypillar = salt['pillar.get']('php-fpm', {}) -%}
{%- set pools = mypillar.get('pools', {}) -%}
{%- set version = mypillar.get('version', 8) -%}
{%- set php = 'php' ~ version -%}
{%- set etcfpm = '/etc/' ~ php ~ '/fpm/' -%}
{%- if mypillar -%}
{%- set enable = true -%}
{%- else -%}
{%- set enable = false -%}
{%- endif -%}
07070100000167000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003600000000salt-formulas-3.0.4/php_fpm-formula/php-fpm/templates07070100000168000081A400000000000000000000000168EB80BB000002FF000000000000000000000000000000000000004600000000salt-formulas-3.0.4/php_fpm-formula/php-fpm/templates/pool.conf.jinja{%- from 'php-fpm/macros.jinja' import value -%}
{{ pillar.get('managed_by_salt_formula_ini', '; Managed by the PHP-FPM formula') }}
{%- for pool, pool_config in pools | dictsort %}
[{{ pool }}]
{%- for k, v in pool_config.get('options', {}).items() %}
{{ k }} = {{ value(v) }}
{%- endfor %}
{%- for prefix in [
'env',
'php_admin_flag',
'php_admin_value',
]
%}
{%- for k, v in pool_config.get(prefix, {}).items() %}
{{ prefix }}[{{ k }}] = {{ value(v) }}
{%- endfor %}
{%- endfor %}
{%- for prefix in [
'listen',
'pm',
'security',
]
%}
{%- for k, v in pool_config.get(prefix, {}).items() %}
{{ prefix }}.{{ k }} = {{ value(v) }}
{%- endfor %}
{%- endfor %}
{%- endfor %}
07070100000169000081A400000000000000000000000168EB80BB000002D9000000000000000000000000000000000000003300000000salt-formulas-3.0.4/php_fpm-formula/pillar.examplephp-fpm:
# default version is 8
# fpm configuration directories from other versions will be removed if present
version: 7
# pools written to salt.conf
# other fpm configuration files will be removed if present
pools:
# define a pool "www"
www:
options:
apparmor_hat: www
user: wwwrun
group: www
listen: /run/php-fpm/php-www-fpm.sock
pm: dynamic
php_admin_flag:
display_errors: false
log_errors: true
php_admin_value:
memory_limit: 32M
pm:
max_children: 5
max_spare_servers: 3
min_spare_servers: 1
start_servers: 2
status_path: /status
security:
limit_extensions: php
0707010000016A000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002600000000salt-formulas-3.0.4/rebootmgr-formula0707010000016B000081A400000000000000000000000168EB80BB00000087000000000000000000000000000000000000003000000000salt-formulas-3.0.4/rebootmgr-formula/README.md# Salt states for rebootmgr
## Available states
`rebootmgr`
Installs and configures [rebootmgr](https://github.com/SUSE/rebootmgr).
0707010000016C000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002F00000000salt-formulas-3.0.4/rebootmgr-formula/metadata0707010000016D000081A400000000000000000000000168EB80BB00000032000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/rebootmgr-formula/metadata/metadata.yml---
summary:
Salt states for managing rebootmgr
0707010000016E000081A400000000000000000000000168EB80BB000000F1000000000000000000000000000000000000003500000000salt-formulas-3.0.4/rebootmgr-formula/pillar.examplerebootmgr:
# by default, the service will be enabled
# to ensure it stays disabled, set this to false
enable: true
# options written to /etc/rebootmgr.conf
window-start: '3:30'
window-duration: '1h30m'
strategy: 'best-effort'
0707010000016F000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003000000000salt-formulas-3.0.4/rebootmgr-formula/rebootmgr07070100000170000081A400000000000000000000000168EB80BB000008E0000000000000000000000000000000000000003900000000salt-formulas-3.0.4/rebootmgr-formula/rebootmgr/init.sls{#-
Salt state file for managing rebootmgr
Copyright (C) 2023-2024 Georg Pfuetzenreuter
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 <https://www.gnu.org/licenses/>.
-#}
{%- from './map.jinja' import options, config, os -%}
rebootmgr_package:
pkg.installed:
- name: rebootmgr
{%- if 'rebootmgr' in pillar %}
{%- if os == 'openSUSE Tumbleweed' %}
rebootmgr_config_file:
file.managed:
- name: /etc/rebootmgr.conf
- replace: false
{%- endif %}
rebootmgr_config_header:
file.prepend:
- name: /etc/rebootmgr.conf
- text: {{ pillar.get('managed_by_salt_formula', '# Managed by the rebootmgr formula') | yaml_encode }}
- require:
{%- if os == 'openSUSE Tumbleweed' %}
- file: rebootmgr_config_file
{%- else %}
- pkg: rebootmgr_package
{%- endif %}
{%- elif os == 'openSUSE Tumbleweed' %}
rebootmgr_config_file:
file.absent:
- name: /etc/rebootmgr.conf
{%- endif %}
{%- if os == 'Leap' or 'rebootmgr' in pillar %}
rebootmgr_config:
ini.options_present:
- name: /etc/rebootmgr.conf
- sections:
rebootmgr:
{%- for option in options %}
{{ option }}: '"{{ config[option] }}"'
{%- endfor %}
- strict: true
- require:
{%- if os == 'openSUSE Tumbleweed' %}
- file: rebootmgr_config_file
{%- else %}
- pkg: rebootmgr_package
{%- endif %}
{%- endif %}
rebootmgr_service:
service.running:
- name: rebootmgr
- enable: {{ config.get('enable', True) }}
{%- if 'rebootmgr' in pillar %}
- watch:
- ini: rebootmgr_config
{%- elif os == 'openSUSE Tumbleweed' %}
- watch:
- file: rebootmgr_config_file
{%- endif %}
- require:
- pkg: rebootmgr_package
07070100000171000081A400000000000000000000000168EB80BB000003E2000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/rebootmgr-formula/rebootmgr/map.jinja{#-
Jinja variables file for the rebootmgr Salt state
Copyright (C) 2023-2024 Georg Pfuetzenreuter
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 <https://www.gnu.org/licenses/>.
-#}
{%- load_yaml as defaults -%}
window-start: '3:30'
window-duration: '1h30m'
strategy: 'best-effort'
{%- endload %}
{%- set config = salt.pillar.get('rebootmgr', default=defaults, merge=True) -%}
{%- set options = defaults.keys() -%}
{%- set os = grains.get('osfullname') %}
07070100000172000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002200000000salt-formulas-3.0.4/redis-formula07070100000173000081A400000000000000000000000168EB80BB00000123000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/redis-formula/README.md# Salt states for Redis
This is a lightweight alternative to the existing Redis formula in the popular saltstack-formulas project. The pillar structures of the two formulas should not be combined.
## Available states
`redis`
Installs and configures [Redis](https://redis.io/) instances.
07070100000174000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002B00000000salt-formulas-3.0.4/redis-formula/metadata07070100000175000081A400000000000000000000000168EB80BB0000002E000000000000000000000000000000000000003800000000salt-formulas-3.0.4/redis-formula/metadata/metadata.yml---
summary:
Salt states for managing Redis
07070100000176000081A400000000000000000000000168EB80BB000002D4000000000000000000000000000000000000003100000000salt-formulas-3.0.4/redis-formula/pillar.exampleredis:
# this example will instantiate a "redis@myapp" service
myapp:
# any available Redis settings can be defined
port: 0
# the following parameters will be set automatically unless they are overwritten
timeout: 0
supervised: systemd
unixsocket: /run/redis/myapp.sock
unixsocketperm: 460
pidfile: /run/redis/myapp.pid
logfile: /var/log/redis/myapp.log
dir: /var/lib/redis/myapp
# this example will instantiate a "redis@yourapp" service
yourapp:
# this completely disables the formula defaults for this instance
# if this is set (to False), other necessary parameters and dependencies need to be provided to allow the instance to start
formula_defaults: False
07070100000177000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002800000000salt-formulas-3.0.4/redis-formula/redis07070100000178000081A400000000000000000000000168EB80BB000000CF000000000000000000000000000000000000003600000000salt-formulas-3.0.4/redis-formula/redis/defaults.yaml---
timeout: 0
supervised: systemd
unixsocket: /run/redis/%%INSTANCE%%.sock
unixsocketperm: 460
pidfile: /run/redis/%%INSTANCE%%.pid
logfile: /var/log/redis/%%INSTANCE%%.log
dir: /var/lib/redis/%%INSTANCE%%
07070100000179000081A400000000000000000000000168EB80BB000006B0000000000000000000000000000000000000003100000000salt-formulas-3.0.4/redis-formula/redis/init.sls{#-
Salt state file for managing Redis
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'redis/map.jinja' import config, dirs, package -%}
redis_package:
pkg.installed:
- name: {{ package }}
{%- for instance, settings in config.items() %}
redis_{{ instance }}_config:
file.managed:
- name: {{ dirs['config'] }}/{{ instance }}.conf
- contents:
{%- for key, value in settings.items() %}
{%- if value is string and '%%INSTANCE%%' in value %}
{%- set value = value.replace('%%INSTANCE%%', instance) %}
{%- endif %}
- {{ key }} {{ value }}
{%- endfor %}
- user: root
- group: redis
- mode: '0640'
- require:
- pkg: redis_package
redis_{{ instance }}_directory:
file.directory:
- name: {{ dirs['data'] }}/{{ instance }}
- user: redis
- group: redis
- mode: '0750'
- require:
- pkg: redis_package
redis_{{ instance }}_service:
service.running:
- name: redis@{{ instance }}
- enable: True
- watch:
- file: redis_{{ instance }}_config
{%- endfor %}
0707010000017A000081A400000000000000000000000168EB80BB00000643000000000000000000000000000000000000003200000000salt-formulas-3.0.4/redis-formula/redis/map.jinja{#-
Jinja variables file for the Redis Salt states
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- import_yaml 'redis/defaults.yaml' as instance_defaults -%}
{%- set instances = salt['pillar.get']('redis', {}) -%}
{%- set config = {} -%}
{%- for instance, iconfig in instances.items() -%}
{%- if iconfig.get('formula_defaults', True) -%}
{%- for defkey in instance_defaults.keys() -%}
{%- if not defkey in iconfig -%}
{%- do iconfig.update(instance_defaults) -%}
{%- endif -%}
{%- endfor -%} {#- end defaults loop -#}
{%- else -%}
{%- do iconfig.pop('formula_defaults') -%}
{%- endif -%} {#- close formula_defaults check -#}
{%- do config.update({instance: iconfig}) -%}
{%- endfor -%} {#- end instances loop -#}
{%- if grains['osrelease'] | float > 15.4 -%}
{%- set package = 'redis7' -%}
{%- else -%}
{%- set package = 'redis' -%}
{%- endif -%}
{%- set dirs = {
'config': '/etc/redis',
'data': '/var/lib/redis'
}
-%}
0707010000017B000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002800000000salt-formulas-3.0.4/redis-formula/tests0707010000017C000081A400000000000000000000000168EB80BB000007F3000000000000000000000000000000000000003600000000salt-formulas-3.0.4/redis-formula/tests/test_redis.py"""
Test suite for assessing the Redis formula configuration results
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
"""
import pytest
@pytest.fixture
def instance():
return {
'config': '/etc/redis/foo.conf',
'socket': '/run/redis/foo.sock',
'datadir': '/var/lib/redis/foo',
'logfile': '/var/log/redis/foo.log',
'pidfile': '/run/redis/foo.pid',
'service': 'redis@foo'
}
def test_redis_config_file_exists(host, instance):
with host.sudo():
exists = host.file(instance['config']).exists
assert exists is True
def test_redis_config_file_contents(host, instance):
with host.sudo():
struct = host.file(instance['config']).content_string
for line in [
'timeout 0',
'supervised systemd',
f'unixsocket {instance["socket"]}',
'unixsocketperm 460',
f'pidfile {instance["pidfile"]}',
f'logfile {instance["logfile"]}',
f'dir {instance["datadir"]}',
]:
assert line in struct
def test_redis_config_file_permissions(host, instance):
with host.sudo():
file = host.file(instance['config'])
assert file.user == 'root'
assert file.group == 'redis'
assert oct(file.mode) == '0o640'
def test_redis_service(host, instance):
assert host.service(instance['service']).is_enabled
0707010000017D000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002400000000salt-formulas-3.0.4/redmine-formula0707010000017E000081A400000000000000000000000168EB80BB000000F8000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/redmine-formula/README.md# Salt states for Redmine
## Available states
`redmine`
Installs and configures [Redmine](https://www.redmine.org/).
This expects packages from the `openSUSE:infrastructure:redmine` repository, which can be configured using the zypper formula.
0707010000017F000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002D00000000salt-formulas-3.0.4/redmine-formula/metadata07070100000180000081A400000000000000000000000168EB80BB00000030000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/redmine-formula/metadata/metadata.yml---
summary:
Salt states for managing Redmine
07070100000181000081A400000000000000000000000168EB80BB00000204000000000000000000000000000000000000003300000000salt-formulas-3.0.4/redmine-formula/pillar.exampleredmine:
# install additional "redmine-" packages
plugins:
- theme-opensuse
- openid_connect
# write configuration files, all fields can be used
config:
# write "configuration.yml"
configuration:
default:
email_delivery:
delivery_method: :smtp
smtp_settings:
address: mailer@example.com
port: 25
# write "database.yml"
database:
production:
adapter: mysql2
database: redmine
host: db.example.com
07070100000182000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/redmine-formula/redmine07070100000183000081A400000000000000000000000168EB80BB000006F2000000000000000000000000000000000000003500000000salt-formulas-3.0.4/redmine-formula/redmine/init.sls{#-
Salt state file for managing Redmine
Copyright (C) 2023-2025 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
#}
{%- from 'redmine/map.jinja' import config, plugins %}
redmine_packages:
pkg.installed:
- pkgs:
- redmine
{%- for plugin in plugins %}
- redmine-{{ plugin }}
{%- endfor %}
redmine_update:
cmd.run:
- name: redmine-update
- use_vt: True
- onchanges:
- pkg: redmine_packages
{%- for file in ['configuration', 'database'] %}
{%- if file in config %}
redmine_file_{{ file }}:
file.serialize:
- dataset: {{ config[file] }}
- name: /etc/redmine/{{ file }}.yml
- serializer: yaml
- group: redmine
- mode: '0640'
- require:
- pkg: redmine_packages
- watch_in:
- service: redmine_hot_units
{%- endif %}
{%- endfor %}
redmine_cold_units:
service.running:
- names:
- redmine.socket
- redmine-reminder.timer
- enable: True
- require:
- pkg: redmine_packages
redmine_hot_units:
service.running:
- names:
- redmine
- redmine-sidekiq
- enable: True
- require:
- pkg: redmine_packages
07070100000184000081A400000000000000000000000168EB80BB0000038B000000000000000000000000000000000000003600000000salt-formulas-3.0.4/redmine-formula/redmine/map.jinja{#-
Jinja variable file for the Redmine Salt state
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
#}
{%- set redmine = salt['pillar.get']('redmine', {}) -%}
{%- set config = redmine.get('config', {}) -%}
{%- set plugins = redmine.get('plugins', []) -%}
07070100000185000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002A00000000salt-formulas-3.0.4/redmine-formula/tests07070100000186000081A400000000000000000000000168EB80BB00000922000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/redmine-formula/tests/test_redmine.py"""
Test suite for assessing the Redmine formula configuration results
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
This program is free software: you can redminetribute 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 <https://www.gnu.org/licenses/>.
"""
import pytest
import yaml
files = ['configuration', 'database']
confdir = '/etc/redmine'
def test_redmine_package(host):
assert host.package('redmine').is_installed
def test_redmine_config_files_exist(host):
with host.sudo():
for file in files:
assert host.file(f'{confdir}/{file}.yml').exists
def test_redmine_config_file_contents(host):
pillar = {'configuration': {
'default': {
'email_delivery': {
'delivery_method': ':smtp',
'smtp_settings': {
'address': 'mailer@example.com',
'port': 25
}
}
}
},
'database': {
'production': {
'adapter': 'mysql2',
'database': 'redmine',
'host': 'db.example.com'
}
}
}
with host.sudo():
for file in files:
struct = yaml.safe_load(host.file(f'{confdir}/{file}.yml').content_string)
assert pillar[file].items() == struct.items()
def test_redmine_config_file_permissions(host):
with host.sudo():
for file in files:
file = host.file(f'{confdir}/{file}.yml')
assert file.user == 'root'
assert file.group == 'redmine'
assert oct(file.mode) == '0o640'
def test_redmine_service(host):
assert host.service('redmine').is_enabled
assert host.service('redmine-sidekiq').is_enabled
07070100000187000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002200000000salt-formulas-3.0.4/rsync-formula07070100000188000081A400000000000000000000000168EB80BB00000057000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/rsync-formula/README.md# Salt states for rsync
## Available states
`rsync`
Installs and configures rsyncd.
07070100000189000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002B00000000salt-formulas-3.0.4/rsync-formula/metadata0707010000018A000081A400000000000000000000000168EB80BB0000002F000000000000000000000000000000000000003800000000salt-formulas-3.0.4/rsync-formula/metadata/metadata.yml---
summary:
Salt states for managing rsyncd
0707010000018B000081A400000000000000000000000168EB80BB00000529000000000000000000000000000000000000003100000000salt-formulas-3.0.4/rsync-formula/pillar.examplersync:
# global section in rsyncd.conf
# defaults below will be written as shown unless overwritten using the pillar
defaults:
address: '::'
gid: users
log format: '%h %o %f %l %b'
secrets file: /etc/rsyncd.secrets
transfer logging: true
use chroot: true
# module sections in rsyncd.conf
# no module sections will be written by default, the below is an example
modules:
mymodule:
path: /srv/data
comment: Example rsync push target
list: false
uid: geeko
gid: users
auth users: syncgeeko
read only: false
# lists are supported
hosts allow:
- 2001:db8::1/128
- 2001:db8:a::/64
# the formula ships /usr/local/bin/nameconvert.py to allow for resolution of names to IDs using the password database
name converter: /usr/local/bin/nameconvert.py
# note that when using "name converter" together with one of the chroot options, "numeric ids" needs to be disabled - see rsyncd.conf(5) MODULE PARAMETERS -> name converter, numeric ids
numeric ids: false
# rsyncd.secrets file
# no users will be written by default, the below is an example
# data should be stored in an encrypted pillar; users can be referenced using "auth users" in modules
users:
syncgeeko: supersecretpassphrase
0707010000018C000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002800000000salt-formulas-3.0.4/rsync-formula/rsync0707010000018D000081A400000000000000000000000168EB80BB00000084000000000000000000000000000000000000003600000000salt-formulas-3.0.4/rsync-formula/rsync/defaults.yaml---
address: '::'
gid: users
log format: '%h %o %f %l %b'
secrets file: /etc/rsyncd.secrets
transfer logging: true
use chroot: true
0707010000018E000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/rsync-formula/rsync/files0707010000018F000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003200000000salt-formulas-3.0.4/rsync-formula/rsync/files/usr07070100000190000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003800000000salt-formulas-3.0.4/rsync-formula/rsync/files/usr/local07070100000191000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/rsync-formula/rsync/files/usr/local/bin07070100000192000081ED00000000000000000000000168EB80BB00000743000000000000000000000000000000000000004E00000000salt-formulas-3.0.4/rsync-formula/rsync/files/usr/local/bin/nameconvert.py.j2#!/usr/bin/python3
"""
{{ pillar.get('managed_by_salt_formula', 'Managed by the rsync formula') }}
Source: https://git.samba.org/?p=rsync.git;a=blob;f=support/nameconvert;h=ecfe28d9ba72c310c92c18cba79160905b69ab72;hb=HEAD
Added further error handling and graceful exit, Georg Pfuetzenreuter <georg.pfuetzenreuter@suse.com>
This implements a simple protocol to do user & group conversions between
names & ids. All input and output consists of simple strings with a
terminating newline.
The requests can be:
uid ID_NUM\n -> NAME\n
gid ID_NUM\n -> NAME\n
usr NAME\n -> ID_NUM\n
grp NAME\n -> ID_NUM\n
An unknown ID_NUM or NAME results in an empty return value.
"""
import sys, argparse, pwd, grp
def main():
for line in sys.stdin:
try:
req, arg = line.rstrip().split(' ', 1)
except:
req = None
try:
if req in ['uid', 'gid'] and not arg.isdigit():
ans = ''
elif req == 'uid':
ans = pwd.getpwuid(int(arg)).pw_name
elif req == 'gid':
ans = grp.getgrgid(int(arg)).gr_name
elif req == 'usr':
ans = pwd.getpwnam(arg).pw_uid
elif req == 'grp':
ans = grp.getgrnam(arg).gr_gid
else:
print('Invalid request', file=sys.stderr)
sys.exit(1)
except KeyError:
ans = ''
# for debugging :)
#with open('/dev/shm/nameconvert.out', 'a') as fh:
# fh.write(f'{req} -> {arg} -> {ans}\n')
print(ans, flush=True)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Convert users & groups between names & numbers for an rsync daemon.')
args = parser.parse_args()
try:
main()
except KeyboardInterrupt:
sys.exit(2)
07070100000193000081A400000000000000000000000168EB80BB0000090F000000000000000000000000000000000000003100000000salt-formulas-3.0.4/rsync-formula/rsync/init.sls{#-
Salt state file for managing rsync
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
Copyright (C) 2023 SUSE LLC
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'rsync/map.jinja' import config, contents %}
rsync_package:
pkg.installed:
- names:
- rsync
{%- if 'rsync' in pillar %}
rsyncd_config:
file.managed:
- names:
- /etc/rsyncd.conf:
- mode: '0640'
- contents: |
{{ pillar.get('managed_by_salt_formula', '# Managed by the rsync formula') | indent(12) }}
# Salt managed defaults
{{ contents(config.get('defaults', {})) }}
# Salt managed modules
{% for module, module_config in config.get('modules', {}).items() %}
[{{ module }}]{{ contents(module_config) | indent(4) }}
{%- endfor %}
- /etc/rsyncd.secrets:
- mode: '0600'
- contents: |
{{ pillar.get('managed_by_salt_formula', '# Managed by the rsync formula') | indent(12) }}
# Salt managed users
{{ contents(config.get('users', {}), ':') }}
rsyncd_socket:
service.running:
- name: rsyncd.socket
- enable: true
- require:
- pkg: rsync_package
- require:
- rsyncd_service
{%- else %}
rsyncd_socket:
service.dead:
- name: rsyncd.socket
- enable: false
{%- endif %}
rsyncd_service:
service.dead:
- name: rsyncd.service
- enable: false
{%- if 'rsync' in pillar %}
- watch:
- file: rsyncd_config
{%- endif %}
rsync_nameconvert:
file.managed:
- name: /usr/local/bin/nameconvert.py
- mode: '0755'
- source: salt://rsync/files/usr/local/bin/nameconvert.py.j2
- template: jinja
07070100000194000081A400000000000000000000000168EB80BB00000429000000000000000000000000000000000000003500000000salt-formulas-3.0.4/rsync-formula/rsync/macros.jinja{#-
Jinja macros file for the rsync Salt states
Copyright (C) 2023-2025 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
Copyright (C) 2023 SUSE LLC
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 <https://www.gnu.org/licenses/>.
-#}
{%- macro contents(data, delimiter=' = ') -%}
{%- for setting, value in data.items() %}
{%- if value is not string and value is sequence %}
{%- set value = ' '.join(value) -%}
{%- endif %}
{{ setting ~ delimiter ~ value }}
{%- endfor %}
{%- endmacro -%}
07070100000195000081A400000000000000000000000168EB80BB00000409000000000000000000000000000000000000003200000000salt-formulas-3.0.4/rsync-formula/rsync/map.jinja{#-
Jinja variable file for the rsync Salt states
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
Copyright (C) 2023 SUSE LLC
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 <https://www.gnu.org/licenses/>.
-#}
{%- import_yaml 'rsync/defaults.yaml' as defaults %}
{%- set config = salt.pillar.get('rsync', default={'defaults': defaults}, merge=True, merge_nested_lists=False) -%}
{%- from 'rsync/macros.jinja' import contents %}
{%- set contents = contents %}
07070100000196000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002500000000salt-formulas-3.0.4/security-formula07070100000197000081A400000000000000000000000168EB80BB0000006E000000000000000000000000000000000000002F00000000salt-formulas-3.0.4/security-formula/README.md# Security formula
This formula manages permissions and capabilities through `permissions(5)` and `chkstat`.
07070100000198000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/security-formula/metadata07070100000199000081A400000000000000000000000168EB80BB00000094000000000000000000000000000000000000003B00000000salt-formulas-3.0.4/security-formula/metadata/metadata.yml---
summary:
Salt states for managing permissions
description:
Salt states for configuring permissions and capabilities.
require:
- sysconfig
0707010000019A000081A400000000000000000000000168EB80BB0000031A000000000000000000000000000000000000003400000000salt-formulas-3.0.4/security-formula/pillar.examplesecurity:
# configures /etc/sysconfig/security
sysconfig:
# sets PERMISSION_SECURITY
# defaults to "secure"
permission_security: secure
# sets PERMISSION_FSCAPS
# defaults to ""
permission_fscaps: yes
# any other sysconfig settings can be given to manage,
# but so far only permission_security and permission_fscaps will be enforced even if not present in the pillar
# configures /etc/permissions.local
permissions:
/usr/sbin/tcpdump:
# user and group default to "root"
user: root
group: root
# mode defaults to "0755"
mode: '0755' # must be a string, not an integer (use quotes in case of a YAML pillar)
# no capabilities by default
capabilities:
- cap_net_raw
- cap_net_admin=ep
0707010000019B000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/security-formula/security0707010000019C000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003400000000salt-formulas-3.0.4/security-formula/security/files0707010000019D000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003800000000salt-formulas-3.0.4/security-formula/security/files/etc0707010000019E000081A400000000000000000000000168EB80BB00000194000000000000000000000000000000000000005000000000salt-formulas-3.0.4/security-formula/security/files/etc/permissions.local.jinja{{ pillar.get('managed_by_salt_formula', '# Managed by the security formula') }}
{%- for path, config in permissions.items() %}
{{ path }} {{ ( config.get('user', 'root') ~ ':' ~ config.get('group', 'root') ~ ' ' ~ config.get('mode', '0755') ) | indent(30 - path | length, true) }}
{%- if 'capabilities' in config %}
+capabilities {{ ','.join(config['capabilities']) }}
{%- endif %}
{%- endfor %}
0707010000019F000081A400000000000000000000000168EB80BB000007DC000000000000000000000000000000000000003700000000salt-formulas-3.0.4/security-formula/security/init.sls{#-
Salt state file for managing permissions
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set mypillar = pillar.get('security', {}) -%}
{%- set permissions = mypillar.get('permissions', {}) -%}
{%- set sysconfig = mypillar.get('sysconfig', {}) -%}
security_permissions_zypp_hook:
{%- if permissions %}
pkg.installed:
{%- else %}
pkg.removed:
{%- endif %}
- name: permissions-zypp-plugin
security_permissions_local:
file.managed:
- name: /etc/permissions.local
- source: salt://{{ slspath }}/files/etc/permissions.local.jinja
- template: jinja
- context:
permissions: {{ permissions }}
security_sysconfig:
suse_sysconfig.sysconfig:
- name: security
- key_values:
PERMISSION_SECURITY: '{{ sysconfig.get('permission_security', 'secure') }} local'
PERMISSION_FSCAPS: '{{ sysconfig.get('permission_fscaps', '') }}'
{%- for key, value in mypillar.items() %}
{%- if key | lower not in ['permission_security', 'permission_fscaps'] %}
{{ key }}: {{ value }}
{%- endif %}
{%- endfor %}
security_apply:
cmd.run:
- name: chkstat --noheader --system
- onlyif:
- fun: cmd.run_stdout
cmd: chkstat --noheader --system --warn
- require:
- file: security_permissions_local
- suse_sysconfig: security_sysconfig
070701000001A0000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002A00000000salt-formulas-3.0.4/smartmontools-formula070701000001A1000081A400000000000000000000000168EB80BB00000133000000000000000000000000000000000000003400000000salt-formulas-3.0.4/smartmontools-formula/README.md# Salt states for smartmontools
## Available states
`smartmontools`
Installs [smartmontools](https://www.smartmontools.org/).
`smartmontools.smartd`
Installs [smartmontools](https://www.smartmontools.org/) and configures [smartd](https://www.smartmontools.org/browser/trunk/smartmontools/smartd.8.in).
070701000001A2000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003300000000salt-formulas-3.0.4/smartmontools-formula/metadata070701000001A3000081A400000000000000000000000168EB80BB00000085000000000000000000000000000000000000004000000000salt-formulas-3.0.4/smartmontools-formula/metadata/metadata.yml---
summary:
Salt states for managing smartmontools
description:
Salt states for installing smartmontools and configuring smartd
070701000001A4000081A400000000000000000000000168EB80BB0000015B000000000000000000000000000000000000003900000000salt-formulas-3.0.4/smartmontools-formula/pillar.examplesmartmontools:
smartd:
# configuration written to /etc/smartd.conf
# by default, the following defaults, originating from the SUSE packaged defaults,
# will be applied. defining the "config" pillar will omit the defaults!
config:
- DEFAULT -d removable -s (S/../.././03|L/../(01|02|03|04|05|06|07)/7/01)
- DEVICESCAN
070701000001A5000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003800000000salt-formulas-3.0.4/smartmontools-formula/smartmontools070701000001A6000081A400000000000000000000000168EB80BB00000084000000000000000000000000000000000000004600000000salt-formulas-3.0.4/smartmontools-formula/smartmontools/defaults.json{
"smartd": {
"config": [
"DEFAULT -d removable -s (S/../.././03|L/../(01|02|03|04|05|06|07)/7/01)",
"DEVICESCAN"
]
}
}
070701000001A7000081A400000000000000000000000168EB80BB0000032F000000000000000000000000000000000000004100000000salt-formulas-3.0.4/smartmontools-formula/smartmontools/init.sls{#-
Salt state file for installing smartmontools
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
smartmontools_package:
pkg.installed:
- name: smartmontools
070701000001A8000081A400000000000000000000000168EB80BB000003BA000000000000000000000000000000000000004200000000salt-formulas-3.0.4/smartmontools-formula/smartmontools/map.jinja{#-
Jinja variables file for the smartmontools Salt states
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- import_json 'smartmontools/defaults.json' as defaults -%}
{%- set smartmontools = salt.pillar.get('smartmontools', default=defaults) -%}
{%- set smartd = smartmontools.get('smartd', {}) -%}
070701000001A9000081A400000000000000000000000168EB80BB000005BC000000000000000000000000000000000000004300000000salt-formulas-3.0.4/smartmontools-formula/smartmontools/smartd.sls{#-
Salt state file for managing smartd
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'smartmontools/map.jinja' import smartd %}
{%- set config = smartd.get('config', []) %}
include:
- .
smartmontools_smartd_config:
file.managed:
- name: /etc/smartd.conf
- template: jinja
- contents:
- {{ pillar.get('managed_by_salt_formula', '# Managed by the smartmontools formula') | yaml_encode }}
{%- for entry in config %}
- {{ entry }}
{%- endfor %}
smartmontools_smartd_service:
service.running:
- name: smartd
{#- service is already enabled through a vendor preset on SUSE distributions, but doesn't hurt to enforce it #}
- enable: true
- require:
- pkg: smartmontools_package
- watch:
- file: smartmontools_smartd_config
070701000001AA000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002800000000salt-formulas-3.0.4/status_mail-formula070701000001AB000081A400000000000000000000000168EB80BB000000A1000000000000000000000000000000000000003200000000salt-formulas-3.0.4/status_mail-formula/README.md# Salt states for systemd-status-mail
## Available states
`status-mail`
Installs and configures [systemd-status-mail](https://github.com/openSUSE/os-update).
070701000001AC000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003100000000salt-formulas-3.0.4/status_mail-formula/metadata070701000001AD000081A400000000000000000000000168EB80BB0000003C000000000000000000000000000000000000003E00000000salt-formulas-3.0.4/status_mail-formula/metadata/metadata.yml---
summary:
Salt states for managing systemd-status-mail
070701000001AE000081A400000000000000000000000168EB80BB000001C8000000000000000000000000000000000000003700000000salt-formulas-3.0.4/status_mail-formula/pillar.examplestatus-mail:
# options written to /etc/default/systemd-status-mail
# the packaged defaults apply, the following are just examples
config:
address: root@localhost
from: root@example
mailer: sendmail
# see https://github.com/openSUSE/os-update/blob/main/src/systemd-status-mail.8.md#configuration-options
# the following lists systemd services systemd-status-mail should be enabled for
# none by default
services:
- os-update
070701000001AF000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003400000000salt-formulas-3.0.4/status_mail-formula/status-mail070701000001B0000081A400000000000000000000000168EB80BB00000B7F000000000000000000000000000000000000003D00000000salt-formulas-3.0.4/status_mail-formula/status-mail/init.sls{#-
Salt state file for managing systemd-status-mail
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set sm = salt.pillar.get('status-mail', {}) -%}
{%- set file = '/etc/default/systemd-status-mail' %}
{%- set services = sm.get('services', []) %}
status-mail_package:
pkg.installed:
- name: systemd-status-mail
{%- if sm %}
{%- if 'config' in sm %}
status-mail_config_file:
file.managed:
- name: {{ file }}
- replace: false
status-mail_config_values:
file.keyvalue:
- name: {{ file }}
- key_values:
{%- for key, value in sm['config'].items() %}
{{ key | upper }}: '"{{ value }}"'
{%- endfor %}
{%- if opts['test'] %}
- ignore_if_missing: true
{%- endif %}
- append_if_not_found: true
- require:
- pkg: status-mail_package
- file: status-mail_config_file
status-mail_config_header:
file.prepend:
- name: {{ file }}
- text: {{ pillar.get('managed_by_salt_formula', '# Managed by the status_mail formula') | yaml_encode }}
- require:
- pkg: status-mail_package
{%- endif %} {#- close config in pillar check #}
{%- if services and services is not string and services is not mapping %}
status-mail_services:
file.managed:
- names:
{%- for service in services %}
- /etc/systemd/system/{{ service }}.service.d/status-mail.conf
{%- endfor %}
- makedirs: True
- contents:
- {{ pillar.get('managed_by_salt_formula', '# Managed by the status_mail formula') | yaml_encode }}
- '[Unit]'
- 'OnFailure=systemd-status-mail@%n.service'
- require:
- pkg: status-mail_package
{%- endif %}
{%- endif %} {#- close status-mail pillar check #}
{%- for found_file in salt['file.find']('/etc/systemd/system', name='status-mail.conf', type='f') %}
{%- set service = found_file.replace('/etc/systemd/system/', '').replace('.service.d/status-mail.conf', '') %}
{%- if service not in services %}
status-mail_disable_{{ service }}:
file.absent:
- name: {{ found_file }}
{%- endif %}
{%- endfor %}
{#- might leave empty service.d directories behind, but better than accidentally deleting other override files #}
{%- if not sm or not 'config' in sm %}
status-mail_config_file:
file.absent:
- name: {{ file }}
{%- endif %}
070701000001B1000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002400000000salt-formulas-3.0.4/suse_ha-formula070701000001B2000081A400000000000000000000000168EB80BB00000236000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/suse_ha-formula/README.md# Salt states for the SUSE Linux Enterprise High Availability Extension
## Available states
`suse_ha`
- Installs and configures a HA node.
`suse_ha.corosync`
- Configures Corosync.
`suse_ha.kernel`
- Enables the kernel software watchdog.
`suse_ha.pacemaker`
- Configures Pacemaker, including:
* IPMI fencing resources
* STONITH
`suse_ha.packages`
- Installs HA related packages
`suse_ha.repositories`
- Configures SLES HA repositories
`suse_ha.resources`
- Configures primitive resources
`suse_ha.service`
- Enables Pacemaker (unfinished state)
070701000001B3000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002D00000000salt-formulas-3.0.4/suse_ha-formula/metadata070701000001B4000081A400000000000000000000000168EB80BB0000009A000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/suse_ha-formula/metadata/metadata.yml---
summary:
Salt states for managing SLE HA clusters
description:
Salt states for managing SUSE Linux Enterprise HA clusters.
require:
- sysconfig
070701000001B5000081A400000000000000000000000168EB80BB00001B2F000000000000000000000000000000000000003300000000salt-formulas-3.0.4/suse_ha-formula/pillar.example#!yaml
# needed for Corosync peer discovery
mine_functions:
network.get_hostname: []
suse_ha:
fencing:
# enabled by default
enable: false
# disabled by default - only works together with 'enable: true' and with a minimum of two nodes
stonith_enable: false
#
# currently, the formula supports the configuration of IPMI and SBD fencing using the agents from cluster-glue
#
# if 'ipmi' is specified, it will be configured - omit the block to not configure IPMI
ipmi:
# optional attribute overrides to use for the IPMI resources
# by default, the values from suse_ha/defaults/fencing/external_ipmi.yaml will be used
primitive:
operations:
start:
timeout: 30
# IPMI resources to configure
hosts:
# any amount of IPMI resources can be configured
# all dictionary keys are mandatory
dev-ipmi0:
ip: 192.168.120.1
user: admin
interface: lanplus
priv: ADMINISTRATOR
# it is recommended to encrypt any IPMI credentials using PGP
# the formula will store the passphrase in a separate file instead of directly in the CIB
secret: password
# if 'sbd' is specified, it will be configured - omit the block to not configure SBD
sbd:
# SBD resources to configure
instances:
minion0:
# currently pcmk_host_list, pcmk_delay_base and pcmk_delay_max can be configured
# these are optional and do not have default values set by the formula
pcmk_host_list: minion0
pcmk_delay_base: 0
# use an empty dictionary to not configure any attributes
minion1: {}
dynamic:
pcmk_delay_max: 5
# block devices to use for SBD
devices:
# it is recommended to use unique identifiers as the formula will overwrite any devices without SBD metadata
# whilst the formula does not limit the amount of devices, the cluster stack might - SBD suggests the use of 1-3 devices
- /dev/sda
- /dev/sdb
cluster:
# the name of the cluster needs to be a string all node hostnames start with
# for example, if the cluster nodes are named "pizza1", "pizza2" and "pizza3", then the cluster name must be "pizza"
# this is needed for Corosync peer discovery using wildcard targeting in the form of "pizza*"
name: salt-minion
# "ipv6" by default
ip_version: ipv4
# native Corosync only requires the nodeid for IPv6 based operation - this formula always requires it
nodeid: 1
# the management settings will be written to the cluster options or the resource defaults
# for compatibility reasons, the keys can use underscores in place of hyphens
management:
# the following are not set by default, and will be removed if configured but not defined in the pillar
no-quorum-policy: stop
allow-migrate: true
batch-limit: 30
migration-limit: 10
# if fencing and STONITH are enabled, this will default to true, otherwise to false
stonith-enabled: true
# the following are not set by default and will only be considered if fencing and STONITH are enabled
migration-threshold: 2
failure-timeout: 60s
multicast:
# configure the bind address in a node-specific pillar and have it merged
bind_address: '192.168.121.55'
address: '239.0.0.1'
# generate an authkey using `corosync-keygen` and transform it to base64 using `base64 -w0 /etc/corosync/authkey`
# it is highly recommended to additionally encrypt the base64 string using PGP, in which case the file header needs to be #!gpg|yaml for the binary to stay intact
cluster_secret: !!binary |
LGljyn1fBRRcxLxFEgOmhILNTFY/13Cn3EwqqaBN6ynrX6flhiGyTjfW8eAQ1zlJex3uO9kssIcANw9uXLLpOCJ/Fvia3yzHNzCIxfW0zayUOBSMypN1TMKjad5/n8frAFZWNBhTcbk1Cwi764yBj8ErhsPh264qEreRzznJFGI=
# cluster resources
resources:
# name of the primitive resource
cluster-httpd:
# options are mostly following the CIB syntax
class: ocf
type: apache
attributes:
configfile: /etc/apache2/httpd.conf
operations:
monitor:
interval: 120s
timeout: 60s
meta_attributes:
target-role: Started
# without "clone" being present, a regular primitive resource will be created
# with "clone", a clone resource containing the primitive resource will be created
clone:
# optional "resource_id", by default, the clone resource will be given the name of the primitive resource with a "-clone" suffix
resource_id: sample_clone
# optional meta attributes for the clone resource
meta_attributes:
target-role: Started
# proper booleans seem to work albeit the Pacemaker documentation using lowercase true/false values
# to be on the safe side, the lowercase boolean value can be quoted
interleave: true
# cluster resource constraints
constraints:
colo_databases: # id for the colocation object
type: rsc_colocation # "rsc_colocation" and "rsc_order" are supported, but with different options:
# "rsc_colocation" takes "score", and "sets" (if used) needs to be a mapping (example in this block)
# "rsc_order" takes "kind", and "sets" (if used) needs to be a list of mappings (example in blocks below)
score: 100 # score to set on the main colocation object - mandatory with "resources", optional with "sets"
resources: # list of a maximum of 2 resources to configure as rsc/with-rsc respectively
- VM_dbvm1
- VM_dbvm2
sets: # alternatively, resource_sets
coloset_databases: # id for the resource_set object
VM_dbvm1: # resource name
score: 100 # optional attributes and values to configure on the resource entries in the set
VM_dbvm2: {} # empty dict if no additional attributes are to be configured on the resource entry in the set
order_postgresql_etherpad:
type: rsc_order
kind: mandatory
resources: # list of a maximum of 2 resources to configure as "first"/"then" respectively
- VM_postgresql.example.com
- VM_etherpad.example.com
order_dns_mysql:
type: rsc_order
kind: mandatory
sets: # list of "resource_set"s inside the "rsc_order", will be configured as <name of constraint>-<index>
- resources: # "resources" will be mapped to ordered "resource_ref" entries
- VM_dns1.example.com
- VM_dns2.example.com
sequential: false # other key/values will be mapped to attributes of the "resource_set"
- resources:
- VM_galera1.example.com
- VM_galera2.example.com
- VM_galera3.example.com
sequential: true
070701000001B6000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha070701000001B7000081A400000000000000000000000168EB80BB000008EF000000000000000000000000000000000000003900000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/corosync.sls{#-
Salt state file for managing Corosync
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'suse_ha/map.jinja' import hapillar, cluster, fencing, multicast, is_primary -%}
{%- if 'sbd' in fencing -%}{%- set hook_sbd = True -%}{%- else -%}{%- set hook_sbd = False %}{%- endif %}
include:
- .packages
{%- if hook_sbd %}
- .sbd
{%- endif %}
{%- if 'name' in cluster %}
corosync.service:
service.running:
- enable: False
- reload: False
- require:
- suse_ha_packages
{%- if hook_sbd %}
- service: sbd_service
{%- endif %}
- watch:
- file: /etc/corosync/corosync.conf
- file: /etc/corosync/authkey
{%- if hook_sbd %}
{%- if is_primary %}
- cmd: sbd_format_devices
{%- endif %}
- suse_sysconfig: sbd_sysconfig
{%- endif %}
/etc/corosync/authkey:
file.managed:
- name: /etc/corosync/authkey
{%- if 'cluster_secret' in hapillar %}
- contents_pillar: 'suse_ha:cluster_secret'
{%- else %}
- contents: 'fix-me-please'
{%- do salt.log.error('suse_ha: No cluster secret provided - Corosync will not operate!') %}
{%- endif %}
- user: root
- group: root
- mode: '0400'
/etc/corosync/corosync.conf:
file.managed:
- source: salt://{{ slspath }}/files/etc/corosync/corosync.conf
- template: jinja
- user: root
- group: root
- mode: '0644'
- context:
cluster: {{ cluster }}
multicast: {{ multicast }}
- require:
- suse_ha_packages
{%- else %}
{%- do salt.log.error('suse_ha: cluster pillar not configured, not sending any Corosync configuration!') %}
{%- endif %}
070701000001B8000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003500000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/defaults070701000001B9000081A400000000000000000000000168EB80BB000000BA000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/defaults.yaml---
resources_dir: /data/kvm/resources
cluster:
crypto_cipher: aes256
crypto_hash: sha512
ip_version: ipv6
fencing:
enable: true
sysconfig:
pacemaker:
LRMD_MAX_CHILDREN: 4
070701000001BA000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003D00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/defaults/fencing070701000001BB000081A400000000000000000000000168EB80BB000000FA000000000000000000000000000000000000005000000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/defaults/fencing/external_ipmi.yaml---
ipmi:
primitive:
operations:
start:
timeout: 20
interval: 0
stop:
timeout: 15
interval: 0
monitor:
timeout: 20
interval: 3600
meta_attributes:
target-role: Started
070701000001BC000081A400000000000000000000000168EB80BB000000C9000000000000000000000000000000000000004F00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/defaults/fencing/external_sbd.yaml---
sbd:
primitive:
operations:
start:
timeout: 20
interval: 0
stop:
timeout: 15
interval: 0
monitor:
timeout: 20
interval: 3600
070701000001BD000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003200000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files070701000001BE000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003600000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/cib070701000001BF000081A400000000000000000000000168EB80BB00000175000000000000000000000000000000000000004300000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/cib/clone.xml.j2{%- set source = 'suse_ha/files/cib/' -%}
{%- include source ~ 'header_resource.xml.j2' -%}
{%- for clone, config in clones.items() %}
{%- set resource_id = clone %}
<clone id="{{ resource_id }}">
{%- include source ~ 'meta_attributes.xml.j2' %}
{%- include source ~ 'primitive.xml.j2' %}
</clone>
{% endfor %}
{%- include source ~ 'footer_resource.xml.j2' -%}
070701000001C0000081A400000000000000000000000168EB80BB0000031E000000000000000000000000000000000000004800000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/cib/colocation.xml.j2{%- if crscs and crscs | length == 2 and cscore is not none %}
<rsc_colocation id="{{ cid }}" score="{{ cscore }}" rsc="{{ crscs[0] }}" with-rsc="{{ crscs[1] }}"/>
{%- elif csets %}
<rsc_colocation id="{{ cid }}"{{ ' score="' ~ cscore ~ '"' if cscore is not none else '' }}>
{%- for cset, csetrscs in csets.items() %}
<resource_set id="{{ cset }}">
{%- for csetrsc, csetrscconfig in csetrscs.items() %}
<resource_ref id="{{ csetrsc }}"{%- for csetrscattr, csetrscval in csetrscconfig.items() -%}{{ ' ' ~ csetrscattr ~ '="' ~ csetrscval ~ '"' }}{%- endfor -%}/>
{%- endfor %}
</resource_set>
{%- endfor %}
</rsc_colocation>
{%- else %}
{%- do salt.log.error('suse_ha: unsupported constraints combination') -%}
{%- endif %}
070701000001C1000081A400000000000000000000000168EB80BB000001C4000000000000000000000000000000000000004800000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/cib/constraint.xml.j2{%- set source = 'suse_ha/files/cib/' -%}
{%- if ctype in ['rsc_colocation', 'rsc_order'] and ( ckind or ( cscore and crscs ) or csets ) -%}
{%- set typetemplate = source ~ ctype.replace('rsc_', '') ~ '.xml.j2' -%}
{%- include source ~ 'header.xml.j2' %}
<constraints>
{%- include typetemplate %}
</constraints>
{% include source ~ 'footer.xml.j2' %}
{%- else -%}
{%- do salt.log.error('suse_ha: unsupported constraint') -%}
{%- endif -%}
070701000001C2000081A400000000000000000000000168EB80BB0000001A000000000000000000000000000000000000004400000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/cib/footer.xml.j2 </configuration>
</cib>
070701000001C3000081A400000000000000000000000168EB80BB00000046000000000000000000000000000000000000004D00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/cib/footer_resource.xml.j2 </resources>
{% include 'suse_ha/files/cib/' ~ 'footer.xml.j2' %}
070701000001C4000081A400000000000000000000000168EB80BB00000091000000000000000000000000000000000000004400000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/cib/header.xml.j2{%- set managed_by_salt = salt['pillar.get']('managed_by_salt_xml', '') -%}
<?xml version="1.0" ?>
{{ managed_by_salt }}
<cib>
<configuration>
070701000001C5000081A400000000000000000000000168EB80BB00000046000000000000000000000000000000000000004D00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/cib/header_resource.xml.j2{%- include 'suse_ha/files/cib/' ~ 'header.xml.j2' %}
<resources>
070701000001C6000081A400000000000000000000000168EB80BB00000176000000000000000000000000000000000000004D00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/cib/meta_attributes.xml.j2 {%- if config['meta_attributes'] | length %}
<meta_attributes id="{{ resource_id }}-meta_attributes">
{%- for nvpair, value in config['meta_attributes'].items() %}
<nvpair name="{{ nvpair }}" value="{{ value }}" id="{{ resource_id }}-meta_attributes-{{ nvpair }}"/>
{%- endfor %}
</meta_attributes>
{%- endif %}
070701000001C7000081A400000000000000000000000168EB80BB0000042F000000000000000000000000000000000000004300000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/cib/order.xml.j2{%- if crscs and crscs | length == 2 and ckind is not none %}
{#- TODO: support other attributes for rsc_order #}
<rsc_order id="{{ cid }}" kind="{{ ckind }}" first="{{ crscs[0] }}" then="{{ crscs[1] }}"/>
{#- the macro default is a dictionary for colocation, but for order constraints, we process a list instead #}
{%- elif csets and csets is iterable and csets is not mapping and csets is not string %}
<rsc_order id="{{ cid }}" kind="{{ ckind }}">
{%- for cset in csets %}
{%- set resources = cset.pop('resources') %}
<resource_set id="{{ cid }}-{{ loop.index0 }}"{% for key, value in cset.items() %}{% if value is sameas true or value is sameas false %}{% set value = value | string | lower %}{% endif %}{{ ' ' ~ key ~ '="' ~ value ~ '"' }}{% endfor %}>
{%- for resource in resources %}
<resource_ref id="{{ resource }}"/>
{%- endfor %}
</resource_set>
{%- endfor %}
</rsc_order>
{%- else %}
{%- do salt.log.error('suse_ha: unsupported constraints combination') -%}
{%- endif %}
070701000001C8000081A400000000000000000000000168EB80BB000004F0000000000000000000000000000000000000004700000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/cib/primitive.xml.j2{%- for primitive, config in primitives.items() %}
{%- set resource_id = primitive %}
<primitive id="{{ resource_id }}" class="{{ config['resource_class'] }}" type="{{ config['resource_type'] }}" {%- if config['provider'] != 'NONE' %} provider="{{ config['provider'] }}" {%- endif -%}>
{%- if config['attributes'] | length > 0 %}
<instance_attributes id="{{ resource_id }}-instance_attributes">
{%- for nvpair, value in config['attributes'].items() %}
<nvpair name="{{ nvpair }}" value="{{ value }}" id="{{ resource_id }}-instance_attributes-{{ nvpair }}"/>
{%- endfor %}
</instance_attributes>
{%- endif %}
{%- if config['operations'] | length > 0 %}
<operations>
{%- for op, opconfig in config['operations'].items() %}
{%- set interval = opconfig.pop('interval') %}
<op name="{{ op }}" timeout="{{ opconfig.pop('timeout') }}" interval="{{ interval }}"{% for property, value in opconfig.items() %}{{ ' ' ~ property ~ '="' ~ value ~ '"' }}{% endfor %} id="{{ resource_id }}-{{ op }}-{{ interval }}"/>
{%- endfor %}
</operations>
{%- endif %}
{%- include source ~ 'meta_attributes.xml.j2' %}
</primitive>
{%- endfor %}
070701000001C9000081A400000000000000000000000168EB80BB000000B6000000000000000000000000000000000000004600000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/cib/resource.xml.j2{%- set source = 'suse_ha/files/cib/' -%}
{%- include source ~ 'header_resource.xml.j2' %}
{%- include source ~ 'primitive.xml.j2' %}
{% include source ~ 'footer_resource.xml.j2' %}
070701000001CA000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003600000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/etc070701000001CB000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003F00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/etc/corosync070701000001CC000081A400000000000000000000000168EB80BB00000494000000000000000000000000000000000000004D00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/files/etc/corosync/corosync.conf{%- set managed_by_salt = salt['pillar.get']('managed_by_salt') -%}
{%- set nodes = salt['mine.get'](cluster.name ~ '*', 'network.get_hostname', tgt_type='compound') -%}
{{ managed_by_salt }}
{#- to-do: drop this template file, generate from dictionary instead #}
totem {
crypto_hash: {{ cluster.crypto_hash }}
rrp_mode: none
token_retransmits_before_loss_const: 10
join: 60
max_messages: 20
vsftype: none
secauth: on
crypto_cipher: {{ cluster.crypto_cipher }}
cluster_name: {{ cluster.name }}
ip_version: {{ cluster.ip_version }}
token: 5000
version: 2
transport: udp
interface {
bindnetaddr: {{ multicast.bind_address }}
mcastaddr: {{ multicast.address }}
ringnumber: 0
ttl: 1
}
consensus: 6000
nodeid: {{ cluster.nodeid }}
}
logging {
to_logfile: no
logfile: /var/log/cluster/corosync.log
timestamp: on
syslog_facility: daemon
logger_subsys {
debug: off
subsys: QUORUM
}
to_syslog: yes
debug: off
to_stderr: no
fileline: off
}
quorum {
{%- set num_nodes = nodes | length() %}
expected_votes: {{ num_nodes }}
{%- if num_nodes == 2 %}
two_node: 1
{%- else %}
two_node: 0
{%- endif %}
provider: corosync_votequorum
}
070701000001CD000081A400000000000000000000000168EB80BB00000322000000000000000000000000000000000000003500000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/init.sls{#-
Salt state file for combined management of SUSE HA components
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
include:
- .packages
- .corosync
- .pacemaker
070701000001CE000081A400000000000000000000000168EB80BB0000035F000000000000000000000000000000000000003700000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/kernel.sls{#-
Salt state file for managing the softdog kernel module
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{#- we do not use this; to-do: configure hardware watchdog #}
load_softdog:
kmod.present:
- mods:
- softdog
070701000001CF000081A400000000000000000000000168EB80BB0000129C000000000000000000000000000000000000003900000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/macros.jinja{#-
Jinja macros file providing helper functions for SUSE HA related Salt state files
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from './map.jinja' import resources_dir, management -%}
{%- macro ha_resource_update(resource, origin) -%}
ha_resource_update_{{ resource }}:
cmd.run:
- name: crm configure load xml update {{ resources_dir }}/{{ resource }}.xml
- onchanges:
- ha_{{ origin }}_file_{{ resource }}
{#- to-do: somehow require this but allow for individual run of suse_ha.resources ?
- require:
- pacemaker.service
#}
{%- endmacro -%}
{%- macro ha_constraint(cid, ctype, ckind=None, cscore=None, crscs=[], csets={}) -%}
ha_constraint_file_{{ cid }}:
file.managed:
- name: {{ resources_dir }}/{{ cid }}.xml
- source: salt://suse_ha/files/cib/constraint.xml.j2
- template: jinja
- context:
cid: {{ cid }}
ctype: {{ ctype }}
ckind: {{ ckind | capitalize if ckind is not none else 'null' }}
cscore: {{ cscore if cscore is not none else 'null' }}
crscs: {{ crscs }}
csets: {{ csets }}
- require:
- file: ha_resources_directory
{{ ha_resource_update(cid, 'constraint') }}
{%- endmacro -%}
{%- macro ha_resource(resource, class, type, instance_attributes, operations, meta_attributes={}, provider='NONE', clone={}, requires=[]) %}
ha_resource_file_{{ resource }}:
file.managed:
- name: {{ resources_dir }}/{{ resource }}.xml
- source: salt://suse_ha/files/cib/{{ 'clone' if clone else 'resource' }}.xml.j2
- template: jinja
- context:
{%- if clone %}
{%- if 'resource_id' in clone -%}
{%- set clone_id = clone['resource_id'] %}
{%- else %}
{%- set clone_id = resource ~ '-clone' %}
{%- endif %}
clones:
{{ clone_id }}:
meta_attributes: {{ clone.get('meta_attributes', {}) }}
{%- endif %}
primitives:
{{ resource }}:
resource_class: {{ class }}
resource_type: {{ type }}
attributes: {{ instance_attributes }}
operations: {{ operations }}
meta_attributes: {{ meta_attributes }}
provider: {{ provider }}
{%- if requires is not none %}
- require:
- file: ha_resources_directory
{%- for require in requires %}
- {{ require }}
{%- endfor %}
{%- endif %}
{{ ha_resource_update(resource, 'resource') }}
ha_resource_start_{{ resource }}:
cmd.run:
- name: crm resource start {{ resource }}
- unless: test $(crm resource status {{ resource }}|awk '{ print $4; exit }') == running
- require:
- cmd: ha_resource_update_{{ resource }}
{%- endmacro %}
{%- macro property(option, value=None) -%}
{%- if value is none %}
{%- set value = management.get(option, management.get(option.replace('-', '_'))) %}
{%- endif %}
ha_property_{{ option }}:
cmd.run:
{%- if value is none %}
- name: 'crm attribute -D -n {{ option }}'
- onlyif: 'crm_attribute -G -n {{ option }}'
{%- else %}
- name: 'crm configure property {{ option }}={{ value }}'
- unless: 'test $(crm configure get_property {{ option }}) == {{ value }}'
{%- endif %}
- require:
- pacemaker.service
{%- endmacro -%}
{%- macro rsc_default(option) -%}
{%- set value = management.get(option.replace('-','_'), None) %}
{%- if value is not none %}
ha_rsc_default_{{ option }}:
cmd.run:
- name: 'crm configure rsc_defaults {{ option }}={{ value }}'
- unless: 'test $(crm_attribute -t rsc_defaults -G -n {{ option }} -q) == {{ value }}'
- require:
- pacemaker.service
{%- else %}
{%- do salt.log.debug('suse_ha: not configuring ' ~ option) %}
{%- endif %}
{%- endmacro -%}
{%- macro ipmi_secret(host, secret, dc) -%}
ha_fencing_ipmi_secret_{{ host }}:
file.managed:
- name: /etc/pacemaker/ha_ipmi_{{ host }}
- contents: '{{ secret }}'
- contents_newline: False
- mode: '0600'
- require:
- suse_ha_packages
{%- if dc %}
- require_in:
- ha_resource_file_{{ host }}
- ha_resource_update_{{ host }}
{%- endif %}
{%- endmacro -%}
070701000001D0000081A400000000000000000000000168EB80BB00000B4C000000000000000000000000000000000000003600000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/map.jinja{#-
Jinja variable file for inclusion in SUSE HA related Salt state files
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- import_yaml './defaults.yaml' as defaults -%}
{%- set hapillar = salt.pillar.get('suse_ha', default=defaults, merge=True, merge_nested_lists=False) -%}
{%- set resources_dir = hapillar.get('resources_dir') -%}
{%- set cluster = hapillar.get('cluster', {}) -%}
{%- set management = hapillar.get('management', {}) -%}
{%- if 'multicast' in hapillar -%}
{%- set multicast = hapillar['multicast'] -%}
{%- else -%}
{%- set multicast = {} -%}
{%- do salt.log.error('suse_ha: No multicast pillar provided - configuration might be incomplete!') -%}
{%- endif -%}
{%- set fencing_base = hapillar.get('fencing', {}) -%}
{%- set constraints = hapillar.get('constraints', {}) -%}
{%- set resources = hapillar.get('resources', {}) -%}
{%- set sysconfig = hapillar.get('sysconfig', {}) -%}
{%- set fence_agents = ['external_ipmi', 'external_sbd'] -%}
{%- set fence_ns = namespace(construct=false) -%}
{%- for agent in fence_agents -%}
{%- if agent.replace('external_', '') in fencing_base -%}
{%- import_yaml './defaults/fencing/' ~ agent ~ '.yaml' as fencing_defaults -%}
{%- do fencing_base.update(fencing_defaults) -%}
{%- set fence_ns.construct = true -%}
{%- endif -%}
{%- endfor -%}
{%- if fence_ns.construct -%}
{%- set fencing = salt.pillar.get('suse_ha:fencing', default=fencing_base, merge=True) -%}
{%- else -%}
{%- set fencing = fencing_base -%}
{%- endif -%}
{%- set sbd = fencing.get('sbd', {}) -%}
{%- set host = grains['host'] -%}
{%- set id = grains['id'] -%}
{%- set clustername = cluster.get('name') -%}
{%- if clustername is none -%}
{%- set nodes = [] -%}
{%- else -%}
{%- set nodes = salt['mine.get'](clustername ~ '*', 'network.get_hostname') | sort -%}
{%- endif -%}
{%- if nodes | length -%}
{%- set primary = nodes[0] -%}
{%- do salt.log.debug('suse_ha: elected primary node is ' ~ primary) -%}
{%- else -%}
{%- do salt.log.error('suse_ha: no nodes found in cluster') -%}
{%- set primary = None -%}
{%- endif -%}
{%- if host == primary or id == primary -%}
{%- set is_primary = True -%}
{%- else -%}
{%- set is_primary = False -%}
{%- endif -%}
{%- do salt.log.debug('suse_ha: is_primary: ' ~ is_primary) -%}
070701000001D1000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003600000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/pacemaker070701000001D2000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003E00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/pacemaker/fencing070701000001D3000081A400000000000000000000000168EB80BB0000070F000000000000000000000000000000000000005000000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/pacemaker/fencing/external_ipmi.sls{#-
Salt state file for managing IPMI fencing resources
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'suse_ha/map.jinja' import fencing -%}
{%- from 'suse_ha/macros.jinja' import ha_resource, ipmi_secret -%}
{%- set fencing_ipmi = fencing.get('ipmi', {}) -%}
{%- if 'hosts' in fencing_ipmi %}
{%- for host, config in fencing_ipmi.hosts.items() %}
{%- set instance_attributes = {
'hostname': host, 'ipaddr': config['ip'], 'passwd': '/etc/pacemaker/ha_ipmi_' ~ host, 'userid': config['user'],
'interface': config['interface'], 'passwd_method': 'file', 'ipmitool': '/usr/bin/ipmitool', 'priv': config['priv'] } %}
{%- if 'port' in config %}
{%- do instance_attributes.update({'ipport': config['port']}) -%}
{%- endif %}
{{ ha_resource(host, class='stonith', type='external/ipmi', instance_attributes=instance_attributes,
operations=fencing.ipmi.primitive.operations, meta_attributes=fencing.ipmi.primitive.meta_attributes) }}
{{ ipmi_secret(host, config['secret'], True) }}
{%- endfor %}
{%- else %}
{%- do salt.log.error('suse_ha: pacemaker.fencing.external_ipmi called, but no hosts defined in pillar') -%}
{%- endif %}
070701000001D4000081A400000000000000000000000168EB80BB0000072A000000000000000000000000000000000000004F00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/pacemaker/fencing/external_sbd.sls{#-
Salt state file for managing SBD fencing resources
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'suse_ha/map.jinja' import fencing -%}
{%- from 'suse_ha/macros.jinja' import ha_resource -%}
{%- set fencing_sbd = fencing.get('sbd', {}) -%}
{%- set instance_defaults = fencing_sbd.get('defaults', {}) -%}
{%- set attributes = ['pcmk_host_list', 'pcmk_delay_base', 'pcmk_delay_max'] %}
include:
- suse_ha.sbd
{%- if 'instances' in fencing_sbd %}
{%- for instance, config in fencing_sbd.instances.items() %}
{%- set instance_attributes = {} -%}
{%- for attribute in attributes %}
{%- if attribute in config %}
{%- do instance_attributes.update({attribute: config[attribute]}) -%}
{%- elif attribute in instance_defaults -%}
{%- do instance_attributes.update({attribute: instance_defaults[attribute]}) -%}
{%- endif %}
{%- endfor %}
{{ ha_resource('sbd-' ~ instance, class='stonith', type='external/sbd', instance_attributes=instance_attributes, operations=fencing.sbd.primitive.operations, requires=['service: sbd_service']) }}
{%- endfor %}
{%- else %}
{%- do salt.log.error('suse_ha: pacemaker.fencing.external_sbd called, but no instances defined in pillar') -%}
{%- endif %}
070701000001D5000081A400000000000000000000000168EB80BB00001237000000000000000000000000000000000000003F00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/pacemaker/init.sls{#-
Salt state file for managing Pacemaker
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'suse_ha/map.jinja' import cluster, fencing, sysconfig -%}
{%- from 'suse_ha/macros.jinja' import ha_resource, property, rsc_default, ipmi_secret -%}
{%- set myfqdn = grains['fqdn'] -%}
{%- set myhost = grains['host'] -%}
{%- if salt['cmd.retcode']('test -x /usr/sbin/crmadmin') == 0 -%}
{%- set clusterdc = salt['cmd.run']('/usr/sbin/crmadmin -q -D 1') -%}
{%- else -%}
{%- do salt.log.error('crmadmin is not available!') -%}
{%- set clusterdc = None -%}
{%- endif -%}
{% if myfqdn == clusterdc or myhost == clusterdc %}
{#- to-do: the crm script generates the XML but fails to patch the config with "name 'cibadmin_opt' is not defined" - bug?
{{ property('default-resource-stickiness', 1000) }}
#}
{%- if fencing.enable and fencing.get('stonith_enable', False)
and (salt['mine.get'](cluster.name ~ '*', 'network.get_hostname', tgt_type='compound') | length()) >= 2 %}
{{ property('stonith-enabled', 'true') }}
{% else %}
{{ property('stonith-enabled', 'false') }}
{% endif %}
{%- if fencing.enable and fencing['stonith_enabled'], False == True %}
{{ property('no-quorum-policy') }}
{#-
optional resource meta configuration
https://clusterlabs.org/pacemaker/doc/deprecated/en-US/Pacemaker/1.1/html/Pacemaker_Explained/s-resource-options.html
-#}
{{ rsc_default('failure-timeout') }}
{{ rsc_default('migration-threshold') }}
{%- endif -%}
{{ property('batch-limit') }}
{{ property('migration-limit') }}
{{ rsc_default('allow-migrate') }}
{#-
we currently don't use this
ha_add_admin_ip:
cmd.run:
- name: 'crm configure primitive admin_addr IPaddr2 params ip={{ management.adm_ip }} op monitor interval=10 timeout=20'
# untested
- unless: 'test $(crm -Dplain configure show admin_addr | grep -oP "ip=\K(.*)") == {{ management.adm_ip }}'
- require:
- pacemaker.service
- ha_setup_stonith
-#}
{#- to-do: figure out if these values make sense #}
{#- to-do: allow override using pillar #}
{%- set utilization_meta_attributes = {'target-role': 'Started'} %}
{%- set utilization_operations = {
'start': {'interval': 0, 'timeout': 90},
'stop': {'interval': 0, 'timeout': 100},
'monitor': {'interval': '60s', 'timeout': '20s'}
} %}
{{ ha_resource('p-node-utilization', class='ocf', type='NodeUtilization', instance_attributes={}, provider='pacemaker',
meta_attributes=utilization_meta_attributes, operations=utilization_operations,
clone={ 'resource_id': 'c-node-utilization', 'meta_attributes': {'target-role': 'Started', 'interleave': 'true'} }) }}
include:
- suse_ha.packages
{%- if fencing.enable %}
{%- if 'ipmi' in fencing %}
- .fencing.external_ipmi
{%- endif %}
{%- if 'sbd' in fencing %}
- .fencing.external_sbd
{%- endif %}
{%- endif %}
- suse_ha.resources
{%- else %}
{%- do salt.log.info('Not sending any Pacemaker configuration - ' ~ myfqdn ~ ' is not the designated controller.') -%}
{%- if fencing.enable and 'ipmi' in fencing %}
{%- for host, config in fencing.ipmi.hosts.items() %}
{{ ipmi_secret(host, config['secret'], False) }}
{%- endfor %}
{%- endif %}
{%- endif %}
{%- if 'name' in cluster %}
pacemaker.service:
service.running:
- enable: True
- reload: True
- retry:
attempts: 3
interval: 10
splay: 5
- require:
- suse_ha_packages
- corosync.service
{%- if sysconfig.pacemaker | length %}
- watch:
- suse_sysconfig: /etc/sysconfig/pacemaker
suse_sysconfig.sysconfig:
- name: /etc/sysconfig/pacemaker
- header_pillar: managed_by_salt_formula_sysconfig
- uncomment: '# '
- upper: False
- quote_booleans: False
- key_values:
{%- for key, value in sysconfig.pacemaker.items() %}
{{ key }}: {{ value }}
{%- endfor %}
- require:
- suse_ha_packages
{%- endif %}
{%- else %}
{%- do salt.log.error('suse_ha: cluster pillar not configured, not enabling Pacemaker!') %}
{%- endif %}
070701000001D6000081A400000000000000000000000168EB80BB0000048E000000000000000000000000000000000000003900000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/packages.sls{#-
Salt state file for managing SUSE HA related packages
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'suse_ha/map.jinja' import fencing -%}
suse_ha_packages:
pkg.installed:
- pkgs:
- conntrack-tools
- corosync
- crmsh
- fence-agents
- ldirectord
- pacemaker
{%- if grains.osfullname != 'openSUSE Tumbleweed' %}
- python3-python-dateutil
{%- endif %}
- resource-agents
- virt-top
{%- if 'sbd' in fencing %}
- sbd
{%- endif %}
070701000001D7000081A400000000000000000000000168EB80BB00000780000000000000000000000000000000000000003A00000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/resources.sls{#-
Salt state file for managing SUSE HA cluster resources
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'suse_ha/map.jinja' import constraints, resources, resources_dir -%}
{%- from 'suse_ha/macros.jinja' import ha_constraint, ha_resource -%}
ha_resources_directory:
file.directory:
- name: {{ resources_dir }}
- mode: '0755'
{#- custom resources if defined in the suse_ha:resources pillar #}
{%- if resources is defined and resources is not none and resources | length > 0 %}
{%- for resource, config in resources.items() %}
{%- if not 'type' in config -%}{%- do salt.log.error('Resource ' ~ resource ~ ' is missing "type"') -%}{%- endif %}
{{ ha_resource(
resource, config.get('class', 'ocf'), config.get('type', None),
config.get('attributes', {}), config.get('operations', {}), config.get('meta_attributes', {}), config.get('provider', 'heartbeat'),
config.get('clone', {})) }}
{%- endfor %}
{%- else %}
{%- do salt.log.debug('Skipping construction of custom resources') %}
{%- endif %}
{%- for constraint, config in constraints.items() %}
{{ ha_constraint(
constraint,
config.get('type'),
config.get('kind'),
config.get('score'),
config.get('resources', []),
config.get('sets', {}),
)
}}
{%- endfor %}
070701000001D8000081A400000000000000000000000168EB80BB00000C30000000000000000000000000000000000000003400000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/sbd.sls{#-
Salt state file for managing SBD devices
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'suse_ha/map.jinja' import sbd, sysconfig, is_primary -%}
{%- if 'devices' in sbd %}
include:
- .packages
{%- set devices = sbd['devices'] -%}
{%- set cmd_base = 'sbd' -%}
{%- set sbd_ns = namespace(deviceargs='', timeoutargs='', devices='') -%}
{%- for device in devices -%}
{%- set sbd_ns.deviceargs = sbd_ns.deviceargs ~ ' -d ' ~ device -%}
{%- endfor -%}
{%- set sbd_ns.devices = devices | join(';') -%}
{%- if 'timeouts' in sbd -%}
{%- set timeout_msgwait = sbd.timeouts.get('msgwait', False) -%}
{%- if timeout_msgwait -%}
{%- set sbd_ns.timeoutargs = sbd_ns.timeoutargs ~ ' -4 ' ~ timeout_msgwait -%}
{%- endif -%}
{%- set timeout_watchdog = sbd.timeouts.get('watchdog', False) -%}
{%- if timeout_watchdog -%}
{%- set sbd_ns.timeoutargs = sbd_ns.timeoutargs ~ ' -1 ' ~ timeout_watchdog -%}
{%- endif -%}
{%- endif -%} {#- close timeouts check -#}
{%- set cmd_base = cmd_base ~ ' ' ~ sbd_ns.deviceargs ~ ' ' -%}
{%- set cmd_format = cmd_base ~ sbd_ns.timeoutargs ~ ' create' -%}
{%- set cmd_check = cmd_base ~ ' dump' -%}
{%- do salt.log.debug('suse_ha: constructed SBD cmd_format: ' ~ cmd_format) -%}
{%- do salt.log.debug('suse_ha: constructed SBD cmd_check: ' ~ cmd_check) %}
{%- if is_primary %}
sbd_shutdown:
service.dead:
- name: corosync
- prereq:
- cmd: sbd_format_devices
- require:
- suse_ha_packages
sbd_format_devices:
cmd.run:
- name: {{ cmd_format }}
{%- if not sbd.get('reconfigure', False) %}
- unless: {{ cmd_check }}
{%- endif %}
- require:
- suse_ha_packages
{%- else %}
{%- do salt.log.debug('suse_ha: skipping SBD device creation on non-primary node') -%}
{%- endif %}
sbd_sysconfig:
suse_sysconfig.sysconfig:
- name: sbd
- uncomment: '#'
- quote_char: "'"
- key_values:
SBD_DEVICE: {{ sbd_ns.devices }}
{%- if sysconfig.get('sbd', False) %}
{%- for key, value in sysconfig.sbd.items() %}
{{ key }}: {{ value }}
{%- endfor %}
{%- endif %}
- require:
- suse_ha_packages
sbd_service:
service.enabled:
- name: sbd
- require:
- suse_ha_packages
{%- if is_primary %}
- cmd: sbd_format_devices
{%- endif %}
- suse_sysconfig: sbd_sysconfig
{%- else %}
{%- do salt.log.error('suse_ha: sbd.devices called with no devices in the pillar') -%}
{%- endif %} {#- close devices check -#}
070701000001D9000081A400000000000000000000000168EB80BB00000476000000000000000000000000000000000000003800000000salt-formulas-3.0.4/suse_ha-formula/suse_ha/service.sls{#-
Salt state file for managing SUSE HA related init/systemd services
Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
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 <https://www.gnu.org/licenses/>.
-#}
{%- from 'suse_ha/map.jinja' import cluster -%}
{%- if 'name' in cluster %}
pacemaker.service:
service.running:
- enable: True
- reload: True
- require:
- suse_ha_packages
- corosync.service
- watch:
- file: /etc/sysconfig/pacemaker
{%- else %}
{%- do salt.log.error('suse_ha: cluster pillar not configured, not enabling Pacemaker!') %}
{%- endif %}
070701000001DA000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002600000000salt-formulas-3.0.4/sysconfig-formula070701000001DB000081A400000000000000000000000168EB80BB0000005E000000000000000000000000000000000000003000000000salt-formulas-3.0.4/sysconfig-formula/README.mdThis is a library formula containing helper code for managing fillup handled sysconfig files.
070701000001DC000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002F00000000salt-formulas-3.0.4/sysconfig-formula/_modules070701000001DD000081A400000000000000000000000168EB80BB000009D0000000000000000000000000000000000000004100000000salt-formulas-3.0.4/sysconfig-formula/_modules/suse_sysconfig.py"""
Execution module helping with managing sysconfig files on openSUSE
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
"""
from salt.modules.file import file_exists, seek_read
def fillup_regex(fillup, header_pillar=None, pattern_type=None):
"""
This returns regular expressions for finding and replacing headers in fillup/sysconfig
files.
fillup = name of the fillup template
header_pillar = pillar to use as a file header ("managed_by_salt_sysconfig" by default)
pattern_type = limit return to only one of the patterns
"""
if pattern_type not in [None, 'replace', 'search']:
__salt__['log.error']('os_file: unknown pattern_type') # noqa F821
return None
header_pillar_fallback = 'managed_by_salt_sysconfig'
if header_pillar is None:
# not set using default arguments to allow for easier handling in state modules
header_pillar = header_pillar_fallback
sysconfig_directory = '/etc/sysconfig/'
if fillup.startswith(sysconfig_directory):
fillup = fillup.replace(sysconfig_directory, '')
fillup_template = f'/usr/share/fillup-templates/sysconfig.{fillup}'
if file_exists(fillup_template):
fillup_header = seek_read(fillup_template, 100, 0).decode().split('\n')[0] + '\n'
else:
fillup_header = ''
salt_header = __pillar__.get(header_pillar) # noqa F821
if salt_header is None and header_pillar != header_pillar_fallback:
salt_header = __pillar__.get(header_pillar_fallback) # noqa F821
if salt_header is None:
salt_header = '# Managed by Salt'
if not salt_header.endswith('\n'):
salt_header = salt_header + '\n'
patterns = {
'replace': fillup_header + salt_header,
'search': '^' + fillup_header + '(?:' + salt_header + ')?',
}
if pattern_type == 'replace':
return patterns['replace']
elif pattern_type == 'search':
return patterns['replace']
return patterns
070701000001DE000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/sysconfig-formula/_states070701000001DF000081A400000000000000000000000168EB80BB000012BC000000000000000000000000000000000000004000000000salt-formulas-3.0.4/sysconfig-formula/_states/suse_sysconfig.py"""
State module for managing sysconfig files on openSUSE
Copyright (C) 2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
"""
def header(name, fillup=None, header_pillar=None):
"""
Ensures a header is present in the given sysconfig file.
name = name of the sysconfig file
fillup = name of the fillup template - defaults to the sysconfig file name
header_pillar = pillar to use as a file header ("managed_by_salt_sysconfig" by default, as defined in _modules/suse_sysconfig)
"""
if fillup is None:
fillup = name
patterns = __salt__['suse_sysconfig.fillup_regex'](fillup, header_pillar) # noqa F821
return __states__['file.replace']( # noqa F821
name=name,
bufsize='file',
count=1,
ignore_if_missing=__opts__['test'], # noqa F821
pattern=patterns['search'],
repl=patterns['replace'],
)
def sysconfig(name, key_values, fillup=None, header_pillar=None, quote=True, quote_char='"', quote_booleans=True, quote_integers=False, quote_strings=True, unbool=True, uncomment=None, upper=True, append_if_not_found=False):
"""
Manages both the header and the key/value pairs in a sysconfig file.
name = sysconfig file to manage (relative paths will be appended to /etc/sysconfig/)
quote = whether to quote values
quote_char = the character to quote values with
quote_booleans = whether to quote boolean values - ignored if quote=False
quote_integers = whether to quote integer values - ignored if quote=False
quote_strings = whether to quote string values - ignored if quote=False
unbool = whether boolean values should be converted to yes/no strings
upper = whether keys should be converted to upper case
All other arguments are passed to the equally named arguments in the suse_sysconfig.header and file.keyvalue functions.
"""
if not name.startswith('/'):
name = f'/etc/sysconfig/{name}'
boolmap = {
True: 'yes',
False: 'no',
}
boolmap_values = boolmap.values()
_key_values = {}
for key, value in key_values.items():
if upper:
key = key.upper()
is_bool = isinstance(value, bool)
if unbool and is_bool:
value = boolmap[value]
if quote and (
quote_strings and isinstance(value, str) and not value.startswith(quote_char) and value not in boolmap_values
or
quote_booleans and ( value in boolmap_values or is_bool )
or
quote_integers and isinstance(value, int) and not is_bool
):
value = f'{quote_char}{value}{quote_char}'
_key_values.update(
{
key: value,
},
)
returns = {
'header': __states__['suse_sysconfig.header'](
name=name,
fillup=fillup,
header_pillar=header_pillar,
),
'config': __states__['file.keyvalue'](
name=name,
append_if_not_found=append_if_not_found,
ignore_if_missing=__opts__['test'],
key_values=_key_values,
uncomment=uncomment,
),
}
return_keys = returns.keys()
results = tuple(
returns[r].get('result') for r in return_keys
)
if False in results:
result = False
elif None in results:
result = None
elif results[0] and results[1]:
result = True
else:
__salt__['log.error']('suse_sysconfig: result merging failed')
result = False
comments = [
returns[r].get('comment') for r in return_keys
]
if comments[0] == 'Changes would have been made' or 'is set to be changed' in comments[1]:
comment = f'File {name} would be modified'
elif comments[0] == 'No changes needed to be made' and not comments[1]:
comment = comments[0]
elif comments[0] == 'Changes were made' or 'Changed' in comments[1]:
comment = f'File {name} modified'
else:
comment = ' - '.join(comments)
diffs = [
returns[r].get('changes', {}).get('diff') for r in return_keys
]
ret = {
'name': name,
'changes': {},
'result': result,
'comment': comment,
}
if diffs[0] is not None:
ret['changes'].update({'diff_header': diffs[0]})
if diffs[1] is not None:
ret['changes'].update({'diff_config': diffs[1]})
return ret
070701000001E0000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002F00000000salt-formulas-3.0.4/sysconfig-formula/metadata070701000001E1000081A400000000000000000000000168EB80BB00000085000000000000000000000000000000000000003C00000000salt-formulas-3.0.4/sysconfig-formula/metadata/metadata.yml---
summary:
Salt helpers for sysconfig
description:
Library formula containing helper code for managing fillup/sysconfig files.
070701000001E2000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002200000000salt-formulas-3.0.4/tayga-formula070701000001E3000081A400000000000000000000000168EB80BB00000076000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/tayga-formula/README.md# Salt states for TAYGA
## Available states
`tayga`
Installs and configures [TAYGA](http://www.litech.org/tayga/).
070701000001E4000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002B00000000salt-formulas-3.0.4/tayga-formula/metadata070701000001E5000081A400000000000000000000000168EB80BB0000006D000000000000000000000000000000000000003800000000salt-formulas-3.0.4/tayga-formula/metadata/metadata.yml---
summary:
Salt states for managing TAYGA
description:
Salt states for managing the TAYGA NAT64 daemon
070701000001E6000081A400000000000000000000000168EB80BB0000015C000000000000000000000000000000000000003100000000salt-formulas-3.0.4/tayga-formula/pillar.exampletayga:
# Mandatory settings (if not set, the shown default will be written)
tun-device: nat64
# Optional settings (if not set, the lines will be omitted)
ipv4-addr: 192.168.255.1
ipv6-addr: 2001:db8:1::2
prefix: 64:ff9b::/96
dynamic-pool: 192.168.255.0/24
maps:
192.168.255.10: 2001:db8:1::10
192.168.255.15: 2001:db8:1::a
070701000001E7000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002800000000salt-formulas-3.0.4/tayga-formula/tayga070701000001E8000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/tayga-formula/tayga/files070701000001E9000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000003200000000salt-formulas-3.0.4/tayga-formula/tayga/files/etc070701000001EA000081A400000000000000000000000168EB80BB000002D1000000000000000000000000000000000000004000000000salt-formulas-3.0.4/tayga-formula/tayga/files/etc/tayga.conf.j2{{ pillar.get('managed_by_salt_formula', '# Managed by the TAYGA formula') }}
{#- SUSE default settings #}
data-dir /var/lib/tayga
{#- Mandatory settings #}
tun-device {{ tayga.get('tun-device', 'nat64') }}
{#- Optional settings #}
{%- if 'ipv4-addr' in tayga %}
ipv4-addr {{ tayga['ipv4-addr'] }}
{%- endif %}
{%- if 'ipv6-addr' in tayga %}
ipv6-addr {{ tayga['ipv6-addr'] }}
{%- endif %}
{%- if 'prefix' in tayga %}
prefix {{ tayga['prefix'] }}
{%- endif %}
{%- if 'dynamic-pool' in tayga %}
dynamic-pool {{ tayga['dynamic-pool'] }}
{%- endif %}
{%- if 'maps' in tayga and tayga['maps'] is mapping %}
{%- for map_from, map_to in tayga['maps'].items() %}
map {{ map_from }} {{ map_to }}
{%- endfor %}
{%- endif %}
070701000001EB000081A400000000000000000000000168EB80BB000004DA000000000000000000000000000000000000003100000000salt-formulas-3.0.4/tayga-formula/tayga/init.sls{#-
Salt state file for managing TAYGA
Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
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 <https://www.gnu.org/licenses/>.
-#}
{%- set tayga = salt.pillar.get('tayga', {}) %}
tayga_package:
pkg.installed:
- name: tayga
{%- if tayga %}
tayga_configuration:
file.managed:
- name: /etc/tayga.conf
- source: salt://{{ slspath }}/files/etc/tayga.conf.j2
- template: jinja
- context:
tayga: {{ tayga }}
tayga_service:
service.running:
- name: tayga
- enable: true
- reload: false
- require:
- pkg: tayga_package
- watch:
- file: tayga_configuration
{%- endif %}
070701000001EC000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000001900000000salt-formulas-3.0.4/test070701000001ED000081A400000000000000000000000168EB80BB00000494000000000000000000000000000000000000002300000000salt-formulas-3.0.4/test/README.md# Testing
Formulas in this repository are planned to ship with integration tests using Pytest/Testinfra.
## Using Scullery
For easy automated testing using Vagrant virtual machines the experimental [Scullery](https://git.com.de/Georg/scullery) script can be used:
`$ scullery --config test/scullery.ini --suite <name of suite> --test`
The tool will take care of:
- configuring a virtual machine
- installing the example Salt pillar data
- installing the Salt states
- applying the Salt states
- executing Pytest
- cleaning up
Available suites can be found in the configuration file:
`$ grep suite. test/scullery.ini`
The idea is to have suites with the naming structure:
`suite.<formula>.<operating system>.<layout>`
## Manually
Of course, Pytest can be used directly by pointing it towards one or multiple hosts the tests should be performed against. Example using SSH:
`$ pytest --hosts=foo.example.com,bar.example.com some-formula/tests/test_example.py`
Visit the [Testinfra documentation](https://testinfra.readthedocs.io/en/latest/backends.html) for more connection options. By default, the tests will be performed against the local machine.
070701000001EE000081A400000000000000000000000168EB80BB000008AC000000000000000000000000000000000000003100000000salt-formulas-3.0.4/test/bootstrap-salt-roots.sh# Helper script for installing the openSUSE Salt formulas in a Vagrant/Scullery test environment.
# Copyright (C) 2023-2024 Georg Pfuetzenreuter <mail+opensuse@georg-pfuetzenreuter.net>
#
# 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 <https://www.gnu.org/licenses/>.
if [ ! -d /srv/formulas ]
then
mkdir /srv/formulas
fi
if [ ! -d /srv/pillar/samples ]
then
mkdir /srv/pillar/samples
fi
for formula in $(find /vagrant -mindepth 1 -maxdepth 1 -type d -name '*-formula' -printf '%P\n')
do
echo "$formula"
fname="${formula%%-*}"
src="/vagrant/$formula"
src_states="$formula/$fname"
src_formula="/vagrant/$src_states"
src_pillar="/vagrant/$formula/pillar.example"
src_test_pillar="/vagrant/$formula/tests/pillar.sls"
if [ ! -d "$src_formula" ]
then
fname="${fname//_/-}"
src_states="$formula/$fname"
src_formula="/vagrant/$src_states"
fi
if [ ! -h "/srv/formulas/$fname" ]
then
ln -s "$src_formula" "/srv/formulas"
fi
dst_pillar="/srv/pillar/samples/$fname.sls"
if [ -f "$src_test_pillar" ]
then
cp "$src_test_pillar" "$dst_pillar"
elif [ -f "$src_pillar" ]
then
cp "$src_pillar" "$dst_pillar"
fi
dst_salt='/srv/salt'
for mod in modules states proxy
do
mod="_$mod"
src_mod="$src/$mod"
dst_mod="$dst_salt/$mod"
if [ ! -d "$dst_mod" ]
then
mkdir "$dst_mod"
fi
if [ -d "$src_mod" ]
then
echo "$fname: $mod"
cp "$src_mod/"* "$dst_mod/"
fi
done
done
tee /srv/pillar/top.sls >/dev/null <<EOF
{{ saltenv }}:
'*':
- full
EOF
tee /srv/pillar/full.sls >/dev/null <<EOF
include:
- samples.*
EOF
/vagrant/test/scripts/proxy.sh
/vagrant/test/scripts/warnings.sh
070701000001EF000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002100000000salt-formulas-3.0.4/test/scripts070701000001F0000081ED00000000000000000000000168EB80BB000007C8000000000000000000000000000000000000002A00000000salt-formulas-3.0.4/test/scripts/proxy.sh#!/bin/sh
# Initializes a Salt proxy for testing formulas on network devices
# Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
#
# 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 <https://www.gnu.org/licenses/>.
set -Ceu
af='/vagrant/.devices'
if [ ! -f "$af" ]
then
echo 'No proxy devices, skipping ...'
exit 0
fi
proxyp='salt-proxy'
rpm -q "$proxyp" >/dev/null || zypper -n in "$proxyp"
proxyc='/etc/salt/proxy'
tee "$proxyc" >/dev/null <<EOF
master: 127.0.0.1
log_level: debug
EOF
proxyf='/etc/salt/proxy_schedule'
tee "$proxyf" >/dev/null <<EOF
schedule:
__mine_interval: {enabled: true, function: mine.update, jid_include: true, maxrunning: 2,
minutes: 60, return_job: false, run_on_start: true}
__proxy_keepalive:
enabled: true
function: status.proxy_reconnect
jid_include: true
kwargs: {proxy_name: napalm}
maxrunning: 1
minutes: 1
return_job: false
enabled: true
EOF
proxyd='/etc/salt/proxy.d/vsrx-device1'
test -d "$proxyd" || mkdir -p "$proxyd"
proxyl="$proxyd/_schedule.conf"
test -L "$proxyl" || ln -s "$proxyf" "$proxyl"
if [ -f "$af" ]
then
dp='/srv/pillar/devices'
if [ ! -d "$dp" ]
then
mkdir "$dp"
fi
while read -r device address
do
if [ -f "$dp/$device.sls" ]
then
rm "$dp/$device.sls"
fi
printf 'proxy:\n host: %s\n' "$address" > "$dp/$device.sls"
systemctl enable --now "salt-proxy@$device"
done < "$af"
else
echo 'No devices'
fi
070701000001F1000081ED00000000000000000000000168EB80BB00000432000000000000000000000000000000000000002D00000000salt-formulas-3.0.4/test/scripts/warnings.sh#!/bin/sh
# Sets up a `salt` wrapper with no Python deprecation warnings
# This is a hack to ensure test suites assessing the stderr output of Salt produce consistent results
# Copyright (C) 2023-2024 SUSE LLC <georg.pfuetzenreuter@suse.com>
#
# 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 <https://www.gnu.org/licenses/>.
set -Ceu
if [ ! -f /usr/local/bin/salt ]
then
sed \
'/salt_main/a import warnings\nwarnings.filterwarnings("ignore", category=DeprecationWarning)' \
/usr/bin/salt > /usr/local/bin/salt
fi
070701000001F2000081A400000000000000000000000168EB80BB0000037C000000000000000000000000000000000000002600000000salt-formulas-3.0.4/test/scullery.ini[box]
bootstrap=test/bootstrap-salt-roots.sh
[box.tumbleweed]
name=tumbleweed-salt.x86_64
image=https://download.opensuse.org/repositories/home:/crameleon:/appliances/openSUSE_Tumbleweed/boxes/tumbleweed-salt.x86_64.json
[box.leap15_4]
name=leap-salt.x86_64
image=https://download.opensuse.org/repositories/home:/crameleon:/appliances:/Leap-15.4/images/boxes/leap-salt.x86_64.json
[box.leap15_5]
name=leap-salt.x86_64
image=https://download.opensuse.org/repositories/home:/crameleon:/appliances:/Leap-15.5/images/boxes/leap-salt.x86_64.json
[suite.grains_formula.tumbleweed.one_minion]
minions=1
box=tumbleweed
test=grains
[suite.grains_formula.leap.one_minion]
minions=1
box=leap15_5
test=grains
[test.grains]
apply=grains
test=grains-formula/tests
[suite.juniper_junos_formula.tumbleweed.one_master]
masters=1
box=tumbleweed
test=junos
[test.junos]
test=juniper_junos-formula/tests
070701000001F3000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002300000000salt-formulas-3.0.4/zypper-formula070701000001F4000081A400000000000000000000000168EB80BB00000288000000000000000000000000000000000000002B00000000salt-formulas-3.0.4/zypper-formula/LICENSE Copyright (c) 2013-2017 Salt Stack Formulas
Copyright (c) 2018-2024 openSUSE contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
070701000001F5000081A400000000000000000000000168EB80BB000001E3000000000000000000000000000000000000002E00000000salt-formulas-3.0.4/zypper-formula/README.rst==============
zypper-formula
==============
Available states
================
.. contents::
:local:
``zypper``
----------
Includes all of the states mentioned below
``zypper.config``
-----------------
Handles /etc/zypp/zypp.conf and /etc/zypp/zypper.conf
``zypper.packages``
-------------------
Installs packages defined in the `zypper:packages` pillar
``zypper.repositories``
-----------------------
Configure repositories defined in the `zypper:repositories` pillar
070701000001F6000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002C00000000salt-formulas-3.0.4/zypper-formula/metadata070701000001F7000081A400000000000000000000000168EB80BB0000009B000000000000000000000000000000000000003900000000salt-formulas-3.0.4/zypper-formula/metadata/metadata.yml---
summary:
Salt states for managing zypper
description:
Salt states for configuring packages, repositories, and zypper itself.
license:
Apache-2.0
070701000001F8000081A400000000000000000000000168EB80BB000001C0000000000000000000000000000000000000003200000000salt-formulas-3.0.4/zypper-formula/pillar.examplezypper:
config:
zypp_conf:
main:
solver.onlyRequires: 'true'
packages:
tmux: {}
vim: {}
fish:
refresh: true
repositories:
repo-oss:
baseurl: https://download.opensuse.org/distribution/leap/15.4/repo/oss
priority: 99
refresh: False
repo-update-oss:
baseurl: https://$mymirror/update/leap/15.4/oss
priority: 99
refresh: True
variables:
mymirror: example.com
070701000001F9000041ED00000000000000000000000268EB80BB00000000000000000000000000000000000000000000002A00000000salt-formulas-3.0.4/zypper-formula/zypper070701000001FA000081A400000000000000000000000168EB80BB00000317000000000000000000000000000000000000003500000000salt-formulas-3.0.4/zypper-formula/zypper/config.sls{%- set mypillar = salt['pillar.get']('zypper:config', {}) %}
{%- set zypp_conf = mypillar.get('zypp_conf', {}) %}
{%- set zypper_conf = mypillar.get('zypper_conf', {}) %}
{%- if zypp_conf %}
/etc/zypp/zypp.conf:
ini.options_present:
- sections:
{%- for section, data in zypp_conf.items() %}
{{ section }}:
{%- for config, value in data.items() %}
{{ config }}: '{{ value }}'
{%- endfor %}
{%- endfor %}
{%- endif %}
{%- if zypper_conf %}
/etc/zypp/zypper.conf:
ini.options_present:
- sections:
{%- for section, data in zypper_conf.items() %}
{{ section }}:
{%- for config, value in data.items() %}
{{ config }}: '{{ value }}'
{%- endfor %}
{%- endfor %}
{%- endif %}
070701000001FB000081A400000000000000000000000168EB80BB000000BE000000000000000000000000000000000000003300000000salt-formulas-3.0.4/zypper-formula/zypper/init.slsinclude:
- zypper.config
- zypper.repositories
{%- if grains['osmajorrelease'] < 15 %}
- zypper.packages_legacy
{%- else %}
- zypper.packages
{%- endif %}
- zypper.variables
070701000001FC000081A400000000000000000000000168EB80BB00000480000000000000000000000000000000000000003700000000salt-formulas-3.0.4/zypper-formula/zypper/packages.sls{%- set packages = salt['pillar.get']('zypper:packages', {}) %}
{%- set defaults = namespace(refresh=False) %}
{%- set fromdefaults = [] %}
{%- set fromrepos = {} %}
{%- for package, config in packages.items() %}
{%- if 'fromrepo' in config -%}
{%- set refresh = config.get('refresh', False) -%}
{%- do fromrepos.update({ package: {'fromrepo': config.fromrepo, 'refresh': refresh} }) %}
{%- else %}
{%- if 'refresh' in config and config.refresh -%}
{%- set defaults.refresh = True -%}
{%- endif %}
{%- do fromdefaults.append(package) %}
{%- endif %}
{%- endfor %}
{%- if fromdefaults | length %}
zypper_packages:
pkg.installed:
- pkgs:
{%- for package in fromdefaults %}
- {{ package }}
{%- endfor %}
{%- if defaults.refresh %}
- refresh: True
{%- endif %}
{%- endif %}
{%- if fromrepos | length %}
{%- for package, data in fromrepos.items() %}
zypper_pkg_{{ package }}:
pkg.installed:
- name: {{ package }}
{%- if 'refresh' in data %}
- refresh: {{ data.refresh }}
{%- endif %}
{%- if 'fromrepo' in data %}
- fromrepo: {{ data.fromrepo }}
{%- endif %}
{%- endfor %}
{%- endif %}
070701000001FD000081A400000000000000000000000168EB80BB0000016A000000000000000000000000000000000000003E00000000salt-formulas-3.0.4/zypper-formula/zypper/packages_legacy.sls{%- set packages = salt['pillar.get']('zypper:packages', {}) %}
{%- for package, data in packages.items() %}
zypper_pkg_{{ package }}:
pkg.installed:
- name: {{ package }}
{%- if 'refresh' in data %}
- refresh: {{ data.refresh }}
{%- endif %}
{%- if 'fromrepo' in data %}
- fromrepo: {{ data.fromrepo }}
{%- endif %}
{%- endfor %}
070701000001FE000081A400000000000000000000000168EB80BB00000308000000000000000000000000000000000000003B00000000salt-formulas-3.0.4/zypper-formula/zypper/repositories.slsinclude:
{#- dependency to avoid refresh failure in case a newly declared variable is used in the repository URLs #}
- zypper.variables
{%- set repositories = salt['pillar.get']('zypper:repositories', {}) %}
{%- for repo, data in repositories.items() %}
{{ repo }}:
pkgrepo.managed:
- baseurl: {{ data.baseurl }}
- enabled: {{ data.enabled | default(True) }}
- priority: {{ data.priority | default(99) }}
- gpgcheck: {{ data.gpgcheck | default(True) }}
- refresh: {{ data.refresh | default(False) }}
{%- if 'gpgkey' in data or 'key_url' in data %}
- gpgautoimport: {{ data.gpgautoimport | default(True) }}
- gpgkey: {{ data.gpgkey | default(data.key_url) }}
{%- endif %}
- require:
- sls: zypper.variables
{%- endfor %}
070701000001FF000081A400000000000000000000000168EB80BB000002F4000000000000000000000000000000000000003800000000salt-formulas-3.0.4/zypper-formula/zypper/variables.sls{%- set mypillar = salt['pillar.get']('zypper:variables', {}) %}
{%- set directory = '/etc/zypp/vars.d/' %}
zypp_variables_directory:
file.directory:
- name: {{ directory }}
- clean: true
{%- if mypillar %}
zypp_variables:
file.managed:
- names:
{%- for key, value in mypillar.items() %}
- {{ directory }}{{ key }}:
- contents:
{#- zypp takes the first line as the value, comments can only go after #}
- '{{ value }}'
- {{ pillar.get('managed_by_salt_formula', '# Managed by the zypper formula') | yaml_encode }}
{%- endfor %}
- mode: '0644'
- user: root
- group: root
- require_in:
- file: zypp_variables_directory
{%- endif %}
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1091 blocks