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
openSUSE Build Service is sponsored by