F5Networks / f5-common-python

Python SDK for configuration and monitoring of F5® BIG-IP® devices via the iControl® REST API.
https://f5-sdk.readthedocs.org
Apache License 2.0
262 stars 135 forks source link

POST iApp template failing Help Wanted #1356

Closed tthomas0702 closed 6 years ago

tthomas0702 commented 6 years ago

I am attempting to import an iApp to a BIG-IP and failing. I am not 100% sure I am doing it correctly so I would like to show how I am doing it and then show the data I collected on the results. I can import an iApp template successfully using Postman.

If I am doing this wrong please give me some pointers and ignore the data I collected about the failure.

Versions: Python 2.7.12 f5-sdk 3.0.8 BIG-IP 13.0.0-HF3

What I am dong that is failing:

>>> from f5.bigip import ManagementRoot
>>> import requests
>>> requests.packages.urllib3.disable_warnings()
>>> mgmt = ManagementRoot('34.214.236.171', 'admin', 'fake1')
>>>
>>> postData = open('code/iapp_rest/json_post_appsvcs_2.0.004', 'r').read()
>>> type(postData)
<type 'str'>
>>>
>>> tmplPost = mgmt.tm.sys.application.templates.template.create(template= postData, partition= 'Common', name= 'appsvcs_integration_v2.0.004')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/f5/bigip/resource.py", line 1002, in create
    return self._create(**kwargs)
  File "/usr/local/lib/python2.7/dist-packages/f5/bigip/resource.py", line 968, in _create
    response = session.post(_create_uri, json=kwargs, **requests_params)
  File "/usr/local/lib/python2.7/dist-packages/icontrol/session.py", line 271, in wrapper
    raise iControlUnexpectedHTTPError(error_message, response=response)
icontrol.exceptions.iControlUnexpectedHTTPError: 400 Unexpected Error: Bad Request for uri: https://34.214.236.171:443/mgmt/tm/sys/application/template/
Text: u'{"code":400,"message":"01070734:3: Configuration error: An application template (/Common/appsvcs_integration_v2.0.004) must define a \\"definition\\" action","errorStack":[],"apiError":3}'

Question: For "template" should I be giving JSON or iApp tcl script? In the above I am giving json.

I took capture durng a failure and during a working example with Postman to see the difference. Some parts have been shorted to make more readable:

Failure:

POST /mgmt/tm/sys/application/template/ HTTP/1.1
Host: localhost:8100
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.18.4 f5-icontrol-rest-python/1.3.4
Content-Type: application/json
Cookie: BIGIPAuthUsernameCookie=admin; BIGIPAuthCookie=C965DA5B774592505CC6D59DCCAD1E14632A84EB
Authorization: Basic YWRtaW46Z29vYnkx
Local-Ip-From-Httpd: 10.0.3.235
REMOTEROLE: 0
REMOTECONSOLE: /usr/bin/tmsh
Tmui-Dubbuf: ceHT++ir1ujaGLEm9lbyXGpG
X-Forwarded-Proto: http
X-Forwarded-For: 50.206.82.152
X-Forwarded-Host: 34.214.236.171
X-Forwarded-Server: localhost.localdomain
Connection: Keep-Alive
Content-Length: 205653

{
  "partition": "Common",
  "name": "appsvcs_integration_v2.0.004",
  "template": "{\n    \"kind\": \"tm:sys:application:template:templatestate\",\n    \"name\": \"appsvcs_integration_v2.0.004\",\n    \"partition\": \"Common\",\n    \"fullPath\": \"/Common/appsvcs_integration_v2.0.004\",\n    \"generation\": 1,\n    \"selfLink\": \"https://localhost/mgmt/tm/sys/application/template/~Common~appsvcs_integration_v2.0.004?expandSubcollections=true&ver=13.0.0\",\n    \"ignoreVerification\": \"false\",\n    \"requiresModules\": [\n        \"ltm\"\n    ],\n    \"totalSigningStatus\": \"not-all-signed\",\n    \"verificationStatus\": \"none\",\n    \"actionsReference\":...

HTTP/1.1 400 Bad Request
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate
Expires: -1
Content-Length: 185
Content-Type: application/json
Connection: keep-alive
Allow: 
REMOTEROLE: 0
Cookie: BIGIPAuthCookie=C965DA5B774592505CC6D59DCCAD1E14632A84EB; BIGIPAuthUsernameCookie=admin; 
Local-Ip-From-Httpd: 10.0.3.235
Accept-Encoding: gzip, deflate
X-Forwarded-Server: localhost.localdomain
X-Forwarded-Proto: http
REMOTECONSOLE: /usr/bin/tmsh
X-Forwarded-Host: 34.214.236.171
Date: 07 Jan 2018 20:19:54 UTC
Server: com.f5.rest.common.RestRequestSender

{"code":400,"message":"01070734:3: Configuration error: An application template (/Common/appsvcs_integration_v2.0.004) must define a \"definition\" action","errorStack":[],"apiError":3}

Working Postman Capture:

POST /mgmt/tm/sys/application/template HTTP/1.1
Host: localhost:8100
X-F5-Auth-Token: PAKPWQQUQNA2VUIVTJWEZV5QFJ
Content-Type: application/json
cache-control: no-cache
User-Agent: PostmanRuntime/7.1.1
Accept: */*
accept-encoding: gzip, deflate
Local-Ip-From-Httpd: 10.0.3.235
X-Forwarded-Proto: http
X-Forwarded-For: 50.206.82.152
X-Forwarded-Host: 34.214.236.171
X-Forwarded-Server: localhost.localdomain
Connection: Keep-Alive
Content-Length: 192048

{
    "kind": "tm:sys:application:template:templatestate",
    "name": "appsvcs_integration_v2.0.004",
    "partition": "Common",
    "fullPath": "/Common/appsvcs_integration_v2.0.004",
    "generation": 6,
    "selfLink": "https://localhost/mgmt/tm/sys/application/template/~Common~appsvcs_integration_v2.0.004?expandSubcollections=true&ver=13.0.0",
    "ignoreVerification": "false",
    "requiresModules": [
        "ltm"
    ],
    "totalSigningStatus": "not-all-signed",
    "verificationStatus": "none",
    "actionsReference": {...

HTTP/1.1 200 OK
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate
Expires: -1
Content-Length: 572
Content-Type: application/json
Connection: keep-alive
Allow: 
Local-Ip-From-Httpd: 10.0.3.235
X-Forwarded-Server: localhost.localdomain
X-Forwarded-Proto: http
accept-encoding: gzip, deflate
X-Forwarded-Host: 34.214.236.171
Date: 07 Jan 2018 20:11:28 UTC
Server: com.f5.rest.common.RestRequestSender

{"kind":"tm:sys:application:template:templatestate","name":"appsvcs_integration_v2.0.004","partition":"Common","fullPath":"/Common/appsvcs_integration_v2.0.004","generation":2753,"selfLink":"https://localhost/mgmt/tm/sys/application/template/~Common~appsvcs_integration_v2.0.004?ver=13.0.0","ignoreVerification":"false","requiresModules":["ltm"],"totalSigningStatus":"not-all-signed","verificationStatus":"none","actionsReference":{"link":"https://localhost/mgmt/tm/sys/application/template/~Common~appsvcs_integration_v2.0.004/actions?ver=13.0.0","isSubcollection":true}}

It looks like, in the failed capture, that some keys are being added into the json that look out of place. eg. "partition", "name", and "template" at the begining. If I leave out "partition" or "name", it errors requiring them.

I have tried a number of modifications to the json but always get the same error. There error message is originating from the BIG-IP:

/var/log/ltm: Jan 7 14:17:48 ip-10-0-3-235 err mcpd[5763]: 01070734:3: Configuration error: An application template (/Common/appsvcs_integration_v2.0.004) must define a "definition" action

I attached the file with the POST data I am using: json_post_appsvcs_2.0.004.txt

caphrim007 commented 6 years ago

@tthomas0702 it's complicated.

There is no such API to just "give it an iApp template". The API instead requires that you provide all of the fields of an iApp template. This means you need to essentially split apart a given iApp template into its component parts and then POST a JSON payload whose fields contain those component parts.

JSON is required, but the fields would hold the APL.

If you query this URL,

You can see the following

{
    "kind": "tm:sys:application:template:templatecollectionstate",
    "selfLink": "https://localhost/mgmt/tm/sys/application/template/example?ver=13.1.0",
    "items": [
        {
            "propertyDescriptions": {
                "actions": "Manage the set of actions associated with an application template.",
                "description": "User defined description.",
                "ignoreVerification": "Set to true to temporarily stop the verification of signature or checksum. Signature or checksum is still retained.",
                "requiresBigipVersionMax": "Specifies the maximum version of BIG-IP required by this template.",
                "requiresBigipVersionMin": "Specifies the minimum version of BIG-IP required by this template.",
                "requiresModules": "Adds, deletes, or replaces the list of modules that are required to be provisioned or enabled for this template to work.",
                "signingKey": "The private key to use for signing the template. Only for use with the generate signature command.",
                "tmplChecksum": "Computes a checksum for the script.",
                "tmplSignature": "Sign the script using the specified private key and corresponding public certificate."
            },
            "actions": {
                "isSubcollection": true,
                "propertyDescriptions": {
                    "htmlHelp": "The help for the application template action formatted as HTML.",
                    "implementation": "The script that is executed to create the configuration objects associated with the application.",
                    "macro": "",
                    "presentation": "Specifies what questions must be answered to create an application from the template.",
                    "roleAcl": "List of roles that are allowed to execute the action.",
                    "runAs": "The user account that will be used to execute the implementation script. If no account is specified, the script is executed as the calling user."
                }
            },
            "description": "",
            "ignoreVerification": "false",
            "requiresBigipVersionMax": "",
            "requiresBigipVersionMin": "",
            "requiresModules": [],
            "signingKey": "",
            "tmplChecksum": "",
            "tmplSignature": "",
            "naturalKeyPropertyNames": [
                "name",
                "partition",
                "subPath"
            ]
        }
    ]
}

You can see that there are a number of fields there, some of them which relate to the sections of an iApp itself. So, the hard part is left for the user to figure out unfortunately.

As you might imagine, trying to split up an iApp template is nearly impossible due to the bracing and escaping of bracing that may be codified in them. It's not totally impossible, but its not just template.split("{") either.

For this reason, the Ansible modules use a different, arguably more simple, approach. Shown here

We upload the template, and then use tmsh over REST to install it. This has the added advantage of picking up (automatically) and procs that you may define outside of the "sys application template FOO { ... }" blob (this inclusion is used heavily by iApps that F5 publishes and supports.

So I would recommend this alternate approach for what you want to do.

Lemme know if you have any other questions

tthomas0702 commented 6 years ago

@caphrim007 I need to make sure I understand.

My goal is to upload an unaltered template to a BIG-IP. This would just be added to the list of available templates but not deployed at this point as an iApp.

Is there a technical reason we cannot do this or is it just not a feature we have yet?

I can do this using the REST API.

caphrim007 commented 6 years ago

@tthomas0702 can you post the full POST payload that is sent? You left some off

tthomas0702 commented 6 years ago

@caphrim007

{ "kind": "tm:sys:application:template:templatestate", "name": "appsvcs_integration_v2.0.004", "partition": "Common", "fullPath": "/Common/appsvcs_integration_v2.0.004", "generation": 6, "selfLink": "https://localhost/mgmt/tm/sys/application/template/~Common~appsvcs_integration_v2.0.004?expandSubcollections=true&ver=13.0.0", "ignoreVerification": "false", "requiresModules": [ "ltm" ], "totalSigningStatus": "not-all-signed", "verificationStatus": "none", "actionsReference": { "link": "https://localhost/mgmt/tm/sys/application/template/~Common~appsvcs_integration_v2.0.004/actions?ver=13.0.0", "isSubcollection": true, "items": [ { "kind": "tm:sys:application:template:actions:actionsstate", "name": "definition", "fullPath": "definition", "generation": 6, "selfLink": "https://localhost/mgmt/tm/sys/application/template/~Common~appsvcs_integration_v2.0.004/actions/definition?ver=13.0.0", "implementation": "# Copyright (c) 2017 F5 Networks, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\npackage require base64\n\nset startTime [clock seconds]\nset bundler_timestamp [clock format $startTime -format {%Y%m%d%H%M%S}]\n\nset NAME \"F5 Application Services Integration iApp (Community Edition)\"\nset TMPLNAME \"appsvcs_integration_v2.0.\"\nset IMPLMAJORVERSION \"2.0\"\nset IMPLMINORVERSION \"004\"\nset IMPLVERSION [format \"%s.%s\" $IMPLMAJORVERSION $IMPLMINORVERSION]\nset POSTDEPLOY_DELAY 0\n\nif { [tmsh::get_field_value [lindex [tmsh::get_config sys scriptd log-level] 0] log-level] eq \"debug\" } {\n set iapplogLevel 10\n}\n\n# Print a timestamped debug message to /var/tmp/scriptd.out\n# Input: headers = TCL list of headers for the log message\n# msg = The message to log\n# level = Integer indicated the log level for this message\nproc debug { headers msg level } {\n if { $::iapplogLevel >= $level } {\n set systemTime [clock seconds]\n set brackets \"\"\n if { [llength $headers] > 0 } {\n set brackets [format \"\[%s\]\" [join $headers \"\]\[\"]]\n }\n set pre [format \"\[%s %s\]\[%s\]%s\" [clock format $systemTime -format %D] [clock format $systemTime -format %H:%M:%S] $::app $brackets]\n puts [format \"%s %s\" $pre [string map [list \"\n\" \"\n$pre \" ] $msg]]\n }\n}\n\n# Credit for psplit: http://wiki.tcl.tk/1499\n# Perform the equivalent of a split on a string except protect an escaped split character in the input\n# Input: str = the string to split\n# seps = the charater(s) to split by\n# Return: list $strings\nproc psplit { str seps {protector \"\\\"}} {\n set out [list]\n set prev \"\"\n set current \"\"\n foreach c [split $str \"\"] {\n if { [string first $c $seps] >= 0 } {\n if { $prev eq $protector } {\n set current [string range $current 0 end-1]\n append current $c\n } else {\n lappend out $current\n set current \"\"\n }\n set prev \"\"\n } else {\n append current $c\n set prev $c\n }\n }\n\n if { $current ne \"\" } {\n lappend out $current\n }\n\n return $out\n}\n\n# Figure out which type of environment we are executing in.\n# Return: list $mode $folder $partition $routedomainid $newdeploy\n# Modes: 1 = Standalone\n# 2 = iWorkflow UNUSED/LEGACY\n# 3 = Cisco APIC\n# 4 = VMware NSX\nproc get_mode { } {\n set folder [tmsh::pwd]\n set app $tmsh::app_name\n set partition [lindex [split $folder /] 1]\n set newdeploy [catch {tmsh::get_config sys application service /$partition/$app.app/$app}]\n debug [list get_mode] [format \"starting folder=%s partition=%s newdeploy=%s\" $folder $partition $newdeploy] 10\n\n if { ! $newdeploy } {\n set ::asoobj [lindex [lindex [tmsh::get_config sys application service /$partition/$app.app/$app] 0] 4]\n }\n # Set the routedomain to the partition default-route-domain\n if { [string tolower $::iapprouteDomain] eq \"auto\"} {\n set obj [tmsh::get_config auth partition $partition default-route-domain]\n set routedomainid [tmsh::get_field_value [lindex $obj 0] default-route-domain]\n debug [list get_mode set_route_domain] [format \"Using partition default-route-domain; routedomainid=%s\" $routedomainid] 10\n } else {\n set routedomainid $::iapprouteDomain\n debug [list get_mode set_route_domain] [format \"Using route domain override; routedomainid=%s\" $routedomainid] 10\n }\n\n # Check for a mode override in $iappmode variable\n if { [string tolower $::iappmode] ne \"auto\" } {\n if { $::iappmode > 0 && $::iappmode < 4 } {\n debug [list get_mode modeoverride] [format \"Mode override detected. Setting mode to %s\" $::iappmode] 10\n return [list $::iappmode $folder $partition $routedomainid $newdeploy]\n } else {\n error \"The mode override specified is invalid.\"\n }\n }\n\n # Check for a partition that starts with apic and return APIC mode (3) and RD if found\n if { [string match -nocase \"apic_\" $partition] || [string match -nocase \"apic-\" $partition] } {\n debug [list getmode apic] \"partition starts with apic, assuming APIC deployment mode (3)\" 10\n set rdobjs [tmsh::get_config net route-domain \"/$partition/$partition\" id]\n set routedomainid [tmsh::get_field_value [lindex $rdobjs 0] \"id\"]\n debug [list getmode apic] [format \"rdobjs=%s routedomainid=%s\" $rdobjs $routedomainid] 10\n return [list 3 $folder $partition $routedomainid $newdeploy]\n }\n\n # Check for an $app name that is formatted like this:\n # edge-<#><#>virtualserver-<#>-serviceprofile-<#>\n # and return NSX mode (4)\n if { [regexp -nocase {^edge-[0-9]+[0-9]+_virtualserver-[0-9]+-serviceprofile-[0-9]+$} $::app] } {\n debug [list get_mode nsx] \"app name matches NSX regexp, assuming NSX deployment mode (4)\" 10\n return [list 4 $folder $partition $routedomainid $newdeploy]\n }\n\n # Default is Standalone mode\n debug [list get_mode standalone] \"no integration vendor found, assuming Standalone deployment mode (1)\" 10\n return [list 1 $folder $partition $routedomainid $newdeploy]\n}\n\n# Create a specfic option command and return it\n# Input: $debug_id, $input_var, $option_string\n# Return: string $cmd\nproc generic_add_option { debug_id input_var option_string custom_format replace_commas } {\n set cmd \" \"\n if { [string length $input_var] > 0 } {\n if { $replace_commas == 1 } {\n set input_var [string map {\",\" \" \"} $input_var]\n }\n\n if { [string length $custom_format] > 0 } {\n set cmd [format $custom_format $input_var]\n } else {\n set cmd [format \" $option_string \\"%s\\"\" $input_var]\n }\n debug [lappend debug_id generic_add_option] [format \"cmd=%s\" $cmd] 10\n }\n return $cmd\n}\n\n# Check to see if an ip has a routedomain included.\n# Return: 0=false; 1=true\nproc has_routedomain { ip } {\n return [string match % $ip]\n}\n\n# Replace a profile within a virtual server definition while preserving the existing context\n# Input: $obj = tmsh obj representing profiles section of the VS get_config\n# $oldprofile = name of the profile to replace\n# $newprofile = name of the new profile\n# Return: string $newprofiles (string suitable for providing to replace-all-with option)\nproc replace_profile { obj oldprofile newprofile } {\n set profiles [tmsh::get_field_value [lindex $obj 0] \"profiles\"]\n set newprofiles \" { \"\n foreach profile $profiles {\n set junk [lindex $profile 0]\n set name [lindex $profile 1]\n set contextobj [lindex $profile 2]\n set context [lindex $contextobj 1]\n if { $name eq $oldprofile } {\n debug [list replace_profile] [format \"replace profile '%s' with '%s' context=%s\" $name $newprofile $context] 10\n append newprofiles [format \"%s { context %s } \" $newprofile $context]\n } else {\n debug [list replace_profile] [format \"preserve profile '%s' context=%s\" $name $context] 10\n append newprofiles [format \"%s { context %s } \" $name $context]\n }\n }\n append newprofiles \" } \"\n return $newprofiles\n}\n\n# Look at a tmsh profile object and determine if $option is a valid profile option\n# Input: $obj = tmsh obj to check\n# $option = option name to look for\n# Return: 1=Valid option; 0=Invalid option\nproc is_valid_profile_option { obj option } {\n debug [list is_valid_profile_option obj] [format \"%s\" $obj] 11\n debug [list is_valid_profile_option option] [format \"looking for %s\" $option] 11\n set found 0\n set fdx 0\n set fields [tmsh::get_field_names value $obj]\n set fields2 [tmsh::get_field_names nested $obj]\n debug [list is_valid_profile_option fields] [format \"%s\" $fields] 11\n debug [list is_valid_profile_option fields2] [format \"%s\" $fields2] 11\n set field_count [llength $fields]\n while { $fdx < $field_count } {\n set field [lindex $fields $fdx]\n if { $field == $option } {\n return 1\n }\n incr fdx\n }\n set field_count [llength $fields2]\n set fdx 0\n while { $fdx < $field_count } {\n set field [lindex $fields2 $fdx]\n if { $field == $option } {\n return 1\n }\n incr fdx\n }\n return 0\n}\n\n# Process a string in the format key1=val1[;keyX=valX] and return an array\n# Input: $string = string to process\n# Return: array { key1 {val1} ... keyX{valX}}\nproc process_kvp_string { string } {\n debug [list process_kvp_string] \"processing string: $string\" 10\n set pairs [psplit $string \";\"]\n array set ret {}\n foreach pair $pairs {\n set key [lindex [split $pair =] 0]\n set val [lindex [split $pair =] 1]\n set ret($key) $val\n debug [list process_kvp_string] \"pair=$pair key=$key val=$val\" 10\n }\n return [array get ret]\n}\n\n# Create an object name\n# Input: $append = string to append\n# Return: $string\nproc create_objname { append } {\n return [format \"%s/%s%s\" $::app_path $::app $append]\n}\n\n# Safely change a variable to a new value. Updates the var value and modifies the ASO with the new value\n# Input: $name = name of variable\n# $value = new value of the variable\n# Return: none\nproc change_var { name value } {\n debug [list change_var] \"updating variable $name to $value (executes post-deployment)\" 10\n set varcmd [create_escaped_tmsh [format \"tmsh::modify sys application service %s/%s variables modify \{ %s \{ value \\"%s\\" \} \}\" $::app_path $::app $name $value]]\n debug [list change_var tmsh_modify_deferred] $varcmd 1\n lappend ::postfinal_deferred_cmds $varcmd\n set [subst ::$name] $value\n set ::aso_config($name) $value\n return\n}\n\n# Check to see if an incoming variable is different than whats stored in the ASO.\n# Input: $name = name of variable\n# Return: 1=value is different; 0=value not different OR not a redeploy\nproc is_new_value { name } {\n if { $::newdeploy } {\n return 0\n }\n set varvalue [get_var $name]\n debug [list is_new_value] [format \"name=%s asovalue=%s varvalue=%s\" $name $varvalue [set [subst ::$name]]] 10\n if { [set [subst ::$name]] == $varvalue } {\n return 0\n }\n return 1\n}\n\n# Get the variable value in the ASO.\n# Input: $name = name of variable\n# $orig = return the original value, not the runtime updated one\n# Return: $string = value of variable\nproc get_var { name { orig 0 }} {\n if { $::newdeploy == 1} {\n return \"\"\n }\n debug [list get_var] [format \"start name=%s\" $name] 10\n\n if { $orig == 0 && [info exists ::aso_config($name)] } {\n set varvalue $::aso_config($name)\n debug [list get_var] [format \"name=%s value=%s\" $name $varvalue] 10\n return $varvalue\n }\n\n if { $orig == 1 && [info exists ::aso_config_orig($name)] } {\n set varvalue $::aso_config_orig($name)\n debug [list get_var original] [format \"name=%s value=%s\" $name $varvalue] 10\n return $varvalue\n }\n return \"\"\n}\n\n# Safely handle the removal of a virtual server option on redeployment\n# Input: $name = name of variable\n# $checkvalue = the string that disables the option\n# $option = TMSH name of the option\n# $module = the BIG-IP module that enables the option\n# Return: 1=Option removed; 0=no action taken\nproc handle_opt_remove_on_redeploy { name checkvalue option module } {\n if { ! $::redeploy || $::pooladdr eq \"255.255.255.254\" } {\n debug [list handle_opt_remove_on_redeploy $name] \"not a redeployment, skipping\" 10\n return 0\n }\n\n if { ! [is_provisioned $module] } {\n debug [list handle_opt_remove_on_redeploy $name] [format \"%s not provisioned, skipping\" $module] 10\n return 0\n }\n\n set vsname [get_var vsName 1]\n set vsobj [lindex [tmsh::get_config ltm virtual $::app_path/$vsname all-properties] 0]\n if { [is_valid_profile_option $vsobj $option] == 0 } {\n debug [list handle_opt_remove_on_redeploy $name] [format \"%s not available, skipping\" $option] 10\n return 0\n }\n\n if { [set [subst ::$name]] == $checkvalue && \\n [is_new_value $name] && \\n $::redeploy } {\n debug [list handle_opt_remove_on_redeploy] [format \"%s %s on redeploy, setting %s to none\" $name $checkvalue $option] 10\n set cmd [format \"ltm virtual %s/%s %s none\" $::app_path $vsname $option]\n debug [list handle_opt_remove_on_redeploy tmsh_modify] $cmd 1\n tmsh::modify $cmd\n return 1\n }\n return 0\n}\n\n# Check provisioning cache for whether a specified module is provisioned and at what levels\n# Adapted from original code including the F5 iApp TCL helper library\n# Input: $module = name of the module\n# Output: $level = integer representation of the provisioning level. See levels array below\nproc is_provisioned { module } {\n array set levels {\n none 0\n minimum 1\n nominal 2\n dedicated 3\n }\n\n if { [info exists ::provision_cache($module)] } {\n debug [list is_provisioned cache_hit] \"$module $::__provision_cache($module)\" 10\n return [expr { $levels($::provision_cache($module)) >= 1 }]\n } else {\n debug [list is_provisioned cache_miss] \"$module\" 10\n return -1\n }\n}\n\n# Load provisioning cache with module provisioning levels\n# Adapted from original code including the F5 iApp TCL helper library\nproc load_provisioned { } {\n array set levels {\n none 0\n minimum 1\n nominal 2\n dedicated 3\n }\n set obj [tmsh::get_config sys provision]\n foreach mod $obj {\n set modname [lindex $mod 2]\n set modlevel [lindex $mod 3]\n if { [llength $modlevel] == 2 } {\n set modlevel [lindex $modlevel 1]\n } else {\n set modlevel none\n }\n set ::provision_cache($modname) $modlevel\n debug [list load_provisioned cache_set] \"$modname $modlevel\" 10\n }\n}\n\n# Consume an APL table and return a list containing the values of the var specified in $key\n# Input: $table = the raw APL table\n# $key = the name of the variable to add to the return list\n# Output: $retlist = A list of strings\nproc single_column_table_to_list { table key } {\n set retlist {}\n foreach row $table {\n array unset column\n\n # extract the iApp table data - borrowed from f5.lbaas.tmpl\n foreach column_data [lrange [split [join $row] \"\n\"] 1 end-1] {\n set name [lindex $column_data 0]\n set column($name) [lrange $column_data 1 end]\n }\n if { [info exists column($key)] && [string length $column($key)] > 0 } {\n lappend retlist \"$column($key)\"\n }\n\n }\n return $retlist\n}\n\n# Process a string in the format node_cache for re-use\n# Input: string node_name = The object name to check for\n# Return: 0 = node does not exist\n# 1 = node does exist\nproc check_node_exist { node_name } {\n if { ! [info exists ::node_cache($node_name)] } {\n set node_status [catch {tmsh::get_config ltm node $node_name} node_status_ret]\n debug [list check_node_exist $node_name status] $node_status_ret 10\n if { [string match \"address\" $node_status_ret] } {\n set ::node_cache($node_name) 1\n debug [list check_node_exist $node_name cache_set] \"1\" 10\n return 1\n } else {\n set ::node_cache($node_name) 0\n debug [list check_node_exist $node_name cache_set] \"0\" 10\n return 0\n }\n } else {\n debug [list check_node_exist $node_name cache_hit] $::node_cache($node_name) 10\n return $::node_cache($node_name)\n }\n}\n\n# Perform string substitution on a URL.\n# Input: string url = the URL to manipulate\n# Output: string url = the final URL\nproc url_subst { url } {\n debug [list url_subst] [format \"url=%s\" $url] 10\n set url_map [list %APP_NAME% $::app \\n %APP_PATH% $::app_path \\n %PARTITION% $::partition \\n %VS_NAME% $::vs__Name \\n %VS_DESCR% $::vsDescription \\n %EXT1% $::extensionsField1 \\n %EXT2% $::extensionsField2 \\n %EXT3% $::extensionsField2 \\n \"url=\" \"\" \\n \"irule:url=\" \"\" \\n \"irule:urloptional=\" \"\" \\n \"asm:url=\" \"\" \\n \"apm:url=\" \"\" ]\n\n set url [string map $url_map $url]\n debug [list url_subst] [format \"return=%s\" $url] 10\n return $url\n}\n\n# Load a crypto cert/key from URL\n# Input: string type = key|cert\n# string url = the URL to get\n# Return: string obj_name = the name of the created TMOS object\nproc load_crypto_object { type url } {\n set url [url_subst $url]\n debug [list load_crypto_object url_subst] $url 10\n\n set url_file_name [lindex [split $url /] end]\n set objname [format \"/%s/%s%s\" $::partition $::app $url_file_name]\n set filename [format \"/var/tmp/appsvcs%s%s%s\" $::app $::bundler_timestamp $url_file_name]\n debug [list load_crypto_object] [format \"obj_name=%s file_name=%s\" $obj_name $file_name] 10\n\n switch -glob $type {\n cert { set verify_cmd [format \"/usr/bin/openssl x509 -noout -in %s\" $file_name] }\n key { set verify_cmd [format \"/usr/bin/openssl rsa -noout -in %s\" $file_name] }\n default { error \"The crypto type specified is not supported\" }\n }\n\n curl_save_file $url $file_name\n\n set verify_status [catch {eval exec $verify_cmd} verify_status_ret]\n debug [list load_crypto_object verify_status $verify_status] $verify_status_ret 10\n\n if { $verify_status } {\n file delete $file_name\n error \"While loading the $type: $verify_status_ret\"\n }\n\n set cmd [format \"sys file ssl-%s %s source-path file://%s\" $type $obj_name $file_name]\n debug [list load_crypto_object tmsh_create] $cmd 10\n set create_status [catch {tmsh::create $cmd} create_status_ret]\n debug [list load_crypto_object create_status $create_status] $create_status_ret 10\n file delete $file_name\n\n return $obj_name\n}\n\n# Credit: http://www.egghelp.org/cgi-bin/tcl_archive.tcl?mode=download&id=97\n# Perform a DNS lookup of a hostname using nslookup. Assumes DNS servers are already\n# configured in TMOS\n# Input: string host = name of host to lookup\n# int mode = 1 => Throw a hard error\n# 0 => Return the error\n# Return: string return = First IP tied to hostname OR Error\nproc dns_lookup { host {mode 1} } {\n set name \"Unknown\"\n set ip \"Unknown\"\n set errmsg \"Unknown\"\n set host [lindex [string tolower $host] 0]\n if {[catch {eval exec \"/usr/bin/nslookup\" [lindex $host 0]} buff]} {\n foreach line [split $buff \n] {\n if {[string first \"${host}:\" $line] != -1} {\n set errmsg [string trim [lindex [split $line :] 1]]\n }\n }\n\n if { $mode } {\n error \"An error occured trying to resolve $host: $errmsg\"\n } else {\n return \"Error: $errmsg\"\n }\n }\n set buff [split $buff \n]\n set buff [lreplace $buff 0 1]\n if {[regexp {name = (.*)\.} $buff -> name]} { set ip $host }\n\n foreach data $buff {\n switch [lindex $data 0] {\n \"Name:\" {\n set name [string trim [lindex [split $data :] 1]]\n }\n \"Address:\" {\n set ip [string trim [lindex [split $data :] 1]]\n }\n \"Addresses:\" {\n set ip [string trim [lindex [split $data :] 1]]\n }\n }\n }\n return \"${ip}\"\n}\n\nset_version_info\n\n# Copyright (c) 2017 F5 Networks, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# ####################################################################\n# Custom extensions example\n#\n# The purpose of custom extensions is to allow functionality to be implemented\n# without modifying the base deployment code. Additionally control over these\n# extensions can be exposed via the extensionsfieldX fields to allow functionality\n# to be added WITHOUT changes to the presentation layer. By exposing the extension\n# fields as tenant editable we can add code to this portion of the iApp to handle\n# new functionality without changing the northbound data model\n#\n# The following procs are called at various points during the implementation:\n# custom_extensions_start: Called at the start of the deployment after mode is determined\n# custom_extensions_before_pools: Called before processing all pool(s) starts\n# custom_extensions_before_pool: Called before processing to create the pool starts\n# custom_extensions_after_pool: Called immediately after the pool is created\n# custom_extensions_after_pools: Called immediately after all pool(s) are created\n# custom_extensions_before_vs: Called before processing to create the virtual server starts\n# custom_extensions_after_vs: Called immediately after the virtual server is created\n# custom_extentionsend: Called at the end of the deployment\n#\n# Guidelines:\n# - Avoid name collisions please prefix variables with 'custom' unless used by the base deployment code\n# - Restrict modifications to global presentation layer variables unless absolutely required\n# - Try to modify the config once created by the base deployment code to maintain compatibility\n#\n# Two examples are implemented here:\n# - custom_example_1: Called from all hooks to dump some info to the debug log\n# - custom_example_2: (Disabled by default) Called at the end of the deployment to execute\n# a tmsh::create command\n\nproc custom_extensions_start {} {\n debug \"[lindex [info level 0] 0]\" \"entering proc\" 6\n\n # Example 1: Parse a string of the format \"key1=val1;key2=val2;key3=val3\" and populate an array.\n # The we call the custom_example proc to dump some info to the /var/tmp/scriptd.out log\n\n # Make the global variable accessable locally. Additionally create a global array to store KVP pairs\n upvar extensionsField1 field1\n upvar custom_field1_kvp kvp_array\n\n # Check to see we got some data in extensionsField1\n if { [string length $field1] > 0 } {\n # Use the process_kvp_string proc to populate an array\n array set kvp_array [process_kvp_string $field1]\n\n debug \"[lindex [info level 0] 0]\" \"kvp_array=[array get kvp_array]\" 0\n # Call our custom_example_1 proc to dump some info to the debug log.\n custom_example_1 [array get kvp_array]\n }\n}\n\nproc custom_extensions_before_pools {} {\n debug \"[lindex [info level 0] 0]\" \"entering proc\" 6\n\n # Call our custom_example_1 proc to dump some info to the debug log.\n upvar custom_field1_kvp kvp_array\n custom_example_1 [array get kvp_array]\n}\n\nproc custom_extensions_before_pool {} {\n debug \"[lindex [info level 0] 0]\" \"entering proc\" 6\n\n # Call our custom_example_1 proc to dump some info to the debug log.\n upvar custom_field1_kvp kvp_array\n custom_example_1 [array get kvp_array]\n}\n\nproc custom_extensions_after_pool {} {\n debug \"[lindex [info level 0] 0]\" \"entering proc\" 6\n\n # Call our custom_example_1 proc to dump some info to the debug log.\n upvar custom_field1_kvp kvp_array\n custom_example_1 [array get kvp_array]\n}\n\nproc custom_extensions_after_pools {} {\n debug \"[lindex [info level 0] 0]\" \"entering proc\" 6\n\n # Call our custom_example_1 proc to dump some info to the debug log.\n upvar custom_field1_kvp kvp_array\n custom_example_1 [array get kvp_array]\n}\n\nproc custom_extensions_before_vs {} {\n debug \"[lindex [info level 0] 0]\" \"entering proc\" 6\n\n # Call our custom_example_1 proc to dump some info to the debug log.\n upvar custom_field1_kvp kvp_array\n custom_example_1 [array get kvp_array]\n}\n\nproc custom_extensions_after_vs {} {\n debug \"[lindex [info level 0] 0]\" \"entering proc\" 6\n\n # Call our custom_example_1 proc to dump some info to the debug log.\n upvar custom_field1_kvp kvp_array\n custom_example_1 [array get kvp_array]\n}\n\nproc custom_extensions_end {} {\n debug \"[lindex [info level 0] 0]\" \"entering proc\" 6\n\n # Call our custom_example_1 proc to dump some info to the debug log.\n upvar custom_field1_kvp kvp_array\n custom_example_1 [array get kvp_array]\n\n # Call our custom_example_2 proc to run a user provided tmsh create command\n #\n # Populate extensionsField2 with a valid command like:\n # ltm data-group internal customDG type string records replace-all-with { record1 { data data1 } record2 { data data2 } }\n #\n # Once the template executes you can see the creation of the datagroup under the application template container\n\n # To enable example 2 uncomment the following two lines \n # upvar extensionsField2 field2\n # custom_example_2 $field2\n}\n\n# Example 1: Simply dump a log line to /var/tmp/scriptd.out\nproc custom_example_1 { kvp_array_in } {\n set calling_proc [lindex [info level -1] 0]\n set current_proc [lindex [info level 0] 0]\n array set kvp_array $kvp_array_in\n\n debug [list $current_proc] \"entering proc kvp_array_in=$kvp_array_in\" 6\n\n if { [info exists kvp_array(custom_example)] && $kvp_array(custom_example) == 1} {\n debug [list $current_proc] \"This is an example of a custom extension called from $calling_proc\" 6\n }\n}\n\n# Example 2: Run the text in extensionsField2 as a tmsh create command\nproc custom_example_2 { cmd } {\n set calling_proc [lindex [info level -1] 0]\n set current_proc [lindex [info level 0] 0]\n\n debug [list $current_proc] \"entering proc cmd=$cmd\" 6\n\n if { [string length $cmd] > 0 } {\n debug [list $current_proc] \"Called from $calling_proc - About the execute tmsh::create $cmd\" 6\n tmsh::create $cmd\n }\n}\n\narray set bundler_objects {}\narray set bundler_data {}\nset bundler_deferred_cmds []\n\n\n\nset app $tmsh::app_name\ndebug [list start] [format \"Starting %s version IMPL=%s TMPLNAME=%s app_name=%s\" $NAME $IMPLVERSION $TMPLNAME $app] 0\ndebug [list version_info] [array get version_info] 3\n\narray set modenames {\n 1 {Standalone}\n 2 {iWorkflow}\n 3 {Cisco APIC}\n 4 {VMware NSX}\n}\n\narray set __provision_cache {}\narray set node_cache {}\nload_provisioned\n\narray set aso_config {}\nset asoobj {}\nset modeinfo [get_mode]\nset mode [lindex $modeinfo 0]\nset folder [lindex $modeinfo 1]\nset partition [lindex $modeinfo 2]\nset rd [lindex $modeinfo 3]\nset newdeploy [lindex $modeinfo 4]\nset app_path [format \"/%s/%s.app\" $partition $app]\nset template_name [format \"appsvcs_integration_v%s\" $IMPLMAJORVERSION]\nset aso [format \"%s/%s\" $app_path $app]\nset redeploy 0\nif { ! $newdeploy } { set redeploy 1 }\nset postfinal_deferred_cmds []\n\ndebug \"modeinfo\" [format \"mode=%s folder=%s partition=%s rd=%s newdeploy=%s redeploy=%s template_name=%s\" $mode $folder $partition $rd $newdeploy $redeploy $template_name] 2\n\n# Cache our ASO object data\nfor { set i 0 } { $i <= [llength $asoobj] } { set i [expr {$i+2}] } {\n set type [lindex $asoobj $i]\n if { $type == \"variables\" || $type == \"lists\" || $type == \"tables\" } {\n for { set j 0 } { $j < [llength [lindex $asoobj [expr {$i+1}]]] } { set j [expr {$j+2}] } {\n set name [lindex [lindex $asoobj [expr {$i+1}]] $j]\n\n if { $type == \"tables\" } {\n set val [lindex [lindex $asoobj [expr {$i+1}]] [expr {$j+1}]]\n } elseif { $type == \"lists\" } {\n set val [lindex [lindex [lindex $asoobj [expr {$i+1}]] [expr {$j+1}]] 1]\n } else {\n set val [lindex [lindex [lindex $asoobj [expr {$i+1}]] [expr {$j+1}]] 1]\n }\n set aso_config($name) $val\n }\n }\n}\n# Copy the array so that we can preserve the original values\n# aso_config() can change at runtime\narray set aso_config_orig [array get aso_config]\n\ndebug [list aso_config] [array names aso_config] 9\nforeach var [array names aso_config] {\n debug [list aso_config $var] $aso_config($var) 9\n}\n\nset asodescr [format \"Deployed by appsvcs_integration_v%s in %s mode on %s\" $IMPLVERSION $modenames($mode) [clock format $startTime -format \"%D %H:%M:%S\"]]\ndebug [list set_aso_decription tmsh_modify] [format \"sys application service %s description \\"%s\\"\" $aso $asodescr] 1\ntmsh::modify [format \"sys application service %s description \\"%s\\"\" $aso $asodescr]\n\n# Define various global values\nset allVars {\n iappstrictUpdates \\n iappappStats \\n iappmode \\n iapplogLevel \\n iapprouteDomain \\n iappasmDeployMode \\n iappapmDeployMode \\n pooladdr \\n poolmask \\n poolport \\n poolDefaultPoolIndex \\n poolPools \\n poolMemberDefaultPort \\n poolMembers \\n monitorMonitors \\n vsListeners \\n vsName \\n vsDescription \\n vsRouteAdv \\n vsSourceAddress \\n vsIpProtocol \\n vsConnectionLimit \\n vsProfileClientProtocol \\n vsProfileServerProtocol \\n vsProfileHTTP \\n vsProfileOneConnect \\n vsProfileCompression \\n vsProfileAnalytics \\n vsProfileRequestLogging \\n vsProfileDefaultPersist \\n vsProfileFallbackPersist \\n vsSNATConfig \\n vsProfileServerSSL \\n vsProfileClientSSL \\n vsProfileClientSSLCert \\n vsProfileClientSSLKey \\n vsProfileClientSSLChain \\n vsProfileClientSSLCipherString \\n vsProfileClientSSLAdvOptions \\n vsProfileSecurityLogProfiles \\n vsProfileSecurityIPBlacklist \\n vsProfileSecurityDoS \\n vsProfileAccess \\n vsProfileConnectivity \\n vsProfilePerRequest \\n vsOptionSourcePort \\n vsOptionConnectionMirroring \\n vsIrules \\n vsBundledItems \\n vsAdvOptions \\n vsAdvProfiles \\n vsAdvPolicies \\n vsVirtualAddrAdvOptions \\n l7policystrategy \\n l7policydefaultASM \\n l7policydefaultL7DOS \\n l7policyrulesMatch \\n l7policyrulesAction \\n featurestatsTLS \\n featurestatsHTTP \\n featureinsertXForwardedFor \\n featureredirectToHTTPS \\n featuresslEasyCipher \\n featuresecurityEnableHSTS \\n featureeasyL4Firewall \\n featureeasyL4FirewallBlacklist \\n featureeasyL4FirewallSourceList \\n extensionsField1 \\n extensionsField2 \\n extensionsField3 \\n}\n\nset requiredVars {\n pooladdr \\n poolmask \\n poolport \\n vsProfileClientProtocol }\n\narray set table_defaults {\n Members {\n Index 0\n State enabled\n IPAddress \"\"\n Port \"\"\n ConnectionLimit 0\n Ratio 1\n PriorityGroup 0\n AdvOptions \"\"\n }\n Pools {\n Index -1\n Name \"\"\n Description \"\"\n LbMethod \"\"\n Monitor \"\"\n AdvOptions \"\"\n }\n Monitors {\n Index -1\n Name \"\"\n Type \"\"\n Options \"\"\n }\n L7P_Match {\n Group -1\n Operand \"\"\n Negate no\n Condition \"\"\n CaseSensitive no\n Value \"\"\n Missing no\n }\n L7P_Action {\n Group -1\n Target error/error/error\n Parameter error\n }\n Listeners {\n Listener \"\"\n Destination \"\"\n }\n}\n\narray set pool_member_state {\n enabled {session user-enabled state user-up}\n disabled {session user-disabled state user-up}\n force-disabled {session user-disabled state user-down}\n drain-disabled {session user-disabled state user-up}\n}\n\n# Fixup incoming variables: If no value is sent for a particular iApp field than the var is not created which\n# results in all sorts of problems. We just check for existence of the var and set to \"\" if it doesn't exist\nforeach var $allVars {\n if {[info exists [subst $var]]} {\n debug \"input\" [format \"%s sent, value is: %s\" $var [set [subst $var]]] 2\n } else {\n set [subst $var] \"\"\n debug \"input\" [format \"%s NOT sent, setting to blank\" $var] 2\n }\n}\n\n# Double check we got all the required variables.\nforeach var $requiredVars {\n debug [list required_check] [format \"var=%s val=%s len=%s\" $var [set [subst $var]] [string length [set [subst $var]]]] 10\n if {! [info exists [subst $var]] || [string length [set [subst $var]]] == 0 } {\n if { ! [string match \"vs\" $var] && $pooladdr != \"255.255.255.254\" } {\n error \"The variable $var is required\"\n }\n }\n}\n\n# Convert the $vsBundledItems table to a list for easier manipulation\nset vsBundledItems [single_column_table_to_list $vsBundledItems \"Resource\"]\ndebug [list convert_bundled] $vsBundledItems 7\n\n# Call the custom_extensions_start proc to allow site-specific customizations\ncustom_extensions_start\n\n# Special handling for the Source Address because it comes in as 0.0.0.0/0 and\n# needs to be 0.0.0.0%xxxx/0, where '%xxxx' is the route-domain ID\nset working $vsSourceAddress\ndebug [list fix_src_addr] \"Check if vsSourceAddress needs to be fixed\" 7\nif { [string length $working] > 0 } {\n set net [lindex [split $working /] 0]\n set cidr [lindex [split $working /] 1]\n set vsSourceAddress \"$net%$rd\/$cidr\"\n debug [list fix_src_addr] [format \" Fixing vsSourceAddress: orig=%s new=%s\" $working $vsSourceAddress] 7\n}\n\n# Create Client-SSL profile if Cert and Key are specified but ClientSSLProfile is not\ndebug [list client_ssl create] \"checking if client ssl cert & key were entered\" 7\nset clientssl 0\nif { [string length $vsProfileClientSSLKey] > 0 && [string length $vsProfileClientSSLCert] > 0 && [string length $vsProfileClientSSL] == 0 } {\n\n set crypto_url_found 0\n if { [string match \"url=\" $vsProfileClientSSLKey] } {\n set vsProfileClientSSLKey [load_crypto_object \"key\" $vs__ProfileClientSSLKey]\n set crypto_url_found 1\n }\n\n if { [string match \"url=\" $vsProfileClientSSLCert] } {\n set vsProfileClientSSLCert [load_crypto_object \"cert\" $vsProfileClientSSLCert]\n set crypto_url_found 1\n }\n\n if { [string match \"url=*\" $vsProfileClientSSLChain] } {\n set vsProfileClientSSLChain [load_crypto_object \"cert\" $vsProfileClientSSLChain]\n set crypto_url_found 1\n }\n\n if { $vsProfileClientSSLKey == \"auto\" } {\n debug [list client_ssl create auto_key] [format \"found auto option for key, setting vsProfileClientSSLKey=/Common/%s.key\" $app] 5\n set vsProfileClientSSLKey \"/Common/$app.key\"\n }\n\n if { $vsProfileClientSSLCert == \"auto\" } {\n debug [list client_ssl create auto_cert] [format \"found auto option for key, setting vsProfileClientSSLCert=/Common/%s.crt\" $app] 5\n set vsProfileClientSSLCert \"/Common/$app.crt\"\n }\n\n if { $crypto_url_found == 0 } {\n tmsh::get_config /sys file ssl-key $vsProfileClientSSLKey\n tmsh::get_config /sys file ssl-cert $vs__ProfileClientSSLCert\n debug [list client_ssl create check_exist] \"ssl cert & key found... creating profile\" 7\n }\n\n set cmd [format \"ltm profile client-ssl %s_clientssl key %s cert %s\" $app $vsProfileClientSSLKey $vsProfileClientSSLCert]\n\n if { [string length $vsProfileClientSSLChain] > 0 } {\n if { $crypto_url_found == 0 } {\n tmsh::get_config /sys file ssl-cert $vsProfileClientSSLChain\n }\n debug [list client_ssl create cert_chain] \"adding cert chain\" 7\n append cmd [format \" chain %s\" $vsProfileClientSSLChain]\n }\n\n\tarray set feature_sslEasyCipher_strings {\n\t\tcompatible {NATIVE:!SSLv3:!SSLv2:!EXPORT:!MD5:!ADH:@STRENGTH}\n\t\tmedium {TLSv1_2+HIGH:TLSv1_1+HIGH:TLSv1+MEDIUM:TLSv1+HIGH:!EXPORT:!RC4:!EXPORT:!MD5:!ADH:@STRENGTH}\n\t\thigh {TLSv1_2+HIGH:TLSv1_1+HIGH:TLSv1+MEDIUM:TLSv1+HIGH:!RC4:!RSA:!DHE:!EXPORT:!MD5:!ADH:@STRENGTH}\n\t\ttls_1.2 {TLSv1_2:!TLSv1_2+LOW:!EXPORT:!MD5:!ADH:@STRENGTH}\n\t\ttls_1.1+1.2 {TLSv1_2:TLSv1_1:!TLSv1_2+LOW:!TLSv1_1+LOW:!EXPORT:!MD5:!ADH:@STRENGTH}\n\t}\n\n\tif { $featuresslEasyCipher ne \"disabled\" && [info exists feature_sslEasyCipher_strings($featuresslEasyCipher)]} {\n\t\tdebug [list client_ssl create ssl_easy_cipher] [format \"sslEasyCipher is not disabled, setting vsProfileClientSSLCipherString=%s\" $feature_sslEasyCipher_strings($featuresslEasyCipher)] 5\n\t\tset vsProfileClientSSLCipherString $feature_sslEasyCipher_strings($featuresslEasyCipher)\n\t}\n\n if { [string length $vsProfileClientSSLCipherString] > 0 } {\n debug [list client_ssl create cipher_string] \"adding cipher string\" 7\n append cmd [format \" ciphers \\"%s\\"\" $vsProfileClientSSLCipherString]\n }\n\n if { [string length $vsProfileClientSSLAdvOptions] > 0 } {\n debug [list client_ssl create adv_options] \"processing advanced options string\" 7\n append cmd [format \" %s\" [process_options_string $vsProfileClientSSLAdvOptions \"profile client-ssl\" \"/Common/clientssl\"]]\n }\n\n debug [list client_ssl create tmsh_create] $cmd 1\n tmsh::create $cmd\n set clientssl 1\n} else {\n if { [string length $vsProfileClientSSL] > 0 } {\n if { ![string match \"create:*\" $vsProfileClientSSL] } {\n debug [list client_ssl associate] \"ClientSSLProfile was provided... checking if it exists\" 5\n tmsh::get_config /ltm profile client-ssl $vsProfileClientSSL\n set clientssl 2\n } else {\n debug [list client_ssl create] \"create ClientSSLProfile was provided...\" 5\n set clientssl 3\n }\n } else {\n set clientssl 0\n if { [string length $vsProfileClientSSLKey] > 0 && [string length $vsProfileClientSSLCert] == 0 } {\n error \"A client-ssl key was specified without a client-ssl certifcate\"\n }\n if { [string length $vsProfileClientSSLKey] == 0 && [string length $vsProfileClientSSLCert] > 0 } {\n error \"A client-ssl certifcate was specified without a client-ssl key\"\n }\n debug [list client_ssl] \"ssl cert & key not specified... skipped Client-SSL profile creation\" 2\n }\n}\n\n# Fixup empty poolPools and monitorMonitors table if poolMembers is populated\n# The behaviour implemented here will create a pool with a round-robin lb-method\n#\n# If a monitor with Index 0 is present in the monitorMonitors table\n# the pool monitor will be set to that.\n#\n# If the monitorMonitors is empty a default monitor will be created\nset monCount [llength $monitorMonitors]\nset poolCount [llength $poolPools]\nset poolMemberCount [llength $poolMembers]\n\nset poolTmpl {{{\n AdvOptions none\n Description\n Index %INDEX%\n LbMethod round-robin\n Monitor %MONITOR%\n Name\n}} }\n\nset monitorTmpl {{{\n Index 0\n Name %NAME%\n Type\n Options\n}} }\n\nif { $poolCount == 0 && $poolMemberCount > 0 } {\n debug [list pools_fixup] [format \"poolCount=%s\" $poolCount] 7\n debug [list pools_fixup] [format \"monCount=%s\" $monCount] 7\n debug [list pools_fixup] [format \"poolMemberCount=%s\" $poolMemberCount] 7\n set poolFixupIndexes []\n array set poolFixupFound {}\n foreach memberRow $poolMembers {\n array unset memberColumn\n array set memberColumn {}\n table_row_to_array $memberRow memberColumn ::table_defaults(Members) [list AdvOptions]\n if { ![info exists poolFixupFound($memberColumn(Index))] } {\n debug [list pools_fixup found_pool] [format \"index=%s\" $memberColumn(Index)] 7\n lappend poolFixupIndexes $memberColumn(Index)\n set poolFixupFound($memberColumn(Index)) 1\n }\n }\n debug [list pools_fixup create_indexes] $poolFixupIndexes 7\n\n set poolFixupMonitor \"0\"\n if { $monCount == 0 } {\n if { $vsIpProtocol == \"tcp\" } {\n if { [string length $vs__ProfileHTTP] > 0 } {\n set monFixupName \"/Common/http\"\n } else {\n set monFixupName \"/Common/tcp\"\n }\n set monTmpl_map [list %NAME% $monFixupName]\n set monitorMonitors [string map $monTmpl_map $monitorTmpl]\n debug [list pools_fixup monitorMonitors] $monitorMonitors 7\n } else {\n set poolFixupMonitor \"\"\n }\n }\n\n set poolTemp \"\"\n foreach foundIndex $poolFixupIndexes {\n set poolTmpl_map [list %INDEX% $foundIndex \\n %MONITOR% $poolFixupMonitor ]\n append poolTemp [string map $poolTmpl_map $poolTmpl]\n }\n set poolPools $poolTemp\n debug [list pools_fixup poolPools] $poolPools 7\n\n set monCount [llength $monitorMonitors]\n set poolCount [llength $poolPools]\n}\n\n# Create Monitors\ndebug [list monitors] [format \"monCount=%s\" $monCount] 7\nset monIdx 0\narray set monNames {}\narray set monCreate {}\nforeach monRow $monitor__Monitors {\n set cmd \"\"\n debug [list monitors $monIdx] [format \"monRow=%s\" $monRow] 9\n\n array unset monColumn\n array set monColumn {}\n table_row_to_array $monRow monColumn ::table_defaults(Monitors)\n debug [list monitors table_row_to_array return] [array get monColumn] 7\n\n # Fixup the Index in case a table with exactly one row and no Index is sent\n if { [llength $monitorMonitors] == 1 && $monColumn(Index) == -1 } {\n debug [list monitors fixup_index] \"setting Index to 0\" 9\n set monColumn(Index) 0\n }\n\n # The BIG-IP UI sends empty rows... above this we set Index to -1 if it wasn't found\n # If a Index is not specified then skip this row in the table\n if { $monColumn(Index) < 0 } {\n debug [list monitors $monIdx check_index] \"no index value found, skipping row\" 9\n continue\n } elseif { [info exists monNames($monColumn(Index))] } {\n error \"A monitor with Index of \\"$monColumn(Index)\\" was already specified\"\n } else {\n if {[string length $monColumn(Name)] > 0 } {\n if { [string match \"/\" $monColumn(Name)] } {\n set monNames($monColumn(Index)) $monColumn(Name)\n set monCreate($monColumn(Index)) 0\n } else {\n set monNames($monColumn(Index)) [format \"%s/%s\" $apppath $monColumn(Name)]\n set monCreate($monColumn(Index)) 1\n }\n } else {\n set monNames($monColumn(Index)) [format \"%s/monitor%s\" $app_path $monColumn(Index)]\n set monCreate($monColumn(Index)) 1\n }\n }\n\n if { $monCreate($monColumn(Index)) == 1 } {\n if { [string length $monColumn(Type)] <= 0 } {\n error \"A Monitor Type was not specified for monitor with Index $monColumn(Index)\"\n }\n\n set cmd [format \"ltm monitor %s %s \" $monColumn(Type) $monNames($monIdx)]\n if { [string length $monColumn(Options)] > 0 } {\n set monColumn(Options) [join $monColumn(Options) \" \"]\n debug [list monitors $monIdx options] [format \"processing options string \\"%s\\"\" $monColumn(Options)] 10\n append cmd [format \" %s\" [process_options_string $monColumn(Options) \"\" \"\"]]\n }\n\n debug [list monitors $monIdx tmsh_create] $cmd 1\n tmsh::create $cmd\n }\n\n incr monIdx\n}\n\n# Call the custom_extensions_before_pool proc to allow site-specific customizations\ncustom_extensions_before_pools\n\n# Create pool\ndebug [list pools] [format \"poolCount=%s\" $poolCount] 7\n\nset poolIdx 0\nset default_pool_name \"\"\narray set poolIndexes {}\narray set poolNames {}\nforeach poolRow $poolPools {\n set cmd \"\"\n set numMembers 0\n\n debug [list pools $poolIdx] [format \"poolRow=%s\" $poolRow] 9\n\n custom_extensions_before_pool\n\n array unset poolColumn\n array set poolColumn {}\n table_row_to_array $poolRow poolColumn ::table_defaults(Pools) [list AdvOptions]\n debug [list pools $poolIdx table_row_to_array return] [array get poolColumn] 7\n\n # Fixup the Index in case a table with exactly one row and no Index is sent\n if { [llength $poolPools] == 1 && $poolColumn(Index) == -1 } {\n debug [list pools $poolIdx fixup_index] \"setting Index to 0\" 9\n set poolColumn(Index) 0\n }\n\n # The BIG-IP UI sends empty rows... above this we set Index to -1 if it wasn't found\n # If a Index is not specified then skip this row in the table\n if { $poolColumn(Index) < 0 } {\n debug [list pools $poolIdx] \"no index value found, skipping row\" 9\n continue\n } elseif { [info exists poolIndexes($poolColumn(Index))] } {\n error \"A pool with Index of \\"$poolColumn(Index)\\" was already specified\"\n } else {\n set poolIndexes($poolColumn(Index)) 1\n }\n\n # Check to see if a poolName was specified... if not set to $apppool$poolColumn(Index)\n if { [string length $poolColumn(Name)] == 0 } {\n set poolColumn(Name) [format \"%spool%s\" $app $poolColumn(Index)]\n debug [list pools $poolIdx] [format \"no pool name specified... setting to %s\" $poolColumn(Name)] 7\n }\n set poolNames($poolColumn(Index)) $poolColumn(Name)\n\n if { $poolColumn(Index) == $poolDefaultPoolIndex } {\n # Set the default pool name for use later during virtual server creation\n set default_pool_name $poolColumn(Name)\n }\n\n # Process the poolMembers table\n set memberStr \"members replace-all-with \{ \"\n foreach memberRow $poolMembers {\n array unset memberColumn\n array set memberColumn {}\n table_row_to_array $memberRow memberColumn ::table_defaults(Members) [list AdvOptions]\n\n set memberId [format \"%s/%s:%s\" $memberColumn(Index) $memberColumn(IPAddress) $memberColumn(Port)]\n\n if { [llength $poolPools] == 1 && $memberColumn(Index) == -1 } {\n set memberColumn(Index) 0\n }\n\n if { $memberColumn(Index) != $poolColumn(Index) } {\n debug [list pools $poolIdx members $memberId skip_index] [format \"not a member of pool %s skipping\" $poolColumn(Index)] 11\n continue\n }\n\n debug [list pools $poolIdx members $memberId config_raw] [array get memberColumn] 9\n\n\n set memberColumn(AdvOptions) [lindex $memberColumn(AdvOptions) 0]\n\n # We support for many option formats for the IPAddress field. Examples are:\n # 0.0.0.0 Special value that signals to skip this pool member\n # x.x.x.x[%y][;nodename] IPv4 Address w/w/o Route Domain or Node Name\n # abcd::0001[%y][;nodename] IPv6 Address w/w/o Route Domain or Node Name\n # /Common/node_name Pre-existing node name\n # node_name Pre-existing node name without folder (default folder=Common)\n # hostname.org.com A DNS Hostname (resolved on deployment)\n #\n # These options are processed as follows:\n # 1) Skip row if IPAddress == \"0.0.0.0\" or is empty\n # 2) Determine if a node object was specified or not\n # 3) If node object does not exist process as follows\n # 3a) If a nodename option was specified create the node object\n # 3b) If not IP than assume a hostname and resolve IP, create node using hostname, add node to member string\n\n # 1) Skip pool members with a 0.0.0.0 or empty IP. Added to allow creation of an empty pool when you still have\n # to expose the pool member IP as a tenant editable field in iWorkflow (Cisco APIC needs this for Dynamic Endpoint Insertion)\n if { [string match 0.0.0.0 $memberColumn(IPAddress)] || [string length $memberColumn(IPAddress)] == 0 } {\n debug [list pools $poolIdx members $memberId skip_ip] \"ip=0.0.0.0 or empty, skipping\" 7\n continue\n } else {\n incr numMembers\n }\n\n # TODO: Is this still required?\n # Sometimes we receive a transposed ip/port from iWorkflow... fix it here\n if {[has_routedomain $memberColumn(Port)]} {\n set new_port $memberColumn(IPAddress)\n set new_ip $memberColumn(Port)\n set memberColumn(Port) $new_port\n set memberColumn(IPAddress) $new_ip\n debug [list pools $poolIdx members $memberId fix_ip_port] [format \"ip=%s port=%s\" $memberColumn(IPAddress) $memberColumn(Port)] 7\n }\n\n set node_default_folder \"/Common/\"\n if { [string first \"/\" $memberColumn(IPAddress)] >= 0 } { set node_default_folder \"\" }\n set node_create 0\n if { [string first \; $memberColumn(IPAddress)] > 1 } {\n set memberColumn(IPAddress) [lindex $memberColumn(IPAddress) 0]\n set node_info [psplit $memberColumn(IPAddress) \;]\n set memberColumn(IPAddress) [lindex $node_info 0]\n set memberColumn(NodeName) [lindex $node_info 1]\n set node_obj_name [format \"%s%s\" $node_default_folder $memberColumn(NodeName)]\n debug [list pools $poolIdx members $memberId named_node node_obj_name] $node_obj_name 7\n } else {\n set node_obj_name [format \"%s%s\" $node_default_folder $memberColumn(IPAddress)]\n debug [list pools $poolIdx members $memberId node_obj_name] $node_obj_name 7\n }\n\n # 2) Determine if a node object was specified rather than an IP address\n set node_exist [check_node_exist $node_obj_name]\n debug [list pools $poolIdx members $memberId set_blank_folder] \"folder=$node_default_folder name=$node_obj_name exist=$node_exist\" 7\n\n debug [list pools $poolIdx members $memberId is_ip] \"$memberColumn(IPAddress) [is_ip $memberColumn(IPAddress)]\" 7\n\n if { [info exists memberColumn(NodeName)] && $node_exist == 0 } {\n set node_create 1\n } else {\n if { $node_exist == 0 && [is_ip $memberColumn(IPAddress)] == 0 } {\n set memberColumn(NodeName) $memberColumn(IPAddress)\n set memberColumn(IPAddress) [dns_lookup $memberColumn(IPAddress)]\n debug [list pools $poolIdx members $memberId resolved_ip] $memberColumn(IPAddress) 7\n set node_create 1\n }\n }\n\n if { $node_create } {\n set node_cmd [format \"ltm node /%s/%s address %s\" $partition $memberColumn(NodeName) $memberColumn(IPAddress)]\n debug [list pools $poolIdx members $memberId named_node tmsh_create] $node_cmd 7\n tmsh::create $node_cmd\n set node_cache($memberColumn(NodeName)) 1\n set node_exist 1\n set memberColumn(IPAddress) [format \"/Common/%s\" $memberColumn(NodeName)]\n }\n\n # Add a route domain if it wasn't included and we don't already have a node object created\n if { $node_exist == 0 && ![has_routedomain $memberColumn(IPAddress)]} {\n set memberColumn(IPAddress) [get_dest_addr $memberColumn(IPAddress)]\n }\n\n # If we don't get a port in the pool member table than use the template value for poolMemberDefaultPort\n # If poolMemberDefaultPort is empty than use the value for poolport\n if { [string length $memberColumn(Port)] == 0} {\n if { [string length $poolMemberDefaultPort] == 0 } {\n debug [list pools $poolIdx members $memberId port_sub_vs] [format \"using %s\" $poolport] 5\n set memberColumn(Port) $poolport\n } else {\n debug [list pools $poolIdx members $memberId port_sub_default] [format \"using %s\" $poolMemberDefaultPort] 5\n set memberColumn(Port) $pool__MemberDefaultPort\n }\n }\n\n debug [list pools $poolIdx members $memberId normalized_config] [array get memberColumn] 7\n\n if { [string length $memberColumn(AdvOptions)] > 0} {\n debug [list pools $poolIdx members $memberId adv_options] \"processing member advanced options string\" 7\n set memberColumn(AdvOptions) [format \" %s\" [process_options_string $memberColumn(AdvOptions) \"\" \"\"]]\n }\n\n if { $node_exist } {\n # Node did exist, create : string\n set memberColumn(Dest) [format \"%s:%s\" $memberColumn(IPAddress) $memberColumn(Port)]\n } else {\n # Node did not exist, get the correctly formatted ip, port string\n set memberColumn(Dest) [get_dest_str $memberColumn(IPAddress) $memberColumn(Port)]\n }\n\n\tif { ![info exists ::pool_member_state($memberColumn(State))] } {\n\t\terror \"The pool member state specified for $memberColumn(Dest) is not valid\"\n\t}\n\n\tlappend postfinal_deferred_cmds [create_escaped_tmsh [format \"tmsh::modify ltm pool %s/%s members modify \{ %s \{ %s \} \}\" $app_path $poolNames($poolColumn(Index)) $memberColumn(Dest) $::pool_member_state($memberColumn(State))]]\n\n append memberStr [format \" %s \{ connection-limit %s ratio %s priority-group %s %s \} \" $memberColumn(Dest) $memberColumn(ConnectionLimit) $memberColumn(Ratio) $memberColumn(PriorityGroup) $memberColumn(AdvOptions)]\n }\n append memberStr \" \} \"\n\n # Check to see if we really have any pool members after table processing\n if { $numMembers == 0 } {\n debug [list pools $poolIdx members] \"no true pool members found after table was processed, setting to none\" 5\n set memberStr \" members none\"\n }\n\n debug [list pools $poolIdx member_str] \"memberStr=$memberStr\" 7\n\n set poolColumn(AdvOptions) [lindex $poolColumn(AdvOptions) 0]\n\n # We support multiple monitors and the ability to specify the minimum number of monitors that need\n # to pass for the pool to be considered healthy. The format of the Monitor string from the Pool table is:\n # ([,][;])\n # For example: 0,1,2;3 specifies that this pool should associate the monitors created with\n # monitor index 0, 1 and 2 and that all 3 monitors need to pass for the\n # pool to be considered available.\n #\n # If no value is specifed no monitor is associated with the pool\n set monitor $poolColumn(Monitor)\n if { [string length $monitor] > 0 } {\n # Monitor info entered\n if { [string match \"\;\" $monitor] } {\n # Min monitors specified\n set monitor [lindex $monitor 0]\n set monparts [split $monitor \;]\n if { [llength $monparts] == 2 } {\n # Set the minimum number of monitors and list of monitors to associate\n set monmin [lindex $monparts 1]\n set monlist [split [lindex $monparts 0] ,]\n }\n } else {\n # Min monitors NOT specified, assume ALL monitors should pass and create list of monitors\n set monmin -1\n set monlist [split [lindex $poolColumn(Monitor) 0] ,]\n }\n\n # Get the names of the monitors that were created above (array keyed by monitor index) and\n # check to make sure all monitors specified were actually created\n set monmapped {}\n foreach mon $monlist {\n if { [info exists monNames($mon)] } {\n lappend monmapped $monNames($mon)\n } else {\n error \"The monitor index '$mon' specified in pool index '$poolColumn(Index)' does not exist\"\n }\n }\n\n # Setup our command\n if { $monmin > 0 } {\n set monitorCmd [format \"monitor min %s of { %s }\" $monmin [join $monmapped \" \"]]\n } else {\n set monitorCmd [format \"monitor \\"%s\\"\" [join $monmapped \" and \"]]\n }\n } else {\n # No monitor specified, set to none\n set monitorCmd \"monitor none\"\n }\n\n # iCR does not like table columns with empty values. Workaround this by allow use of keyword 'none' and NOOP\n if { [string tolower $poolColumn(AdvOptions)] == \"none\" } {\n set poolColumn(AdvOptions) \"\"\n }\n\n # Setup the base pool create command\n set cmd [format \"ltm pool %s/%s %s %s \" $app_path $poolColumn(Name) $memberStr $monitorCmd]\n\n array set pool_options {\n \"poolColumn(LbMethod)\" \"load-balancing-mode\"\n \"poolColumn(Description)\" \"description\"\n }\n\n foreach {optionvar optioncmd} [array get pool_options] {\n append cmd [generic_add_option [list pools $poolIdx options] [set [subst $optionvar]] $optioncmd \"\" 0]\n }\n\n if { [string length poolColumn(AdvOptions)] > 0 } {\n debug [list pools $poolIdx adv_options] \"processing advanced options string\" 7\n append cmd [format \" %s\" [process_options_string $poolColumn(AdvOptions) \"\" \"\"]]\n }\n\n debug [list pools $poolIdx tmsh_create] $cmd 1\n tmsh::create $cmd\n\n custom_extensions_after_pool\n incr poolIdx\n}\n\nif { ![info exists poolIndexes($poolDefaultPoolIndex)] \n && $poolDefaultPoolIndex ne \"\" \n && $poolCount > 0 } {\n error \"The default pool index specified was not present in the pool table\"\n}\n\n# Call the custom_extensions_after_pool proc to allow site-specific customizations\ncustom_extensions_after_pools\n\n# Check to see if a vsName was specified... if not set to $app_vs\nif { [string length $vsName] == 0 } {\n set vsName [format \"%s_defaultvs%s\" $app $poolport]\n set addl_vs_basename [format \"%s_idx\" $app]\n change_var vsName $vsName\n debug [list virtual_server set_vs_name] [format \"no VS Name specified... setting to %s\" $vs__Name] 5\n} else {\n set addl_vs_basename [format \"%s_idx\" $vsName]\n}\n\n# Create L7 Traffic policy\nset l7p_matchGroups [list]\narray set l7p_matchGroupMap {}\narray set l7p_matchRules {}\nset l7p_actionGroups [list]\narray set l7p_actionGroupMap {}\narray set l7p_actionRules {}\narray set l7p_requires {}\narray set l7p_controls {}\narray set l7p_asmrule {}\narray set l7p_l7dosrule {}\nset l7p_defer_create 0\n\nset l7p_numMatchRows [llength $l7policyrulesMatch]\nset l7p_numActionRows [llength $l7policyrulesAction]\ndebug \"l7policy\" [format \"numMatchRows=%s numActionRows=%s\" $l7p_numMatchRows, $l7p_numActionRows] 7\n\n# Prepare the l7p_matchGroups list and the associated l7p_matchGroupMap array.\n# The list holds an ordered set of the discreet groups received\n# The array holds a mapping of the group to it's associated rows in the table\nset l7p_matchIdx 0\nforeach l7p_matchRow $l7policyrulesMatch {\n debug [list l7policy match_prep $l7p_matchIdx] [format \"matchRow=%s\" $l7p_matchRow] 9\n\n array unset l7p_matchColumn\n array set l7p_matchColumn {}\n table_row_to_array $l7p_matchRow l7p_matchColumn ::table_defaults(L7P_Match)\n\n if { [string tolower $l7p_matchColumn(Group)] != \"default\" && $l7p_matchColumn(Group) < 0 } {\n debug [list l7policy match_prep] \"skipping row, Group < 0\" 9\n incr l7p_matchIdx\n continue\n }\n\n if { ![info exists l7p_matchGroupMap($l7p_matchColumn(Group))] } {\n set l7p_matchGroupMap($l7p_matchColumn(Group)) [list]\n lappend l7p_matchGroups $l7p_matchColumn(Group)\n }\n lappend l7p_matchGroupMap($l7p_matchColumn(Group)) $l7p_matchIdx\n incr l7p_matchIdx\n}\n\n# Prepare the l7p_actionGroups list and the associated l7p_actionGroupMap array.\n# The list holds an ordered set of the discreet groups received\n# The array holds a mapping of the group to it's associated rows in the table\nset l7p_actionIdx 0\nforeach l7p_actionRow $l7policy__rulesAction {\n debug [list l7policy action_prep $l7p_actionIdx] [format \"actionRow=%s\" $l7p_actionRow] 9\n\n array unset l7p_actionColumn\n array set l7p_actionColumn {}\n table_row_to_array $l7p_actionRow l7p_actionColumn ::table_defaults(L7P_Action)\n\n if { [string tolower $l7p_actionColumn(Group)] != \"default\" && $l7p_actionColumn(Group) < 0 } {\n debug [list l7policy action_prep] \"skipping row, index < 0\" 9\n incr l7p_actionIdx\n continue\n }\n\n if { ![info exists l7p_actionGroupMap($l7p_actionColumn(Group))] } {\n set l7p_actionGroupMap($l7p_actionColumn(Group)) [list]\n lappend l7p_actionGroups $l7p_actionColumn(Group)\n }\n lappend l7p_actionGroupMap($l7p_actionColumn(Group)) $l7p_actionIdx\n incr l7p_actionIdx\n}\n\n# Perform a sanity check. The number of groups should match between the match and action tables\nforeach l7p_matchGroup $l7p_matchGroups {\n if { ! [info exists l7p_actionGroupMap($l7p_matchGroup)] } {\n error \"The L7 Policy Match Group '$l7p_matchGroup' was specified, however, an associated Action Group was not found\"\n }\n}\n\n# Iterate through the l7p_matchGroups list (and the associated mapping array) and create our conditions.\nforeach l7p_matchGroup $l7p_matchGroups {\n debug [list l7policy match $l7p_matchGroup] [format \"matchGroup=%s, mapping=%s\" $l7p_matchGroup $l7p_matchGroupMap($l7p_matchGroup)] 9\n\n set l7p_matchRuleIdx 0\n foreach l7p_matchRule $l7p_matchGroupMap($l7p_matchGroup) {\n set l7p_matchRow [lindex $l7policyrulesMatch $l7p_matchRule]\n debug [list l7policy match $l7p_matchGroup $l7p_matchRuleIdx] [format \"data=%s\" $l7p_matchRow] 9\n\n array unset l7p_matchColumn\n array set l7p_matchColumn {}\n table_row_to_array $l7p_matchRow l7p_matchColumn ::table_defaults(L7P_Match)\n\n # Determine which profile is required in the policy for the specified operand\n switch -glob [string tolower $l7p_matchColumn(Operand)] {\n client-ssl { set l7p_requires(\"client-ssl\") 1 }\n http { set l7p_requires(\"http\") 1 }\n ssl { set l7p_requires(\"ssl-persistence\") 1 }\n tcp { set l7p_requires(\"tcp\") 1 }\n default {\n if { $l7p_matchColumn(Group) != \"default\" } {\n error \"Could not determine the correct profile type for L7 Policy Match, Group $l7p_matchColumn(Group), Operand $l7p_matchColumn(Operand)\"\n }\n }\n }\n\n # Set our tmsh modifiers\n if { [string tolower $l7p_matchColumn(Negate)] == \"no\" } { set l7p_matchColumn(Negate) \"\" }\n if { [string tolower $l7p_matchColumn(Negate)] == \"yes\" } { set l7p_matchColumn(Negate) \"not\" }\n if { [string tolower $l7p_matchColumn(Missing)] == \"no\" } { set l7p_matchColumn(Missing) \"\" }\n if { [string tolower $l7p_matchColumn(Missing)] == \"yes\" } { set l7p_matchColumn(Missing) \"missing\" }\n if { [string tolower $l7p_matchColumn(CaseSensitive)] == \"no\" } { set l7p_matchColumn(CaseSensitive) \"case-insensitive\" }\n if { [string tolower $l7p_matchColumn(CaseSensitive)] == \"yes\" } { set l7p_matchColumn(CaseSensitive) \"case-sensitive\" }\n\n # Process the operand. The '/' character gets replaced with a ' ' to build the tmsh\n # command. Additionally the ',' character gets replaced with a ' ' to allow for multiple\n # values to be passed to the operand.\n set l7p_match_oper [split $l7p_matchColumn(Operand) /]\n if { [llength $l7p_match_oper] > 0 } {\n set l7p_rule_opertmp [join $l7p_match_oper \" \"]\n set l7p_rule_valtmp \"\"\n if { [string length $l7p_matchColumn(Value)] > 0 } {\n set l7p_rule_valtmp [format \"%s %s values { \\"%s\\" }\" $l7p_matchColumn(Negate) $l7p_matchColumn(Condition) [string map {, \"\\" \\"\"} $l7p_matchColumn(Value)]]\n }\n lappend l7p_matchRules($l7p_matchGroup) [format \"%s { %s %s %s %s }\" $l7p_matchRuleIdx $l7p_rule_opertmp $l7p_matchColumn(Missing) $l7p_matchColumn(CaseSensitive) $l7p_rule_valtmp]\n } else {\n lappend l7p_matchRules($l7p_matchGroup) \"\"\n }\n debug [list l7policy match $l7p_matchGroup $l7p_matchRuleIdx] [format \"rule=%s\" [lindex $l7p_matchRules($l7p_matchGroup) $l7p_matchRuleIdx]] 7\n incr l7p_matchRuleIdx\n }\n}\n\n# Iterate through the l7p_actionGroups list (and the associated mapping array) and create our actions.\nforeach l7p_actionGroup $l7p_actionGroups {\n debug [list l7policy action $l7p_actionGroup] [format \"actionGroup=%s, mapping=%s\" $l7p_actionGroup $l7p_actionGroupMap($l7p_actionGroup)] 9\n\n set l7p_actionRuleIdx 0\n foreach l7p_actionRule $l7p_actionGroupMap($l7p_actionGroup) {\n set l7p_actionRow [lindex $l7policy__rulesAction $l7p_actionRule]\n debug [list l7policy action $l7p_actionGroup $l7p_actionRuleIdx] [format \"data=%s\" $l7p_actionRow] 9\n\n array unset l7p_actionColumn\n array set l7p_actionColumn {}\n table_row_to_array $l7p_actionRow l7p_actionColumn ::table_defaults(L7P_Action)\n\n if { [string tolower $l7p_actionColumn(Group)] == \"default\" } {\n set l7p_action_found_default 1\n }\n\n # Determine which profile is required in the policy for the specified operand\n switch -glob [string tolower $l7p_actionColumn(Target)] {\n asm {\n set l7p_controls(\"asm\") 1\n set l7p_asmrule($l7p_actionGroup) 1\n }\n cache { set l7p_controls(\"cache\") 1 }\n compress { set l7p_controls(\"compression\") 1 }\n forward { set l7p_controls(\"forwarding\") 1 }\n http { set l7p_controls(\"forwarding\") 1 }\n l7dos {\n set l7p_controls(\"l7dos\") 1\n set l7p_controls(\"asm\") 1\n set l7p_l7dosrule($l7p_actionGroup) 1\n }\n log { set l7p_controls(\"forwarding\") 1 }\n request-adapt { set l7p_controls(\"request-adaption\") 1 }\n response-adapt { set l7p_controls(\"response-adaptions\") 1 }\n server-ssl { set l7p_controls(\"server-ssl\") 1 }\n tcp-nagle { set l7p_controls(\"forwarding\") 1 }\n tcl { set l7p_controls(\"tcl\") 1 }\n default {\n error \"Could not determine the correct profile type for L7 Policy Action, Group $l7p_actionColumn(Group), Target $l7p_actionColumn(Target)\"\n }\n }\n\n # Process the target(s). Multiple targets/parameters are delimited by a '|' seperator. The '/' character\n # gets replaced with a ' ' to build the tmsh command. We then determine the type of target by\n # counting the number unique target elements. 3 element targets don't require a parameter\n # (eg: forward/request/reset). 4 element target require parameters. We then parse the 4th element\n # as a comma-seperated string to determine the number of unique parameters required. The\n # entered parameter is checked to ensure a parameters are entered (can be blank) and the the\n # tmsh command is created.\n set l7p_action_target_chunk \"\"\n set l7p_action_targets [split $l7p_actionColumn(Target) /]\n switch [llength $l7p_action_targets] {\n 3 {\n set l7p_rule_targettmp [join $l7p_action_targets \" \"]\n set l7p_action_target_chunk [format \"%s \{ %s \}\" $l7p_actionRuleIdx $l7p_rule_targettmp]\n debug [list l7policy action $l7p_actionGroup $l7p_actionRuleIdx 3_elements] [format \"chunk=%s\" $l7p_action_target_chunk] 7\n }\n 4 {\n set l7p_rule_targettmp [format \"%s %s %s\" [lindex $l7p_action_targets 0] [lindex $l7p_action_targets 1] [lindex $l7p_action_targets 2]]\n set l7p_rule_parameters [psplit [lindex $l7p_action_targets 3] ,]\n # Fix the list in the case that we got a reserved character\n if { [llength $l7p_actionColumn(Parameter)] == 1 } {\n set l7p_actionColumn(Parameter) [lindex $l7p_actionColumn(Parameter) 0]\n }\n\n set l7p_rule_values [psplit $l7p_actionColumn(Parameter) ,]\n debug [list l7policy action $l7p_actionGroup $l7p_actionRuleIdx val_list] $l7p_actionColumn(Parameter) 7\n\n set l7p_action_parIdx 0\n\n set l7p_action_target_chunk [format \"%s \{ %s \" $l7p_actionRuleIdx $l7p_rule_targettmp]\n foreach l7p_action_parameter $l7p_rule_parameters {\n if { [llength $l7p_rule_parameters] == 1 } {\n set l7p_action_parameter_value $l7p_actionColumn(Parameter)\n } else {\n set l7p_action_parameter_value [lindex $l7p_rule_values $l7p_action_parIdx]\n }\n\n # Special handling for forward/request/select/(pool|clone-pool). Either a full path to\n # a pool can be entered (eg: /Common/mypool) or the index of a pool created in the poolPools\n # table can be referenced. If a pool index is referenced we replace it here with the name\n # of the pool\n switch -regexp $l7p_actionColumn(Target) {\n ^.(pool|clone-pool)$ {\n set l7p_action_parameter_poolidx -1\n if { [regexp {^pool:[0-9]+$} $l7p_action_parameter_value] } {\n set l7p_action_parameter_poolidx [lindex [split $l7p_action_parameter_value :] 1]\n debug [list l7policy action $l7p_actionGroup $l7p_actionRuleIdx pool_substitute] [format \"idx=%s val=%s name=%s\" $l7p_action_parameter_poolidx $l7p_action_parameter_value $poolNames($l7p_action_parameter_poolidx)] 7\n set l7p_action_parameter_value [format \"%s/%s\" $app_path $poolNames($l7p_action_parameter_poolidx)]\n }\n }\n ^asm.enable.$ {\n if { [regexp {^bundled:(.$)} $l7p_action_parameter_value -> l7p_action_parameter_asmpolicy] } {\n debug [list l7policy action $l7p_actionGroup $l7p_actionRuleIdx asm_policy] [format \"%s\" $l7p_action_parameter_asmpolicy] 7\n if { ! [string match $l7p_action_parameter_asmpolicy $vsBundledItems] } {\n error \"L7 Policy Action Rule with Group $l7p_actionGroup Index $l7p_actionRuleIdx specified a bundled policy that wasn't selected for deployment\"\n }\n set l7p_action_parameter_asmpolicy [string map [list \"%APP_NAME%\" $app] $l7p_action_parameter_asmpolicy]\n set l7p_action_parameter_value [format \"%s/%s\" $app_path $l7p_action_parameter_asmpolicy]\n # Flag deferred creation of the policy because of bundled ASM policy\n set l7p_defer_create 1\n }\n }\n }\n\n append l7p_action_target_chunk [format \"%s \\"%s\\" \" $l7p_action_parameter $l7p_action_parameter_value]\n incr l7p_action_parIdx\n }\n append l7p_action_target_chunk \"\} \"\n debug [list l7policy action $l7p_actionGroup $l7p_actionRuleIdx 4_element] [format \"chunk=%s\" $l7p_action_target_chunk] 7\n }\n default { error \"The target $l7p_actionColumn(Target) could not be processed\" }\n }\n lappend l7p_actionRules($l7p_actionGroup) $l7p_action_target_chunk\n debug [list l7policy action $l7p_actionGroup $l7p_actionRuleIdx] [format \"rule=%s\" [lindex $l7p_actionRules($l7p_actionGroup) $l7p_actionRuleIdx]] 7\n incr l7p_actionRuleIdx\n }\n debug [list l7policy action $l7p_actionGroup] [format \"rules=%s\" $l7p_actionRules($l7p_actionGroup)] 7\n}\n\nif { [info exists l7p_controls(\"asm\")] && ! $l7p_action_found_default } {\n error \"A 'default' L7 Policy Action must be defined if you wish you use an ASM policy\"\n}\n\n# Build our L7 ruleset\nset l7p_cmd_rules \"rules replace-all-with \{ \"\nset l7p_ruleIdx 0\nforeach l7p_matchGroup $l7p_matchGroups {\n debug [list l7policy rules $l7p_matchGroup \"match \"] $l7p_matchRules($l7p_matchGroup) 7\n debug [list l7policy rules $l7p_matchGroup action] $l7p_actionRules($l7p_matchGroup) 7\n\n # If an ASM target was selected we must add a bypass action to each action in the ruleset\n # that does not contain an ASM target\n set l7p_rule_asmdefault \"\"\n if { ![info exists l7p_asmrule($l7p_matchGroup)] && [info exists l7p_controls(\"asm\")] } {\n if { [string tolower $l7policydefaultASM] != \"bypass\" } {\n if { [regexp {^bundled:(.$)} $l7policy__defaultASM -> l7p_action_default_asmpolicy] } {\n debug [list l7policy l7policy rules $l7p_matchGroup action default_asmpolicy] [format \"%s\" $l7p_action_default_asmpolicy] 7\n if { ! [string match $l7p_action_default_asmpolicy* $vsBundledItems] } {\n error \"L7 Policy Default ASM Policy specified a bundled policy that wasn't selected for deployment\"\n }\n set l7policydefaultASM [format \"%s/%s\" $app_path $l7p_action_default_asmpolicy]\n set l7p_defer_create 1\n }\n set l7p_rule_asmdefault [format \" 98 { asm request enable policy %s } \" $l7policydefaultASM]\n set l7p_controls(\"asm\") 1\n } else {\n set l7p_rule_asmdefault \" 98 { asm request disable } \"\n }\n }\n\n set l7p_rule_l7dosdefault \"\"\n if { ![info exists l7p_l7dosrule($l7p_matchGroup)] && [info exists l7p_controls(\"l7dos\")] } {\n if { [string tolower $l7policydefaultL7DOS] != \"bypass\" } {\n set l7p_rule_l7dosdefault [format \" 99 { l7dos request enable from-profile %s } \" $l7policydefaultL7DOS]\n set l7p_controls(\"asm\") 1\n set l7p_controls(\"l7dos\") 1\n } else {\n set l7p_rule_l7dosdefault \" 99 { l7dos request disable } \"\n }\n }\n\n set l7p_rule_default [format \"%s %s\" $l7p_rule_asmdefault $l7p_rule_l7dosdefault]\n\n set l7p_rule_condpart \"\"\n if { [llength $l7p_matchRules($l7p_matchGroup)] > 0 && [string length [lindex $l7p_matchRules($l7p_matchGroup) 0]] > 0 } {\n set l7p_rule_condpart [format \"conditions replace-all-with \{ %s \}\" [join $l7p_matchRules($l7p_matchGroup) \" \"]]\n }\n set l7p_rule_actionpart [format \"actions replace-all-with \{ %s %s \}\" [join $l7p_actionRules($l7p_matchGroup) \" \"] $l7p_rule_default]\n append l7p_cmd_rules [format \"%s \{ %s %s ordinal %s \} \" $l7p_matchGroup $l7p_rule_condpart $l7p_rule_actionpart [expr {$l7p_ruleIdx+1}]]\n incr l7p_ruleIdx\n}\n\n# Finish building the tmsh command and execute it\nset l7p_cmd_requires [format \" requires replace-all-with { %s } \" [join [array names l7p_requires] \" \"]]\nset l7p_cmd_controls [format \" controls replace-all-with { %s } \" [join [array names l7p_controls] \" \"]]\n\n# TMOS 12.1 introduced a new draft/publish model for L7 policies. Check for\n# that version and set a mode accordingly\nif { [string match \"12.1\" $version_info(version)] } {\n debug [list l7policy version_check] \"12.1 or newer detected\" 7\n set l7p_new_model 1\n} else {\n debug [list l7policy version_check] \"12.0 or older detected\" 7\n set l7p_new_model 0\n}\n\nif { $l7p_new_model } {\n if { $l7p_defer_create > 0 } {\n set l7p_cmd [format \"ltm policy %s/Drafts/%s_l7policy\" $app_path $app]\n } else {\n set l7p_cmd [format \"ltm policy %s/Drafts/%s_l7policy legacy\" $app_path $app]\n }\n set l7p_publish_cmd [format \"ltm policy %s/Drafts/%s_l7policy\" $app_path $app]\n} else {\n set l7p_cmd [format \"ltm policy %s/%s_l7policy\" $app_path $app]\n set l7p_defer_cmd $l7p_cmd\n}\n\nappend l7p_cmd [format \" strategy %s %s %s %s \}\" $l7policystrategy $l7p_cmd_requires $l7p_cmd_controls $l7p_cmd_rules]\ndebug [list l7policy l7p_cmd] $l7p_cmd 7\n\nif { [llength $l7p_matchGroups] > 0 && [llength $l7p_actionGroups] > 0 } {\n if { $l7p_defer_create > 0 } {\n lappend bundler_deferred_cmds [format \"catch { %s }\" [create_escaped_tmsh [format \"tmsh::create sys folder %s/Drafts\" $app_path]]]\n\n debug [list l7policy defer_create] $l7p_cmd 1\n set l7p_cmd_create [format \"tmsh::create %s\" $l7p_cmd]\n set l7p_cmd_modify [format \"tmsh::modify %s\" $l7p_cmd]\n\n lappend bundler_deferred_cmds [format \"catch { %s }\" [create_escaped_tmsh $l7p_cmd_modify]]\n lappend bundler_deferred_cmds [format \"catch { %s }\" [create_escaped_tmsh $l7p_cmd_create]]\n\n if { $l7p_new_model } {\n \tlappend bundler_deferred_cmds [format \"catch { tmsh::publish %s }\" [create_escaped_tmsh $l7p_publish_cmd]]\n }\n\n lappend bundler_deferred_cmds [format \"catch { %s }\" [create_escaped_tmsh [format \"tmsh::modify ltm virtual %s/%s profiles add \{ /Common/websecurity \{ \} \}\" $app_path $vsName]]]\n lappend bundler_deferred_cmds [format \"catch { %s }\" [create_escaped_tmsh [format \"tmsh::modify ltm virtual %s/%s policies add \{ %s/%s_l7policy \}\" $app_path $vsName $app_path $app]]]\n\n if { $l7p_new_model } {\n lappend bundler_deferred_cmds [format \"tmsh::delete sys folder %s/Drafts \" $app_path]\n }\n } else {\n tmsh::create [format \"sys folder %s/Drafts\" $app_path]\n debug [list l7policy tmsh_create] $l7p_cmd 1\n tmsh::create $l7p_cmd\n \tif { $l7p_new_model } {\n \t\tdebug [list l7policy tmsh_publish] $l7p_publish_cmd 1\n \t\ttmsh::publish $l7p_publish_cmd\n tmsh::delete [format \"sys folder %s/Drafts\" $app_path]\n \t}\n\n # Add the created policy to the vsAdvPolicies variable so we attach it to the\n # Virtual Server when it's created.\n append vsAdvPolicies [format \" %s/%s_l7policy \" $app_path $app]\n debug [list l7policy add_policy_to_vs] [format \"vsAdvPolicies=%s\" $vsAdvPolicies] 5\n }\n} else {\n debug [list l7policy skip_creation] \"No valid actions or rules after processing, skipping creation\" 7\n}\n\n# Call the custom_extensions_before_vs proc to allow site-specific customizations\ncustom_extensions_before_vs\n\n# Create virtual Server\n\n# Process the HTTP dependent features\nif { [string length $vsProfileHTTP] > 0 } {\n # Process the 'auto' flag for featureredirectToHTTPS\n if { $featureredirectToHTTPS eq \"auto\" && $poolport eq \"443\" && $pooladdr ne \"255.255.255.254\"} {\n debug [list virtual_server featureredirectToHTTPS] \"found auto flag and port is 443, setting feature to enabled\" 5\n set featureredirectToHTTPS enabled\n }\n\n # Process the 'auto' flag for featureinsertXForwardedFor\n if { $featureinsertXForwardedFor eq \"auto\" && $vsSNATConfig ne \"\"} {\n debug [list virtual_server featureinsertXForwardedFor] \"found auto flag, port is 443 or 80 and SNAT enabled, setting feature to enabled\" 5\n set featureinsertXForwardedFor enabled\n }\n}\n\n# Process the vs__ProfileSecurityIPBlacklist option.\nset ipi_mode 0\nswitch -glob [string tolower $vsProfileSecurityIPBlacklist] {\n enabled-block { set ipi_action \"drop\" }\n enabled-log { set ipi_action \"accept\" }\n none { set ipi_mode 0 }\n / { set ipi_mode 2 }\n default {\n set ipi_create 0\n set vsProfileSecurityIPBlacklist none\n }\n}\n\n# Process featureeasyL4Firewall options\nset afm_auto_ipistring \"\"\nif { [is_provisioned afm] && $pooladdr != \"255.255.255.254\" } {\n switch [string tolower $featureeasyL4Firewall] {\n auto {\n debug [list virtual_server featureeasyL4Firewall] \"found auto option, setting feature to enabled\" 5\n set featureeasyL4Firewall enabled\n set afm_auto_ipistring \"none\"\n }\n base {\n debug [list virtual_server featureeasyL4Firewall] \"found base flag, setting feature to enabled, vsProfileSecurityIPBlacklist to disabled\" 5\n set featureeasyL4Firewall enabled\n set afm_auto_ipistring \"none\"\n }\n base+ip_blacklist_block {\n debug [list virtual_server featureeasyL4Firewall] \"found auto option, setting feature to enabled, vsProfileSecurityIPBlacklist to enabled-block\" 5\n set feature__easyL4Firewall enabled\n set afm_auto_ipistring \"enabled-block\"\n }\n base+ip_blacklist_log {\n debug [list virtual_server featureeasyL4Firewall] \"found base+ipblacklist_log option, setting feature to enabled, vsProfileSecurityIPBlacklist to enabled-log\" 5\n set featureeasyL4Firewall enabled\n set afm_auto_ipistring \"enabled-log\"\n }\n default {\n if { [get_var featureeasyL4Firewall] == \"auto\"} {\n set afm_auto_ipistring \"none\"\n }\n set featureeasyL4Firewall disabled\n }\n }\n if { $ipi_mode < 2 } {\n change_var vsProfileSecurityIPBlacklist $afm_auto_ipistring\n }\n} else {\n debug [list virtual_server featureeasyL4Firewall] \"AFM not provisioned, skipping\" 5\n if { $featureeasyL4Firewall != \"auto\" } {\n change_var featureeasyL4Firewall disabled\n }\n set featureeasyL4Firewall disabled\n}\n\n# Check for HTTP Strict Transport Security (HSTS) option. We do this here\n# so the irule can be easily appended to the existing iRule list\nif { $clientssl > 0 && [string match enabled* $featuresecurityEnableHSTS] } {\n # include iRules used for featuresecurityEnableHSTS\n set irule_HSTS {\n when HTTP_RESPONSE {\n HTTP::header insert Strict-Transport-Security \"%HSTSOPTIONS%\"\n}\n }; # end irule_HSTS\n set irule_HSTS_redirect {\n when HTTP_REQUEST {\n HTTP::respond 301 Location \"https://[getfield [HTTP::host] \":\" 1][HTTP::uri]\"\n}\n };\n\n debug [list virtual_server featuresecurityEnableHSTS] \"creating HSTS iRule\" 5\n set hstsrule [format \"%s/hsts_irule\" $app_path]\n\n # Substitute in HSTS options is specified\n if { [string match \";\" $featuresecurityEnableHSTS] } {\n set hstsoptions [format \"%s\; \" [lindex [split $featuresecurityEnableHSTS \;] 1]]\n set featuresecurityEnableHSTS [lindex [split $feature__securityEnableHSTS \;] 0]\n debug [list virtual_server featuresecurityEnableHSTS options] $hstsoptions 7\n } else {\n set hstsoptions \"max-age=31536000; \"\n }\n\n switch [string tolower $featuresecurityEnableHSTS] {\n enabled-preload { append hstsoptions \"preload\" }\n enabled-subdomain { append hstsoptions \"includeSubDomains\" }\n enabled-preload-subdomain { append hstsoptions \"includeSubDomains\; preload\" }\n default { error \"An invalid option was specified for featuresecurityEnableHSTS\" }\n }\n\n set irule_HSTS_final [string map [list %HSTSOPTIONS% $hstsoptions] $irule_HSTS]\n\n set hstscmd \"ltm rule $hstsrule $irule_HSTS_final\"\n debug [list virtual_server featuresecurityEnableHSTS tmsh_create] $hstscmd 1\n tmsh::create $hstscmd\n\n if { $feature__redirectToHTTPS eq \"enabled\"} {\n debug [list virtual_server featuresecurityEnableHSTS ssl_redirect_check] \"feature_redirectToHTTPS enabled, creating HSTS redirect iRule\" 5\n set hstsredirectrule [format \"%s/hsts_redirect_irule\" $app_path]\n set hstsredirectcmd \"ltm rule $hstsredirectrule $irule_HSTS_redirect\"\n debug [list virtual_server featuresecurityEnableHSTS tmsh_create] $hstsredirectcmd 1\n tmsh::create $hstsredirectcmd\n }\n\n if { [string length $vsIrules] > 0 } {\n append vsIrules \",$hstsrule\"\n } else {\n set vs__Irules $hstsrule\n }\n debug [list virtual_server featuresecurityEnableHSTS add_irule_to_vs] [format \"vsIrules=%s\" $vsIrules] 7\n}\n\nset cmd [format \"ltm virtual %s/%s \" $app_path $vsName]\n\n# Setup our listener destination address\nset vs_dest_addr [get_dest_addr $pooladdr]\n\n# Keep vs_dest_addr as is for use by other features, create vs_dest with full %: format\nset vs_dest [get_dest_str $vs_dest_addr $poolport]\n\ndebug [list virtual_server set_dest] [format \"vs_dest_addr=%s vs_dest=%s\" $vs_dest_addr $vs_dest] 7\n\n# Set virtual server options we support. This array assumes a format \" IpProtocol\" \"ip-protocol\"\n \"vsConnectionLimit\" \"connection-limit\"\n \"vsDescription\" \"description\"\n \"vsSourceAddress\" \"source\"\n \"vsOptionSourcePort\" \"source-port\"\n \"vsOptionConnectionMirroring\" \"mirror\"\n \"vsProfileFallbackPersist\" \"fallback-persistence\"\n \"vsProfilePerRequest\" \"per-flow-request-access-policy\"\n}\n\nif { [string length $default_pool_name] > 0 } {\n set vs_options(default_pool_name) \"pool\"\n}\n\n# Set virtual server options we support. This array allows specifcation of the specific TMSH command format\narray set vs_options_custom {\n \"vsIrules\" \" rules \{ %s \} \"\n \"vsProfileDefaultPersist\" \" persist replace-all-with \{ %s \} \"\n \"vsProfileSecurityLogProfiles\" \" security-log-profiles replace-all-with \{ %s \} \"\n}\n\n# Process the create: option for persistence profiles\narray set persist_create_defaults {\n \"cookie\" { default \"/Common/cookie\" }\n \"dest-addr\" { default \"/Common/dest_addr\" }\n \"hash\" { default \"/Common/hash\" }\n \"msrdp\" { default \"/Common/msrdp\" }\n \"sip\" { default \"/Common/sip_info\" }\n \"source-addr\" { default \"/Common/source_addr\" }\n \"ssl\" { default \"/Common/ssl\" }\n \"universal\" { default \"/Common/universal\" }\n}\n\n# Loop over the two fields that allow this option\nforeach persist_var [list vsProfileDefaultPersist vsProfileFallbackPersist] {\n set persist_val [set [subst $persist_var]]\n set persist_type \"\"\n if { [regexp -nocase {^create:} $persist_val] } {\n set persist_val [string map {\"create:\" \"\"} $persist_val]\n\n # Process the string, check to see the persistence type was specified and is valid\n array set persist_options [process_kvp_string $persist_val]\n if { ! [info exists persist_options(type)] } {\n error \"The create string specified for $persist_var needs to include a 'type=(cookie|dest-addr|hash|msrdp|sip|source-addr|ssl|universal)' option\"\n }\n\n if { ! [info exists persist_create_defaults($persist_options(type))] } {\n error \"The persistence type '$persist_options(type)' specified for $persist_var is not valid\"\n }\n\n # Set some inital values\n array set persist_attr [subst $::persist_create_defaults($persist_options(type))]\n set persist_name [format \"%spersistence%s\" $app $persist_options(type)]\n set persist_cmd [format \"ltm persistence %s %s/%s \" $persist_options(type) $app_path $persist_name]\n\n # Remove the 'type=XXX;' field from the create string\n set persist_val [string map [list \"type=$persist_options(type);\" \"\"] $persist_val]\n\n # Process the rest of the options and get the TMSH string portion for options\n set persist_option_cmd [process_options_string $persist_val \"persistence $persist_options(type)\" $persist_attr(default) 1]\n debug [list virtual_server persistence create_handler $persist_var $persist_options(type)] [format \"%s\" $persist_option_cmd] 7\n\n # Build our full TMSH command\n append persist_cmd $persist_option_cmd\n\n # Reset the APL var to point to the new profile name\n set [subst $persist_var] [format \"%s/%s\" $app_path $persist_name]\n debug [list virtual_server persistence create_handler $persist_var $persist_options(type)] [format \"%s=%s\" $persist_var [set [subst $persist_var]]] 1\n debug [list virtual_server persistence create_handler $persist_var $persist_options(type) tmsh_create] [format \"%s\" $persist_cmd] 1\n tmsh::create $persist_cmd\n }\n}\n\nhandle_opt_remove_on_redeploy vsProfilePerRequest \"\" \"per-flow-request-access-policy\" \"apm\"\nhandle_opt_remove_on_redeploy vsProfileSecurityIPBlacklist \"none\" \"ip-intelligence-policy\" \"ltm\"\n\n# Process the vsProfileSecurityIPBlacklist option according to $ipi_mode set above\nif { $ipi_mode == 1 } {\n debug [list virtual_server ip_blacklist create] [format \"ipi_action=%s, creating IPI policy\" $ipi_action] 7\n set ipi_name [create_obj_name \"ip_blacklist\"]\n set ipi_cmd [format \"security ip-intelligence policy %s default-action %s default-log-blacklist-hit-only yes\" $ipi_name $ipi_action]\n debug [list virtual_server ip_blacklist tmsh_create] $ipi_cmd 1\n tmsh::create $ipi_cmd\n set vsProfileSecurityIPBlacklist $ipi_name\n array set vs_options [list vsProfileSecurityIPBlacklist ip-intelligence-policy]\n}\n\nif { $ipi_mode == 2 } {\n debug [list virtual_server ip_blacklist associate] [format \"adding existing IPI policy %s\" $vsProfileSecurityIPBlacklist] 7\n array set vs_options [list vsProfileSecurityIPBlacklist ip-intelligence-policy]\n}\n\n# Process the featureeasyL4Firewall option\nhandle_opt_remove_on_redeploy featureeasyL4Firewall \"disabled\" \"fw-enforced-policy\" \"afm\"\n\nif { $featureeasyL4Firewall == \"enabled\" } {\n debug [list virtual_server l4_firewall] \"creating FW policy\" 5\n\n set cidr_blacklist [single_column_table_to_list $featureeasyL4FirewallBlacklist \"CIDRRange\"]\n debug [list virtual_server l4_firewall cidr_blacklist] $cidr_blacklist 7\n\n set cidr_sourcelist [single_column_table_to_list $feature__easyL4FirewallSourceList \"CIDRRange\"]\n debug [list virtual_server l4_firewall cidr_sourcelist] $cidr_sourcelist 7\n\n if { [llength $cidr_blacklist] > 0 } {\n debug [list virtual_server l4_firewall create_blacklist] \"creating static blacklist address-list\" 7\n set feature_easyL4Firewall_blacklistcmd [format \"security firewall address-list %s/afm_staticBlacklist addresses replace-all-with { %s }\" \\n $app_path [join $cidr_blacklist \" \"]]\n\n debug [list virtual_server l4_firewall create_blacklist tmsh_create] $feature_easyL4Firewall_blacklistcmd 1\n tmsh::create $feature_easyL4Firewall_blacklistcmd\n set feature_easyL4Firewall_blacklisttmpl [format \"staticBlacklist { action drop source { address-lists replace-all-with { %s/afm_staticBlacklist } } }\" $app_path]\n } else {\n set feature_easyL4Firewall_blacklisttmpl \"\"\n }\n\n if { [llength $cidr_sourcelist] > 0 } {\n debug [list virtual_server l4_firewall create_sourcelist] \"creating source address-list\" 7\n set feature_easyL4Firewall_srclistcmd [format \"security firewall address-list %s/afm_sourceList addresses replace-all-with { %s }\" \\n $app_path [join $cidr_sourcelist \" \"]]\n\n debug [list virtual_server l4_firewall create_sourcelist tmsh_create] $feature_easyL4Firewall_srclistcmd 1\n tmsh::create $feature_easyL4Firewall_srclistcmd\n } else {\n debug [list virtual_server l4_firewall create_sourcelist] \"creating DEFAULT source address-list\" 7\n set feature_easyL4Firewall_srclistcmd [format \"security firewall address-list %s/afm_sourceList addresses replace-all-with { 0.0.0.0/0 }\" $app_path]\n\n debug [list virtual_server l4_firewall create_sourcelist tmsh_create] $feature_easyL4Firewall_srclistcmd 1\n tmsh::create $feature_easyL4Firewall_srclistcmd\n }\n set feature_easyL4Firewall_srclist [format \"%s/afm_sourceList\" $app_path]\n\n set fw_name [create_obj_name \"firewall\"]\n set fw_cmd [format \"\"]\n set fw_tmpl {\nsecurity firewall policy %NAME% {\n rules replace-all-with {\n %STATIC_BLACKLIST%\n allowFrom {\n action accept\n ip-protocol %IP_PROTOCOL%\n source {\n address-lists replace-all-with {\n %SOURCE_LIST%\n }\n }\n }\n defaultDeny {\n action drop\n ip-protocol %IP_PROTOCOL%\n log yes\n source {\n addresses replace-all-with {\n 0.0.0.0/0 { }\n }\n }\n }\n }\n}\n };\n\n set tmpl_map [list %NAME% $fw_name \\n %IP_PROTOCOL% $vsIpProtocol \\n %STATIC_BLACKLIST% $feature_easyL4Firewall_blacklisttmpl \\n %SOURCE_LIST% $feature_easyL4Firewall_srclist ]\n\n set fw_policy [string map $tmpl_map $fw_tmpl]\n debug [list virtual_server l4_firewall tmsh_create] $fw_policy 1\n tmsh::create $fw_policy\n array set vs_options [list fw_name fw-enforced-policy]\n}\n\n# Process bundled iRules\nset vsBundledItems [string map {\",\" \" \" \";\" \" \"} $vsBundledItems]\nset bundled_irules [get_items_starting_with \"irule:\" $vsBundledItems]\ndebug [list virtual_server bundled_irule get_list] [format \"%s\" $bundled_irules] 7\n\nif { [llength $bundled_irules] > 0 } {\n set bundled_irule_map [list %APP_PATH% $app_path \\n %APP_NAME% $app \\n %VS_NAME% $vs__Name \\n %POOL_NAME% $default_pool_name \\n %PARTITION% $partition ]\n\n foreach bundled_irule $bundled_irules {\n debug [list virtual_server bundled_irule create_irule] [format \"deploying bundled iRule %s\" $bundled_irule] 5\n\n set bundled_irule_curl_mode -1\n if { [string match \"irule:url=\" $bundled_irule] } {\n set bundled_irule_curl_mode 1\n } elseif { [string match \"irule:urloptional=\" $bundled_irule] } {\n set bundled_irule_curl_mode 2\n } else {\n if {! [info exists bundler_objects($bundled_irule)] } {\n error \"A bundled iRule named '$bundled_irule' was not found in the template\"\n }\n set bundled_irule_src [string map $bundled_irule_map [::base64::decode $bundler_data($bundled_irule)]]\n set bundled_irule [string map {\"irule:\" \"\"} $bundled_irule]\n set bundled_irule_do_add 1\n }\n\n debug [list virtual_server bundled_irule create_irule curl_mode] [format \"mode=%s\" $bundled_irule_curl_mode] 7\n if { $bundled_irule_curl_mode > 0 } {\n set bundled_irule_isurl 1\n set bundled_irule_url [url_subst $bundled_irule]\n\n regexp {^./(.).irule} $bundled_irule_url -> bundled_irule\n set bundled_irule_filename [format \"/var/tmp/appsvcsirule%s%s%s.irule\" $::app $bundled_irule $bundler_timestamp]\n\n set bundled_irule_curl_state [curl_save_file $bundled_irule_url $bundled_irule_filename $bundled_irule_curl_mode]\n debug [list virtual_server bundled_irule create_irule curl_state] [format \"state=%s\" $bundled_irule_curl_state] 7\n\n if { $bundled_irule_curl_state } {\n set bundled_irule_fh [open $bundled_irule_filename]\n set bundled_irule_src [string map $bundled_irule_map [read $bundled_irule_fh]]\n close $bundled_irule_fh\n set bundled_irule_do_add 1\n } else {\n set bundled_irule_do_add 0\n }\n file delete $bundled_irule_filename\n }\n\n debug [list virtual_server bundled_irule $bundled_irule do_add] [format \"%s\" $bundled_irule_do_add] 7\n if { $bundled_irule_do_add } {\n set bundled_irule_cmd [format \"ltm rule %s/%s \{\n%s\n\}\" $app_path $bundled_irule $bundled_irule_src]\n debug [list virtual_server bundled_irule $bundled_irule tmsh_create] $bundled_irule_cmd 1\n tmsh::create $bundled_irule_cmd\n if { [string length $vsIrules] > 0 } {\n append vsIrules \",\"\n }\n append vsIrules [format \"%s/%s\" $app_path $bundled_irule]\n }\n }\n debug [list virtual_server bundled_irule add_irule_to_vs] [format \"vsIrules=\\"%s\\"\" $vsIrules] 7\n}\n\n# Process the vs_options array\nforeach {optionvar optioncmd} [array get vs_options] {\n append cmd [generic_add_option [list virtual_server options] [set [subst $optionvar]] $optioncmd \"\" 0]\n}\n\n# Process the vs_options_custom array\nforeach {optionvar optioncmd} [array get vs_options_custom] {\n append cmd [generic_add_option [list virtual_server options_custom] [set [subst $optionvar]] \"\" $optioncmd 1]\n}\n\nif { [string length $vsAdvOptions] > 0 } {\n debug [list virtual_server adv_options] \"processing advanced options string\" 7\n append cmd [format \" %s\" [process_options_string $vsAdvOptions \"\" \"\"]]\n}\n\nset snatcmd \"\"\n# Add SNAT options\nif { [string length $vsSNATConfig] > 0 } {\n switch -glob [string tolower $vsSNATConfig] {\n automap {\n append snatcmd \" source-address-translation \{ type automap \}\"\n }\n partition-default {\n append snatcmd [format \" source-address-translation \{ pool /%s/%s type snat \}\" $partition $partition]\n }\n none {\n append snatcmd \" source-address-translation \{ type none \}\"\n }\n create:* {\n # split a string formatted like this: \"[,]\"\n\n set create_snat_iplist [split [string map {\"create:\" \"\"} $vsSNATConfig] ,]\n set create_snat_poolname [format \"%s/%s_snatpool\" $app_path $app]\n set create_snat_poolcmd [format \"ltm snatpool %s members replace-all-with { \" $create_snat_poolname]\n foreach ip $create_snat_iplist {\n append create_snat_poolcmd [format \" %s%%%s \" $ip $rd]\n }\n append create_snat_poolcmd \"} \"\n debug [list virtual_server snat create_snat_pool tmsh_create] $create_snat_poolcmd 1\n tmsh::create $create_snat_poolcmd\n append snatcmd [format \" source-address-translation \{ pool %s type snat \}\" $create_snat_poolname]\n }\n default {\n tmsh::get_config /ltm snatpool $vsSNATConfig\n append snatcmd [format \" source-address-translation \{ pool %s type snat \}\" $vsSNATConfig]\n }\n }\n debug [list virtual_server snatcmd] $snatcmd 7\n}\nappend cmd $snatcmd\n\n# Process featureinsertXForwardedFor\nif { $featureinsertXForwardedFor eq \"enabled\"} {\n if { [regexp -nocase {^create:} $vsProfileHTTP] } {\n if { ! [regexp -nocase {insert-xforwarded-for=enabled} $vsProfileHTTP] } {\n debug [list virtual_server featureinsertXForwardedFor append] \"Appending insert-xforwarded-for=enabled to existing HTTP profile customization string\" 5\n append vsProfileHTTP \";insert-xforwarded-for=enabled\"\n } else {\n debug [list virtual_server featureinsertXForwardedFor ignore] \"insert-xforwarded-for=enabled alredy in HTTP profile customization string... doing nothing\" 5\n }\n } else {\n debug [list virtual_server featureinsertXForwardedFor create] [format \"Creating HTTP profile customization string \\"create:insert-xforwarded-for=enabled;defaults-from=%s\\"\" $vsProfileHTTP] 5\n set vsProfileHTTP [format \"create:insert-xforwarded-for=enabled;defaults-from=%s\" $vsProfileHTTP]\n }\n}\n\n# Process the create: option for profiles in the array below.\n# Profiles that we support the \"create:option[=value][,option2[=value2]]\" format for option customization\narray set profile_create_supported {\n \"vsProfileClientProtocol\" { append \"_clientside\" }\n \"vs__ProfileServerProtocol\" { append \"_serverside\"}\n \"vsProfileHTTP\" { type \"http\" append \"\"}\n \"vsProfileOneConnect\" { type \"one-connect\" append \"\"}\n \"vsProfileCompression\" { type \"http-compression\" append \"\"}\n \"vsProfileRequestLogging\" { type \"request-log\" append \"\"}\n \"vsProfileServerSSL\" { type \"server-ssl\" append \"\"}\n \"vsProfileClientSSL\" { type \"client-ssl\" append \"\"}\n}\n\narray set profile_create_defaults {\n \"tcp\" { default \"/Common/tcp\" }\n \"udp\" { default \"/Common/udp\" }\n \"fastl4\" { default \"/Common/fastL4\" }\n \"fasthttp\" { default \"/Common/fasthttp\" }\n \"sctp\" { default \"/Common/sctp\" }\n \"ipother\" { default \"/Common/ipother\" }\n \"http\" { default \"/Common/http\" }\n \"one-connect\" { default \"/Common/oneconnect\" }\n \"http-compression\" { default \"/Common/httpcompression\" }\n \"request-log\" { default \"/Common/request-log\" }\n \"server-ssl\" { default \"/Common/serverssl\" }\n \"client-ssl\" { default \"/Common/clientssl\" }\n}\n\n# Loop through the array\nforeach {profile_var} [array names profile_create_supported] {\n # Setup some base vars\n array unset profile_attr\n array set profile_attr [subst $::profile_create_supported($profile_var)]\n set profile_val [set [subst $profile_var]]\n if { [regexp -nocase {^create:} $profile_val] } {\n set profile_val [string map {\"create:\" \"\"} $profile_val]\n\n # Process the string, check to see the profile type was specified and is valid\n array unset profile_options\n array unset profile_default_array\n array set profile_options [process_kvp_string $profile_val]\n if { ! [info exists profile_options(type)] } {\n if { [info exists profile_attr(type)] } {\n set profile_options(type) $profile_attr(type)\n } else {\n error \"The create string specified for $profile_var needs to include a 'type' option specifying the type of profile to create\"\n }\n }\n\n if { ! [info exists profile_create_defaults($profile_options(type))] } {\n error \"The profile type '$profile_options(type)' specified for $profile_var is not valid\"\n }\n\n # Remove the 'type=XXX;' field from the create string\n set profile_val [string map [list \"type=$profile_options(type);\" \"\"] $profile_val]\n set profile_name [format \"%sprofile%s%s\" $app $profile_options(type) $profile_attr(append)]\n set profile_cmd [format \"ltm profile %s %s/%s \" $profile_options(type) $app_path $profile_name]\n array set profile_default_array [subst $::profile_create_defaults($profile_options(type))]\n set profile_default $profile_default_array(default)\n\n # Create the options portion of the TMSH command\n set profile_option_cmd [process_options_string $profile_val \"profile $profile_options(type)\" $profile_default 1]\n debug [list virtual_server profiles create_handler $profile_var] [format \"%s\" $profile_option_cmd] 7\n\n # Build the final TMSH command\n append profile_cmd $profile_option_cmd\n\n # Replace the APL var with the new profile name\n set [subst $profile_var] [format \"%s/%s\" $app_path $profile_name]\n debug [list virtual_server profiles create_handler $profile_var] [format \"%s=%s\" $profile_var [set [subst $profile_var]]] 1\n\n # Allow run-time substition of the app name\n set profile_cmd [string map [list \"%APP_NAME%\" $app] $profile_cmd]\n debug [list virtual_server profiles create_handler $profile_var tmsh_create] [format \"%s\" $profile_cmd] 1\n tmsh::create $profile_cmd\n }\n}\n\n# Add profiles\nset vsprofiles \" profiles replace-all-with \{ \"\ndebug [list virtual_server profiles] [format \"adding base vsprofiles=%s\" $vsprofiles] 7\n\n# We have to specify context aware profiles first\n# Figure out the correct context to apply protocol profiles\nset clientContext \"all\"\nset serverContext \"all\"\n\nif { [string length $vsProfileClientProtocol] > 0 && [string length $vsProfileServerProtocol] > 0 && $vsProfileClientProtocol ne $vsProfileServerProtocol } {\n debug [list virtual_server profiles protocol] \"got both client and server protocol profiles\" 7\n set clientContext \"clientside\"\n set serverContext \"serverside\"\n}\n\n# Client-side protocol\nif { [string length $vsProfileClientProtocol] > 0 } {\n append vsprofiles [format \" %s \{ context %s \}\" $vsProfileClientProtocol $clientContext]\n debug [list virtual_server profiles protocol] [format \"clientside protocol name=%s context=%s\" $vsProfileClientProtocol $clientContext] 7\n}\n\n# Server-side protocol\nif { [string length $vsProfileServerProtocol] > 0 && $vsProfileClientProtocol ne $vsProfileServerProtocol } {\n append vsprofiles [format \" %s \{ context %s \}\" $vs__ProfileServerProtocol $serverContext]\n debug [list virtual_server profiles protocol] [format \"serverside protocol name=%s context=%s\" $vsProfileServerProtocol $serverContext] 7\n}\n\n\n# Set virtual server profiles we support. The tmsh format expected is:\n# profiles replace-all-with { [ { context [clientside|serverside|all] } ] }\n# To achieve this while re-using generic_add_option() we simply pass the var name with a blank option string\n# Profiles that specify a proxy context can be specified in the vs_profiles_contextual array with the value\n# specifying the proxy context\narray set vs_profiles_contextual {\n \"vsProfileConnectivity\" \"clientside\"\n}\n\narray set vs_profiles {\n \"vsProfileHTTP\" \"\"\n \"vsProfileOneConnect\" \"\"\n \"vsProfileCompression\" \"\"\n \"vsProfileAnalytics\" \"\"\n \"vsProfileRequestLogging\" \"\"\n \"vsProfileServerSSL\" \"\"\n \"vsProfileSecurityDoS\" \"\"\n}\n\n# Handle the 'use-bundled' value for the VS Access Profile\n# The bundler code will\nset bundler_apm_associate 0\nif { $vsProfileAccess eq \"use-bundled\" } {\n set bundler_apm_associate 1\n} else {\n set vs_profiles(vsProfileAccess) \"\"\n}\n\n# Save the base profile string for later use by featureredirectToHTTPS\nif { $featureredirectToHTTPS eq \"enabled\"} {\n set vsprofiles_redirect $vsprofiles\n}\n\n# Client-SSL profile created by iApp\nif { $clientssl == 1 } {\n set vsProfileClientSSL [format \"%s/%s_clientssl\" $app_path $app]\n set vs_profiles(vs__ProfileClientSSL) \"\"\n debug [list virtual_server client_ssl associate_created] [format \"name=%s\" $vsProfileClientSSL] 7\n}\n\n# Client-SSL profile specified via vsProfileClientSSL\nif { $clientssl == 2 || $clientssl == 3} {\n set vs_profiles(vs__ProfileClientSSL) \"\"\n debug [list virtual_server client_ssl associate_existing] [format \"name=%s\" $vsProfileClientSSL] 7\n}\n\n# Process the vs_profiles_contextual array first to make sure profiles that require a proxy\n# context are added first\nforeach {optionvar optioncmd} [array get vs_profiles_contextual] {\n append vsprofiles [generic_add_option [list virtual_server options] [set [subst $optionvar]] \"\" \" %s { context $optioncmd } \" 0]\n}\n\n# Process the vs_profiles array to build the profiles command\nforeach {optionvar optioncmd} [array get vs_profiles] {\n append vsprofiles [generic_add_option [list virtual_server options] [set [subst $optionvar]] $optioncmd \"\" 0]\n}\n\nif { [string length $vsAdvProfiles] > 0 } {\n debug [list virtual_server adv_options] \"processing advanced profile string\" 7\n append vsprofiles [format \" %s\" [generic_add_option [list virtual_server adv_profiles] $vsAdvProfiles \"\" \"%s\" 1]]\n}\n\nappend vsprofiles \" \}\"\ndebug [list virtual_server profiles cmd] $vsprofiles 7\n\n# Add the profile string to the TMSH command\nappend cmd $vsprofiles\n\n# Process the $vsAdvPolicies option\nif { [string length $vsAdvPolicies] > 0 } {\n debug [list virtual_server adv_policies] \"processing advanced policies string\" 7\n # Add the polcies string to the TMSH command\n set vspolicies [format \" policies replace-all-with \{ %s \} \" [generic_add_option [list virtual_server adv_policies] $vsAdvPolicies \"\" \"%s\" 1]]\n debug [list virtual_server adv_policies cmd] $vspolicies 7\n append cmd $vspolicies\n}\n\n# Create the virtual server\nset stats_vs 0\nif { $pool__addr ne \"255.255.255.254\" } {\n debug [list virtual_server tmsh_create] $cmd 1\n tmsh::create $cmd\n set stats_vs 1\n\n # Process the additional listeners table\n set redirect_listeners []\n set vs_origcmd $cmd\n\n debug [list virtual_server add_listeners] [format \"listenerCount=%s\" [llength $vsListeners]] 7\n\n set listenerIdx 0\n set listenerRedirOverlap 0\n foreach listenerRow $vsListeners {\n debug [list virtual_server add_listeners $listenerIdx] [format \"listenerRow=%s\" $listenerRow] 9\n\n set listenerMap [list]\n array unset listenerColumn\n array set listenerColumn {}\n table_row_to_array $listenerRow listenerColumn ::table_defaults(Listeners)\n debug [list virtual_server add_listeners $listenerIdx table_row_to_array return] [array get listenerColumn] 7\n\n set listenerColumn(Listener) [lindex $listenerColumn(Listener) 0]\n\n if { [string length $listenerColumn(Listener)] == 0 } {\n incr listenerIdx\n continue\n }\n\n set listenerColumn(Destination) [lindex $listenerColumn(Destination) 0]\n array unset listenerDestOptions\n array set listenerDestOptions [process_kvp_string $listenerColumn(Destination)]\n debug [list virtual_server add_listeners $listenerIdx dest_options] [array get listenerDestOptions] 7\n regexp {^(.*):.$} $listenerColumn(Listener) --> listenerColumn(Addr) listenerColumn(Port)\n\n # If this row had the 'redirect' destination specified save it for later and skip this row\n if { [info exists listenerDestOptions(redirect)] } {\n if { $featureredirectToHTTPS != \"enabled\" } {\n error \"To use the 'redirect' Destination for Listener $listenerColumn(Listener) featureredirectToHTTPS but be enabled\"\n }\n\n debug [list virtual_server add_listeners $listenerIdx redirect] $listenerColumn(Listener) 7\n lappend redirectlisteners [list $listenerColumn(Listener) [format \"%s%sredirect%s\" $addl_vs_basename $listenerIdx $listenerColumn(Port)]]\n incr listenerIdx\n continue\n }\n\n # If this row had the 'nossl,noclientssl,noserverssl' destination specified do not attach SSL profiles to the virtual server\n if { [info exists listenerDestOptions(nossl)] } {\n debug [list virtual_server add_listeners $listenerIdx nossl] $listenerColumn(Listener) 7\n lappend listenerMap \"\\"$vsProfileClientSSL\\"\" \"\"\n lappend listenerMap \"\\"$vsProfileServerSSL\\"\" \"\"\n set listenerColumn(Destination) [string map [list \"\;nossl\" \"\"] $listenerColumn(Destination)]\n }\n if { [info exists listenerDestOptions(noclientssl)] } {\n debug [list virtual_server add_listeners $listenerIdx noclientssl] $listenerColumn(Listener) 7\n lappend listenerMap \"\\"$vsProfileClientSSL\\"\" \"\"\n set listenerColumn(Destination) [string map [list \"\;noclientssl\" \"\"] $listenerColumn(Destination)]\n }\n if { [info exists listenerDestOptions(noserverssl)] } {\n debug [list virtual_server add_listeners $listenerIdx noserverssl] $listenerColumn(Listener) 7\n lappend listenerMap \"\\"$vsProfileServerSSL\\"\" \"\"\n set listenerColumn(Destination) [string map [list \"\;noserverssl\" \"\"] $listenerColumn(Destination)]\n }\n\n if { $listenerColumn(Destination) eq \"\" || [string tolower $listenerColumn(Destination)] eq \"default\"} {\n set listenerColumn(Pool) $default_poolname\n } elseif { [string first \"/\" $listenerColumn(Destination)] >= 0 } {\n set listenerColumn(Pool) $listenerColumn(Destination)\n } else {\n if { ![info exist poolNames($listenerColumn(Destination))] } {\n error \"The listener $listenerColumn(Listener) referenced a destination pool index $listenerColumn(Destination) which does not exist\"\n }\n set listenerColumn(Pool) $poolNames($listenerColumn(Destination))\n }\n\n # Setup our new tmsh command string\n set listenerColumn(Name) [format \"%s%s_%s\" $addl_vs_basename $listenerIdx $listenerColumn(Port)]\n set listenerColumn(Dest) [get_dest_str $listenerColumn(Addr) $listenerColumn(Port)]\n\n # Check to see if there is a potential overlap with the featureredirectToHTTPS functionality\n if { $listenerColumn(Dest) eq [format \"%s:80\" $vs_dest_addr] } {\n set listenerRedirOverlap 1\n }\n\n debug [list virtual_server add_listeners $listenerIdx] [format \"name=%s addr=%s port=%s dest=%s\" $listenerColumn(Name) $listenerColumn(Addr) $listenerColumn(Port) $listenerColumn(Dest)] 7\n set vs_listener_cmd [string map [list $vsName $listenerColumn(Name) \"$vs_dest_addr:$poolport\" $listenerColumn(Dest) $default_pool_name $listenerColumn(Pool)] $vs_origcmd]\n # If our listener address is IPv6 we need to fixup the VS source filter and destination mask\n if { [is_ipv6 $listenerColumn(Addr)] } {\n set vs_listener_cmd [string map [list $vsSourceAddress [format \"::%%%s/0\" $rd]] $vs_listener_cmd]\n set vs_listener_cmd [string map [list $poolmask [format \"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff\" $poolmask]] $vs_listener_cmd]\n }\n\n set vs_listener_cmd [string map $listenerMap $vs_listener_cmd]\n\n debug [list virtual_server add_listeners $listenerIdx tmsh_create] $vs_listener_cmd 1\n tmsh::create $vs_listener_cmd\n incr listenerIdx\n }\n\n if { !$listenerRedirOverlap } {\n # Add a listener for the default virtual server redirect\n lappend redirect_listeners [list [format \"%s:80\" $vs_dest_addr] [format \"%s_default_vs_redirect_80\" $app]]\n }\n} else {\n debug [list virtual_server skip_create] \"found 255.255.255.254 as pooladdr, skipping creation\" 2\n}\n\n# Call the custom_extensions_after_vs proc to allow site-specific customizations\ncustom_extensions_after_vs\n\n# Create an additional virtual server on port 80 for featureredirectToHTTPS\nif { $featureredirectToHTTPS eq \"enabled\" && $pooladdr ne \"255.255.255.254\" } {\n set redirect_listener_idx 0\n debug [list virtual_server feature__redirectToHTTPS redirect_listeners] $redirect_listeners 7\n foreach redirect_listener_list $redirect_listeners {\n set redirect_listener [lindex $redirect_listener_list 0]\n set redirect_listener_name [lindex $redirect_listener_list 1]\n debug [list virtual_server featureredirectToHTTPS $redirect_listener_idx] [format \"dest=%s name=%s\" $redirect_listener $redirect_listener_name] 5\n\n regexp {^(.):.$} $redirect_listener --> redirect_listener_addr redirect_listener_port\n\n set redirect_listener_dest [get_dest_str $redirect_listener_addr $redirect_listener_port]\n debug [list virtual_server featureredirectToHTTPS $redirect_listener_idx] [format \"creating redirect virtual server on %s\" $redirect_listener_dest] 5\n\n array set redirect_listener_options {\n \"redirect_listener_mask\" \"mask\"\n \"redirect_listener_src\" \"source\"\n \"vsIpProtocol\" \"ip-protocol\"\n }\n\n if { $featureeasyL4Firewall == \"enabled\" } {\n set redirect_listener_options(fw_name) \"fw-enforced-policy\"\n debug [list virtual_server featureredirectToHTTPS $redirect_listener_idx fw_check] [format \"featureeasyL4Firewall is enabled, using %s\" $redirect_listener_options(fw_name)] 5\n }\n\n if { [is_ipv6 $redirect_listener_addr] } {\n set redirect_listener_src [format \"::0.0.0.0%%%s/0\" $rd]\n set redirect_listener_mask \"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff\"\n } else {\n set redirect_listener_src $vsSourceAddress\n set redirect_listener_mask \"255.255.255.255\"\n }\n\n set redirect_listener_cmd [format \"ltm virtual %s/%s destination %s \" $app_path $redirect_listener_name $redirect_listener_dest]\n\n # Process the vs_options array\n foreach {optionvar optioncmd} [array get redirect_listener_options] {\n append redirect_listener_cmd [generic_add_option [list virtual_server feature__redirectToHTTPS $redirect_listener_idx options] [set [subst $optionvar]] $optioncmd \"\" 0]\n }\n\n # The HSTS spec recommends that when redirected a 301 Redirect is used, rather than a 302 like _sys_https_redirect uses\n if { [string match \"enabled\" $featuresecurityEnableHSTS] } {\n debug [list virtual_server feature__redirectToHTTPS $redirect_listener_idx hsts_check] [format \"featuresecurityEnableHSTS is enabled, using %s\" $hstsredirectrule] 5\n append redirect_listener_cmd \" rules { $hstsredirectrule } \"\n } else {\n append redirect_listener_cmd \" rules { /Common/_sys_https_redirect } \"\n }\n\n append redirect_listener_cmd $vsprofiles_redirect\n append redirect_listener_cmd [generic_add_option [list virtual_server featureredirectToHTTPS $redirect_listener_idx options] $vsProfileHTTP \"\" \"\" 0]\n append redirect_listener_cmd \" \}\"\n debug [list virtual_server featureredirectToHTTPS $redirect_listener_idx tmsh_create] $redirect_listener_cmd 1\n tmsh::create $redirect_listener_cmd\n incr redirect_listener_idx\n }\n}\n\n# Create iCall statistics publisher\ndebug [list stats] [format \"mode=%s iappappStats=%s\" $mode $iappappStats] 7\nif { ($iappappStats eq \"enabled\") } {\n # Create the iCall stats publisher\n debug [list stats] \"creating icall stats publisher\" 7\n # START EMBEDDED ICALL SCRIPT\n set icall_script_tmpl {\n# Copyright (c) 2017 F5 Networks, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset app %APP_NAME%\nset partition %PARTITION%\nset aso \"sys.application.service /$partition/${app}.app/$app\"\n\nset enabled_stats {}\n\nset pool_enabled %POOL_ENABLED%\nset vs_enabled %VS_ENABLED%\nset http_enabled %HTTP_ENABLED%\nset ssl_enabled %SSL_ENABLED%\n\nif { $pool_enabled } { lappend enabled_stats \"pool\" }\nif { $vs_enabled } { lappend enabled_stats \"virtual\" }\nif { $http_enabled } { lappend enabled_stats \"http\" }\nif { $ssl_enabled } { lappend enabled_stats \"ssl\" }\n\nset virtual_path \"ltm virtual /$partition/${app}.app/%VS_NAME%\"\nset http_path \"ltm profile http %HTTP_PROFILE%\"\nset ssl_path \"ltm profile client-ssl %SSL_PROFILE%\"\nset pool_path \"ltm pool /$partition/${app}.app/%POOL_NAME%\"\n\n#puts \"app=$app\"\n#puts \"partition=$partition\"\n#puts \"aso=$aso\"\n#puts \"virtual_path=$virtual_path\"\n#puts \"http_path=$http_path\"\n#puts \"pool_path=$pool_path\"\n\n# these lists represent strings taken from \"show ... field-fmt\"\nset http_stats {\n get-reqs number-reqs post-reqs resp-2xx-cnt resp-3xx-cnt resp-4xx-cnt\n resp-5xx-cnt resp-bucket-1k resp-bucket-2m resp-bucket-4k resp-bucket-16k\n resp-bucket-32k resp-bucket-64k resp-bucket-128k resp-bucket-512k\n resp-bucket-large\n}\nset ssl_stats {\n common.cipher-uses.adh-keyxchg common.cipher-uses.aes-bulk common.cipher-uses.aes-gcm-bulk\n common.cipher-uses.des-bulk common.cipher-uses.dh-rsa-keyxchg common.cipher-uses.dhe-dss-keyxchg\n common.cipher-uses.ecdh-ecdsa-keyxchg common.cipher-uses.ecdh-rsa-keyxchg\n common.cipher-uses.ecdhe-ecdsa-keyxchg common.cipher-uses.ecdhe-rsa-keyxchg\n common.cipher-uses.edh-rsa-keyxchg common.cipher-uses.idea-bulk common.cipher-uses.md5-digest\n common.cipher-uses.null-bulk common.cipher-uses.null-digest common.cipher-uses.rc2-bulk\n common.cipher-uses.rc4-bulk common.cipher-uses.rsa-keyxchg common.cipher-uses.sha-digest\n common.cur-compat-conns common.cur-conns common.cur-native-conns common.decrypted-bytes-in\n common.decrypted-bytes-out common.encrypted-bytes-in common.encrypted-bytes-out\n common.fatal-alerts common.fully-hw-accelerated-conns common.handshake-failures\n common.insecure-handshake-accepts common.insecure-handshake-rejects common.insecure-renegotiation-rejects\n common.max-compat-conns common.max-conns common.max-native-conns\n common.non-hw-accelerated-conns common.partially-hw-accelerated-conns\n common.protocol-uses.dtlsv1 common.protocol-uses.sslv2 common.protocol-uses.sslv3\n common.protocol-uses.tlsv1 common.protocol-uses.tlsv1-1 common.protocol-uses.tlsv1-2\n common.secure-handshakes common.tot-compat-conns common.tot-native-conns\n}\nset virtual_stats {\n clientside.bits-in clientside.bits-out clientside.cur-conns\n clientside.max-conns clientside.pkts-in clientside.pkts-out\n clientside.tot-conns status.availability-state status.enabled-state\n status.status-reason\n}\nset pool_stats {\n active-member-cnt serverside.bits-in serverside.bits-out\n serverside.cur-conns serverside.max-conns serverside.pkts-in\n serverside.pkts-out serverside.tot-conns\n}\n\n# loop over each type of object we want to look at, building the name\n# of the path and the stats for it as needed\nforeach type $enabled_stats {\n # making this its own variable made the Tcl validator stop throwing\n # a warning - though it should be fine to move it inline w/its use\n set path [set ${type}_path]\n set objs [tmsh::get_status $path raw]\n if { [llength $objs] == 0 } {\n puts \"no object found for: $type\"\n continue\n }\n set obj [lindex $objs 0]\n #puts \"obj=$obj\"\n foreach stat [set ${type}_stats] {\n #puts \" stat=$stat\"\n set value [tmsh::get_field_value $obj $stat]\n #puts \" aso=$aso type=$type stat=$stat value=$value\"\n # associate the iStat with the app service\n istats::set \"$aso string $stat\" $value\n }\n}\n\n# Set an additional iStat for the size of the pool, as this is not\n# stored as a stat but is nice to have when looking at pool health.\n# Do that here each run through rather than in the iApp because if\n# an external pool is used or strictness was off the size of the\n# pool can change and the iApp wouldn't run to adjust the size\nif { $pool_enabled } {\n set pools [tmsh::get_config $pool_path]\n set numpools [llength $pools]\n set pool_size 0\n #puts \"numpools=$numpools pool_size=$pool_size\"\n if { $numpools == 1 } {\n # safe to assume only obj in list is our pool now. grab its size\n # and move along\n set pool [lindex $pools 0]\n set fdx 0\n set fields [tmsh::get_field_names nested $pool]\n set field_count [llength $fields]\n while { $fdx < $field_count } {\n set field [lindex $fields $fdx]\n #puts \"field=$field\"\n incr fdx\n if { $field eq \"members\"} {\n set pool_size [llength [tmsh::get_field_value $pool members]]\n #puts \"new pool_size=$pool_size\"\n }\n }\n }\n istats::set \"$aso string total-member-cnt\" $pool_size\n}\n#set tmc [istats::get $aso total-member-cnt]\n#puts \"tmc=$tmc\"\n }; # END EMBEDDED ICALL SCRIPT\n\n set stats_pool 0\n if { $poolCount > 0 && $default_pool_name ne \"\" } {\n set stats_pool 1\n }\n\n if { [expr {$featurestatsHTTP eq \"enabled\" || $featurestatsHTTP eq \"auto\"}] && [string length $vsProfileHTTP] > 0 } {\n debug [list stats feature_statsHTTP] \"enabling HTTP stats\" 7\n set featurestatsHTTP 1\n } else {\n set featurestatsHTTP 0\n }\n\n if { [expr {$featurestatsTLS eq \"enabled\" || $featurestatsTLS eq \"auto\"}] && [string length $vsProfileClientSSL] > 0 } {\n debug [list stats feature_statsTLS] \"enabling TLS stats\" 7\n set featurestatsTLS 1\n } else {\n set featurestatsTLS 0\n }\n\n # used to fill in variables within iCall script\n set script_map [list %APP_NAME% $app \\n %VS_NAME% $vsName \\n %POOL_NAME% $default_pool_name \\n %PARTITION% $partition \\n %POOL_ENABLED% $stats_pool \\n %VS_ENABLED% $stats_vs \\n %HTTP_ENABLED% $featurestatsHTTP \\n %HTTP_PROFILE% [format \"%s\" $vsProfileHTTP] \\n %SSL_ENABLED% $feature__statsTLS \\n %SSL_PROFILE% [format \"%s\" $vsProfileClientSSL] ]\n\n set icall_script_src [string map $script_map $icall_script_tmpl]\n debug [list stats icall_src] $icall_script_src 10\n\n debug [list stats icall_script tmsh_create] \"publish_stats script\" 1\n tmsh::create sys icall script publish_stats definition \{ $icall_script_src \}\n debug [list stats icall_handler tmsh_create] \"iCall handler\" 1\n tmsh::create sys icall handler periodic publish_stats interval 60 script publish_stats\n}\n\n# Process deferred deployment bundled ASM policies\nset bundler_asm_policies [get_items_starting_with \"asm:\" $vsBundledItems]\nset bundler_apm_policies [get_items_starting_with \"apm:\" $vs__BundledItems]\nset bundler_asm_deploy []\nset bundler_apm_deploy []\nset bundler_all_deploy 0\nset postdeploy_final_state 1\n\n# First perform all our checks\nif { [llength $bundler_asm_policies] > 0 } {\n if { ![is_provisioned asm]} {\n error \"A bundled ASM policy was selected, however, the ASM module is not provisioned on this device\"\n }\n\n if { [string length $vsProfileHTTP] == 0 } {\n error \"A HTTP Profile is required to use ASM functionality\"\n }\n}\n\nif { [llength $bundler_apm_policies] > 1 } {\n error \"Only one bundled APM policy may be selected for deployment\"\n}\n\nif { [llength $bundler_apm_policies] == 1 } {\n if { ![is_provisioned apm]} {\n error \"A bundled APM policy was selected, however, the APM module is not provisioned on this device\"\n }\n if { [string length $vsProfileHTTP] == 0 } {\n error \"A HTTP Profile is required to use APM functionality\"\n }\n}\n\n# Process deferred deployment bundled ASM policies\nset bundler_asm_mode 0\nif { [llength $bundler_asm_policies] > 0 } {\n foreach bundled_asm $bundler_asm_policies {\n set bundled_asm_isurl 0\n if { [string match \"asm:url=\" $bundled_asm] } {\n set bundled_asm_isurl 1\n set bundled_asm_url [url_subst $bundled_asm]\n regexp {^./(.).xml} $bundled_asm_url -> bundled_asm_stripped\n set bundled_asm_filename [format \"/var/tmp/appsvcsasm%s%s%s.xml\" $::app $bundled_asm_stripped $bundler_timestamp]\n } else {\n set bundled_asm_stripped [string map {\"asm:\" \"\"} $bundled_asm]\n set bundled_asm_filename [format \"/var/tmp/appsvcsasm%s%s%s.xml\" $::app $bundled_asm_stripped $bundler_timestamp]\n }\n\n debug [list bundler asm check_preserve] [format \"%s %s\" $bundled_asm [string match $bundled_asm* [get_items_starting_with \"asm:\" [get_var vsBundledItems]]]] 7\n if { $newdeploy || \\n [expr { $redeploy && [string match redeploy $iapp__asmDeployMode]}] || \\n [expr { $redeploy && [string match $bundled_asm [get_items_starting_with \"asm:\" [get_var vs__BundledItems]]]}] == 0 } {\n debug [list bundler asm deploy] $bundled_asm 5\n set bundler_asm_mode 1\n\n if { $bundled_asm_isurl } {\n curl_save_file $bundled_asm_url $bundled_asm_filename\n } else {\n if {! [info exists bundler_objects($bundled_asm)] } {\n error \"A bundled ASM policy named '$bundled_asm' was not found in the template\"\n }\n\n set outfile [open $bundled_asm_filename w]\n puts -nonewline $outfile [::base64::decode $bundler_data($bundled_asm)]\n close $outfile\n }\n } else {\n set bundler_asm_mode 2\n set savecmd [format \"asm policy %s/%s min-xml-file %s\" $app_path $bundled_asm_stripped $bundled_asm_filename]\n debug [list bundler asm preserve] [format \"preserving existing policy... save to %s\" $bundled_asm_filename] 5\n debug [list bundler asm preserve tmsh_save] $savecmd 1\n tmsh::save $savecmd\n }\n lappend bundler_asm_deploy $bundled_asm_stripped\n incr bundler_all_deploy\n }\n}\n\n# Process deferred deployment bundled APM policies\nset bundler_apm_mode 0\n\nif { [llength $bundler_apm_policies] == 1 } {\n set bundled_apm [lindex $bundler_apm_policies 0]\n\n set bundled_apm_isurl 0\n if { [string match \"apm:url=\" $bundled_apm] } {\n set bundled_apm_isurl 1\n set bundled_apm_url [url_subst $bundled_apm]\n regexp {^./(.).tar.gz} $bundled_apm_url -> bundled_apm_stripped\n set bundled_apm_filename [format \"/var/tmp/appsvcsapm%s%s%s.tar.gz\" $::app $bundled_apm_stripped $bundler_timestamp]\n } else {\n set bundled_apm_stripped [string map {\"apm:\" \"\"} $bundled_apm]\n set bundled_apm_filename [format \"/var/tmp/appsvcsapm%s%s%s.tar.gz\" $::app $bundled_apm_stripped $bundler_timestamp]\n }\n\n debug [list bundler apm check_preserve] [format \"%s %s\" $bundled_apm [string match $bundled_apm [get_items_starting_with \"apm:\" [get_var vsBundledItems]]]] 7\n if { $newdeploy || \\n [expr { $redeploy && [string match redeploy* $iappapmDeployMode]}] || \\n [expr { $redeploy && [llength [get_items_starting_with \"apm:\" [get_var vsBundledItems]]]}] == 0 } {\n debug [list bundler apm deploy] $bundled_apm 5\n set bundler_apm_mode 1\n\n if { $bundled_apm_isurl } {\n curl_save_file $bundled_apm_url $bundled_apm_filename\n } else {\n if {! [info exists bundler_objects($bundled_apm)] } {\n error \"A bundled APM policy named '$bundled_apm' was not found in the template\"\n }\n\n debug [list bundler apm deploy version_check] [format \"bundled_version=%s system_version=%s\" $bundler_objects($bundled_apm) $version_info(version)] 7\n if {! [string match $bundler_objects($bundled_apm) $version_info(version)] } {\n error \"The bundled APM policy '$bundled_apm' requires BIG-IP version $bundler_objects($bundled_apm). This system is running version $version_info(version)\"\n }\n\n set outfile [open $bundled_apm_filename w]\n fconfigure $outfile -translation binary\n puts -nonewline $outfile [::base64::decode $bundler_data($bundled_apm)]\n close $outfile\n }\n } else {\n debug [list bundler apm preserve] $bundled_apm 5\n set bundler_apm_mode 2\n set bundled_apm_export_filename [format \"appsvcsapm%s%s%s\" $::app $bundled_apm_stripped $bundler_timestamp]\n switch -glob $version_info(version) {\n 11.5 {\n #ng_export [] FIXTHIS\n set bundler_apm_exportcmd [format \"/usr/bin/env REMOTEUSER=admin USER=admin /usr/bin/ng_export %s.app/bundled_apm_policy %s %s\" $app $bundled_apm_export_filename $partition]\n set bundler_apm_renamecmd [format \"mv /tmp/%s.conf.tar.gz %s\" $bundled_apm_export_filename $bundled_apm_filename]\n }\n 11.6 {\n set bundler_apm_exportcmd [format \"/usr/bin/env REMOTEUSER=admin USER=admin /usr/bin/ng_export %s.app/bundled_apm_policy %s -p %s\" $app $bundled_apm_export_filename $partition]\n set bundler_apm_renamecmd [format \"mv /tmp/profile-%s.conf.tar.gz %s\" $bundled_apm_export_filename $bundled_apm_filename]\n }\n 12. {\n set bundler_apm_exportcmd [format \"/usr/bin/env REMOTEUSER=admin USER=admin /usr/bin/ng_export %s.app/bundled_apm_policy %s -p %s\" $app $bundled_apm_export_filename $partition]\n set bundler_apm_renamecmd [format \"mv /tmp/profile-%s.conf.tar.gz %s\" $bundled_apm_export_filename $bundled_apm_filename]\n }\n default { error \"The TMOS version running on this device does not support the preserve APM deployment modes\" }\n }\n debug [list bundler apm preserve] [format \"preserving existing policy... save to %s\" $bundled_apm_filename] 5\n debug [list bundler apm preserve export_cmd] $bundler_apm_exportcmd 1\n debug [list bundler apm preserve rename_cmd] $bundler_apm_renamecmd 1\n eval exec $bundler_apm_exportcmd\n eval exec $bundler_apm_renamecmd\n\n }\n lappend bundler_apm_deploy $bundled_apm_stripped\n incr bundler_all_deploy\n}\n\nif { $bundler_all_deploy } {\n set postdeploy_final_state 0\n set bundler_enablevs 0\n if { [string match *\-block $iappasmDeployMode] || [string match \-block $iapp__apmDeployMode] } {\n set bundler_vs_cmd [format \"ltm virtual %s/%s disabled\" $app_path $vsName]\n debug [list bundler check_deploy_mode] \"iapp(asm|apm)DeployMode specified block mode, disabling virtual server\" 5\n debug [list bundler check_deploy_mode tmsh_modify] $bundler_vs_cmd 1\n tmsh::modify $bundler_vs_cmd\n set bundler_enablevs 1\n }\n\n set bundler_icall_tmpl {\nsys icall script %APP_PATH%/postdeploy_bundler {\n app-service %APP_PATH%/%APP_NAME%\n definition {\nset app %APP_NAME%\nset app_path %APP_PATH%\nset partition %PARTITION%\nset vs_name %VS_NAME%\nset enable_vs %ENABLEVS%\nset asm_policy_list [list %ASMPOLICYLIST%]\nset apm_policy_list [list %APMPOLICYLIST%]\nset all_policy_list [list %ASMPOLICYLIST% %APMPOLICYLIST%]\nset timestamp %TIMESTAMP%\nset apm_import_cmd [list %APMIMPORTCMD%]\nset newdeploy %NEWDEPLOY%\nset redeploy %REDEPLOY%\nset apm_associate %APMASSOCIATE%\nset asm_mode %ASMMODE%\nset apm_mode %APMMODE%\nset strict_updates %STRICTUPDATES%\n\nset aso \"/$partition/${app}.app/$app\"\nset iaso [format \"sys.application.service %s\" $aso]\nset logprefix \"\[appsvcs_postdeploy_bundler\]\[$app\]\"\n\nset systemTime [clock seconds]\nputs \"$logprefix Starting at [clock format $systemTime -format %D] [clock format $systemTime -format %H:%M:%S]\"\n\nset file_found 0\nforeach policy $asm_policy_list {\n\tset policy_filename [format \"/var/tmp/appsvcsasm%s%s%s.xml\" $app $policy $timestamp]\n\tputs \"$logprefix Looking for file '$policy_filename'...\"\n\tif { [file exists $policy_filename] } {\n\t\tputs \"$logprefix Found file matching policy '$policy' at '$policy_filename'\"\n\t\tincr file_found\n\t}\n}\n\nforeach policy $apm_policy_list {\n\tset policy_filename [format \"/var/tmp/appsvcsapm%s%s%s.tar.gz\" $app $policy $timestamp]\n\n\tputs \"$logprefix Looking for file '$policy_filename'...\"\n\tif { [file exists $policy_filename] } {\n\t\tputs \"$logprefix Found file matching policy '$policy' at '$policy_filename'\"\n\t\tincr file_found\n\t}\n}\n\nputs \"$logprefix [llength $all_policy_list] policies selected for deployment, $file_found policy files found\"\n\nif { $file_found != [llength $all_policy_list] } {\n\t\tputs \"$logprefix All policy files were not found, exiting (this is normal on the secondary device)\"\n\t\texit 0\n}\n\ntmsh::cd $app_path\nputs \"$logprefix Setting iCall handler to inactive...\"\ntmsh::modify sys application service $aso strict-updates disabled\ntmsh::modify sys icall handler periodic postdeploy_bundler status inactive\n\nistats::set [format \"%s string deploy.postdeploy_bundler\" $iaso] \"ASM_IN_PROGRESS\"\nforeach policy $asm_policy_list {\n\tistats::set [format \"%s string deploy.postdeploy_bundler.asm.%s\" $iaso $policy] \"DEPLOY_IN_PROGRESS\"\n\tset policy_filename [format \"/var/tmp/appsvcsasm%s%s%s.xml\" $app $policy $timestamp]\n\tputs \"$logprefix Loading ASM policy from $policy_filename to ASO $aso...\"\n\ttmsh::load asm policy $policy file $policy_filename overwrite\n\ttmsh::modify asm policy $policy app-service $aso\n\n\tputs \"$logprefix Setting ASM policy $app_path/$policy active...\"\n\ttmsh::modify asm policy $policy active\n\n\tputs \"$logprefix Deleting file $policy_filename...\"\n\tfile delete $policy_filename\n\tistats::set [format \"%s string deploy.postdeploy_bundler.asm.%s\" $iaso $policy] \"DEPLOY_COMPLETE\"\n\n}\nistats::set [format \"%s string deploy.postdeploy_bundler\" $iaso] \"ASM_COMPLETE\"\n\nistats::set [format \"%s string deploy.postdeploy_bundler\" $iaso] \"APM_IN_PROGRESS\"\nset apm_apply 0\nforeach policy $apm_policy_list {\n\tistats::set [format \"%s string deploy.postdeploy_bundler.apm.%s\" $iaso $policy] \"DEPLOY_IN_PROGRESS\"\n\tset policy_filename [format \"/var/tmp/appsvcsapm%s%s%s.tar.gz\" $app $policy $timestamp]\n\n\tif { $redeploy && $apm_mode == 2 } {\n\t\tcatch { tmsh::modify [format \"ltm virtual %s/%s profiles delete \{ %s/bundled_apm_policy \{ \} \}\" $app_path $vs_name $app_path]\n\t\t\t\ttmsh::delete apm profile access bundled_apm_policy\n\t\t\t\ttmsh::delete apm policy access-policy bundled_apm_policy }\n\t}\n\n\tputs \"$logprefix Loading APM policy from $policy_filename to ASO $aso...\"\n\teval exec $apm_import_cmd 2>&1\n\n\t# Iterate through all the create APM objects and set the ASO\n\tforeach {apm_obj} [tmsh::get_config /apm] {\n\t\tset apm_objname [lrange $apm_obj 0 end-1]\n\t\ttmsh::modify [format \"%s app-service %s\" $apm_objname $aso]\n\t}\n\n\tputs \"$logprefix Applying APM policy $app_path/bundled_apm_policy...\"\n\t# We need to sleep outside of scriptd to allow TMM to commit the APM config\n\t# Credit to F5 iApp team/CTX VDI iApp for finding this\n set fn [format \"/var/tmp/appsvcsapmapply%s.sh\" $app]\n catch {\n set fh [open $fn w]\n puts $fh \"sleep 5\"\n puts $fh [format \"tmsh modify apm profile access %s/bundled_apm_policy generation-action increment\" $app_path]\n if { $enable_vs } {\n \tset apm_apply 1\n\t\t\tputs \"$logprefix Re-enabling Virtual Server after apply operation completes (block modifier was specified in deployment mode)\"\n \tputs $fh [format \"tmsh modify ltm virtual %s/%s enabled\" $app_path $vs_name]\n }\n close $fh\n exec chmod 777 $fn\n exec $fn &\n } {}\n\n\tif { $apm_associate } {\n\t\tputs \"$logprefix Associating APM policy with VS (use-bundled value specified for vs__ProfileAccess)\"\n\t\ttmsh::modify [format \"ltm virtual %s/%s profiles add \{ %s/bundled_apm_policy \{ \} \}\" $app_path $vs_name $app_path]\n\t}\n\n\tputs \"$logprefix Deleting file $policy_filename...\"\n\tfile delete $policy_filename\n\tistats::set [format \"%s string deploy.postdeploy_bundler.apm.%s\" $iaso $policy] \"DEPLOY_COMPLETE\"\n}\nistats::set [format \"%s string deploy.postdeploy_bundler\" $iaso] \"APM_COMPLETE\"\n\nistats::set [format \"%s string deploy.postdeploy_bundler\" $iaso] \"DEFERRED_CMDS_IN_PROGRESS\"\n%DEFERREDCMDS%\nistats::set [format \"%s string deploy.postdeploy_bundler\" $iaso] \"DEFERRED_CMDS_COMPLETE\"\n\nputs \"$logprefix Bundled policy deployment completed\"\n\nif { $enable_vs && ! $apm_apply} {\n\tputs \"$logprefix Re-enabling Virtual Server (block modifier was specified in deployment mode)\"\n\ttmsh::modify [format \"ltm virtual %s/%s enabled\" $app_path $vs_name]\n}\n\n# puts \"$logprefix Delaying to ensure completion...\"\n# after 10000\n\nset dellist {}\nlappend dellist [format \"/var/tmp/appsvcsapmapply%s.sh\" $app]\n# lappend dellist [format \"/var/tmp/appsvcspostdeploy%s.conf\" $app]\n# lappend dellist [format \"/var/tmp/appsvcs_loadpostdeploy%s.sh\" $app]\n\nputs \"$logprefix Cleaning up...\"\nforeach df $dellist {\n\tif { [file exists $df] } {\n\t\tputs \"$logprefix Deleting file '$df'\"\n\t\tfile delete $df\n\t}\n}\n\ntmsh::delete sys icall handler periodic postdeploy_bundler\ntmsh::modify sys icall handler periodic postdeploy_final first-occurrence now status active\nif { $strict_updates eq \"enabled\" } {\n\ttmsh::modify sys application service $aso strict-updates enabled\n}\n\nset systemTime [clock seconds]\nputs \"$logprefix Finished at [clock format $systemTime -format %D] [clock format $systemTime -format %H:%M:%S]\"\nistats::set [format \"%s string deploy.postdeploy_bundler\" $iaso] \"FINISHED\"\n }\n description none\n events none\n}\n\nsys icall handler periodic %APP_PATH%/postdeploy_bundler {\n app-service %APP_PATH%/%APP_NAME%\n first-occurrence %ICALLTIME%\n interval 3000\n last-occurrence now+10m\n script %APP_PATH%/postdeploy_bundler\n status active\n}\n };\n\n set bundler_apm_importcmd \"\"\n if { [llength $bundler_apm_policies] == 1 } {\n switch -glob $version_info(version) {\n 11.5 {\n #ng_export []\n set bundler_apm_importcmd [format \"/usr/bin/ng_import %s %s.app/bundled_apm_policy %s\" $bundled_apm_filename $app $partition]\n }\n 11.6 {\n #ng_import [-s] <templatefile.conf.tar[.gz]> [-p|-partition ]\n set bundler_apm_importcmd [format \"/usr/bin/ng_import %s %s.app/bundled_apm_policy -p %s\" $bundled_apm_filename $app $partition]\n }\n 12. {\n #ng_import [-s] <templatefile.conf.tar[.gz]> [-p|-partition ]\n set bundler_apm_importcmd [format \"/usr/bin/ng_import %s %s.app/bundled_apm_policy -p %s\" $bundled_apm_filename $app $partition]\n }\n default { error \"The TMOS version running on this device does not support the preserve APM deployment modes\" }\n }\n }\n\n set bundler_icall_time [clock format [expr {[clock seconds] + $::POSTDEPLOY_DELAY}] -format {%Y-%m-%d:%H:%M:%S}]\n set bundler_script_map [list %APP_NAME% $::app \\n %APP_PATH% $::app_path \\n %VS_NAME% $::vsName \\n %PARTITION% $::partition \\n %ENABLEVS% $bundler_enablevs \\n %ASMPOLICYLIST% $bundler_asm_deploy \\n %APMPOLICYLIST% $bundler_apm_deploy \\n %TIMESTAMP% $bundler_timestamp \\n %APMIMPORTCMD% $bundler_apm_importcmd \\n %ICALLTIME% $bundler_icall_time \\n %NEWDEPLOY% $newdeploy \\n %REDEPLOY% $redeploy \\n %ASMMODE% $bundler_asm_mode \\n %APMMODE% $bundler_apm_mode \\n %APMASSOCIATE% $bundler_apm_associate \\n %DEFERREDCMDS% [join $bundler_deferred_cmds \"\n\"] \\n %STRICTUPDATES% $iappstrictUpdates ]\n\n set bundler_icall_src [string map $bundler_script_map $bundler_icall_tmpl]\n debug [list bundler icall_src] [format \"%s\" $bundler_icall_src] 10\n debug [list bundler icall_handler] [format \"creating iCall handler; executing postdeploy script at: %s\" $bundler_icall_time] 7\n\n set fn [format \"/var/tmp/appsvcspostdeploy%s.conf\" $app]\n catch {\n set fh [open $fn w]\n puts $fh $bundler_icall_src\n close $fh\n } {}\n\n debug [list bundler deploy] \"Bundled policy deployment will complete momentarily...\" 5\n}\n\nif { [string length $vsRouteAdv] > 0 && $vsRouteAdv ne \"disabled\" } {\n switch $vsRouteAdv {\n \"any_vs\" { set routeadv_mode \"any\" }\n \"all_vs\" { set routeadv_mode \"all\" }\n \"always\" { set routeadv_mode \"none\" }\n default { error \"The value specified for the Route Advertisement mode (vsRouteAdv) is invalid\" }\n }\n debug [list virtual_address route-adv] [format \"enabling route advertisement for virtual address %s with mode %s (postdeploy_final)\" $vs_dest_addr $routeadv_mode] 5\n lappend postfinal_deferred_cmds [create_escaped_tmsh [format \"tmsh::modify ltm virtual-address /%s/%s route-advertisement enabled\" $partition $vs_dest_addr]]\n lappend postfinal_deferred_cmds [create_escaped_tmsh [format \"tmsh::modify ltm virtual-address /%s/%s server-scope %s\" $partition $vs_dest_addr $routeadv_mode]]\n}\n\nif { [string length $vsVirtualAddrAdvOptions] > 0 } {\n set cmd [format \"tmsh::modify ltm virtual-address /%s/%s\" $partition $vs_dest_addr]\n if { [string length $vsVirtualAddrAdvOptions] > 0 } {\n debug [list virtual_address adv_options] \"processing advanced options string\" 7\n append cmd [format \" %s\" [process_options_string $vsVirtualAddrAdvOptions \"\" \"\"]]\n }\n debug [list virtual_address adv_options] $cmd 5\n lappend postfinal_deferred_cmds [create_escaped_tmsh $cmd]\n}\n\n# Call the custom_extensions_end proc to allow site-specific customizations\ncustom_extensions_end\n\nset postfinal_icall_tmpl {\nsys icall script %APP_PATH%/postdeploy_final {\n app-service %APP_PATH%/%APP_NAME%\n definition {\nset app %APP_NAME%\nset app_path %APP_PATH%\nset partition %PARTITION%\nset vs_name %VS_NAME%\nset newdeploy %NEWDEPLOY%\nset redeploy %REDEPLOY%\nset strict_updates %STRICTUPDATES%\n\nset aso \"/$partition/${app}.app/$app\"\nset iaso [format \"sys.application.service %s\" $aso]\nset logprefix \"\[appsvcs_postdeploy_final\]\[$app\]\"\n\nset systemTime [clock seconds]\nputs \"$logprefix Starting at [clock format $systemTime -format %D] [clock format $systemTime -format %H:%M:%S]\"\n\ntmsh::cd $app_path\nputs \"$logprefix Setting iCall handler to inactive...\"\ntmsh::modify sys application service $aso strict-updates disabled\ntmsh::modify sys icall handler periodic postdeploy_final status inactive\n\nistats::set [format \"%s string deploy.postdeploy_final\" $iaso] \"STARTED\"\nistats::set [format \"%s string deploy.postdeploy_final\" $iaso] \"DEFERRED_CMDS_IN_PROGRESS\"\nputs \"$logprefix Executing deferred commands...\"\n%DEFERREDCMDS%\nputs \"$logprefix Completed executing deferred commands...\"\nistats::set [format \"%s string deploy.postdeploy_final\" $iaso] \"DEFERRED_CMDS_COMPLETE\"\n\ntmsh::delete sys icall handler periodic postdeploy_final\nif { $strict_updates eq \"enabled\" } {\n\ttmsh::modify sys application service $aso strict-updates enabled\n}\n#tmsh::save sys config\n\nset systemTime [clock seconds]\nputs \"$logprefix Finished at [clock format $systemTime -format %D] [clock format $systemTime -format %H:%M:%S]\"\nistats::set [format \"%s string deploy.postdeployfinal\" $iaso] [format \"FINISHED%s\" $systemTime]\n }\n description none\n events none\n}\n\nsys icall handler periodic %APP_PATH%/postdeploy_final {\n app-service %APP_PATH%/%APP_NAME%\n first-occurrence %ICALLTIME%\n interval 3000\n last-occurrence now+10m\n script %APP_PATH%/postdeploy_final\n status %HANDLER_STATE%\n}\n\ncli script /Common/appsvcs_get_istat {\nproc script::init {} {\n}\n\nproc script::run {} {\n if { $tmsh::argc < 2 } {\n puts \"Please specify a iStat key to get\"\n exit\n }\n puts [istats::get [lindex $tmsh::argv 1]]\n}\n\nproc script::help {} {\n}\n\nproc script::tabc {} {\n}\n}\n};\n\nset postfinal_handler_state \"inactive\"\nif { $postdeploy_final_state } {\n set postfinal_handler_state \"active\"\n}\n\nset postfinal_deferred_cmds_str [join $postfinal_deferred_cmds \"\n\"]\n\nset postfinal_icall_time [clock format [expr {[clock seconds] + $::POSTDEPLOY_DELAY}] -format {%Y-%m-%d:%H:%M:%S}]\nset postfinal_script_map [list %APP_NAME% $::app \\n %APP_PATH% $::app_path \\n %VS_NAME% $::vsName \\n %PARTITION% $::partition \\n %ICALLTIME% $postfinal_icall_time \\n %NEWDEPLOY% $newdeploy \\n %REDEPLOY% $redeploy \\n %DEFERREDCMDS% $postfinal_deferred_cmds_str \\n %STRICTUPDATES% $iappstrictUpdates \\n %HANDLER_STATE% $postfinal_handler_state ]\n\nset postfinal_icall_src [string map $postfinal_script_map $postfinal_icall_tmpl]\ndebug [list postfinal icall_src] [format \"%s\" $postfinal_icall_src] 10\ndebug [list postfinal icall_handler] [format \"creating iCall handler; executing postdeploy_final script at: %s\" $postfinal_icall_time] 7\n\nset fn [format \"/var/tmp/appsvcspostdeploy%s.conf\" $app]\ncatch {\n if { $bundler_all_deploy } {\n set fh [open $fn a]\n } else {\n set fh [open $fn w]\n }\n puts $fh \"\"\n puts $fh $postfinal_icall_src\n close $fh\n} {}\n\nset fn [format \"/var/tmp/appsvcs_loadpostdeploy%s.sh\" $app]\ncatch {\n set fh [open $fn w]\n puts $fh \"sleep 5\"\n puts $fh [format \"tmsh load sys config file /var/tmp/appsvcspostdeploy%s.conf merge\" $app]\n puts $fh [format \"rm -f /var/tmp/appsvcspostdeploy%s.conf\" $app]\n puts $fh [format \"rm -f /var/tmp/appsvcs_loadpostdeploy%s.sh\" $app]\n close $fh\n exec chmod 777 $fn\n exec $fn &\n} {}\n\nif { $iappstrictUpdates eq \"disabled\" } {\n debug [list strict_updates] \"disabling strict updates\" 5\n tmsh::modify [format \"sys application service %s/%s strict-updates disabled\" $app_path $app]\n}\n\nset runTime [expr {[clock seconds] - $startTime}]\ndebug [list stop] [format \"Finished app_name=%s, total run time was %s seconds\" $app $runTime] 0", "presentation": "#\n# Copyright (c) 2017 F5 Networks, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nsection intro {\n\tmessage hello\n}\n\nsection iapp {\n\tchoice strictUpdates default \"enabled\" {\n\t\t\"enabled\",\n\t\t\"disabled\"\n\t}\n\tchoice appStats default \"enabled\" {\n\t\t\"enabled\",\n\t\t\"disabled\"\n\t}\n\tstring mode display \"large\" default \"auto\"\n\tstring logLevel display \"large\" default \"7\"\n\tstring routeDomain display \"large\" default \"auto\"\n\tchoice asmDeployMode display \"large\" default \"preserve-bypass\" {\n\t\t\"preserve-bypass\",\n\t\t\"preserve-block\",\n\t\t\"redeploy-bypass\",\n\t\t\"redeploy-block\"\n\t}\n\tchoice apmDeployMode display \"large\" default \"preserve-bypass\" {\n\t\t\"preserve-bypass\",\n\t\t\"preserve-block\",\n\t\t\"redeploy-bypass\",\n\t\t\"redeploy-block\"\n\t}\n}\n\nsection pool {\n\tstring addr required display \"large\" validator \"IpAddress\" default \"\"\n\tstring mask required display \"large\" validator \"IpAddress\" default \"255.255.255.255\"\n\tstring port required display \"small\" validator \"PortNumber\" default \"\"\n\tstring DefaultPoolIndex display \"small\" validator \"NonNegativeNumber\" default \"0\"\n\ttable Pools {\n\t\tstring Index display \"small\" validator \"NonNegativeNumber\" default \"0\"\n\t\tstring Name display \"medium\" default \"\"\n\t\tstring Description display \"medium\" default \"\"\n\t\tchoice LbMethod display \"medium\" default \"round-robin\" {\n\t\t\t\"dynamic-ratio-member\",\n\t\t\t\"dynamic-ratio-node\",\n\t\t\t\"fastest-app-response\",\n\t\t\t\"fastest-node\",\n\t\t\t\"least-connections-member\",\n\t\t\t\"least-connections-node\",\n\t\t\t\"least-sessions\",\n\t\t\t\"observed-member\",\n\t\t\t\"observed-node\",\n\t\t\t\"predictive-member\",\n\t\t\t\"predictive-node\",\n\t\t\t\"round-robin\",\n\t\t\t\"ratio-member\",\n\t\t\t\"ratio-node\",\n\t\t\t\"ratio-session\",\n\t\t\t\"ratio-least-connections-member\",\n\t\t\t\"ratio-least-connections-node\",\n\t\t\t\"weighted-least-connections-member\"\n\t\t}\n\t\tstring Monitor display \"medium\" default \"\"\n\t\tstring AdvOptions display \"medium\" default \"\"\n\t}\n\tstring MemberDefaultPort display \"small\" default \"\"\n\ttable Members {\n\t\tstring Index display \"small\" validator \"NonNegativeNumber\" default \"0\"\n\t\teditchoice IPAddress display \"large\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list ltm node]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\t\tstring Port display \"small\" default \"80\"\n\t\tstring ConnectionLimit display \"medium\" default \"0\"\n\t\tstring Ratio display \"small\" default \"1\"\n\t\tstring PriorityGroup display \"small\" default \"0\"\n\t\tchoice State display \"large\" default \"enabled\" {\n\t\t\t\"enabled\",\n\t\t\t\"drain-disabled\",\n\t\t\t\"disabled\"\n\t\t}\n\t\tstring AdvOptions display \"medium\"\n\t}\n}\n\nsection monitor {\n\ttable Monitors {\n\t\tstring Index display \"small\" validator \"NonNegativeNumber\" default \"0\"\n\t\tstring Name display \"medium\" default \"\"\n\t\tstring Type display \"medium\" default \"\"\n\t\tstring Options display \"medium\" default \"\"\n\t}\n}\n\nsection vs {\n\ttable Listeners {\n\t\tstring Listener display \"large\"\n\t\tstring Destination display \"medium\" default \"\"\n\t}\n\tstring Name display \"xxlarge\" default \"\"\n\tstring Description display \"xxlarge\" default \"\"\n\tchoice RouteAdv display \"medium\" default \"disabled\" {\n\t\t\"disabled\",\n\t\t\"all_vs\",\n\t\t\"any_vs\",\n\t\t\"always\"\n\t}\n\tstring SourceAddress display \"large\" default \"0.0.0.0/0\"\n\tstring IpProtocol display \"small\" default \"tcp\"\n\tstring ConnectionLimit display \"medium\" default \"0\"\n\teditchoice ProfileClientProtocol display \"xxlarge\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {ltm profile tcp} {ltm profile udp} {ltm profile fastl4}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileServerProtocol display \"xxlarge\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {ltm profile tcp} {ltm profile udp} {ltm profile fastl4}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileHTTP display \"xxlarge\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {ltm profile http}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileOneConnect display \"xxlarge\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {ltm profile one-connect}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileCompression display \"xxlarge\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {ltm profile http-compression}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\tstring ProfileAnalytics display \"large\" default \"\"\n\teditchoice ProfileRequestLogging display \"xxlarge\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {ltm profile request-log}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileDefaultPersist display \"large\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {ltm persistence cookie} {ltm persistence dest-addr} {ltm persistence hash} {ltm persistence msrdp} {ltm persistence sip} {ltm persistence source-addr} {ltm persistence ssl} {ltm persistence universal}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileFallbackPersist display \"large\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {ltm persistence cookie} {ltm persistence dest-addr} {ltm persistence hash} {ltm persistence msrdp} {ltm persistence sip} {ltm persistence source-addr} {ltm persistence ssl} {ltm persistence universal}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice SNATConfig display \"large\" default \"automap\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {ltm snatpool}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileServerSSL display \"xxlarge\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {ltm profile server-ssl}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileClientSSL display \"large\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {ltm profile client-ssl}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileClientSSLCert display \"large\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {sys file ssl-cert}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileClientSSLKey display \"large\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {sys file ssl-key}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileClientSSLChain display \"large\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {sys file ssl-cert}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\tstring ProfileClientSSLCipherString display \"xxlarge\" default \"DEFAULT\"\n\tstring ProfileClientSSLAdvOptions display \"xxlarge\" default \"\"\n\teditchoice ProfileSecurityLogProfiles display \"xxlarge\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {security log profile}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileSecurityIPBlacklist display \"large\" default \"none\" {\n\t\t\"none\",\n\t\t\"enabled-block\",\n\t\t\"enabled-log\"\n\t}\n\teditchoice ProfileSecurityDoS display \"xxlarge\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {security dos profile}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileAccess display \"xxlarge\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {apm profile access}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\teditchoice ProfileConnectivity display \"xxlarge\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {apm profile connectivity}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\tstring ProfilePerRequest display \"xxlarge\" default \"\"\n\tchoice OptionSourcePort display \"large\" default \"preserve\" {\n\t\t\"preserve\",\n\t\t\"preserve-strict\",\n\t\t\"change\"\n\t}\n\tchoice OptionConnectionMirroring default \"disabled\" {\n\t\t\"enabled\",\n\t\t\"disabled\"\n\t}\n\teditchoice Irules display \"xxlarge\" default \"\" tcl {\n\t\ttmsh::cd /\n set ::choices \"\"\n\t set cmds [list {ltm rule}]\n\t foreach cmd $cmds {\n\t set objs [list]\n\t set objs_status [catch {tmsh::get_config $cmd recursive} objs]\n\t if { $objs_status == 1 } { continue }\n\t foreach obj $objs {\n\t \tset name [string map {\"\\"\" \"\"} [tmsh::get_name $obj]]\n\t \tif { $name ne \"\" } {\n append ::choices \"/$name\"\n append ::choices \"\n\"\n\t\t }\n\t }\n\t }\n\n return [iapp::safe_display ::choices]\n\t}\n\n\ttable BundledItems {\n\t\teditchoice Resource display \"large\" {\n\t\t\t\" no bundled items \"\n\t\t}\n\t}\n\tstring AdvOptions display \"xxlarge\" default \"\"\n\tstring AdvProfiles display \"xxlarge\" default \"\"\n\tstring AdvPolicies display \"xxlarge\" default \"\"\n\tstring VirtualAddrAdvOptions display \"xxlarge\" default \"\"\n}\n\nsection l7policy {\n\teditchoice strategy display \"large\" default \"/Common/first-match\" {\n\t\t\"/Common/first-match\",\n\t\t\"/Common/best-match\",\n\t\t\"/Common/all-match\"\n\t}\n\tstring defaultASM display \"large\" default \"bypass\"\n\tstring defaultL7DOS display \"large\" default \"bypass\"\n\ttable rulesMatch {\n\t\tstring Group display \"small\" default \"\"\n\t\teditchoice Operand display \"xlarge\" {\n\t\t\t\"client-ssl/request/cipher\",\n\t\t\t\"client-ssl/request/cipher-bits\",\n\t\t\t\"client-ssl/request/protocol\",\n\t\t\t\"client-ssl/response/cipher\",\n\t\t\t\"client-ssl/response/cipher-bits\",\n\t\t\t\"client-ssl/response/protocol\",\n\t\t\t\"http-basic-auth/request/username\",\n\t\t\t\"http-basic-auth/request/password\",\n\t\t\t\"http-cookie/request/all/name/<name>\",\n\t\t\t\"http-header/request/all/name/<name>\",\n\t\t\t\"http-header/request/all/name/<name>\",\n\t\t\t\"http-host/request/all\",\n\t\t\t\"http-host/request/host\",\n\t\t\t\"http-host/request/port\",\n\t\t\t\"http-method/request/all\",\n\t\t\t\"http-referer/request/all\",\n\t\t\t\"http-referer/request/extension\",\n\t\t\t\"http-referer/request/host\",\n\t\t\t\"http-referer/request/path\",\n\t\t\t\"http-referer/request/path-segment/index/<index>\",\n\t\t\t\"http-referer/request/port\",\n\t\t\t\"http-referer/request/query-parameter/name/<name>\",\n\t\t\t\"http-referer/request/scheme\",\n\t\t\t\"http-referer/request/unnamed-query-parameter/index/<index>\",\n\t\t\t\"http-set-cookie/response/domain/name/<name>\",\n\t\t\t\"http-set-cookie/response/expiry/name/<name>\",\n\t\t\t\"http-set-cookie/response/path/name/<name>\",\n\t\t\t\"http-set-cookie/response/value/name/<name>\",\n\t\t\t\"http-set-cookie/response/version/name/<name>\",\n\t\t\t\"http-status/response/all\",\n\t\t\t\"http-status/response/code\",\n\t\t\t\"http-status/response/text\",\n\t\t\t\"http-uri/request/all\",\n\t\t\t\"http-uri/request/extension\",\n\t\t\t\"http-uri/request/host\",\n\t\t\t\"http-uri/request/path\",\n\t\t\t\"http-uri/request/path-segment/index/<index>\",\n\t\t\t\"http-uri/request/port\",\n\t\t\t\"http-uri/request/query-parameter/name/<name>\",\n\t\t\t\"http-uri/request/scheme\",\n\t\t\t\"http-uri/request/unnamed-query-parameter/index/<index>\",\n\t\t\t\"http-version/request/all\",\n\t\t\t\"http-version/request/major\",\n\t\t\t\"http-version/request/minor\",\n\t\t\t\"http-version/request/protocol\",\n\t\t\t\"http-version/response/all\",\n\t\t\t\"http-version/response/major\",\n\t\t\t\"http-version/response/minor\",\n\t\t\t\"http-version/response/protocol\",\n\t\t\t\"ssl-cert/ssl-server-handshake/common-name/index/<index>\",\n\t\t\t\"ssl-extension/ssl-client-hello/alpn\",\n\t\t\t\"ssl-extension/ssl-client-hello/npn\",\n\t\t\t\"ssl-extension/ssl-client-hello/server-name\",\n\t\t\t\"ssl-extension/ssl-server-hello/alpn\",\n\t\t\t\"ssl-extension/ssl-server-hello/npn\",\n\t\t\t\"ssl-extension/ssl-server-hello/server-name\",\n\t\t\t\"tcp/request/mss/internal\",\n\t\t\t\"tcp/request/port/internal\",\n\t\t\t\"tcp/request/port/local\",\n\t\t\t\"tcp/request/route-domain/internal\",\n\t\t\t\"tcp/request/rtt/internal\",\n\t\t\t\"tcp/request/vlan/internal\",\n\t\t\t\"tcp/request/vlan-id/internal\"\n\t\t}\n\t\tchoice Negate display \"small\" default \"no\" {\n\t\t\t\"no\",\n\t\t\t\"yes\"\n\t\t}\n\t\tchoice Condition display \"large\" {\n\t\t\t\"equals\",\n\t\t\t\"starts-with\",\n\t\t\t\"ends-with\",\n\t\t\t\"contains\",\n\t\t\t\"greater\",\n\t\t\t\"greater-or-equal\",\n\t\t\t\"less\",\n\t\t\t\"less-or-equal\"\n\t\t}\n\t\tstring Value display \"large\" default \"\"\n\t\tchoice CaseSensitive display \"small\" default \"no\" {\n\t\t\t\"no\",\n\t\t\t\"yes\"\n\t\t}\n\t\tchoice Missing display \"small\" default \"no\" {\n\t\t\t\"no\",\n\t\t\t\"yes\"\n\t\t}\n\t}\n\ttable rulesAction {\n\t\tstring Group display \"small\" default \"\"\n\t\teditchoice Target display \"xlarge\" {\n\t\t\t\"asm/request/enable/policy\",\n\t\t\t\"asm/request/disable\",\n\t\t\t\"cache/request/enable/pin\",\n\t\t\t\"cache/request/disable\",\n\t\t\t\"cache/response/enable/pin\",\n\t\t\t\"cache/respones/disable\",\n\t\t\t\"compress/request/enable\",\n\t\t\t\"compress/request/disable\",\n\t\t\t\"compress/response/enable\",\n\t\t\t\"compress/response/disable\",\n\t\t\t\"decompress/request/enable\",\n\t\t\t\"decompress/request/disable\",\n\t\t\t\"decompress/response/enable\",\n\t\t\t\"decompress/response/disable\",\n\t\t\t\"forward/request/reset\",\n\t\t\t\"forward/request/select/clone-pool\",\n\t\t\t\"forward/request/select/member\",\n\t\t\t\"forward/request/select/nexthop\",\n\t\t\t\"forward/request/select/node\",\n\t\t\t\"forward/request/select/pool\",\n\t\t\t\"forward/request/select/rateclass\",\n\t\t\t\"forward/request/select/snat\",\n\t\t\t\"forward/request/select/snatpool\",\n\t\t\t\"forward/request/select/vlan\",\n\t\t\t\"forward/request/select/vlan-id\",\n\t\t\t\"http/request/enable\",\n\t\t\t\"http/request/disable\",\n\t\t\t\"http-cookie/request/insert/name,value\",\n\t\t\t\"http-cookie/request/remove/name\",\n\t\t\t\"http-header/request/insert/name,value\",\n\t\t\t\"http-header/request/remove/name\",\n\t\t\t\"http-header/request/replace/name,value\",\n\t\t\t\"http-header/response/insert/name,value\",\n\t\t\t\"http-header/response/remove/name\",\n\t\t\t\"http-header/response/replace/name,value\",\n\t\t\t\"http-host/request/replace/value\",\n\t\t\t\"http-referer/request/insert/value\",\n\t\t\t\"http-referer/request/remove\",\n\t\t\t\"http-referer/request/replace/value\",\n\t\t\t\"http-reply/request/redirect/location\",\n\t\t\t\"http-reply/response/redirect/location\",\n\t\t\t\"http-set-cookie/response/insert/name,domain,path,value\",\n\t\t\t\"http-set-cookie/response/remove/name\",\n\t\t\t\"http-uri/response/replace/path,query-string,value\",\n\t\t\t\"l7dos/request/enable/from-profile\",\n\t\t\t\"l7dos/request/disable\",\n\t\t\t\"log/request/write/message\",\n\t\t\t\"log/response/write/message\",\n\t\t\t\"request-adapt/request/enable/internal-virtual-server\",\n\t\t\t\"request-adapt/request/disable\",\n\t\t\t\"request-adapt/response/enable/internal-virtual-server\",\n\t\t\t\"request-adapt/response/disable\",\n\t\t\t\"response-adapt/request/enable/internal-virtual-server\",\n\t\t\t\"response-adapt/request/disable\",\n\t\t\t\"response-adapt/response/enable/internal-virtual-server\",\n\t\t\t\"request-adapt/response/disable\",\n\t\t\t\"server-ssl/request/enable\",\n\t\t\t\"server-ssl/request/disable\",\n\t\t\t\"tcl/request/set-variable/name,expression\",\n\t\t\t\"tcl/response/set-variable/name,expression\",\n\t\t\t\"tcl/ssl-client-hello/set-variable/name,expression\",\n\t\t\t\"tcl/ssl-server-handshake/set-variable/name,expression\",\n\t\t\t\"tcl/ssl-server-hello/set-variable/name,expression\",\n\t\t\t\"tcp-nagle/request/enable\",\n\t\t\t\"tcp-nagle/request/disable\"\n\t\t}\n\t\tstring Parameter display \"large\" default \"\"\n\t}\n}\n\nsection feature {\n\tchoice statsTLS display \"medium\" default \"auto\" {\n\t\t\"auto\",\n\t\t\"enabled\",\n\t\t\"disabled\"\n\t}\n\tchoice statsHTTP display \"medium\" default \"auto\" {\n\t\t\"auto\",\n\t\t\"enabled\",\n\t\t\"disabled\"\n\t}\n\tchoice insertXForwardedFor display \"medium\" default \"auto\" {\n\t\t\"auto\",\n\t\t\"enabled\",\n\t\t\"disabled\"\n\t}\n\tchoice redirectToHTTPS display \"medium\" default \"auto\" {\n\t\t\"auto\",\n\t\t\"enabled\",\n\t\t\"disabled\"\n\t}\n\tchoice sslEasyCipher display \"medium\" default \"disabled\" {\n\t\t\"compatible\",\n\t\t\"medium\",\n\t\t\"high\",\n\t\t\"tls_1.2\",\n\t\t\"tls_1.1+1.2\",\n\t\t\"disabled\"\n\t}\n\teditchoice securityEnableHSTS display \"xlarge\" default \"disabled\" {\n\t\t\"disabled\",\n\t\t\"enabled\",\n\t\t\"enabled-preload\",\n\t\t\"enabled-subdomain\",\n\t\t\"enabled-preload-subdomain\"\n\t}\n\tchoice easyL4Firewall display \"xlarge\" default \"auto\" {\n\t\t\"auto\",\n\t\t\"base\",\n\t\t\"base+ip_blacklist_block\",\n\t\t\"base+ip_blacklist_log\",\n\t\t\"disabled\"\n\t}\n\ttable easyL4FirewallBlacklist {\n\t\tstring CIDRRange display \"large\"\n\t}\n\ttable easyL4FirewallSourceList {\n\t\tstring CIDRRange display \"large\" default \"0.0.0.0/0\"\n\t}\n}\n\nsection extensions {\n\tstring Field1 display \"xxlarge\" default \"\"\n\tstring Field2 display \"xxlarge\" default \"\"\n\tstring Field3 display \"xxlarge\" default \"\"\n}\n\n\ntext {\n\tintro \"F5 Application Services Integration iApp v2.0.004 (Community Edition)\"\n\tintro.hello \"Introduction\" \"Please complete the following template\"\n\n\tiapp \"iApp Options\"\n\tiapp.strictUpdates \"iApp: Strict Updates\"\n\tiapp.appStats \"iApp: Statistics Handler Creation\"\n\tiapp.mode \"iApp: Mode\"\n\tiapp.logLevel \"iApp: Log Level\"\n\tiapp.routeDomain \"iApp: Route Domain\"\n\tiapp.asmDeployMode \"iApp: ASM: Deployment Mode\"\n\tiapp.apmDeployMode \"iApp: APM: Deployment Mode\"\n\n\tpool \"Virtual Server Listener & Pool Configuration\"\n\tpool.addr \"Virtual Server: Address\"\n\tpool.mask \"Virtual Server: Mask\"\n\tpool.port \"Virtual Server: Port\"\n\tpool.DefaultPoolIndex \"Virtual Server: Default Pool Index\"\n\tpool.Pools \"Pool: Pool Table\"\n\tpool.Pools.Index \"Index:\"\n\tpool.Pools.Name \"Name:\"\n\tpool.Pools.Description \"Description:\"\n\tpool.Pools.LbMethod \"LB Method:\"\n\tpool.Pools.Monitor \"Monitor(s):\"\n\tpool.Pools.AdvOptions \"Adv Options:\"\n\tpool.MemberDefaultPort \"Pool: Member Default Port\"\n\tpool.Members \"Pool: Members\"\n\tpool.Members.Index \"Pool Idx:\"\n\tpool.Members.IPAddress \"IP/Node Name:\"\n\tpool.Members.Port \"Port:\"\n\tpool.Members.ConnectionLimit \"Connection Limit:\"\n\tpool.Members.Ratio \"Ratio:\"\n\tpool.Members.PriorityGroup \"Priority Group:\"\n\tpool.Members.State \"State:\"\n\tpool.Members.AdvOptions \"Adv Options:\"\n\n\tmonitor \"Pool Monitor(s) Configuration\"\n\tmonitor.Monitors \"Monitor: Monitor Table\"\n\tmonitor.Monitors.Index \"Index:\"\n\tmonitor.Monitors.Name \"Name:\"\n\tmonitor.Monitors.Type \"Type:\"\n\tmonitor.Monitors.Options \"Options:\"\n\n\tvs \"Virtual Server Configuration\"\n\tvs.Listeners \"Virtual Server: Additional Listeners\"\n\tvs.Listeners.Listener \"Listener:\"\n\tvs.Listeners.Destination \"Destination\"\n\tvs.Name \"Virtual Server: Name\"\n\tvs.Description \"Virtual Server: Description\"\n\tvs.RouteAdv \"Virtual Server: Route Advertisement\"\n\tvs.SourceAddress \"Virtual Server: Source Address\"\n\tvs.IpProtocol \"Virtual Server: IP Protocol\"\n\tvs.ConnectionLimit \"Virtual Server: Virtual Server Connection Limit (0=unlimited)\"\n\tvs.ProfileClientProtocol \"Virtual Server: Client-side L4 Protocol Profile\"\n\tvs.ProfileServerProtocol \"Virtual Server: Server-side L4 Protocol Profile\"\n\tvs.ProfileHTTP \"Virtual Server: HTTP Profile\"\n\tvs.ProfileOneConnect \"Virtual Server: OneConnect Profile\"\n\tvs.ProfileCompression \"Virtual Server: Compression Profile\"\n\tvs.ProfileAnalytics \"Virtual Server: Analytics Profile\"\n\tvs.ProfileRequestLogging \"Virtual Server: Request Logging Profile\"\n\tvs.ProfileDefaultPersist \"Virtual Server: Default Persistence Profile\"\n\tvs.ProfileFallbackPersist \"Virtual Server: Fallback Persistence Profile\"\n\tvs.SNATConfig \"Virtual Server: SNAT Configuration (enter SNAT pool name, 'automap' or leave blank to disable SNAT)\"\n\tvs.ProfileServerSSL \"Virtual Server: Server SSL Profile\"\n\tvs.ProfileClientSSL \"Virtual Server: Client SSL Profile\"\n\tvs.ProfileClientSSLCert \"Virtual Server: Client SSL Certificate\"\n\tvs.ProfileClientSSLKey \"Virtual Server: Client SSL Key\"\n\tvs.ProfileClientSSLChain \"Virtual Server: Client SSL Certificate Chain\"\n\tvs.ProfileClientSSLCipherString \"Virtual Server: Client SSL Cipher String\"\n\tvs.ProfileClientSSLAdvOptions \"Virtual Server: Client SSL Advanced Options\"\n\tvs.ProfileSecurityLogProfiles \"Virtual Server: Security Logging Profiles\"\n\tvs.ProfileSecurityIPBlacklist \"Virtual Server: IP Blacklist Profile\"\n\tvs.ProfileSecurityDoS \"Virtual Server: Security: DoS Profile\"\n\tvs.ProfileAccess \"Virtual Server: Access Profile\"\n\tvs.ProfileConnectivity \"Virtual Server: Connectivity Profile\"\n\tvs.ProfilePerRequest \"Virtual Server: Per-Request Profile\"\n\tvs.OptionSourcePort \"Virtual Server: Source Port Behavior\"\n\tvs.OptionConnectionMirroring \"Virtual Server: Connection Mirroring\"\n\tvs.Irules \"Virtual Server: iRules (to specify multiple iRules seperate with a comma ex: irule1,irule2,irule3)\"\n\tvs.BundledItems \"Virtual Server: Bundled Items\"\n\tvs.BundledItems.Resource \"Resource:\"\n\tvs.AdvOptions \"Virtual Server: Advanced Options\"\n\tvs.AdvProfiles \"Virtual Server: Advanced Profiles\"\n\tvs.AdvPolicies \"Virtual Server: Advanced Policies\"\n\tvs.VirtualAddrAdvOptions \"Virtual Address: Advanced Options\"\n\n\tl7policy \"L7 Traffic Policy\"\n\tl7policy.strategy \"L7 Policy: Match Strategy\"\n\tl7policy.defaultASM \"L7 Policy: Default ASM Policy\"\n\tl7policy.defaultL7DOS \"L7 Policy: Default L7 DoS Policy\"\n\tl7policy.rulesMatch \"L7 Policy: Rules: Matching\"\n\tl7policy.rulesMatch.Group \"Group:\"\n\tl7policy.rulesMatch.Operand \"Operand:\"\n\tl7policy.rulesMatch.Negate \"Negate:\"\n\tl7policy.rulesMatch.Condition \"Condition:\"\n\tl7policy.rulesMatch.Value \"Value:\"\n\tl7policy.rulesMatch.CaseSensitive \"Case Sensitive:\"\n\tl7policy.rulesMatch.Missing \"Missing:\"\n\tl7policy.rulesAction \"L7 Policy: Rules: Action\"\n\tl7policy.rulesAction.Group \"Group:\"\n\tl7policy.rulesAction.Target \"Target:\"\n\tl7policy.rulesAction.Parameter \"Parameter:\"\n\n\tfeature \"L4-7 Helpers\"\n\tfeature.statsTLS \"TLS/SSL: Stats Reporting\"\n\tfeature.statsHTTP \"HTTP: Stats Reporting\"\n\tfeature.insertXForwardedFor \"HTTP: Insert X-Forwarded-For Header\"\n\tfeature.redirectToHTTPS \"HTTP: Security: Create HTTP(80)->HTTPS(443) Redirect\"\n\tfeature.sslEasyCipher \"TLS/SSL: Easy Cipher String (overrides VS section setting)\"\n\tfeature.securityEnableHSTS \"HTTP: Security: Enable HTTP Strict Transport Security (only valid if ClientSSL is configured)\"\n\tfeature.easyL4Firewall \"Security: Firewall: Configure L4 Firewall Policy\"\n\tfeature.easyL4FirewallBlacklist \"Security: Firewall: Static Blacklisted Addresses (CIDR Format)\"\n\tfeature.easyL4FirewallBlacklist.CIDRRange \"CIDR Block:\"\n\tfeature.easyL4FirewallSourceList \"Security: Firewall: Static Allowed Source Addresses (CIDR Format)\"\n\tfeature.easyL4FirewallSourceList.CIDRRange \"CIDR Block:\"\n\n\textensions \"Custom Extensions Section\"\n\textensions.Field1 \"Extensions: Field 1\"\n\textensions.Field2 \"Extensions: Field 2\"\n\textensions.Field3 \"Extensions: Field 3\"\n\n}" } ] } }

tthomas0702 commented 6 years ago

@caphrim007 I am not sure If I posted that in a way that is usable. If the above is too much of a mess let me know and I will give it another try to have it formatted better.

caphrim007 commented 6 years ago

@tthomas0702 this gets the point across.

Here's my point. You mention that you are able to post the entire template using postman. The fact of the matter is that you posted a single template, and that template is special because nowhere in it are fields such as "presentation", "macro", "html-help", "role-acl", etc.

Your template there is only an implementation.

An iApp template has a loose specification, but a basic set of fields that it can take. For example, see this stub.

https://github.com/F5Networks/f5-ansible/blob/devel/test/integration/targets/bigip_iapp_template/files/basic-iapp.tmpl

You have provided only a single one of those fields, implementation. Were your iApp to have any number of other fields, you would not be able to give the iApp to the REST api in the manner that you did. A presentation field cannot go in an implementation field.

This is the crux of the problem with the REST API for iApp templates and the reason that I suggest you avoid using it entirely. The iApps that F5 releases contain all those fields in that example and more. Some of the additional fields are not even settable via the iApp template API and need to be set at other APIS (such as procs).

If you have one of those iapps, then the burden of parsing this file (such as this https://github.com/F5Networks/f5-ansible/blob/devel/test/integration/targets/bigip_iapp_template/files/f5.microsoft_exchange_2016.v1.0.0.tmpl) is entirely on your shoulders. Because the valid REST representation of that iApp is

tm.sys.application.templates_template.create(
    implementation=YOUR_PARSED_IMPLEMENTATION_BLOCK,
    presentation=YOUR_PARSED_PRESENTATION_BLOCK,
    htmlHelp=YOUR_PARSED_HTML_HELP_BLOCK,
    macros=YOUR_PARSED_MACROS_BLOCK
)
as well as 10's or more API calls to create each proc at the expected APIs.

This is hard and no customer would ever expect to have to do this...I mean...F5 gave me this iApp, why do I need to parse it? That's crazy talk to expect that. And yet, this is the API.

So to mitigate that problem, the approach I recommend is to not use the API at all at this time. Instead, use the method of put the iApp on the box via an upload, and then use tmsh over REST to install it. At least until such time as a new API exists that behaves in a similar manner (or they fix the existing one because it is, arguably, unusable).

tthomas0702 commented 6 years ago

@caphrim007 OK, I understand better now.

Thanks for explaining. I think I was a little confused by the fact that it works when I POST it. The appscvs iApp must be a special case. I will stop trying to import an iApp and move on to deploying an iApp.

wojtek0806 commented 6 years ago

closing as answered