TMSH
2162 TopicsiControl REST Cookbook - Virtual Server (ltm virtual)
This cookbook lists selected ready-to-use iControl REST curl commands for virtual-server related resources. Each recipe consists of the curl command, it's tmsh equivalent, and sample output. In this cookbook, the following curl options are used. Option Meaning ______________________________________________________________________________________ -s Suppress progress meter. Handy when you want to pipe the output. ______________________________________________________________________________________ -k Allows "insecure" SSL connections. ______________________________________________________________________________________ -u Specify user ID and password. For the start, you should use the "admin" account that you normally use to access the Configuration Utility. When you specify the password at the same time, concatenate with ":". e.g., admin:admin. ______________________________________________________________________________________ -X <method> Specify the HTTP method. When omitted, the default is GET. In the REST framework, POST means create (tmsh create), PATCH means overwriting the existing resource with the data sent (tmsh modify), and PATCH is for merging (ditto). ______________________________________________________________________________________ -H <Header> Specify the request header. When you send (POST, PATCH, PUT) data, you need to tell the server that the data is in JSON format. i.e., -H "Content-Type: application/json. ______________________________________________________________________________________ -d 'data' The JSON data to send. Note that you need to quote the entire json blob, and each "name":"value" pairs must be quoted. When you have nested quotes, make sure you escape (\) them. Get information of the virtual <vs> tmsh list ltm <vs> curl -sku admin:admin https://<host>/mgmt/tm/ltm/virtual/<vs> Sample Output { kind: 'tm:ltm:virtual:virtualstate', name: 'vs', fullPath: 'vs', generation: 1109, selfLink: 'https://localhost/mgmt/tm/ltm/virtual/vs?ver=12.1.0', addressStatus: 'yes', autoLasthop: 'default', cmpEnabled: 'yes', connectionLimit: 0, description: 'TestData', destination: '/Common/192.168.184.226:80', enabled: true, gtmScore: 0, ipProtocol: 'tcp', mask: '255.255.255.255', mirror: 'disabled', mobileAppTunnel: 'disabled', nat64: 'disabled', pool: '/Common/vs-pool', poolReference: { link: 'https://localhost/mgmt/tm/ltm/pool/~Common~vs-pool?ver=12.1.0' }, rateLimit: 'disabled', rateLimitDstMask: 0, rateLimitMode: 'object', rateLimitSrcMask: 0, serviceDownImmediateAction: 'none', source: '0.0.0.0/0', sourceAddressTranslation: { type: 'automap' }, sourcePort: 'preserve', synCookieStatus: 'not-activated', translateAddress: 'enabled', translatePort: 'enabled', vlansDisabled: true, vsIndex: 4, rules: [ '/Common/irule' ], rulesReference: [ { link: 'https://localhost/mgmt/tm/ltm/rule/~Common~iRuleTest?ver=12.1.0' } ], policiesReference: { link: 'https://localhost/mgmt/tm/ltm/virtual/~Common~vs/policies?ver=12.1.0', isSubcollection: true }, profilesReference: { link: 'https://localhost/mgmt/tm/ltm/virtual/~Common~vs/profiles?ver=12.1.0', isSubcollection: true } } Get only specfic field of the virtual <vs> The naming convension for the parameters is slightly different from the ones on tmsh, so look for the familiar names in the GET response above. The example below queris the Default Pool (pool). tmsh list ltm <vs> pool curl -sku admin:admin https://<host>/mgmt/tm/ltm/virtual/<vs>?options=pool Sample Output { kind: 'tm:ltm:virtual:virtualstate', name: 'vs', fullPath: 'vs', generation: 1, selfLink: 'https://localhost/mgmt/tm/ltm/virtual/vs?options=pool&ver=12.1.1', pool: '/Common/vs-pool', poolReference: { link: 'https://localhost/mgmt/tm/ltm/pool/~Common~vs-pool?ver=12.1.1' } } Get all the information of the virtual <vs> Unlike the tmsh equivalent, iControl REST GET does not return the configuration information of the attached policies and profiles. To see them, use expandSubcollections tmsh list ltm <vs> curl -sku admin:admin https://<host>/mgmt/tm/ltm/virtual/<vs>?expandSubcollections=true Sample Output { "addressStatus": "yes", "autoLasthop": "default", "cmpEnabled": "yes", "connectionLimit": 0, "destination": "/Common/192.168.184.240:80", "enabled": true, "fullPath": "vs", "generation": 291, "gtmScore": 0, "ipProtocol": "tcp", "kind": "tm:ltm:virtual:virtualstate", "mask": "255.255.255.255", "mirror": "disabled", "mobileAppTunnel": "disabled", "name": "vs", "nat64": "disabled", "policiesReference": { "isSubcollection": true, "link": "https://localhost/mgmt/tm/ltm/virtual/~Common~vs/policies?ver=13.1.0" }, "pool": "/Common/CentOS-all80", "poolReference": { "link": "https://localhost/mgmt/tm/ltm/pool/~Common~CentOS-all80?ver=13.1.0" }, "profilesReference": { "isSubcollection": true, "items": [ { "context": "all", "fullPath": "/Common/http", "generation": 291, "kind": "tm:ltm:virtual:profiles:profilesstate", "name": "http", "nameReference": { "link": "https://localhost/mgmt/tm/ltm/profile/http/~Common~http?ver=13.1.0" }, "partition": "Common", "selfLink": "https://localhost/mgmt/tm/ltm/virtual/~Common~vs/profiles/~Common~http?ver=13.1.0" }, { "context": "all", "fullPath": "/Common/tcp", "generation": 287, "kind": "tm:ltm:virtual:profiles:profilesstate", "name": "tcp", "nameReference": { "link": "https://localhost/mgmt/tm/ltm/profile/tcp/~Common~tcp?ver=13.1.0" }, "partition": "Common", "selfLink": "https://localhost/mgmt/tm/ltm/virtual/~Common~vs/profiles/~Common~tcp?ver=13.1.0" } ], "link": "https://localhost/mgmt/tm/ltm/virtual/~Common~vs/profiles?ver=13.1.0" }, "rateLimit": "disabled", "rateLimitDstMask": 0, "rateLimitMode": "object", "rateLimitSrcMask": 0, "selfLink": "https://localhost/mgmt/tm/ltm/virtual/vs?expandSubcollections=true&ver=13.1.0", "serviceDownImmediateAction": "none", "source": "0.0.0.0/0", "sourceAddressTranslation": { "type": "automap" }, "sourcePort": "preserve", "synCookieStatus": "not-activated", "translateAddress": "enabled", "translatePort": "enabled", "vlansDisabled": true, "vsIndex": 2 } Get stats of the virtual <vs> tmsh show ltm <vs> curl -sku admin:admin https://<host>/mgmt/tm/ltm/virtual/<vs>/stats Sample Output { kind: 'tm:ltm:virtual:virtualstats', generation: 1109, selfLink: 'https://localhost/mgmt/tm/ltm/virtual/vs/stats?ver=12.1.0', entries: { 'https://localhost/mgmt/tm/ltm/virtual/vs/~Common~vs/stats': { nestedStats: { kind: 'tm:ltm:virtual:virtualstats', selfLink: 'https://localhost/mgmt/tm/ltm/virtual/vs/~Common~vs/stats?ver=12.1.0', entries: { 'clientside.bitsIn': { value: 12880 }, 'clientside.bitsOut': { value: 34592 }, 'clientside.curConns': { value: 0 }, 'clientside.evictedConns': { value: 0 }, 'clientside.maxConns': { value: 2 }, 'clientside.pktsIn': { value: 26 }, 'clientside.pktsOut': { value: 26 }, 'clientside.slowKilled': { value: 0 }, 'clientside.totConns': { value: 6 }, cmpEnableMode: { description: 'all-cpus' }, cmpEnabled: { description: 'enabled' }, csMaxConnDur: { value: 37 }, csMeanConnDur: { value: 29 }, csMinConnDur: { value: 17 }, destination: { description: '192.168.184.226:80' }, 'ephemeral.bitsIn': { value: 0 }, 'ephemeral.bitsOut': { value: 0 }, 'ephemeral.curConns': { value: 0 }, 'ephemeral.evictedConns': { value: 0 }, 'ephemeral.maxConns': { value: 0 }, 'ephemeral.pktsIn': { value: 0 }, 'ephemeral.pktsOut': { value: 0 }, 'ephemeral.slowKilled': { value: 0 }, 'ephemeral.totConns': { value: 0 }, fiveMinAvgUsageRatio: { value: 0 }, fiveSecAvgUsageRatio: { value: 0 }, tmName: { description: '/Common/vs' }, oneMinAvgUsageRatio: { value: 0 }, 'status.availabilityState': { description: 'available' }, 'status.enabledState': { description: 'enabled' }, 'status.statusReason': { description: 'The virtual server is available' }, syncookieStatus: { description: 'not-activated' }, 'syncookie.accepts': { value: 0 }, 'syncookie.hwAccepts': { value: 0 }, 'syncookie.hwSyncookies': { value: 0 }, 'syncookie.hwsyncookieInstance': { value: 0 }, 'syncookie.rejects': { value: 0 }, 'syncookie.swsyncookieInstance': { value: 0 }, 'syncookie.syncacheCurr': { value: 0 }, 'syncookie.syncacheOver': { value: 0 }, 'syncookie.syncookies': { value: 0 }, totRequests: { value: 4 } } } } } } Change one of the configuration options of the virtual <vs> The command below changes the Description field of the virtual ("description" in tmsh and iControl REST). tmsh modify ltm virtual <vs> description "Hello World!" curl -sku admin:admin https://<host>/mgmt/tm/ltm/virtual/<vs> \ -X PATCH -H "Content-Type: application/json" \ -d '{"description": "Hello World!"}' Sample Output { kind: 'tm:ltm:virtual:virtualstate', name: 'vs', ... description: 'Hello World!', <==== Changed. ... } Disable the virtual <vs> The command syntax is same as above: To change the state of a virtual from "enabled" to "disabled", send "disabled":true. For enabling the virtual, use "enabled":true. Note that the Boolean type true/false does not require quotations. tmsh modify ltm virtual <vs> disabled curl -sku admin:admin https://<host>/mgmt/tm/ltm/virtual/<vs> \ -X PATCH -H "Content-Type: application/json" \ -d '{"disabled": true}' \ Sample Output { kind: 'tm:ltm:virtual:virtualstate', name: 'vs', fullPath: 'vs', ... disabled: true, <== Changed ... } Add another iRule to <vs> When the virtual has iRules already attached, you need to send the existing ones too along with the additional one. For example, to add /Common/testRule1 to the virtual with /Common/testRule1, specify both in an array (square brackets). Note that the /Common/testRule2 iRule object should be already created. tmsh modify ltm virtual <vs> rules {testRule1 testRule2} curl -sku admin:admin https://<host>/mgmt/tm/ltm/virtual/<vs> \ -X PATCH -H "Content-Type: application/json" \ -d '{"rules": ["/Common/testRule1", "/Common/testRule2"] }' Sample Output { kind: 'tm:ltm:virtual:virtualstate', name: 'vs', fullPath: 'vs', ... rules: [ '/Common/test1', '/Common/test2' ], <== Changed rulesReference: [ { link: 'https://localhost/mgmt/tm/ltm/rule/~Common~test1?ver=12.1.1' }, { link: 'https://localhost/mgmt/tm/ltm/rule/~Common~test2?ver=12.1.1' } ], ... } Create a new virtual <vs> You can create a skeleton virtual by specifying only Destination Address and Mask. The remaining parameters such as profiles are set to default. You can later modify the parameters by PATCH-ing. tmsh create ltm virtual <vs> destination <ip:port> mask <ip> curl -sku admin:admin -X POST -H "Content-Type: application/json" \ -d '{"name": "vs", "destination":"192.168.184.230:80", "mask":"255.255.255.255"}' \ https://<host>/mgmt/tm/ltm/virtual Sample Output { kind: 'tm:ltm:virtual:virtualstate', name: 'vs', partition: 'Common', fullPath: '/Common/vs', ... destination: '/Common/192.168.184.230:80', <== Created ... mask: '255.255.255.255', <== Created ... } Create a new virtual <vs> with a lot of parameters You can specify all the essential parameters upon creation. This example creates a new virtual with pool, default persistence profile, profiles, iRule, and source address translation. The call fails if any of the parameters conflicts. For example, you cannot specify "Cookie Persistence" without specifying appropriate profiles. If you do not specify any profile, it falls back to the default fastL4 , which is not compatible with Cookie Persistence. tmsh create ltm virtual <vs> destination <ip:port> mask <ip> pool <pool> persist replace-all-with { cookie } profiles add { tcp http clientssl } rules { <rule> } source-address-translation { type automap } curl -sku admin:admin https://<host>/mgmt/tm/ltm/virtual -H "Content-Type: application/json" -X POST -d '{"name": "vs", \ "destination": "10.10.10.10:10", \ "mask": "255.255.255.255", \ "pool": "CentOS-all80", \ "persist": [ {"name": "cookie"} ], \ "profilesReference": {"items": [ {"context": "all", "name": "http"}, {"context": "all", "name": "tcp"}, {"context": "clientside", "name": "clientssl"}] }, \ "rules": [ "ShowVersion" ], \ "sourceAddressTranslation": {"type": "automap"} }' Sample Output { "addressStatus": "yes", "autoLasthop": "default", "cmpEnabled": "yes", "connectionLimit": 0, "destination": "/Common/10.10.10.10:10", "enabled": true, "fullPath": "/Common/test", "generation": 592, "gtmScore": 0, "ipProtocol": "tcp", "kind": "tm:ltm:virtual:virtualstate", "mask": "255.255.255.255", "mirror": "disabled", "mobileAppTunnel": "disabled", "name": "vs", "nat64": "disabled", "partition": "Common", "persist": [ { "name": "cookie", "nameReference": { "link": "https://localhost/mgmt/tm/ltm/persistence/cookie/~Common~cookie?ver=13.1.0" }, "partition": "Common", "tmDefault": "yes" } ], "policiesReference": { "isSubcollection": true, "link": "https://localhost/mgmt/tm/ltm/virtual/~Common~test/policies?ver=13.1.0" }, "pool": "/Common/CentOS-all80", "poolReference": { "link": "https://localhost/mgmt/tm/ltm/pool/~Common~CentOS-all80?ver=13.1.0" }, "profilesReference": { "isSubcollection": true, "link": "https://localhost/mgmt/tm/ltm/virtual/~Common~test/profiles?ver=13.1.0" }, "rateLimit": "disabled", "rateLimitDstMask": 0, "rateLimitMode": "object", "rateLimitSrcMask": 0, "rules": [ "/Common/ShowVersion" ], "rulesReference": [ { "link": "https://localhost/mgmt/tm/ltm/rule/~Common~ShowVersion?ver=13.1.0" } ], "selfLink": "https://localhost/mgmt/tm/ltm/virtual/~Common~test?ver=13.1.0", "serviceDownImmediateAction": "none", "source": "0.0.0.0/0", "sourceAddressTranslation": { "type": "automap" }, "sourcePort": "preserve", "synCookieStatus": "not-activated", "translateAddress": "enabled", "translatePort": "enabled", "vlansDisabled": true, "vsIndex": 52 } Delete a virtual <vs> tmsh delete ltm virtual <vs> curl -sku admin:admin https://192.168.226.55/mgmt/tm/ltm/virtual/<vs> -X DELETE Sample Output No output (just 200 OK and no response body) References curl.1 the man page curl Releases and Downloads ... including the port for Windows Jason Rahm's "Demystifying iControl REST" series(DevCentral) -- This is Part I of 7 at the time of this article. iControl REST API reference (DevCentral) iControl® REST API User Guide (DevCentral) -- Link is for 12.1. Search for the older versions.17KViews3likes13CommentsNeed help on CLI command to fetch < VIP Name + current connections >
Hello Experts , I need help in modifying below command which should also give me information of VIP name along with current connection . show ltm virtual recursive all | grep 'Availability\|Current Connections' As you can see this command only gives info of current connection but it will not tell which VIP has the current connection value ... blue.whale@(F5-BIGIP-ACT)(cfg-sync In Sync)(Active)(/Common)(tmos)# show ltm virtual recursive all | grep 'Availability\|Current Connections' Display all 1942 items? (y/n) y Availability : available Current Connections 0 0 - Availability : available Current Connections 38 0 - Availability : available Current Connections 0 0 - Availability : available Current Connections 73 0 - Availability : available Current Connections 0 0 - Availability : available Current Connections 0 0 -Solved56Views0likes1CommentHow to modify client-ssl on multiple VS using TMSH
Hi, I was able to get the list of virtual servers using cert1 for example by list and grep. Now, I want to use TMSH to use the CLI in replacing client-ssl cert1 to cert2. If I have like 50 VS, how to do that using modify on TMSH? If I do modify /ltm virtual VSname profile add SSLname context clientside, im getting this error: "vs has more than one clientssl/serverssl profile with same server name" If I do delete, im getting this error: "Syntax Error: "context" is a read-only property" If I do replace-all-with, cert2 is configured but wipes all other profiles and i don't want to put alot of config to reconfigure all profiles in a VS. Is there a 1 line command I can do? TIA1.3KViews2likes3Commentsnetwork failover/serial cable for HA
for HA configurations, there are 3 options network failover in network failover the heartbeat is the network packet serial failover serial cable failover is just checking a voltage heartbeat between the two systems. network and Serial failover I not sure does it mean failover only take place when network and serial both are failed or ? if use both, how to test if it is working?Solved1.3KViews0likes14CommentsWorking with subsets of data-group records via iControl REST
The BIG-IP iControl REST interface method for data-groups does not define the records as a subcollection (like pool members.) This is problematic for many because the records are just a list attribute in the data-group object. This means that if you want to add, modify, or delete a single record in a data-group, you have replace the entire list. A short while back I was on a call with iRule extraordinaire John Alam, and he was showing me a management tool he was working on where he could change individual records in a data-group via REST. I was intrigued so we dug into the details and I was floored at how simple the solution to this problem is! UPDATE: Chris L mentioned in the comments below that working with subsets IS possible without the tmsh script, as helearned in this thread in the Q&A section. The normal endpoint (/mgmt/tm/ltm/data-group/internal/yourDGname) works just fine, but instead of trying to change a subset of the records attribute, which only results in the replace-all-with behavior, you can use theoptions query parameterand then pass the normal tmsh command records data as arguments. An example of this request would be to PATCHwith an empty json payload ({})to urlhttps://{{host}}/mgmt/tm/ltm/data-group/internal/mydg?options=records%20modify%20%7B%20k3%20%7Bdata%20v3%20%7D%20%7D(without the encoding, that query value format is “records modify { k3 { data v3 } }”). As this article is still a good learning exercise on how to use tmsh scripts with iControl REST, I’ll keep the article as is, but an updated script for the specific problem we’re solving can befound in this gist on Github. Enter the tmsh script! Even though the iControl REST doesn’t treat data-group records as individual objects, the tmsh cli does. So if you can create a tmsh script to manage the local manipulation of the records, pass your record data into that script, and execute it from REST, well, that’s where the gold is, people. Let’s start with the tmsh script, written by John Alam but modified very slightly by me. cli script dgmgmt { proc script::init {} { } proc script::run {} { set record_data [lindex $tmsh::argv 3] switch [lindex $tmsh::argv 1] { "add-record" { tmsh::modify ltm data-group internal [lindex $tmsh::argv 2] type string records add $record_data puts "Record [lindex $tmsh::argv 3] added." } "modify-record" { tmsh::modify ltm data-group internal [lindex $tmsh::argv 2] type string records modify $record_data puts "Record changed [lindex $tmsh::argv 3]." } "delete-record" { tmsh::modify ltm data-group internal [lindex $tmsh::argv 2] type string records delete $record_data puts "Record [lindex $tmsh::argv 3] deleted." } "save" { tmsh::save sys config puts "Config saved." } } } proc script::help {} { } proc script::tabc {} { } total-signing-status not-all-signed } This script is installed on the BIG-IP and is a regular object in the BIG-IP configuration, stored in the bigip_script.conf file. There are four arguments passed. The first (arg 0) is always the script name. The other args we pass to the script are: arg 1 - action. Are we adding, modifying, or deleting records? arg 2 - data-group name arg 3 - data-group records to be changed The commands are pretty straight forward. Notice, however, that the record data at the tail end of each of those commands is just the data passed to the script, so the required tmsh format is left to the remote side of this transaction. Since I’m writing that side of the solution, that’s ok, but if I were to put my best practices hat on, the record formatting work should really be done in the tmsh script, so that all I have to do on the remote side is pass the key/value data. Executing the script! Now that we have a shiny new tmsh script for the BIG-IP, we have two issues. We need to install that script on the BIG-IP in order to use it We need to be able to run that script remotely, and pass data to it This is where you grab your programming language of choice and go at it! For me, that would be python. And I’ll be using the BIGREST SDK to interact with BIG-IP. Let’s start with the program flow: if __name__ == "__main__": args = build_parser() b = instantiate_bigip(args.host, args.user) if not b.exist("/mgmt/tm/cli/script/dgmgmt"): print( "\n\tThe data-group management tmsh script is not yet on your system, installing..." ) deploy_tmsh_script(b) sleep(2) if not b.exist(f"/mgmt/tm/ltm/data-group/internal/{args.datagroup}"): print( f"\n\tThe {args.datagroup} data-group doesn't exist. Please specify an existing data-group.\n" ) sys.exit() cli_arguments = format_records(args.action, args.datagroup, args.dgvalues) dg_update(b, cli_arguments) dg_listing(b, args.datagroup) This is a cli script, so we need to create a parser to handle the arguments. After collecting the data, we instantiate BIG-IP. Next, we check for the existence of the tmsh script on BIG-IP and install it if it is not present. We then format the record data and proceed to supply that output as arguments when we make the REST call to run the tmsh script. Finally, we print the results. This last step is probably not something you'd want to do for large data sets, but it's included here for validation. Now, let's look at each step of the flow. The imports and tmsh script # Imports used in this script from bigrest.bigip import BIGIP from time import sleep import argparse import getpass import sys # The tmsh script DGMGMT_SCRIPT = 'proc script::init {} {\n}\n\nproc script::run {} {\nset record_data [lindex $tmsh::argv 3]\n\n' \ 'switch [lindex $tmsh::argv 1] {\n "add-record" {\n tmsh::modify ltm data-group internal ' \ '[lindex $tmsh::argv 2] type string records add $record_data\n ' \ 'puts "Record [lindex $tmsh::argv 3] added."\n }\n "modify-record" {\n ' \ 'tmsh::modify ltm data-group internal [lindex $tmsh::argv 2] type string records modify' \ ' $record_data\n puts "Record changed [lindex $tmsh::argv 3]."\n }\n "delete-record" {\n' \ ' tmsh::modify ltm data-group internal [lindex $tmsh::argv 2] type string records delete' \ ' $record_data\n puts "Record [lindex $tmsh::argv 3] deleted."\n }\n "save" {\n ' \ ' tmsh::save sys config\n puts "Config saved."\n }\n}\n}\n' \ 'proc script::help {} {\n}\n\nproc script::tabc {} {\n}\n' These are defined at the top of the script and are necessary to the appropriate functions defined in the below sections. You could move the script into a file and load it, but it's small enough that it doesn't clutter the script and makes it easier not to have to manage multiple files. The parser def build_parser(): parser = argparse.ArgumentParser() parser.add_argument("host", help="BIG-IP IP/FQDN") parser.add_argument("user", help="BIG-IP Username") parser.add_argument( "action", help="add | modify | delete", choices=["add", "modify", "delete"] ) parser.add_argument("datagroup", help="Data-Group name you wish to change") parser.add_argument( "dgvalues", help='Key or KV Pairs, in this format: "k1,k2,k3=v3,k4=v4,k5"' ) return parser.parse_args() This is probably the least interesting part, but I'm including it here to be thorough. The one thing to note is the cli format to supply the key/value pairs for the data-group records. I could have also added an alternate option to load a file instead, but I'll leave that as an exercise for future development. If you supply no arguments or the optional -h/--help, you'll get the help message. % python dgmgmt.py -h usage: dgmgmt.py [-h] host user {add,modify,delete} datagroup dgvalues positional arguments: host BIG-IP IP/FQDN user BIG-IP Username {add,modify,delete} add | modify | delete datagroup Data-Group name you wish to change dgvalues Key or KV Pairs, in this format: "k1,k2,k3=v3,k4=v4,k5" optional arguments: -h, --help show this help message and exit Instantiation def instantiate_bigip(host, user): pw = getpass.getpass(prompt=f"\n\tWell hello {user}, please enter your password: ") try: obj = BIGIP(host, user, pw) except Exception as e: print(f"Failed to connect to {args.host} due to {type(e).__name__}:\n") print(f"{e}") sys.exit() return obj I don't like typing out my passwords on the cli so I use getpass here to ask for it after I kick off the script. You'll likely want to add an argument for the password if you automate this script with any of your tooling. This function makes a request to BIG-IP and builds a local python object to be used for future requests. Uploading the tmsh script def deploy_tmsh_script(bigip): try: cli_script = {"name": "dgmgmt", "apiAnonymous": DGMGMT_SCRIPT} bigip.create("/mgmt/tm/cli/script", cli_script) except Exception as e: print(f"Failed to create the tmsh script due to {type(e).__name__}:\n") print(f"{e}") sys.exit() Because tmsh scripts are BIG-IP objects, we don't have to interact with the file system. It's just a simple object creation like creating a pool. I have taken the liberty to hardcode the script name to limit the number of arguments required to pass on the cli, but that can be updated if you so desire by either changing the name in the script, or adding arguments. Formatting the records def format_records(action, name, records): recs = "" for record in records.split(","): x = record.split("=") record_key = x[0] if len(x) == 1 and action != 'modify': recs += f"{record_key} " elif len(x) == 1 and action == 'modify': recs += f'{record_key} {{ data \\\"\\\" }} ' elif len(x) == 2: record_value = x[1] recs += f'{record_key} {{ data \\\"{record_value}\\\" }} ' else: raise ValueError("Max record items is 2: key or key/value pair.") return f"{action}-record {name} '{{ {recs} }}'" This is the function I spent the most time ironing out. As I pointed out earlier, it would be better handled in the tmsh script, but since that work was already completed by John, I focused on the python side of things. The few things that I fleshed out in testing that I didn't consider while making it work the first time: Escaping all the special characters that make iControl REST unhappy. Handling whitespace in the data value. This requires quotes around the data value. Modifying a key by removing an existing value. This requires you to provide an empty data reference. Executing the script def dg_update(bigip, cli_args): try: dg_mods = {"command": "run", "name": "/Common/dgmgmt", "utilCmdArgs": cli_args} bigip.command("/mgmt/tm/cli/script", dg_mods) except Exception as e: print(f"Failed to modify the data-group due to {type(e).__name__}:\n") print(f"{e}") sys.exit() With all the formatting out of the way, the update is actually anticlimactic. iControl REST requires a json payload for the command, which is running the cli script. The cli arguments for that script are passed in the utilCmdArgs attribute. Validating the results def dg_listing(bigip, dgname): dg = b.load(f'/mgmt/tm/ltm/data-group/internal/{dgname}') print(f'\n\t{args.datagroup}\'s updated record set: ') for i in dg.properties['records']: print(f'\t\tkey: {i["name"]}, value: {i["data"]}') print('\n\n') And finally, we bask in the validity of our updates! Like the update function, this one doesn't have much to do. It grabs the data-group contents from BIG-IP and prints out each of the key/value pairs. As I indicated earlier, this may not be desirable on large data sets. You could modify the function by passing the keys you changed and compare that to the full results returned from BIG-IP and only print the updates, but I'll leave that as another exercise for future development. This is a cool workaround to the non-subcollection problem with data-groups that I wish I'd thought of years ago! The full script is in the codeshare. I hope you got something out of this article, drop a comment below and let me know!1.5KViews0likes2CommentsSupported way to use MFA to BIG-IP GUI and shell
I have read on DevCentral various mechanisms to implement 2FA (MFA) using APM and even some packages to change the PAM and implement this on the SSH shell. Are there any supported mechanisms to protect the BIG-IP Web interface via multi-factor? Even if one had the APM, can it be turned around to control the BIG-IP GUI itself? Also, what about SSH access? I am curious if others have solved this issue. It is surprising to me that at least the GUI does not have a native MFA solution to basic administration. Thanks, Tom1.2KViews1like5CommentsModifying multiple entries in a datagroup via api?
We have a datagroup with entries like this: domain1.com := virtual /Common/www.domain1.com_vs_443 domain2.com := virtual /Common/www.domain2.com_vs_443 domain3.com := virtual /Common/www.domain3.com_vs_443 And so forth. This datagroup gets used by an iRule for re-routing traffic based on SNI. I need to be able to make an HTTP call to the F5 to remap these to put up a mainteance page. So in other words I want to modify the above to: domain1.com := virtual /Common/maintenance.domain1.com_vs_443 domain2.com := virtual /Common/maintenance.domain2.com_vs_443 domain3.com := virtual /Common/maintenance.domain3.com_vs_443 I figured out that I can make a curl request as such to delete entries from the datagroup: curl -ku "admin:superS3cret" -X PATCH -H 'Content-type: application/json' -d '{ "name":"dummy" }' https://lb1.internal.local/mgmt/tm/ltm/data-group/internal/dummy?options=records%20delete%20%7B%20www.domain1.com,www.domain2.com,www.domain3.com%20%7D |jq . And via this articlehttps://community.f5.com/t5/technical-forum/add-new-key-into-data-group-without-updating-entire-list-using/td-p/272699I was able to determine we could add a key using this: curl -ku "admin:superS3cret" -X PATCH -H 'Content-type: application/json' -d '{ "name":"dummy" }' 'https://lb1.internal.local/mgmt/tm/ltm/data-group/internal/dummy?options=records%20add%20%7b%20www.domain1.com%20%7b%20data%20%22virtual%20%2FCommon%2Fmaintenance.domain1.com_vs_443%22%20%7d%20%7d' |jq . However then I have to have one HTTP Request per domain. If I need to repoint 20 sites for example I have to make 20 different requests instead of one request with all domains. Is there a way to add multiple records at once? I tried something like this: curl -ku "admin:superS3cret" -X PATCH -H 'Content-type: application/json' -d '{ "name":"dummy" }' 'https://lb1.internal.local/mgmt/tm/ltm/data-group/internal/dummy?options%3Drecords%20add%20%7B%20www.domain1.com%20%7B%20data%20%22virtual%20%2FCommon%2Fmaintenance.domain1.com_vs_443%22%20%7D%20domain1.com%20%7B%20data%20%22virtual%20%2FCommon%2Fmaintenance.domain1.com_vs_443%22%20%7D%20www.domain2.com%20%7B%20data%20%22virtual%20%2FCommon%2Fmaintenance.domain2.com_vs_443%22%20%7D%20www.domain3.com%20%7B%20data%20%22virtual%20%2FCommon%2Fmaintenance.domain3.com_vs_443%22%20%7D%20www.domain4.com%20%7B%20data%20%22virtual%20%2FCommon%2Fmaintenance.domain4.com_vs_443%22%20%7D%7D' |jq . Here's the URL HTML decoded (so its easier to read): https://lb1.internal.local/mgmt/tm/ltm/data-group/internal/dummy?options=records add { www.domain1.com { data "virtual /Common/maintenance.domain1.com_vs_443" } domain1.com { data "virtual /Common/maintenance.domain1.com_vs_443" } www.domain2.com { data "virtual /Common/maintenance.domain2.com_vs_443" } www.domain3.com { data "virtual /Common/maintenance.domain3.com_vs_443" } www.domain4.com { data "virtual /Common/maintenance.domain4.com_vs_443" }} However I get this error: { "code": 400, "message": "one or more properties must be specified", "errorStack": [], "apiError": 26214401 } I haven't been able to figure out if adding multiple records isn't supported or if I have the syntax wrong and frankly the documentation here:https://clouddocs.f5.com/api/icontrol-rest/APIRef_tm_ltm_data-group_internal.htmland here:https://clouddocs.f5.com/cli/tmsh-reference/v14/modules/ltm/ltm_data-group_internal.htmlisn't super clear. Frankly the fact that patch doesn't just modify entries sent seems like a bug to me and using ?options record add seems like a hack. Alternatively is there some way to run a modify instead of running delete and then add? That would be even more efficient (less HTTP calls) and less opportunity for a call to fail. I know I can just get all records, modify them and then post all records back. The trouble with that approach is that then I'm touching records I really don't want to touch and having to do some sort of regex replace. This seems like it could have the potential to accidentally modify records I don't want to modify if my regex isn't very explicit. I would prefer a more targeted approach which only modifies the records that need modifying and that touches nothing else. ThanksSolved843Views0likes9CommentsDevice name under 'Device Management'
I'm looking for some information as to why there's device name shown as 'bigip1(self)' and also a hostname under 'Device Management > Devices', though the device hostname has been changed. Also, is there a way to change the device name from the CLI?737Views0likes5CommentsExport Virtual Server Configuration in CSV - tmsh cli script
Problem this snippet solves: This is a simple cli script used to collect all the virtuals name, its VIP details, Pool names, members, all Profiles, Irules, persistence associated to each, in all partitions. A sample output would be like below, One can customize the code to extract other fields available too. The same logic can be allowed to pull information's from profiles stats, certificates etc. Update: 5th Oct 2020 Added Pool members capture in the code. After the Pool-Name, Pool-Members column will be found. If a pool does not have members - field not present: "members" will shown in the respective Pool-Members column. If a pool itself is not bound to the VS, then Pool-Name, Pool-Members will have none in the respective columns. Update: 21st Jan 2021 Added logic to look for multiple partitions & collect configs Update: 12th Feb 2021 Added logic to add persistence to sheet. Update: 26th May 2021 Added logic to add state & status to sheet. Update: 24th Oct 2023 Added logic to add hostname, Pool Status,Total-Connections & Current-Connections. Note: The codeshare has multiple version, use the latest version alone. The reason to keep the other versions is for end users to understand & compare, thus helping them to modify to their own requirements. Hope it helps. How to use this snippet: Login to the LTM, create your script by running the below commands and paste the code provided in snippet tmsh create cli script virtual-details So when you list it, it should look something like below, [admin@labltm:Active:Standalone] ~ # tmsh list cli script virtual-details cli script virtual-details { proc script::run {} { puts "Virtual Server,Destination,Pool-Name,Profiles,Rules" foreach { obj } [tmsh::get_config ltm virtual all-properties] { set profiles [tmsh::get_field_value $obj "profiles"] set remprof [regsub -all {\n} [regsub -all"context" [join $profiles "\n"] "context"] " "] set profilelist [regsub -all "profiles " $remprof ""] puts "[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],[tmsh::get_field_value $obj "pool"],$profilelist,[tmsh::get_field_value $obj "rules"]" } } total-signing-status not-all-signed } [admin@labltm:Active:Standalone] ~ # And you can run the script like below, tmsh run cli script virtual-details > /var/tmp/virtual-details.csv And get the output from the saved file, cat /var/tmp/virtual-details.csv Old Codes: cli script virtual-details { proc script::run {} { puts "Virtual Server,Destination,Pool-Name,Profiles,Rules" foreach { obj } [tmsh::get_config ltm virtual all-properties] { set profiles [tmsh::get_field_value $obj "profiles"] set remprof [regsub -all {\n} [regsub -all " context" [join $profiles "\n"] "context"] " "] set profilelist [regsub -all "profiles " $remprof ""] puts "[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],[tmsh::get_field_value $obj "pool"],$profilelist,[tmsh::get_field_value $obj "rules"]" } } total-signing-status not-all-signed } ###=================================================== ###2.0 ###UPDATED CODE BELOW ### DO NOT MIX ABOVE CODE & BELOW CODE TOGETHER ###=================================================== cli script virtual-details { proc script::run {} { puts "Virtual Server,Destination,Pool-Name,Pool-Members,Profiles,Rules" foreach { obj } [tmsh::get_config ltm virtual all-properties] { set poolname [tmsh::get_field_value $obj "pool"] set profiles [tmsh::get_field_value $obj "profiles"] set remprof [regsub -all {\n} [regsub -all " context" [join $profiles "\n"] "context"] " "] set profilelist [regsub -all "profiles " $remprof ""] if { $poolname != "none" }{ set poolconfig [tmsh::get_config /ltm pool $poolname] foreach poolinfo $poolconfig { if { [catch { set member_name [tmsh::get_field_value $poolinfo "members" ]} err] } { set pool_member $err puts "[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,$pool_member,$profilelist,[tmsh::get_field_value $obj "rules"]" } else { set pool_member "" set member_name [tmsh::get_field_value $poolinfo "members" ] foreach member $member_name { append pool_member "[lindex $member 1] " } puts "[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,$pool_member,$profilelist,[tmsh::get_field_value $obj "rules"]" } } } else { puts "[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,none,$profilelist,[tmsh::get_field_value $obj "rules"]" } } } total-signing-status not-all-signed } ###=================================================== ### Version 3.0 ### UPDATED CODE BELOW FOR MULTIPLE PARTITION ### DO NOT MIX ABOVE CODE & BELOW CODE TOGETHER ###=================================================== cli script virtual-details { proc script::run {} { puts "Partition,Virtual Server,Destination,Pool-Name,Pool-Members,Profiles,Rules" foreach all_partitions [tmsh::get_config auth partition] { set partition "[lindex [split $all_partitions " "] 2]" tmsh::cd /$partition foreach { obj } [tmsh::get_config ltm virtual all-properties] { set poolname [tmsh::get_field_value $obj "pool"] set profiles [tmsh::get_field_value $obj "profiles"] set remprof [regsub -all {\n} [regsub -all " context" [join $profiles "\n"] "context"] " "] set profilelist [regsub -all "profiles " $remprof ""] if { $poolname != "none" }{ set poolconfig [tmsh::get_config /ltm pool $poolname] foreach poolinfo $poolconfig { if { [catch { set member_name [tmsh::get_field_value $poolinfo "members" ]} err] } { set pool_member $err puts "$partition,[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,$pool_member,$profilelist,[tmsh::get_field_value $obj "rules"]" } else { set pool_member "" set member_name [tmsh::get_field_value $poolinfo "members" ] foreach member $member_name { append pool_member "[lindex $member 1] " } puts "$partition,[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,$pool_member,$profilelist,[tmsh::get_field_value $obj "rules"]" } } } else { puts "$partition,[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,none,$profilelist,[tmsh::get_field_value $obj "rules"]" } } } } total-signing-status not-all-signed } ###=================================================== ### Version 4.0 ### UPDATED CODE BELOW FOR CAPTURING PERSISTENCE ### DO NOT MIX ABOVE CODE & BELOW CODE TOGETHER ###=================================================== cli script virtual-details { proc script::run {} { puts "Partition,Virtual Server,Destination,Pool-Name,Pool-Members,Profiles,Rules,Persist" foreach all_partitions [tmsh::get_config auth partition] { set partition "[lindex [split $all_partitions " "] 2]" tmsh::cd /$partition foreach { obj } [tmsh::get_config ltm virtual all-properties] { set poolname [tmsh::get_field_value $obj "pool"] set profiles [tmsh::get_field_value $obj "profiles"] set remprof [regsub -all {\n} [regsub -all " context" [join $profiles "\n"] "context"] " "] set profilelist [regsub -all "profiles " $remprof ""] set persist [lindex [lindex [tmsh::get_field_value $obj "persist"] 0] 1] if { $poolname != "none" }{ set poolconfig [tmsh::get_config /ltm pool $poolname] foreach poolinfo $poolconfig { if { [catch { set member_name [tmsh::get_field_value $poolinfo "members" ]} err] } { set pool_member $err puts "$partition,[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,$pool_member,$profilelist,[tmsh::get_field_value $obj "rules"],$persist" } else { set pool_member "" set member_name [tmsh::get_field_value $poolinfo "members" ] foreach member $member_name { append pool_member "[lindex $member 1] " } puts "$partition,[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,$pool_member,$profilelist,[tmsh::get_field_value $obj "rules"],$persist" } } } else { puts "$partition,[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,none,$profilelist,[tmsh::get_field_value $obj "rules"],$persist" } } } } total-signing-status not-all-signed } ###=================================================== ### 5.0 ### UPDATED CODE BELOW ### DO NOT MIX ABOVE CODE & BELOW CODE TOGETHER ###=================================================== cli script virtual-details { proc script::run {} { puts "Partition,Virtual Server,Destination,Pool-Name,Pool-Members,Profiles,Rules,Persist,Status,State" foreach all_partitions [tmsh::get_config auth partition] { set partition "[lindex [split $all_partitions " "] 2]" tmsh::cd /$partition foreach { obj } [tmsh::get_config ltm virtual all-properties] { foreach { status } [tmsh::get_status ltm virtual [tmsh::get_name $obj]] { set vipstatus [tmsh::get_field_value $status "status.availability-state"] set vipstate [tmsh::get_field_value $status "status.enabled-state"] } set poolname [tmsh::get_field_value $obj "pool"] set profiles [tmsh::get_field_value $obj "profiles"] set remprof [regsub -all {\n} [regsub -all " context" [join $profiles "\n"] "context"] " "] set profilelist [regsub -all "profiles " $remprof ""] set persist [lindex [lindex [tmsh::get_field_value $obj "persist"] 0] 1] if { $poolname != "none" }{ set poolconfig [tmsh::get_config /ltm pool $poolname] foreach poolinfo $poolconfig { if { [catch { set member_name [tmsh::get_field_value $poolinfo "members" ]} err] } { set pool_member $err puts "$partition,[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,$pool_member,$profilelist,[tmsh::get_field_value $obj "rules"],$persist,$vipstatus,$vipstate" } else { set pool_member "" set member_name [tmsh::get_field_value $poolinfo "members" ] foreach member $member_name { append pool_member "[lindex $member 1] " } puts "$partition,[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,$pool_member,$profilelist,[tmsh::get_field_value $obj "rules"],$persist,$vipstatus,$vipstate" } } } else { puts "$partition,[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,none,$profilelist,[tmsh::get_field_value $obj "rules"],$persist,$vipstatus,$vipstate" } } } } total-signing-status not-all-signed } Latest Code: cli script virtual-details { proc script::run {} { set hostconf [tmsh::get_config /sys global-settings hostname] set hostname [tmsh::get_field_value [lindex $hostconf 0] hostname] puts "Hostname,Partition,Virtual Server,Destination,Pool-Name,Pool-Status,Pool-Members,Profiles,Rules,Persist,Status,State,Total-Conn,Current-Conn" foreach all_partitions [tmsh::get_config auth partition] { set partition "[lindex [split $all_partitions " "] 2]" tmsh::cd /$partition foreach { obj } [tmsh::get_config ltm virtual all-properties] { foreach { status } [tmsh::get_status ltm virtual [tmsh::get_name $obj]] { set vipstatus [tmsh::get_field_value $status "status.availability-state"] set vipstate [tmsh::get_field_value $status "status.enabled-state"] set total_conn [tmsh::get_field_value $status "clientside.tot-conns"] set curr_conn [tmsh::get_field_value $status "clientside.cur-conns"] } set poolname [tmsh::get_field_value $obj "pool"] set profiles [tmsh::get_field_value $obj "profiles"] set remprof [regsub -all {\n} [regsub -all " context" [join $profiles "\n"] "context"] " "] set profilelist [regsub -all "profiles " $remprof ""] set persist [lindex [lindex [tmsh::get_field_value $obj "persist"] 0] 1] if { $poolname != "none" }{ foreach { p_status } [tmsh::get_status ltm pool $poolname] { set pool_status [tmsh::get_field_value $p_status "status.availability-state"] } set poolconfig [tmsh::get_config /ltm pool $poolname] foreach poolinfo $poolconfig { if { [catch { set member_name [tmsh::get_field_value $poolinfo "members" ]} err] } { set pool_member $err puts "$hostname,$partition,[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,$pool_status,$pool_member,$profilelist,[tmsh::get_field_value $obj "rules"],$persist,$vipstatus,$vipstate,$total_conn,$curr_conn" } else { set pool_member "" set member_name [tmsh::get_field_value $poolinfo "members" ] foreach member $member_name { append pool_member "[lindex $member 1] " } puts "$hostname,$partition,[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,$pool_status,$pool_member,$profilelist,[tmsh::get_field_value $obj "rules"],$persist,$vipstatus,$vipstate,$total_conn,$curr_conn" } } } else { puts "$hostname,$partition,[tmsh::get_name $obj],[tmsh::get_field_value $obj "destination"],$poolname,none,none,$profilelist,[tmsh::get_field_value $obj "rules"],$persist,$vipstatus,$vipstate,$total_conn,$curr_conn" } } } } } Tested this on version: 13.08.5KViews9likes25CommentsHow to set different Priority Groups while adding nodes to pool with TMSH?
Version 11.4.1 and 11.5.0. I want to use a single TMSH command to create a pool and add 2 nodes each with a different Priority Group. Is this even possible? I am trying this in tmsh ltm: create pool pool_psc_p1_2_443 members add { view-p1-psc3:443 } priority-group 10 add { view-p1-psc4:443 } priority-group 1 monitor vmware_psc_monitor_TCP load-balancing-mode round-robin ...but I get this error: Syntax Error: "priority-group" unknown property I think I will have to first create the pool, then go back and modify it. I find the BigIP command line and TMSH difficult enough already, so I am inclined not to keep fiddling with it to try to make this work. The TMSH guide for 11.4.1 is rather useless for me here because the examples "delete pool my_pool" and "list pool my_pool" are a slightly a bit too overly simple.958Views0likes3Comments