Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:infrastructure
infrastructure-formulas
salt-formulas-2.7.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File salt-formulas-2.7.obscpio of Package infrastructure-formulas
07070100000000000081A400000000000000000000000167217D3000000023000000000000000000000000000000000000001D00000000salt-formulas-2.7/.gitignore.vagrant/ __pycache__/ .scullery_* 07070100000001000081A400000000000000000000000167217D300000894D000000000000000000000000000000000000001A00000000salt-formulas-2.7/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>. 07070100000002000081A400000000000000000000000167217D30000002B8000000000000000000000000000000000000001C00000000salt-formulas-2.7/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. 07070100000003000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002700000000salt-formulas-2.7/apache_httpd-formula07070100000004000081A400000000000000000000000167217D3000000129000000000000000000000000000000000000003100000000salt-formulas-2.7/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`. 07070100000005000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003400000000salt-formulas-2.7/apache_httpd-formula/apache_httpd07070100000006000081A400000000000000000000000167217D3000000243000000000000000000000000000000000000004200000000salt-formulas-2.7/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" ] } } 07070100000007000081A400000000000000000000000167217D3000000FB8000000000000000000000000000000000000003D00000000salt-formulas-2.7/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 #} 07070100000008000081A400000000000000000000000167217D3000000678000000000000000000000000000000000000004100000000salt-formulas-2.7/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 %} 07070100000009000081A400000000000000000000000167217D3000000A4C000000000000000000000000000000000000003E00000000salt-formulas-2.7/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', } -%} 0707010000000A000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003C00000000salt-formulas-2.7/apache_httpd-formula/apache_httpd/modules0707010000000B000081A400000000000000000000000167217D300000014B000000000000000000000000000000000000004500000000salt-formulas-2.7/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" } 0707010000000C000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003F00000000salt-formulas-2.7/apache_httpd-formula/apache_httpd/modules/os0707010000000D000081A400000000000000000000000167217D3000000BCF000000000000000000000000000000000000006800000000salt-formulas-2.7/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" ] } 0707010000000E000081A400000000000000000000000167217D3000000BEE000000000000000000000000000000000000006800000000salt-formulas-2.7/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" ] } 0707010000000F0000A1FF00000000000000000000000167217D3000000028000000000000000000000000000000000000005700000000salt-formulas-2.7/apache_httpd-formula/apache_httpd/modules/os/openSUSE_Leap_15.5.jsonSUSE_Linux_Enterprise_Server_15_SP5.json070701000000100000A1FF00000000000000000000000167217D3000000028000000000000000000000000000000000000005700000000salt-formulas-2.7/apache_httpd-formula/apache_httpd/modules/os/openSUSE_Leap_15.6.jsonSUSE_Linux_Enterprise_Server_15_SP6.json07070100000011000081A400000000000000000000000167217D30000004C0000000000000000000000000000000000000004100000000salt-formulas-2.7/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 07070100000012000081A400000000000000000000000167217D300000063D000000000000000000000000000000000000003E00000000salt-formulas-2.7/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 %} 07070100000013000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003D00000000salt-formulas-2.7/apache_httpd-formula/apache_httpd/services07070100000014000081A400000000000000000000000167217D3000000403000000000000000000000000000000000000004600000000salt-formulas-2.7/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 07070100000015000081A400000000000000000000000167217D30000003B8000000000000000000000000000000000000004700000000salt-formulas-2.7/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 07070100000016000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003E00000000salt-formulas-2.7/apache_httpd-formula/apache_httpd/templates07070100000017000081A400000000000000000000000167217D3000000EF1000000000000000000000000000000000000004B00000000salt-formulas-2.7/apache_httpd-formula/apache_httpd/templates/config.jinja{{ 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.error(customlog) %} {%- for customlog_option in defaults['customlog'].keys() %} {%- do salt.log.error(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 %} {%- if option in repetitive_options %} {%- for entry in value %} {{ ( option ~ ' ' ~ entry ) | indent(i, True) }} {%- endfor %} {%- else %} {{ ( option ~ ' ' ~ ' '.join(value) ) | indent(i, True) }} {%- endif %} {%- 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 %} {%- for low_low_value in low_low_values %} {{ ( option ~ ' ' ~ low_option ~ ' ' ~ low_low_option ~ ' ' ~ low_low_value ) | indent(i, True) }} {%- endfor %} {%- else %} {{ ( low_low_option ~ ' ' ~ ' '.join(low_low_values) ) | indent(i + 2, True) }} {%- 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 #} 07070100000018000081A400000000000000000000000167217D3000000599000000000000000000000000000000000000005200000000salt-formulas-2.7/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 #} 07070100000019000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002B00000000salt-formulas-2.7/apache_httpd-formula/bin0707010000001A000081ED00000000000000000000000167217D300000093E000000000000000000000000000000000000003600000000salt-formulas-2.7/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" 0707010000001B000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003000000000salt-formulas-2.7/apache_httpd-formula/metadata0707010000001C000081A400000000000000000000000167217D30000000B8000000000000000000000000000000000000003D00000000salt-formulas-2.7/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 0707010000001D000081A400000000000000000000000167217D300000127E000000000000000000000000000000000000003600000000salt-formulas-2.7/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 # 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: [] 0707010000001E000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002D00000000salt-formulas-2.7/apache_httpd-formula/tests0707010000001F000081A400000000000000000000000167217D3000000655000000000000000000000000000000000000003900000000salt-formulas-2.7/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 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}') 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') 07070100000020000081A400000000000000000000000167217D300000039E000000000000000000000000000000000000003800000000salt-formulas-2.7/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 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 07070100000021000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003700000000salt-formulas-2.7/apache_httpd-formula/tests/reference07070100000022000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003B00000000salt-formulas-2.7/apache_httpd-formula/tests/reference/etc07070100000023000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004300000000salt-formulas-2.7/apache_httpd-formula/tests/reference/etc/apache207070100000024000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004A00000000salt-formulas-2.7/apache_httpd-formula/tests/reference/etc/apache2/conf.d07070100000025000081A400000000000000000000000167217D300000004E000000000000000000000000000000000000005300000000salt-formulas-2.7/apache_httpd-formula/tests/reference/etc/apache2/conf.d/log.conf# Managed by the apache_httpd formula SetEnvIf Request_URI ^/health$ donotlog 07070100000026000081A400000000000000000000000167217D300000002B000000000000000000000000000000000000005700000000salt-formulas-2.7/apache_httpd-formula/tests/reference/etc/apache2/conf.d/log.conf.md5f36f2adb9df5b321b6b2383a339de780 log.conf 07070100000027000081A400000000000000000000000167217D3000000087000000000000000000000000000000000000005600000000salt-formulas-2.7/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 07070100000028000081A400000000000000000000000167217D300000002E000000000000000000000000000000000000005A00000000salt-formulas-2.7/apache_httpd-formula/tests/reference/etc/apache2/conf.d/remote.conf.md52d4e69a65a3c77743f8504af4ae2415a remote.conf 07070100000029000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004C00000000salt-formulas-2.7/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d0707010000002A000081A400000000000000000000000167217D300000013D000000000000000000000000000000000000005900000000salt-formulas-2.7/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 </Directory> </VirtualHost> 0707010000002B000081A400000000000000000000000167217D300000002F000000000000000000000000000000000000005D00000000salt-formulas-2.7/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d/mysite1.conf.md5da428b1d98b77a0679a7cb7f1b981be9 mysite1.conf 0707010000002C000081A400000000000000000000000167217D30000001BA000000000000000000000000000000000000005900000000salt-formulas-2.7/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> 0707010000002D000081A400000000000000000000000167217D300000002F000000000000000000000000000000000000005D00000000salt-formulas-2.7/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d/mysite2.conf.md5d0004f7ee286d5402c55fa2f187f0bab mysite2.conf 0707010000002E000081A400000000000000000000000167217D3000000128000000000000000000000000000000000000005800000000salt-formulas-2.7/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> 0707010000002F000081A400000000000000000000000167217D300000002E000000000000000000000000000000000000005C00000000salt-formulas-2.7/apache_httpd-formula/tests/reference/etc/apache2/vhosts.d/status.conf.md5dda3e0dd18b99d5c3525dd1b43e35081 status.conf 07070100000030000081ED00000000000000000000000167217D30000000A1000000000000000000000000000000000000003400000000salt-formulas-2.7/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/ "$@" 07070100000031000081A400000000000000000000000167217D3000000F91000000000000000000000000000000000000003B00000000salt-formulas-2.7/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': 'da428b1d98b77a0679a7cb7f1b981be9', 'vhosts.d/mysite2.conf': 'd0004f7ee286d5402c55fa2f187f0bab', '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 07070100000032000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002700000000salt-formulas-2.7/backupscript-formula07070100000033000081A400000000000000000000000167217D300000020A000000000000000000000000000000000000003100000000salt-formulas-2.7/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. 07070100000034000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003400000000salt-formulas-2.7/backupscript-formula/backupscript07070100000035000081A400000000000000000000000167217D3000000AB3000000000000000000000000000000000000003D00000000salt-formulas-2.7/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 #} 07070100000036000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003000000000salt-formulas-2.7/backupscript-formula/metadata07070100000037000081A400000000000000000000000167217D30000000BF000000000000000000000000000000000000003D00000000salt-formulas-2.7/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 07070100000038000081A400000000000000000000000167217D3000000175000000000000000000000000000000000000003600000000salt-formulas-2.7/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: {} 07070100000039000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002500000000salt-formulas-2.7/bootloader-formula0707010000003A000081A400000000000000000000000167217D30000001A6000000000000000000000000000000000000002F00000000salt-formulas-2.7/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. 0707010000003B000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003000000000salt-formulas-2.7/bootloader-formula/bootloader0707010000003C000081A400000000000000000000000167217D300000054F000000000000000000000000000000000000003F00000000salt-formulas-2.7/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 %} 0707010000003D000081A400000000000000000000000167217D30000008CD000000000000000000000000000000000000003900000000salt-formulas-2.7/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 -#} 0707010000003E000081A400000000000000000000000167217D300000032A000000000000000000000000000000000000003900000000salt-formulas-2.7/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 0707010000003F000081A400000000000000000000000167217D30000003A7000000000000000000000000000000000000003A00000000salt-formulas-2.7/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', {}) -%} 07070100000040000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002E00000000salt-formulas-2.7/bootloader-formula/metadata07070100000041000081A400000000000000000000000167217D30000000A3000000000000000000000000000000000000003B00000000salt-formulas-2.7/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 07070100000042000081A400000000000000000000000167217D30000002BB000000000000000000000000000000000000003400000000salt-formulas-2.7/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 07070100000043000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002300000000salt-formulas-2.7/doofetch-formula07070100000044000081A400000000000000000000000167217D300000009C000000000000000000000000000000000000002D00000000salt-formulas-2.7/doofetch-formula/README.md# Salt states for doofetch ## Available states `doofetch` Installs and configures [doofetch](https://github.com/tacerus/doofetch) and enables its timer. 07070100000045000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002C00000000salt-formulas-2.7/doofetch-formula/doofetch07070100000046000081A400000000000000000000000167217D3000000664000000000000000000000000000000000000003500000000salt-formulas-2.7/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 07070100000047000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002C00000000salt-formulas-2.7/doofetch-formula/metadata07070100000048000081A400000000000000000000000167217D300000008C000000000000000000000000000000000000003900000000salt-formulas-2.7/doofetch-formula/metadata/metadata.yml--- summary: Salt states for managing doofetch description: Salt states for installing and configuring doofetch. require: - sysconfig 07070100000049000081A400000000000000000000000167217D300000022B000000000000000000000000000000000000003200000000salt-formulas-2.7/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 0707010000004A000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002000000000salt-formulas-2.7/gitea-formula0707010000004B000081A400000000000000000000000167217D3000000072000000000000000000000000000000000000002A00000000salt-formulas-2.7/gitea-formula/README.md# Salt states for Gitea ## Available states `gitea` Installs and configures [Gitea](https://about.gitea.com/). 0707010000004C000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002600000000salt-formulas-2.7/gitea-formula/gitea0707010000004D000081A400000000000000000000000167217D30000002AF000000000000000000000000000000000000003400000000salt-formulas-2.7/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 0707010000004E000081A400000000000000000000000167217D3000000570000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 0707010000004F000081A400000000000000000000000167217D3000000389000000000000000000000000000000000000003000000000salt-formulas-2.7/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', {}) -%} 07070100000050000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002900000000salt-formulas-2.7/gitea-formula/metadata07070100000051000081A400000000000000000000000167217D300000002E000000000000000000000000000000000000003600000000salt-formulas-2.7/gitea-formula/metadata/metadata.yml--- summary: Salt states for managing Gitea 07070100000052000081A400000000000000000000000167217D300000015D000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 07070100000053000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002100000000salt-formulas-2.7/grains-formula07070100000054000081A400000000000000000000000167217D3000000288000000000000000000000000000000000000002900000000salt-formulas-2.7/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. 07070100000055000081A400000000000000000000000167217D300000029C000000000000000000000000000000000000002C00000000salt-formulas-2.7/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. 07070100000056000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002800000000salt-formulas-2.7/grains-formula/grains07070100000057000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002E00000000salt-formulas-2.7/grains-formula/grains/files07070100000058000081A400000000000000000000000167217D3000000078000000000000000000000000000000000000003500000000salt-formulas-2.7/grains-formula/grains/files/grains{% set cfg_grains = salt['pillar.get']('grains', {}) -%} {% if cfg_grains -%} {{ cfg_grains|yaml(False) }} {% endif -%} 07070100000059000081A400000000000000000000000167217D30000000A1000000000000000000000000000000000000003100000000salt-formulas-2.7/grains-formula/grains/init.sls/etc/salt/grains: file.managed: - source: - salt://grains/files/grains - user: root - group: root - mode: '0644' - template: jinja 0707010000005A000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002A00000000salt-formulas-2.7/grains-formula/metadata0707010000005B000081A400000000000000000000000167217D3000000044000000000000000000000000000000000000003700000000salt-formulas-2.7/grains-formula/metadata/metadata.yml--- summary: Salt state for managing grains license: Apache-2.0 0707010000005C000081A400000000000000000000000167217D3000000067000000000000000000000000000000000000003000000000salt-formulas-2.7/grains-formula/pillar.examplegrains: roles: - webserver - memcache deployment: datacenter4 cabinet: 13 cab_u: 14-15 0707010000005D000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002700000000salt-formulas-2.7/grains-formula/tests0707010000005E000081A400000000000000000000000167217D300000003F000000000000000000000000000000000000003200000000salt-formulas-2.7/grains-formula/tests/pillar.slsgrains: snack: peanuts treats: - chocolate - candy 0707010000005F000081A400000000000000000000000167217D30000002D4000000000000000000000000000000000000003600000000salt-formulas-2.7/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' 07070100000060000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002900000000salt-formulas-2.7/infrastructure-formula07070100000061000081A400000000000000000000000167217D30000005BF000000000000000000000000000000000000003300000000salt-formulas-2.7/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`. 07070100000062000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003100000000salt-formulas-2.7/infrastructure-formula/_states07070100000063000081A400000000000000000000000167217D3000000FE3000000000000000000000000000000000000003F00000000salt-formulas-2.7/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) 07070100000064000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003800000000salt-formulas-2.7/infrastructure-formula/infrastructure07070100000065000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004000000000salt-formulas-2.7/infrastructure-formula/infrastructure/libvirt07070100000066000081A400000000000000000000000167217D30000000F6000000000000000000000000000000000000005000000000salt-formulas-2.7/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 %} 07070100000067000081A400000000000000000000000167217D3000001AC9000000000000000000000000000000000000004C00000000salt-formulas-2.7/infrastructure-formula/infrastructure/libvirt/domains.sls{#- Salt state file for managing libvirt domains 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 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'] -%} {%- 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 domainpillar = lowpillar['domains'][domain] -%} {%- set clusterpillar = domainpillar['clusters'] -%} {%- set machinepillar = domainpillar['machines'] -%} {%- 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'] ) %} {%- if machinepillar is not none %} {%- for machine, config in machinepillar.items() %} {%- set machine = machine ~ '.' ~ domain %} {%- 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 %} {%- endfor %} {%- endif %} {%- 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 %} 07070100000068000081A400000000000000000000000167217D3000000035000000000000000000000000000000000000004900000000salt-formulas-2.7/infrastructure-formula/infrastructure/libvirt/init.slsinclude: - .packages - .directories - .domains 07070100000069000081A400000000000000000000000167217D300000007A000000000000000000000000000000000000004D00000000salt-formulas-2.7/infrastructure-formula/infrastructure/libvirt/packages.slsinfrastructure_libvirt_packages: pkg.installed: - pkgs: - python3-libvirt-python - reload_modules: true 0707010000006A000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004200000000salt-formulas-2.7/infrastructure-formula/infrastructure/orchestra0707010000006B000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004C00000000salt-formulas-2.7/infrastructure-formula/infrastructure/orchestra/deploy_vm0707010000006C000081A400000000000000000000000167217D3000000651000000000000000000000000000000000000005600000000salt-formulas-2.7/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 }}' 0707010000006D000081A400000000000000000000000167217D3000000549000000000000000000000000000000000000005600000000salt-formulas-2.7/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 }}' 0707010000006E000081A400000000000000000000000167217D300000081A000000000000000000000000000000000000005600000000salt-formulas-2.7/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 -%} 0707010000006F000081A400000000000000000000000167217D30000005EE000000000000000000000000000000000000005500000000salt-formulas-2.7/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 }}' 07070100000070000081A400000000000000000000000167217D3000000471000000000000000000000000000000000000005700000000salt-formulas-2.7/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 }}' 07070100000071000081A400000000000000000000000167217D30000004DD000000000000000000000000000000000000005E00000000salt-formulas-2.7/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 }}' 07070100000072000081A400000000000000000000000167217D30000006FE000000000000000000000000000000000000005A00000000salt-formulas-2.7/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 }}' 07070100000073000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003D00000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt07070100000074000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004300000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/files07070100000075000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004700000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/files/etc07070100000076000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004C00000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/files/etc/salt07070100000077000081A400000000000000000000000167217D3000000196000000000000000000000000000000000000005D00000000salt-formulas-2.7/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 %} 07070100000078000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004F00000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/files/etc/systemd07070100000079000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000005600000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/files/etc/systemd/system0707010000007A000081A400000000000000000000000167217D3000000537000000000000000000000000000000000000006A00000000salt-formulas-2.7/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 0707010000007B000081A400000000000000000000000167217D30000003BC000000000000000000000000000000000000006800000000salt-formulas-2.7/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 0707010000007C000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004700000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/files/srv0707010000007D000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004F00000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/files/srv/reactor0707010000007E000081A400000000000000000000000167217D3000000287000000000000000000000000000000000000006500000000salt-formulas-2.7/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 -%} 0707010000007F000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004700000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/files/usr07070100000080000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004D00000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/files/usr/local07070100000081000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000005200000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/files/usr/local/sbin07070100000082000081ED00000000000000000000000167217D30000008EA000000000000000000000000000000000000007000000000salt-formulas-2.7/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-2024 SUSE LLC <ignacio.torres@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 | 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" 07070100000083000081ED00000000000000000000000167217D3000000365000000000000000000000000000000000000005700000000salt-formulas-2.7/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 07070100000084000081ED00000000000000000000000167217D30000007E9000000000000000000000000000000000000006C00000000salt-formulas-2.7/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 <==' 07070100000085000081A400000000000000000000000167217D30000003D8000000000000000000000000000000000000006700000000salt-formulas-2.7/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 07070100000086000081ED00000000000000000000000167217D3000000635000000000000000000000000000000000000006D00000000salt-formulas-2.7/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" 07070100000087000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004100000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/git07070100000088000081A400000000000000000000000167217D300000026F000000000000000000000000000000000000004E00000000salt-formulas-2.7/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 %} 07070100000089000081A400000000000000000000000167217D300000038C000000000000000000000000000000000000004A00000000salt-formulas-2.7/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 0707010000008A000081A400000000000000000000000167217D300000045A000000000000000000000000000000000000004700000000salt-formulas-2.7/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', {}) -%} 0707010000008B000081A400000000000000000000000167217D30000005DE000000000000000000000000000000000000004800000000salt-formulas-2.7/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 %} /etc/salt/gpgkeys: file.directory: - user: salt - group: salt - dir_mode: '0700' - file_mode: '0600' - recurse: - user - group - mode /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 %} 0707010000008C000081A400000000000000000000000167217D3000000024000000000000000000000000000000000000004800000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/minion.slsinclude: - grains - salt.minion 0707010000008D000081A400000000000000000000000167217D3000000576000000000000000000000000000000000000005A00000000salt-formulas-2.7/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 -#} 0707010000008E000081A400000000000000000000000167217D3000000421000000000000000000000000000000000000004E00000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/proxy_master.sls{%- set domain = grains['domain'] %} {%- set highpillar = salt['pillar.get']('infrastructure:salt', {}) -%} {%- 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 %} /srv/pillar_network/network_data/switches: file.directory: - user: {{ highpillar.get('sync_user', 'sync') }} - group: salt - makedirs: true /srv/pillar_network/top.sls: file.managed: - group: salt - contents: | {% raw %}{{ saltenv }}{%- endraw %}: '*': - network_data - require: - file: /srv/pillar_network/network_data/switches 0707010000008F000081A400000000000000000000000167217D300000104D000000000000000000000000000000000000005900000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/proxy_networkautomation.sls{%- set highpillar = salt['pillar.get']('infrastructure:salt', {}) -%} {%- set mypillar = highpillar.get('proxy', {}) -%} {%- if mypillar %} salt_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 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 %} - /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 %} include: - podman.containers 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 -#} 07070100000090000081A400000000000000000000000167217D300000023F000000000000000000000000000000000000004E00000000salt-formulas-2.7/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 %} 07070100000091000081A400000000000000000000000167217D300000026F000000000000000000000000000000000000004800000000salt-formulas-2.7/infrastructure-formula/infrastructure/salt/syndic.slsinclude: - salt.gitfs.pygit2 - salt.syndic - .master {%- set id = grains['id'] %} {%- set gpg_script = '/usr/local/sbin/create_salt_master_gpg_key.sh' %} 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 07070100000092000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004000000000salt-formulas-2.7/infrastructure-formula/infrastructure/suse_ha07070100000093000081A400000000000000000000000167217D3000000D63000000000000000000000000000000000000004E00000000salt-formulas-2.7/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'] -%} {%- 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 -%} {%- set domainpillar = lowpillar['domains'][domain] -%} {%- 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 ~ '.' ~ domain %} {%- do salt.log.debug(machine) %} {%- if config['cluster'] == cluster %} {%- set instance_attributes = { 'config': domaindir ~ '/' ~ machine ~ '.xml', 'hypervisor': 'qemu:///system', '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}, 'migrate_from': {'timeout': 550, 'interval': 0} } %} {%- 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 %} 07070100000094000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003200000000salt-formulas-2.7/infrastructure-formula/metadata07070100000095000081A400000000000000000000000167217D300000009A000000000000000000000000000000000000003F00000000salt-formulas-2.7/infrastructure-formula/metadata/metadata.yml--- summary: Salt states specific to the openSUSE/SUSE infrastructures description: Custom Salt states specific to the openSUSE/SUSE infrastructures. 07070100000096000081A400000000000000000000000167217D300000083E000000000000000000000000000000000000003C00000000salt-formulas-2.7/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 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 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 # *RT: attribute is only used for updating RackTables 07070100000097000081A400000000000000000000000167217D30000033D5000000000000000000000000000000000000002F00000000salt-formulas-2.7/infrastructure-formulas.spec# # spec file for package infrastructure-formulas # # Copyright (c) 2024 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 Name: infrastructure-formulas Version: 2.7 Release: 0 Summary: Custom Salt states for the openSUSE/SUSE infrastructures 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: 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: 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 Custom Salt states used in the openSUSE and SUSE infrastructures. %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 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 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. %prep mv %{_sourcedir}/salt-formulas-%{version}/* . %build %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" src_states="$formula/$fname" if [ ! -d "$src_states" ] then src_states="$formula/${fname//_/-}" fi src_execumodules="$formula/_modules" src_statemodules="$formula/_states" src_bin="$formula/bin" dst_metadata="%{mdir}/$fname" dst_states="%{sdir}/$fname" 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 %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 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 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 %changelog 07070100000098000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002200000000salt-formulas-2.7/jenkins-formula07070100000099000081A400000000000000000000000167217D3000000161000000000000000000000000000000000000002C00000000salt-formulas-2.7/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. 0707010000009A000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002A00000000salt-formulas-2.7/jenkins-formula/jenkins0707010000009B000081A400000000000000000000000167217D30000005F5000000000000000000000000000000000000003400000000salt-formulas-2.7/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 %} 0707010000009C000081A400000000000000000000000167217D300000084C000000000000000000000000000000000000003900000000salt-formulas-2.7/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 0707010000009D000081A400000000000000000000000167217D30000000FE000000000000000000000000000000000000003800000000salt-formulas-2.7/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' 0707010000009E000081A400000000000000000000000167217D30000003E1000000000000000000000000000000000000003400000000salt-formulas-2.7/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', {}) -%} 0707010000009F000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002B00000000salt-formulas-2.7/jenkins-formula/metadata070701000000A0000081A400000000000000000000000167217D3000000094000000000000000000000000000000000000003800000000salt-formulas-2.7/jenkins-formula/metadata/metadata.yml--- summary: Salt states for managing Jenkins description: Salt states for managing Jenkins Controller and Agent servers require: - sysconfig 070701000000A1000081A400000000000000000000000167217D300000040F000000000000000000000000000000000000003100000000salt-formulas-2.7/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' 070701000000A2000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002800000000salt-formulas-2.7/jenkins-formula/tests070701000000A3000081A400000000000000000000000167217D30000006F5000000000000000000000000000000000000004600000000salt-formulas-2.7/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 070701000000A4000081A400000000000000000000000167217D300000047A000000000000000000000000000000000000004100000000salt-formulas-2.7/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 070701000000A5000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002800000000salt-formulas-2.7/juniper_junos-formula070701000000A6000081A400000000000000000000000167217D3000000A99000000000000000000000000000000000000003200000000salt-formulas-2.7/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` 070701000000A7000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003100000000salt-formulas-2.7/juniper_junos-formula/_modules070701000000A8000081A400000000000000000000000167217D3000001C2D000000000000000000000000000000000000003E00000000salt-formulas-2.7/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 070701000000A9000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002C00000000salt-formulas-2.7/juniper_junos-formula/bin070701000000AA000081ED00000000000000000000000167217D3000003CE5000000000000000000000000000000000000004200000000salt-formulas-2.7/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() 070701000000AB000081ED00000000000000000000000167217D3000000BF9000000000000000000000000000000000000003300000000salt-formulas-2.7/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 070701000000AC000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003600000000salt-formulas-2.7/juniper_junos-formula/juniper_junos070701000000AD000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003C00000000salt-formulas-2.7/juniper_junos-formula/juniper_junos/files070701000000AE000081A400000000000000000000000167217D300000020F000000000000000000000000000000000000004900000000salt-formulas-2.7/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 -#} 070701000000AF000081A400000000000000000000000167217D30000000D9000000000000000000000000000000000000004800000000salt-formulas-2.7/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' -%} 070701000000B0000081A400000000000000000000000167217D3000000124000000000000000000000000000000000000004800000000salt-formulas-2.7/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' -%} 070701000000B1000081A400000000000000000000000167217D3000000F60000000000000000000000000000000000000004A00000000salt-formulas-2.7/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 %} 070701000000B2000081A400000000000000000000000167217D3000000268000000000000000000000000000000000000004D00000000salt-formulas-2.7/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 %} 070701000000B3000081A400000000000000000000000167217D3000000BDB000000000000000000000000000000000000004300000000salt-formulas-2.7/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 %} 070701000000B4000081A400000000000000000000000167217D30000001AD000000000000000000000000000000000000004300000000salt-formulas-2.7/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 %} 070701000000B5000081A400000000000000000000000167217D30000002ED000000000000000000000000000000000000004800000000salt-formulas-2.7/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 -#} 070701000000B6000081A400000000000000000000000167217D30000007C9000000000000000000000000000000000000004900000000salt-formulas-2.7/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 #} 070701000000B7000081A400000000000000000000000167217D300000021B000000000000000000000000000000000000004A00000000salt-formulas-2.7/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 -#} 070701000000B8000081A400000000000000000000000167217D300000021D000000000000000000000000000000000000004600000000salt-formulas-2.7/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 -#} 070701000000B9000081A400000000000000000000000167217D3000000220000000000000000000000000000000000000004400000000salt-formulas-2.7/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 -#} 070701000000BA000081A400000000000000000000000167217D30000000F1000000000000000000000000000000000000004600000000salt-formulas-2.7/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' -%} 070701000000BB000081A400000000000000000000000167217D30000000F0000000000000000000000000000000000000004E00000000salt-formulas-2.7/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 %} 070701000000BC000081A400000000000000000000000167217D30000003F8000000000000000000000000000000000000004600000000salt-formulas-2.7/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 %} 070701000000BD000081A400000000000000000000000167217D30000004B8000000000000000000000000000000000000004500000000salt-formulas-2.7/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 %} 070701000000BE000081A400000000000000000000000167217D3000000393000000000000000000000000000000000000004500000000salt-formulas-2.7/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 -#} 070701000000BF000081A400000000000000000000000167217D3000000399000000000000000000000000000000000000004300000000salt-formulas-2.7/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 070701000000C0000081A400000000000000000000000167217D3000000304000000000000000000000000000000000000004000000000salt-formulas-2.7/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') -%} 070701000000C1000081A400000000000000000000000167217D3000000394000000000000000000000000000000000000004100000000salt-formulas-2.7/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 070701000000C2000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003100000000salt-formulas-2.7/juniper_junos-formula/metadata070701000000C3000081A400000000000000000000000167217D3000000089000000000000000000000000000000000000003E00000000salt-formulas-2.7/juniper_junos-formula/metadata/metadata.yml--- summary: Salt states for managing Junos description: Salt states for managing Juniper Junos based network devices using pillars. 070701000000C4000081A400000000000000000000000167217D3000000FA9000000000000000000000000000000000000003B00000000salt-formulas-2.7/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 070701000000C5000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002E00000000salt-formulas-2.7/juniper_junos-formula/tests070701000000C6000081A400000000000000000000000167217D3000000987000000000000000000000000000000000000003A00000000salt-formulas-2.7/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() 070701000000C7000081A400000000000000000000000167217D30000005D2000000000000000000000000000000000000003500000000salt-formulas-2.7/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}') 070701000000C8000081A400000000000000000000000167217D3000000CE3000000000000000000000000000000000000003900000000salt-formulas-2.7/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 070701000000C9000081A400000000000000000000000167217D3000000683000000000000000000000000000000000000004600000000salt-formulas-2.7/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'] 070701000000CA000081A400000000000000000000000167217D3000001920000000000000000000000000000000000000004100000000salt-formulas-2.7/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)) 070701000000CB000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002000000000salt-formulas-2.7/kexec-formula070701000000CC000081A400000000000000000000000167217D30000000AA000000000000000000000000000000000000002A00000000salt-formulas-2.7/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. 070701000000CD000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002600000000salt-formulas-2.7/kexec-formula/kexec070701000000CE000081A400000000000000000000000167217D300000047C000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 070701000000CF000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002900000000salt-formulas-2.7/kexec-formula/metadata070701000000D0000081A400000000000000000000000167217D3000000079000000000000000000000000000000000000003600000000salt-formulas-2.7/kexec-formula/metadata/metadata.yml--- summary: Salt states for managing Kexec description: Salt states for managing Kexec using the kexec-load service 070701000000D1000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002200000000salt-formulas-2.7/libvirt-formula070701000000D2000081A400000000000000000000000167217D3000000065000000000000000000000000000000000000002C00000000salt-formulas-2.7/libvirt-formula/README.md# Salt states for Libvirt ## Available states `libvirt` Installs and configures a Libvirt server. 070701000000D3000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002A00000000salt-formulas-2.7/libvirt-formula/libvirt070701000000D4000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003300000000salt-formulas-2.7/libvirt-formula/libvirt/defaults070701000000D5000081A400000000000000000000000167217D3000000020000000000000000000000000000000000000004000000000salt-formulas-2.7/libvirt-formula/libvirt/defaults/libvirt.yaml--- uri_default: qemu:///system 070701000000D6000081A400000000000000000000000167217D3000000015000000000000000000000000000000000000004100000000salt-formulas-2.7/libvirt-formula/libvirt/defaults/libvirtd.yaml--- max_clients: 128 070701000000D7000081A400000000000000000000000167217D3000000080000000000000000000000000000000000000003D00000000salt-formulas-2.7/libvirt-formula/libvirt/defaults/qemu.yaml--- security_driver: apparmor security_default_confined: 1 security_require_confined: 1 lock_manager: lockd set_process_name: 1 070701000000D8000081A400000000000000000000000167217D300000000E000000000000000000000000000000000000004000000000salt-formulas-2.7/libvirt-formula/libvirt/defaults/sockets.yaml--- tcp: true 070701000000D9000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003000000000salt-formulas-2.7/libvirt-formula/libvirt/files070701000000DA000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003400000000salt-formulas-2.7/libvirt-formula/libvirt/files/etc070701000000DB000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003C00000000salt-formulas-2.7/libvirt-formula/libvirt/files/etc/libvirt070701000000DC000081A400000000000000000000000167217D30000000F4000000000000000000000000000000000000004900000000salt-formulas-2.7/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 %} 070701000000DD000081A400000000000000000000000167217D30000006DE000000000000000000000000000000000000003500000000salt-formulas-2.7/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 %} 070701000000DE000081A400000000000000000000000167217D3000000996000000000000000000000000000000000000003300000000salt-formulas-2.7/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 %} 070701000000DF000081A400000000000000000000000167217D3000000575000000000000000000000000000000000000003400000000salt-formulas-2.7/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) -%} 070701000000E0000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002B00000000salt-formulas-2.7/libvirt-formula/metadata070701000000E1000081A400000000000000000000000167217D3000000080000000000000000000000000000000000000003800000000salt-formulas-2.7/libvirt-formula/metadata/metadata.yml--- summary: Salt states for managing libvirt description: Salt states for managing libvirt servers. require: - sysconfig 070701000000E2000081A400000000000000000000000167217D300000061B000000000000000000000000000000000000003100000000salt-formulas-2.7/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 070701000000E3000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002000000000salt-formulas-2.7/lldpd-formula070701000000E4000081A400000000000000000000000167217D30000000E6000000000000000000000000000000000000002A00000000salt-formulas-2.7/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. 070701000000E5000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002600000000salt-formulas-2.7/lldpd-formula/lldpd070701000000E6000081A400000000000000000000000167217D300000021F000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 - reload: false - require: - pkg: lldpd_package {%- if 'lldpd' in pillar and 'sysconfig' in pillar['lldpd'] %} - watch: - suse_sysconfig: lldpd_sysconfig {%- endif %} 070701000000E7000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002900000000salt-formulas-2.7/lldpd-formula/metadata070701000000E8000081A400000000000000000000000167217D3000000086000000000000000000000000000000000000003600000000salt-formulas-2.7/lldpd-formula/metadata/metadata.yml--- summary: Salt states for managing lldpd description: Salt states for installing and configuring lldpd. require: - sysconfig 070701000000E9000081A400000000000000000000000167217D3000000050000000000000000000000000000000000000002F00000000salt-formulas-2.7/lldpd-formula/pillar.examplelldpd: # written to /etc/sysconfig/lldpd sysconfig: lldpd_options: -M 1 070701000000EA000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000001F00000000salt-formulas-2.7/lock-formula070701000000EB000081A400000000000000000000000167217D30000002E6000000000000000000000000000000000000002900000000salt-formulas-2.7/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 }}' ``` 070701000000EC000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002700000000salt-formulas-2.7/lock-formula/_states070701000000ED000081A400000000000000000000000167217D3000000DD3000000000000000000000000000000000000002F00000000salt-formulas-2.7/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) 070701000000EE000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002800000000salt-formulas-2.7/lock-formula/metadata070701000000EF000081A400000000000000000000000167217D30000000E9000000000000000000000000000000000000003500000000salt-formulas-2.7/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. 070701000000F0000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002100000000salt-formulas-2.7/lunmap-formula070701000000F1000081A400000000000000000000000167217D300000006F000000000000000000000000000000000000002B00000000salt-formulas-2.7/lunmap-formula/README.md# Salt state for managing a LUN mapping file ## Available states `lunmap` Creates or updates `/etc/lunmap`. 070701000000F2000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002800000000salt-formulas-2.7/lunmap-formula/lunmap070701000000F3000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002E00000000salt-formulas-2.7/lunmap-formula/lunmap/files070701000000F4000081A400000000000000000000000167217D3000000495000000000000000000000000000000000000003800000000salt-formulas-2.7/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 %} 070701000000F5000081A400000000000000000000000167217D300000035A000000000000000000000000000000000000003100000000salt-formulas-2.7/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 070701000000F6000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002A00000000salt-formulas-2.7/lunmap-formula/metadata070701000000F7000081A400000000000000000000000167217D3000000065000000000000000000000000000000000000003700000000salt-formulas-2.7/lunmap-formula/metadata/metadata.yml--- summary: Salt states for managing lunmap description: Salt states for managing LUN mappings. 070701000000F8000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002000000000salt-formulas-2.7/mtail-formula070701000000F9000081A400000000000000000000000167217D3000000188000000000000000000000000000000000000002A00000000salt-formulas-2.7/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. 070701000000FA000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002900000000salt-formulas-2.7/mtail-formula/metadata070701000000FB000081A400000000000000000000000167217D3000000045000000000000000000000000000000000000003600000000salt-formulas-2.7/mtail-formula/metadata/metadata.yml--- summary: Salt states for managing mtail require: - sysconfig 070701000000FC000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002600000000salt-formulas-2.7/mtail-formula/mtail070701000000FD000081A400000000000000000000000167217D300000008A000000000000000000000000000000000000003400000000salt-formulas-2.7/mtail-formula/mtail/defaults.yamlsysconfig: args: logs: /var/log/syslog logtostderr: true port: 3903 progs: /etc/mtail syslog_use_current_year: true 070701000000FE000081A400000000000000000000000167217D3000000946000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 %} 070701000000FF000081A400000000000000000000000167217D300000038A000000000000000000000000000000000000003000000000salt-formulas-2.7/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) -%} 07070100000100000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002F00000000salt-formulas-2.7/mtail-formula/mtail/programs07070100000101000081A400000000000000000000000167217D3000001C8F000000000000000000000000000000000000003D00000000salt-formulas-2.7/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++ } } 07070100000102000081A400000000000000000000000167217D3000000324000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 07070100000103000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002400000000salt-formulas-2.7/multipath-formula07070100000104000081A400000000000000000000000167217D3000000063000000000000000000000000000000000000002E00000000salt-formulas-2.7/multipath-formula/README.md# Salt states for multipath ## Available states `multipath` Installs and configures multipathd. 07070100000105000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002D00000000salt-formulas-2.7/multipath-formula/metadata07070100000106000081A400000000000000000000000167217D300000008E000000000000000000000000000000000000003A00000000salt-formulas-2.7/multipath-formula/metadata/metadata.yml--- summary: Salt states for managing multipath description: Salt states for installing multipath-tools and managing multipath/multipathd 07070100000107000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002E00000000salt-formulas-2.7/multipath-formula/multipath07070100000108000081A400000000000000000000000167217D300000014E000000000000000000000000000000000000003C00000000salt-formulas-2.7/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 07070100000109000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003400000000salt-formulas-2.7/multipath-formula/multipath/files0707010000010A000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003800000000salt-formulas-2.7/multipath-formula/multipath/files/etc0707010000010B000081A400000000000000000000000167217D30000002CB000000000000000000000000000000000000004D00000000salt-formulas-2.7/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 %} 0707010000010C000081A400000000000000000000000167217D30000006D4000000000000000000000000000000000000003700000000salt-formulas-2.7/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 %} 0707010000010D000081A400000000000000000000000167217D30000003B4000000000000000000000000000000000000003B00000000salt-formulas-2.7/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 -%} 0707010000010E000081A400000000000000000000000167217D3000000461000000000000000000000000000000000000003800000000salt-formulas-2.7/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} -%} 0707010000010F000081A400000000000000000000000167217D3000000334000000000000000000000000000000000000003B00000000salt-formulas-2.7/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 07070100000110000081A400000000000000000000000167217D30000001A8000000000000000000000000000000000000003300000000salt-formulas-2.7/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 07070100000111000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002200000000salt-formulas-2.7/network-formula07070100000112000081A400000000000000000000000167217D3000000492000000000000000000000000000000000000002C00000000salt-formulas-2.7/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`). 07070100000113000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002B00000000salt-formulas-2.7/network-formula/metadata07070100000114000081A400000000000000000000000167217D30000000A9000000000000000000000000000000000000003800000000salt-formulas-2.7/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 07070100000115000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002A00000000salt-formulas-2.7/network-formula/network07070100000116000081A400000000000000000000000167217D30000003D8000000000000000000000000000000000000003300000000salt-formulas-2.7/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 %} 07070100000117000081A400000000000000000000000167217D3000000482000000000000000000000000000000000000003400000000salt-formulas-2.7/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'] -%} 07070100000118000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003100000000salt-formulas-2.7/network-formula/network/wicked07070100000119000081A400000000000000000000000167217D300000051E000000000000000000000000000000000000003C00000000salt-formulas-2.7/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 }} 0707010000011A000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003700000000salt-formulas-2.7/network-formula/network/wicked/files0707010000011B000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003B00000000salt-formulas-2.7/network-formula/network/wicked/files/usr0707010000011C000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004100000000salt-formulas-2.7/network-formula/network/wicked/files/usr/local0707010000011D000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000004600000000salt-formulas-2.7/network-formula/network/wicked/files/usr/local/sbin0707010000011E000081ED00000000000000000000000167217D3000001B7A000000000000000000000000000000000000005200000000salt-formulas-2.7/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 0707010000011F000081A400000000000000000000000167217D300000032F000000000000000000000000000000000000003A00000000salt-formulas-2.7/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 07070100000120000081A400000000000000000000000167217D3000001538000000000000000000000000000000000000004000000000salt-formulas-2.7/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 %} 07070100000121000081A400000000000000000000000167217D300000044C000000000000000000000000000000000000003B00000000salt-formulas-2.7/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' %} 07070100000122000081A400000000000000000000000167217D3000000602000000000000000000000000000000000000003F00000000salt-formulas-2.7/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 %} 07070100000123000081A400000000000000000000000167217D30000009A4000000000000000000000000000000000000003C00000000salt-formulas-2.7/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 %} 07070100000124000081A400000000000000000000000167217D3000000377000000000000000000000000000000000000003D00000000salt-formulas-2.7/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 07070100000125000081A400000000000000000000000167217D3000000821000000000000000000000000000000000000003100000000salt-formulas-2.7/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 07070100000126000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002400000000salt-formulas-2.7/orchestra-formula07070100000127000081A400000000000000000000000167217D3000000156000000000000000000000000000000000000002E00000000salt-formulas-2.7/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. 07070100000128000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002D00000000salt-formulas-2.7/orchestra-formula/_modules07070100000129000081A400000000000000000000000167217D3000000812000000000000000000000000000000000000003900000000salt-formulas-2.7/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) 0707010000012A000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002C00000000salt-formulas-2.7/orchestra-formula/_states0707010000012B000081A400000000000000000000000167217D30000023EC000000000000000000000000000000000000003600000000salt-formulas-2.7/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) 0707010000012C000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002D00000000salt-formulas-2.7/orchestra-formula/metadata0707010000012D000081A400000000000000000000000167217D300000008D000000000000000000000000000000000000003A00000000salt-formulas-2.7/orchestra-formula/metadata/metadata.yml--- summary: Salt orchestration helper states description: Salt helper states for the openSUSE/SUSE infrastructure orchestration states. 0707010000012E000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002E00000000salt-formulas-2.7/orchestra-formula/orchestra0707010000012F000081A400000000000000000000000167217D3000000402000000000000000000000000000000000000003B00000000salt-formulas-2.7/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 }} 07070100000130000081A400000000000000000000000167217D30000005D0000000000000000000000000000000000000003A00000000salt-formulas-2.7/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 %} 07070100000131000081A400000000000000000000000167217D30000008D5000000000000000000000000000000000000003A00000000salt-formulas-2.7/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 07070100000132000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002400000000salt-formulas-2.7/os_update-formula07070100000133000081A400000000000000000000000167217D300000008B000000000000000000000000000000000000002E00000000salt-formulas-2.7/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). 07070100000134000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002D00000000salt-formulas-2.7/os_update-formula/metadata07070100000135000081A400000000000000000000000167217D3000000032000000000000000000000000000000000000003A00000000salt-formulas-2.7/os_update-formula/metadata/metadata.yml--- summary: Salt states for managing os-update 07070100000136000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002E00000000salt-formulas-2.7/os_update-formula/os-update07070100000137000081A400000000000000000000000167217D3000000ACB000000000000000000000000000000000000003700000000salt-formulas-2.7/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 07070100000138000081A400000000000000000000000167217D30000004F1000000000000000000000000000000000000003800000000salt-formulas-2.7/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() -%} 07070100000139000081A400000000000000000000000167217D300000027C000000000000000000000000000000000000003300000000salt-formulas-2.7/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 0707010000013A000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000001C00000000salt-formulas-2.7/packaging0707010000013B000081A400000000000000000000000167217D3000000D3B000000000000000000000000000000000000002700000000salt-formulas-2.7/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)) 0707010000013C000081A400000000000000000000000167217D3000000241000000000000000000000000000000000000003E00000000salt-formulas-2.7/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') 0707010000013D000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002600000000salt-formulas-2.7/packaging/templates0707010000013E000081A400000000000000000000000167217D3000000FBF000000000000000000000000000000000000004600000000salt-formulas-2.7/packaging/templates/infrastructure-formulas.spec.j2# # spec file for package infrastructure-formulas # # Copyright (c) 2024 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 Name: infrastructure-formulas Version: 0 Release: 0 Summary: Custom Salt states for the openSUSE/SUSE infrastructures 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 Custom Salt states used in the openSUSE and SUSE infrastructures. %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 %} %prep mv %{_sourcedir}/salt-formulas-%{version}/* . %build %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" src_states="$formula/$fname" if [ ! -d "$src_states" ] then src_states="$formula/${fname//_/-}" fi src_execumodules="$formula/_modules" src_statemodules="$formula/_states" src_bin="$formula/bin" dst_metadata="%{mdir}/$fname" dst_states="%{sdir}/$fname" 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 %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 %} %changelog 0707010000013F000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002400000000salt-formulas-2.7/rebootmgr-formula07070100000140000081A400000000000000000000000167217D3000000087000000000000000000000000000000000000002E00000000salt-formulas-2.7/rebootmgr-formula/README.md# Salt states for rebootmgr ## Available states `rebootmgr` Installs and configures [rebootmgr](https://github.com/SUSE/rebootmgr). 07070100000141000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002D00000000salt-formulas-2.7/rebootmgr-formula/metadata07070100000142000081A400000000000000000000000167217D3000000032000000000000000000000000000000000000003A00000000salt-formulas-2.7/rebootmgr-formula/metadata/metadata.yml--- summary: Salt states for managing rebootmgr 07070100000143000081A400000000000000000000000167217D30000000F1000000000000000000000000000000000000003300000000salt-formulas-2.7/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' 07070100000144000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002E00000000salt-formulas-2.7/rebootmgr-formula/rebootmgr07070100000145000081A400000000000000000000000167217D30000008E0000000000000000000000000000000000000003700000000salt-formulas-2.7/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 07070100000146000081A400000000000000000000000167217D30000003E2000000000000000000000000000000000000003800000000salt-formulas-2.7/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') %} 07070100000147000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002000000000salt-formulas-2.7/redis-formula07070100000148000081A400000000000000000000000167217D3000000123000000000000000000000000000000000000002A00000000salt-formulas-2.7/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. 07070100000149000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002900000000salt-formulas-2.7/redis-formula/metadata0707010000014A000081A400000000000000000000000167217D300000002E000000000000000000000000000000000000003600000000salt-formulas-2.7/redis-formula/metadata/metadata.yml--- summary: Salt states for managing Redis 0707010000014B000081A400000000000000000000000167217D30000002D4000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 0707010000014C000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002600000000salt-formulas-2.7/redis-formula/redis0707010000014D000081A400000000000000000000000167217D30000000CF000000000000000000000000000000000000003400000000salt-formulas-2.7/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%% 0707010000014E000081A400000000000000000000000167217D30000006B0000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 %} 0707010000014F000081A400000000000000000000000167217D3000000643000000000000000000000000000000000000003000000000salt-formulas-2.7/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' } -%} 07070100000150000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002600000000salt-formulas-2.7/redis-formula/tests07070100000151000081A400000000000000000000000167217D30000007F3000000000000000000000000000000000000003400000000salt-formulas-2.7/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 07070100000152000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002200000000salt-formulas-2.7/redmine-formula07070100000153000081A400000000000000000000000167217D30000000F8000000000000000000000000000000000000002C00000000salt-formulas-2.7/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. 07070100000154000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002B00000000salt-formulas-2.7/redmine-formula/metadata07070100000155000081A400000000000000000000000167217D3000000030000000000000000000000000000000000000003800000000salt-formulas-2.7/redmine-formula/metadata/metadata.yml--- summary: Salt states for managing Redmine 07070100000156000081A400000000000000000000000167217D3000000204000000000000000000000000000000000000003100000000salt-formulas-2.7/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 07070100000157000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002A00000000salt-formulas-2.7/redmine-formula/redmine07070100000158000081A400000000000000000000000167217D300000061E000000000000000000000000000000000000003300000000salt-formulas-2.7/redmine-formula/redmine/init.sls{#- Salt state file for managing Redmine 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 '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_service {%- endif %} {%- endfor %} redmine_service: service.running: - name: redmine - enable: True - require: - pkg: redmine_packages 07070100000159000081A400000000000000000000000167217D300000038B000000000000000000000000000000000000003400000000salt-formulas-2.7/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', []) -%} 0707010000015A000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002800000000salt-formulas-2.7/redmine-formula/tests0707010000015B000081A400000000000000000000000167217D30000008EC000000000000000000000000000000000000003800000000salt-formulas-2.7/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 0707010000015C000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002000000000salt-formulas-2.7/rsync-formula0707010000015D000081A400000000000000000000000167217D3000000057000000000000000000000000000000000000002A00000000salt-formulas-2.7/rsync-formula/README.md# Salt states for rsync ## Available states `rsync` Installs and configures rsyncd. 0707010000015E000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002900000000salt-formulas-2.7/rsync-formula/metadata0707010000015F000081A400000000000000000000000167217D300000002F000000000000000000000000000000000000003600000000salt-formulas-2.7/rsync-formula/metadata/metadata.yml--- summary: Salt states for managing rsyncd 07070100000160000081A400000000000000000000000167217D3000000529000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 07070100000161000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002600000000salt-formulas-2.7/rsync-formula/rsync07070100000162000081A400000000000000000000000167217D3000000084000000000000000000000000000000000000003400000000salt-formulas-2.7/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 07070100000163000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002C00000000salt-formulas-2.7/rsync-formula/rsync/files07070100000164000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003000000000salt-formulas-2.7/rsync-formula/rsync/files/usr07070100000165000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003600000000salt-formulas-2.7/rsync-formula/rsync/files/usr/local07070100000166000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003A00000000salt-formulas-2.7/rsync-formula/rsync/files/usr/local/bin07070100000167000081ED00000000000000000000000167217D3000000743000000000000000000000000000000000000004C00000000salt-formulas-2.7/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) 07070100000168000081A400000000000000000000000167217D300000090F000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 07070100000169000081A400000000000000000000000167217D300000042A000000000000000000000000000000000000003300000000salt-formulas-2.7/rsync-formula/rsync/macros.jinja{#- Jinja macros 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/>. -#} {%- 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 -%} 0707010000016A000081A400000000000000000000000167217D3000000409000000000000000000000000000000000000003000000000salt-formulas-2.7/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 %} 0707010000016B000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002300000000salt-formulas-2.7/security-formula0707010000016C000081A400000000000000000000000167217D300000006E000000000000000000000000000000000000002D00000000salt-formulas-2.7/security-formula/README.md# Security formula This formula manages permissions and capabilities through `permissions(5)` and `chkstat`. 0707010000016D000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002C00000000salt-formulas-2.7/security-formula/metadata0707010000016E000081A400000000000000000000000167217D3000000094000000000000000000000000000000000000003900000000salt-formulas-2.7/security-formula/metadata/metadata.yml--- summary: Salt states for managing permissions description: Salt states for configuring permissions and capabilities. require: - sysconfig 0707010000016F000081A400000000000000000000000167217D300000031A000000000000000000000000000000000000003200000000salt-formulas-2.7/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 07070100000170000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002C00000000salt-formulas-2.7/security-formula/security07070100000171000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003200000000salt-formulas-2.7/security-formula/security/files07070100000172000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003600000000salt-formulas-2.7/security-formula/security/files/etc07070100000173000081A400000000000000000000000167217D3000000194000000000000000000000000000000000000004E00000000salt-formulas-2.7/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 %} 07070100000174000081A400000000000000000000000167217D30000007DC000000000000000000000000000000000000003500000000salt-formulas-2.7/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 07070100000175000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002800000000salt-formulas-2.7/smartmontools-formula07070100000176000081A400000000000000000000000167217D3000000133000000000000000000000000000000000000003200000000salt-formulas-2.7/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). 07070100000177000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003100000000salt-formulas-2.7/smartmontools-formula/metadata07070100000178000081A400000000000000000000000167217D3000000085000000000000000000000000000000000000003E00000000salt-formulas-2.7/smartmontools-formula/metadata/metadata.yml--- summary: Salt states for managing smartmontools description: Salt states for installing smartmontools and configuring smartd 07070100000179000081A400000000000000000000000167217D300000015B000000000000000000000000000000000000003700000000salt-formulas-2.7/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 0707010000017A000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003600000000salt-formulas-2.7/smartmontools-formula/smartmontools0707010000017B000081A400000000000000000000000167217D3000000084000000000000000000000000000000000000004400000000salt-formulas-2.7/smartmontools-formula/smartmontools/defaults.json{ "smartd": { "config": [ "DEFAULT -d removable -s (S/../.././03|L/../(01|02|03|04|05|06|07)/7/01)", "DEVICESCAN" ] } } 0707010000017C000081A400000000000000000000000167217D300000032F000000000000000000000000000000000000003F00000000salt-formulas-2.7/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 0707010000017D000081A400000000000000000000000167217D30000003BA000000000000000000000000000000000000004000000000salt-formulas-2.7/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', {}) -%} 0707010000017E000081A400000000000000000000000167217D30000005BC000000000000000000000000000000000000004100000000salt-formulas-2.7/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 0707010000017F000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002600000000salt-formulas-2.7/status_mail-formula07070100000180000081A400000000000000000000000167217D30000000A1000000000000000000000000000000000000003000000000salt-formulas-2.7/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). 07070100000181000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002F00000000salt-formulas-2.7/status_mail-formula/metadata07070100000182000081A400000000000000000000000167217D300000003C000000000000000000000000000000000000003C00000000salt-formulas-2.7/status_mail-formula/metadata/metadata.yml--- summary: Salt states for managing systemd-status-mail 07070100000183000081A400000000000000000000000167217D30000001C8000000000000000000000000000000000000003500000000salt-formulas-2.7/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 07070100000184000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003200000000salt-formulas-2.7/status_mail-formula/status-mail07070100000185000081A400000000000000000000000167217D3000000B7F000000000000000000000000000000000000003B00000000salt-formulas-2.7/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 %} 07070100000186000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002200000000salt-formulas-2.7/suse_ha-formula07070100000187000081A400000000000000000000000167217D3000000236000000000000000000000000000000000000002C00000000salt-formulas-2.7/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) 07070100000188000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002B00000000salt-formulas-2.7/suse_ha-formula/metadata07070100000189000081A400000000000000000000000167217D300000009A000000000000000000000000000000000000003800000000salt-formulas-2.7/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 0707010000018A000081A400000000000000000000000167217D30000016A8000000000000000000000000000000000000003100000000salt-formulas-2.7/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 # currently, only rsc_colocation is supported 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 0707010000018B000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002A00000000salt-formulas-2.7/suse_ha-formula/suse_ha0707010000018C000081A400000000000000000000000167217D30000008EF000000000000000000000000000000000000003700000000salt-formulas-2.7/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 %} 0707010000018D000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003300000000salt-formulas-2.7/suse_ha-formula/suse_ha/defaults0707010000018E000081A400000000000000000000000167217D30000000BA000000000000000000000000000000000000003800000000salt-formulas-2.7/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 0707010000018F000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003B00000000salt-formulas-2.7/suse_ha-formula/suse_ha/defaults/fencing07070100000190000081A400000000000000000000000167217D30000000FA000000000000000000000000000000000000004E00000000salt-formulas-2.7/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 07070100000191000081A400000000000000000000000167217D30000000C9000000000000000000000000000000000000004D00000000salt-formulas-2.7/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 07070100000192000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003000000000salt-formulas-2.7/suse_ha-formula/suse_ha/files07070100000193000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003400000000salt-formulas-2.7/suse_ha-formula/suse_ha/files/cib07070100000194000081A400000000000000000000000167217D3000000175000000000000000000000000000000000000004100000000salt-formulas-2.7/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' -%} 07070100000195000081A400000000000000000000000167217D300000031E000000000000000000000000000000000000004600000000salt-formulas-2.7/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 %} 07070100000196000081A400000000000000000000000167217D3000000179000000000000000000000000000000000000004600000000salt-formulas-2.7/suse_ha-formula/suse_ha/files/cib/constraint.xml.j2{%- set source = 'suse_ha/files/cib/' -%} {%- if ctype == 'rsc_colocation' -%} {%- set typetemplate = source ~ 'colocation' ~ '.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 -%} 07070100000197000081A400000000000000000000000167217D300000001A000000000000000000000000000000000000004200000000salt-formulas-2.7/suse_ha-formula/suse_ha/files/cib/footer.xml.j2 </configuration> </cib> 07070100000198000081A400000000000000000000000167217D3000000046000000000000000000000000000000000000004B00000000salt-formulas-2.7/suse_ha-formula/suse_ha/files/cib/footer_resource.xml.j2 </resources> {% include 'suse_ha/files/cib/' ~ 'footer.xml.j2' %} 07070100000199000081A400000000000000000000000167217D3000000091000000000000000000000000000000000000004200000000salt-formulas-2.7/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> 0707010000019A000081A400000000000000000000000167217D3000000046000000000000000000000000000000000000004B00000000salt-formulas-2.7/suse_ha-formula/suse_ha/files/cib/header_resource.xml.j2{%- include 'suse_ha/files/cib/' ~ 'header.xml.j2' %} <resources> 0707010000019B000081A400000000000000000000000167217D3000000176000000000000000000000000000000000000004B00000000salt-formulas-2.7/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 %} 0707010000019C000081A400000000000000000000000167217D300000046C000000000000000000000000000000000000004500000000salt-formulas-2.7/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() %} <op name="{{ op }}" timeout="{{ opconfig ['timeout'] }}" interval="{{ opconfig ['interval'] }}" id="{{ resource_id }}-{{ op }}-{{ opconfig ['interval'] }}"/> {%- endfor %} </operations> {%- endif %} {%- include source ~ 'meta_attributes.xml.j2' %} </primitive> {%- endfor %} 0707010000019D000081A400000000000000000000000167217D30000000B6000000000000000000000000000000000000004400000000salt-formulas-2.7/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' %} 0707010000019E000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003400000000salt-formulas-2.7/suse_ha-formula/suse_ha/files/etc0707010000019F000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003D00000000salt-formulas-2.7/suse_ha-formula/suse_ha/files/etc/corosync070701000001A0000081A400000000000000000000000167217D3000000494000000000000000000000000000000000000004B00000000salt-formulas-2.7/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 } 070701000001A1000081A400000000000000000000000167217D3000000322000000000000000000000000000000000000003300000000salt-formulas-2.7/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 070701000001A2000081A400000000000000000000000167217D300000035F000000000000000000000000000000000000003500000000salt-formulas-2.7/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 070701000001A3000081A400000000000000000000000167217D3000001247000000000000000000000000000000000000003700000000salt-formulas-2.7/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, 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 }} 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 -%} 070701000001A4000081A400000000000000000000000167217D3000000B4C000000000000000000000000000000000000003400000000salt-formulas-2.7/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) -%} 070701000001A5000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003400000000salt-formulas-2.7/suse_ha-formula/suse_ha/pacemaker070701000001A6000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003C00000000salt-formulas-2.7/suse_ha-formula/suse_ha/pacemaker/fencing070701000001A7000081A400000000000000000000000167217D300000070F000000000000000000000000000000000000004E00000000salt-formulas-2.7/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 %} 070701000001A8000081A400000000000000000000000167217D300000072A000000000000000000000000000000000000004D00000000salt-formulas-2.7/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 %} 070701000001A9000081A400000000000000000000000167217D3000001237000000000000000000000000000000000000003D00000000salt-formulas-2.7/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 %} 070701000001AA000081A400000000000000000000000167217D300000048E000000000000000000000000000000000000003700000000salt-formulas-2.7/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 %} 070701000001AB000081A400000000000000000000000167217D3000000766000000000000000000000000000000000000003800000000salt-formulas-2.7/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('score'), config.get('resources', []), config.get('sets', {}), ) }} {%- endfor %} 070701000001AC000081A400000000000000000000000167217D3000000C30000000000000000000000000000000000000003200000000salt-formulas-2.7/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 -#} 070701000001AD000081A400000000000000000000000167217D3000000476000000000000000000000000000000000000003600000000salt-formulas-2.7/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 %} 070701000001AE000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002400000000salt-formulas-2.7/sysconfig-formula070701000001AF000081A400000000000000000000000167217D300000005E000000000000000000000000000000000000002E00000000salt-formulas-2.7/sysconfig-formula/README.mdThis is a library formula containing helper code for managing fillup handled sysconfig files. 070701000001B0000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002D00000000salt-formulas-2.7/sysconfig-formula/_modules070701000001B1000081A400000000000000000000000167217D30000009D0000000000000000000000000000000000000003F00000000salt-formulas-2.7/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 070701000001B2000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002C00000000salt-formulas-2.7/sysconfig-formula/_states070701000001B3000081A400000000000000000000000167217D30000012BC000000000000000000000000000000000000003E00000000salt-formulas-2.7/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 070701000001B4000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002D00000000salt-formulas-2.7/sysconfig-formula/metadata070701000001B5000081A400000000000000000000000167217D3000000085000000000000000000000000000000000000003A00000000salt-formulas-2.7/sysconfig-formula/metadata/metadata.yml--- summary: Salt helpers for sysconfig description: Library formula containing helper code for managing fillup/sysconfig files. 070701000001B6000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002000000000salt-formulas-2.7/tayga-formula070701000001B7000081A400000000000000000000000167217D3000000076000000000000000000000000000000000000002A00000000salt-formulas-2.7/tayga-formula/README.md# Salt states for TAYGA ## Available states `tayga` Installs and configures [TAYGA](http://www.litech.org/tayga/). 070701000001B8000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002900000000salt-formulas-2.7/tayga-formula/metadata070701000001B9000081A400000000000000000000000167217D300000006D000000000000000000000000000000000000003600000000salt-formulas-2.7/tayga-formula/metadata/metadata.yml--- summary: Salt states for managing TAYGA description: Salt states for managing the TAYGA NAT64 daemon 070701000001BA000081A400000000000000000000000167217D300000015C000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 070701000001BB000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002600000000salt-formulas-2.7/tayga-formula/tayga070701000001BC000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002C00000000salt-formulas-2.7/tayga-formula/tayga/files070701000001BD000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000003000000000salt-formulas-2.7/tayga-formula/tayga/files/etc070701000001BE000081A400000000000000000000000167217D30000002D1000000000000000000000000000000000000003E00000000salt-formulas-2.7/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 %} 070701000001BF000081A400000000000000000000000167217D30000004DA000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 %} 070701000001C0000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000001700000000salt-formulas-2.7/test070701000001C1000081A400000000000000000000000167217D3000000494000000000000000000000000000000000000002100000000salt-formulas-2.7/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. 070701000001C2000081A400000000000000000000000167217D30000008AC000000000000000000000000000000000000002F00000000salt-formulas-2.7/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 070701000001C3000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000001F00000000salt-formulas-2.7/test/scripts070701000001C4000081ED00000000000000000000000167217D30000007C8000000000000000000000000000000000000002800000000salt-formulas-2.7/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 070701000001C5000081ED00000000000000000000000167217D3000000432000000000000000000000000000000000000002B00000000salt-formulas-2.7/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 070701000001C6000081A400000000000000000000000167217D300000037C000000000000000000000000000000000000002400000000salt-formulas-2.7/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 070701000001C7000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002100000000salt-formulas-2.7/zypper-formula070701000001C8000081A400000000000000000000000167217D3000000288000000000000000000000000000000000000002900000000salt-formulas-2.7/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. 070701000001C9000081A400000000000000000000000167217D30000001E3000000000000000000000000000000000000002C00000000salt-formulas-2.7/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 070701000001CA000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002A00000000salt-formulas-2.7/zypper-formula/metadata070701000001CB000081A400000000000000000000000167217D300000009B000000000000000000000000000000000000003700000000salt-formulas-2.7/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 070701000001CC000081A400000000000000000000000167217D30000001C0000000000000000000000000000000000000003000000000salt-formulas-2.7/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 070701000001CD000041ED00000000000000000000000267217D3000000000000000000000000000000000000000000000002800000000salt-formulas-2.7/zypper-formula/zypper070701000001CE000081A400000000000000000000000167217D3000000317000000000000000000000000000000000000003300000000salt-formulas-2.7/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 %} 070701000001CF000081A400000000000000000000000167217D30000000BE000000000000000000000000000000000000003100000000salt-formulas-2.7/zypper-formula/zypper/init.slsinclude: - zypper.config - zypper.repositories {%- if grains['osmajorrelease'] < 15 %} - zypper.packages_legacy {%- else %} - zypper.packages {%- endif %} - zypper.variables 070701000001D0000081A400000000000000000000000167217D3000000480000000000000000000000000000000000000003500000000salt-formulas-2.7/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 %} 070701000001D1000081A400000000000000000000000167217D300000016A000000000000000000000000000000000000003C00000000salt-formulas-2.7/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 %} 070701000001D2000081A400000000000000000000000167217D3000000308000000000000000000000000000000000000003900000000salt-formulas-2.7/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 %} 070701000001D3000081A400000000000000000000000167217D30000002F4000000000000000000000000000000000000003600000000salt-formulas-2.7/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!!!989 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor