BIG-IP Next Automation: Working with the AS3 API endpoints
In my last article I covered the basics of AS3 as it relates to getting started with automation with BIG-IP Next. I also walked through an application migration in a previous article that addresses some of the issues you'll need to work through moving to Next, but whereas I touched the AS3 slightly in the workflow, all the work was accomplished in the Central Manager web UI. In this article, I'll walk you through creating two applications, one a simple DNS load balancing application and the other a TLS-protected HTTP application with an associated iRule. For each application, I'll use the compatibility API and the documents API for working through the CRUD operations.
Creating the declarations
You can go about this a few different ways. You can start from the AS3 schema reference and climb up from scratch, you can spin up Visual Studio Code and work with the F5 Extension to interrogate your own BIG-IP configurations and use the AS3 Config Converter to automagically do the work for you, or you can just ask chatGPT to generate the AS3 for you to get started like I did. And after that didn't work without a lot of tweaking...I went back to VSCode.
Example 1 - DNS application service declaration
Here's what I ended up with for the DNS application service:
{
"$schema": "https://raw.githubusercontent.com/F5Networks/f5-appsvcs-extension/master/schema/latest/as3-schema.json",
"class": "AS3",
"declaration": {
"class": "ADC",
"schemaVersion": "3.37.0",
"id": "urn:uuid:3a71dceb-f56c-4dc1-901a-2feae0244c46",
"label": "Converted Declaration",
"remark": "Generated by Automation Config Converter",
"Common": {
"class": "Tenant",
"Shared": {
"class": "Application",
"template": "shared",
"vip.ns-cluster-1": {
"layer4": "udp",
"pool": "pool.ns-cluster-1",
"translateServerAddress": true,
"translateServerPort": true,
"class": "Service_UDP",
"profileUDP": {
"bigip": "/Common/udp"
},
"virtualAddresses": [
"10.100.100.100"
],
"virtualPort": 53,
"snat": "auto"
},
"pool.ns-cluster-1": {
"members": [
{
"addressDiscovery": "static",
"servicePort": 53,
"serverAddresses": [
"10.10.100.101",
"10.10.100.102",
"10.10.100.103",
"10.10.100.104"
],
"shareNodes": true
}
],
"monitors": [
{
"bigip": "/Common/udp"
}
],
"class": "Pool"
}
}
}
}
}
Note that in BIG-IP Next, there isn't an alternative to the AS3 class, so that wrapper for the ADC class declaration is unnecessary and will result in an error if posted. So the only change required at this time is to remove the wrapper, and change common/shared to tenant1/dnsapp1 as shown below.
{
"class": "ADC",
"schemaVersion": "3.37.0",
"id": "urn:uuid:3a71dceb-f56c-4dc1-901a-2feae0244c46",
"label": "Converted Declaration",
"remark": "Generated by Automation Config Converter",
"tenant1": {
"class": "Tenant",
"dnsapp1": {
"class": "Application",
"template": "shared",
"vip.ns-cluster-1": {
"layer4": "udp",
"pool": "pool.ns-cluster-1",
"translateServerAddress": true,
"translateServerPort": true,
"class": "Service_UDP",
"profileUDP": {
"bigip": "/Common/udp"
},
"virtualAddresses": [
"10.100.100.100"
],
"virtualPort": 53,
"snat": "auto"
},
"pool.ns-cluster-1": {
"members": [
{
"addressDiscovery": "static",
"servicePort": 53,
"serverAddresses": [
"10.10.100.101",
"10.10.100.102",
"10.10.100.103",
"10.10.100.104"
],
"shareNodes": true
}
],
"monitors": [
{
"bigip": "/Common/udp"
}
],
"class": "Pool"
}
}
}
}
But wait! There's more! Now that I'm channeling my inner Billy Mays, the declaration is not quite ready for Next. After a quick test or five or six, there are some problems with my schema in the move to Next. Here are the necessary changes, followed by the final declaration I'll used with the API endpoints.
- Swapped out the UDP monitor for ICMP since there is not currently a UDP monitor available
- Removed the profileUDP, layer4, and translateServerPort attributes from the Service_UDP class
{
"class": "ADC",
"schemaVersion": "3.37.0",
"id": "urn:uuid:3a71dceb-f56c-4dc1-901a-2feae0244c46",
"label": "Converted Declaration",
"remark": "Generated by Automation Config Converter",
"tenant1": {
"class": "Tenant",
"dnsapp1": {
"class": "Application",
"template": "shared",
"vip.ns-cluster-1": {
"pool": "pool.ns-cluster-1",
"translateServerAddress": true,
"class": "Service_UDP",
"virtualAddresses": [
"10.100.100.100"
],
"virtualPort": 53,
"snat": "auto"
},
"pool.ns-cluster-1": {
"members": [
{
"addressDiscovery": "static",
"servicePort": 53,
"serverAddresses": [
"10.10.100.101",
"10.10.100.102",
"10.10.100.103",
"10.10.100.104"
],
"shareNodes": true
}
],
"monitors": [
"icmp"
],
"class": "Pool"
}
}
}
}
Example 2 - TLS-protected HTTP application service with iRule declaration
And here's the HTTP application service as converted in VSCode but without the AS3 class wrapper:
{
"class": "ADC",
"schemaVersion": "3.37.0",
"id": "urn:uuid:bd9c9728-8c20-4c4d-a625-68450e35e133",
"label": "Converted Declaration",
"remark": "Generated by Automation Config Converter",
"Common": {
"class": "Tenant",
"Shared": {
"class": "Application",
"template": "shared",
"vip.acme_labs": {
"layer4": "tcp",
"pool": "pool.acme_labs",
"iRules": [
{
"use": "/Common/Shared/full_uri_decode"
}
],
"translateServerAddress": true,
"translateServerPort": true,
"class": "Service_HTTPS",
"serverTLS": "/Common/Shared/cssl.acme_labs",
"profileHTTP": {
"bigip": "/Common/http"
},
"profileTCP": {
"bigip": "/Common/tcp"
},
"redirect80": false,
"virtualAddresses": [
"172.16.101.133"
],
"virtualPort": 443,
"snat": "auto"
},
"pool.acme_labs": {
"loadBalancingMode": "least-connections-member",
"members": [
{
"addressDiscovery": "static",
"servicePort": 80,
"serverAddresses": [
"172.16.102.5"
],
"shareNodes": true
}
],
"monitors": [
{
"bigip": "/Common/http"
}
],
"class": "Pool"
},
"www.acmelabs.com": {
"class": "Certificate",
"certificate": {
"bigip": "/Common/www.acmelabs.com"
},
"privateKey": {
"bigip": "/Common/www.acmelabs.com"
}
},
"cssl.acme_labs": {
"certificates": [
{
"certificate": "/Common/Shared/www.acmelabs.com"
}
],
"class": "TLS_Server",
"tls1_0Enabled": true,
"tls1_1Enabled": true,
"tls1_2Enabled": true,
"tls1_3Enabled": false,
"singleUseDhEnabled": false,
"insertEmptyFragmentsEnabled": true
},
"full_uri_decode": {
"class": "iRule",
"iRule": {
"base64": "d2hlbiBIVFRQX1JFUVVFU1QgewogICMgZGVjb2RlIG9yaWdpbmFsIFVSSS4KICBzZXQgdG1wVXJpIFtIVFRQOjp1cmldCiAgc2V0IHVyaSBbVVJJOjpkZWNvZGUgJHRtcFVyaV0KICAjIHJlcGVhdCBkZWNvZGluZyB1bnRpbCB0aGUgZGVjb2RlZCB2ZXJzaW9uIGVxdWFscyB0aGUgcHJldmlvdXMgdmFsdWUuCiAgd2hpbGUgeyAkdXJpIG5lICR0bXBVcmkgfSB7CiAgICBzZXQgdG1wVXJpICR1cmkKICAgIHNldCB1cmkgW1VSSTo6ZGVjb2RlICR0bXBVcmldCiAgfQogIEhUVFA6OnVyaSAkdXJpCiAgbG9nIGxvY2FsMC4gIk9yaWdpbmFsIFVSSTogW0hUVFA6OnVyaV0iCiAgbG9nIGxvY2FsMC4gIkZ1bGx5IGRlY29kZWQgVVJJOiAkdXJpIgp9"
}
}
}
}
}
This is mostly ok with the exception of the certificate handling in lines 56-70. If I was posting this back to my local BIG-IP in place of the imperative configuration it'd be fine. But Central Manager has no context for where those certificates are so I'll need to do a little work here to prep the declaration. I need to drop the certificate and key into the Certificate class (your security-sense should be tingling, remember these are private keys so in your environment you'd be pulling these credentials in from a vault and NOT storing these in a file) and then updating the reference to the local object in the TLS_Server class.
NOTE: It might be confusing for long-time BIG-IP users, but the TLS_Server class in AS3 is the equivalent of a client-ssl profile, and the TLS_CLIENT class in AS3 is the equivalent of a server-ssl profile. This change was made in AS3 to align more with industry-standard nomenclature.
After these changes, and changes to Common/Shared, the updated declaration is shown below.
{
"class": "ADC",
"schemaVersion": "3.37.0",
"id": "urn:uuid:bd9c9728-8c20-4c4d-a625-68450e35e133",
"label": "Converted Declaration",
"remark": "Generated by Automation Config Converter",
"tenant2": {
"class": "Tenant",
"httpsapp1": {
"class": "Application",
"template": "shared",
"vip.acme_labs": {
"layer4": "tcp",
"pool": "pool.acme_labs",
"iRules": [
{
"use": "/Common/Shared/full_uri_decode"
}
],
"translateServerAddress": true,
"translateServerPort": true,
"class": "Service_HTTPS",
"serverTLS": "/Common/Shared/cssl.acme_labs",
"profileHTTP": {
"bigip": "/Common/http"
},
"profileTCP": {
"bigip": "/Common/tcp"
},
"redirect80": false,
"virtualAddresses": [
"172.16.101.133"
],
"virtualPort": 443,
"snat": "auto"
},
"pool.acme_labs": {
"loadBalancingMode": "least-connections-member",
"members": [
{
"addressDiscovery": "static",
"servicePort": 80,
"serverAddresses": [
"172.16.102.5"
],
"shareNodes": true
}
],
"monitors": [
{
"bigip": "/Common/http"
}
],
"class": "Pool"
},
"www.acmelabs.com": {
"class": "Certificate",
"certificate": "-----BEGIN CERTIFICATE-----\n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\n-----END CERTIFICATE-----",
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAt6FDfpu8jBbE8dew0m5t2ax/p6LE0mI0BMJIZA1TxglDvQjgVethDPWp7rTr655ZNuYUZ4p/QV/Uummo0NxhE4VQyIK1tcKnGs/tX2BVmx/augrcqOpGwZKAeKsRDxB8UBS/BmovlQQgRqBym3lg7AewI20BwtSvrCSviGmByBPW7cjOFoe8n706XZvEDFiZgj/OuV2V1giCzqqKUJ5mLAqSh25465IVcTJQxKkok668rHOgpUO2GDav7cnrtLm71Oxv6m64gcQJ+e2xzaxa0/OfykuXn4W84RFKwm6im3lAbgNI+CwCjTNtXXs88TxMG49GuTol9ddeS+4aF9GvCQIDAQABAoIBAGeJbdz9QppaXEFgNDryOM37DR8gD4nwBRSJ1vdS7GFE6AS19Id9aAM+oMoPCNaZOgRSRj77QDVEK1XQLXdWSwYOrTXhPUN2tXHQuy6DysDkfRdY+IHlVm/egsGG8t9jlDQy/mJHjPygjvJDlVtEXPm4e//9fni0IzkUlkR7+MkuMT3vvKGYnUNTlI1hJokcNJ75r91O82j+qQsmvJG3FOUn0DpnEBgIvEbFvD3wMHY1K/fTUsBVJMKkjjXmjykGB9y7V4oKHQLsxH+lrUneWdD/s23hoVgAV31YeXtf7mI/eWPJt6DiGwTfaNcNcptvwugsR/7jCWaKS9Hya/qbJuECgYEA6wNm2doCmwl6ksbKSik0VgVha7DN3VjoFTDFcYZNfjB/kr6/xSODbAxJMwQdevFj2eWQxeHZnJc96x2xWzCA3mp1BhNzcfT8XRZ4LMcLUpcl1VXUEVc562vL8AdVuSODDc/rBXP/aFSXGdE+ZSPhYBrNlnK1FY10aaGsrEaRxL8CgYEAyAcyKVKY+wpdweiVXsUHIHAwQUmi8pKd1j5KlCjJkn2Wtiqex0v1eDy2/iKrZDWRiRFE4WOIb7A9GYm7FfqDyn9WvVNI0bz8Ywi+bCTdawGZ8H328q3R4/xIPprGmKV6olQHHUGZUNkLTK+cDHK4w9JRSf9kB6PUgGnBTgZoFjcCgYB/E2bQ03ZnOLfjl8QYV7Fp9hzYa1DVqFZN5wJMQW+zlSvWQHhXc72Ddh06jbYXHWF9mAkxRs8xQgKEGJknEtIL8gp3D5tz+iFfgF/Y7oPr07jsYy15du3lo3MxxfWPV2ls1YlieHeZhWvy1NblP4KFQdj6yemqzsMsvvQsbzgw5wKBgQCASZ0yQ3c6Cnv3UWP7VAIuG8XXGZMYYFA6h9jtDPu6qDFwxATxbRYR916lvzaNHo4oiprSszNd7npBVsRWZEUCKolHA5NAcSStn34BfeNELdK9Gwy2uCRVRAhRnpKgdAEi+yFU8i2SXKGSnU5H7Yvyi4D3JITTIY+4jBseH53CIQKBgByjoPYp+eMXpUmg4W5M1irXGm8sjrRBKvnxu9L+etvajWIb+AUAtoNoQmcKpf8bBK84PdCwiDSQmRDbWieT9RsSqbyWOcQf2C2L0qujUb+bM+kSTYp4oAV/rukoZ46NHjYBE3NbI7HcspWbpu5zl0Ke9pLvDwFwrmRy5KM7EiSh\n-----END RSA PRIVATE KEY-----"
},
"cssl.acme_labs": {
"certificates": [
{
"certificate": "www.acmelabs.com"
}
],
"class": "TLS_Server",
"tls1_0Enabled": true,
"tls1_1Enabled": true,
"tls1_2Enabled": true,
"tls1_3Enabled": false,
"singleUseDhEnabled": false,
"insertEmptyFragmentsEnabled": true
},
"full_uri_decode": {
"class": "iRule",
"iRule": {
"base64": "d2hlbiBIVFRQX1JFUVVFU1QgewogICMgZGVjb2RlIG9yaWdpbmFsIFVSSS4KICBzZXQgdG1wVXJpIFtIVFRQOjp1cmldCiAgc2V0IHVyaSBbVVJJOjpkZWNvZGUgJHRtcFVyaV0KICAjIHJlcGVhdCBkZWNvZGluZyB1bnRpbCB0aGUgZGVjb2RlZCB2ZXJzaW9uIGVxdWFscyB0aGUgcHJldmlvdXMgdmFsdWUuCiAgd2hpbGUgeyAkdXJpIG5lICR0bXBVcmkgfSB7CiAgICBzZXQgdG1wVXJpICR1cmkKICAgIHNldCB1cmkgW1VSSTo6ZGVjb2RlICR0bXBVcmldCiAgfQogIEhUVFA6OnVyaSAkdXJpCiAgbG9nIGxvY2FsMC4gIk9yaWdpbmFsIFVSSTogW0hUVFA6OnVyaV0iCiAgbG9nIGxvY2FsMC4gIkZ1bGx5IGRlY29kZWQgVVJJOiAkdXJpIgp9"
}
}
}
}
}
I didn't mention it above, but you'll notice that the iRule is base64 encoded. The conversion to AS3 in VSCode did that automatically. You can do the same for the certificate and privateKey attributes as well if you want, but that'll need the base64 attribute within the curly brackets like the iRule.
Billy Mays here again...buy 1, get another free! Like the DNS app, there are a few things native to classic in this declaration that aren't supported in Next, so we need to make a few more changes after a few tests:
- I removed profileHTTP and profileTCP attributes from the Service_HTTPS class. These are allowed, but since I am not setting anything non-default, I don't need them. As is, they were not acceptable referencing bigip classic profiles
- Removed layer4 and translateServerPort attributes from the Service_HTTPS class as they are not currently supported in Next
- Removed tls1_Enabled, singleUseDhEnabled, and insertEmptyFragmentsEnabled attributes from TLS_Server class as they are not currently supported in Next.
- Added the ciphers attribute with RSA value to the TLS_Server class. The instance would not accept the deployment without this, I got an expired or invalid certificate error without it.
- Changed the iRules refererence in the Service_HTTPS class from a classic BIG-IP object to a local declaration object.
These final changes resulted in the following declaration I'll use with the API endpoints:
{
"class": "ADC",
"schemaVersion": "3.37.0",
"id": "urn:uuid:bd9c9728-8c20-4c4d-a625-68450e35e133",
"label": "Converted Declaration",
"remark": "Generated by Automation Config Converter",
"tenant2": {
"class": "Tenant",
"httpsapp1": {
"class": "Application",
"template": "shared",
"vip.acme_labs": {
"pool": "pool.acme_labs",
"iRules": [
"full_uri_decode"
],
"translateServerAddress": true,
"class": "Service_HTTPS",
"serverTLS": "cssl.acme_labs",
"redirect80": false,
"virtualAddresses": [
"172.16.101.133"
],
"virtualPort": 443,
"snat": "auto"
},
"pool.acme_labs": {
"loadBalancingMode": "least-connections-member",
"members": [
{
"addressDiscovery": "static",
"servicePort": 80,
"serverAddresses": [
"172.16.102.5"
],
"shareNodes": true
}
],
"monitors": [
"http"
],
"class": "Pool"
},
"www.acmelabs.com": {
"class": "Certificate",
"certificate": "-----BEGIN CERTIFICATE-----\nMIIC3DCCAcSgAwIBAgIGAZAW7PncMA0GCSqGSIb3DQEBDQUAMC8xCzAJBgNVBAYTAlVTMSAwHgYDVQQDExdteXNlbGZzaWduZWQudGVzdC5sb2NhbDAeFw0yNDA2MTQxMzI1NDdaFw0zNDA2MTIxMzI1NDdaMC8xCzAJBgNVBAYTAlVTMSAwHgYDVQQDExdteXNlbGZzaWduZWQudGVzdC5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMYpeRm4f1mPgW7STMM4gZXZ5p02nCWshNwVkaOLpRJAOdR2ZpuhLW4tWpAssvmTRlS0cFjZKA6ecVg4Q7+wvw7dIG8gVAviOqmHb6sDaomBTn3+ISFYW0Uxb1GNvZqlktJQI7hCsaS5Kf/f4pImVa8jQffWTdgLwxCm+0suaXy1XykVOCdOs1lsCOHjMoVREWxLIAtzMpqdO+8IRhSJgPJPf3GnY861T0LDjuT5rgwY1qK/H2NuEcPWOWVtqTN9aQAz9cKxDbJq48U8adzrl6G8uUYlEPEtneePErygy8wRk8KkVNkuDj5gQKxi3b3Q8/K7bPhh9aUnZRQWmhVTw2kCAwEAATANBgkqhkiG9w0BAQ0FAAOCAQEAOh3doWxnjb5j5XojnEtYUWJG6yw9a3xZhEiq7myWz7apmy5eAe0QAL9kFAuiBwgjqwzPCXzMDp21FdLC+o9Znx5A8kXE2W2G+h36kc21f3v0jumRdkU1zZ9py9iKHAOUSAYsALNWH4mosFFbodpqcFZL7Fqmh/AoIcqY3GqSWOZ6geYbMIOwTZFnsuE1LTjJrnypz1ZyglGoftzU9j501aq3eJ3YUyRIZ28/ARJxn4sUfdvjvs31EdFEOOC6hwN2U7JXdWWK/fATTenglSkUqChJRW6kRL7uFf6FCCZjXyGINJnOYVz+8gxDWA557+ogYfEquQVML5gvMK9Ff67W6A==\n-----END CERTIFICATE-----",
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAxil5Gbh/WY+BbtJMwziBldnmnTacJayE3BWRo4ulEkA51HZmm6Etbi1akCyy+ZNGVLRwWNkoDp5xWDhDv7C/Dt0gbyBUC+I6qYdvqwNqiYFOff4hIVhbRTFvUY29mqWS0lAjuEKxpLkp/9/ikiZVryNB99ZN2AvDEKb7Sy5pfLVfKRU4J06zWWwI4eMyhVERbEsgC3Mymp077whGFImA8k9/cadjzrVPQsOO5PmuDBjWor8fY24Rw9Y5ZW2pM31pADP1wrENsmrjxTxp3OuXoby5RiUQ8S2d548SvKDLzBGTwqRU2S4OPmBArGLdvdDz8rts+GH1pSdlFBaaFVPDaQIDAQABAoIBAEUsIv7MfX/o7TifJnabGfkSOEM21ej8wOAGk3EwhO3LB6TXs9etuqsUH+HmCI/ATjOxTOpm22nG+y/dbCDU9MyeefzwnwYK8YlOIrfimGTpg1nNxQjby/hqWj5wqPf7xjWuDdn7RgGHNVcBcxirUwuw1g1KfJ/m8y+z6lKDIAWMuPegPFgQy0UoJmE5gjtdNYuRrPKESfjdgYhbmzl75k2zqm35Ngwgvp6YYq1jeGpDb4lDBDvn9KdpScC1y9w++7k4n1AyMZXsfgn3oSiFp9G6rZNraykOPYkQu309DVBqYtW0DHSU/xDYh1MTwJEwhcISYu12s2PIDGv/prgMRwUCgYEA7SdaqLT0B/btPkO84gnRx40rgsSM8gPewiVHerc95/tR6tCdMg1eNGJEK+biZMR/oxLQ3Ajr14BE3O8Dxhcqx/5vdo5qrX2oytDkl87oObK5rL0kdlmg/SQdnCsG/GkGtZlXLdMmjibSglGn23E69bsS0+IHspZnT2KHb1v1OZcCgYEA1ejfdHxmyOe+ke9QYn0umLLI/u6vDm6qkzEJrmzkpjrQrwftYRBeSr7CRJdRWtQ6dKA6kGZEfumFMg0ptFtwDGuLnzXek8UC3gKXjDnHyTugTXLprgB3A1AUYy0jvxmMTY8/AZLmDnqXma1WFnyxIUrTbzQq6uJPD4b33cWciv8CgYEAumnT1ocex1/uzqG6SEeFsYEjMZBEZjxqjlt1W13MeJxRoO1Ikz50zWJsycGcNa9L0SiKKluM3wGBn9T1N3GgfEJg5WU/L4517q7S8Q1/91KopsKqdakwZatM5yPfQutfjcGyCGBQjy6vDCcZdeIEgYICY7DpchTNslX1tbAoC5MCgYA9f9hOyz1Z4Zbeqik4R7lP2YcEFGdsBNExxFV+Onx6dkptKCBNWcFiR/necorHTGEKCs8LmPt0aXsL6tDks61BROI9geVeIrQyVBhyDmKsLmJmIfWhOyz8XNefs+ilFplJ6zc4Ip3V59USL82iZXMfmT20qRD1ut70Hd/BeQEKzQKBgQCoiTGlal7FaOHZmjvPOc6lzvOC2RIZL3yT5U1r9XsMFC2pPU/YinTc0cEpMmbeqLKuINjKOYyVp8HZEdpB6atU/WYDT2INe7VaphWpHkd5F56plzo0hlTDr1eFlHBsj23MVFR/UvpL0PeGzfnBd7ga2s0ymWDDnIhMJKzwu5GvDw==\n-----END RSA PRIVATE KEY-----"
},
"cssl.acme_labs": {
"certificates": [
{
"certificate": "www.acmelabs.com"
}
],
"ciphers": "RSA",
"class": "TLS_Server",
"tls1_1Enabled": true,
"tls1_2Enabled": true,
"tls1_3Enabled": false
},
"full_uri_decode": {
"class": "iRule",
"iRule": {
"base64": "d2hlbiBIVFRQX1JFUVVFU1QgewogICMgZGVjb2RlIG9yaWdpbmFsIFVSSS4KICBzZXQgdG1wVXJpIFtIVFRQOjp1cmldCiAgc2V0IHVyaSBbVVJJOjpkZWNvZGUgJHRtcFVyaV0KICAjIHJlcGVhdCBkZWNvZGluZyB1bnRpbCB0aGUgZGVjb2RlZCB2ZXJzaW9uIGVxdWFscyB0aGUgcHJldmlvdXMgdmFsdWUuCiAgd2hpbGUgeyAkdXJpIG5lICR0bXBVcmkgfSB7CiAgICBzZXQgdG1wVXJpICR1cmkKICAgIHNldCB1cmkgW1VSSTo6ZGVjb2RlICR0bXBVcmldCiAgfQogIEhUVFA6OnVyaSAkdXJpCiAgbG9nIGxvY2FsMC4gIk9yaWdpbmFsIFVSSTogW0hUVFA6OnVyaV0iCiAgbG9nIGxvY2FsMC4gIkZ1bGx5IGRlY29kZWQgVVJJOiAkdXJpIgp9"
}
}
}
}
}
OK, we have our declarations handy, now we can move on to working this the API endpoints!
CRUD operations
We sure love our acronyms in tech, don't we? CRUD stands for create, read, update, and delete. These are the most common operations for interacting with an API. (If you've used the iControl REST interface before on classic BIG-IP, you know that we need to perform additional operations like running commands (load, save, run, etc), so that needed to be folded in somehow to the CRUD model. We'll address those use cases in future articles.) Before we can use the API endpoints, however, we need to be authenticated to the Central Manager. This requires a login request that returns a bearer token to be used in subsequent requests. I wrote a short bash script to get the token which I set to a local variable in my shell. First, the script:
#!/bin/zsh
token=$(curl -ks --location 'https://172.16.31.105/api/login' \
--header 'Content-Type: application/json' \
--data '{
"username": "admin",
"password": "notsofastmyfriend"
}' | jq -r '.access_token')
echo $token
Next, setting the token variable for use in future commands:
jrahm@mymac as3testing % token=$(./gt.sh)
jrahm@mymac as3testing % echo $token
D1RrEpn1RCHpm5FrGCIiXrwu3coSO8vWGT8e8kHLd2QbeUUiGAgw6pFb1B2l2bHeG7KsrqiipfuNGbx/DaCyUDQ0niaDiQizHIj6w7xOIWLNd5e/Bz2emGskM959E7CnMRTV36qPpu0SLDJsdvThZf6wLvm9oe5cX25Uqzf2/6Y+eNxDLs2WjsA4IFFRO2QWkjrq807kxJIoIX8BvICSxyjlx7PEQkWBAdUV7z6zayX03FtA3lqR66dzzMtIr9L7na+T7/i5cqSETGYQYt1z4a996oA/jMcAEy5J6PsuinCdN3ZZNt5Bfi4ck/5/bA3RJEZR8niU5u77DGasckdcUlRjl0/8UOgmEq19BRopAGFCXvRyiX/g6CVR6NDNG5dlmVjVcJ2+IzYJ8utGfr7raKMIgDIEn/G1AVqy0kj+x2ANdHpo0PQG678JoXChHObiDwjcOMrUiW2cC/YMLp36lcBEgp0uySokSwwYBTJjLJezFE74I+x154yDIWYD0+I8xbIqAHA4a3IxMljR14wowIJp84SxfeuJcrcUAZESzw==
Now that I don't have to worry about re-upping on my token while working with curl at the command line, let's work through each of these CRUD operations in order.
Application service create operation
The create operation is accomplished with an HTTP POST method. As we are creating an object, we need to send some data along with that. That data in our case is the AS3 declaration. I put each declaration in a file
Compatibility API
It's a single request to deploy the workload with the compatibility interface to the /api/v1/spaces/default/appsvcs/declare endpoint with a target_address of the instances as a query parameter. Interestingly, the successful declaration is returned to you in its entirety in the response.
DNS App
jrahm@mymac as3testing % curl -sk \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d "@dns-app.json" \
--location 'https://172.16.2.105/api/v1/spaces/default/appsvcs/declare?target_address=172.16.2.161' | jq .
{
"declaration": {
"class": "ADC",
"id": "urn:uuid:3a71dceb-f56c-4dc1-901a-2feae0244c46",
"label": "Converted Declaration",
"remark": "Generated by Automation Config Converter",
"schemaVersion": "3.37.0",
"tenant1": {
"class": "Tenant",
"dnsapp1": {
"class": "Application",
"pool.ns-cluster-1": {
"class": "Pool",
"members": [
{
"addressDiscovery": "static",
"serverAddresses": [
"10.10.100.101",
"10.10.100.102",
"10.10.100.103",
"10.10.100.104"
],
"servicePort": 53,
"shareNodes": true
}
],
"monitors": [
"icmp"
]
},
"template": "shared",
"vip.ns-cluster-1": {
"class": "Service_UDP",
"pool": "pool.ns-cluster-1",
"snat": "auto",
"translateServerAddress": true,
"virtualAddresses": [
"10.100.100.100"
],
"virtualPort": 53
}
}
}
},
"results": [
{
"code": 200,
"host": "172.16.2.161",
"message": "success",
"runTime": 1948,
"tenant": "tenant1"
}
]
}
HTTPS App
jrahm@mymac as3testing % curl -sk \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d "@https-app.json" \
--location 'https://172.16.2.105/api/v1/spaces/default/appsvcs/declare?target_address=172.16.2.161' | jq .
{
"declaration": {
"class": "ADC",
"id": "urn:uuid:bd9c9728-8c20-4c4d-a625-68450e35e133",
"label": "Converted Declaration",
"remark": "Generated by Automation Config Converter",
"schemaVersion": "3.37.0",
"tenant2": {
"class": "Tenant",
"httpsapp1": {
"class": "Application",
"cssl.acme_labs": {
"certificates": [
{
"certificate": "www.acmelabs.com"
}
],
"ciphers": "RSA",
"class": "TLS_Server",
"tls1_1Enabled": true,
"tls1_2Enabled": true,
"tls1_3Enabled": false
},
"full_uri_decode": {
"class": "iRule",
"iRule": {
"base64": "d2hlbiBIVFRQX1JFUVVFU1QgewogICMgZGVjb2RlIG9yaWdpbmFsIFVSSS4KICBzZXQgdG1wVXJpIFtIVFRQOjp1cmldCiAgc2V0IHVyaSBbVVJJOjpkZWNvZGUgJHRtcFVyaV0KICAjIHJlcGVhdCBkZWNvZGluZyB1bnRpbCB0aGUgZGVjb2RlZCB2ZXJzaW9uIGVxdWFscyB0aGUgcHJldmlvdXMgdmFsdWUuCiAgd2hpbGUgeyAkdXJpIG5lICR0bXBVcmkgfSB7CiAgICBzZXQgdG1wVXJpICR1cmkKICAgIHNldCB1cmkgW1VSSTo6ZGVjb2RlICR0bXBVcmldCiAgfQogIEhUVFA6OnVyaSAkdXJpCiAgbG9nIGxvY2FsMC4gIk9yaWdpbmFsIFVSSTogW0hUVFA6OnVyaV0iCiAgbG9nIGxvY2FsMC4gIkZ1bGx5IGRlY29kZWQgVVJJOiAkdXJpIgp9"
}
},
"pool.acme_labs": {
"class": "Pool",
"loadBalancingMode": "least-connections-member",
"members": [
{
"addressDiscovery": "static",
"serverAddresses": [
"172.16.102.5"
],
"servicePort": 80,
"shareNodes": true
}
],
"monitors": [
"http"
]
},
"template": "shared",
"vip.acme_labs": {
"class": "Service_HTTPS",
"iRules": [
"full_uri_decode"
],
"pool": "pool.acme_labs",
"redirect80": false,
"serverTLS": "cssl.acme_labs",
"snat": "auto",
"translateServerAddress": true,
"virtualAddresses": [
"172.16.101.133"
],
"virtualPort": 443
},
"www.acmelabs.com": {
"certificate": "-----BEGIN CERTIFICATE-----\nMIIC3DCCAcSgAwIBAgIGAZAW7PncMA0GCSqGSIb3DQEBDQUAMC8xCzAJBgNVBAYTAlVTMSAwHgYDVQQDExdteXNlbGZzaWduZWQudGVzdC5sb2NhbDAeFw0yNDA2MTQxMzI1NDdaFw0zNDA2MTIxMzI1NDdaMC8xCzAJBgNVBAYTAlVTMSAwHgYDVQQDExdteXNlbGZzaWduZWQudGVzdC5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMYpeRm4f1mPgW7STMM4gZXZ5p02nCWshNwVkaOLpRJAOdR2ZpuhLW4tWpAssvmTRlS0cFjZKA6ecVg4Q7+wvw7dIG8gVAviOqmHb6sDaomBTn3+ISFYW0Uxb1GNvZqlktJQI7hCsaS5Kf/f4pImVa8jQffWTdgLwxCm+0suaXy1XykVOCdOs1lsCOHjMoVREWxLIAtzMpqdO+8IRhSJgPJPf3GnY861T0LDjuT5rgwY1qK/H2NuEcPWOWVtqTN9aQAz9cKxDbJq48U8adzrl6G8uUYlEPEtneePErygy8wRk8KkVNkuDj5gQKxi3b3Q8/K7bPhh9aUnZRQWmhVTw2kCAwEAATANBgkqhkiG9w0BAQ0FAAOCAQEAOh3doWxnjb5j5XojnEtYUWJG6yw9a3xZhEiq7myWz7apmy5eAe0QAL9kFAuiBwgjqwzPCXzMDp21FdLC+o9Znx5A8kXE2W2G+h36kc21f3v0jumRdkU1zZ9py9iKHAOUSAYsALNWH4mosFFbodpqcFZL7Fqmh/AoIcqY3GqSWOZ6geYbMIOwTZFnsuE1LTjJrnypz1ZyglGoftzU9j501aq3eJ3YUyRIZ28/ARJxn4sUfdvjvs31EdFEOOC6hwN2U7JXdWWK/fATTenglSkUqChJRW6kRL7uFf6FCCZjXyGINJnOYVz+8gxDWA557+ogYfEquQVML5gvMK9Ff67W6A==\n-----END CERTIFICATE-----",
"class": "Certificate",
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAxil5Gbh/WY+BbtJMwziBldnmnTacJayE3BWRo4ulEkA51HZmm6Etbi1akCyy+ZNGVLRwWNkoDp5xWDhDv7C/Dt0gbyBUC+I6qYdvqwNqiYFOff4hIVhbRTFvUY29mqWS0lAjuEKxpLkp/9/ikiZVryNB99ZN2AvDEKb7Sy5pfLVfKRU4J06zWWwI4eMyhVERbEsgC3Mymp077whGFImA8k9/cadjzrVPQsOO5PmuDBjWor8fY24Rw9Y5ZW2pM31pADP1wrENsmrjxTxp3OuXoby5RiUQ8S2d548SvKDLzBGTwqRU2S4OPmBArGLdvdDz8rts+GH1pSdlFBaaFVPDaQIDAQABAoIBAEUsIv7MfX/o7TifJnabGfkSOEM21ej8wOAGk3EwhO3LB6TXs9etuqsUH+HmCI/ATjOxTOpm22nG+y/dbCDU9MyeefzwnwYK8YlOIrfimGTpg1nNxQjby/hqWj5wqPf7xjWuDdn7RgGHNVcBcxirUwuw1g1KfJ/m8y+z6lKDIAWMuPegPFgQy0UoJmE5gjtdNYuRrPKESfjdgYhbmzl75k2zqm35Ngwgvp6YYq1jeGpDb4lDBDvn9KdpScC1y9w++7k4n1AyMZXsfgn3oSiFp9G6rZNraykOPYkQu309DVBqYtW0DHSU/xDYh1MTwJEwhcISYu12s2PIDGv/prgMRwUCgYEA7SdaqLT0B/btPkO84gnRx40rgsSM8gPewiVHerc95/tR6tCdMg1eNGJEK+biZMR/oxLQ3Ajr14BE3O8Dxhcqx/5vdo5qrX2oytDkl87oObK5rL0kdlmg/SQdnCsG/GkGtZlXLdMmjibSglGn23E69bsS0+IHspZnT2KHb1v1OZcCgYEA1ejfdHxmyOe+ke9QYn0umLLI/u6vDm6qkzEJrmzkpjrQrwftYRBeSr7CRJdRWtQ6dKA6kGZEfumFMg0ptFtwDGuLnzXek8UC3gKXjDnHyTugTXLprgB3A1AUYy0jvxmMTY8/AZLmDnqXma1WFnyxIUrTbzQq6uJPD4b33cWciv8CgYEAumnT1ocex1/uzqG6SEeFsYEjMZBEZjxqjlt1W13MeJxRoO1Ikz50zWJsycGcNa9L0SiKKluM3wGBn9T1N3GgfEJg5WU/L4517q7S8Q1/91KopsKqdakwZatM5yPfQutfjcGyCGBQjy6vDCcZdeIEgYICY7DpchTNslX1tbAoC5MCgYA9f9hOyz1Z4Zbeqik4R7lP2YcEFGdsBNExxFV+Onx6dkptKCBNWcFiR/necorHTGEKCs8LmPt0aXsL6tDks61BROI9geVeIrQyVBhyDmKsLmJmIfWhOyz8XNefs+ilFplJ6zc4Ip3V59USL82iZXMfmT20qRD1ut70Hd/BeQEKzQKBgQCoiTGlal7FaOHZmjvPOc6lzvOC2RIZL3yT5U1r9XsMFC2pPU/YinTc0cEpMmbeqLKuINjKOYyVp8HZEdpB6atU/WYDT2INe7VaphWpHkd5F56plzo0hlTDr1eFlHBsj23MVFR/UvpL0PeGzfnBd7ga2s0ymWDDnIhMJKzwu5GvDw==\n-----END RSA PRIVATE KEY-----"
}
}
}
},
"results": [
{
"code": 200,
"host": "172.16.2.161",
"message": "success",
"runTime": 1950,
"tenant": "tenant2"
}
]
}
Documents API
With this approach, you send the document first with the /api/v1/spaces/default/appsvcs/documents endpoint and then deploy with the /api/v1/spaces/default/appsvcs/documents/<id>/deployments endpoint. The document and deployment each have their own object ID, and then the deployment also has a task ID that can be referenced in the logs.
DNS App
jrahm@mymac as3testing % curl -skX POST \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d "@dns-app.json" \
https://172.16.2.105/api/v1/spaces/default/appsvcs/documents | jq .
{
"Message": "Application service created successfully",
"_links": {
"self": {
"href": "/api/v1/spaces/default/appsvcs/documents/d5d0a360-75ec-434c-9802-62083a26c4d3"
}
},
"id": "d5d0a360-75ec-434c-9802-62083a26c4d3"
}
jrahm@mymac as3testing % curl -skX POST \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d '{"target": "172.16.2.161"}' \
https://172.16.2.105/api/v1/spaces/default/appsvcs/documents/d5d0a360-75ec-434c-9802-62083a26c4d3/deployments | jq .
{
"Message": "Deployment task created successfully",
"_links": {
"self": {
"href": "/api/v1/spaces/default/appsvcs/documents/d5d0a360-75ec-434c-9802-62083a26c4d3/deployments"
}
},
"id": "ed48899b-fcb0-4a60-b8f2-2c0e012aa28d",
"task_id": "771beda9-5ca4-4049-bebc-97b9d52da524"
}
HTTPS App
jrahm@mymac as3testing % curl -skX POST \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d "@https-app.json" \
https://172.16.2.105/api/v1/spaces/default/appsvcs/documents | jq .
{
"Message": "Application service created successfully",
"_links": {
"self": {
"href": "/api/v1/spaces/default/appsvcs/documents/3102ce15-e3d4-498f-a466-60f4bf02c2ab"
}
},
"id": "3102ce15-e3d4-498f-a466-60f4bf02c2ab"
}
jrahm@mymac as3testing % curl -skX POST \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d '{"target": "172.16.2.161"}' \
https://172.16.2.105/api/v1/spaces/default/appsvcs/documents/3102ce15-e3d4-498f-a466-60f4bf02c2ab/deployments | jq .
{
"Message": "Deployment task created successfully",
"_links": {
"self": {
"href": "/api/v1/spaces/default/appsvcs/documents/3102ce15-e3d4-498f-a466-60f4bf02c2ab/deployments"
}
},
"id": "400e2b06-b451-4035-a26b-beaf90b283a5",
"task_id": "f529800a-f515-4bec-9cfe-1f3214dec229"
}
Central Manager view of API-deployed apps
This is the result in Central Manager after deploying the two applications via the two different methodologies.
Notice the different naming scheme applied to each approach.
Application service read operation
The read operation is accomplished with an HTTP GET method. No payload is necessary on the request.
Compatibility API
Note here that both the DNS and HTTP apps will be returned, and for that matter, both could have been deployed together as well! Also note that this is for apps on the targeted instance only, however. The AS3 deployments follow the curl command options.
jrahm@mymac as3testing % curl -sk \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
"https://172.16.2.105/api/v1/spaces/default/appsvcs/declare?target_address=172.16.2.161" | jq .
{
"class": "ADC",
"controls": null,
"schemaVersion": "3.0.0",
"target": {
"address": "172.16.2.161"
},
"tenant1": {
"class": "Tenant",
"dnsapp1": {
"class": "Application",
"pool.ns-cluster-1": {
"class": "Pool",
"members": [
{
"addressDiscovery": "static",
"serverAddresses": [
"10.10.100.101",
"10.10.100.102",
"10.10.100.103",
"10.10.100.104"
],
"servicePort": 53,
"shareNodes": true
}
],
"monitors": [
"icmp"
]
},
"template": "shared",
"vip.ns-cluster-1": {
"class": "Service_UDP",
"pool": "pool.ns-cluster-1",
"snat": "auto",
"translateServerAddress": true,
"virtualAddresses": [
"10.100.100.101"
],
"virtualPort": 53
}
}
},
"tenant2": {
"class": "Tenant",
"httpsapp1": {
"class": "Application",
"cssl.acme_labs": {
"certificates": [
{
"certificate": "www.acmelabs.com"
}
],
"ciphers": "RSA",
"class": "TLS_Server",
"tls1_1Enabled": true,
"tls1_2Enabled": true,
"tls1_3Enabled": false
},
"full_uri_decode": {
"class": "iRule",
"iRule": {
"base64": "d2hlbiBIVFRQX1JFUVVFU1QgewogICMgZGVjb2RlIG9yaWdpbmFsIFVSSS4KICBzZXQgdG1wVXJpIFtIVFRQOjp1cmldCiAgc2V0IHVyaSBbVVJJOjpkZWNvZGUgJHRtcFVyaV0KICAjIHJlcGVhdCBkZWNvZGluZyB1bnRpbCB0aGUgZGVjb2RlZCB2ZXJzaW9uIGVxdWFscyB0aGUgcHJldmlvdXMgdmFsdWUuCiAgd2hpbGUgeyAkdXJpIG5lICR0bXBVcmkgfSB7CiAgICBzZXQgdG1wVXJpICR1cmkKICAgIHNldCB1cmkgW1VSSTo6ZGVjb2RlICR0bXBVcmldCiAgfQogIEhUVFA6OnVyaSAkdXJpCiAgbG9nIGxvY2FsMC4gIk9yaWdpbmFsIFVSSTogW0hUVFA6OnVyaV0iCiAgbG9nIGxvY2FsMC4gIkZ1bGx5IGRlY29kZWQgVVJJOiAkdXJpIgp9"
}
},
"pool.acme_labs": {
"class": "Pool",
"loadBalancingMode": "least-connections-member",
"members": [
{
"addressDiscovery": "static",
"serverAddresses": [
"172.16.102.5"
],
"servicePort": 80,
"shareNodes": true
}
],
"monitors": [
"http"
]
},
"template": "shared",
"vip.acme_labs": {
"class": "Service_HTTPS",
"iRules": [
"full_uri_decode"
],
"pool": "pool.acme_labs",
"redirect80": false,
"serverTLS": "cssl.acme_labs",
"snat": "auto",
"translateServerAddress": true,
"virtualAddresses": [
"172.16.101.133"
],
"virtualPort": 443
},
"www.acmelabs.com": {
"certificate": "-----BEGIN CERTIFICATE-----\nMIIC3DCCAcSgAwIBAgIGAZAW7PncMA0GCSqGSIb3DQEBDQUAMC8xCzAJBgNVBAYTAlVTMSAwHgYDVQQDExdteXNlbGZzaWduZWQudGVzdC5sb2NhbDAeFw0yNDA2MTQxMzI1NDdaFw0zNDA2MTIxMzI1NDdaMC8xCzAJBgNVBAYTAlVTMSAwHgYDVQQDExdteXNlbGZzaWduZWQudGVzdC5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMYpeRm4f1mPgW7STMM4gZXZ5p02nCWshNwVkaOLpRJAOdR2ZpuhLW4tWpAssvmTRlS0cFjZKA6ecVg4Q7+wvw7dIG8gVAviOqmHb6sDaomBTn3+ISFYW0Uxb1GNvZqlktJQI7hCsaS5Kf/f4pImVa8jQffWTdgLwxCm+0suaXy1XykVOCdOs1lsCOHjMoVREWxLIAtzMpqdO+8IRhSJgPJPf3GnY861T0LDjuT5rgwY1qK/H2NuEcPWOWVtqTN9aQAz9cKxDbJq48U8adzrl6G8uUYlEPEtneePErygy8wRk8KkVNkuDj5gQKxi3b3Q8/K7bPhh9aUnZRQWmhVTw2kCAwEAATANBgkqhkiG9w0BAQ0FAAOCAQEAOh3doWxnjb5j5XojnEtYUWJG6yw9a3xZhEiq7myWz7apmy5eAe0QAL9kFAuiBwgjqwzPCXzMDp21FdLC+o9Znx5A8kXE2W2G+h36kc21f3v0jumRdkU1zZ9py9iKHAOUSAYsALNWH4mosFFbodpqcFZL7Fqmh/AoIcqY3GqSWOZ6geYbMIOwTZFnsuE1LTjJrnypz1ZyglGoftzU9j501aq3eJ3YUyRIZ28/ARJxn4sUfdvjvs31EdFEOOC6hwN2U7JXdWWK/fATTenglSkUqChJRW6kRL7uFf6FCCZjXyGINJnOYVz+8gxDWA557+ogYfEquQVML5gvMK9Ff67W6A==\n-----END CERTIFICATE-----",
"class": "Certificate",
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAxil5Gbh/WY+BbtJMwziBldnmnTacJayE3BWRo4ulEkA51HZmm6Etbi1akCyy+ZNGVLRwWNkoDp5xWDhDv7C/Dt0gbyBUC+I6qYdvqwNqiYFOff4hIVhbRTFvUY29mqWS0lAjuEKxpLkp/9/ikiZVryNB99ZN2AvDEKb7Sy5pfLVfKRU4J06zWWwI4eMyhVERbEsgC3Mymp077whGFImA8k9/cadjzrVPQsOO5PmuDBjWor8fY24Rw9Y5ZW2pM31pADP1wrENsmrjxTxp3OuXoby5RiUQ8S2d548SvKDLzBGTwqRU2S4OPmBArGLdvdDz8rts+GH1pSdlFBaaFVPDaQIDAQABAoIBAEUsIv7MfX/o7TifJnabGfkSOEM21ej8wOAGk3EwhO3LB6TXs9etuqsUH+HmCI/ATjOxTOpm22nG+y/dbCDU9MyeefzwnwYK8YlOIrfimGTpg1nNxQjby/hqWj5wqPf7xjWuDdn7RgGHNVcBcxirUwuw1g1KfJ/m8y+z6lKDIAWMuPegPFgQy0UoJmE5gjtdNYuRrPKESfjdgYhbmzl75k2zqm35Ngwgvp6YYq1jeGpDb4lDBDvn9KdpScC1y9w++7k4n1AyMZXsfgn3oSiFp9G6rZNraykOPYkQu309DVBqYtW0DHSU/xDYh1MTwJEwhcISYu12s2PIDGv/prgMRwUCgYEA7SdaqLT0B/btPkO84gnRx40rgsSM8gPewiVHerc95/tR6tCdMg1eNGJEK+biZMR/oxLQ3Ajr14BE3O8Dxhcqx/5vdo5qrX2oytDkl87oObK5rL0kdlmg/SQdnCsG/GkGtZlXLdMmjibSglGn23E69bsS0+IHspZnT2KHb1v1OZcCgYEA1ejfdHxmyOe+ke9QYn0umLLI/u6vDm6qkzEJrmzkpjrQrwftYRBeSr7CRJdRWtQ6dKA6kGZEfumFMg0ptFtwDGuLnzXek8UC3gKXjDnHyTugTXLprgB3A1AUYy0jvxmMTY8/AZLmDnqXma1WFnyxIUrTbzQq6uJPD4b33cWciv8CgYEAumnT1ocex1/uzqG6SEeFsYEjMZBEZjxqjlt1W13MeJxRoO1Ikz50zWJsycGcNa9L0SiKKluM3wGBn9T1N3GgfEJg5WU/L4517q7S8Q1/91KopsKqdakwZatM5yPfQutfjcGyCGBQjy6vDCcZdeIEgYICY7DpchTNslX1tbAoC5MCgYA9f9hOyz1Z4Zbeqik4R7lP2YcEFGdsBNExxFV+Onx6dkptKCBNWcFiR/necorHTGEKCs8LmPt0aXsL6tDks61BROI9geVeIrQyVBhyDmKsLmJmIfWhOyz8XNefs+ilFplJ6zc4Ip3V59USL82iZXMfmT20qRD1ut70Hd/BeQEKzQKBgQCoiTGlal7FaOHZmjvPOc6lzvOC2RIZL3yT5U1r9XsMFC2pPU/YinTc0cEpMmbeqLKuINjKOYyVp8HZEdpB6atU/WYDT2INe7VaphWpHkd5F56plzo0hlTDr1eFlHBsj23MVFR/UvpL0PeGzfnBd7ga2s0ymWDDnIhMJKzwu5GvDw==\n-----END RSA PRIVATE KEY-----"
}
}
}
}
Documents API
With this interface, Central Manager lists out all the documents, including the compatibility interface applications.
jrahm@mymac as3testing % curl -sk \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
https://172.16.2.105/api/v1/spaces/default/appsvcs/documents | jq ._embedded.appsvcs
[
{
"_links": {
"self": {
"href": "/api/v1/spaces/default/appsvcs/documents/3102ce15-e3d4-498f-a466-60f4bf02c2ab"
}
},
"created": "2024-06-17T17:38:08.186126Z",
"deployments": [
{
"id": "400e2b06-b451-4035-a26b-beaf90b283a5",
"instance_id": "a4148c93-5306-4605-b8bb-92d6b1f78c26",
"target": {
"instance_ip": "172.16.2.161"
},
"last_successful_deploy_time": "2024-06-17T17:38:42.404675Z",
"modified": "2024-06-17T17:38:42.404675Z",
"last_record": {
"id": "64894415-38d0-49f9-989d-8f00c88196b3",
"task_id": "f529800a-f515-4bec-9cfe-1f3214dec229",
"start_time": "2024-06-17T17:38:41.103539Z",
"status": "completed"
}
}
],
"deployments_count": {
"total": 1,
"completed": 1
},
"id": "3102ce15-e3d4-498f-a466-60f4bf02c2ab",
"name": "httpsapp1",
"tenant_name": "tenant2",
"type": "AS3"
},
{
"_links": {
"self": {
"href": "/api/v1/spaces/default/appsvcs/documents/7938a0a2-b5d4-4687-99f8-e73d9e6b3d51"
}
},
"created": "2024-06-17T17:52:41.397543Z",
"deployments": [
{
"id": "0c50d882-f8d1-4833-af31-2b71e465f2f5",
"instance_id": "a4148c93-5306-4605-b8bb-92d6b1f78c26",
"target": {
"instance_ip": "172.16.2.161"
},
"last_successful_deploy_time": "2024-06-17T17:54:51.531445Z",
"modified": "2024-06-17T17:54:51.531445Z",
"last_record": {
"id": "a8f786a5-f1c6-4f99-83bb-59cc024e1c34",
"task_id": "ee1a3afa-c9d4-4e29-9271-632bbb93b6e7",
"start_time": "2024-06-17T17:54:50.167979Z",
"status": "completed"
}
}
],
"deployments_count": {
"total": 1,
"completed": 1
},
"id": "7938a0a2-b5d4-4687-99f8-e73d9e6b3d51",
"modified": "2024-06-17T17:54:50.164813Z",
"name": "tenant1.dnsapp1.NzKPI4xZ",
"tenant_name": "default",
"type": "AS3"
},
{
"_links": {
"self": {
"href": "/api/v1/spaces/default/appsvcs/documents/87ec6d3a-063d-4660-b32a-08cf183a21a8"
}
},
"created": "2024-06-17T17:50:02.621622Z",
"deployments": [
{
"id": "5da24b69-491e-45a1-b8eb-18395c4b2b12",
"instance_id": "a4148c93-5306-4605-b8bb-92d6b1f78c26",
"target": {
"instance_ip": "172.16.2.161"
},
"last_successful_deploy_time": "2024-06-17T17:50:03.929715Z",
"modified": "2024-06-17T17:50:03.929715Z",
"last_record": {
"id": "1f3bc580-da07-4c26-b4d2-7e8bcb632869",
"task_id": "dc8fbdc8-4dd0-4aeb-9e7d-cf3038d42c07",
"start_time": "2024-06-17T17:50:02.640417Z",
"status": "completed"
}
}
],
"deployments_count": {
"total": 1,
"completed": 1
},
"id": "87ec6d3a-063d-4660-b32a-08cf183a21a8",
"name": "tenant2.httpsapp1.NzKPI4xZ",
"tenant_name": "default",
"type": "AS3"
},
{
"_links": {
"self": {
"href": "/api/v1/spaces/default/appsvcs/documents/d5d0a360-75ec-434c-9802-62083a26c4d3"
}
},
"created": "2024-06-17T17:56:04.957896Z",
"deployments": [
{
"id": "ed48899b-fcb0-4a60-b8f2-2c0e012aa28d",
"instance_id": "a4148c93-5306-4605-b8bb-92d6b1f78c26",
"target": {
"instance_ip": "172.16.2.161"
},
"last_successful_deploy_time": "2024-06-17T17:56:34.410606Z",
"modified": "2024-06-17T17:56:34.410606Z",
"last_record": {
"id": "7178d940-5ae7-4c18-bca6-6f7d14604d5e",
"task_id": "771beda9-5ca4-4049-bebc-97b9d52da524",
"start_time": "2024-06-17T17:56:33.123687Z",
"status": "completed"
}
}
],
"deployments_count": {
"total": 1,
"completed": 1
},
"id": "d5d0a360-75ec-434c-9802-62083a26c4d3",
"name": "dnsapp1",
"tenant_name": "tenant1",
"type": "AS3"
}
]
Application service update operation
For the update operation, this could be an HTTP PUT or PATCH method, depending on what the endpoints support. PUT is supposed to be a total replacement and PATCH a partial replacement, but I've found the implementations of many APIs to not follow this pattern. These methods require a payload with the request. In this section forward, we'll focus more on the mechanics of the API rather than the specifics on the application services, so I might work with one or the other unless both need attention.
Compatibility API
This is where I throw a curveball at you! As the compatibility interface is intended to match BIG-IP classic AS3 behavior so it is in fact, uh, compatible, the operation for an update is actually still a POST as if you're creating the application service for the first time, so there's no need to do anything new here. Make the change to your declaration and POST as shown in the create section and you're good to go.
Documents API
To modify the AS3 application service, the API reference states that the PUT method should be used, and the declaration should be complete. So I changed the virtual server IP address in the declaration and sent a PUT request to the appropriate document ID and it was successfully deployed.
jrahm@mymac as3testing % curl -skX PUT \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d "@dns-app.json" \
https://172.16.2.105/api/v1/spaces/default/appsvcs/documents/d5d0a360-75ec-434c-9802-62083a26c4d3 | jq .
{
"_links": {
"self": {
"href": "/api/v1/spaces/default/appsvcs/documents/d5d0a360-75ec-434c-9802-62083a26c4d3"
}
},
"deployments": [
{
"Message": "Update deployment task created",
"id": "ed48899b-fcb0-4a60-b8f2-2c0e012aa28d",
"task_id": "b07fa2de-7d73-4c7e-988a-1383cc45e441"
}
],
"id": "d5d0a360-75ec-434c-9802-62083a26c4d3",
"message": "Application service updated successfully"
}
Application service delete operation
An HTTP DELETE method performs the delete operation. Typically you just need the object ID in the request URL to remove the desired object. This is the fun part, at least in the lab environment. BLOW STUFF UP! Just kidding, but not really. I, like the Joker before me, like to make things go bye bye. Maybe if the Joker could have been a force for good he'd be a great chaos engineer.
Compatibility API
This is where I put up the RED FLAG and caution you to know what you're doing here. If you send a DELETE to the compatibility interface with an empty payload you can blow away ALL the AS3 configuration on that instance. So don't do that... Instead, make sure you include the tenant name in the URI as shown below.
jrahm@mymac as3testing % curl -skX DELETE \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
--location 'https://172.16.2.105/api/v1/spaces/default/appsvcs/declare/tenant1?target_address=172.16.2.161'
{
"declaration":{},
"results":[
{
"code":200,
"host":"172.16.2.161",
"message":"success",
"runTime":1331,
"tenant":"tenant1"
}
]
}
Documents API
You have two options here. You can delete the deployment only (you'll need to provide the document ID and the deployment ID) and then choose whether to the leave the draft or delete it (I show the document delete as well):
jrahm@mymac as3testing % curl -skX DELETE \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d '{"target": "172.16.2.161"}' \
https://172.16.2.105/api/v1/spaces/default/appsvcs/documents/d5d0a360-75ec-434c-9802-62083a26c4d3/deployments/ed48899b-fcb0-4a60-b8f2-2c0e012aa28d | jq .
{
"Message": "Delete Deployment task created successfully",
"_links": {
"self": {
"href": "/api/v1/spaces/default/appsvcs/documents/d5d0a360-75ec-434c-9802-62083a26c4d3/deployments/ed48899b-fcb0-4a60-b8f2-2c0e012aa28d"
}
},
"id": "ed48899b-fcb0-4a60-b8f2-2c0e012aa28d",
"task_id": "9c5a8fe0-d8b9-4b41-a47f-3283586c88f1"
}
jrahm@mymac as3testing % curl -skX DELETE \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d '{"target": "172.16.2.161"}' \
https://172.16.2.105/api/v1/spaces/default/appsvcs/documents/d5d0a360-75ec-434c-9802-62083a26c4d3/ | jq .
{
"_links": {
"self": {
"href": "/api/v1/spaces/default/appsvcs/documents/d5d0a360-75ec-434c-9802-62083a26c4d3/"
}
},
"id": "d5d0a360-75ec-434c-9802-62083a26c4d3",
"message": "The application has been deleted successfully"
}
Or you can delete the document outright in one step which will clean up the deployment as well:
jrahm@mymac as3testing % curl -skX DELETE \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json" \
-d '{"target": "172.16.2.161"}' \
https://172.16.2.105/api/v1/spaces/default/appsvcs/documents/3102ce15-e3d4-498f-a466-60f4bf02c2ab/ | jq .
{
"_links": {
"self": {
"href": "/api/v1/spaces/default/appsvcs/documents/3102ce15-e3d4-498f-a466-60f4bf02c2ab/"
}
},
"deployments": [
{
"Message": "Delete Deployment task created successfully",
"id": "/declare/3102ce15-e3d4-498f-a466-60f4bf02c2ab/deployments/400e2b06-b451-4035-a26b-beaf90b283a5",
"task_id": "73001fa0-2690-4906-96d6-52c2bb162bb0"
}
],
"id": "3102ce15-e3d4-498f-a466-60f4bf02c2ab",
"message": "The application delete has been submitted successfully"
}
One more AS3 schema insight
This article focused on the API endpoints and to make things simpler I used a declaration that works with both approaches. That said, if you are starting out with BIG-IP Next, you don't need the ADC or Tenant classes in your declaration, you can instead use a named document and start at the application class. Check out this diff in VSCode for the DNS app used in this article.
Next up...
I've been configuration-focused in the first couple of articles in the automation series. In the next article, I'll walk through some of the BIG-IP Next Postman collection, looking at system as well as configuration things. The visual experience in Postman might be a little easier on the eyes for those getting started than a bunch of curl commands. Stay tuned!
Resources
- BIG-IP Next AS3 Schema
- BIG-IP Next API Reference
- Manage Application Services on Central Manager with AS3
Great article! I will dig into it soon, as I will port my own automation framework to Next. This is a good starting point.