chore(ops/kontemplate): drop
We don't use this. Change-Id: I3e6825521928f444a50426e493f448400c752b4e Reviewed-on: https://cl.snix.dev/c/snix/+/30183 Autosubmit: Florian Klink <flokli@flokli.de> Reviewed-by: Ryan Lahfa <masterancpp@gmail.com> Tested-by: besadii
This commit is contained in:
		
							parent
							
								
									dd7372782a
								
							
						
					
					
						commit
						29b4d0367b
					
				
					 48 changed files with 0 additions and 3422 deletions
				
			
		
							
								
								
									
										2
									
								
								ops/kontemplate/.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								ops/kontemplate/.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -1,2 +0,0 @@ | |||
| .idea/ | ||||
| release/ | ||||
|  | @ -1,674 +0,0 @@ | |||
|                     GNU GENERAL PUBLIC LICENSE | ||||
|                        Version 3, 29 June 2007 | ||||
| 
 | ||||
|  Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> | ||||
|  Everyone is permitted to copy and distribute verbatim copies | ||||
|  of this license document, but changing it is not allowed. | ||||
| 
 | ||||
|                             Preamble | ||||
| 
 | ||||
|   The GNU General Public License is a free, copyleft license for | ||||
| software and other kinds of works. | ||||
| 
 | ||||
|   The licenses for most software and other practical works are designed | ||||
| to take away your freedom to share and change the works.  By contrast, | ||||
| the GNU General Public License is intended to guarantee your freedom to | ||||
| share and change all versions of a program--to make sure it remains free | ||||
| software for all its users.  We, the Free Software Foundation, use the | ||||
| GNU General Public License for most of our software; it applies also to | ||||
| any other work released this way by its authors.  You can apply it to | ||||
| your programs, too. | ||||
| 
 | ||||
|   When we speak of free software, we are referring to freedom, not | ||||
| price.  Our General Public Licenses are designed to make sure that you | ||||
| have the freedom to distribute copies of free software (and charge for | ||||
| them if you wish), that you receive source code or can get it if you | ||||
| want it, that you can change the software or use pieces of it in new | ||||
| free programs, and that you know you can do these things. | ||||
| 
 | ||||
|   To protect your rights, we need to prevent others from denying you | ||||
| these rights or asking you to surrender the rights.  Therefore, you have | ||||
| certain responsibilities if you distribute copies of the software, or if | ||||
| you modify it: responsibilities to respect the freedom of others. | ||||
| 
 | ||||
|   For example, if you distribute copies of such a program, whether | ||||
| gratis or for a fee, you must pass on to the recipients the same | ||||
| freedoms that you received.  You must make sure that they, too, receive | ||||
| or can get the source code.  And you must show them these terms so they | ||||
| know their rights. | ||||
| 
 | ||||
|   Developers that use the GNU GPL protect your rights with two steps: | ||||
| (1) assert copyright on the software, and (2) offer you this License | ||||
| giving you legal permission to copy, distribute and/or modify it. | ||||
| 
 | ||||
|   For the developers' and authors' protection, the GPL clearly explains | ||||
| that there is no warranty for this free software.  For both users' and | ||||
| authors' sake, the GPL requires that modified versions be marked as | ||||
| changed, so that their problems will not be attributed erroneously to | ||||
| authors of previous versions. | ||||
| 
 | ||||
|   Some devices are designed to deny users access to install or run | ||||
| modified versions of the software inside them, although the manufacturer | ||||
| can do so.  This is fundamentally incompatible with the aim of | ||||
| protecting users' freedom to change the software.  The systematic | ||||
| pattern of such abuse occurs in the area of products for individuals to | ||||
| use, which is precisely where it is most unacceptable.  Therefore, we | ||||
| have designed this version of the GPL to prohibit the practice for those | ||||
| products.  If such problems arise substantially in other domains, we | ||||
| stand ready to extend this provision to those domains in future versions | ||||
| of the GPL, as needed to protect the freedom of users. | ||||
| 
 | ||||
|   Finally, every program is threatened constantly by software patents. | ||||
| States should not allow patents to restrict development and use of | ||||
| software on general-purpose computers, but in those that do, we wish to | ||||
| avoid the special danger that patents applied to a free program could | ||||
| make it effectively proprietary.  To prevent this, the GPL assures that | ||||
| patents cannot be used to render the program non-free. | ||||
| 
 | ||||
|   The precise terms and conditions for copying, distribution and | ||||
| modification follow. | ||||
| 
 | ||||
|                        TERMS AND CONDITIONS | ||||
| 
 | ||||
|   0. Definitions. | ||||
| 
 | ||||
|   "This License" refers to version 3 of the GNU General Public License. | ||||
| 
 | ||||
|   "Copyright" also means copyright-like laws that apply to other kinds of | ||||
| works, such as semiconductor masks. | ||||
| 
 | ||||
|   "The Program" refers to any copyrightable work licensed under this | ||||
| License.  Each licensee is addressed as "you".  "Licensees" and | ||||
| "recipients" may be individuals or organizations. | ||||
| 
 | ||||
|   To "modify" a work means to copy from or adapt all or part of the work | ||||
| in a fashion requiring copyright permission, other than the making of an | ||||
| exact copy.  The resulting work is called a "modified version" of the | ||||
| earlier work or a work "based on" the earlier work. | ||||
| 
 | ||||
|   A "covered work" means either the unmodified Program or a work based | ||||
| on the Program. | ||||
| 
 | ||||
|   To "propagate" a work means to do anything with it that, without | ||||
| permission, would make you directly or secondarily liable for | ||||
| infringement under applicable copyright law, except executing it on a | ||||
| computer or modifying a private copy.  Propagation includes copying, | ||||
| distribution (with or without modification), making available to the | ||||
| public, and in some countries other activities as well. | ||||
| 
 | ||||
|   To "convey" a work means any kind of propagation that enables other | ||||
| parties to make or receive copies.  Mere interaction with a user through | ||||
| a computer network, with no transfer of a copy, is not conveying. | ||||
| 
 | ||||
|   An interactive user interface displays "Appropriate Legal Notices" | ||||
| to the extent that it includes a convenient and prominently visible | ||||
| feature that (1) displays an appropriate copyright notice, and (2) | ||||
| tells the user that there is no warranty for the work (except to the | ||||
| extent that warranties are provided), that licensees may convey the | ||||
| work under this License, and how to view a copy of this License.  If | ||||
| the interface presents a list of user commands or options, such as a | ||||
| menu, a prominent item in the list meets this criterion. | ||||
| 
 | ||||
|   1. Source Code. | ||||
| 
 | ||||
|   The "source code" for a work means the preferred form of the work | ||||
| for making modifications to it.  "Object code" means any non-source | ||||
| form of a work. | ||||
| 
 | ||||
|   A "Standard Interface" means an interface that either is an official | ||||
| standard defined by a recognized standards body, or, in the case of | ||||
| interfaces specified for a particular programming language, one that | ||||
| is widely used among developers working in that language. | ||||
| 
 | ||||
|   The "System Libraries" of an executable work include anything, other | ||||
| than the work as a whole, that (a) is included in the normal form of | ||||
| packaging a Major Component, but which is not part of that Major | ||||
| Component, and (b) serves only to enable use of the work with that | ||||
| Major Component, or to implement a Standard Interface for which an | ||||
| implementation is available to the public in source code form.  A | ||||
| "Major Component", in this context, means a major essential component | ||||
| (kernel, window system, and so on) of the specific operating system | ||||
| (if any) on which the executable work runs, or a compiler used to | ||||
| produce the work, or an object code interpreter used to run it. | ||||
| 
 | ||||
|   The "Corresponding Source" for a work in object code form means all | ||||
| the source code needed to generate, install, and (for an executable | ||||
| work) run the object code and to modify the work, including scripts to | ||||
| control those activities.  However, it does not include the work's | ||||
| System Libraries, or general-purpose tools or generally available free | ||||
| programs which are used unmodified in performing those activities but | ||||
| which are not part of the work.  For example, Corresponding Source | ||||
| includes interface definition files associated with source files for | ||||
| the work, and the source code for shared libraries and dynamically | ||||
| linked subprograms that the work is specifically designed to require, | ||||
| such as by intimate data communication or control flow between those | ||||
| subprograms and other parts of the work. | ||||
| 
 | ||||
|   The Corresponding Source need not include anything that users | ||||
| can regenerate automatically from other parts of the Corresponding | ||||
| Source. | ||||
| 
 | ||||
|   The Corresponding Source for a work in source code form is that | ||||
| same work. | ||||
| 
 | ||||
|   2. Basic Permissions. | ||||
| 
 | ||||
|   All rights granted under this License are granted for the term of | ||||
| copyright on the Program, and are irrevocable provided the stated | ||||
| conditions are met.  This License explicitly affirms your unlimited | ||||
| permission to run the unmodified Program.  The output from running a | ||||
| covered work is covered by this License only if the output, given its | ||||
| content, constitutes a covered work.  This License acknowledges your | ||||
| rights of fair use or other equivalent, as provided by copyright law. | ||||
| 
 | ||||
|   You may make, run and propagate covered works that you do not | ||||
| convey, without conditions so long as your license otherwise remains | ||||
| in force.  You may convey covered works to others for the sole purpose | ||||
| of having them make modifications exclusively for you, or provide you | ||||
| with facilities for running those works, provided that you comply with | ||||
| the terms of this License in conveying all material for which you do | ||||
| not control copyright.  Those thus making or running the covered works | ||||
| for you must do so exclusively on your behalf, under your direction | ||||
| and control, on terms that prohibit them from making any copies of | ||||
| your copyrighted material outside their relationship with you. | ||||
| 
 | ||||
|   Conveying under any other circumstances is permitted solely under | ||||
| the conditions stated below.  Sublicensing is not allowed; section 10 | ||||
| makes it unnecessary. | ||||
| 
 | ||||
|   3. Protecting Users' Legal Rights From Anti-Circumvention Law. | ||||
| 
 | ||||
|   No covered work shall be deemed part of an effective technological | ||||
| measure under any applicable law fulfilling obligations under article | ||||
| 11 of the WIPO copyright treaty adopted on 20 December 1996, or | ||||
| similar laws prohibiting or restricting circumvention of such | ||||
| measures. | ||||
| 
 | ||||
|   When you convey a covered work, you waive any legal power to forbid | ||||
| circumvention of technological measures to the extent such circumvention | ||||
| is effected by exercising rights under this License with respect to | ||||
| the covered work, and you disclaim any intention to limit operation or | ||||
| modification of the work as a means of enforcing, against the work's | ||||
| users, your or third parties' legal rights to forbid circumvention of | ||||
| technological measures. | ||||
| 
 | ||||
|   4. Conveying Verbatim Copies. | ||||
| 
 | ||||
|   You may convey verbatim copies of the Program's source code as you | ||||
| receive it, in any medium, provided that you conspicuously and | ||||
| appropriately publish on each copy an appropriate copyright notice; | ||||
| keep intact all notices stating that this License and any | ||||
| non-permissive terms added in accord with section 7 apply to the code; | ||||
| keep intact all notices of the absence of any warranty; and give all | ||||
| recipients a copy of this License along with the Program. | ||||
| 
 | ||||
|   You may charge any price or no price for each copy that you convey, | ||||
| and you may offer support or warranty protection for a fee. | ||||
| 
 | ||||
|   5. Conveying Modified Source Versions. | ||||
| 
 | ||||
|   You may convey a work based on the Program, or the modifications to | ||||
| produce it from the Program, in the form of source code under the | ||||
| terms of section 4, provided that you also meet all of these conditions: | ||||
| 
 | ||||
|     a) The work must carry prominent notices stating that you modified | ||||
|     it, and giving a relevant date. | ||||
| 
 | ||||
|     b) The work must carry prominent notices stating that it is | ||||
|     released under this License and any conditions added under section | ||||
|     7.  This requirement modifies the requirement in section 4 to | ||||
|     "keep intact all notices". | ||||
| 
 | ||||
|     c) You must license the entire work, as a whole, under this | ||||
|     License to anyone who comes into possession of a copy.  This | ||||
|     License will therefore apply, along with any applicable section 7 | ||||
|     additional terms, to the whole of the work, and all its parts, | ||||
|     regardless of how they are packaged.  This License gives no | ||||
|     permission to license the work in any other way, but it does not | ||||
|     invalidate such permission if you have separately received it. | ||||
| 
 | ||||
|     d) If the work has interactive user interfaces, each must display | ||||
|     Appropriate Legal Notices; however, if the Program has interactive | ||||
|     interfaces that do not display Appropriate Legal Notices, your | ||||
|     work need not make them do so. | ||||
| 
 | ||||
|   A compilation of a covered work with other separate and independent | ||||
| works, which are not by their nature extensions of the covered work, | ||||
| and which are not combined with it such as to form a larger program, | ||||
| in or on a volume of a storage or distribution medium, is called an | ||||
| "aggregate" if the compilation and its resulting copyright are not | ||||
| used to limit the access or legal rights of the compilation's users | ||||
| beyond what the individual works permit.  Inclusion of a covered work | ||||
| in an aggregate does not cause this License to apply to the other | ||||
| parts of the aggregate. | ||||
| 
 | ||||
|   6. Conveying Non-Source Forms. | ||||
| 
 | ||||
|   You may convey a covered work in object code form under the terms | ||||
| of sections 4 and 5, provided that you also convey the | ||||
| machine-readable Corresponding Source under the terms of this License, | ||||
| in one of these ways: | ||||
| 
 | ||||
|     a) Convey the object code in, or embodied in, a physical product | ||||
|     (including a physical distribution medium), accompanied by the | ||||
|     Corresponding Source fixed on a durable physical medium | ||||
|     customarily used for software interchange. | ||||
| 
 | ||||
|     b) Convey the object code in, or embodied in, a physical product | ||||
|     (including a physical distribution medium), accompanied by a | ||||
|     written offer, valid for at least three years and valid for as | ||||
|     long as you offer spare parts or customer support for that product | ||||
|     model, to give anyone who possesses the object code either (1) a | ||||
|     copy of the Corresponding Source for all the software in the | ||||
|     product that is covered by this License, on a durable physical | ||||
|     medium customarily used for software interchange, for a price no | ||||
|     more than your reasonable cost of physically performing this | ||||
|     conveying of source, or (2) access to copy the | ||||
|     Corresponding Source from a network server at no charge. | ||||
| 
 | ||||
|     c) Convey individual copies of the object code with a copy of the | ||||
|     written offer to provide the Corresponding Source.  This | ||||
|     alternative is allowed only occasionally and noncommercially, and | ||||
|     only if you received the object code with such an offer, in accord | ||||
|     with subsection 6b. | ||||
| 
 | ||||
|     d) Convey the object code by offering access from a designated | ||||
|     place (gratis or for a charge), and offer equivalent access to the | ||||
|     Corresponding Source in the same way through the same place at no | ||||
|     further charge.  You need not require recipients to copy the | ||||
|     Corresponding Source along with the object code.  If the place to | ||||
|     copy the object code is a network server, the Corresponding Source | ||||
|     may be on a different server (operated by you or a third party) | ||||
|     that supports equivalent copying facilities, provided you maintain | ||||
|     clear directions next to the object code saying where to find the | ||||
|     Corresponding Source.  Regardless of what server hosts the | ||||
|     Corresponding Source, you remain obligated to ensure that it is | ||||
|     available for as long as needed to satisfy these requirements. | ||||
| 
 | ||||
|     e) Convey the object code using peer-to-peer transmission, provided | ||||
|     you inform other peers where the object code and Corresponding | ||||
|     Source of the work are being offered to the general public at no | ||||
|     charge under subsection 6d. | ||||
| 
 | ||||
|   A separable portion of the object code, whose source code is excluded | ||||
| from the Corresponding Source as a System Library, need not be | ||||
| included in conveying the object code work. | ||||
| 
 | ||||
|   A "User Product" is either (1) a "consumer product", which means any | ||||
| tangible personal property which is normally used for personal, family, | ||||
| or household purposes, or (2) anything designed or sold for incorporation | ||||
| into a dwelling.  In determining whether a product is a consumer product, | ||||
| doubtful cases shall be resolved in favor of coverage.  For a particular | ||||
| product received by a particular user, "normally used" refers to a | ||||
| typical or common use of that class of product, regardless of the status | ||||
| of the particular user or of the way in which the particular user | ||||
| actually uses, or expects or is expected to use, the product.  A product | ||||
| is a consumer product regardless of whether the product has substantial | ||||
| commercial, industrial or non-consumer uses, unless such uses represent | ||||
| the only significant mode of use of the product. | ||||
| 
 | ||||
|   "Installation Information" for a User Product means any methods, | ||||
| procedures, authorization keys, or other information required to install | ||||
| and execute modified versions of a covered work in that User Product from | ||||
| a modified version of its Corresponding Source.  The information must | ||||
| suffice to ensure that the continued functioning of the modified object | ||||
| code is in no case prevented or interfered with solely because | ||||
| modification has been made. | ||||
| 
 | ||||
|   If you convey an object code work under this section in, or with, or | ||||
| specifically for use in, a User Product, and the conveying occurs as | ||||
| part of a transaction in which the right of possession and use of the | ||||
| User Product is transferred to the recipient in perpetuity or for a | ||||
| fixed term (regardless of how the transaction is characterized), the | ||||
| Corresponding Source conveyed under this section must be accompanied | ||||
| by the Installation Information.  But this requirement does not apply | ||||
| if neither you nor any third party retains the ability to install | ||||
| modified object code on the User Product (for example, the work has | ||||
| been installed in ROM). | ||||
| 
 | ||||
|   The requirement to provide Installation Information does not include a | ||||
| requirement to continue to provide support service, warranty, or updates | ||||
| for a work that has been modified or installed by the recipient, or for | ||||
| the User Product in which it has been modified or installed.  Access to a | ||||
| network may be denied when the modification itself materially and | ||||
| adversely affects the operation of the network or violates the rules and | ||||
| protocols for communication across the network. | ||||
| 
 | ||||
|   Corresponding Source conveyed, and Installation Information provided, | ||||
| in accord with this section must be in a format that is publicly | ||||
| documented (and with an implementation available to the public in | ||||
| source code form), and must require no special password or key for | ||||
| unpacking, reading or copying. | ||||
| 
 | ||||
|   7. Additional Terms. | ||||
| 
 | ||||
|   "Additional permissions" are terms that supplement the terms of this | ||||
| License by making exceptions from one or more of its conditions. | ||||
| Additional permissions that are applicable to the entire Program shall | ||||
| be treated as though they were included in this License, to the extent | ||||
| that they are valid under applicable law.  If additional permissions | ||||
| apply only to part of the Program, that part may be used separately | ||||
| under those permissions, but the entire Program remains governed by | ||||
| this License without regard to the additional permissions. | ||||
| 
 | ||||
|   When you convey a copy of a covered work, you may at your option | ||||
| remove any additional permissions from that copy, or from any part of | ||||
| it.  (Additional permissions may be written to require their own | ||||
| removal in certain cases when you modify the work.)  You may place | ||||
| additional permissions on material, added by you to a covered work, | ||||
| for which you have or can give appropriate copyright permission. | ||||
| 
 | ||||
|   Notwithstanding any other provision of this License, for material you | ||||
| add to a covered work, you may (if authorized by the copyright holders of | ||||
| that material) supplement the terms of this License with terms: | ||||
| 
 | ||||
|     a) Disclaiming warranty or limiting liability differently from the | ||||
|     terms of sections 15 and 16 of this License; or | ||||
| 
 | ||||
|     b) Requiring preservation of specified reasonable legal notices or | ||||
|     author attributions in that material or in the Appropriate Legal | ||||
|     Notices displayed by works containing it; or | ||||
| 
 | ||||
|     c) Prohibiting misrepresentation of the origin of that material, or | ||||
|     requiring that modified versions of such material be marked in | ||||
|     reasonable ways as different from the original version; or | ||||
| 
 | ||||
|     d) Limiting the use for publicity purposes of names of licensors or | ||||
|     authors of the material; or | ||||
| 
 | ||||
|     e) Declining to grant rights under trademark law for use of some | ||||
|     trade names, trademarks, or service marks; or | ||||
| 
 | ||||
|     f) Requiring indemnification of licensors and authors of that | ||||
|     material by anyone who conveys the material (or modified versions of | ||||
|     it) with contractual assumptions of liability to the recipient, for | ||||
|     any liability that these contractual assumptions directly impose on | ||||
|     those licensors and authors. | ||||
| 
 | ||||
|   All other non-permissive additional terms are considered "further | ||||
| restrictions" within the meaning of section 10.  If the Program as you | ||||
| received it, or any part of it, contains a notice stating that it is | ||||
| governed by this License along with a term that is a further | ||||
| restriction, you may remove that term.  If a license document contains | ||||
| a further restriction but permits relicensing or conveying under this | ||||
| License, you may add to a covered work material governed by the terms | ||||
| of that license document, provided that the further restriction does | ||||
| not survive such relicensing or conveying. | ||||
| 
 | ||||
|   If you add terms to a covered work in accord with this section, you | ||||
| must place, in the relevant source files, a statement of the | ||||
| additional terms that apply to those files, or a notice indicating | ||||
| where to find the applicable terms. | ||||
| 
 | ||||
|   Additional terms, permissive or non-permissive, may be stated in the | ||||
| form of a separately written license, or stated as exceptions; | ||||
| the above requirements apply either way. | ||||
| 
 | ||||
|   8. Termination. | ||||
| 
 | ||||
|   You may not propagate or modify a covered work except as expressly | ||||
| provided under this License.  Any attempt otherwise to propagate or | ||||
| modify it is void, and will automatically terminate your rights under | ||||
| this License (including any patent licenses granted under the third | ||||
| paragraph of section 11). | ||||
| 
 | ||||
|   However, if you cease all violation of this License, then your | ||||
| license from a particular copyright holder is reinstated (a) | ||||
| provisionally, unless and until the copyright holder explicitly and | ||||
| finally terminates your license, and (b) permanently, if the copyright | ||||
| holder fails to notify you of the violation by some reasonable means | ||||
| prior to 60 days after the cessation. | ||||
| 
 | ||||
|   Moreover, your license from a particular copyright holder is | ||||
| reinstated permanently if the copyright holder notifies you of the | ||||
| violation by some reasonable means, this is the first time you have | ||||
| received notice of violation of this License (for any work) from that | ||||
| copyright holder, and you cure the violation prior to 30 days after | ||||
| your receipt of the notice. | ||||
| 
 | ||||
|   Termination of your rights under this section does not terminate the | ||||
| licenses of parties who have received copies or rights from you under | ||||
| this License.  If your rights have been terminated and not permanently | ||||
| reinstated, you do not qualify to receive new licenses for the same | ||||
| material under section 10. | ||||
| 
 | ||||
|   9. Acceptance Not Required for Having Copies. | ||||
| 
 | ||||
|   You are not required to accept this License in order to receive or | ||||
| run a copy of the Program.  Ancillary propagation of a covered work | ||||
| occurring solely as a consequence of using peer-to-peer transmission | ||||
| to receive a copy likewise does not require acceptance.  However, | ||||
| nothing other than this License grants you permission to propagate or | ||||
| modify any covered work.  These actions infringe copyright if you do | ||||
| not accept this License.  Therefore, by modifying or propagating a | ||||
| covered work, you indicate your acceptance of this License to do so. | ||||
| 
 | ||||
|   10. Automatic Licensing of Downstream Recipients. | ||||
| 
 | ||||
|   Each time you convey a covered work, the recipient automatically | ||||
| receives a license from the original licensors, to run, modify and | ||||
| propagate that work, subject to this License.  You are not responsible | ||||
| for enforcing compliance by third parties with this License. | ||||
| 
 | ||||
|   An "entity transaction" is a transaction transferring control of an | ||||
| organization, or substantially all assets of one, or subdividing an | ||||
| organization, or merging organizations.  If propagation of a covered | ||||
| work results from an entity transaction, each party to that | ||||
| transaction who receives a copy of the work also receives whatever | ||||
| licenses to the work the party's predecessor in interest had or could | ||||
| give under the previous paragraph, plus a right to possession of the | ||||
| Corresponding Source of the work from the predecessor in interest, if | ||||
| the predecessor has it or can get it with reasonable efforts. | ||||
| 
 | ||||
|   You may not impose any further restrictions on the exercise of the | ||||
| rights granted or affirmed under this License.  For example, you may | ||||
| not impose a license fee, royalty, or other charge for exercise of | ||||
| rights granted under this License, and you may not initiate litigation | ||||
| (including a cross-claim or counterclaim in a lawsuit) alleging that | ||||
| any patent claim is infringed by making, using, selling, offering for | ||||
| sale, or importing the Program or any portion of it. | ||||
| 
 | ||||
|   11. Patents. | ||||
| 
 | ||||
|   A "contributor" is a copyright holder who authorizes use under this | ||||
| License of the Program or a work on which the Program is based.  The | ||||
| work thus licensed is called the contributor's "contributor version". | ||||
| 
 | ||||
|   A contributor's "essential patent claims" are all patent claims | ||||
| owned or controlled by the contributor, whether already acquired or | ||||
| hereafter acquired, that would be infringed by some manner, permitted | ||||
| by this License, of making, using, or selling its contributor version, | ||||
| but do not include claims that would be infringed only as a | ||||
| consequence of further modification of the contributor version.  For | ||||
| purposes of this definition, "control" includes the right to grant | ||||
| patent sublicenses in a manner consistent with the requirements of | ||||
| this License. | ||||
| 
 | ||||
|   Each contributor grants you a non-exclusive, worldwide, royalty-free | ||||
| patent license under the contributor's essential patent claims, to | ||||
| make, use, sell, offer for sale, import and otherwise run, modify and | ||||
| propagate the contents of its contributor version. | ||||
| 
 | ||||
|   In the following three paragraphs, a "patent license" is any express | ||||
| agreement or commitment, however denominated, not to enforce a patent | ||||
| (such as an express permission to practice a patent or covenant not to | ||||
| sue for patent infringement).  To "grant" such a patent license to a | ||||
| party means to make such an agreement or commitment not to enforce a | ||||
| patent against the party. | ||||
| 
 | ||||
|   If you convey a covered work, knowingly relying on a patent license, | ||||
| and the Corresponding Source of the work is not available for anyone | ||||
| to copy, free of charge and under the terms of this License, through a | ||||
| publicly available network server or other readily accessible means, | ||||
| then you must either (1) cause the Corresponding Source to be so | ||||
| available, or (2) arrange to deprive yourself of the benefit of the | ||||
| patent license for this particular work, or (3) arrange, in a manner | ||||
| consistent with the requirements of this License, to extend the patent | ||||
| license to downstream recipients.  "Knowingly relying" means you have | ||||
| actual knowledge that, but for the patent license, your conveying the | ||||
| covered work in a country, or your recipient's use of the covered work | ||||
| in a country, would infringe one or more identifiable patents in that | ||||
| country that you have reason to believe are valid. | ||||
| 
 | ||||
|   If, pursuant to or in connection with a single transaction or | ||||
| arrangement, you convey, or propagate by procuring conveyance of, a | ||||
| covered work, and grant a patent license to some of the parties | ||||
| receiving the covered work authorizing them to use, propagate, modify | ||||
| or convey a specific copy of the covered work, then the patent license | ||||
| you grant is automatically extended to all recipients of the covered | ||||
| work and works based on it. | ||||
| 
 | ||||
|   A patent license is "discriminatory" if it does not include within | ||||
| the scope of its coverage, prohibits the exercise of, or is | ||||
| conditioned on the non-exercise of one or more of the rights that are | ||||
| specifically granted under this License.  You may not convey a covered | ||||
| work if you are a party to an arrangement with a third party that is | ||||
| in the business of distributing software, under which you make payment | ||||
| to the third party based on the extent of your activity of conveying | ||||
| the work, and under which the third party grants, to any of the | ||||
| parties who would receive the covered work from you, a discriminatory | ||||
| patent license (a) in connection with copies of the covered work | ||||
| conveyed by you (or copies made from those copies), or (b) primarily | ||||
| for and in connection with specific products or compilations that | ||||
| contain the covered work, unless you entered into that arrangement, | ||||
| or that patent license was granted, prior to 28 March 2007. | ||||
| 
 | ||||
|   Nothing in this License shall be construed as excluding or limiting | ||||
| any implied license or other defenses to infringement that may | ||||
| otherwise be available to you under applicable patent law. | ||||
| 
 | ||||
|   12. No Surrender of Others' Freedom. | ||||
| 
 | ||||
|   If conditions are imposed on you (whether by court order, agreement or | ||||
| otherwise) that contradict the conditions of this License, they do not | ||||
| excuse you from the conditions of this License.  If you cannot convey a | ||||
| covered work so as to satisfy simultaneously your obligations under this | ||||
| License and any other pertinent obligations, then as a consequence you may | ||||
| not convey it at all.  For example, if you agree to terms that obligate you | ||||
| to collect a royalty for further conveying from those to whom you convey | ||||
| the Program, the only way you could satisfy both those terms and this | ||||
| License would be to refrain entirely from conveying the Program. | ||||
| 
 | ||||
|   13. Use with the GNU Affero General Public License. | ||||
| 
 | ||||
|   Notwithstanding any other provision of this License, you have | ||||
| permission to link or combine any covered work with a work licensed | ||||
| under version 3 of the GNU Affero General Public License into a single | ||||
| combined work, and to convey the resulting work.  The terms of this | ||||
| License will continue to apply to the part which is the covered work, | ||||
| but the special requirements of the GNU Affero General Public License, | ||||
| section 13, concerning interaction through a network will apply to the | ||||
| combination as such. | ||||
| 
 | ||||
|   14. Revised Versions of this License. | ||||
| 
 | ||||
|   The Free Software Foundation may publish revised and/or new versions of | ||||
| the GNU General Public License from time to time.  Such new versions will | ||||
| be similar in spirit to the present version, but may differ in detail to | ||||
| address new problems or concerns. | ||||
| 
 | ||||
|   Each version is given a distinguishing version number.  If the | ||||
| Program specifies that a certain numbered version of the GNU General | ||||
| Public License "or any later version" applies to it, you have the | ||||
| option of following the terms and conditions either of that numbered | ||||
| version or of any later version published by the Free Software | ||||
| Foundation.  If the Program does not specify a version number of the | ||||
| GNU General Public License, you may choose any version ever published | ||||
| by the Free Software Foundation. | ||||
| 
 | ||||
|   If the Program specifies that a proxy can decide which future | ||||
| versions of the GNU General Public License can be used, that proxy's | ||||
| public statement of acceptance of a version permanently authorizes you | ||||
| to choose that version for the Program. | ||||
| 
 | ||||
|   Later license versions may give you additional or different | ||||
| permissions.  However, no additional obligations are imposed on any | ||||
| author or copyright holder as a result of your choosing to follow a | ||||
| later version. | ||||
| 
 | ||||
|   15. Disclaimer of Warranty. | ||||
| 
 | ||||
|   THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | ||||
| APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT | ||||
| HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY | ||||
| OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, | ||||
| THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | ||||
| PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM | ||||
| IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF | ||||
| ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | ||||
| 
 | ||||
|   16. Limitation of Liability. | ||||
| 
 | ||||
|   IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | ||||
| WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS | ||||
| THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY | ||||
| GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE | ||||
| USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF | ||||
| DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD | ||||
| PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), | ||||
| EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF | ||||
| SUCH DAMAGES. | ||||
| 
 | ||||
|   17. Interpretation of Sections 15 and 16. | ||||
| 
 | ||||
|   If the disclaimer of warranty and limitation of liability provided | ||||
| above cannot be given local legal effect according to their terms, | ||||
| reviewing courts shall apply local law that most closely approximates | ||||
| an absolute waiver of all civil liability in connection with the | ||||
| Program, unless a warranty or assumption of liability accompanies a | ||||
| copy of the Program in return for a fee. | ||||
| 
 | ||||
|                      END OF TERMS AND CONDITIONS | ||||
| 
 | ||||
|             How to Apply These Terms to Your New Programs | ||||
| 
 | ||||
|   If you develop a new program, and you want it to be of the greatest | ||||
| possible use to the public, the best way to achieve this is to make it | ||||
| free software which everyone can redistribute and change under these terms. | ||||
| 
 | ||||
|   To do so, attach the following notices to the program.  It is safest | ||||
| to attach them to the start of each source file to most effectively | ||||
| state the exclusion of warranty; and each file should have at least | ||||
| the "copyright" line and a pointer to where the full notice is found. | ||||
| 
 | ||||
|     <one line to give the program's name and a brief idea of what it does.> | ||||
|     Copyright (C) <year>  <name of author> | ||||
| 
 | ||||
|     This program is free software: you can redistribute it and/or modify | ||||
|     it under the terms of the GNU General Public License as published by | ||||
|     the Free Software Foundation, either version 3 of the License, or | ||||
|     (at your option) any later version. | ||||
| 
 | ||||
|     This program is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|     GNU General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU General Public License | ||||
|     along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| Also add information on how to contact you by electronic and paper mail. | ||||
| 
 | ||||
|   If the program does terminal interaction, make it output a short | ||||
| notice like this when it starts in an interactive mode: | ||||
| 
 | ||||
|     <program>  Copyright (C) <year>  <name of author> | ||||
|     This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | ||||
|     This is free software, and you are welcome to redistribute it | ||||
|     under certain conditions; type `show c' for details. | ||||
| 
 | ||||
| The hypothetical commands `show w' and `show c' should show the appropriate | ||||
| parts of the General Public License.  Of course, your program's commands | ||||
| might be different; for a GUI interface, you would use an "about box". | ||||
| 
 | ||||
|   You should also get your employer (if you work as a programmer) or school, | ||||
| if any, to sign a "copyright disclaimer" for the program, if necessary. | ||||
| For more information on this, and how to apply and follow the GNU GPL, see | ||||
| <http://www.gnu.org/licenses/>. | ||||
| 
 | ||||
|   The GNU General Public License does not permit incorporating your program | ||||
| into proprietary programs.  If your program is a subroutine library, you | ||||
| may consider it more useful to permit linking proprietary applications with | ||||
| the library.  If this is what you want to do, use the GNU Lesser General | ||||
| Public License instead of this License.  But first, please read | ||||
| <http://www.gnu.org/philosophy/why-not-lgpl.html>. | ||||
|  | @ -1,185 +0,0 @@ | |||
| Kontemplate - A simple Kubernetes templater | ||||
| =========================================== | ||||
| 
 | ||||
| Kontemplate is a simple CLI tool that can take sets of Kubernetes resource files | ||||
| with placeholders and insert values per environment. | ||||
| 
 | ||||
| This tool was made because in many cases all I want in terms of Kubernetes | ||||
| configuration is simple value interpolation per environment (i.e. Kubernetes | ||||
| cluster), but with the same deployment files. | ||||
| 
 | ||||
| In my experience this is often enough and more complex solutions such as | ||||
| [Helm][] are not required. | ||||
| 
 | ||||
| Check out a Kontemplate setup example and the feature list below! | ||||
| 
 | ||||
| <!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --> | ||||
| **Table of Contents** | ||||
| 
 | ||||
| - [Kontemplate - A simple Kubernetes templater](#kontemplate---a-simple-kubernetes-templater) | ||||
|     - [Features](#features) | ||||
|     - [Example](#example) | ||||
|     - [Installation](#installation) | ||||
|         - [Homebrew](#homebrew) | ||||
|         - [Arch Linux](#arch-linux) | ||||
|         - [Building repeatably from source](#building-repeatably-from-source) | ||||
|         - [Building from source](#building-from-source) | ||||
|     - [Usage](#usage) | ||||
|     - [Contributing](#contributing) | ||||
| 
 | ||||
| <!-- markdown-toc end --> | ||||
| 
 | ||||
| ## Features | ||||
| 
 | ||||
| * [Simple, yet powerful templates](docs/templates.md) | ||||
| * [Clean cluster configuration files](docs/cluster-config.md) | ||||
| * [Resources organised as simple resource sets](docs/resource-sets.md) | ||||
| * Integration with pass | ||||
| * Integration with kubectl | ||||
| 
 | ||||
| ## Example | ||||
| 
 | ||||
| Kontemplate lets you describe resources as you normally would in a simple folder structure: | ||||
| 
 | ||||
| ``` | ||||
| . | ||||
| ├── prod-cluster.yaml | ||||
| └── some-api | ||||
|     ├── deployment.yaml | ||||
|     └── service.yaml | ||||
| ``` | ||||
| 
 | ||||
| This example has all resources belonging to `some-api` (no file naming conventions enforced at all!) in the `some-api` | ||||
| folder and the configuration for the cluster `prod-cluster` in the corresponding file. | ||||
| 
 | ||||
| Lets take a short look at `prod-cluster.yaml`: | ||||
| 
 | ||||
| ```yaml | ||||
| --- | ||||
| context: k8s.prod.mydomain.com | ||||
| global: | ||||
|   globalVar: lizards | ||||
| include: | ||||
|   - name: some-api | ||||
|     values: | ||||
|       version: 1.0-0e6884d | ||||
|       importantFeature: true | ||||
|       apiPort: 4567 | ||||
| ``` | ||||
| 
 | ||||
| Those values are then templated into the resource files of `some-api`. That's it! | ||||
| 
 | ||||
| You can also set up more complicated folder structures for organisation, for example: | ||||
| 
 | ||||
| ``` | ||||
| . | ||||
| ├── api | ||||
| │   ├── image-api | ||||
| │   │   └── deployment.yaml | ||||
| │   └── music-api | ||||
| │       └── deployment.yaml | ||||
| │   │   └── default.json | ||||
| ├── frontend | ||||
| │   ├── main-app | ||||
| │   │   ├── deployment.yaml | ||||
| │   │   └── service.yaml | ||||
| │   └── user-page | ||||
| │       ├── deployment.yaml | ||||
| │       └── service.yaml | ||||
| ├── prod-cluster.yaml | ||||
| └── test-cluster.yaml | ||||
| ``` | ||||
| 
 | ||||
| And selectively template or apply resources with a command such as | ||||
| `kontemplate apply test-cluster.yaml --include api --include frontend/user-page` | ||||
| to only update the `api` resource sets and the `frontend/user-page` resource set. | ||||
| 
 | ||||
| ## Installation | ||||
| 
 | ||||
| It is recommended to install Kontemplate from the [Nix](https://nixos.org/) package set, | ||||
| where it is available since NixOS 17.09 as `kontemplate`. | ||||
| 
 | ||||
| If using Nix is not an option for you, several other methods of installation are | ||||
| available: | ||||
| 
 | ||||
| ### Binary releases | ||||
| 
 | ||||
| Signed binary releases are available on the [releases page][] for Linux, OS X, FreeBSD and | ||||
| Windows. | ||||
| 
 | ||||
| Releases are signed with the GPG key `DCF34CFAC1AC44B87E26333136EE34814F6D294A`. | ||||
| 
 | ||||
| ### Building from source | ||||
| 
 | ||||
| You can clone Kontemplate either by cloning the full TVL | ||||
| [depot][https://code.tvl.fyi] or by just cloning the kontemplate | ||||
| subtree like so: | ||||
| 
 | ||||
|     git clone https://code.tvl.fyi/depot.git:/ops/kontemplate.git | ||||
| 
 | ||||
| The `go` tooling can be used as normal with this cloned repository. In | ||||
| a full clone of the depot, Nix can be used to build Kontemplate: | ||||
| 
 | ||||
|     nix-build -A ops.kontemplate | ||||
| 
 | ||||
| ## Usage | ||||
| 
 | ||||
| You must have `kubectl` installed to use Kontemplate effectively. | ||||
| 
 | ||||
| ``` | ||||
| usage: kontemplate [<flags>] <command> [<args> ...] | ||||
| 
 | ||||
| simple Kubernetes resource templating | ||||
| 
 | ||||
| Flags: | ||||
|   -h, --help                 Show context-sensitive help (also try --help-long and --help-man). | ||||
|   -i, --include=INCLUDE ...  Resource sets to include explicitly | ||||
|   -e, --exclude=EXCLUDE ...  Resource sets to exclude explicitly | ||||
| 
 | ||||
| Commands: | ||||
|   help [<command>...] | ||||
|     Show help. | ||||
| 
 | ||||
|   template <file> | ||||
|     Template resource sets and print them | ||||
| 
 | ||||
|   apply [<flags>] <file> | ||||
|     Template resources and pass to 'kubectl apply' | ||||
| 
 | ||||
|   replace <file> | ||||
|     Template resources and pass to 'kubectl replace' | ||||
| 
 | ||||
|   delete <file> | ||||
|     Template resources and pass to 'kubectl delete' | ||||
| 
 | ||||
|   create <file> | ||||
|     Template resources and pass to 'kubectl create' | ||||
| 
 | ||||
| ``` | ||||
| 
 | ||||
| Examples: | ||||
| 
 | ||||
| ``` | ||||
| # Look at output for a specific resource set and check to see if it's correct ... | ||||
| kontemplate template example/prod-cluster.yaml -i some-api | ||||
| 
 | ||||
| # ... maybe do a dry-run to see what kubectl would do: | ||||
| kontemplate apply example/prod-cluster.yaml --dry-run | ||||
| 
 | ||||
| # And actually apply it if you like what you see: | ||||
| kontemplate apply example/prod-cluster.yaml | ||||
| ``` | ||||
| 
 | ||||
| Check out the feature list and the individual feature documentation above. Then you should be good to go! | ||||
| 
 | ||||
| ## Contributing | ||||
| 
 | ||||
| Feel free to contribute pull requests, file bugs and open issues with feature suggestions! | ||||
| 
 | ||||
| Kontemplate is licensed under the GPLv3, a copy of the license and its terms can be found | ||||
| in the `LICENSE` file. | ||||
| 
 | ||||
| Please follow the [code of conduct](CODE_OF_CONDUCT.md). | ||||
| 
 | ||||
| [Helm]: https://helm.sh/ | ||||
| [releases page]: https://github.com/tazjin/kontemplate/releases | ||||
|  | @ -1,75 +0,0 @@ | |||
| #!/usr/bin/env bash | ||||
| set -ueo pipefail | ||||
| 
 | ||||
| # Copyright (C) 2016-2019  Vincent Ambo <mail@tazj.in> | ||||
| # | ||||
| # This file is part of Kontemplate. | ||||
| # | ||||
| # Kontemplate is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| 
 | ||||
| readonly GIT_HASH="$(git rev-parse --short HEAD)" | ||||
| readonly LDFLAGS="-X main.gitHash=${GIT_HASH} -w -s" | ||||
| readonly VERSION="1.8.0-${GIT_HASH}" | ||||
| 
 | ||||
| function binary-name() { | ||||
|     local os="${1}" | ||||
|     local target="${2}" | ||||
|     if [ "${os}" = "windows" ]; then | ||||
|         echo -n "${target}/kontemplate.exe" | ||||
|     else | ||||
|         echo -n "${target}/kontemplate" | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| function build-for() { | ||||
|     local os="${1}" | ||||
|     local arch="${2}" | ||||
|     local target="release/${os}/${arch}" | ||||
|     local bin=$(binary-name "${os}" "${target}") | ||||
| 
 | ||||
|     echo "Building kontemplate for ${os}-${arch} in ${target}" | ||||
| 
 | ||||
|     mkdir -p "${target}" | ||||
| 
 | ||||
|     env GOOS="${os}" GOARCH="${arch}" go build \ | ||||
|         -ldflags "${LDFLAGS}" \ | ||||
|         -o "${bin}" \ | ||||
|         -tags netgo | ||||
| } | ||||
| 
 | ||||
| function sign-for() { | ||||
|     local os="${1}" | ||||
|     local arch="${2}" | ||||
|     local target="release/${os}/${arch}" | ||||
|     local bin=$(binary-name "${os}" "${target}") | ||||
|     local tar="release/kontemplate-${VERSION}-${os}-${arch}.tar.gz" | ||||
| 
 | ||||
|     echo "Packing release into ${tar}" | ||||
|     tar czvf "${tar}" -C "${target}" $(basename "${bin}") | ||||
| 
 | ||||
|     local hash=$(sha256sum "${tar}") | ||||
|     echo "Signing kontemplate release tarball for ${os}-${arch} with SHA256 ${hash}" | ||||
|     gpg --armor --detach-sig --sign "${tar}" | ||||
| } | ||||
| 
 | ||||
| case "${1}" in | ||||
|     "build") | ||||
|         # Build releases for various operating systems: | ||||
|         build-for "linux" "amd64" | ||||
|         build-for "darwin" "amd64" | ||||
|         build-for "windows" "amd64" | ||||
|         build-for "freebsd" "amd64" | ||||
|         exit 0 | ||||
|         ;; | ||||
|     "sign") | ||||
|         # Bundle and sign releases: | ||||
|         sign-for "linux" "amd64" | ||||
|         sign-for "darwin" "amd64" | ||||
|         sign-for "windows" "amd64" | ||||
|         sign-for "freebsd" "amd64" | ||||
|         exit 0 | ||||
|         ;; | ||||
| esac | ||||
|  | @ -1,266 +0,0 @@ | |||
| // Copyright (C) 2016-2019  Vincent Ambo <mail@tazj.in> | ||||
| // | ||||
| // This file is part of Kontemplate. | ||||
| // | ||||
| // Kontemplate is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| 
 | ||||
| package context | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/tazjin/kontemplate/util" | ||||
| ) | ||||
| 
 | ||||
| type ResourceSet struct { | ||||
| 	// Name of the resource set. This can be used in include/exclude statements during kontemplate runs. | ||||
| 	Name string `json:"name"` | ||||
| 
 | ||||
| 	// Path to the folder containing the files for this resource set. This defaults to the value of the 'name' field | ||||
| 	// if unset. | ||||
| 	Path string `json:"path"` | ||||
| 
 | ||||
| 	// Values to include when interpolating resources from this resource set. | ||||
| 	Values map[string]interface{} `json:"values"` | ||||
| 
 | ||||
| 	// Args to pass on to kubectl for this resource set. | ||||
| 	Args []string `json:"args"` | ||||
| 
 | ||||
| 	// Nested resource sets to include | ||||
| 	Include []ResourceSet `json:"include"` | ||||
| 
 | ||||
| 	// Parent resource set for flattened resource sets. Should not be manually specified. | ||||
| 	Parent string | ||||
| } | ||||
| 
 | ||||
| type Context struct { | ||||
| 	// The name of the kubectl context | ||||
| 	Name string `json:"context"` | ||||
| 
 | ||||
| 	// Global variables that should be accessible by all resource sets | ||||
| 	Global map[string]interface{} `json:"global"` | ||||
| 
 | ||||
| 	// File names of YAML or JSON files including extra variables that should be globally accessible | ||||
| 	VariableImportFiles []string `json:"import"` | ||||
| 
 | ||||
| 	// The resource sets to include in this context | ||||
| 	ResourceSets []ResourceSet `json:"include"` | ||||
| 
 | ||||
| 	// Variables imported from additional files | ||||
| 	ImportedVars map[string]interface{} | ||||
| 
 | ||||
| 	// Explicitly set variables (via `--var`) that should override all others | ||||
| 	ExplicitVars map[string]interface{} | ||||
| 
 | ||||
| 	// This field represents the absolute path to the context base directory and should not be manually specified. | ||||
| 	BaseDir string | ||||
| } | ||||
| 
 | ||||
| func contextLoadingError(filename string, cause error) error { | ||||
| 	return fmt.Errorf("Context loading failed on file %s due to: \n%v", filename, cause) | ||||
| } | ||||
| 
 | ||||
| // Attempt to load and deserialise a Context from the specified file. | ||||
| func LoadContext(filename string, explicitVars *[]string) (*Context, error) { | ||||
| 	var ctx Context | ||||
| 	err := util.LoadData(filename, &ctx) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return nil, contextLoadingError(filename, err) | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.BaseDir = path.Dir(filename) | ||||
| 
 | ||||
| 	// Prepare the resource sets by resolving parents etc. | ||||
| 	ctx.ResourceSets = flattenPrepareResourceSetPaths(&ctx.BaseDir, &ctx.ResourceSets) | ||||
| 
 | ||||
| 	// Add variables explicitly specified on the command line | ||||
| 	ctx.ExplicitVars, err = loadExplicitVars(explicitVars) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("Error setting explicit variables: %v\n", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Add variables loaded from import files | ||||
| 	ctx.ImportedVars, err = ctx.loadImportedVariables() | ||||
| 	if err != nil { | ||||
| 		return nil, contextLoadingError(filename, err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Merge variables defined at different levels. The | ||||
| 	// `mergeContextValues` function is documented with the merge | ||||
| 	// hierarchy. | ||||
| 	ctx.ResourceSets = ctx.mergeContextValues() | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return nil, contextLoadingError(filename, err) | ||||
| 	} | ||||
| 
 | ||||
| 	return &ctx, nil | ||||
| } | ||||
| 
 | ||||
| // Kontemplate supports specifying additional variable files with the | ||||
| // `import` keyword. This function loads those variable files and | ||||
| // merges them together with the context's other global variables. | ||||
| func (ctx *Context) loadImportedVariables() (map[string]interface{}, error) { | ||||
| 	allImportedVars := make(map[string]interface{}) | ||||
| 
 | ||||
| 	for _, file := range ctx.VariableImportFiles { | ||||
| 		// Ensure that the filename is not merged with the baseDir if | ||||
| 		// it is set to an absolute path. | ||||
| 		var filePath string | ||||
| 		if path.IsAbs(file) { | ||||
| 			filePath = file | ||||
| 		} else { | ||||
| 			filePath = path.Join(ctx.BaseDir, file) | ||||
| 		} | ||||
| 
 | ||||
| 		var importedVars map[string]interface{} | ||||
| 		err := util.LoadData(filePath, &importedVars) | ||||
| 
 | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		allImportedVars = *util.Merge(&allImportedVars, &importedVars) | ||||
| 	} | ||||
| 
 | ||||
| 	return allImportedVars, nil | ||||
| } | ||||
| 
 | ||||
| // Correctly prepares the file paths for resource sets by inferring implicit paths and flattening resource set | ||||
| // collections, i.e. resource sets that themselves have an additional 'include' field set. | ||||
| // Those will be regarded as a short-hand for including multiple resource sets from a subfolder. | ||||
| // See https://github.com/tazjin/kontemplate/issues/9 for more information. | ||||
| func flattenPrepareResourceSetPaths(baseDir *string, rs *[]ResourceSet) []ResourceSet { | ||||
| 	flattened := make([]ResourceSet, 0) | ||||
| 
 | ||||
| 	for _, r := range *rs { | ||||
| 		// If a path is not explicitly specified it should default to the resource set name. | ||||
| 		// This is also the classic behaviour prior to kontemplate 1.2 | ||||
| 		if r.Path == "" { | ||||
| 			r.Path = r.Name | ||||
| 		} | ||||
| 
 | ||||
| 		// Paths are made absolute by resolving them relative to the context base, | ||||
| 		// unless absolute paths were specified. | ||||
| 		if !path.IsAbs(r.Path) { | ||||
| 			r.Path = path.Join(*baseDir, r.Path) | ||||
| 		} | ||||
| 
 | ||||
| 		if len(r.Include) == 0 { | ||||
| 			flattened = append(flattened, r) | ||||
| 		} else { | ||||
| 			for _, subResourceSet := range r.Include { | ||||
| 				if subResourceSet.Path == "" { | ||||
| 					subResourceSet.Path = subResourceSet.Name | ||||
| 				} | ||||
| 
 | ||||
| 				subResourceSet.Parent = r.Name | ||||
| 				subResourceSet.Name = path.Join(r.Name, subResourceSet.Name) | ||||
| 				subResourceSet.Path = path.Join(r.Path, subResourceSet.Path) | ||||
| 				subResourceSet.Values = *util.Merge(&r.Values, &subResourceSet.Values) | ||||
| 				flattened = append(flattened, subResourceSet) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return flattened | ||||
| } | ||||
| 
 | ||||
| // Merges the context and resource set variables according in the | ||||
| // desired precedence order. | ||||
| // | ||||
| // For now the reasoning behind the merge order is from least specific | ||||
| // in relation to the cluster configuration, which means that the | ||||
| // precedence is (in ascending order): | ||||
| // | ||||
| // 1. Default values in resource sets. | ||||
| // 2. Values imported from files (via `import:`) | ||||
| // 3. Global values in a cluster configuration | ||||
| // 4. Values set in a resource set's `include`-section | ||||
| // 5. Explicit values set on the CLI (`--var`) | ||||
| // | ||||
| // For a discussion on the reasoning behind this order, please consult | ||||
| // https://github.com/tazjin/kontemplate/issues/142 | ||||
| func (ctx *Context) mergeContextValues() []ResourceSet { | ||||
| 	updated := make([]ResourceSet, len(ctx.ResourceSets)) | ||||
| 
 | ||||
| 	// Merging has to happen separately for every individual | ||||
| 	// resource set to make use of the default values: | ||||
| 	for i, rs := range ctx.ResourceSets { | ||||
| 		// Begin by loading default values from the resource | ||||
| 		// sets configuration. | ||||
| 		// | ||||
| 		// Resource sets are used across different cluster | ||||
| 		// contexts and the default values in them have the | ||||
| 		// lowest precedence. | ||||
| 		defaultValues := loadDefaultValues(&rs, ctx) | ||||
| 
 | ||||
| 		// Continue by merging default values with values | ||||
| 		// imported from external files. Those values are also | ||||
| 		// used across cluster contexts, but have higher | ||||
| 		// precedence than defaults. | ||||
| 		merged := util.Merge(defaultValues, &ctx.ImportedVars) | ||||
| 
 | ||||
| 		// Merge global values defined in the cluster context: | ||||
| 		merged = util.Merge(merged, &ctx.Global) | ||||
| 
 | ||||
| 		// Merge values configured in the resource set's | ||||
| 		// `include` section: | ||||
| 		merged = util.Merge(merged, &rs.Values) | ||||
| 
 | ||||
| 		// Merge values defined explicitly on the CLI: | ||||
| 		merged = util.Merge(merged, &ctx.ExplicitVars) | ||||
| 
 | ||||
| 		// Continue with the newly merged resource set: | ||||
| 		rs.Values = *merged | ||||
| 		updated[i] = rs | ||||
| 	} | ||||
| 
 | ||||
| 	return updated | ||||
| } | ||||
| 
 | ||||
| // Loads default values for a resource set collection from | ||||
| // path/to/set/default.{json|yaml}. | ||||
| func loadDefaultValues(rs *ResourceSet, c *Context) *map[string]interface{} { | ||||
| 	var defaultVars map[string]interface{} | ||||
| 
 | ||||
| 	for _, filename := range util.DefaultFilenames { | ||||
| 		err := util.LoadData(path.Join(rs.Path, filename), &defaultVars) | ||||
| 		if err == nil { | ||||
| 			return &defaultVars | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// The actual error is not inspected here. The reasoning for | ||||
| 	// this is that in case of serious problems (e.g. permission | ||||
| 	// issues with the folder / folder not existing) failure will | ||||
| 	// occur a bit later anyways. | ||||
| 	// | ||||
| 	// Otherwise we'd have to differentiate between | ||||
| 	// file-not-found-errors (no default values specified) and | ||||
| 	// other errors here. | ||||
| 	return &rs.Values | ||||
| } | ||||
| 
 | ||||
| // Prepares the variables specified explicitly via `--var` when | ||||
| // executing kontemplate for adding to the context. | ||||
| func loadExplicitVars(vars *[]string) (map[string]interface{}, error) { | ||||
| 	explicitVars := make(map[string]interface{}, len(*vars)) | ||||
| 
 | ||||
| 	for _, v := range *vars { | ||||
| 		varParts := strings.SplitN(v, "=", 2) | ||||
| 		if len(varParts) != 2 { | ||||
| 			return nil, fmt.Errorf(`invalid explicit variable provided (%s), name and value should be separated with "="`, v) | ||||
| 		} | ||||
| 
 | ||||
| 		explicitVars[varParts[0]] = varParts[1] | ||||
| 	} | ||||
| 
 | ||||
| 	return explicitVars, nil | ||||
| } | ||||
|  | @ -1,353 +0,0 @@ | |||
| // Copyright (C) 2016-2019  Vincent Ambo <mail@tazj.in> | ||||
| // | ||||
| // This file is part of Kontemplate. | ||||
| // | ||||
| // Kontemplate is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| 
 | ||||
| package context | ||||
| 
 | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| var noExplicitVars []string = make([]string, 0) | ||||
| 
 | ||||
| func TestLoadFlatContextFromFile(t *testing.T) { | ||||
| 	ctx, err := LoadContext("testdata/flat-test.yaml", &noExplicitVars) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	expected := Context{ | ||||
| 		Name: "k8s.prod.mydomain.com", | ||||
| 		Global: map[string]interface{}{ | ||||
| 			"globalVar": "lizards", | ||||
| 		}, | ||||
| 		ResourceSets: []ResourceSet{ | ||||
| 			{ | ||||
| 				Name: "some-api", | ||||
| 				Path: "testdata/some-api", | ||||
| 				Values: map[string]interface{}{ | ||||
| 					"apiPort":          float64(4567), // yep! | ||||
| 					"importantFeature": true, | ||||
| 					"version":          "1.0-0e6884d", | ||||
| 					"globalVar":        "lizards", | ||||
| 				}, | ||||
| 				Include: nil, | ||||
| 				Parent:  "", | ||||
| 			}, | ||||
| 		}, | ||||
| 		BaseDir:      "testdata", | ||||
| 		ImportedVars: make(map[string]interface{}, 0), | ||||
| 		ExplicitVars: make(map[string]interface{}, 0), | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(*ctx, expected) { | ||||
| 		t.Error("Loaded context and expected context did not match") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestLoadContextWithArgs(t *testing.T) { | ||||
| 	ctx, err := LoadContext("testdata/flat-with-args-test.yaml", &noExplicitVars) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	expected := Context{ | ||||
| 		Name: "k8s.prod.mydomain.com", | ||||
| 		ResourceSets: []ResourceSet{ | ||||
| 			{ | ||||
| 				Name:   "some-api", | ||||
| 				Path:   "testdata/some-api", | ||||
| 				Values: make(map[string]interface{}, 0), | ||||
| 				Args: []string{ | ||||
| 					"--as=some-user", | ||||
| 					"--as-group=hello:world", | ||||
| 					"--as-banana", | ||||
| 					"true", | ||||
| 				}, | ||||
| 				Include: nil, | ||||
| 				Parent:  "", | ||||
| 			}, | ||||
| 		}, | ||||
| 		BaseDir:      "testdata", | ||||
| 		ImportedVars: make(map[string]interface{}, 0), | ||||
| 		ExplicitVars: make(map[string]interface{}, 0), | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(*ctx, expected) { | ||||
| 		t.Error("Loaded context and expected context did not match") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestLoadContextWithResourceSetCollections(t *testing.T) { | ||||
| 	ctx, err := LoadContext("testdata/collections-test.yaml", &noExplicitVars) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	expected := Context{ | ||||
| 		Name: "k8s.prod.mydomain.com", | ||||
| 		Global: map[string]interface{}{ | ||||
| 			"globalVar": "lizards", | ||||
| 		}, | ||||
| 		ResourceSets: []ResourceSet{ | ||||
| 			{ | ||||
| 				Name: "some-api", | ||||
| 				Path: "testdata/some-api", | ||||
| 				Values: map[string]interface{}{ | ||||
| 					"apiPort":          float64(4567), // yep! | ||||
| 					"importantFeature": true, | ||||
| 					"version":          "1.0-0e6884d", | ||||
| 					"globalVar":        "lizards", | ||||
| 				}, | ||||
| 				Include: nil, | ||||
| 				Parent:  "", | ||||
| 			}, | ||||
| 			{ | ||||
| 				Name: "collection/nested", | ||||
| 				Path: "testdata/collection/nested", | ||||
| 				Values: map[string]interface{}{ | ||||
| 					"lizards":   "good", | ||||
| 					"globalVar": "lizards", | ||||
| 				}, | ||||
| 				Include: nil, | ||||
| 				Parent:  "collection", | ||||
| 			}, | ||||
| 		}, | ||||
| 		BaseDir:      "testdata", | ||||
| 		ImportedVars: make(map[string]interface{}, 0), | ||||
| 		ExplicitVars: make(map[string]interface{}, 0), | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(*ctx, expected) { | ||||
| 		t.Error("Loaded context and expected context did not match") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func TestSubresourceVariableInheritance(t *testing.T) { | ||||
| 	ctx, err := LoadContext("testdata/parent-variables.yaml", &noExplicitVars) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	expected := Context{ | ||||
| 		Name: "k8s.prod.mydomain.com", | ||||
| 		ResourceSets: []ResourceSet{ | ||||
| 			{ | ||||
| 				Name: "parent/child", | ||||
| 				Path: "testdata/parent/child", | ||||
| 				Values: map[string]interface{}{ | ||||
| 					"foo": "bar", | ||||
| 					"bar": "baz", | ||||
| 				}, | ||||
| 				Include: nil, | ||||
| 				Parent:  "parent", | ||||
| 			}, | ||||
| 		}, | ||||
| 		BaseDir:      "testdata", | ||||
| 		ImportedVars: make(map[string]interface{}, 0), | ||||
| 		ExplicitVars: make(map[string]interface{}, 0), | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(*ctx, expected) { | ||||
| 		t.Error("Loaded and expected context did not match") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestSubresourceVariableInheritanceOverride(t *testing.T) { | ||||
| 	ctx, err := LoadContext("testdata/parent-variable-override.yaml", &noExplicitVars) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	expected := Context{ | ||||
| 		Name: "k8s.prod.mydomain.com", | ||||
| 		ResourceSets: []ResourceSet{ | ||||
| 			{ | ||||
| 				Name: "parent/child", | ||||
| 				Path: "testdata/parent/child", | ||||
| 				Values: map[string]interface{}{ | ||||
| 					"foo": "newvalue", | ||||
| 				}, | ||||
| 				Include: nil, | ||||
| 				Parent:  "parent", | ||||
| 			}, | ||||
| 		}, | ||||
| 		BaseDir:      "testdata", | ||||
| 		ImportedVars: make(map[string]interface{}, 0), | ||||
| 		ExplicitVars: make(map[string]interface{}, 0), | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(*ctx, expected) { | ||||
| 		t.Error("Loaded and expected context did not match") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestDefaultValuesLoading(t *testing.T) { | ||||
| 	ctx, err := LoadContext("testdata/default-loading.yaml", &noExplicitVars) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	rs := ctx.ResourceSets[0] | ||||
| 	if rs.Values["defaultValues"] != "loaded" { | ||||
| 		t.Errorf("Default values not loaded from YAML file") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	if rs.Values["override"] != "notAtAll" { | ||||
| 		t.Error("Default values should not override other values") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestImportValuesLoading(t *testing.T) { | ||||
| 	ctx, err := LoadContext("testdata/import-vars-simple.yaml", &noExplicitVars) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	expected := map[string]interface{}{ | ||||
| 		"override": "true", | ||||
| 		"music": map[string]interface{}{ | ||||
| 			"artist": "Pallida", | ||||
| 			"track":  "Tractor Beam", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(ctx.ImportedVars, expected) { | ||||
| 		t.Error("Expected imported values after loading imports did not match!") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestExplicitPathLoading(t *testing.T) { | ||||
| 	ctx, err := LoadContext("testdata/explicit-path.yaml", &noExplicitVars) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	expected := Context{ | ||||
| 		Name: "k8s.prod.mydomain.com", | ||||
| 		ResourceSets: []ResourceSet{ | ||||
| 			{ | ||||
| 				Name: "some-api-europe", | ||||
| 				Path: "testdata/some-api", | ||||
| 				Values: map[string]interface{}{ | ||||
| 					"location": "europe", | ||||
| 				}, | ||||
| 				Include: nil, | ||||
| 				Parent:  "", | ||||
| 			}, | ||||
| 			{ | ||||
| 				Name: "some-api-asia", | ||||
| 				Path: "testdata/some-api", | ||||
| 				Values: map[string]interface{}{ | ||||
| 					"location": "asia", | ||||
| 				}, | ||||
| 				Include: nil, | ||||
| 				Parent:  "", | ||||
| 			}, | ||||
| 		}, | ||||
| 		BaseDir:      "testdata", | ||||
| 		ImportedVars: make(map[string]interface{}, 0), | ||||
| 		ExplicitVars: make(map[string]interface{}, 0), | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(*ctx, expected) { | ||||
| 		t.Error("Loaded context and expected context did not match") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestExplicitSubresourcePathLoading(t *testing.T) { | ||||
| 	ctx, err := LoadContext("testdata/explicit-subresource-path.yaml", &noExplicitVars) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	expected := Context{ | ||||
| 		Name: "k8s.prod.mydomain.com", | ||||
| 		ResourceSets: []ResourceSet{ | ||||
| 			{ | ||||
| 				Name:   "parent/child", | ||||
| 				Path:   "testdata/parent-path/child-path", | ||||
| 				Parent: "parent", | ||||
| 				Values: make(map[string]interface{}, 0), | ||||
| 			}, | ||||
| 		}, | ||||
| 		BaseDir:      "testdata", | ||||
| 		ImportedVars: make(map[string]interface{}, 0), | ||||
| 		ExplicitVars: make(map[string]interface{}, 0), | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(*ctx, expected) { | ||||
| 		t.Error("Loaded context and expected context did not match") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestSetVariablesFromArguments(t *testing.T) { | ||||
| 	vars := []string{"version=some-service-version"} | ||||
| 	ctx, _ := LoadContext("testdata/default-loading.yaml", &vars) | ||||
| 
 | ||||
| 	if version := ctx.ExplicitVars["version"]; version != "some-service-version" { | ||||
| 		t.Errorf(`Expected variable "version" to have value "some-service-version" but was "%s"`, version) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestSetInvalidVariablesFromArguments(t *testing.T) { | ||||
| 	vars := []string{"version: some-service-version"} | ||||
| 	_, err := LoadContext("testdata/default-loading.yaml", &vars) | ||||
| 
 | ||||
| 	if err == nil { | ||||
| 		t.Error("Expected invalid variable to return an error") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // This test ensures that variables are merged in the correct order. | ||||
| // Please consult the test data in `testdata/merging`. | ||||
| func TestValueMergePrecedence(t *testing.T) { | ||||
| 	cliVars := []string{"cliVar=cliVar"} | ||||
| 	ctx, _ := LoadContext("testdata/merging/context.yaml", &cliVars) | ||||
| 
 | ||||
| 	expected := map[string]interface{}{ | ||||
| 		"defaultVar": "defaultVar", | ||||
| 		"importVar":  "importVar", | ||||
| 		"globalVar":  "globalVar", | ||||
| 		"includeVar": "includeVar", | ||||
| 		"cliVar":     "cliVar", | ||||
| 	} | ||||
| 
 | ||||
| 	result := ctx.ResourceSets[0].Values | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(expected, result) { | ||||
| 		t.Errorf("Merged values did not match expected result: \n%v", result) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
|  | @ -1,15 +0,0 @@ | |||
| --- | ||||
| context: k8s.prod.mydomain.com | ||||
| global: | ||||
|   globalVar: lizards | ||||
| include: | ||||
|   - name: some-api | ||||
|     values: | ||||
|       version: 1.0-0e6884d | ||||
|       importantFeature: true | ||||
|       apiPort: 4567 | ||||
|   - name: collection | ||||
|     include: | ||||
|       - name: nested | ||||
|         values: | ||||
|           lizards: good | ||||
|  | @ -1,6 +0,0 @@ | |||
| --- | ||||
| context: default-loading | ||||
| include: | ||||
|   - name: default | ||||
|     values: | ||||
|       override: notAtAll | ||||
|  | @ -1,2 +0,0 @@ | |||
| defaultValues: loaded | ||||
| override: noop | ||||
|  | @ -1,11 +0,0 @@ | |||
| --- | ||||
| context: k8s.prod.mydomain.com | ||||
| include: | ||||
|   - name: some-api-europe | ||||
|     path: some-api | ||||
|     values: | ||||
|       location: europe | ||||
|   - name: some-api-asia | ||||
|     path: some-api | ||||
|     values: | ||||
|       location: asia | ||||
|  | @ -1,8 +0,0 @@ | |||
| --- | ||||
| context: k8s.prod.mydomain.com | ||||
| include: | ||||
|   - name: parent | ||||
|     path: parent-path | ||||
|     include: | ||||
|       - name: child | ||||
|         path: child-path | ||||
							
								
								
									
										10
									
								
								ops/kontemplate/context/testdata/flat-test.yaml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								ops/kontemplate/context/testdata/flat-test.yaml
									
										
									
									
										vendored
									
									
								
							|  | @ -1,10 +0,0 @@ | |||
| --- | ||||
| context: k8s.prod.mydomain.com | ||||
| global: | ||||
|   globalVar: lizards | ||||
| include: | ||||
|   - name: some-api | ||||
|     values: | ||||
|       version: 1.0-0e6884d | ||||
|       importantFeature: true | ||||
|       apiPort: 4567 | ||||
|  | @ -1,9 +0,0 @@ | |||
| --- | ||||
| context: k8s.prod.mydomain.com | ||||
| include: | ||||
|   - name: some-api | ||||
|     args: | ||||
|       - --as=some-user | ||||
|       - --as-group=hello:world | ||||
|       - --as-banana | ||||
|       - "true" | ||||
|  | @ -1,5 +0,0 @@ | |||
| --- | ||||
| context: k8s.prod.mydomain.com | ||||
| import: | ||||
|   - test-vars.yaml | ||||
| include: [] | ||||
|  | @ -1,15 +0,0 @@ | |||
| # This context file is intended to test the merge hierarchy of | ||||
| # variables defined at different levels. | ||||
| --- | ||||
| context: merging.in.kontemplate.works | ||||
| global: | ||||
|   globalVar: globalVar | ||||
|   includeVar: should be overridden (global) | ||||
|   cliVar: should be overridden (global) | ||||
| import: | ||||
|   - import-vars.yaml | ||||
| include: | ||||
|   - name: resource | ||||
|     values: | ||||
|       includeVar: includeVar | ||||
|       cliVar: should be overridden (include) | ||||
|  | @ -1,4 +0,0 @@ | |||
| importVar: importVar | ||||
| globalVar: should be overridden (import) | ||||
| includeVar: should be overridden (import) | ||||
| cliVar: should be overridden (import) | ||||
|  | @ -1,5 +0,0 @@ | |||
| defaultVar: defaultVar | ||||
| importVar: should be overridden (default) | ||||
| globalVar: should be overridden (default) | ||||
| includeVar: should be overridden (default) | ||||
| cliVar: should be overridden (default) | ||||
|  | @ -1,5 +0,0 @@ | |||
| defaultVar: {{ .defaultVar }} | ||||
| importVar: {{ .importVar }} | ||||
| globalVar: {{ .globalVar }} | ||||
| includeVar: {{ .includeVar }} | ||||
| cliVar: {{ .cliVar }} | ||||
|  | @ -1,10 +0,0 @@ | |||
| --- | ||||
| context: k8s.prod.mydomain.com | ||||
| include: | ||||
|   - name: parent | ||||
|     values: | ||||
|       foo: bar | ||||
|     include: | ||||
|       - name: child | ||||
|         values: | ||||
|           foo: newvalue | ||||
|  | @ -1,10 +0,0 @@ | |||
| --- | ||||
| context: k8s.prod.mydomain.com | ||||
| include: | ||||
|   - name: parent | ||||
|     values: | ||||
|       foo: bar | ||||
|     include: | ||||
|       - name: child | ||||
|         values: | ||||
|           bar: baz | ||||
|  | @ -1,3 +0,0 @@ | |||
| --- | ||||
| override: 3 | ||||
| place: Oslo | ||||
|  | @ -1,5 +0,0 @@ | |||
| --- | ||||
| override: 'true' | ||||
| music: | ||||
|   artist: Pallida | ||||
|   track: Tractor Beam | ||||
|  | @ -1,37 +0,0 @@ | |||
| # Copyright (C) 2016-2021  Vincent Ambo <mail@tazj.in> | ||||
| # | ||||
| # This file is part of Kontemplate. | ||||
| # | ||||
| # Kontemplate is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # This file is the Nix derivation used to install Kontemplate on | ||||
| # Nix-based systems. | ||||
| 
 | ||||
| { lib, pkgs, ... }: | ||||
| 
 | ||||
| pkgs.buildGoModule rec { | ||||
|   name = "kontemplate-${version}"; | ||||
|   version = "canon"; | ||||
|   src = ./.; | ||||
| 
 | ||||
|   buildInputs = [ pkgs.parallel ]; | ||||
| 
 | ||||
|   # Enable checks and configure check-phase to include vet: | ||||
|   doCheck = true; | ||||
|   preCheck = '' | ||||
|     for pkg in $(getGoDirs ""); do | ||||
|       buildGoDir vet "$pkg" | ||||
|     done | ||||
|   ''; | ||||
| 
 | ||||
|   vendorHash = "sha256-xPGVM2dq5fAVOiuodOXhDm3v3k+ncNLhlk6aCtF5S9E="; | ||||
| 
 | ||||
|   meta = with lib; { | ||||
|     description = "A resource templating helper for Kubernetes"; | ||||
|     homepage = "http://kontemplate.works/"; | ||||
|     license = licenses.gpl3; | ||||
|   }; | ||||
| } | ||||
|  | @ -1,106 +0,0 @@ | |||
| Cluster configuration | ||||
| ========================== | ||||
| 
 | ||||
| Every cluster (or "environment") that requires individual configuration is specified in | ||||
| a very simple YAML file in Kontemplate. | ||||
| 
 | ||||
| An example file for a hypothetical test environment could look like this: | ||||
| 
 | ||||
| ```yaml | ||||
| --- | ||||
| context: k8s.test.mydomain.com | ||||
| global: | ||||
|   clusterName: test-cluster | ||||
|   defaultReplicas: 2 | ||||
| import: | ||||
|   - test-secrets.yaml | ||||
| include: | ||||
|   - name: gateway | ||||
|     path: tools/nginx | ||||
|     values: | ||||
|       tlsDomains: | ||||
|         - test.oslo.pub | ||||
|         - test.tazj.in | ||||
|   - path: backend | ||||
|     values: | ||||
|       env: test | ||||
|     include: | ||||
|       - name: blog | ||||
|         values: | ||||
|           url: test.tazj.in | ||||
|       - name: pub-service | ||||
| ``` | ||||
| 
 | ||||
| <!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --> | ||||
| **Table of Contents** | ||||
| 
 | ||||
| - [Cluster configuration](#cluster-configuration) | ||||
|     - [Fields](#fields) | ||||
|         - [`context`](#context) | ||||
|         - [`global`](#global) | ||||
|         - [`import`](#import) | ||||
|         - [`include`](#include) | ||||
|     - [External variables](#external-variables) | ||||
| 
 | ||||
| <!-- markdown-toc end --> | ||||
| 
 | ||||
| ## Fields | ||||
| 
 | ||||
| This is documentation for the individual fields in a cluster context file. | ||||
| 
 | ||||
| ### `context` | ||||
| 
 | ||||
| The `context` field contains the name of the kubectl-context. You can list context names with | ||||
| 'kubectl config get-contexts'. | ||||
| 
 | ||||
| This must be set here so that Kontemplate can use the correct context when calling kubectl. | ||||
| 
 | ||||
| This field is **required** for `kubectl`-wrapping commands. It can be left out if only the `template`-command is used. | ||||
| 
 | ||||
| ### `global` | ||||
| 
 | ||||
| The `global` field contains a key/value map of variables that should be available to all resource | ||||
| sets in the cluster. | ||||
| 
 | ||||
| This field is **optional**. | ||||
| 
 | ||||
| ### `import` | ||||
| 
 | ||||
| The `import` field contains the file names of additional YAML or JSON files from which global | ||||
| variables should be loaded. Using this field makes it possible to keep certain configuration that | ||||
| is the same for some, but not all, clusters in a common place. | ||||
| 
 | ||||
| This field is **optional**. | ||||
| 
 | ||||
| ### `include` | ||||
| 
 | ||||
| The `include` field contains the actual resource sets to be included in the cluster. | ||||
| 
 | ||||
| Information about the structure of resource sets can be found in the [resource set documentation][]. | ||||
| 
 | ||||
| This field is **required**. | ||||
| 
 | ||||
| ## External variables | ||||
| 
 | ||||
| As mentioned above, extra variables can be loaded from additional YAML or JSON files. Assuming you | ||||
| have a file called `test-secrets.yaml` which contains variables that should be shared between a `test` | ||||
| and `dev` cluster, you could import it in your context as such: | ||||
| 
 | ||||
| ```yaml | ||||
| # test-secrets.yaml: | ||||
| mySecretVar: foo-bar-12345 | ||||
| 
 | ||||
| # test-cluster.yaml: | ||||
| context: k8s.test.mydomain.com | ||||
| import: | ||||
|   - test-secrets.yaml | ||||
| 
 | ||||
| # dev-cluster.yaml: | ||||
| context: k8s.dev.mydomain.com | ||||
| import: | ||||
|   - test-secrets.yaml | ||||
| ``` | ||||
| 
 | ||||
| The variable `mySecretVar` is then available as a global variable. | ||||
| 
 | ||||
| [resource set documentation]: resource-sets.md | ||||
|  | @ -1,170 +0,0 @@ | |||
| Resource Sets | ||||
| ================ | ||||
| 
 | ||||
| Resource sets are collections of Kubernetes resources that should be passed to `kubectl` together. | ||||
| 
 | ||||
| Technically a resource set is simply a folder with a few YAML and/or JSON templates in it. | ||||
| 
 | ||||
| <!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --> | ||||
| **Table of Contents** | ||||
| 
 | ||||
| - [Resource Sets](#resource-sets) | ||||
| - [Creating resource sets](#creating-resource-sets) | ||||
|     - [Default variables](#default-variables) | ||||
| - [Including resource sets](#including-resource-sets) | ||||
|     - [Fields](#fields) | ||||
|         - [`name`](#name) | ||||
|         - [`path`](#path) | ||||
|         - [`values`](#values) | ||||
|         - [`args`](#args) | ||||
|         - [`include`](#include) | ||||
|     - [Multiple includes](#multiple-includes) | ||||
|     - [Nesting resource sets](#nesting-resource-sets) | ||||
|         - [Caveats](#caveats) | ||||
| 
 | ||||
| <!-- markdown-toc end --> | ||||
| 
 | ||||
| # Creating resource sets | ||||
| 
 | ||||
| Simply create a folder in your Kontemplate repository and place a YAML or JSON file in it. These | ||||
| files get interpreted as [templates][] during Kontemplate runs and variables (as well as template | ||||
| logic or functions) will be interpolated. | ||||
| 
 | ||||
| Refer to the template documentation for information on how to write templates. | ||||
| 
 | ||||
| ## Default variables | ||||
| 
 | ||||
| Sometimes it is useful to specify default values for variables that should be interpolated during | ||||
| a run if the [cluster configuration][] does not specify a variable explicitly. | ||||
| 
 | ||||
| This can be done simply by placing a `default.yaml` or `default.json` file in the resource set | ||||
| folder and filling it with key/value pairs of the intended default variables. | ||||
| 
 | ||||
| Kontemplate will error during interpolation if any variables are left unspecified. | ||||
| 
 | ||||
| # Including resource sets | ||||
| 
 | ||||
| Under the cluster configuration `include` key resource sets are included and required variables | ||||
| are specified. For example: | ||||
| 
 | ||||
| ```yaml | ||||
| include: | ||||
|   - name: some-api | ||||
|     values: | ||||
|       version: 1.2-SNAPSHOT | ||||
| ``` | ||||
| 
 | ||||
| This will include a resource set from a folder called `some-api` and set the specified `version` variable. | ||||
| 
 | ||||
| ## Fields | ||||
| 
 | ||||
| The available fields when including a resource set are these: | ||||
| 
 | ||||
| ### `name` | ||||
| 
 | ||||
| The `name` field contains the name of the resource set. This name can be used to refer to the resource set | ||||
| when specifying explicit includes or excludes during a run. | ||||
| 
 | ||||
| By default it is assumed that the `name` is the path to the resource set folder, but this can be overridden. | ||||
| 
 | ||||
| This field is **required**. | ||||
| 
 | ||||
| ### `path` | ||||
| 
 | ||||
| The `path` field specifies an explicit path to a resource set folder in the case that it should differ from | ||||
| the resource set's `name`. | ||||
| 
 | ||||
| This field is **optional**. | ||||
| 
 | ||||
| ### `values` | ||||
| 
 | ||||
| The `values` field specifies key/values pairs of variables that should be available during templating. | ||||
| 
 | ||||
| This field is **optional**. | ||||
| 
 | ||||
| ### `args` | ||||
| 
 | ||||
| The `args` field specifies a list of arguments that should be passed to `kubectl`. | ||||
| 
 | ||||
| This field is **optional**. | ||||
| 
 | ||||
| ### `include` | ||||
| 
 | ||||
| The `include` field specifies additional resource sets that should be included and that should inherit the | ||||
| variables of this resource set. | ||||
| 
 | ||||
| The fully qualified names of "nested" resource sets are set to `${PARENT_NAME}/${CHILD_NAME}` and paths are | ||||
| merged in the same way. | ||||
| 
 | ||||
| This makes it easy to organise different resource sets as "groups" to include / exclude them collectively | ||||
| during runs. | ||||
| 
 | ||||
| This field is **optional**. | ||||
| 
 | ||||
| ## Multiple includes | ||||
| 
 | ||||
| Resource sets can be included multiple times with different configurations. In this case it is recommended | ||||
| to set the `path` and `name` fields explicitly. For example: | ||||
| 
 | ||||
| ```yaml | ||||
| include: | ||||
|   - name: forwarder-europe | ||||
|     path: tools/forwarder | ||||
|     values: | ||||
|       source: europe | ||||
|   - name: forwarder-asia | ||||
|     path: tools/forwarder | ||||
|     values: | ||||
|       source: asia | ||||
| ``` | ||||
| 
 | ||||
| The two different configurations can be referred to by their set names, but will use the same resource | ||||
| templates with different configurations. | ||||
| 
 | ||||
| ## Nesting resource sets | ||||
| 
 | ||||
| As mentioned above for the `include` field, resource sets can be nested. This lets users group resource | ||||
| sets in logical ways using simple folder structures. | ||||
| 
 | ||||
| Assuming a folder structure like: | ||||
| 
 | ||||
| ``` | ||||
| ├── backend | ||||
| │   ├── auth-api | ||||
| │   ├── message-api | ||||
| │   └── order-api | ||||
| └── frontend | ||||
|     ├── app-page | ||||
|     └── login-page | ||||
| ``` | ||||
| 
 | ||||
| With each of these folders being a resource set, they could be included in a cluster configuration like so: | ||||
| 
 | ||||
| ```yaml | ||||
| include: | ||||
|   - name: backend | ||||
|     include: | ||||
|       - name: auth-api | ||||
|       - name: message-api | ||||
|       - name: order-api | ||||
|   - name: frontend: | ||||
|     include: | ||||
|       - name: app-page | ||||
|       - name: login-page | ||||
| ``` | ||||
| 
 | ||||
| Kontemplate could then be run with, for example, `--include backend` to only include the resource sets nested | ||||
| in the backend group. Specific resource sets can also be targeted, for example as `--include backend/order-api`. | ||||
| 
 | ||||
| Variables specified in the parent resource set are inherited by the children. | ||||
| 
 | ||||
| ### Caveats | ||||
| 
 | ||||
| Two caveats apply that users should be aware of: | ||||
| 
 | ||||
| 1. The parent resource set can not contain any resource templates itself. | ||||
| 
 | ||||
| 2. Only one level of nesting is supported. Specifying `include` again on a nested resource set will be ignored. | ||||
| 
 | ||||
| [templates]: templates.md | ||||
| [cluster configuration]: cluster-config.md | ||||
|  | @ -1,153 +0,0 @@ | |||
| Kontemplate templates | ||||
| ===================== | ||||
| 
 | ||||
| The template file format is based on Go's [templating engine][] in combination | ||||
| with a small extension library called [sprig][] that adds additional template | ||||
| functions. | ||||
| 
 | ||||
| Go templates can either simply display variables or build more complicated | ||||
| *pipelines* in which variables are passed to functions for further processing, | ||||
| or in which conditionals are evaluated for more complex template logic. | ||||
| 
 | ||||
| It is recommended that you check out the Golang [documentation][] for the templating | ||||
| engine in addition to the cherry-picked features listed here. | ||||
| 
 | ||||
| <!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --> | ||||
| **Table of Contents** | ||||
| 
 | ||||
| - [Kontemplate templates](#kontemplate-templates) | ||||
|     - [Basic variable interpolation](#basic-variable-interpolation) | ||||
|         - [Example:](#example) | ||||
|     - [Template functions](#template-functions) | ||||
|     - [Examples:](#examples) | ||||
|     - [Conditionals & ranges](#conditionals--ranges) | ||||
|     - [Caveats](#caveats) | ||||
| 
 | ||||
| <!-- markdown-toc end --> | ||||
| 
 | ||||
| ## Basic variable interpolation | ||||
| 
 | ||||
| The basic template format uses `{{ .variableName }}` as the interpolation format. | ||||
| 
 | ||||
| ### Example: | ||||
| 
 | ||||
| Assuming that you include a resource set as such: | ||||
| 
 | ||||
| ``` | ||||
| - name: api-gateway | ||||
|   values: | ||||
|     internalHost: http://my-internal-host/ | ||||
| ``` | ||||
| 
 | ||||
| And the api-gateway resource set includes a ConfigMap (some fields left out for | ||||
| the example): | ||||
| 
 | ||||
| ``` | ||||
| # api-gateway/configmap.yaml: | ||||
| --- | ||||
| kind: ConfigMap | ||||
| metadata: | ||||
|   name: api-gateway-config | ||||
| data: | ||||
|   internalHost: {{ .internalHost }} | ||||
| ``` | ||||
| 
 | ||||
| The resulting output will be: | ||||
| 
 | ||||
| ``` | ||||
| 
 | ||||
| --- | ||||
| kind: ConfigMap | ||||
| metadata: | ||||
|   name: api-gateway-config | ||||
| data: | ||||
|   internalHost: http://my-internal-host/ | ||||
| ``` | ||||
| 
 | ||||
| ## Template functions | ||||
| 
 | ||||
| Go templates support template functions which you can think of as a sort of | ||||
| shell-like pipeline where text flows through transformations from left to | ||||
| right. | ||||
| 
 | ||||
| Some template functions come from Go's standard library and are listed in the | ||||
| [Go documentation][]. In addition the functions declared by [sprig][] are | ||||
| available in kontemplate, as well as five custom functions: | ||||
| 
 | ||||
| * `json`: Encodes any supplied data structure as JSON. | ||||
| * `gitHEAD`: Retrieves the commit hash at Git `HEAD`. | ||||
| * `passLookup`: Looks up the supplied key in [pass][]. | ||||
| * `insertFile`: Insert the contents of the given file in the resource | ||||
|   set folder as a string. | ||||
| * `insertTemplate`: Insert the contents of the given template in the resource | ||||
|   set folder as a string. | ||||
| 
 | ||||
| ## Examples: | ||||
| 
 | ||||
| ``` | ||||
| # With the following values: | ||||
| name: Donald | ||||
| certKeyPath: my-website/cert-key | ||||
| 
 | ||||
| # The following interpolations are possible: | ||||
| 
 | ||||
| {{ .name | upper }} | ||||
| -> DONALD | ||||
| 
 | ||||
| {{ .name | upper | repeat 2 }} | ||||
| -> DONALD DONALD | ||||
| 
 | ||||
| {{ .certKeyPath | passLookup }} | ||||
| -> Returns content of 'my-website/cert-key' from pass | ||||
| 
 | ||||
| {{ gitHEAD }} | ||||
| -> Returns the Git commit hash at HEAD. | ||||
| ``` | ||||
| 
 | ||||
| ## Conditionals & ranges | ||||
| 
 | ||||
| Some logic is supported in Golang templates and can be used in Kontemplate, too. | ||||
| 
 | ||||
| With the following values: | ||||
| 
 | ||||
| ``` | ||||
| useKube2IAM: true | ||||
| servicePorts: | ||||
|   - 8080 | ||||
|   - 9090 | ||||
| ``` | ||||
| 
 | ||||
| The following interpolations are possible: | ||||
| 
 | ||||
| ``` | ||||
| # Conditionally insert something in the template: | ||||
| metadata: | ||||
|   annotations: | ||||
|     foo: bar | ||||
|     {{ if .useKube2IAM -}} iam.amazonaws.com/role: my-api {{- end }} | ||||
| ``` | ||||
| 
 | ||||
| ``` | ||||
| # Iterate over a list of values | ||||
| ports: | ||||
|   {{ range .servicePorts }} | ||||
|   - port: {{ . }} | ||||
|   {{ end }} | ||||
| ``` | ||||
| 
 | ||||
| Check out the Golang documentation (linked above) for more information about template logic. | ||||
| 
 | ||||
| ## Caveats | ||||
| 
 | ||||
| Kontemplate does not by itself parse any of the content of the templates, which | ||||
| means that it does not validate whether the resources you supply are valid YAML | ||||
| or JSON. | ||||
| 
 | ||||
| You can perform some validation by using `kontemplate apply --dry-run` which | ||||
| will make use of the Dry-Run functionality in `kubectl`. | ||||
| 
 | ||||
| [templating engine]: https://golang.org/pkg/text/template/ | ||||
| [documentation]: https://golang.org/pkg/text/template/ | ||||
| [sprig]: http://masterminds.github.io/sprig/ | ||||
| [Go documentation]: https://golang.org/pkg/text/template/#hdr-Functions | ||||
| [pass]: https://www.passwordstore.org/ | ||||
|  | @ -1,77 +0,0 @@ | |||
| Kontemplate tips & tricks | ||||
| ========================= | ||||
| 
 | ||||
| <!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --> | ||||
| **Table of Contents** | ||||
| 
 | ||||
| - [Kontemplate tips & tricks](#kontemplate-tips--tricks) | ||||
|     - [Update Deployments when ConfigMaps change](#update-deployments-when-configmaps-change) | ||||
|     - [direnv & pass](#direnv--pass) | ||||
| 
 | ||||
| <!-- markdown-toc end --> | ||||
| 
 | ||||
| ## Update Deployments when ConfigMaps change | ||||
| 
 | ||||
| Kubernetes does [not currently][] have the ability to perform rolling updates | ||||
| of Deployments and other resource types when `ConfigMap` or `Secret` objects | ||||
| are updated. | ||||
| 
 | ||||
| It is possible to make use of annotations and templating functions in | ||||
| Kontemplate to force updates to these resources anyways. | ||||
|   | ||||
| For example: | ||||
| 
 | ||||
| ```yaml | ||||
| # A ConfigMap that contains some configuration for your app | ||||
| --- | ||||
| kind: ConfigMap | ||||
| metadata: | ||||
|   name: app-config | ||||
| data: | ||||
|   app.conf: | | ||||
|     name: {{ .appName }} | ||||
|     foo: bar | ||||
| ``` | ||||
| 
 | ||||
| Now whenever the `appName` variable changes or we make an edit to the | ||||
| `ConfigMap` we would like to update the `Deployment` making use of it, too. We | ||||
| can do this by adding a hash of the parsed template to the annotations of the | ||||
| created `Pod` objects: | ||||
| 
 | ||||
| ```yaml | ||||
| 
 | ||||
| --- | ||||
| kind: Deployment | ||||
| metadata: | ||||
|   name: app | ||||
| spec: | ||||
|   template: | ||||
|     metadata: | ||||
|       annotations: | ||||
|         configHash: {{ insertTemplate "app-config.yaml" | sha256sum }} | ||||
|     spec: | ||||
|       containers: | ||||
|         - name: app | ||||
|           # Some details omitted ...  | ||||
|           volumeMounts: | ||||
|             - name: config | ||||
|               mountPath: /etc/app/ | ||||
|       volumes: | ||||
|         - name: config | ||||
|           configMap: | ||||
|             name: app-config | ||||
| ``` | ||||
| 
 | ||||
| Now any change to the `ConfigMap` - either by directly editing the yaml file or | ||||
| via a changed template variable - will cause the annotation to change, | ||||
| triggering a rolling update of all relevant pods. | ||||
| 
 | ||||
| ## direnv & pass | ||||
| 
 | ||||
| Users of `pass` may have multiple different password stores on their machines. | ||||
| Assuming that `kontemplate` configuration exists somewhere on the filesystem | ||||
| per project, it is easy to use [direnv][] to switch to the correct | ||||
| `PASSWORD_STORE_DIR` variable when entering the folder. | ||||
| 
 | ||||
| [not currently]: https://github.com/kubernetes/kubernetes/issues/22368 | ||||
| [direnv]: https://direnv.net/ | ||||
|  | @ -1,7 +0,0 @@ | |||
| --- | ||||
| apiVersion: extensions/v1beta1 | ||||
| kind: ConfigMap | ||||
| metadata: | ||||
|   name: other-config | ||||
| data: | ||||
|   globalData: {{ .globalVar }} | ||||
|  | @ -1,16 +0,0 @@ | |||
| { | ||||
|   "context": "k8s.prod.mydomain.com", | ||||
|   "global": { | ||||
|     "globalVar": "lizards" | ||||
|   }, | ||||
|   "include": [ | ||||
|     { | ||||
|       "name": "some-api", | ||||
|       "values": { | ||||
|         "version": "1.0-SNAPSHOT-0e6884d", | ||||
|         "importantFeature": true, | ||||
|         "apiPort": 4567 | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
| } | ||||
|  | @ -1,17 +0,0 @@ | |||
| --- | ||||
| context: k8s.prod.mydomain.com | ||||
| global: | ||||
|   globalVar: lizards | ||||
| include: | ||||
|   # By default resource sets are included from a folder with the same | ||||
|   # name as the resource set's name | ||||
|   - name: some-api | ||||
|     values: | ||||
|       version: 1.0-0e6884d | ||||
|       importantFeature: true | ||||
|       apiPort: 4567 | ||||
| 
 | ||||
|   # Paths can also be specified manually (and point at single template | ||||
|   # files!) | ||||
|   - name: other-config | ||||
|     path: other-config.yaml | ||||
|  | @ -1,52 +0,0 @@ | |||
| --- | ||||
| apiVersion: v1 | ||||
| kind: Secret | ||||
| metadata: | ||||
|   name: secret-certificate | ||||
| data: | ||||
|   cert.pem: {{ passLookup "my/secret/certificate" | b64enc }} | ||||
| --- | ||||
| apiVersion: extensions/v1beta1 | ||||
| kind: ConfigMap | ||||
| metadata: | ||||
|   name: some-config | ||||
| data: | ||||
|   # The content of the example configuration file is templated in here | ||||
|   # by the 'insertFile' function and indented for YAML-compatibility | ||||
|   # with the 'indent' function: | ||||
|   some.cfg: | | ||||
| {{ insertFile "some.cfg" | indent 4 }} | ||||
| --- | ||||
| apiVersion: extensions/v1beta1 | ||||
| kind: Deployment | ||||
| metadata: | ||||
|   name: some-api | ||||
| spec: | ||||
|   replicas: 1 | ||||
|   template: | ||||
|     metadata: | ||||
|       labels: | ||||
|         app: some-api | ||||
|     spec: | ||||
|       containers: | ||||
|         - image: my.container.repo/some-api:{{ .version }} | ||||
|           name: some-api | ||||
|           env: | ||||
|             - name: ENABLE_IMPORTANT_FEATURE | ||||
|               value: {{ .importantFeature }} | ||||
|             - name: SOME_GLOBAL_VAR | ||||
|               value: {{ .globalVar }} | ||||
| --- | ||||
| apiVersion: v1 | ||||
| kind: Service | ||||
| metadata: | ||||
|   name: some-api | ||||
|   labels: | ||||
|     app: some-api | ||||
| spec: | ||||
|   selector: | ||||
|     app: some-api | ||||
|   ports: | ||||
|     - port: 80 | ||||
|       targetPort: {{ .apiPort }} | ||||
|       name: http | ||||
|  | @ -1,4 +0,0 @@ | |||
| { | ||||
|   "something": 1542, | ||||
|   "other-thing": "да" | ||||
| } | ||||
|  | @ -1,25 +0,0 @@ | |||
| module github.com/tazjin/kontemplate | ||||
| 
 | ||||
| go 1.22.3 | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/Masterminds/sprig/v3 v3.2.3 | ||||
| 	github.com/alecthomas/kingpin/v2 v2.4.0 | ||||
| 	github.com/ghodss/yaml v1.0.0 | ||||
| ) | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/Masterminds/goutils v1.1.1 // indirect | ||||
| 	github.com/Masterminds/semver/v3 v3.2.0 // indirect | ||||
| 	github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect | ||||
| 	github.com/google/uuid v1.1.1 // indirect | ||||
| 	github.com/huandu/xstrings v1.3.3 // indirect | ||||
| 	github.com/imdario/mergo v0.3.11 // indirect | ||||
| 	github.com/mitchellh/copystructure v1.0.0 // indirect | ||||
| 	github.com/mitchellh/reflectwalk v1.0.0 // indirect | ||||
| 	github.com/shopspring/decimal v1.2.0 // indirect | ||||
| 	github.com/spf13/cast v1.3.1 // indirect | ||||
| 	github.com/xhit/go-str2duration/v2 v2.1.0 // indirect | ||||
| 	golang.org/x/crypto v0.3.0 // indirect | ||||
| 	gopkg.in/yaml.v2 v2.3.0 // indirect | ||||
| ) | ||||
|  | @ -1,75 +0,0 @@ | |||
| github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= | ||||
| github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= | ||||
| github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= | ||||
| github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= | ||||
| github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= | ||||
| github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= | ||||
| github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= | ||||
| github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= | ||||
| github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= | ||||
| github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= | ||||
| github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | ||||
| github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= | ||||
| github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||
| github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= | ||||
| github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= | ||||
| github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= | ||||
| github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= | ||||
| github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= | ||||
| github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= | ||||
| github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= | ||||
| github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= | ||||
| github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= | ||||
| github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= | ||||
| github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||
| github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||||
| github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= | ||||
| github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||||
| github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= | ||||
| github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= | ||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= | ||||
| golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= | ||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||
| golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||
| golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= | ||||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||
| golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= | ||||
| gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
|  | @ -1,15 +0,0 @@ | |||
| FROM alpine:3.10 | ||||
| 
 | ||||
| ADD hashes /root/hashes | ||||
| ADD https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubectl /usr/bin/kubectl | ||||
| ADD https://github.com/tazjin/kontemplate/releases/download/v1.8.0/kontemplate-1.8.0-6c3b299-linux-amd64.tar.gz /tmp/kontemplate.tar.gz | ||||
| 
 | ||||
| # Pass release version is 1.7.3 | ||||
| ADD https://raw.githubusercontent.com/zx2c4/password-store/74fdfb5022f317ad48d449e29543710bdad1afda/src/password-store.sh /usr/bin/pass | ||||
| 
 | ||||
| RUN sha256sum -c /root/hashes && \ | ||||
|     apk add -U bash tree gnupg git && \ | ||||
|     chmod +x /usr/bin/kubectl /usr/bin/pass && \ | ||||
|     tar xzvf /tmp/kontemplate.tar.gz && \ | ||||
|     mv kontemplate /usr/bin/kontemplate && \ | ||||
|     /usr/bin/kontemplate version | ||||
|  | @ -1,12 +0,0 @@ | |||
| Kontemplate Docker image | ||||
| ======================== | ||||
| 
 | ||||
| This builds a simple Docker image available on the Docker Hub as `tazjin/kontemplate`. | ||||
| 
 | ||||
| Builds are automated based on the Dockerfile contained here. | ||||
| 
 | ||||
| It contains both `kontemplate` and `kubectl` and can be used as part of container-based | ||||
| CI pipelines. | ||||
| 
 | ||||
| `pass` and its dependencies are also installed to enable the use of the `passLookup` | ||||
| template function if desired. | ||||
|  | @ -1,2 +0,0 @@ | |||
| a39dfdd77e4655acaabe301285cf389cb5fc8145060f5677dc93db1cc20911a4  /tmp/kontemplate.tar.gz | ||||
| 6e805054a1fb2280abb53f75b57a1b92bf9c66ffe0d2cdcd46e81b079d93c322  /usr/bin/kubectl | ||||
|  | @ -1,242 +0,0 @@ | |||
| // Copyright (C) 2016-2019  Vincent Ambo <mail@tazj.in> | ||||
| // | ||||
| // Kontemplate is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| 
 | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/alecthomas/kingpin/v2" | ||||
| 	"github.com/tazjin/kontemplate/context" | ||||
| 	"github.com/tazjin/kontemplate/templater" | ||||
| ) | ||||
| 
 | ||||
| const version string = "1.8.0" | ||||
| 
 | ||||
| // This variable will be initialised by the Go linker during the builder | ||||
| var gitHash string | ||||
| 
 | ||||
| var ( | ||||
| 	app = kingpin.New("kontemplate", "simple Kubernetes resource templating") | ||||
| 
 | ||||
| 	// Global flags | ||||
| 	includes   = app.Flag("include", "Resource sets to include explicitly").Short('i').Strings() | ||||
| 	excludes   = app.Flag("exclude", "Resource sets to exclude explicitly").Short('e').Strings() | ||||
| 	variables  = app.Flag("var", "Provide variables to templates explicitly").Strings() | ||||
| 	kubectlBin = app.Flag("kubectl", "Path to the kubectl binary (default 'kubectl')").Default("kubectl").String() | ||||
| 
 | ||||
| 	// Commands | ||||
| 	template          = app.Command("template", "Template resource sets and print them") | ||||
| 	templateFile      = template.Arg("file", "Cluster configuration file to use").Required().String() | ||||
| 	templateOutputDir = template.Flag("output", "Output directory in which to save templated files instead of printing them").Short('o').String() | ||||
| 
 | ||||
| 	apply       = app.Command("apply", "Template resources and pass to 'kubectl apply'") | ||||
| 	applyFile   = apply.Arg("file", "Cluster configuration file to use").Required().String() | ||||
| 	applyDryRun = apply.Flag("dry-run", "Print remote operations without executing them").Default("false").Bool() | ||||
| 
 | ||||
| 	replace     = app.Command("replace", "Template resources and pass to 'kubectl replace'") | ||||
| 	replaceFile = replace.Arg("file", "Cluster configuration file to use").Required().String() | ||||
| 
 | ||||
| 	delete     = app.Command("delete", "Template resources and pass to 'kubectl delete'") | ||||
| 	deleteFile = delete.Arg("file", "Cluster configuration file to use").Required().String() | ||||
| 
 | ||||
| 	create     = app.Command("create", "Template resources and pass to 'kubectl create'") | ||||
| 	createFile = create.Arg("file", "Cluster configuration file to use").Required().String() | ||||
| 
 | ||||
| 	versionCmd = app.Command("version", "Show kontemplate version") | ||||
| ) | ||||
| 
 | ||||
| func main() { | ||||
| 	app.HelpFlag.Short('h') | ||||
| 
 | ||||
| 	switch kingpin.MustParse(app.Parse(os.Args[1:])) { | ||||
| 	case template.FullCommand(): | ||||
| 		templateCommand() | ||||
| 
 | ||||
| 	case apply.FullCommand(): | ||||
| 		applyCommand() | ||||
| 
 | ||||
| 	case replace.FullCommand(): | ||||
| 		replaceCommand() | ||||
| 
 | ||||
| 	case delete.FullCommand(): | ||||
| 		deleteCommand() | ||||
| 
 | ||||
| 	case create.FullCommand(): | ||||
| 		createCommand() | ||||
| 
 | ||||
| 	case versionCmd.FullCommand(): | ||||
| 		versionCommand() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func versionCommand() { | ||||
| 	if gitHash == "" { | ||||
| 		fmt.Printf("Kontemplate version %s (git commit unknown)\n", version) | ||||
| 	} else { | ||||
| 		fmt.Printf("Kontemplate version %s (git commit: %s)\n", version, gitHash) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func templateCommand() { | ||||
| 	_, resourceSets := loadContextAndResources(templateFile) | ||||
| 
 | ||||
| 	for _, rs := range *resourceSets { | ||||
| 		if len(rs.Resources) == 0 { | ||||
| 			fmt.Fprintf(os.Stderr, "Warning: Resource set '%s' does not exist or contains no valid templates\n", rs.Name) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if *templateOutputDir != "" { | ||||
| 			templateIntoDirectory(templateOutputDir, rs) | ||||
| 		} else { | ||||
| 			for _, r := range rs.Resources { | ||||
| 				fmt.Fprintf(os.Stderr, "Rendered file %s/%s:\n", rs.Name, r.Filename) | ||||
| 				fmt.Println(r.Rendered) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func templateIntoDirectory(outputDir *string, rs templater.RenderedResourceSet) { | ||||
| 	// Attempt to create the output directory if it does not | ||||
| 	// already exist: | ||||
| 	if err := os.MkdirAll(*templateOutputDir, 0775); err != nil { | ||||
| 		app.Fatalf("Could not create output directory: %v\n", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Nested resource sets may contain slashes in their names. | ||||
| 	// These are replaced with dashes for the purpose of writing a | ||||
| 	// flat list of output files: | ||||
| 	setName := strings.Replace(rs.Name, "/", "-", -1) | ||||
| 
 | ||||
| 	for _, r := range rs.Resources { | ||||
| 		filename := fmt.Sprintf("%s/%s-%s", *templateOutputDir, setName, r.Filename) | ||||
| 		fmt.Fprintf(os.Stderr, "Writing file %s\n", filename) | ||||
| 
 | ||||
| 		file, err := os.Create(filename) | ||||
| 		if err != nil { | ||||
| 			app.Fatalf("Could not create file %s: %v\n", filename, err) | ||||
| 		} | ||||
| 
 | ||||
| 		_, err = fmt.Fprintf(file, r.Rendered) | ||||
| 		if err != nil { | ||||
| 			app.Fatalf("Error writing file %s: %v\n", filename, err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func applyCommand() { | ||||
| 	ctx, resources := loadContextAndResources(applyFile) | ||||
| 
 | ||||
| 	var kubectlArgs []string | ||||
| 
 | ||||
| 	if *applyDryRun { | ||||
| 		kubectlArgs = []string{"apply", "-f", "-", "--dry-run"} | ||||
| 	} else { | ||||
| 		kubectlArgs = []string{"apply", "-f", "-"} | ||||
| 	} | ||||
| 
 | ||||
| 	if err := runKubectlWithResources(ctx, &kubectlArgs, resources); err != nil { | ||||
| 		failWithKubectlError(err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func replaceCommand() { | ||||
| 	ctx, resources := loadContextAndResources(replaceFile) | ||||
| 	args := []string{"replace", "--save-config=true", "-f", "-"} | ||||
| 
 | ||||
| 	if err := runKubectlWithResources(ctx, &args, resources); err != nil { | ||||
| 		failWithKubectlError(err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func deleteCommand() { | ||||
| 	ctx, resources := loadContextAndResources(deleteFile) | ||||
| 	args := []string{"delete", "-f", "-"} | ||||
| 
 | ||||
| 	if err := runKubectlWithResources(ctx, &args, resources); err != nil { | ||||
| 		failWithKubectlError(err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func createCommand() { | ||||
| 	ctx, resources := loadContextAndResources(createFile) | ||||
| 	args := []string{"create", "--save-config=true", "-f", "-"} | ||||
| 
 | ||||
| 	if err := runKubectlWithResources(ctx, &args, resources); err != nil { | ||||
| 		failWithKubectlError(err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func loadContextAndResources(file *string) (*context.Context, *[]templater.RenderedResourceSet) { | ||||
| 	ctx, err := context.LoadContext(*file, variables) | ||||
| 	if err != nil { | ||||
| 		app.Fatalf("Error loading context: %v\n", err) | ||||
| 	} | ||||
| 
 | ||||
| 	resources, err := templater.LoadAndApplyTemplates(includes, excludes, ctx) | ||||
| 	if err != nil { | ||||
| 		app.Fatalf("Error templating resource sets: %v\n", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return ctx, &resources | ||||
| } | ||||
| 
 | ||||
| func runKubectlWithResources(c *context.Context, kubectlArgs *[]string, resourceSets *[]templater.RenderedResourceSet) error { | ||||
| 	argsWithContext := append(*kubectlArgs, fmt.Sprintf("--context=%s", c.Name)) | ||||
| 
 | ||||
| 	for _, rs := range *resourceSets { | ||||
| 		if len(rs.Resources) == 0 { | ||||
| 			fmt.Fprintf(os.Stderr, "Warning: Resource set '%s' contains no valid templates\n", rs.Name) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		argsWithResourceSetArgs := append(argsWithContext, rs.Args...) | ||||
| 
 | ||||
| 		kubectl := exec.Command(*kubectlBin, argsWithResourceSetArgs...) | ||||
| 
 | ||||
| 		stdin, err := kubectl.StdinPipe() | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("kubectl error: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		kubectl.Stdout = os.Stdout | ||||
| 		kubectl.Stderr = os.Stderr | ||||
| 
 | ||||
| 		if err = kubectl.Start(); err != nil { | ||||
| 			return fmt.Errorf("kubectl error: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		for _, r := range rs.Resources { | ||||
| 			fmt.Printf("Passing file %s/%s to kubectl\n", rs.Name, r.Filename) | ||||
| 			fmt.Fprintln(stdin, r.Rendered) | ||||
| 		} | ||||
| 		stdin.Close() | ||||
| 
 | ||||
| 		if err = kubectl.Wait(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func failWithKubectlError(err error) { | ||||
| 	app.Fatalf("Kubectl error: %v\n", err) | ||||
| } | ||||
|  | @ -1,58 +0,0 @@ | |||
| # Copyright (C) 2016-2019  Vincent Ambo <mail@tazj.in> | ||||
| # | ||||
| # This file is part of Kontemplate. | ||||
| # | ||||
| # Kontemplate is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # This file is the Nix derivation used to build release binaries for | ||||
| # several different architectures and operating systems. | ||||
| 
 | ||||
| let | ||||
|   pkgs = import | ||||
|     ((import <nixpkgs> { }).fetchFromGitHub { | ||||
|       owner = "NixOS"; | ||||
|       repo = "nixpkgs-channels"; | ||||
|       rev = "541d9cce8af7a490fb9085305939569567cb58e6"; | ||||
|       sha256 = "0jgz72hhzkd5vyq5v69vpljjlnf0lqaz7fh327bvb3cvmwbfxrja"; | ||||
|     }) | ||||
|     { }; | ||||
| in | ||||
| with pkgs; buildGoPackage rec { | ||||
|   name = "kontemplate-${version}"; | ||||
|   version = "canon"; | ||||
|   src = ./.; | ||||
|   goPackagePath = "github.com/tazjin/kontemplate"; | ||||
|   goDeps = ./deps.nix; | ||||
| 
 | ||||
|   # This configuration enables the building of statically linked | ||||
|   # executables. For some reason, those will have multiple references | ||||
|   # to the Go compiler's installation path in them, which is the | ||||
|   # reason for setting the 'allowGoReference' flag. | ||||
|   dontStrip = true; # Linker configuration handles stripping | ||||
|   allowGoReference = true; | ||||
|   CGO_ENABLED = "0"; | ||||
|   GOCACHE = "off"; | ||||
| 
 | ||||
|   # Configure release builds via the "build-matrix" script: | ||||
|   buildInputs = [ git ]; | ||||
|   buildPhase = '' | ||||
|     cd go/src/${goPackagePath} | ||||
|     patchShebangs build-release.sh | ||||
|     ./build-release.sh build | ||||
|   ''; | ||||
| 
 | ||||
|   outputs = [ "out" ]; | ||||
|   installPhase = '' | ||||
|     mkdir $out | ||||
|     cp -r release/ $out | ||||
|   ''; | ||||
| 
 | ||||
|   meta = with lib; { | ||||
|     description = "A resource templating helper for Kubernetes"; | ||||
|     homepage = "http://kontemplate.works/"; | ||||
|     license = licenses.gpl3; | ||||
|   }; | ||||
| } | ||||
|  | @ -1,35 +0,0 @@ | |||
| // Copyright (C) 2016-2019  Vincent Ambo <mail@tazj.in> | ||||
| // | ||||
| // This file is part of Kontemplate. | ||||
| // | ||||
| // Kontemplate is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This file contains the implementation of a template function for retrieving | ||||
| // IP addresses from DNS | ||||
| 
 | ||||
| package templater | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"os" | ||||
| ) | ||||
| 
 | ||||
| func GetIPsFromDNS(host string) ([]interface{}, error) { | ||||
| 	fmt.Fprintf(os.Stderr, "Attempting to look up IP for %s in DNS\n", host) | ||||
| 	ips, err := net.LookupIP(host) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("IP address lookup failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	var result []interface{} = make([]interface{}, len(ips)) | ||||
| 	for i, ip := range ips { | ||||
| 		result[i] = ip | ||||
| 	} | ||||
| 
 | ||||
| 	return result, nil | ||||
| } | ||||
|  | @ -1,34 +0,0 @@ | |||
| // Copyright (C) 2016-2019  Vincent Ambo <mail@tazj.in> | ||||
| // | ||||
| // This file is part of Kontemplate. | ||||
| // | ||||
| // Kontemplate is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This file contains the implementation of a template function for retrieving | ||||
| // variables from 'pass', the standard UNIX password manager. | ||||
| 
 | ||||
| package templater | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| func GetFromPass(key string) (string, error) { | ||||
| 	fmt.Fprintf(os.Stderr, "Attempting to look up %s in pass\n", key) | ||||
| 	pass := exec.Command("pass", "show", key) | ||||
| 
 | ||||
| 	output, err := pass.CombinedOutput() | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("Pass lookup failed: %s (%v)", output, err) | ||||
| 	} | ||||
| 
 | ||||
| 	trimmed := strings.TrimSpace(string(output)) | ||||
| 
 | ||||
| 	return trimmed, nil | ||||
| } | ||||
|  | @ -1,236 +0,0 @@ | |||
| // Copyright (C) 2016-2019  Vincent Ambo <mail@tazj.in> | ||||
| // | ||||
| // This file is part of Kontemplate. | ||||
| // | ||||
| // Kontemplate is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| 
 | ||||
| package templater | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| 	"text/template" | ||||
| 
 | ||||
| 	"github.com/Masterminds/sprig/v3" | ||||
| 	"github.com/tazjin/kontemplate/context" | ||||
| 	"github.com/tazjin/kontemplate/util" | ||||
| ) | ||||
| 
 | ||||
| const failOnMissingKeys string = "missingkey=error" | ||||
| 
 | ||||
| type RenderedResource struct { | ||||
| 	Filename string | ||||
| 	Rendered string | ||||
| } | ||||
| 
 | ||||
| type RenderedResourceSet struct { | ||||
| 	Name      string | ||||
| 	Resources []RenderedResource | ||||
| 	Args      []string | ||||
| } | ||||
| 
 | ||||
| func LoadAndApplyTemplates(include *[]string, exclude *[]string, c *context.Context) ([]RenderedResourceSet, error) { | ||||
| 	limitedResourceSets := applyLimits(&c.ResourceSets, include, exclude) | ||||
| 	renderedResourceSets := make([]RenderedResourceSet, 0) | ||||
| 
 | ||||
| 	if len(*limitedResourceSets) == 0 { | ||||
| 		return renderedResourceSets, fmt.Errorf("No valid resource sets included!") | ||||
| 	} | ||||
| 
 | ||||
| 	for _, rs := range *limitedResourceSets { | ||||
| 		set, err := processResourceSet(c, &rs) | ||||
| 
 | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		renderedResourceSets = append(renderedResourceSets, *set) | ||||
| 	} | ||||
| 
 | ||||
| 	return renderedResourceSets, nil | ||||
| } | ||||
| 
 | ||||
| func processResourceSet(ctx *context.Context, rs *context.ResourceSet) (*RenderedResourceSet, error) { | ||||
| 	fmt.Fprintf(os.Stderr, "Loading resources for %s\n", rs.Name) | ||||
| 
 | ||||
| 	fileInfo, err := os.Stat(rs.Path) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	var files []os.FileInfo | ||||
| 	var resources []RenderedResource | ||||
| 
 | ||||
| 	// Treat single-file resource paths separately from resource | ||||
| 	// sets containing multiple templates | ||||
| 	if fileInfo.IsDir() { | ||||
| 		// Explicitly discard this error, which will give us an empty | ||||
| 		// list of files instead. | ||||
| 		// This will end up printing a warning to the user, but it | ||||
| 		// won't stop the rest of the process. | ||||
| 		files, _ = ioutil.ReadDir(rs.Path) | ||||
| 		resources, err = processFiles(ctx, rs, files) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} else { | ||||
| 		resource, err := templateFile(ctx, rs, rs.Path) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		resources = []RenderedResource{resource} | ||||
| 	} | ||||
| 
 | ||||
| 	return &RenderedResourceSet{ | ||||
| 		Name:      rs.Name, | ||||
| 		Resources: resources, | ||||
| 		Args:      rs.Args, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func processFiles(ctx *context.Context, rs *context.ResourceSet, files []os.FileInfo) ([]RenderedResource, error) { | ||||
| 	resources := make([]RenderedResource, 0) | ||||
| 
 | ||||
| 	for _, file := range files { | ||||
| 		if !file.IsDir() && isResourceFile(file) { | ||||
| 			path := path.Join(rs.Path, file.Name()) | ||||
| 			res, err := templateFile(ctx, rs, path) | ||||
| 
 | ||||
| 			if err != nil { | ||||
| 				return resources, err | ||||
| 			} | ||||
| 
 | ||||
| 			resources = append(resources, res) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return resources, nil | ||||
| } | ||||
| 
 | ||||
| func templateFile(ctx *context.Context, rs *context.ResourceSet, filepath string) (RenderedResource, error) { | ||||
| 	var resource RenderedResource | ||||
| 
 | ||||
| 	tpl, err := template.New(path.Base(filepath)).Funcs(templateFuncs(ctx, rs)).Option(failOnMissingKeys).ParseFiles(filepath) | ||||
| 	if err != nil { | ||||
| 		return resource, fmt.Errorf("Could not load template %s: %v", filepath, err) | ||||
| 	} | ||||
| 
 | ||||
| 	var b bytes.Buffer | ||||
| 	err = tpl.Execute(&b, rs.Values) | ||||
| 	if err != nil { | ||||
| 		return resource, fmt.Errorf("Error while templating %s: %v", filepath, err) | ||||
| 	} | ||||
| 
 | ||||
| 	resource = RenderedResource{ | ||||
| 		Filename: path.Base(filepath), | ||||
| 		Rendered: b.String(), | ||||
| 	} | ||||
| 
 | ||||
| 	return resource, nil | ||||
| } | ||||
| 
 | ||||
| // Applies the limits of explicitly included or excluded resources and returns the updated resource set. | ||||
| // Exclude takes priority over include | ||||
| func applyLimits(rs *[]context.ResourceSet, include *[]string, exclude *[]string) *[]context.ResourceSet { | ||||
| 	if len(*include) == 0 && len(*exclude) == 0 { | ||||
| 		return rs | ||||
| 	} | ||||
| 
 | ||||
| 	// Exclude excluded resource sets | ||||
| 	excluded := make([]context.ResourceSet, 0) | ||||
| 	for _, r := range *rs { | ||||
| 		if !matchesResourceSet(exclude, &r) { | ||||
| 			excluded = append(excluded, r) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Include included resource sets | ||||
| 	if len(*include) == 0 { | ||||
| 		return &excluded | ||||
| 	} | ||||
| 	included := make([]context.ResourceSet, 0) | ||||
| 	for _, r := range excluded { | ||||
| 		if matchesResourceSet(include, &r) { | ||||
| 			included = append(included, r) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return &included | ||||
| } | ||||
| 
 | ||||
| // Check whether an include/exclude string slice matches a resource set | ||||
| func matchesResourceSet(s *[]string, rs *context.ResourceSet) bool { | ||||
| 	for _, r := range *s { | ||||
| 		r = strings.TrimSuffix(r, "/") | ||||
| 		if r == rs.Name || r == rs.Parent { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func templateFuncs(c *context.Context, rs *context.ResourceSet) template.FuncMap { | ||||
| 	m := sprig.TxtFuncMap() | ||||
| 	m["json"] = func(data interface{}) string { | ||||
| 		b, _ := json.Marshal(data) | ||||
| 		return string(b) | ||||
| 	} | ||||
| 	m["passLookup"] = GetFromPass | ||||
| 	m["gitHEAD"] = func() (string, error) { | ||||
| 		out, err := exec.Command("git", "-C", c.BaseDir, "rev-parse", "HEAD").Output() | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		output := strings.TrimSpace(string(out)) | ||||
| 		return output, nil | ||||
| 	} | ||||
| 	m["lookupIPAddr"] = GetIPsFromDNS | ||||
| 	m["insertFile"] = func(file string) (string, error) { | ||||
| 		data, err := ioutil.ReadFile(path.Join(rs.Path, file)) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 
 | ||||
| 		return string(data), nil | ||||
| 	} | ||||
| 	m["insertTemplate"] = func(file string) (string, error) { | ||||
| 		data, err := templateFile(c, rs, path.Join(rs.Path, file)) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 
 | ||||
| 		return data.Rendered, nil | ||||
| 	} | ||||
| 	m["default"] = func(defaultVal interface{}, varName string) interface{} { | ||||
| 		if val, ok := rs.Values[varName]; ok { | ||||
| 			return val | ||||
| 		} | ||||
| 
 | ||||
| 		return defaultVal | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| // Checks whether a file is a resource file (i.e. is YAML or JSON) and not a default values file. | ||||
| func isResourceFile(f os.FileInfo) bool { | ||||
| 	for _, defaultFile := range util.DefaultFilenames { | ||||
| 		if f.Name() == defaultFile { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return strings.HasSuffix(f.Name(), "yaml") || | ||||
| 		strings.HasSuffix(f.Name(), "yml") || | ||||
| 		strings.HasSuffix(f.Name(), "json") | ||||
| } | ||||
|  | @ -1,205 +0,0 @@ | |||
| // Copyright (C) 2016-2019  Vincent Ambo <mail@tazj.in> | ||||
| // | ||||
| // This file is part of Kontemplate. | ||||
| // | ||||
| // Kontemplate is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| 
 | ||||
| package templater | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/tazjin/kontemplate/context" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| func TestApplyNoLimits(t *testing.T) { | ||||
| 	resources := []context.ResourceSet{ | ||||
| 		{ | ||||
| 			Name: "testResourceSet1", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name: "testResourceSet2", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	result := applyLimits(&resources, &[]string{}, &[]string{}) | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(resources, *result) { | ||||
| 		t.Error("Resource set slice changed, but shouldn't have.") | ||||
| 		t.Errorf("Expected: %v\nResult: %v\n", resources, *result) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestApplyIncludeLimits(t *testing.T) { | ||||
| 	resources := []context.ResourceSet{ | ||||
| 		{ | ||||
| 			Name: "testResourceSet1", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name: "testResourceSet2", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "testResourceSet3", | ||||
| 			Parent: "included", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	includes := []string{"testResourceSet1", "included"} | ||||
| 
 | ||||
| 	result := applyLimits(&resources, &includes, &[]string{}) | ||||
| 
 | ||||
| 	expected := []context.ResourceSet{ | ||||
| 		{ | ||||
| 			Name: "testResourceSet1", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "testResourceSet3", | ||||
| 			Parent: "included", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(expected, *result) { | ||||
| 		t.Error("Result does not contain expected resource sets.") | ||||
| 		t.Errorf("Expected: %v\nResult: %v\n", expected, *result) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestApplyExcludeLimits(t *testing.T) { | ||||
| 	resources := []context.ResourceSet{ | ||||
| 		{ | ||||
| 			Name: "testResourceSet1", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name: "testResourceSet2", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "testResourceSet3", | ||||
| 			Parent: "included", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	exclude := []string{"testResourceSet2"} | ||||
| 
 | ||||
| 	result := applyLimits(&resources, &[]string{}, &exclude) | ||||
| 
 | ||||
| 	expected := []context.ResourceSet{ | ||||
| 		{ | ||||
| 			Name: "testResourceSet1", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "testResourceSet3", | ||||
| 			Parent: "included", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(expected, *result) { | ||||
| 		t.Error("Result does not contain expected resource sets.") | ||||
| 		t.Errorf("Expected: %v\nResult: %v\n", expected, *result) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestApplyLimitsExcludeIncludePrecedence(t *testing.T) { | ||||
| 	resources := []context.ResourceSet{ | ||||
| 		{ | ||||
| 			Name:   "collection/nested1", | ||||
| 			Parent: "collection", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "collection/nested2", | ||||
| 			Parent: "collection", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "collection/nested3", | ||||
| 			Parent: "collection", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name: "something-else", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	include := []string{"collection"} | ||||
| 	exclude := []string{"collection/nested2"} | ||||
| 
 | ||||
| 	result := applyLimits(&resources, &include, &exclude) | ||||
| 
 | ||||
| 	expected := []context.ResourceSet{ | ||||
| 		{ | ||||
| 			Name:   "collection/nested1", | ||||
| 			Parent: "collection", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "collection/nested3", | ||||
| 			Parent: "collection", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(expected, *result) { | ||||
| 		t.Error("Result does not contain expected resource sets.") | ||||
| 		t.Errorf("Expected: %v\nResult: %v\n", expected, *result) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestFailOnMissingKeys(t *testing.T) { | ||||
| 	ctx := context.Context{} | ||||
| 	resourceSet := context.ResourceSet{} | ||||
| 
 | ||||
| 	_, err := templateFile(&ctx, &resourceSet, "testdata/test-template.txt") | ||||
| 
 | ||||
| 	if err == nil { | ||||
| 		t.Errorf("Template with missing keys should have failed.\n") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	if !strings.Contains(err.Error(), "map has no entry for key \"testName\"") { | ||||
| 		t.Errorf("Templating failed with unexpected error: %v\n", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestDefaultTemplateFunction(t *testing.T) { | ||||
| 	ctx := context.Context{} | ||||
| 	resourceSet := context.ResourceSet{} | ||||
| 
 | ||||
| 	res, err := templateFile(&ctx, &resourceSet, "testdata/test-default.txt") | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Templating with default values should have succeeded.\n") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	if res.Rendered != "defaultValue\n" { | ||||
| 		t.Error("Result does not contain expected rendered default value.") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestInsertTemplateFunction(t *testing.T) { | ||||
| 	ctx := context.Context{} | ||||
| 	resourceSet := context.ResourceSet{ | ||||
| 		Path: "testdata", | ||||
| 		Values: map[string]interface{}{ | ||||
| 			"testName": "TestInsertTemplateFunction", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	res, err := templateFile(&ctx, &resourceSet, "testdata/test-insertTemplate.txt") | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 		t.Errorf("Templating with an insertTemplate call should have succeeded.\n") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 
 | ||||
| 	if res.Rendered != "Inserting \"Template for test TestInsertTemplateFunction\".\n" { | ||||
| 		t.Error("Result does not contain expected rendered template value.") | ||||
| 		t.Error(res.Rendered) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
|  | @ -1 +0,0 @@ | |||
| {{ default "defaultValue" "missingVar" }} | ||||
|  | @ -1 +0,0 @@ | |||
| Inserting "{{ insertTemplate "test-template.txt" | trim }}". | ||||
|  | @ -1 +0,0 @@ | |||
| Template for test {{ .testName }} | ||||
|  | @ -1,74 +0,0 @@ | |||
| // Copyright (C) 2016-2019  Vincent Ambo <mail@tazj.in> | ||||
| // | ||||
| // This file is part of Kontemplate. | ||||
| // | ||||
| // Kontemplate is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| 
 | ||||
| package util | ||||
| 
 | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 
 | ||||
| 	"github.com/ghodss/yaml" | ||||
| ) | ||||
| 
 | ||||
| // Filenames excluded from templating for the purpose of containing default variable values inside a resource set. | ||||
| var DefaultFilenames []string = []string{"default.yml", "default.yaml", "default.json"} | ||||
| 
 | ||||
| // Merges two maps together. Values from the second map override values in the first map. | ||||
| // The returned map is new if anything was changed. | ||||
| func Merge(in1 *map[string]interface{}, in2 *map[string]interface{}) *map[string]interface{} { | ||||
| 	if in1 == nil || len(*in1) == 0 { | ||||
| 		return in2 | ||||
| 	} | ||||
| 
 | ||||
| 	if in2 == nil || len(*in2) == 0 { | ||||
| 		return in1 | ||||
| 	} | ||||
| 
 | ||||
| 	// The maps are map[string]interface{} with unknown depth. | ||||
| 	// Loop over both maps into every level and merge them. | ||||
| 	new := make(map[string]interface{}) | ||||
| 
 | ||||
| 	for k, v := range *in1 { | ||||
| 		new[k] = v | ||||
| 	} | ||||
| 
 | ||||
| 	for k, v := range *in2 { | ||||
| 		if existing, ok := new[k]; ok { | ||||
| 			// If both values are maps, merge them recursively | ||||
| 			if existingMap, ok := existing.(map[string]interface{}); ok { | ||||
| 				if newMap, ok := v.(map[string]interface{}); ok { | ||||
| 					new[k] = *Merge(&existingMap, &newMap) | ||||
| 				} else { | ||||
| 					new[k] = v | ||||
| 				} | ||||
| 			} else { | ||||
| 				new[k] = v | ||||
| 			} | ||||
| 		} else { | ||||
| 			new[k] = v | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return &new | ||||
| } | ||||
| 
 | ||||
| // Loads either a YAML or JSON file from the specified path and | ||||
| // deserialises it into the provided interface. | ||||
| func LoadData(filename string, addr interface{}) error { | ||||
| 	file, err := ioutil.ReadFile(filename) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	err = yaml.Unmarshal(file, addr) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
|  | @ -1,89 +0,0 @@ | |||
| // Copyright (C) 2016-2019  Vincent Ambo <mail@tazj.in> | ||||
| // | ||||
| // This file is part of Kontemplate. | ||||
| // | ||||
| // Kontemplate is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| 
 | ||||
| package util | ||||
| 
 | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| func TestMergeWithEmptyMap(t *testing.T) { | ||||
| 	testMap := map[string]interface{}{ | ||||
| 		"foo": "bar", | ||||
| 	} | ||||
| 
 | ||||
| 	empty := make(map[string]interface{}) | ||||
| 
 | ||||
| 	res1 := Merge(&testMap, &empty) | ||||
| 	res2 := Merge(&empty, &testMap) | ||||
| 
 | ||||
| 	if res1 != &testMap || res2 != &testMap { | ||||
| 		t.Error("A new map was returned incorrectly.") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestMergeWithNilMap(t *testing.T) { | ||||
| 	testMap := map[string]interface{}{ | ||||
| 		"foo": "bar", | ||||
| 	} | ||||
| 
 | ||||
| 	res1 := Merge(&testMap, nil) | ||||
| 	res2 := Merge(nil, &testMap) | ||||
| 
 | ||||
| 	if res1 != &testMap || res2 != &testMap { | ||||
| 		t.Error("A new map was returned incorrectly.") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestMergeMaps(t *testing.T) { | ||||
| 	map1 := map[string]interface{}{ | ||||
| 		"foo": "bar", | ||||
| 		"baz": map[string]interface{}{ | ||||
| 			"qux": "quux", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	map2 := map[string]interface{}{ | ||||
| 		"bar": "baz", | ||||
| 	} | ||||
| 
 | ||||
| 	result := Merge(&map1, &map2) | ||||
| 	expected := map[string]interface{}{ | ||||
| 		"foo": "bar", | ||||
| 		"baz": map[string]interface{}{ | ||||
| 			"qux": "quux", | ||||
| 		}, | ||||
| 		"bar": "baz", | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(*result, expected) { | ||||
| 		t.Error("Maps were merged incorrectly.") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestMergeMapsPrecedence(t *testing.T) { | ||||
| 	map1 := map[string]interface{}{ | ||||
| 		"foo": "incorrect", | ||||
| 	} | ||||
| 
 | ||||
| 	map2 := map[string]interface{}{ | ||||
| 		"foo": "correct", | ||||
| 	} | ||||
| 
 | ||||
| 	result := Merge(&map1, &map2) | ||||
| 
 | ||||
| 	if (*result)["foo"] != "correct" { | ||||
| 		t.Error("Map merge precedence test failed.") | ||||
| 		t.Fail() | ||||
| 	} | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue