├── LICENSE ├── README.md └── tmsh2iapp.pl /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > This repo is archived and is no longer actively maintained. 2 | 3 | ![tmsh2iapp main diagram](https://github.com/f5devcentral/f5-tmsh2iapp/wiki/images/tmsh2iapp_main_diagram.png) 4 | 5 | # f5-tmsh2iapp 6 | tmsh2iapp is an iApp generator using existing configurations as a template. The resulting iApps are **fully parametrizable**. It also generates Ansible playbooks & roles, Heat templates, iWorkflow JSON files and container ConfigMaps to ease the deployment 7 | 8 | The purpose of this project is to make as easy as possible deploying services in the F5, 9 | automate and orchestrate them with third party SDN/NFV/Automation/Orchestration products. 10 | 11 | One of the goals of the tool is that it's use is as simply as possible: iApp development knowlege is **not required** 12 | but some fluency in tmsh syntax is recommended. 13 | 14 | # Main characteristics of tmsh2iapp 15 | 16 | * Allows creating an iApp using a **declarative** tmsh-like syntax (these are called .t2i files) 17 | * It has been used with **LTM, ASM (limited support in v12 full support in v13+), APM, AFM and PEM modules**. 18 | * Baseline .t2i files can be created easily with the BIG-IP GUI 19 | * Helps deploying the iApp by creating sample Ansible roles & playbooks, Heat templates and iWorkflow JSON files 20 | * Input varialbles can be **JSON lists** (ie: multiple-choice/multiple-select **survey variables in Ansible Tower**) 21 | * Can easily create **iApps that contain base configurations (self-IPs, VLANs, etc...) that can be deployed in a BIG-IP cluster and config-sync'ed assigning per-BIG-IP values appropiately**. 22 | * It can be used in any computer with perl installed -- including the BIG-IP 23 | * It supports **passing parameters as JSON lists** (useful for handling multiple-choice/multiple-select survey variables in Ansible Tower or any other YAML based automation) 24 | * It is not BIG-IP version specific but it is being tested with BIG-IP 11.6-13.1 25 | * Supports **route-domains and partitions** 26 | * Supports LTM policies 27 | * It allows **importing (from external files or URL) the following configuration objects**: 28 | * apache-ssl-cert SSL certificates management 29 | * asm-policy 30 | * browser-capabilities-db browser capabilities DB file management 31 | * dashboard-viewset 32 | * data-group External Data Group files management 33 | * device-capabilities-db Device capabilities DB file management 34 | * external-monitor External Monitor files management 35 | * ifile iFile files management 36 | * lwtunneltbl LW4o6 table files management 37 | * ssl-cert SSL certificates management 38 | * ssl-crl SSL CRL files management 39 | * ssl-csr SSL Certificate Signing Request 40 | * ssl-key SSL certificate keys management 41 | * **Options available to beautify the GUI** allowing specifying field text width, validators, and single and multi-selector which lists that can be static or dynamically populated at run time. 42 | 43 | # Advantages of using iApps for automation instead of plain ansible, plain REST calls, etc... 44 | 45 | * When deploying configurations as iApps the **configurations are self-contained**, that is: on delete all objects created by the iApp are automatically deleted and there will be no stale configurations left 46 | 47 | * iApps can be deployed with ansiblle, REST, etc... **configurations just become easier to deploy** because regardless the configuration size only two calls are quired: one call to import the template (if it is noy already in the BIG-IP) and one to create an instance of the iApp. 48 | 49 | * **tmsh2iapp** allows creation of iApps that contain **base configs too (ie: L1/L2/L3 network elements including non floating ones)** which can be sync'ed in HA configurations seamlessly. 50 | 51 | # Alternatives to tmsh2iapp 52 | 53 | The world is full of choices! Please consier these: 54 | 55 | * BIG-IQ 6.0 supports creation of configuration templates, several BIG-IP modules are supported too. Functionally wise it is comprenhensive. Provides sample API calls to instantiate the resulting templates. It is also possible to deploy pre-defined templates with bigiq_application_* ansible modules in a simplified manner. 56 | 57 | * ![Application Services 3 (AS3)](http://clouddocs.f5.com/products/extensions/f5-appsvcs-extension/3/) provides another F5 supported way deploying configurations in a declarative fashion supporting common use cases/configurations using a REST interface. At present Ansible instantiation can also be performed with the URI module submiting JSON documents with the desired configuration. 58 | 59 | These two are excellent alternatives and they are officially supported by F5. On the other hand, official F5 support of tmsh2iapp is limited to the API usage that tmsh2iapp makes use of BIG-IP. This non-official open-source project will provide support on a best effort basis. 60 | 61 | tmsh2iapp plus points are: 62 | * Virtually any configuration can be templatized with tmsh2iapp. This is because tmsh2iapp doesn't intend to know much about the desired configuration. Configuration parsing is mainly left up to the BIG-IP. This makes tmsh2iapp simpler to maintain and more flexible with respect of what configuration can accept and which BIG-IP versions are supported. 63 | * tmsh2iapp iApps doesn't require an intermediary device - in the case of BIG-IQ - or intermediary REST nodejs worker (aka API Services Gateway) - in the case of AS3. 64 | * tmsh2iapp is able to generate template configuration for base configs (ie: non-floating self-IPs, VLANs, /system configs, etc..) which can be used in BIG-IP HA configurations. 65 | 66 | # Using tmsh2iapp 67 | 68 | **Download the tool from ![this link](https://raw.githubusercontent.com/f5devcentral/f5-tmsh2iapp/master/tmsh2iapp.pl).** 69 | 70 | **Please refer to ![project's wiki page](https://github.com/f5devcentral/f5-tmsh2iapp/wiki/) for documentation, examples and guidelines of use.** 71 | 72 | This tool is not supported by F5 support. If you find an issue, we would love to hear about it. Please let us know by [filling an issue](https://github.com/f5devcentral/f5-tmsh2iapp/issues) in this repository. Tell us as much as you can about what you found, how you found it, your environment, etc.. We also welcome you to file feature requests as issues. 73 | 74 | If you have any question post it in https://devcentral.f5.com/questions. Note there is also a [Frequently Asked Questions page](https://github.com/f5devcentral/f5-tmsh2iapp/wiki/02-%7C-FAQ) 75 | We would love to hear your experiences with this tool, please share them in [this link of devcentral](https://devcentral.f5.com/codeshare/tmsh2iapp-iapp-generator-create-iapps-in-minutes-1065). 76 | 77 | **tmsh2iapp is released to the community under the [Apache v2 license](http://www.apache.org/licenses/LICENSE-2.0.txt)**. It is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 78 | 79 | CLA assistant 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /tmsh2iapp.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # 3 | # tmsh2iapp.pl - A tool for easily generating an iApp from an existing TMSH configuration using a TMSH-like template 4 | # 5 | # The tool is also able to generate a HEAT template to instantiate this iApp in Openstack 6 | # 7 | ########################################################################################################################### 8 | # Copyright 2016-2018 F5 Networks Inc 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | ########################################################################################################################### 23 | # 24 | # changelog: 25 | # 26 | # Ulises Alonso Camaro 27 | # 28 | # 2016/06/10 - u.alonsocamaro@f5.com - Initial version 29 | # 2016/06/13 - u.alonsocamaro@f5.com - Embedding of TMSH configuration in the iApp and initial documentation 30 | # 2016/06/14 - u.alonsocamaro@f5.com - FIX: when there is only one pool defined 31 | # 2016/06/14 - u.alonsocamaro@f5.com - warn when pools are defined when using the "system" option 32 | # 2016/06/14 - u.alonsocamaro@f5.com - FIX: do not create var section when there are no variables 33 | # 2016/06/14 - u.alonsocamaro@f5.com - strings are now displayed with the xxlarge display hint 34 | # 2016/06/23 - u.alonsocamaro@f5.com - Force the customer to use a full path when specifying the template 35 | # 2016/06/24 - u.alonsocamaro@f5.com - Optionally allow labels and properties to variables, pool members and internal data-group records 36 | # 2016/06/24 - u.alonsocamaro@f5.com - Allow dynamic number of records for internal data-groups 37 | # 2016/06/24 - u.alonsocamaro@f5.com - pool_members_modify and data_records_modify now add code in the iApp to only modify the pool/data-group if the there are pool members/data records 38 | # 2016/06/24 - u.alonsocamaro@f5.com - FIX: allow templates which contain fields with double quotes in them 39 | # 2016/07/22 - u.alonsocamaro@f5.com - Removed app-service keyword from resulting iApp 40 | # 2016/07/22 - u.alonsocamaro@f5.com - Introduced disable strict updates. Allows the generated iApp by modified. I needed this for a use-case. 41 | # Note: This is a workaround until HEAT teampltes allow deploying iApps with strict updates disabled. 42 | # 2016/09/20 - u.alonsocamaro@f5.com - Make conditional "disable strict updates" by making it a parameter. Also found that it doesn't work for "system" iApps so this is also a FIX. 43 | # 2016/09/20 - u.alonsocamaro@f5.com - Usability improvements (hopefully :-): 44 | # + Full path of the template is no longer needed 45 | # + Now the resulting .cfg file which is load at iapp instantiation is stored in /var/tmp, this can be changed with the cfgdir variable 46 | # + Now the template file (input file of this utility) needs to have .tmpl extension 47 | # + Now the resulting iapp is not written to stdout but instead to a file in the same folder as the template but with the .iapp extension. 48 | # 2016/11/30 - u.alonsocamaro@f5.com - Added common and common-disable-strict-updates to allow creation of iApps that have objects in the /Common partition 49 | # 50 | # 2016/12/01 - u.alonsocamaro@f5.com - HEAT template creation preparation: iApp creation functions renamed with iapp_ prefix 51 | # 2016/12/01 - u.alonsocamaro@f5.com - HEAT template creation preparation: function relocation 52 | # 2016/12/01 - u.alonsocamaro@f5.com - HEAT template creation introduced 53 | # 2016/12/07 - u.alonsocamaro@f5.com - Now the input file of this tool must have .t2i extension instead of .tmpl to avoid confusion because .tmpl is sometimes used with iApps. 54 | # The iApps generated by this tool will continue having .iapp extension because it is unambiguous. 55 | # 2017/04/18 - u.alonsocamaro@f5.com - Initial support for firewall address and port lists. heat-create needs to be updated. 56 | # 2017/04/20 - u.alonsocamaro@f5.com - Assign empty strings to variables if these are not defined and assign them {} for proper substitution by string map 57 | # 2017/04/24 - u.alonsocamaro@f5.com - Allow regular variables that have values separated by spaces 58 | # 2017/04/28 - u.alonsocamaro@f5.com - Previous change makes the init_variables code not working well with empty values because they now become {}. That previous change on 2017/04/20 seems not useful any more. Commented out. 59 | # 2017/05/23 - u.alonsocamaro@f5.com - Initial support for PEM URL categories in policy rules 60 | # 2017/05/24 - u.alonsocamaro@f5.com - Some objects in the .t2i need to reference the iApp's instance folder, we make this folder available through a @service_folder token. This is only available when using the service and service-disable-strict-updates options. 61 | # 2017/05/25 - u.alonsocamaro@f5.com - Introduction of @vartypes for simplifyling the script and making easier to extend for new var types 62 | # 2017/05/25 - u.alonsocamaro@f5.com - Re-implemented heat-create using @vartypes 63 | # 2017/05/25 - u.alonsocamaro@f5.com - Implementation of ansible-create 64 | # 2017/06/09 - u.alonsocamaro@f5.com - Produced iApps are no longer have .iapp extension. Now .tmpl is used to follow the conventions. 65 | # 2017/06/09 - u.alonsocamaro@f5.com - Implementation of iapp attributes, ie allowing the following to be specified: 66 | # 67 | # description User defined description. 68 | # ignore-verification Set to true to temporarily stop the verification of signature or checksum. Signature or checksum is still retained. 69 | # requires-bigip-version-max Specifies the maximum version of BIG-IP required by this template. 70 | # requires-bigip-version-min Specifies the minimum version of BIG-IP required by this template. 71 | # requires-modules Adds, deletes, or replaces the list of modules that are required to be provisioned or enabled for this template to work. 72 | # 73 | # Example usage: 74 | # 75 | # @iapp(description): "Creates a SIP load balancer" 76 | # @iapp(ignore-verification): true 77 | # @iapp(requires-bigip-version-min): 12.1.1 78 | # @iapp(requires-modules): { ltm asm } 79 | # 80 | # Note that the syntax of the iApp attributes is the same of TMSH. 81 | # 82 | # The iapp attribute unsupported-bigip-versions is an extension that is only applied to iWorkflow. The format is defined by iWorkflow: 83 | # 84 | # @iapp(unsupported-bigip-versions): ["11.6.1", "12.0.1"] 85 | # 86 | # There is currently no support for signing or creating a checksum iApps. 87 | # 88 | # 2017/06/15 - u.alonsocamaro@f5.com - Implementation of iworkflow-json-apl and iworkflow-template-import 89 | # 2017/06/15 - u.alonsocamaro@f5.com - version and usage options 90 | # 2017/06/22 - u.alonsocamaro@f5.com - Support for iWorkflow variables __pool__addr__ and __pool__port__ for automatic statistics. See https://devcentral.f5.com/wiki/iWorkflow.iWorkflowOpsGuide_v22_7.ashx 91 | # 2017/06/27 - u.alonsocamaro@f5.com - iworkflow-template-import previously only created "service" iApps. Now any iApp type can be used. It depends in that template has been previously generated 92 | # 2017/06/27 - u.alonsocamaro@f5.com - Introduction of pool member's properties without requiring additional variable types. Found some problems in tmsh and reported in C2469542/ID671372 and C2470063 93 | # 2017/06/27 - u.alonsocamaro@f5.com - Fixed heat-create variable's template section curly braces 94 | # 2017/06/27 - u.alonsocamaro@f5.com - Added starting/finished messages when the iApp is run 95 | # 2017/06/27 - u.alonsocamaro@f5.com - @properties tag previously used for the presentation has been renamed to @apl for two reasons: 96 | # + allow easing migration to tmsh2iapp is migrated to iAppsLX 97 | # + @properties is a very generic tag and I might use it for other purposes later on 98 | # 99 | # 2017/06/28 - u.alonsocamaro@f5.com - Introduction of baseline .t2i generation. Usage: 100 | # 101 | # tmsh2iapp snapshot-create 102 | # 103 | # tmsh2iapp snapshot-create 104 | # 105 | # tmsh2iapp t2i-create 106 | # 107 | # The snapshots are stored in /var/tmp and the resulting .t2i is stored in the same folder too with the name baseline--.t2i 108 | # 109 | # 2017/07/11 - u.alonsocamaro@f5.com - pool__addr and pool__port are no longer mandated for iworkflow templates 110 | # 2017/07/12 - u.alonsocamaro@f5.com - Removal support for per BIG-IP variables 111 | # 2017/07/13 - u.alonsocamaro@f5.com - Introduction of importable objects, ie: 112 | # 113 | # @import(ssl-cert): __cert1__ __cert2__ __cert3__ 114 | # @import(ssl-crl): __crl1__ 115 | # @import(ssl-csr): __csr1__ __csr2__ 116 | # @import(ssl-key): __key1__ __key2__ 117 | # @import(data-group): __dg1__ 118 | # @import(external-monitor): __mon1__ 119 | # 120 | # Where ____ are variables in the .t2i file. The @import tag will create an additional automatic variable named ___import__ where the URL from where to import the actual object to be imported. 121 | # 122 | # This URL can be a local file (ie: file:///tmp/mycert.crt), a remote web server (ie: http://mywebserver.local.net/mycert.crt) etc... any curl URL basically 123 | # 124 | # The types of objects that can be imported depend on the BIG-IP version, in this release up to v13 object types are supported: 125 | # 126 | # apache-ssl-cert SSL certificates management 127 | # browser-capabilities-db browser capabilities DB file management 128 | # dashboard-viewset 129 | # data-group External Data Group files management 130 | # device-capabilities-db Device capabilities DB file management 131 | # external-monitor External Monitor files management 132 | # ifile iFile files management 133 | # lwtunneltbl LW4o6 table files management 134 | # ssl-cert SSL certificates management 135 | # ssl-crl SSL CRL files management 136 | # ssl-csr SSL Certificate Signing Request 137 | # ssl-key SSL certificate keys management 138 | # 139 | # Note for data-group variables a @property(____) attribute must be set, this is used to: 140 | # 141 | # - specify the type of the records in the data-group (ie: string|integer|ip). This is mandatory. 142 | # - the separator used for the records. This is optional. 143 | # 144 | # The syntax follows tmsh syntax, see the examples for more info. 145 | # 146 | # Search for import_file_types and import_perl_types variables for implementation details. Main functions are attribute_import_type, iapp_implementation_import. 147 | # 148 | # 2017/07/17 - u.alonsocamaro@f5.com - preparation functions for @import(asm-policy) 149 | # 2017/07/17 - u.alonsocamaro@f5.com - All foreach loops on variable's arrays have been sorted 150 | # 2017/07/18 - u.alonsocamaro@f5.com - Initial support of LTM policies 151 | # 152 | # Didn't find a way of loading them with tmsh::load hence these will be created with tmsh::create which requires extracting 153 | # the relevant section of the .t2i file. The load of the .t2i is now done incrementally. 154 | # 155 | # 2017/07/20 - u.alonsocamaro@f5.com - Initial support of ASM policies, they don't work yet unless using already imported policies. Researching this issue. 156 | # 2017/07/21 - u.alonsocamaro@f5.com - Support comments within the .t2i file by using the # symbol 157 | # 2017/07/21 - u.alonsocamaro@f5.com - Made more relaxed the usage of @ and #. They can have spaces from the beginning of the line now. They must match ^\s*(@|#) 158 | # 2017/07/31 - u.alonsocamaro@f5.com - Added check_variable_names to avoid mistake of using hyphen in variable names 159 | # 2017/08/01 - u.alonsocamaro@f5.com - FIX: @service_folder can now be really placed anywhere 160 | # 2017/08/02 - u.alonsocamaro@f5.com - FIX: @service_folder can now be really placed anywhere (part 2) 161 | # 2017/08/02 - u.alonsocamaro@f5.com - FIX: incremental loader would previously fail if policy is at the top of the .t2i file 162 | # 2017/09/28 - u.alonsocamaro@f5.com - Introduction of per bigip's variables. I call these __local__ variables and are intended to be used for base configs / not sync'ed configs like non-floating self-IPs 163 | # 164 | # RATIONALE 165 | # 166 | # The behaviour of iApps (in general) for non-floating/not sync'ed configuration elements is the following: 167 | # 168 | # - The iApp template is sync'ed 169 | # - On instantiation of the iApp in a BIG-IP the parameters (ie: "list sys application service ") will be also sync'ed to other BIG-IPs 170 | # but the iApps are not re-run so that local configuration is not generated in the BIG-IPs where the iApp was instantiated in the first place. 171 | # - The iApp's parameters can be changed in the other BIG-IPs where the iApp was not instantiated initially and the appropiate configuration for the BIG-IP would be in place. 172 | # 173 | # This iApp behaviour has the following inconvenients: 174 | # 175 | # - Even after config-sync When looking in the GUI of the different BIG-IPs the iApps parameters will look inconsistent in the BIG-IPs 176 | # with the exception the last one where the iApp was instantiated. 177 | # 178 | # Note although they look like inconsistent the actual local config is correct because the actual config is generated in each BIG-IP and it is not sync'ed on config-sync. 179 | # 180 | # - Because the iApp parameters change across BIG-IPs blindly re-configuring an iApp might lead to wrong configuration being applied. 181 | # 182 | # Local variables avoid these problems. Note that although they still require to be executed in all nodes at deletion time it is 183 | # only required to do a delete in any BIG-IP and then sync the configuration. 184 | # 185 | # USING __local__ VARIABLES 186 | # 187 | # The syntax is very similar to regular variables but adding the __local prefix. An example follows: 188 | # 189 | # @label(__local__self_vxlan__): IP address with prefix of the self interface connected to the VxLAN 190 | # 191 | # net self self-vxlan-__vxlanid__ { 192 | # address __local__self_vxlan__ 193 | # traffic-group /Common/traffic-group-local-only 194 | # vlan vxlan-__vxlanid__ 195 | # } 196 | # 197 | # 198 | # When there is a __local__ variable defined in a .t2i file the tmsh2iapp tool automatically generates an additional iApp parameter to indicate the BIG-IPs host names 199 | # where the values are going to be applied following the same order. This automatic variable is called local__bigip_names. 200 | # 201 | # Following the above example if we apply the next values (ie: in the GUI): 202 | # 203 | # local__bigip_names: bigip1 bigip2 204 | # local__self_vxlan: 1.2.3.4/24 1.2.3.5/24 205 | # 206 | # Then bigip1 will apply the value 1.2.3.4/24 and bigip2 will apply the value 1.2.3.5/24. 207 | # 208 | # Note that the names of the BIG-IP's are matched with the beginning of the BIG-IP's FQDN's. It is not necessary to specify the full name, 209 | # it can be "bigip1.subdomain1 bigip1.subdomain2" or just "bigip1 bigip2" as shown in the example. 210 | # 211 | # The values stored in the variables are defined as TCL lists, ie you can use the following formats when assigning the values for the different BIG-IPs 212 | # 213 | # Exemple 1: value1 value2 value3 value4 214 | # Example 2: {value1} {value2} {value3} {value4} 215 | # Example 3: {some multi value1} {} {another multi value3} {and yet one more multi value4} 216 | # 217 | # In other words, the two benefits of defining them as TCL lists allow for: 218 | # 219 | # - multi-word values 220 | # - empty values are possible too 221 | # 222 | # 223 | # 224 | # 2017/09/28 - u.alonsocamaro@f5.com - FIX: removed sort of vartypes and only sorted actual variable names. 225 | # 226 | # This at random generated wrong iApps. ie: running the tmsh2iapp over the same .t2i file eventually generated different iApps. 227 | # 228 | # Note: if your iApp was properly generated you should not worry about existing generated iApps. 229 | # 230 | # 2017/09/28 - u.alonsocamaro@f5.com - FIX: incremental loader: do not longer remove lines that contain a hash 231 | # 2017/10/13 - u.alonsocamaro@f5.com - Implemented "nofolder" and "nofolder-disable-strict-updates" options 232 | # 2017/10/13 - u.alonsocamaro@f5.com - Implemented @partition variable 233 | # 2018/02/28 - cstubbs@gmail.com - Detect default route domain for deployment partition 234 | # 2018/02/28 - cstubbs@gmail.com - Improved auto-creation of nodes by way of pool members within partitions that have non-default (non-zero) route domain 235 | # 2018/03/01 - cstubbs@gmail.com - Implemented @routedomainid variable 236 | # 2018/05/23 - u.alonsocamaro@f5.com - Renamed @routedomainid as @defaultrd 237 | # 2018/05/23 - u.alonsocamaro@f5.com - Added @option attribute for generic options. 238 | # 239 | # The first option added is "default-route-domain" which can be true (default) or false. This allows disabling the new default behaviour of using partition's 240 | # default-route-domain when creating pool members. @defaultrd is in any case defined. 241 | # 242 | # Example usage: 243 | # 244 | # @option(default-route-domain): "false" 245 | # 246 | # 2018/05/23 - u.alonsocamaro@f5.com - Applying @defaultrd to pool members is not performed when (1) @defaultrd is 0 or (new condition) their addresses already contain % 247 | # 2018/05/23 - u.alonsocamaro@f5.com - Renamed ansible-create to ansible-playbook 248 | # 2018/05/23 - u.alonsocamaro@f5.com - Implemented ansible-role 249 | # 2018/05/24 - u.alonsocamaro@f5.com - FIX: $changepath is now only once at the top of the implementation 250 | # 2018/05/24 - u.alonsocamaro@f5.com - FIX: LTM policies are now implemented with "add" instead of "replace-all-with" allowing re-configuration without eliminating rules not added by this iApp 251 | # 2018/05/24 - u.alonsocamaro@f5.com - Added preliminary ILX workspace import support: @import(ilx-workspace): but not working yet 252 | # 2018/05/24 - u.alonsocamaro@f5.com - FIX: moved $changepath before imports 253 | # 2018/05/25 - u.alonsocamaro@f5.com - Added auto parameter to allow specifying the iApp type to be generated as @option(iapp-type) in the .t2i file 254 | # 2018/05/25 - u.alonsocamaro@f5.com - $changepath is now a function and some other small cleanups 255 | # 2018/05/28 - u.alonsocamaro@f5.com - Several small fixes 256 | # 2018/05/30 - u.alonsocamaro@f5.com - @import(asm-policy) is now supported with upcoming BIG-IP v14 or using v13 + fixes for ID693694 and ID675232. 257 | # Note that to due to pending fix for ID721717 in order to allow iApp reconfiguration the iApp has to be created with -disable-strict-updates 258 | # 2018/05/31 - u.alonsocamaro@f5.com - FIX: stray character in heat_parameters_file 259 | # 2018/06/04 - u.alonsocamaro@f5.com - Added container-configmap feature to create config maps for the BIG-IP Controller aka Container Connector 260 | # 2018/06/04 - u.alonsocamaro@f5.com - Introduction of attribute @json(list) to allow handling of JSON lists by tmsh2iapp by transforming the JSONlist into a space separated list. 261 | # 262 | # This feature has been created for handling multiple-choice/multiple-select survey variables in Ansible Tower but it might be useful for other use cases too. 263 | # 264 | # In Ansible Tower these variables are defined in yaml as: 265 | # 266 | # sample_list: 267 | # - value1 268 | # - value2 269 | # 270 | # And converted into JSON as "['value1', 'value2']" in a variable which will be a parameter of the iApp. 271 | # 272 | # This new feature would transform this JSON list into "value1 value2" which is the native tmsh2iapp format 273 | # 274 | # For usage define one @json attribute statement for each variable. This might change in the future. 275 | # 276 | # @json(list): __variable1__ 277 | # @json(list): __variable2__ 278 | # 279 | # 280 | # CAVEAT: It has been found that values that have quoted values with spaces within the quotes fail. Seems a problem in the REST worker. 281 | # 282 | # example of this issue: - 'key4 \{ data "value 4" \}' 283 | # 284 | # NOTE: At the moment PEM URL categories and local variables don't support JSON list formatted values. 285 | # 286 | # 2018/06/04 - u.alonsocamaro@f5.com - Added iapp_implementation_variables_log to print all variables when the iApp is being instantiated 287 | # 2018/07/17 - u.alonsocamaro@f5.com - Added @apl support allowing the use of multichoice, editchoice and choice GUI inputs 288 | 289 | $tmsh2iapp_version= "20180717.1"; 290 | 291 | # use strict; 292 | binmode STDOUT, ":utf8"; 293 | use utf8; 294 | use JSON; 295 | use File::Basename; 296 | use Text::Balanced qw (extract_codeblock); 297 | 298 | $debug= 0; 299 | 300 | sub check_iworkflow_variables; 301 | sub check_variable_names; 302 | sub attribute_option_default_route_domain; 303 | sub attribute_option_iapp_type; 304 | sub attribute_import_type; 305 | 306 | $cfgdir="/var/tmp"; 307 | 308 | if ($#ARGV == -1) { 309 | print_synopsis(); 310 | exit 1; 311 | } 312 | 313 | if ($#ARGV == 0) { 314 | 315 | if ($ARGV[0] eq "version") { 316 | print_version(); 317 | exit 0; 318 | } elsif ($ARGV[0] eq "usage") { 319 | print_usage(); 320 | exit 0; 321 | } else { 322 | print STDERR "Wrong usage, check the possible options.\n"; 323 | print_synopsis(); 324 | exit 1; 325 | } 326 | } 327 | 328 | 329 | if (($#ARGV == 1) && 330 | ($ARGV[0] ne "auto") && 331 | ($ARGV[0] ne "service") && 332 | ($ARGV[0] ne "service-disable-strict-updates") && 333 | ($ARGV[0] ne "common") && 334 | ($ARGV[0] ne "common-disable-strict-updates") && 335 | ($ARGV[0] ne "nofolder") && 336 | ($ARGV[0] ne "nofolder-disable-strict-updates") && 337 | ($ARGV[0] ne "system") && 338 | ($ARGV[0] ne "heat-create") && 339 | ($ARGV[0] ne "ansible-playbook") && 340 | ($ARGV[0] ne "ansible-role") && 341 | ($ARGV[0] ne "iworkflow-json-apl") && 342 | ($ARGV[0] ne "iworkflow-template-import") && 343 | ($ARGV[0] ne "container-configmap") && 344 | ($ARGV[0] ne "snapshot-create")) { 345 | 346 | print STDERR "Wrong usage, check the possible options shown next\n\n"; 347 | print_synopsis(); 348 | exit 1; 349 | } 350 | 351 | if (($#ARGV == 1) && ($ARGV[0] eq "snapshot-create")) { 352 | 353 | check_this_is_bigip(); 354 | make_snapshot($ARGV[1]); 355 | exit(0); 356 | } 357 | 358 | if (($#ARGV == 2) && ($ARGV[0] eq "t2i-create")) { 359 | 360 | check_this_is_bigip(); 361 | diff_snapshots($ARGV[1], $ARGV[2]); 362 | exit(0); 363 | } 364 | 365 | if ($#ARGV >= 2) { 366 | print STDERR "Wrong usage, check the possible options shown next\n\n"; 367 | print_synopsis(); 368 | exit 1; 369 | } 370 | 371 | 372 | ### From now on all the processing is for creating an output based on a .t2i file 373 | 374 | ### 375 | 376 | $t2i=$ARGV[1]; 377 | 378 | if (! ($t2i=~ m/\.t2i$/)) { 379 | 380 | print STDERR "Wrong usage: the template file must have .t2i extension. See further details below.\n\n"; 381 | print_synopsis(); 382 | exit 1; 383 | } 384 | 385 | ### Variables that must exist 386 | 387 | $iapp_name= basename($t2i); 388 | # iApp name is basename without extension .t2i 389 | $iapp_name=~ s/\.t2i$//; 390 | # Resulting configuration file after template gets the variables replaced by their values 391 | $cfgfile= $iapp_name . ".cfg"; 392 | 393 | # iApp output file name 394 | $iapp_filename= $t2i; 395 | $iapp_filename=~ s/\.t2i$/\.tmpl/; 396 | $iapp_filename_relative= basename($iapp_filename); 397 | 398 | # APL json output file name 399 | $apl_filename= $t2i; 400 | $apl_filename=~ s/\.t2i$/\.apl.json/; 401 | 402 | # IMPORT json output file name 403 | $import_filename= $t2i; 404 | $import_filename=~ s/\.t2i$/\.import.json/; 405 | 406 | ### Default settings 407 | 408 | $option{"default-route-domain"}= "true"; 409 | $iapp{"description"}= "\"iApp $iapp_name generated with tmsh2iapp $tmsh2iapp_version\""; 410 | 411 | ### 412 | 413 | open TMPL, "<", $t2i 414 | or die "Can't open template file $t2i: $!"; 415 | 416 | my $raw_content = do { local $/; }; 417 | close TMPL; 418 | 419 | # remove the attributes but not @service_folder / @partition / @defaultrd 420 | $content= join("\n", grep(!/^\s*(@(label|apl|properties|iapp|import|json|option))/, split(/\n/, $raw_content))); 421 | 422 | # get the variables from the t2i file 423 | my @matches = uniq ( $content =~ /(__pm__.+?__|__dr__.+?__|__fwal__.+?__|__fwpl__.+?__|__urlcat_match__.+?_.+?__|__urlcat_nomatch__.+?_.+?__|__pool__.+?__|__local__.+?__|__.+?__)/g ); 424 | 425 | check_variable_names(\@matches); 426 | 427 | # we separate the items in arrays depending the type of variables. All have to be registered in the @vartypes array. 428 | 429 | my @variables= grep(!/^(__pm|__dr|__fwal|__fwpl|__app_service__|__urlcat_match__|__urlcat_nomatch__|__pool__|__local__)/, @matches); 430 | my @pool_members= grep(/^__pm/, @matches); 431 | my @data_records= grep(/^__dr/, @matches); 432 | my @fw_address_list= grep(/^__fwal/, @matches); 433 | my @fw_port_list= grep(/^__fwpl/, @matches); 434 | my @urlcat_match_list= grep(/^__urlcat_match/, @matches); 435 | my @urlcat_nomatch_list= grep(/^__urlcat_nomatch/, @matches); 436 | my @iworkflow_variables= grep(/^__pool/, @matches); # __pool__addr__ and __pool__port__ are currently a constrain to allow the iApp to expose stats automatically to iWorkflow 437 | # check_iworkflow_variables(\@iworkflow_variables); 438 | my @localvars= grep(/^__local/, @matches); 439 | 440 | my @vartypes= (\@variables, \@pool_members, \@data_records, \@fw_address_list, \@fw_port_list, \@urlcat_match_list, \@urlcat_nomatch_list, \@iworkflow_variables, \@localvars); 441 | my @vartypes_desc= ("General variables", "Pool members", "Internal data-group records", "Firewall address lists", "Firewall port lists", "PEM URL match category lists", "PEM URL no match category lists", "iWorkflow VIP address and VIP port variables", "Per BIG-IP local variables"); 442 | 443 | my @import_file_types= ("apache-ssl-cert", "browser-capabilities-db", "dashboard-viewset", "data-group", "device-capabilities-db", "external-monitor", "ifile", "lwtunneltbl", "ssl-cert", "ssl-crl", "ssl-csr", "ssl-key", "asm-policy", "ilx-workspace"); 444 | my @import_perl_types= @import_file_types; 445 | 446 | # check if the template contains attributes, if so gather them... 447 | my @attribute_lines= grep /^\s*@/, split(/\n/, $raw_content); 448 | 449 | foreach $al (@attribute_lines) { 450 | 451 | $attribute= ""; 452 | $variable= ""; 453 | $value= ""; 454 | 455 | print STDERR "parsing: attribute line: $al\n" if ($debug); 456 | 457 | $_= $al; 458 | 459 | if (/@(label|apl|properties|iapp|import|json|option)\((.*?)\):\s*(.*)/) { 460 | $attribute= $1; 461 | $variable= $2; 462 | $value= $3; 463 | 464 | print STDERR "attribute: $attribute, variable: $variable, value: $value\n" if ($debug); 465 | 466 | } elsif (/\@service_folder/) { 467 | # Do nothing, @service_folder can be anywhere 468 | next; 469 | } elsif (/\@partition/) { 470 | # Do nothing, @partition can be anywhere 471 | next; 472 | } elsif (/\@defaultrd/) { 473 | # Do nothing, @defaultrd can be anywhere 474 | next; 475 | } elsif ($attribute eq "json") { 476 | 477 | if ($variable ne "list") { 478 | print STDERR "Non supported json type \"$variable\". The offending line is shown next: $al\n"; 479 | exit(1); 480 | } 481 | 482 | $json_list{$value}= "true"; 483 | 484 | } else { 485 | print STDERR "Aborting due to unexpected attribute line. The offending line is shown next: $al\n"; 486 | exit(1); 487 | } 488 | 489 | if ($attribute eq "label") { 490 | $labels{$variable}= $value; 491 | } elsif ($attribute eq "apl") { 492 | $apl{$variable}= $value; 493 | } elsif ($attribute eq "properties") { 494 | check_legacy_properties_usage($variable, $value); 495 | $properties{$variable}= $value; 496 | } elsif ($attribute eq "iapp") { 497 | 498 | $iapp{$variable}= $value; 499 | 500 | } elsif ($attribute eq "option") { 501 | 502 | if ($variable eq "default-route-domain") { 503 | attribute_option_default_route_domain($value); 504 | } elsif ($variable eq "iapp-type") { 505 | attribute_option_iapp_type($value); 506 | } else { 507 | print STDERR "Aborting due to unknown \@option $variable\n"; 508 | exit(1); 509 | } 510 | 511 | } elsif ($attribute eq "import") { 512 | 513 | $arg= $variable; # rename it for clarity: in the case of @import the argument of is not a variable name 514 | 515 | attribute_import_type($arg, $value); 516 | 517 | 518 | } elsif ($attribute eq "json") { 519 | 520 | if ($variable ne "list") { 521 | print STDERR "Non supported json type \"$variable\". The offending line is shown next: $al\n"; 522 | exit(1); 523 | } 524 | 525 | $json_list{$value}= "true"; 526 | 527 | } else { 528 | # Given the check above this should never be triggered 529 | print STDERR "Aborting due to unknown attribute \"$attribute\" in the t2i template. The offending line is shown next: $al\n"; 530 | exit(1); 531 | } 532 | } 533 | 534 | for my $i (0 .. $#import_file_types) { # the perl variables are the same as tmsh but we replace "-" with "_"; 535 | 536 | $import_perl_types[$i]=~ s/-/_/g; 537 | } 538 | 539 | # Sanity checks 540 | 541 | if (($ARGV[0] eq "system") && ($raw_content =~ /ltm pool/)) { 542 | 543 | print STDERR "Warning: pools should not be specified when the system option is used. This will trigger an error when assigning members to it\n"; 544 | } 545 | 546 | if ($ARGV[0] eq "auto") { 547 | 548 | print STDERR "Aborting: \"auto\" specified in the command line but no valid \@option(iapp-type) found in the .t2i file\n"; 549 | exit(1); 550 | } 551 | 552 | ################################################################################################### 553 | # Command to execute on the .t2i file 554 | 555 | if ($ARGV[0] eq "heat-create") { 556 | 557 | heat_create(); 558 | 559 | } elsif ($ARGV[0] eq "ansible-playbook") { 560 | 561 | ansible_playbook(); 562 | 563 | } elsif ($ARGV[0] eq "ansible-role") { 564 | 565 | ansible_role(); 566 | 567 | } elsif ($ARGV[0] eq "iworkflow-json-apl") { 568 | 569 | iworkflow_json_apl(1); 570 | 571 | } elsif ($ARGV[0] eq "iworkflow-template-import") { 572 | 573 | iworkflow_json_import(); 574 | 575 | } elsif ($ARGV[0] eq "container-configmap") { 576 | 577 | container_configmap(); 578 | 579 | } else { 580 | 581 | iapp_create(1); 582 | } 583 | 584 | exit 0; 585 | 586 | ################################################################################################### 587 | 588 | sub iapp_create { 589 | 590 | my $create_file= $_[0]; 591 | 592 | if ($create_file) { 593 | 594 | open IAPP, ">", $iapp_filename 595 | or die "Can't open file where the iApp would be written ($iapp_filename): $!"; 596 | } 597 | 598 | $iapp= iapp_head(); 599 | $iapp.= iapp_implementation_begin(); 600 | $iapp.= iapp_implementation_procs(); 601 | 602 | $iapp.= iapp_implementation_changepath(); 603 | 604 | $iapp.= iapp_implementation_import(); 605 | $iapp.= iapp_implementation_variables_log(); 606 | 607 | $iapp.= iapp_implementation_incremental_loader(); 608 | 609 | $iapp.= iapp_implementation_pool_members_modify(); 610 | $iapp.= iapp_implementation_data_records_modify(); 611 | $iapp.= iapp_implementation_afm_address_list_modify(); 612 | $iapp.= iapp_implementation_afm_port_list_modify(); 613 | $iapp.= iapp_implementation_pem_urlcat_modify(); 614 | 615 | if (($ARGV[0] eq "service-disable-strict-updates") || ($ARGV[0] eq "common-disable-strict-updates") || ($ARGV[0] eq "nofolder-disable-strict-updates")) { 616 | 617 | $iapp.= iapp_implementation_disable_strict_updates(); 618 | } 619 | 620 | $iapp.= iapp_implementation_end(); 621 | $iapp.= iapp_presentation(); 622 | $iapp.= iapp_tail(); 623 | 624 | 625 | if ($create_file) { 626 | 627 | print IAPP $iapp; 628 | 629 | close IAPP 630 | or die "Couldn't close the file for the iApp ($iapp_filename): $!"; 631 | 632 | print "Written the resulting iApp of type $ARGV[0] to $iapp_filename\n"; 633 | 634 | } else { 635 | 636 | return $iapp; 637 | } 638 | } 639 | 640 | sub iapp_head { 641 | 642 | my $head = <<"HEAD"; 643 | cli admin-partitions { 644 | update-partition Common 645 | } 646 | 647 | sys application template $iapp_name { 648 | 649 | actions { 650 | definition { 651 | HEAD 652 | 653 | return $head; 654 | } 655 | 656 | sub iapp_implementation_begin { 657 | 658 | my $implementation_begin= << "IMPLEMENTATION_BEGIN"; 659 | 660 | implementation { 661 | 662 | puts "################################################################################################################" 663 | puts "Starting iApp \$tmsh::app_name.app generated with tmsh2iapp version $tmsh2iapp_version" 664 | 665 | set partition "/[lindex [split [tmsh::pwd] /] 1]" 666 | set partition_name "[lindex [split [tmsh::pwd] /] 1]" 667 | 668 | if { \$partition == "/" } { 669 | puts "Warning: behaviour not well defined when \@partition is \\"/\\"" 670 | set defaultrd 0 671 | } else { 672 | set obj [tmsh::get_config auth partition \$partition_name default-route-domain] 673 | set defaultrd [tmsh::get_field_value [lindex \$obj 0] default-route-domain] 674 | } 675 | 676 | puts "The iApp of type $ARGV[0] is being instantiated in \@partition \$partition, \@defaultrd is \$defaultrd" 677 | 678 | IMPLEMENTATION_BEGIN 679 | 680 | return $implementation_begin; 681 | } 682 | 683 | sub iapp_implementation_procs { 684 | 685 | my $implementation_proc_debug= << "IMPLEMENTATION_PROC_DEBUG"; 686 | 687 | # Modified from appsvcs_integration_v2.0.tmpl 688 | 689 | # Print a timestamped debug message to /var/tmp/scriptd.out 690 | # Input: headers = TCL list of headers for the log message 691 | # msg = The message to log 692 | # level = Integer indicated the log level for this message 693 | proc debug { headers msg level } { 694 | set systemTime [clock seconds] 695 | set brackets "" 696 | if { [llength \$headers] > 0 } { 697 | set brackets [format "\\[%s\\]" [join \$headers "\\]\\["]] 698 | } 699 | set pre [format "\\[%s %s\\]\\[%s\\]%s" [clock format \$systemTime -format %D] [clock format \$systemTime -format %H:%M:%S] \$::tmsh::app_name.app \$brackets] 700 | puts [format "%s %s" \$pre [string map [list "\n" "\n\$pre " ] \$msg]] 701 | } 702 | 703 | IMPLEMENTATION_PROC_DEBUG 704 | 705 | my $implementation_proc_jsonlist2txt= << "IMPLEMENTATION_PROC_JSONLIST2TXT"; 706 | 707 | # jsonlist2txt processes JSON list, ie: ['key1 \{ data "value 1" \}', 'key2 \{ data "value 2" \}', 'key3 \{ data "value 3" \}'] 708 | # and transforms it into: key1 { data "value 1" } key2 { data "value 2" } key3 { data "value 3" } 709 | # 710 | # This proc is far from being YAML conformant: the procedure expects that the elements in the list doesn't contain the character "," 711 | # 712 | 713 | proc jsonlist2txt {jlist }{ 714 | 715 | puts "jsonlist2txt: processing >\$jlist<" 716 | 717 | # remove the outer brackets 718 | set jlist [string trim \$jlist {[]}] 719 | # split the elements, using "," as delimiter 720 | set jsplit [split \$jlist ,] 721 | 722 | # trim the quotes and spaces of each element and concatenate them with an space. je stands for json element 723 | set retval "" 724 | foreach je \$jsplit { 725 | set jetrim [subst [string trim \$je {"' }]] 726 | append retval \$jetrim " " 727 | } 728 | 729 | # remove leading space 730 | set retval [string trim \$retval] 731 | 732 | puts "jsonlist2txt: output >\$retval<" 733 | 734 | return \$retval 735 | } 736 | 737 | IMPLEMENTATION_PROC_JSONLIST2TXT 738 | 739 | 740 | my $implementation_proc_curl_save_file= << "IMPLEMENTATION_PROC_CURL_SAVE_FILE"; 741 | 742 | # Modified from appsvcs_integration_v2.0.tmpl to support local files as well 743 | 744 | # Run the cURL command and save the URL to a file. Throws a hard error if cURL 745 | # exits uncleanly 746 | # Input: string url = URL to fetch 747 | # string filename = filename to save output to 748 | # int error_exit = 1 => throw hard error on non 200 response code 749 | # >1 => ignore error and return response code 750 | proc curl_save_file { url filename {error_exit 1}} { 751 | debug [list curl_save_file start] "url=\$url filename=\$filename error_exit=\$error_exit" 9 752 | set status [catch { 753 | exec curl --connect-timeout 5 -k -s -w 'RESPCODE=\%\{response_code\}' -o \$filename \$url 754 | } message] 755 | 756 | debug [list curl_save_file done] "status=\$status message=\$message" 9 757 | 758 | if { \$status != 0 } { 759 | if { \$error_exit == 1} { 760 | error "Error occured while trying to retrieve \$url (status \$status): \$message" 761 | } else { 762 | return 0 763 | } 764 | } 765 | 766 | return 1 767 | } 768 | 769 | IMPLEMENTATION_PROC_CURL_SAVE_FILE 770 | 771 | my $retval= ""; 772 | 773 | $check_import_type= "asm_policy"; # To avoid the following warning Name "main::asm_policy" used only once: possible typo at ./tmsh2iapp.pl line 477. 774 | if (@$check_import_type) { 775 | 776 | $retval.= $implementation_proc_debug; 777 | $retval.= $implementation_proc_curl_save_file; 778 | } 779 | 780 | if (scalar(keys %json_list)){ 781 | $retval.= $implementation_proc_jsonlist2txt; 782 | } 783 | 784 | return $retval . "\n"; 785 | } 786 | 787 | sub iapp_implementation_changepath { 788 | 789 | my $changepath= " "; 790 | 791 | if ($ARGV[0] eq "system") { 792 | $changepath.='tmsh::cd "/"'; 793 | } elsif (($ARGV[0] eq "common") || ($ARGV[0] eq "common-disable-strict-updates")) { 794 | $changepath.='tmsh::cd "/Common"'; 795 | } elsif (($ARGV[0] eq "nofolder") || ($ARGV[0] eq "nofolder-disable-strict-updates")) { 796 | $changepath.='tmsh::cd ".."'; 797 | } else { 798 | $changepath= ""; 799 | } 800 | 801 | $changepath.= "\n\n"; 802 | 803 | return $changepath; 804 | } 805 | 806 | sub iapp_implementation_import { 807 | 808 | my $import= ""; 809 | my $import_type; 810 | my $import_otype; 811 | 812 | foreach $import_type (@import_perl_types) { 813 | 814 | foreach $var (sort @$import_type) { 815 | 816 | $import_otype= $import_type; 817 | $import_otype=~ s/_/-/g; 818 | 819 | $vname= $var; 820 | $vname=~ s/__(.*?)__/var__$1/; 821 | 822 | $url= $var; 823 | $url=~ s/__(.*?)__/var__$1_import/; 824 | 825 | if ($import_otype eq "data-group") { 826 | 827 | $import.= " set cmd \"tmsh::create sys file $import_otype \${::$vname} source-path \${::$url} $properties{$var}\"\n"; 828 | $import.= " puts \$cmd\n"; 829 | $import.= " eval \$cmd\n"; 830 | 831 | } elsif ($import_otype eq "asm-policy") { 832 | 833 | $import.= " curl_save_file \${::$url} $cfgdir/\${::$vname}.xml\n"; 834 | 835 | $import.= " set cmd \"tmsh::load asm policy \${::$vname} file $cfgdir/\${::$vname}.xml overwrite\"\n"; 836 | $import.= " puts \$cmd\n"; 837 | $import.= " eval \$cmd\n"; 838 | 839 | $import.= " set cmd \"tmsh::modify asm policy \${::$vname} active\"\n"; 840 | $import.= " puts \$cmd\n"; 841 | $import.= " eval \$cmd\n"; 842 | 843 | } elsif ($import_otype eq "ilx-workspace") { 844 | 845 | $import.= " set cmd \"tmsh::create ilx workspace \${::$vname} from-uri \${::$url}\"\n"; 846 | $import.= " puts \$cmd\n"; 847 | $import.= " eval \$cmd\n"; 848 | 849 | } else { 850 | 851 | $import.= " set cmd \"tmsh::create sys file $import_otype \${::$vname} source-path \${::$url}\"\n"; 852 | $import.= " puts \$cmd\n"; 853 | $import.= " eval \$cmd\n"; 854 | 855 | } 856 | 857 | } 858 | } 859 | 860 | $import.= "\n"; 861 | 862 | return $import; 863 | } 864 | 865 | sub iapp_implementation_variables_log { 866 | 867 | my $sp= " "; 868 | my $retval= "\n"; 869 | 870 | $retval.= $sp . "puts \"Dumping values passed to iApp variables\"\n"; 871 | 872 | $retval.= $sp . "puts \">>> regular variables\"\n"; 873 | 874 | foreach $v (sort @variables) { 875 | 876 | $vname= $v; 877 | $vname=~ s/__(.*?)__/var__$1/; 878 | $retval.= $sp . "if {[info exists {::$vname}]} { puts \"> $v >\${::$vname}<\" } else { puts \"> $v is undefined\" }\n" . "\n"; 879 | } 880 | 881 | $retval.= $sp . "puts \">>> pool member variables\"\n"; 882 | 883 | foreach $pm (sort @pool_members) { 884 | 885 | $vname= $pm; 886 | $vname=~ s/__(.*)__/$1/; 887 | $retval.= $sp . "if {[info exists {::$vname}]} { puts \"> $pm >\${::$vname}<\" } else { puts \"> $pm is undefined\" }\n" . "\n"; 888 | 889 | $vname.= "_properties"; 890 | $retval.= $sp . "if {[info exists {::$vname}]} { puts \"> $pm properties >\${::$vname}<\" } else { puts \"> $pm properties is undefined\" }\n" . "\n"; 891 | } 892 | 893 | $retval.= $sp . "puts \">>> data record variables\"\n"; 894 | 895 | foreach $dr (sort @data_records) { 896 | 897 | $vname= $dr; 898 | $vname=~ s/__(.*)__/$1/; 899 | $retval.= $sp . "if {[info exists {::$vname}]} { puts \"> $dr >\${::$vname}<\" } else { puts \"> $dr properties is undefined\" }\n" . "\n"; 900 | } 901 | 902 | $retval.= $sp . "puts \">>> firewall address list variables\"\n"; 903 | 904 | foreach $fwal (sort @fw_address_list) { 905 | 906 | $vname= $fwal; 907 | $vname=~ s/__(.*)__/$1/; 908 | $retval.= $sp . "if {[info exists {::$vname}]} { puts \"> $fwal >\${::$vname}<\" } else { puts \"> $fwal is undefined\" }\n" . "\n"; 909 | } 910 | 911 | $retval.= $sp . "puts \">>> firewall port list variables\"\n"; 912 | 913 | foreach $fwpl (sort @fw_port_list) { 914 | 915 | $vname= $fwpl; 916 | $vname=~ s/__(.*)__/$1/; 917 | $retval.= $sp . "if {[info exists {::$vname}]} { puts \"> $fwpl >\${::$vname}<\" } else { puts \"> $fwpl is undefined\" }\n" . "\n"; 918 | } 919 | 920 | $retval.= $sp . "puts \">>> url match category list variables\"\n"; 921 | 922 | foreach $urlcat (sort @urlcat_match_list) { 923 | 924 | $vname= $urlcat; 925 | $vname=~ s/__(.*)__/$1/; 926 | $retval.= "if {[info exists {::$vname}]} { puts \"> $urlcat >\${::$vname}<\" } else { puts \"> $urlcat is undefined\" }\n" . "\n"; 927 | } 928 | 929 | $retval.= $sp . "puts \">>> url nomatch category list variables\"\n"; 930 | 931 | foreach $urlcat (sort @urlcat_nomatch_list) { 932 | 933 | $vname= $urlcat; 934 | $vname=~ s/__(.*)__/$1/; 935 | $retval.= $sp . "if {[info exists {::$vname}]} { puts \"> $urlcat >\${::$vname}<\" } else { puts \"> $urlcat is undefined\" }\n" . "\n"; 936 | } 937 | 938 | $retval.= $sp . "puts \">>> iworkflow variables\"\n"; 939 | 940 | foreach $iwork (sort @iworkflow_variables) { 941 | 942 | $vname= $iwork; 943 | $vname=~ s/__(.*)__/$1/; 944 | $retval.= $sp . "if {[info exists {::$vname}]} { puts \"> $iwork >\${::$vname}<\" } else { puts \"> $iwork is undefined\" }\n" . "\n"; 945 | } 946 | 947 | $retval.= $sp . "puts \"End of dumping values passed to iApp variables\"\n"; 948 | return $retval; 949 | } 950 | 951 | sub iapp_implementation_variables_instantiation { 952 | 953 | my $map= ""; 954 | my $retval= ""; 955 | 956 | # tmsh2iapp defined variables 957 | 958 | $map.= "\@service_folder \$tmsh::app_name.app "; 959 | $map.= "\@partition \$partition "; 960 | $map.= "\@defaultrd \$defaultrd "; 961 | 962 | # Plain iApp variables 963 | 964 | $retval.= "\n"; 965 | 966 | foreach $v (sort @variables) { 967 | 968 | $vname= $v; 969 | $vname=~ s/__(.*?)__/var__$1/; 970 | 971 | # is there a value JSON list transformation for this variable? 972 | if (defined($json_list{$v})) { 973 | 974 | $retval.= " set {::$vname} [jsonlist2txt \${::$vname}]\n"; 975 | } 976 | 977 | $map.= "$v {\${::$vname}} "; 978 | } 979 | 980 | $retval.= "\n"; 981 | 982 | # The next variables are list types and are replaced with "" or with a default value and a _modify function will instantiate them later 983 | foreach $pm (sort @pool_members) { 984 | 985 | $map.= "$pm {} "; 986 | } 987 | 988 | foreach $dr (sort @data_records) { 989 | 990 | $vname= $dr; 991 | $vname=~ s/__(dr__.*?)__/$1/; 992 | 993 | $map.= "$dr {} "; 994 | } 995 | 996 | foreach $fwal (sort @fw_address_list) { 997 | # Unfortunatly it doesn't allow an empty value 998 | $map.= "$fwal 255.255.255.255/32 "; 999 | } 1000 | 1001 | foreach $fwpl (sort @fw_port_list) { 1002 | # Unfortunately it doesn't allow an tmpty value 1003 | $map.= "$fwpl 65535 "; 1004 | } 1005 | 1006 | foreach $urlcat (sort @urlcat_match_list) { 1007 | $map.= "$urlcat {} "; 1008 | } 1009 | 1010 | foreach $urlcat (sort @urlcat_nomatch_list) { 1011 | $map.= "$urlcat {} "; 1012 | } 1013 | 1014 | foreach $iwork (sort @iworkflow_variables) { 1015 | 1016 | $vname= $iwork; 1017 | $vname=~ s/__(.*)__/$1/; 1018 | 1019 | $map.= "$iwork {\${::$vname}} "; 1020 | } 1021 | 1022 | # app-service 1023 | $full_app_service= "\$tmsh::app_name" . ".app" . "/" . "\$tmsh::app_name"; 1024 | $map.= "__app_service__ $full_app_service "; 1025 | 1026 | # Actual variable replacement 1027 | $retval.= " set cfg [string map \"$map\" \$cfg]\n"; 1028 | 1029 | return $retval; 1030 | } 1031 | 1032 | sub iapp_implementation_config_load_section { 1033 | 1034 | $_= $_[0]; 1035 | if (m/^\s*$/) { 1036 | print STDERR "load_section: No content in section: \n$_[0]\n" if ($debug); 1037 | return ""; 1038 | } else { 1039 | print STDERR "load_section: content found in section: \n$_[0]\n" if ($debug); 1040 | } 1041 | 1042 | my $vars_instantiation= iapp_implementation_variables_instantiation(); 1043 | $vars_instantiation.= iapp_implementation_local_variables_instantiation(); 1044 | 1045 | my $txt = << "CFG"; 1046 | 1047 | set cfg { $_[0] } 1048 | CFG 1049 | 1050 | my $load_cmd = <<"LOAD_CMD"; 1051 | 1052 | set fileId [open $cfgdir/$cfgfile "w"] 1053 | puts -nonewline \$fileId \$cfg 1054 | close \$fileId 1055 | 1056 | tmsh::load sys config merge file $cfgdir/$cfgfile 1057 | LOAD_CMD 1058 | 1059 | return $txt . $vars_instantiation . $load_cmd; 1060 | } 1061 | 1062 | sub iapp_implementation_create_ltm_policy { 1063 | 1064 | my $retval= ""; 1065 | my $policy= $_[0]; 1066 | 1067 | $policy=~ s/\n//g; 1068 | $policy=~ s/\s+/ /g; 1069 | $policy=~ s/ltm policy ((\w+|-|_)+)/ltm policy $1 legacy/; 1070 | 1071 | $policy=~ s/actions/actions add/g; 1072 | $policy=~ s/controls/controls add/g; 1073 | $policy=~ s/conditions/conditions add/g; 1074 | $policy=~ s/requires/requires add/g; 1075 | $policy=~ s/rules/rules add/g; 1076 | $policy=~ s/{(.*)}/$1/g; 1077 | 1078 | ### instantiate variables in the policy, this should be moved to a function eventually 1079 | 1080 | my $map= ""; 1081 | 1082 | ## tmsh2iapp-own variables 1083 | 1084 | # Previously this variable was only valid when using service or service-disable-strict-updates 1085 | $map.= "\@service_folder \$tmsh::app_name.app "; 1086 | $map.= "\@partition \$partition "; 1087 | $map.= "\@defaultrd \$defaultrd "; 1088 | 1089 | ## Plain variables 1090 | 1091 | foreach $v (sort @variables) { 1092 | 1093 | $vname= $v; 1094 | $vname=~ s/__(.*?)__/var__$1/; 1095 | 1096 | $map.= "$v {\${::$vname}} "; 1097 | } 1098 | 1099 | $full_app_service= "\$tmsh::app_name" . ".app" . "/" . "\$tmsh::app_name"; 1100 | $map.= "__app_service__ $full_app_service "; 1101 | 1102 | # build the return value 1103 | 1104 | $retval = " set policy [string map \"$map\" {$policy}]\n"; 1105 | $retval.= " puts \"\$policy\"\n"; 1106 | $retval.= " tmsh::create \$policy\n"; 1107 | 1108 | return $retval; 1109 | } 1110 | 1111 | sub iapp_implementation_incremental_loader { 1112 | 1113 | @lines= split /^/, $content; 1114 | $remaining= $content; 1115 | my $retval= ""; 1116 | 1117 | $i= 0; 1118 | 1119 | while($i < scalar(@lines)) { 1120 | 1121 | $_= $lines[$i]; 1122 | 1123 | if (/^\s*ltm policy/) { 1124 | 1125 | @prev= @lines[0..$i-1]; 1126 | @next= @lines[$i..scalar(@lines)-1]; 1127 | 1128 | $prev_flat= join("", @prev); 1129 | $next_flat= join("", @next); 1130 | 1131 | print STDERR ">>> prev_flat:\n", $prev_flat, "\n" if ($debug); 1132 | print STDERR ">>> next_flat:\n", $next_flat, "\n" if ($debug); 1133 | 1134 | ($extracted, $remaining, $prefix)= extract_codeblock($next_flat, '{}', '^\s*ltm\s+policy\s+(\w+|-|_)+\s+'); 1135 | 1136 | if (!defined($extracted)) { 1137 | print STDERR "Aborting: could not match a valid ltm policy starting with the line $lines[$i]"; 1138 | print STDERR "parsing: next_flat is:\n>>>\n$next_flat\n<<<\n" if ($debug); 1139 | exit(0); 1140 | } 1141 | 1142 | $policy= $prefix . $extracted; 1143 | 1144 | print STDERR ">>> extracted:\n", $extracted, "\n" if ($debug); 1145 | print STDERR ">>> remaining:\n", $remaining, "\n" if ($debug); 1146 | 1147 | ### 1148 | 1149 | $retval.= iapp_implementation_config_load_section($prev_flat) . "\n"; 1150 | $retval.= iapp_implementation_create_ltm_policy($policy) . "\n"; 1151 | 1152 | ### 1153 | 1154 | $i= 0; 1155 | @lines= split /^/, $remaining; 1156 | 1157 | next; 1158 | } 1159 | 1160 | $i= $i +1; 1161 | } 1162 | 1163 | print STDERR ">>> remaining:\n", $remaining, "\n" if ($debug); 1164 | $retval.= iapp_implementation_config_load_section($remaining); 1165 | 1166 | return $retval; 1167 | } 1168 | 1169 | sub iapp_implementation_pool_members_modify { 1170 | 1171 | 1172 | $retval= "\n\n"; 1173 | 1174 | foreach $pm (sort @pool_members) { 1175 | 1176 | $pname= $pm; # pool name in the tmsh config 1177 | $pname=~ s/__pm__(.*?)__/$1/; 1178 | 1179 | $vname= $pm; # var name in the iapp script 1180 | $vname=~ s/__(.*)__/$1/; 1181 | 1182 | if (defined($json_list{$pm})) { 1183 | $retval.= " set {::$vname} [jsonlist2txt \${::$vname}]\n"; 1184 | } 1185 | 1186 | $vname_properties= $vname . "_properties"; 1187 | 1188 | $retval.= " if {([info exists {::$vname}]) && ([string length \${::$vname}] > 0)} {\n"; 1189 | 1190 | if (defined($json_list{$pm})) { 1191 | $retval.= " set {::$vname} [jsonlist2txt \${::$vname}]\n"; 1192 | } 1193 | 1194 | #it ($option{"nodes-in-common"} eq "true") { 1195 | # 1196 | #} 1197 | 1198 | if ($option{"default-route-domain"} eq "true") { 1199 | 1200 | $retval.= " if {(\$defaultrd != 0) && ([string first \"%\" \${::$vname}] == -1)} {\n"; 1201 | $retval.= " set cmd \"tmsh::modify ltm pool $pname { members replace-all-with { [string map \": %\${defaultrd}:\" \${::$vname}] } }\"\n"; 1202 | $retval.= " } else {\n"; 1203 | $retval.= " set cmd \"tmsh::modify ltm pool $pname { members replace-all-with { \${::$vname} } }\"\n"; 1204 | $retval.= " }\n"; 1205 | 1206 | } else { 1207 | $retval.= " set cmd \"tmsh::modify ltm pool $pname { members replace-all-with { \${::$vname} } }\"\n"; 1208 | } 1209 | 1210 | $retval.= " puts \"\$cmd\"\n"; 1211 | $retval.= " eval \$cmd\n"; 1212 | $retval.= " if {([info exists {::$vname_properties}]) && ([string length \${::$vname_properties}] > 0)} {\n"; 1213 | $retval.= " set cmd \"tmsh::modify ltm pool $pname { members modify { all { \${::$vname_properties} } } }\"\n"; 1214 | $retval.= " puts \"\$cmd\"\n"; 1215 | $retval.= " eval \$cmd\n"; 1216 | $retval.= " }\n"; 1217 | $retval.= " }\n"; 1218 | 1219 | } 1220 | 1221 | return $retval; 1222 | } 1223 | 1224 | 1225 | sub iapp_implementation_data_records_modify { 1226 | 1227 | 1228 | $retval= "\n\n"; 1229 | 1230 | foreach $dr (sort @data_records) { 1231 | 1232 | $dname= $dr; # data group name in the tmsh config 1233 | $dname=~ s/__dr__(.*?)__/$1/; 1234 | 1235 | $vname= $dr; # var name in the iapp script 1236 | $vname=~ s/__(.*)__/$1/; 1237 | 1238 | $retval.= " if {[info exists {::$vname}] && [string length \${::$vname}] > 0} {\n"; 1239 | 1240 | if (defined($json_list{$dr})) { 1241 | $retval.= " set {::$vname} [jsonlist2txt \${::$vname}]\n"; 1242 | } 1243 | 1244 | $retval.= " set cmd \"tmsh::modify ltm data-group internal $dname records { replace-all-with { \${::$vname} } }\"\n"; 1245 | 1246 | $retval.= " puts \"\$cmd\"\n"; 1247 | $retval.= " eval \$cmd\n"; 1248 | $retval.= " }\n"; 1249 | } 1250 | 1251 | return $retval; 1252 | } 1253 | 1254 | sub iapp_implementation_afm_address_list_modify { 1255 | 1256 | 1257 | $tmsh_cmds= "\n\n"; 1258 | 1259 | foreach $fwal (sort @fw_address_list) { 1260 | 1261 | $fwal_name= $fwal; # firewall address list name in the tmsh config 1262 | $fwal_name=~ s/__fwal__(.*?)__/$1/; 1263 | 1264 | $vname= $fwal; # var name in the iapp script 1265 | $vname=~ s/__(.*)__/$1/; 1266 | 1267 | $tmsh_cmds.= " if {[info exists {::$vname}] && [string length \${::$vname}] > 0} {\n"; 1268 | 1269 | if (defined($json_list{$v})) { 1270 | 1271 | $tmsh_cmds.= " set {::$vname} [jsonlist2txt \${::$vname}]\n"; 1272 | } 1273 | 1274 | $tmsh_cmds.= " set cmd \"tmsh::modify security firewall address-list $fwal_name { addresses replace-all-with { \${::$vname} } }\"\n"; 1275 | 1276 | $tmsh_cmds.= " puts \"\$cmd\"\n"; 1277 | $tmsh_cmds.= " eval \$cmd\n"; 1278 | $tmsh_cmds.= " }\n"; 1279 | } 1280 | 1281 | return $tmsh_cmds; 1282 | } 1283 | 1284 | sub iapp_implementation_afm_port_list_modify { 1285 | 1286 | 1287 | $tmsh_cmds= "\n\n"; 1288 | 1289 | foreach $fwpl (sort @fw_port_list) { 1290 | 1291 | $fwpl_name= $fwpl; # firewall port list name in the tmsh config 1292 | $fwpl_name=~ s/__fwpl__(.*?)__/$1/; 1293 | 1294 | $vname= $fwpl; # var name in the iapp script 1295 | $vname=~ s/__(.*)__/$1/; 1296 | 1297 | $tmsh_cmds.= " if {[info exists {::$vname}] && [string length \${::$vname}] > 0} {\n"; 1298 | 1299 | if (defined($json_list{$v})) { 1300 | 1301 | $tmsh_cmds.= " set {::$vname} [jsonlist2txt \${::$vname}]\n"; 1302 | } 1303 | 1304 | $tmsh_cmds.= " set cmd \"tmsh::modify security firewall port-list $fwpl_name { ports replace-all-with { \${::$vname} } }\"\n"; 1305 | 1306 | $tmsh_cmds.= " puts \"\$cmd\"\n"; 1307 | $tmsh_cmds.= " eval \$cmd\n"; 1308 | $tmsh_cmds.= " }\n"; 1309 | } 1310 | 1311 | return $tmsh_cmds; 1312 | } 1313 | 1314 | sub iapp_implementation_pem_urlcat_modify { 1315 | 1316 | # match URL categories 1317 | 1318 | $tmsh_cmds= "\n\n"; 1319 | foreach $urlcat (sort @urlcat_match_list) { 1320 | 1321 | $policy_name= $urlcat; 1322 | $policy_name=~ s/__urlcat_match__(.*?)_(.*?)__/$1/; 1323 | 1324 | $rule_name= $urlcat; 1325 | $rule_name=~ s/__urlcat_match__(.*?)_(.*?)__/$2/; 1326 | 1327 | $vname= $urlcat; # var name in the iapp script 1328 | $vname=~ s/__(.*)__/$1/; 1329 | 1330 | $tmsh_cmds.= " set urlcats [regexp -all -inline {\\S+} \${::$vname}]\n"; 1331 | $tmsh_cmds.= " set urlcats [split \$urlcats]\n"; 1332 | $tmsh_cmds.= " set i 0\n"; 1333 | $tmsh_cmds.= " foreach cat \$urlcats {\n"; 1334 | 1335 | # 1336 | # for some unknown funny reason tmsh::modify requires braces after rules and CLI tmsh doesn't: 1337 | # root@(afm)(cfg-sync Standalone)(Active)(/Common)(tmos)# modify pem policy test.app/customfiltering rules { modify { filterurls { url-categorization-filters add { url_category0 { url-category Social_Networking operation match } } } } } 1338 | # Syntax Error: "rules" unexpected argument "{" one of the following must be specified: 1339 | # add, delete, modify, none, replace-all-with 1340 | $tmsh_cmds.= " set cmd \"tmsh::modify pem policy $policy_name rules { modify { $rule_name { url-categorization-filters add { url_category\$i { url-category \$cat operation match } } } } }\"\n"; 1341 | 1342 | $tmsh_cmds.= " puts \"\$cmd\"\n"; 1343 | $tmsh_cmds.= " eval \$cmd\n"; 1344 | $tmsh_cmds.= " incr i\n"; 1345 | $tmsh_cmds.= " }\n"; 1346 | } 1347 | 1348 | 1349 | # nomatch URL categories 1350 | 1351 | $tmsh_cmds.= "\n\n"; 1352 | foreach $urlcat (sort @urlcat_nomatch_list) { 1353 | 1354 | $policy_name= $urlcat; 1355 | $policy_name=~ s/__urlcat_nomatch__(.*?)_(.*?)__/$1/; 1356 | 1357 | $rule_name= $urlcat; 1358 | $rule_name=~ s/__urlcat_nomatch__(.*?)_(.*?)__/$2/; 1359 | 1360 | $vname= $urlcat; # var name in the iapp script 1361 | $vname=~ s/__(.*)__/$1/; 1362 | 1363 | $tmsh_cmds.= " set urlcats [regexp -all -inline {\\S+} \${::$vname}]\n"; 1364 | $tmsh_cmds.= " set urlcats [split \$urlcats]\n"; 1365 | $tmsh_cmds.= " set i 0\n"; 1366 | $tmsh_cmds.= " foreach cat \$urlcats {\n"; 1367 | # 1368 | # for some unknown funny reason tmsh::modify requires braces after rules and CLI tmsh doesn't: 1369 | $tmsh_cmds.= " set cmd \"tmsh::modify pem policy $policy_name rules { modify { $rule_name { url-categorization-filters add { url_category\$i { url-category \$cat operation nomatch } } } } }\"\n"; 1370 | 1371 | $tmsh_cmds.= " puts \"\$cmd\"\n"; 1372 | $tmsh_cmds.= " eval \$cmd\n"; 1373 | $tmsh_cmds.= " incr i\n"; 1374 | $tmsh_cmds.= " }\n"; 1375 | } 1376 | 1377 | return $tmsh_cmds; 1378 | } 1379 | 1380 | sub iapp_implementation_local_variables_instantiation { 1381 | 1382 | 1383 | if ((scalar @localvars) == 0) { 1384 | return ""; 1385 | } 1386 | 1387 | $tmsh_cmds= "\n\n"; 1388 | 1389 | $tmsh_cmds.= " set this_bigip [ tmsh::list sys global-settings hostname ]\n"; 1390 | $tmsh_cmds.= " regexp -line \"hostname (.*)\" \$this_bigip x this_bigip\n"; 1391 | $tmsh_cmds.= " puts \"Found this BIG-IP is \$this_bigip\"\n"; 1392 | $tmsh_cmds.= " set index_bigip -1\n"; 1393 | $tmsh_cmds.= " set list_bigips [split [regexp -all -inline {\\S+} \$local__bigip_names]]\n"; 1394 | $tmsh_cmds.= " set i 0\n\n"; 1395 | 1396 | $tmsh_cmds.= " foreach bigip \$list_bigips {\n"; 1397 | $tmsh_cmds.= " if {[regexp -nocase ^\$bigip \$this_bigip]} {\n"; 1398 | # We do not break the foreach loop to catch possible errors and through a user friendly message 1399 | $tmsh_cmds.= " puts \"Matched \$bigip with \$this_bigip\ using index \$i\"\n"; 1400 | $tmsh_cmds.= " if {\$index_bigip != -1} {\n"; 1401 | $tmsh_cmds.= " set i_name [lindex \$i \$list_bigips]\n"; 1402 | $tmsh_cmds.= " set index_name [lindex \$index_bigip \$list_bigips]\n"; 1403 | $tmsh_cmds.= " error \"Several matches found for \$this_bigip with indexes \$index_bigip (\$index_name) and \$i (\$i_name)\"\n"; 1404 | $tmsh_cmds.= " }\n"; 1405 | $tmsh_cmds.= " set index_bigip \$i\n"; 1406 | $tmsh_cmds.= " }\n"; 1407 | $tmsh_cmds.= " incr i\n"; 1408 | $tmsh_cmds.= " }\n"; 1409 | 1410 | $tmsh_cmds.= " if {\$index_bigip == -1} {\n"; 1411 | $tmsh_cmds.= " error \"Could not find \$this_bigip in list >\$local__bigip_names<\"\n"; 1412 | $tmsh_cmds.= " }\n\n"; 1413 | 1414 | $tmsh_cmds.= " set n_bigips [llength \$list_bigips]\n"; 1415 | 1416 | foreach $lv (@localvars) { 1417 | 1418 | $vname= $lv; # var name in the iapp script 1419 | $vname=~ s/__(.*)__/$1/; 1420 | 1421 | $tmsh_cmds.= " set ll [llength \${::$vname}]\n"; 1422 | 1423 | $tmsh_cmds.= " if {\$ll != \$n_bigips} {\n"; 1424 | $tmsh_cmds.= " error \"The number of values in the variable $vname (\$ll) is different than the number of BIG-IPs (\$n_bigips), variable value is >\${::$vname}<\"\n"; 1425 | $tmsh_cmds.= " }\n"; 1426 | $tmsh_cmds.= "\n"; 1427 | $tmsh_cmds.= " set value [lindex \${::$vname} \$index_bigip]\n"; 1428 | $tmsh_cmds.= " puts \"Applying local value >\$value< for $lv\"\n"; 1429 | $tmsh_cmds.= " set cfg [string map \"$lv {\$value}\" \$cfg]\n"; 1430 | } 1431 | 1432 | return $tmsh_cmds; 1433 | } 1434 | 1435 | sub iapp_implementation_disable_strict_updates { 1436 | 1437 | $tmsh_cmds= "\n\n"; 1438 | 1439 | if ($ARGV[0] eq "common-disable-strict-updates") { 1440 | $service_path= "\$tmsh::app_name" . ".app" . "/" . "\$tmsh::app_name"; 1441 | } elsif ($ARGV[0] eq "nofolder-disable-strict-updates") { 1442 | $service_path= "\$tmsh::app_name" . ".app" . "/" . "\$tmsh::app_name"; 1443 | } elsif ($ARGV[0] eq "service-disable-strict-updates") { 1444 | $service_path= "\$tmsh::app_name"; 1445 | } 1446 | 1447 | $tmsh_cmds.= "set cmd \"tmsh::modify sys application service $service_path strict-updates disabled\"\n"; 1448 | 1449 | $tmsh_cmds.= " puts \"\$cmd\"\n"; 1450 | $tmsh_cmds.= " eval \$cmd\n"; 1451 | 1452 | return $tmsh_cmds; 1453 | } 1454 | 1455 | sub iapp_implementation_end { 1456 | 1457 | my $implementation_end = << "IMPLEMENTATION_END"; 1458 | 1459 | puts "Finished iApp \$tmsh::app_name.app generated with tmsh2iapp version $tmsh2iapp_version" 1460 | } 1461 | IMPLEMENTATION_END 1462 | 1463 | return $implementation_end; 1464 | } 1465 | 1466 | sub iapp_presentation { 1467 | 1468 | my $txt= << "TXT"; 1469 | 1470 | presentation { 1471 | 1472 | include "/Common/f5.apl_common" 1473 | TXT 1474 | 1475 | ### variable definitions 1476 | 1477 | foreach $vartype (@vartypes) { 1478 | 1479 | @vt= @$vartype; 1480 | 1481 | if (($#vt +1) > 0) { 1482 | 1483 | $_= $vt[0]; 1484 | 1485 | # vt_name = variable type name 1486 | if (/__(.+)__(.+)__/) { 1487 | $vt_name= $1; # non plain variables type 1488 | } else { 1489 | $vt_name= "var"; # plain variables type 1490 | } 1491 | 1492 | $txt= $txt . " section $vt_name {\n"; 1493 | 1494 | if ($vt_name eq "local") { # Special processing for local variables 1495 | $txt= $txt . " string bigip_names display \"xxlarge\"\n"; 1496 | } 1497 | 1498 | 1499 | foreach $v (sort @$vartype) { 1500 | 1501 | $vname= $v; 1502 | $vname=~ s/__(.*__|)(.+)__/$2/; 1503 | 1504 | $input_type= "string"; # By default all variables are strings 1505 | 1506 | if (defined($apl{$v})) { # but we can also allow the user to specify multichoice, editchoice and choice in the GUI with the @apl attribute 1507 | 1508 | $p= $apl{$v}; 1509 | $_= $p; 1510 | 1511 | if (/^(\s*(multichoice|editchoice|choice)\s+)(.*)/) { 1512 | 1513 | $input_type= $2; 1514 | $p= $3; 1515 | $p=~ s/\\n/\n/g; # We want to interpret newlines 1516 | } 1517 | 1518 | print STDERR "\@apl attribute for variable \"$vname\" of type \"$input_type\" is \"$p\"\n" if ($debug); 1519 | 1520 | } else { 1521 | $p= "display \"xxlarge\""; 1522 | } 1523 | 1524 | $txt= $txt . " $input_type $vname $p\n"; 1525 | 1526 | if ($vt_name eq "pm") { # Special processing for pm properties automatic variables 1527 | $vname_properties= $vname . "_properties"; 1528 | $txt= $txt . " string $vname_properties $p\n"; 1529 | } 1530 | 1531 | if (defined($imports{$v})) { # Special processing for imported objects 1532 | $vname_import= $vname . "_import"; 1533 | $txt= $txt . " string $vname_import display \"xxlarge\"\n"; 1534 | } 1535 | } 1536 | 1537 | $txt= $txt . " }\n\n"; 1538 | } 1539 | } 1540 | 1541 | ### Variables presentation 1542 | 1543 | $txt= $txt . " text {\n"; 1544 | 1545 | my $i= 0; 1546 | my $vartype_plain= 1; 1547 | 1548 | foreach $vartype (@vartypes) { 1549 | 1550 | @vt= @$vartype; 1551 | 1552 | if (($#vt +1) > 0) { 1553 | 1554 | $_= $vt[0]; 1555 | 1556 | # vt_name = variable type name 1557 | if (/__(.+)__(.+)__/) { 1558 | $vt_name= $1; # non plain variables type 1559 | } else { 1560 | $vt_name= "var"; # plain variables type 1561 | } 1562 | 1563 | $txt= $txt . "\n $vt_name \"$vartypes_desc[$i]\"\n"; 1564 | 1565 | if ($vt_name eq "local") { # Special processing for local variables 1566 | $txt= $txt . " $vt_name.bigip_names \"List of all the BIG-IP names in the cluster\"\n"; 1567 | } 1568 | 1569 | foreach $v (sort @$vartype) { 1570 | 1571 | if (defined($labels{$v})) { 1572 | $label= "$labels{$v}"; 1573 | } else { 1574 | 1575 | # we append a __var prefix if it is plain var type 1576 | if ($vartype_plain == 1) { 1577 | $prefix= "__var"; 1578 | } else { 1579 | $prefix= ""; 1580 | } 1581 | 1582 | $label= $prefix . $v; 1583 | } 1584 | 1585 | $vname= $v; 1586 | $vname=~ s/__(.*__|)(.+)__/$2/; 1587 | 1588 | $txt= $txt . " $vt_name.$vname \"$label\"\n"; 1589 | 1590 | if ($vt_name eq "pm") { # Special processing for pm properties automatic variables 1591 | 1592 | $vname_properties= $vname . "_properties"; 1593 | $label= "Properties for " . $label; 1594 | $txt= $txt . " $vt_name.$vname_properties \"$label\"\n"; 1595 | } 1596 | 1597 | if (defined($imports{$v})) { # Special processing for imported objects 1598 | $vname_import= $vname . "_import"; 1599 | $label= "URL for " . $label; 1600 | $txt= $txt . " $vt_name.$vname_import \"$label\"\n"; 1601 | } 1602 | 1603 | } 1604 | } 1605 | 1606 | $i= $i +1; 1607 | $vartype_plain= 0; 1608 | } 1609 | 1610 | ### 1611 | 1612 | my $presentation_end = << "PRESENTATION_END"; 1613 | } 1614 | } 1615 | PRESENTATION_END 1616 | 1617 | $txt= $txt . $presentation_end; 1618 | 1619 | return $txt; 1620 | } 1621 | 1622 | sub iapp_tail { 1623 | 1624 | my $tail = <<"TAIL"; 1625 | role-acl { admin manager resource-admin } 1626 | } 1627 | } 1628 | TAIL 1629 | 1630 | foreach my $attr (sort keys %iapp) { 1631 | if ($attr ne "unsupported-bigip-versions") { 1632 | $tail.= " $attr $iapp{$attr}\n"; 1633 | } 1634 | } 1635 | 1636 | $tail.= "\n}\n"; 1637 | 1638 | return $tail; 1639 | } 1640 | 1641 | ### The next functions are for the heat_create option 1642 | 1643 | sub heat_create { 1644 | 1645 | # HEAT output file names 1646 | $heat_filename= $t2i; 1647 | $heat_filename=~ s/\.t2i$/\.yaml/; 1648 | $heat_params_filename= $t2i; 1649 | $heat_params_filename=~ s/\.t2i$/\.parameters/; 1650 | 1651 | 1652 | open HEAT, ">", $heat_filename 1653 | or die "Can't open file where the HEAT template would be written ($heat_filename): $!"; 1654 | 1655 | print HEAT heat_head(); 1656 | print HEAT heat_parameters(); 1657 | print HEAT heat_resources_static(); 1658 | print HEAT heat_resources_variables(); 1659 | 1660 | close HEAT 1661 | or die "Couldn't close the file for the HEAT template ($heat_filename): $!"; 1662 | 1663 | print "Written the resulting HEAT template to $heat_filename\n"; 1664 | 1665 | 1666 | open HEAT_PARAMS, ">", $heat_params_filename 1667 | or die "Can't open file where the HEAT parameters would be written ($heat_params_filename): $!"; 1668 | 1669 | print HEAT_PARAMS heat_parameters_file(); 1670 | 1671 | close HEAT_PARAMS 1672 | or die "Couldn't close the file for the HEAT parameters ($heat_params_filename): $!"; 1673 | 1674 | print "Written a sample HEAT parameters file for the HEAT template in $heat_params_filename\n"; 1675 | } 1676 | 1677 | sub heat_head { 1678 | 1679 | my $head = << "HEAD"; 1680 | 1681 | heat_template_version: 2015-04-30 1682 | 1683 | description: HEAT deployer of iApp template $iapp_name 1684 | 1685 | 1686 | HEAD 1687 | 1688 | return $head; 1689 | } 1690 | 1691 | sub heat_parameters { 1692 | 1693 | my $parameters = << "DEFAULT_PARAMS"; 1694 | 1695 | parameters: 1696 | iapp_service_name: 1697 | type: string 1698 | label: iApp Service Name 1699 | default: $iapp_name 1700 | ve_instance: 1701 | type: string 1702 | label: BIG-IP's IP address 1703 | description: Where the iApp will be deployed. Typically this is the IP address of the management interface 1704 | bigip_un: 1705 | type: string 1706 | label: BigIP Login Username 1707 | default: admin 1708 | bigip_pw: 1709 | type: string 1710 | label: BigIP Login Password 1711 | default: admin 1712 | DEFAULT_PARAMS 1713 | 1714 | foreach $vartype (@vartypes) { 1715 | 1716 | @vt= @$vartype; # Special handling for local variables 1717 | if (($#vt +1) > 0) { 1718 | $_= $vt[0]; 1719 | if (/^__local__/) { 1720 | 1721 | $parameters= $parameters . " " . local__bigip_names . ":\n"; 1722 | $parameters= $parameters . " " . " " . "type: string\n"; 1723 | $parameters= $parameters . " " . " " . "label: List of all BIG-IP names in the cluster\n"; 1724 | } 1725 | } 1726 | 1727 | foreach $v (sort @$vartype) { 1728 | 1729 | $v_name= $v; 1730 | $v_name=~ s/__(.*)__/$1/; 1731 | 1732 | $parameters= $parameters . " " . $v_name . ":\n"; 1733 | $parameters= $parameters . " " . " " . "type: string\n"; 1734 | 1735 | if (defined($labels{$v})) { 1736 | $parameters= $parameters . " " . " " . "label: " . $labels{$v} . "\n"; 1737 | } 1738 | 1739 | $_= $v_name; # Special handling for pool members properties 1740 | if (/^pm__/) { 1741 | 1742 | $v_name_properties= $v_name . "_properties"; 1743 | 1744 | $parameters= $parameters . " " . $v_name_properties . ":\n"; 1745 | $parameters= $parameters . " " . " " . "type: string\n"; 1746 | 1747 | if (defined($labels{$v})) { 1748 | $parameters= $parameters . " " . " " . "label: " . "Properties for " . $labels{$v} . "\n"; 1749 | } 1750 | } 1751 | 1752 | if (defined($imports{$v})) { # Special processing for imported objects 1753 | $v_name_import= $v_name . "_import"; 1754 | 1755 | $parameters= $parameters . " " . $v_name_import . ":\n"; 1756 | $parameters= $parameters . " " . " " . "type: string\n"; 1757 | 1758 | if (defined($labels{$v})) { 1759 | $parameters= $parameters . " " . " " . "label: " . "URL for " . $labels{$v} . "\n"; 1760 | } 1761 | 1762 | } 1763 | 1764 | } 1765 | } 1766 | 1767 | return $parameters; 1768 | } 1769 | 1770 | sub heat_resources_static { 1771 | 1772 | my $resources = << "RESOURCES"; 1773 | resources: 1774 | bigip_rsrc: 1775 | type: F5::BigIP::Device 1776 | properties: 1777 | ip: { get_param: ve_instance } 1778 | username: { get_param: bigip_un } 1779 | password: { get_param: bigip_pw } 1780 | partition: 1781 | type: F5::Sys::Partition 1782 | depends_on: bigip_rsrc 1783 | properties: 1784 | name: Common 1785 | bigip_server: { get_resource: bigip_rsrc } 1786 | iapp_template: 1787 | type: F5::Sys::iAppFullTemplate 1788 | depends_on: [bigip_rsrc, partition] 1789 | properties: 1790 | bigip_server: { get_resource: bigip_rsrc } 1791 | partition: { get_resource: partition } 1792 | full_template: { get_file: $iapp_filename_relative } 1793 | iapp_service: 1794 | type: F5::Sys::iAppService 1795 | depends_on: iapp_template 1796 | properties: 1797 | bigip_server: { get_resource: bigip_rsrc } 1798 | partition: { get_resource: partition } 1799 | traffic_group: none 1800 | name: { get_param: iapp_service_name } 1801 | template_name: $iapp_name 1802 | RESOURCES 1803 | 1804 | return $resources; 1805 | } 1806 | 1807 | sub heat_resources_variables { 1808 | 1809 | $vars_count= 0; 1810 | 1811 | foreach $vartype (@vartypes) { 1812 | 1813 | @vt= @$vartype; 1814 | 1815 | $vars_count+= ($#vt +1); 1816 | } 1817 | 1818 | if ($vars_count == 0) { 1819 | return ""; 1820 | } 1821 | 1822 | my $section= << "VARIABLES"; 1823 | variables: 1824 | str_replace: 1825 | params: 1826 | VARIABLES 1827 | 1828 | foreach $vartype (@vartypes) { 1829 | 1830 | @vt= @$vartype; # Special handling for local variables 1831 | if (($#vt +1) > 0) { 1832 | $_= $vt[0]; 1833 | if (/^__local__/) { 1834 | $section.= " " . "_local__bigip_names_" . ":" . " { get_param: local__bigip_names }\n"; 1835 | } 1836 | } 1837 | 1838 | foreach $v (sort @$vartype) { 1839 | $v_name= $v; 1840 | $v_name=~ s/__(.*)__/$1/; 1841 | 1842 | $section.= " " . "_" . $v_name . "_" . ":" . " { get_param: " . $v_name . " }\n"; 1843 | 1844 | $_= $v_name; # Special handling for pool members properties 1845 | if (/^pm__/) { 1846 | 1847 | $v_name_properties= $v_name . "_properties"; 1848 | $section.= " " . "_" . $v_name_properties . "_" . ":" . " { get_param: " . $v_name_properties . " }\n"; 1849 | } 1850 | 1851 | if (defined($imports{$v})) { # Special processing for imported objects 1852 | $v_name_import= $v_name . "_import"; 1853 | $section.= " " . "_" . $v_name_import . "_" . ":" . " { get_param: " . $v_name_import . " }\n"; 1854 | } 1855 | 1856 | } 1857 | } 1858 | 1859 | $section.= " template: |\n"; 1860 | $section.= " [{\n"; 1861 | 1862 | $vars_i= 1; 1863 | $vartype_plain= 1; 1864 | 1865 | foreach $vartype (@vartypes) { 1866 | 1867 | @vt= @$vartype; # Special handling for local variables 1868 | if (($#vt +1) > 0) { 1869 | 1870 | $_= $vt[0]; 1871 | if (/^__local__/) { 1872 | 1873 | $section.= " \"name\": " . "\"local__bigip_names\",\n"; 1874 | $section.= " \"encrypted\": \"no\",\n"; 1875 | $section.= " \"value\": " . "\"_local__bigip_names_\"\n"; 1876 | $section.= " }, {\n"; 1877 | } 1878 | } 1879 | 1880 | # we append a var__ prefix if it is plain var type 1881 | if ($vartype_plain == 1) { 1882 | $prefix= "var__"; 1883 | $vartype_plain= 0; 1884 | } else { 1885 | $prefix= ""; 1886 | } 1887 | 1888 | foreach $v (sort @$vartype) { 1889 | 1890 | $v_name= $v; 1891 | $v_name=~ s/__(.*)__/$1/; 1892 | 1893 | $section.= " \"name\": " . "\"" . $prefix . $v_name . "\",\n"; 1894 | $section.= " \"encrypted\": \"no\",\n"; 1895 | $section.= " \"value\": " . "\"_" . $v_name . "_\"\n"; 1896 | 1897 | $_= $v_name; 1898 | if (/^pm__/) { # Special processing for pool members 1899 | 1900 | $section.= " }, {\n"; 1901 | 1902 | $v_name_properties= $v_name . "_properties"; 1903 | 1904 | $section.= " \"name\": " . "\"" . $prefix . $v_name_properties . "\",\n"; 1905 | $section.= " \"encrypted\": \"no\",\n"; 1906 | $section.= " \"value\": " . "\"_" . $v_name_properties . "_\"\n"; 1907 | } 1908 | 1909 | 1910 | if (defined($imports{$v})) { # Special processing for imported objects 1911 | 1912 | $section.= " }, {\n"; 1913 | 1914 | $v_name_import= $v_name . "_import"; 1915 | 1916 | $section.= " \"name\": " . "\"" . $prefix . $v_name_import . "\",\n"; 1917 | $section.= " \"encrypted\": \"no\",\n"; 1918 | $section.= " \"value\": " . "\"_" . $v_name_import . "_\"\n"; 1919 | } 1920 | 1921 | if ($vars_i < $vars_count) { 1922 | $section.= " }, {\n"; 1923 | } 1924 | 1925 | $vars_i= $vars_i +1; 1926 | } 1927 | } 1928 | 1929 | $section.= " }]\n"; 1930 | 1931 | return $section; 1932 | } 1933 | 1934 | sub heat_parameters_file { 1935 | 1936 | my $default_params= << "DEFAULT_PARAMS"; 1937 | parameters: 1938 | # iapp_service_name: $iapp_name 1939 | ve_instance: 1.2.3.4 1940 | # bigip_un: admin 1941 | # bigip_pw: admin 1942 | DEFAULT_PARAMS 1943 | 1944 | my $params= $default_params; 1945 | 1946 | foreach $vartype (@vartypes) { 1947 | 1948 | @vt= @$vartype; # Special handling for local variables 1949 | if (($#vt +1) > 0) { 1950 | 1951 | $_= $vt[0]; 1952 | if (/^__local__/) { 1953 | $params= $params . " " . local__bigip_names . ":\n"; 1954 | } 1955 | } 1956 | 1957 | foreach $v (sort @$vartype) { 1958 | 1959 | $v_name= $v; 1960 | $v_name=~ s/__(.*)__/$1/; 1961 | 1962 | $params= $params . " " . $v_name . ":\n"; 1963 | 1964 | $_= $v_name; 1965 | if (/^pm__/) { # Special processing for pool members 1966 | 1967 | $v_name_properties= $v_name . "_properties"; 1968 | $params= $params . " " . $v_name_properties . ":\n"; 1969 | } 1970 | 1971 | if (defined($imports{$v})) { # Special processing for imported objects 1972 | $v_name_import= $v_name . "_import"; 1973 | $params= $params . " " . $v_name_import . ":\n"; 1974 | } 1975 | 1976 | 1977 | } 1978 | } 1979 | 1980 | return $params; 1981 | } 1982 | 1983 | 1984 | ### The next functions are for the ansible-playbook and ansible-role options 1985 | 1986 | sub ansible_playbook { 1987 | 1988 | # Ansible output file 1989 | $ansible_filename= $t2i; 1990 | $ansible_filename=~ s/\.t2i$/\.yaml/; 1991 | 1992 | open ANSIBLE, ">", $ansible_filename 1993 | or die "Can't open file where the Ansible playbook would be written ($ansible_filename): $!"; 1994 | 1995 | print ANSIBLE ansible_playbook_head(); 1996 | print ANSIBLE ansible_variables(); 1997 | 1998 | close ANSIBLE 1999 | or die "Couldn't close the file for the Ansible playbook ($ansible_filename): $!"; 2000 | 2001 | print "Written Ansible playbook $ansible_filename\n"; 2002 | 2003 | } 2004 | 2005 | sub ansible_playbook_head { 2006 | 2007 | my $head = << "HEAD"; 2008 | 2009 | --- 2010 | - hosts: 2011 | tasks: 2012 | - name: Add to the BIG-IP library the iApp file $iapp_name.tmpl 2013 | delegate_to: localhost 2014 | bigip_iapp_template: 2015 | content: "{{ lookup('template', '$iapp_name.tmpl') }}" 2016 | # force: 2017 | server: 2018 | state: "present" 2019 | - name: Deploy the iApp $iapp_name 2020 | delegate_to: localhost 2021 | bigip_iapp_service: 2022 | name: 2023 | template: $iapp_name 2024 | # force: 2025 | server: 2026 | state: "present" 2027 | HEAD 2028 | 2029 | return $head; 2030 | } 2031 | 2032 | sub ansible_role_print { 2033 | 2034 | my $role= << "ROLE1"; 2035 | 2036 | --- 2037 | 2038 | 2039 | ## Deploy section 2040 | 2041 | - name: Add to the BIG-IP library the iApp file $iapp_name.tmpl 2042 | delegate_to: localhost 2043 | bigip_iapp_template: 2044 | content: "{{ lookup('template', '$iapp_name.tmpl') }}" 2045 | # force: 2046 | server: "{{ bigip }}" 2047 | state: "present" 2048 | tags: 2049 | - add 2050 | 2051 | - name: Deploy the iApp $iapp_name 2052 | delegate_to: localhost 2053 | bigip_iapp_service: 2054 | name: "{{ iapp_service_name }}" 2055 | template: $iapp_name 2056 | force: true 2057 | server: "{{ bigip }}" 2058 | state: "present" 2059 | ROLE1 2060 | 2061 | $role.= ansible_variables(); 2062 | 2063 | $role.= << "ROLE2"; 2064 | tags: 2065 | - add 2066 | ROLE2 2067 | 2068 | $role.= << "ROLE3"; 2069 | 2070 | ### Undeploy section 2071 | 2072 | - name: Undeploy the iApp $iapp_name 2073 | delegate_to: localhost 2074 | bigip_iapp_service: 2075 | name: "{{ iapp_service_name }}" 2076 | template: $iapp_name 2077 | force: true 2078 | server: "{{ bigip }}" 2079 | state: "absent" 2080 | tags: 2081 | - del 2082 | 2083 | - name: Delete from BIG-IP the iApp file $iapp_name.tmpl 2084 | delegate_to: localhost 2085 | bigip_iapp_template: 2086 | content: "{{ lookup('template', '$iapp_name.tmpl') }}" 2087 | force: true 2088 | server: "{{ bigip }}" 2089 | state: "absent" 2090 | register: result 2091 | failed_when: 2092 | - not result|success 2093 | - "'referenced by one or more applications' not in result.msg" 2094 | tags: 2095 | - del 2096 | 2097 | ROLE3 2098 | 2099 | return $role; 2100 | } 2101 | 2102 | sub ansible_variables_value{ 2103 | 2104 | my $variable= $_[0]; 2105 | 2106 | if ($ARGV[0] eq "ansible-role") { 2107 | return "\"{{ $variable }}\""; 2108 | } 2109 | 2110 | return "\"\""; 2111 | } 2112 | 2113 | sub ansible_variables{ 2114 | 2115 | if ($ARGV[0] eq "ansible-playbook") { 2116 | # Additional space identation 2117 | $sp= " "; 2118 | } else { 2119 | $sp= ""; 2120 | } 2121 | 2122 | my $vars_count= 0; 2123 | my $value; 2124 | 2125 | foreach $vartype (@vartypes) { 2126 | 2127 | @v= $vartype; 2128 | $vars_count+= $#v +1; 2129 | } 2130 | 2131 | if ($vars_count == 0) { 2132 | return ""; 2133 | } 2134 | 2135 | my $section= << "VARIABLES"; 2136 | $sp parameters: 2137 | $sp variables: 2138 | VARIABLES 2139 | 2140 | $vartype_plain= 1; 2141 | 2142 | foreach $vartype (@vartypes) { 2143 | 2144 | @vt= @$vartype; # Special handling for local variables 2145 | if (($#vt +1) > 0) { 2146 | 2147 | $_= $vt[0]; 2148 | if (/^__local__/) { 2149 | $section.= "$sp - name: \"local__bigip_names\"\n"; 2150 | $value= ansible_variables_value("local__bigip_names"); 2151 | $section.= "$sp value: $value\n"; 2152 | } 2153 | } 2154 | 2155 | # we append a var__ prefix if it is plain var type 2156 | if ($vartype_plain == 1) { 2157 | $prefix= "var__"; 2158 | $vartype_plain= 0; 2159 | } else { 2160 | $prefix= ""; 2161 | } 2162 | 2163 | foreach $v (sort @$vartype) { 2164 | 2165 | $v_name= $v; 2166 | $v_name=~ s/__(.*)__/$1/; 2167 | $v_name= $prefix . $v_name; 2168 | 2169 | $section.= "$sp - name: \"$v_name\"\n"; 2170 | $value= ansible_variables_value($v_name); 2171 | $section.= "$sp value: $value\n"; 2172 | 2173 | $_= $v_name; 2174 | if (/^pm__/) { # Special handling for pool members 2175 | 2176 | $v_name_properties= $v_name . "_properties"; 2177 | $section.= "$sp - name: \"$v_name_properties\"\n"; 2178 | $value= ansible_variables_value($v_name_properties); 2179 | $section.= "$sp value: $value\n"; 2180 | } 2181 | 2182 | if (defined($imports{$v})) { # Special processing for imported objects 2183 | $v_name_import= $v_name . "_import"; 2184 | $section.= "$sp - name: \"$v_name_import\"\n"; 2185 | $value= ansible_variables_value($v_name_import); 2186 | $section.= "$sp value: $value\n"; 2187 | } 2188 | 2189 | } 2190 | } 2191 | 2192 | return $section; 2193 | } 2194 | 2195 | sub ansible_role { 2196 | 2197 | $ansible_directory= "roles" . "/" . $iapp_name . "/" . "tasks"; 2198 | $ansible_filename= $ansible_directory . "/" . "main.yaml"; 2199 | 2200 | system("mkdir -p $ansible_directory"); 2201 | 2202 | if ( $? == -1 ) { 2203 | die "Error while trying to create directory for role $ansible_directory: $!"; 2204 | } 2205 | 2206 | open ANSIBLE, ">", $ansible_filename 2207 | or die "Can't open file for the Ansible role would be written ($ansible_filename): $!"; 2208 | 2209 | print ANSIBLE ansible_role_print(); 2210 | 2211 | close ANSIBLE 2212 | or die "Couldn't close the file for the Ansible role ($ansible_filename): $!"; 2213 | 2214 | print "Written Ansible role $ansible_filename\n\n"; 2215 | 2216 | print "Don't forget to copy the iApp template $iapp_name.tmpl into the $iapp_name directory\n\n"; 2217 | 2218 | } 2219 | 2220 | 2221 | ### The next functions are for the iworkflow-json-apl option 2222 | 2223 | sub variable_is_required { 2224 | my $vname= $_[0]; 2225 | 2226 | if (defined($properties{$vname})) { 2227 | $_= $properties{$vname}; 2228 | 2229 | if (/(^|\s+)required($|\s+)/) { 2230 | return "true"; 2231 | } 2232 | } 2233 | 2234 | return "false"; 2235 | } 2236 | 2237 | sub iworkflow_json_apl { 2238 | 2239 | my $create_file= $_[0]; 2240 | 2241 | if ($create_file) { 2242 | 2243 | open APL, ">", $apl_filename 2244 | or die "Can't open file where the APL json would be written ($apl_filename): $!"; 2245 | } 2246 | 2247 | my $json= { }; 2248 | my $i= 0; # iterates on the JSON elements 2249 | my $j= 0; # iterates on the variable type descriptions 2250 | 2251 | foreach $vartype (@vartypes) { 2252 | 2253 | @vt= @$vartype; 2254 | 2255 | if (($#vt +1) > 0) { 2256 | 2257 | $_= $vt[0]; 2258 | 2259 | # vt_name = variable type name 2260 | if (/__(.+)__(.+)__/) { 2261 | $vt_name= $1; # non plain variables type 2262 | } else { 2263 | $vt_name= "var"; # plain variables type 2264 | } 2265 | 2266 | $json->{'sections'}->[$i]->{'description'}= $vartypes_desc[$j]; 2267 | $json->{'sections'}->[$i]->{'displayName'}= $vt_name; 2268 | 2269 | $i= $i +1; 2270 | } 2271 | 2272 | $j= $j +1; 2273 | } 2274 | 2275 | my $vartype_plain= 1; 2276 | $i= 0; 2277 | 2278 | foreach $vartype (@vartypes) { 2279 | 2280 | @vt= @$vartype; 2281 | 2282 | if (($#vt +1) > 0) { 2283 | 2284 | $_= $vt[0]; 2285 | 2286 | # vt_name = variable type name 2287 | if (/__(.+)__(.+)__/) { 2288 | $vt_name= $1; # non plain variables type 2289 | } else { 2290 | $vt_name= "var"; # plain variables type 2291 | } 2292 | 2293 | $_= $vt_name; # Special handling for local variables 2294 | if (/^local/) { 2295 | $json->{'vars'}->[$i]->{name}= "bigip_names"; 2296 | $json->{'vars'}->[$i]->{isRequired}= "false"; 2297 | $json->{'vars'}->[$i]->{description}= "List of all BIG-IP names in the cluster"; 2298 | $json->{'vars'}->[$i]->{displayName}= "List of all BIG-IP names in the cluster"; 2299 | $json->{'vars'}->[$i]->{section}= "local"; 2300 | 2301 | $i= $i +1; 2302 | } 2303 | 2304 | # This sort actually doesn't help because the order of the elements in the json is guaranteed 2305 | foreach $v (sort @$vartype) { 2306 | 2307 | $vname= $v; 2308 | 2309 | if (defined($labels{$vname})) { 2310 | $label= "$labels{$vname}"; 2311 | } else { 2312 | 2313 | # we append a __var prefix if it is plain var type 2314 | if ($vartype_plain == 1) { 2315 | $prefix= "__var"; 2316 | } else { 2317 | $prefix= ""; 2318 | } 2319 | 2320 | $label= $prefix . $vname; 2321 | } 2322 | 2323 | $short_vname= $vname; 2324 | $short_vname=~ s/__(.*__|)(.+)__/$2/; 2325 | 2326 | $json->{'vars'}->[$i]->{name}= $short_vname; 2327 | $json->{'vars'}->[$i]->{isRequired}= variable_is_required($vname); 2328 | $json->{'vars'}->[$i]->{description}= "$label"; 2329 | $json->{'vars'}->[$i]->{displayName}= "$label"; 2330 | $json->{'vars'}->[$i]->{section}= $vt_name; 2331 | 2332 | $i= $i +1; 2333 | 2334 | if ($vt_name eq "pm") { 2335 | 2336 | $short_vname_properties= $short_vname . "_properties"; 2337 | 2338 | $json->{'vars'}->[$i]->{name}= $short_vname_properties; 2339 | $json->{'vars'}->[$i]->{isRequired}= variable_is_required($vname); 2340 | $json->{'vars'}->[$i]->{description}= "Properties for " . "$label"; 2341 | $json->{'vars'}->[$i]->{displayName}= "Properties for " . "$label"; 2342 | $json->{'vars'}->[$i]->{section}= $vt_name; 2343 | 2344 | $i= $i +1; 2345 | } 2346 | 2347 | if (defined($imports{$v})) { # Special processing for imported objects 2348 | $short_vname_import= $short_vname . "_import"; 2349 | 2350 | $json->{'vars'}->[$i]->{name}= $short_vname_import; 2351 | $json->{'vars'}->[$i]->{isRequired}= variable_is_required($vname); 2352 | $json->{'vars'}->[$i]->{description}= "URL for " . "$label"; 2353 | $json->{'vars'}->[$i]->{displayName}= "URL for " . "$label"; 2354 | $json->{'vars'}->[$i]->{section}= $vt_name; 2355 | 2356 | $i= $i +1; 2357 | } 2358 | 2359 | } 2360 | } 2361 | 2362 | $vartype_plain= 0; 2363 | } 2364 | 2365 | 2366 | if ($create_file) { 2367 | 2368 | my $json_text = to_json($json, {utf8 => 1, pretty => 1}); 2369 | 2370 | print APL $json_text; 2371 | 2372 | close APL 2373 | or die "Couldn't close the file for the APL json ($apl_filename): $!"; 2374 | 2375 | print "Written the resulting APL json to $apl_filename\n"; 2376 | } else { 2377 | return $json; 2378 | } 2379 | } 2380 | 2381 | sub iworkflow_json_import { 2382 | 2383 | open IMPORT, ">", $import_filename 2384 | or die "Can't open file where the IMPORT json would be written ($import_filename): $!"; 2385 | 2386 | my $json= { }; 2387 | 2388 | $json->{'name'}= $iapp_name; 2389 | 2390 | open IAPP, "<", $iapp_filename 2391 | or die "Can't open file of the iApp ($iapp_filename). This file has to be generated previously: $!"; 2392 | $json->{'templateContent'}= do { local $/; }; 2393 | close IAPP; 2394 | 2395 | if (defined($iapp{"requires-bigip-version-min"})) { 2396 | $json->{'minSupportedBIGIPVersion'}= $iapp{"requires-bigip-version-min"}; 2397 | } 2398 | if (defined($iapp{"requires-bigip-version-max"})) { 2399 | $json->{'maxSupportedBIGIPVersion'}= $iapp{"requires-bigip-version-max"}; 2400 | } 2401 | if (defined($iapp{"unsupported-bigip-versions"})) { 2402 | $json->{'unsupportedBIGIPVersions'}= from_json($iapp{"unsupported-bigip-versions"}); 2403 | } 2404 | $json->{'template'}= iworkflow_json_apl(0); 2405 | 2406 | my $json_text = to_json($json, {utf8 => 1, pretty => 1}); 2407 | 2408 | print IMPORT $json_text; 2409 | 2410 | close IMPORT 2411 | or die "Couldn't close the file for the IMPORT json ($import_filename): $!"; 2412 | 2413 | print "Written the resulting IMPORT json to $import_filename\n"; 2414 | 2415 | } 2416 | 2417 | sub check_variable_names() { 2418 | 2419 | my @variables = @{$_[0]}; 2420 | 2421 | if (grep(/-/, @variables)) { 2422 | 2423 | print STDERR "Aborting: found variables that have hyphen in their name and iApps don't support these. Please rename the following variables: "; 2424 | 2425 | foreach $v (@variables) { 2426 | 2427 | $_= $v; 2428 | if (/-/) { 2429 | 2430 | print STDERR "$v "; 2431 | } 2432 | } 2433 | 2434 | print STDERR "\n"; 2435 | 2436 | exit(1); 2437 | } 2438 | 2439 | } 2440 | 2441 | sub check_iworkflow_variables() { 2442 | 2443 | my @v = @{$_[0]}; 2444 | 2445 | if (($#v == 1) || ($#v == -1)) { # or we have the two expected variables or we have none 2446 | return 0; 2447 | } 2448 | 2449 | print STDERR "Aborting: when using iWorkflow's variables both __pool__port__ and __pool__addr__ must be defined. Showing the variables found: "; 2450 | 2451 | foreach my $i (@v) { 2452 | print STDERR "$i "; 2453 | } 2454 | 2455 | print STDERR "\n"; 2456 | 2457 | exit(1); 2458 | } 2459 | 2460 | #################################################################################### 2461 | # Functions for BIG-IP Controller (aka Container Connector) ConfigMaps 2462 | 2463 | sub container_configmap { 2464 | 2465 | # Ansible output file 2466 | $configmap_filename= $t2i; 2467 | $configmap_filename=~ s/\.t2i$/\.yaml/; 2468 | 2469 | open CONFIGMAP, ">", $configmap_filename 2470 | or die "Can't open file where the Container ConfigMap would be written ($configmap_filename): $!"; 2471 | 2472 | print CONFIGMAP container_configmap_head(); 2473 | print CONFIGMAP container_configmap_variables(); 2474 | print CONFIGMAP container_configmap_tail(); 2475 | 2476 | close CONFIGMAP 2477 | or die "Couldn't close the file for the Container ConfigMap (configmap_filename): $!"; 2478 | 2479 | print "Written Container ConfigMap $configmap_filename\n"; 2480 | 2481 | } 2482 | 2483 | sub container_configmap_head { 2484 | 2485 | my $head = << "HEAD"; 2486 | 2487 | --- 2488 | kind: ConfigMap 2489 | apiVersion: v1 2490 | metadata: 2491 | name: "%%servicename%%" 2492 | namespace: "%%namespace%%" 2493 | labels: 2494 | f5type: virtual-server 2495 | data: 2496 | # schema v.0.1.4 is required as of k8s-bigip-ctlr v1.3.0 2497 | schema: "f5schemadb://bigip-virtual-server_v0.1.4.json" 2498 | data: | 2499 | { 2500 | "virtualServer": { 2501 | "backend": { 2502 | "serviceName": "%%backend-servicename%%", 2503 | "servicePort": "%%backend-serviceport%%" 2504 | }, 2505 | "frontend": { 2506 | "partition": "%%partition%%", 2507 | "iapp": "/Common/$iapp_name", 2508 | "iappPoolMemberTable": { 2509 | "name": "pool__members", 2510 | "columns": [ 2511 | {"name": "addr", "kind": "IPAddress"}, 2512 | {"name": "port", "kind": "Port"}, 2513 | {"name": "connection_limit", "value": "0"} 2514 | ] 2515 | }, 2516 | "iappOptions": { 2517 | "description": $iapp{"description"}, 2518 | "trafficGroup": "/Common/traffic-group-1" 2519 | }, 2520 | HEAD 2521 | 2522 | return $head; 2523 | } 2524 | 2525 | sub container_configmap_variables_value{ 2526 | 2527 | my $variable= $_[0]; 2528 | 2529 | return "\"%%" . $variable . "%%\""; 2530 | } 2531 | 2532 | sub container_configmap_variables { 2533 | 2534 | my $after_variable= 0; 2535 | 2536 | my $section= << "VARIABLES"; 2537 | "iappVariables": { 2538 | VARIABLES 2539 | 2540 | $vartype_plain= 1; 2541 | 2542 | foreach $vartype (@vartypes) { 2543 | 2544 | @vt= @$vartype; # Special handling for local variables 2545 | if (($#vt +1) > 0) { 2546 | 2547 | $_= $vt[0]; 2548 | if (/^__local__/) { 2549 | 2550 | $section.= ",\n" if ($after_variable); 2551 | 2552 | $section.= ' "local__bigip_names": '; 2553 | $section.= container_configmap_variables_value("local__bigip_names"); 2554 | 2555 | $after_variable= 1; 2556 | } 2557 | } 2558 | 2559 | # we append a var__ prefix if it is plain var type 2560 | if ($vartype_plain == 1) { 2561 | $prefix= "var__"; 2562 | $vartype_plain= 0; 2563 | } else { 2564 | $prefix= ""; 2565 | } 2566 | 2567 | foreach $v (sort @$vartype) { 2568 | 2569 | $v_name= $v; 2570 | $v_name=~ s/__(.*)__/$1/; 2571 | $v_name= $prefix . $v_name; 2572 | 2573 | $section.= ",\n" if ($after_variable); 2574 | $section.= " \"$v_name\": "; 2575 | $section.= container_configmap_variables_value($v_name); 2576 | 2577 | $after_variable= 1; 2578 | 2579 | $_= $v_name; 2580 | if (/^pm__/) { # Special handling for pool members 2581 | $v_name_properties= $v_name . "_properties"; 2582 | 2583 | $section.= ",\n" if ($after_variable); 2584 | $section.= " \"$v_name_properties\": "; 2585 | $section.= container_configmap_variables_value($v_name_properties); 2586 | } 2587 | 2588 | if (defined($imports{$v})) { # Special processing for imported objects 2589 | $v_name_import= $v_name . "_import"; 2590 | 2591 | $section.= ",\n" if ($after_variable); 2592 | $section.= " \"$v_name_import\": "; 2593 | $section.= container_configmap_variables_value($v_name_import); 2594 | } 2595 | 2596 | } 2597 | } 2598 | 2599 | $section.= "\n }\n"; 2600 | 2601 | return $section; 2602 | 2603 | } 2604 | 2605 | sub container_configmap_tail { 2606 | 2607 | my $tail = << "TAIL"; 2608 | } 2609 | } 2610 | } 2611 | TAIL 2612 | 2613 | return $tail; 2614 | } 2615 | 2616 | 2617 | #################################################################################### 2618 | # Functions for t2i generation 2619 | 2620 | sub check_this_is_bigip { 2621 | 2622 | if (( -r "/config/bigip.conf") && ( -r "/config/bigip_base.conf" )) { 2623 | return 1; 2624 | } 2625 | 2626 | return 0; 2627 | } 2628 | 2629 | sub make_snapshot { 2630 | my $snapshot= $_[0]; 2631 | 2632 | system("mkdir -p $cfgdir/tmsh2iapp.$snapshot && cp /config/bigip.conf /config/bigip_base.conf $cfgdir/tmsh2iapp.$snapshot"); 2633 | 2634 | if ( $? == -1 ) { 2635 | die "Error while trying to make snapshot $snapshot: $!"; 2636 | } 2637 | 2638 | print "Created snapshot of configuration files in folder $cfgdir/tmsh2iapp.$snapshot\n"; 2639 | } 2640 | 2641 | sub diff_snapshots { 2642 | 2643 | my $snapshot1= $_[0]; 2644 | my $snapshot2= $_[1]; 2645 | 2646 | open(DIFF, "diff -ur $cfgdir/tmsh2iapp.$snapshot1 $cfgdir/tmsh2iapp.$snapshot2 | egrep '^\\+\\+\\+' | wc -l |") || die "Could not geneterate the baseline .t2i (when running first diff command): $!\n"; 2647 | $changed= do { local $/; }; 2648 | close(DIFF); 2649 | 2650 | if ($changed == 0) { 2651 | print STDERR "There are no differences between snapshot $snapshot1 and $snapshot2\n"; 2652 | exit(1); 2653 | } elsif ($changed == 2) { 2654 | print STDERR "Warning: Both bigip.conf and bigip_base.conf differ in snapshots $snapshot1 and $snapshot2. An iApp should contain either local of cluster-wide objects but not both\n"; 2655 | } 2656 | 2657 | # We also remove some basic stuff like the app-service field 2658 | open(DIFF, "diff -ur $cfgdir/tmsh2iapp.$snapshot1 $cfgdir/tmsh2iapp.$snapshot2 | egrep '^\\+' | grep -v app-service | egrep -v '^\\+\\+\\+' | cut -c 2- |") || die "Could not geneterate the baseline .t2i (when running the second diff command): $!"; 2659 | 2660 | $baseline= do { local $/; }; 2661 | $baseline=~ s/\/Common\///g; 2662 | close DIFF; 2663 | 2664 | $output= $cfgdir . "/" . "tmsh2iapp-" . $snapshot1 . "-" . $snapshot2 . ".t2i"; 2665 | open(OUTPUT, ">", $output) || die "Could not geneterate the baseline file $output: $!"; 2666 | 2667 | do { 2668 | local $/; 2669 | print OUTPUT $baseline; 2670 | }; 2671 | 2672 | close OUTPUT; 2673 | 2674 | print "Written the resulting baseline file to $output\n"; 2675 | } 2676 | 2677 | #################################################################################### 2678 | # @option(default-route-domain) processing 2679 | 2680 | sub attribute_option_default_route_domain { 2681 | 2682 | my $value= $_[0]; 2683 | 2684 | if (($value ne "true") && ($value ne "false")) { 2685 | print STDERR "Aborting due to \@option(default-route-domain) is not either \"true\" or \"false\". Value found is $value\n"; 2686 | exit(1); 2687 | } 2688 | 2689 | $option{"default-route-domain"}= $value; 2690 | } 2691 | 2692 | #################################################################################### 2693 | # @option(iapp-type) processing 2694 | 2695 | sub attribute_option_iapp_type { 2696 | 2697 | my $value= $_[0]; 2698 | 2699 | if (($ARGV[0] eq "heat-create") || 2700 | ($ARGV[0] eq "ansible-playbook") || 2701 | ($ARGV[0] eq "ansible-role") || 2702 | ($ARGV[0] eq "iworkflow-json-apl") || 2703 | ($ARGV[0] eq "iworkflow-template-import") || 2704 | ($ARGV[0] eq "container-configmap") || 2705 | ($ARGV[0] eq "snapshot-create") || 2706 | ($ARGV[0] eq "t2i-create")) { 2707 | 2708 | return; 2709 | } 2710 | 2711 | if (defined($option{"iapp-type"})) { 2712 | 2713 | print STDERR "Aborting due to \@option(iapp-type) has been specified more than once\n"; 2714 | exit(1); 2715 | } 2716 | 2717 | if ($ARGV[0] ne "auto") { 2718 | 2719 | print STDERR "Notice: not using \@option(iapp-type): $value because iapp-type is being overridden in the command line\n"; 2720 | return; 2721 | } 2722 | 2723 | $ARGV[0]= $value; 2724 | 2725 | # Checking of $value is done in the regular $ARGV[0] checking 2726 | $option{"iapp-type"}= $value; 2727 | } 2728 | 2729 | #################################################################################### 2730 | # BIG-IP object import functions 2731 | 2732 | # Instantiates perl arrays for import types with the the iApp variables in their contents, one array per type 2733 | 2734 | sub attribute_import_type { 2735 | 2736 | print STDERR "attribute_import_type($_[0], $_[1])\n" if ($debug); 2737 | 2738 | my $import_otype= $_[0]; # original type name 2739 | my $import_type= $import_otype; 2740 | $import_type=~ s/-/_/g; # the perl variables are the same as tmsh but we replace "-" with "_"; 2741 | my $value= $_[1]; 2742 | 2743 | if (!grep( /^$import_otype$/, @import_file_types)) { 2744 | print STDERR "Aborting due to unknown \@import of \"$import_otype\" in the t2i template. Check the name is correct.\n"; 2745 | exit(1); 2746 | } 2747 | 2748 | if (@$import_type) { 2749 | print STDERR "Aborting due to \"$import_otype\" already defined as \@import. Pleasse correct the .t2i file.\n"; 2750 | exit(1); 2751 | } 2752 | 2753 | @$import_type= split /\s+/, $value; # we assign the values into an array of each type, ie: ssl_cert[0]= __var_name_1__ ... 2754 | 2755 | foreach $var (@$import_type) { 2756 | 2757 | $imports{$var}= "true"; # This hash defines all variables that have an import regardless of the type 2758 | 2759 | if (!grep(/^$var$/, @variables)) { 2760 | 2761 | print STDERR "Error while trying to import $import_otype: there is no variable named $var in the .t2i file. Check that the name is correct.\n"; 2762 | exit(1); 2763 | } 2764 | 2765 | if ($import_type eq "data_group") { # We do additional checking for data-groups 2766 | 2767 | if (!defined($properties{$var})) { 2768 | 2769 | print STDERR "Aborting due to \@properties for import data-group variable $var not being defined before the \@import of the variable. Put the \@properties at the top of the .t2i file.\n"; 2770 | exit(1); 2771 | } 2772 | 2773 | $_= $properties{$var}; 2774 | 2775 | if (!/type\s+(string|integer|ip)/) { 2776 | 2777 | print STDERR "Aborting due to \@properties for import data-group variable $var doesn't have the type indicated. Possible options are:\n"; 2778 | print STDERR "type string\n"; 2779 | print STDERR "type integer\n"; 2780 | print STDERR "type ip\n\n"; 2781 | print STDERR "Check the tmsh2iapp documentation for more information.\n"; 2782 | exit(1); 2783 | } 2784 | } 2785 | } 2786 | } 2787 | 2788 | 2789 | #################################################################################### 2790 | # Generic functions 2791 | 2792 | sub uniq { 2793 | my %seen; 2794 | return grep { !$seen{$_}++ } @_; 2795 | } 2796 | 2797 | sub print_version { 2798 | 2799 | print "$tmsh2iapp_version\n"; 2800 | } 2801 | 2802 | sub check_legacy_properties_usage { 2803 | 2804 | $v_name= $_[0]; 2805 | $v_value= $_[1]; 2806 | 2807 | $_= $v_value; 2808 | 2809 | if (/default|display|required|validator/) { 2810 | 2811 | print STDERR "Aborting: \@properties attribute for variable \"$v_name\" is using legacy APL values: \"$v_value\"\n"; 2812 | print STDERR "You have two options:\n"; 2813 | print STDERR "- Use a version of tmsh2iapp earlier than 20170627.1\n"; 2814 | print STDERR "- Rename \@properties to \@apl in the .t2i file (recommended option)\n"; 2815 | 2816 | exit(1); 2817 | } 2818 | } 2819 | 2820 | sub print_synopsis { 2821 | 2822 | my $synopsis= << "SYNOPSIS"; 2823 | 2824 | Synopsis: 2825 | 2826 | - iApp creation: 2827 | 2828 | tmsh2iapp.pl auto