├── 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 | 
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 | *  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 .**
69 |
70 | **Please refer to  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 |
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
2829 | tmsh2iapp.pl (service|nofolder|common)
2830 | tmsh2iapp.pl (service|nofolder|common)-disable-strict-updates
2831 | tmsh2iapp.pl system
2832 |
2833 | - Heat template: tmsh2iapp.pl heat-create
2834 |
2835 | - Ansible playbook: tmsh2iapp.pl ansible-playbook
2836 |
2837 | - Ansible role: tmsh2iapp.pl ansible-role
2838 |
2839 | - iWorkflow JSON files: tmsh2iapp.pl (iworkflow-template-import|iworkflow-json-apl)
2840 |
2841 | - BIG-IP Controller (aka Container Connector) ConfigMap: tmsh2iapp.pl container-configmap
2842 |
2843 | - For creating a baseline .t2i file:
2844 |
2845 | tmsh2iapp.pl snapshot-create
2846 | tmsh2iapp.pl t2i-create
2847 |
2848 | Additional information:
2849 |
2850 | - For more information on using this tool: tmsh2iapp.pl usage
2851 |
2852 | - For version information of this tool: tmsh2iapp.pl version
2853 |
2854 | SYNOPSIS
2855 |
2856 | print STDERR $synopsis;
2857 | }
2858 |
2859 |
2860 | sub print_usage {
2861 |
2862 | my $description= << "DESCRIPTION";
2863 |
2864 | tmsh2iapp creates an iAPP from a TMSH-like template. The resulting iApp is written to a file with the same name but .tmpl extension.
2865 |
2866 | This utility can also create helper files to use the resulting iApp in orchestration systems (Heat/Ansible/iWorkflow).
2867 |
2868 | DESCRIPTION
2869 |
2870 | print STDERR $description;
2871 |
2872 | print_synopsis();
2873 |
2874 | my $details= <<"DETAILS";
2875 |
2876 | Details:
2877 |
2878 | + Parameters for iApp creation:
2879 |
2880 | auto - The type of iApp generated is indicated with an \@option(iapp-type) attribute in the .t2i file. Any of the options below is possible. ie: \@option(iapp-type): service
2881 |
2882 | service - This is the most typical option. The configuration will be contained in it's own iApp folder in the partition where the iApp is instantiated.
2883 | service-disable-strict-updates - Like "service" but allows the iApp generated configuration to be modified from outside the iApp.
2884 |
2885 | common - The configuration will be placed in the /Common partition without an iApp folder.
2886 | common-disable-strict-updates - Like "common" but allows the iApp generated configuration to be modified from outside the iApp.
2887 |
2888 | nofolder - The configuration will be placed in the partition where the iApp is created without an iApp folder.
2889 | nofolder-disable-strict-updates - Like "nofolder" but allows the iApp generated configuration to be modified from outside the iApp.
2890 |
2891 | Note that "nofolder" should have the same behaviour as the "common" when the iApp is instantiated in the /Common partition.
2892 |
2893 | system - Indicates that on iApp instantiation the configuration will be contained in the / folder.
2894 |
2895 | + Parameter for HEAT template creation:
2896 |
2897 | heat-create - Creates HEAT parameters (.parameters) and HEAT template (.yaml) files to load the iApp and instantiate it in Openstack.
2898 |
2899 | + Parameters for Ansible playbook creation:
2900 |
2901 | ansible-playbook - Creates a template Ansible playbook file to ease deploying the iApp.
2902 | ansible-role - Creates a template Ansible role directory alongside the iApp to ease deploying the iApp.
2903 |
2904 | + Parameters for iWorkflow:
2905 |
2906 | iworkflow-template-import - Create a compound JSON file that incorporates iApp, APL and other bits to allow single file import via REST or GUI. This command requires the iApp .tmpl file to be present in the same folder as the .t2i. This JSON file doesn't incoporate the Service Tier information. This will be implemented with service templates.
2907 | iworkflow-json-apl - Only create the APL JSON file.
2908 |
2909 | Note: At the moment tmsh2iapp doesn't feature creating service templates and service instances JSON files.
2910 |
2911 | + Parameter for creating ConfigMaps for BIG-IP Controller (aka Container Connector):
2912 |
2913 | container-configmap - Creates a ConfigMap for deploying the iApp with BIG-IP Controller.
2914 |
2915 | + Parameters for creating a baseline .t2i file (these are expected to be run in a BIG-IP):
2916 |
2917 | snapshot-create - Copies bigip.conf and bigip.conf in a snapshot folder (specified as parameter) located in $cfgdir
2918 | t2i-create - Compares two snapshot folders and produces a baseline .t2i file in $cfgdir
2919 |
2920 | DETAILS
2921 |
2922 | print STDERR $details;
2923 | }
2924 |
2925 |
--------------------------------------------------------------------------------