├── LICENSE.md ├── README.md ├── TERMS_OF_USE.md ├── aws-sts-fetcher ├── README.md └── f5-aws-sts-fetcher.py ├── cli-csv-example ├── README.md └── super_pool.py ├── kubernetes-example ├── README.md ├── config-sample.sh ├── ingress-app.yaml ├── ingress-svc.yaml ├── k8s2f5.py └── www.yaml └── pcf-example ├── README.md ├── dora_template_v1.0.yaml ├── dora_template_v2.0.yaml ├── iwf-helper.py ├── pcf-phase1.py ├── pcf-phase2.py └── pcf-phase3.py /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) Copyright (c) 2015, F5 Networks, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | F5 iControlREST Code Share - Python 2 | ================= 3 | 4 | 5 | Introduction 6 | ------------ 7 | 8 | Examples of using iControlREST with Python. Please feel free to contribute 9 | your own examples. 10 | 11 | Prerequisites 12 | ------------ 13 | 14 | * Python 15 | * F5 Python SDK (recommended) 16 | 17 | 18 | ### Further Documentation 19 | 20 | * iControlREST: https://clouddocs.f5.com/api/icontrol-rest/ 21 | * F5 Python SDK: https://github.com/F5Networks/f5-common-python 22 | -------------------------------------------------------------------------------- /TERMS_OF_USE.md: -------------------------------------------------------------------------------- 1 | THIS LICENSE AGREEMENT IS ENTERED INTO BETWEEN THE SUBMITTING PARTY AND F5 NETWORKS, INC. AND THE SUBMITTING PARTY AGREES TO BE BOUND BY THE TERMS OF THIS AGREEMENT BY SUBMITTING, POSTING, DOWNLOADING, COPYING, MODIFYING, INPUTTING, INSTALLATION, UPLOAD OR OTHER USE OF F5 MATERIALS AND THE SUBMISSIONS. IF YOU DO NOT AGREE TO THE FOREGOING, DO NOT POST THE SUBISSIONS OR USE THE F5 MATERIALS. (1) F5 does not claim ownership of the materials you provide to F5 (including feedback and suggestions) or post, upload, input or submit to any F5 GitHub repository (collectively "Submissions"). However, by posting, uploading, inputting, providing or submitting your Submission you grant F5, its affiliated companies and necessary sub-licensees a full, complete, irrevocable copyright license to use your Submission including, without limitation, the rights to: copy, distribute, transmit, publicly display, publicly perform, reproduce, edit, translate and reformat your Submission; and to publish your name in connection with your Submission. In addition, you agree that your submission will be subject to the terms of the MIT License (F5 MIT License[1]). (2) By posting, uploading, inputting, providing or submitting your Submission you warrant and represent that you own, are approved by your employer, or otherwise control all of the rights to your Submission as described including, without limitation, all the rights necessary for you to provide, post, upload, input or submit the Submissions. (3) Infringement Indemnification. Submitting party will defend and indemnify F5 against a claim that any information, design, specification, instruction, software, data, or material furnished by the submitting party under this license infringes a trademark, copyright, or patent. F5 will notify the submitting party promptly of such claim and will give sole control of defense and all related settlement negotiations to submitting party. F5 will provide reasonable assistance, information, and authority necessary to perform these obligations. Reasonable out-of-pocket expenses incurred by F5 for providing such assistance will be reimbursed by the submitting party. (4) THE MATERIALS AND SERVICES MADE AVAILABLE AT AND THROUGH THIS SITE ARE PROVIDED BY F5 ON AN "AS IS" BASIS. F5 MAKES NO REPRESENTATIONS, WARRANTIES OR GUARANTIES OF ANY KIND, EXPRESS OR IMPLIED, AS TO THE OPERATION OF THIS SITE, ITS CONTENT, OR ANY PRODUCTS OR SERVICES DESCRIBED OR OFFERED BY THIS SITE. TO THE FULL EXTENT PERMISSIBLE BY APPLICABLE LAW, F5 DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY, INCLUDING MERCHANTABILITY OF COMPUTER PROGRAMS AND INFORMATIONAL CONTENT, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, TITLE, OR THAT THE SITE CONTENT IS RELIABLE, ACCURATE, OR TIMELY. F5 WILL NOT BE LIABLE FOR ANY DAMAGES OF ANY KIND ARISING FROM THE USE OF THIS SITE, INCLUDING, BUT NOT LIMITED TO DIRECT, INDIRECT, INCIDENTAL, PUNITIVE, SPECIAL, CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF USE, DATA OR PROFITS, ARISING OUT OF OR IN ANY WAY CONNECTED WITH THE USE OR PERFORMANCE OF THE WEB SITE, WITH THE DELAY OR INABILITY TO USE THE WEB SITE OR RELATED SERVICES, THE PROVISION OF OR FAILURE TO PROVIDE SERVICES, OR FOR ANY INFORMATION, SOFTWARE, PRODUCTS, SERVICES AND RELATED GRAPHICS OBTAINED THROUGH THE WEB SITE, OR OTHERWISE ARISING OUT OF THE USE OF THE WEB SITE, WHETHER BASED ON CONTRACT, TORT, NEGLIGENCE, STRICT LIABILITY OR OTHERWISE, EVEN IF F5 OR ANY OF ITS SUPPLIERS HAS BEEN ADVISED OF THE POSSIBILITY OF DAMAGES. BECAUSE SOME STATES/JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU. WHILE THIS SITE MAY PROVIDE LINKS TO THIRD PARTY SITES, F5 DOES NOT CONTROL OR ENDORSE ANY THIRD PARTY SITE AND DISCLAIMS ANY RESPONSIBILITY FOR ITS FUNCTIONALITY OR CONTENT. THESE DISCLAIMERS AND LIMITATIONS ARE MADE IN ADDITION TO THOSE MADE IN AND APPLICABLE TO VARIOUS PAGES OR SECTIONS OF THIS SITE. [1]Just need to add in the hyperlink once both documents are posted. -------------------------------------------------------------------------------- /aws-sts-fetcher/README.md: -------------------------------------------------------------------------------- 1 | F5 AWS Token Fetcher 2 | ====================== 3 | 4 | ### About 5 | 6 | Uses F5 APM to retrieve SAML token and generate STS token to be used with AWS CLI. 7 | 8 | ### References 9 | 10 | * https://devcentral.f5.com/codeshare/saas-federation-iapp 11 | * https://aws.amazon.com/blogs/security/how-to-implement-federated-api-and-cli-access-using-saml-2-0-and-ad-fs/ 12 | 13 | ### Requirements 14 | 15 | * Python 16 | * F5 APM 17 | 18 | ### Usage 19 | 20 | Modify script to reference correct AWS SAML ID 21 | 22 | ``` 23 | % python f5-aws-sts-fetcher.py https://f5apm.example.com/saml/idp/res?id=/Common/awsaccess 24 | Username: erchen 25 | Password: 26 | 27 | % aws --profile saml ec2 describe-instances 28 | ... 29 | 30 | ``` 31 | 32 | 33 | ### Authored By 34 | 35 | [Eric Chen](https://devcentral.f5.com/users/123940) | [@chen23](https://github.com/chen23) 36 | -------------------------------------------------------------------------------- /aws-sts-fetcher/f5-aws-sts-fetcher.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | import boto3 5 | import requests 6 | import getpass 7 | import ConfigParser 8 | import base64 9 | import xml.etree.ElementTree as ET 10 | 11 | from os.path import expanduser 12 | from urlparse import urlparse, urlunparse 13 | import time 14 | import re 15 | import os 16 | import json 17 | import cPickle 18 | 19 | DEBUG = os.getenv('DEBUG') 20 | 21 | if DEBUG: 22 | try: 23 | import http.client as http_client 24 | except ImportError: 25 | # Python 2 26 | import httplib as http_client 27 | 28 | http_client.HTTPConnection.debuglevel = 1 29 | 30 | import logging 31 | logging.basicConfig() 32 | logging.getLogger().setLevel(logging.DEBUG) 33 | requests_log = logging.getLogger("requests.packages.urllib3") 34 | requests_log.setLevel(logging.DEBUG) 35 | requests_log.propogate = True 36 | 37 | # Author: Eric Chen 38 | # 39 | # Based on 40 | # 41 | # https://aws.amazon.com/blogs/security/how-to-implement-federated-api-and-cli-access-using-saml-2-0-and-ad-fs/ 42 | # 43 | 44 | ########################################################################## 45 | # Variables 46 | 47 | # region: The default AWS region that this script will connect 48 | # to for all API calls 49 | region = 'us-east-1' 50 | 51 | # output format: The AWS CLI output format that will be configured in the 52 | # saml profile (affects subsequent CLI calls) 53 | outputformat = 'json' 54 | 55 | # awsconfigfile: The file where this script will store the temp 56 | # credentials under the saml profile 57 | awsconfigfile = '/.aws/credentials' 58 | 59 | # SSL certificate verification: Whether or not strict certificate 60 | # verification is done, False should only be used for dev/test 61 | sslverification = True 62 | 63 | # idpentryurl: The initial URL that starts the authentication process. 64 | idpentryurl = sys.argv[1] 65 | 66 | ########################################################################## 67 | 68 | get_assertion = re.compile("name=\"SAMLResponse\" value=\"([^\"]+)") 69 | 70 | # Get the federated credentials from the user 71 | username = os.getenv('USERNAME') 72 | 73 | if not username: 74 | print "Username:", 75 | username = raw_input() 76 | 77 | password = os.getenv('PASSWORD') 78 | if not password: 79 | password = getpass.getpass() 80 | print '' 81 | 82 | mfamethod = os.getenv('MFAMETHOD') 83 | if not mfamethod: 84 | print 'MFA Method [push]/token:', 85 | mfamethod = raw_input() 86 | 87 | if not mfamethod: 88 | mfamethod = 'push' 89 | else: 90 | mfamethod = mfamethod.strip() 91 | 92 | mfatoken = '' 93 | print 'MFA method',mfamethod 94 | 95 | if mfamethod != 'push' and mfamethod != 'none': 96 | print 'MFA Token:', 97 | if re.search(re.compile("^\d+$"),mfamethod): 98 | mfatoken = mfamethod 99 | mfamethod = 'token' 100 | else: 101 | mfatoken = raw_input() 102 | mfatoken = mfatoken.strip() 103 | 104 | 105 | # Initiate session handler 106 | session = requests.Session() 107 | if os.path.exists('session.pickle'): 108 | cookies = cPickle.load(open('session.pickle')) 109 | session.cookies = cookies 110 | 111 | # Programatically get the SAML assertion 112 | 113 | # Opens the initial AD FS URL and follows all of the HTTP302 redirects 114 | 115 | headers = {'user-agent':'f5-aws-sts-fetcher/0.1'} 116 | 117 | response = session.get(idpentryurl, verify=sslverification, headers=headers) 118 | 119 | posturl = response.url 120 | u = urlparse(response.url) 121 | baseurl = u.scheme + '://' + u.netloc 122 | 123 | m = get_assertion.search(response.text) 124 | 125 | if m: 126 | mfamethod = 'none' 127 | else: 128 | response = session.post(posturl,data={'username':username,'password':password}, headers=headers) 129 | 130 | 131 | if "F5 Dynamic Webtop" in response.text: 132 | # existing MFA session, skip MFA 133 | mfamethod = 'none' 134 | 135 | if mfamethod != 'none': 136 | if ">Please select your preferred method for Multi-Factor Authentication<" not in response.text: 137 | print "Authentication Failed" 138 | sys.exit(1) 139 | 140 | if mfamethod == 'push': 141 | print 'Waiting for MFA push' 142 | 143 | payload = {'mfamethod':mfamethod,'mfatoken':mfatoken,'vhost':'standard'} 144 | 145 | response = session.post(posturl,data=payload, headers=headers) 146 | 147 | cookies = session.cookies.copy() 148 | retryurl = response.url 149 | 150 | if "F5 Dynamic Webtop" not in response.text and not m: 151 | print 'MFA failed' 152 | sys.exit(1) 153 | 154 | response = session.get(baseurl +'/vdesk/resource_list.xml?resourcetype=res',verify=sslverification,headers=headers) 155 | 156 | try: 157 | response = session.get(idpentryurl, verify=sslverification, headers=headers) 158 | except requests.exceptions.ConnectionError: 159 | response = session.get(idpentryurl, verify=sslverification, headers=headers) 160 | 161 | if "SAMLResponse" not in response.text: 162 | print 'Failed to retrieve SAML token, trying again\n\n\n\n\n\n' 163 | time.sleep(1) 164 | try: 165 | response = session.get(retryurl, verify=sslverification, headers=headers, cookies=cookies) 166 | response = session.get(idpentryurl, verify=sslverification, headers=headers, cookies=cookies) 167 | except requests.exceptions.ConnectionError: 168 | response = session.get(idpentryurl, verify=sslverification, headers=headers, cookies=cookies) 169 | 170 | if "SAMLResponse" not in response.text: 171 | print 'Failed to retrieve SAML token' 172 | sys.exit(1) 173 | 174 | m = get_assertion.search(response.text) 175 | 176 | if m: 177 | assertion = m.groups()[0] 178 | else: 179 | print 'failed to find assertion' 180 | sys.exit(1) 181 | 182 | # Parse the returned assertion and extract the authorized roles 183 | awsroles = [] 184 | root = ET.fromstring(base64.b64decode(assertion)) 185 | 186 | for saml2attribute in root.iter('{urn:oasis:names:tc:SAML:2.0:assertion}Attribute'): 187 | if (saml2attribute.get('Name') == 'https://aws.amazon.com/SAML/Attributes/Role'): 188 | for saml2attributevalue in saml2attribute.iter('{urn:oasis:names:tc:SAML:2.0:assertion}AttributeValue'): 189 | awsroles.append(saml2attributevalue.text) 190 | 191 | 192 | 193 | # Note the format of the attribute value should be role_arn,principal_arn 194 | # but lots of blogs list it as principal_arn,role_arn so let's reverse 195 | # them if needed 196 | for awsrole in awsroles: 197 | chunks = awsrole.split(',') 198 | if'saml-provider' in chunks[0]: 199 | newawsrole = chunks[1] + ',' + chunks[0] 200 | index = awsroles.index(awsrole) 201 | awsroles.insert(index, newawsrole) 202 | awsroles.remove(awsrole) 203 | 204 | # If I have more than one role, ask the user which one they want, 205 | # otherwise just proceed 206 | print "" 207 | if len(awsroles) > 1: 208 | i = 0 209 | print "Please choose the role you would like to assume:" 210 | for awsrole in awsroles: 211 | print '[', i, ']: ', awsrole.split(',')[0] 212 | i += 1 213 | 214 | print "Selection: ", 215 | selectedroleindex = raw_input() 216 | 217 | # Basic sanity check of input 218 | if int(selectedroleindex) > (len(awsroles) - 1): 219 | print 'You selected an invalid role index, please try again' 220 | sys.exit(0) 221 | 222 | role_arn = awsroles[int(selectedroleindex)].split(',')[0] 223 | principal_arn = awsroles[int(selectedroleindex)].split(',')[1] 224 | 225 | else: 226 | role_arn = awsroles[0].split(',')[0] 227 | principal_arn = awsroles[0].split(',')[1] 228 | 229 | 230 | # Use the assertion to get an AWS STS token using Assume Role with SAML 231 | conn = boto3.client('sts',region_name=region) 232 | token = conn.assume_role_with_saml(RoleArn=role_arn, 233 | PrincipalArn =principal_arn, 234 | SAMLAssertion=assertion) 235 | 236 | # Write the AWS STS token into the AWS credential file 237 | home = expanduser("~") 238 | filename = home + awsconfigfile 239 | 240 | # Read in the existing config file 241 | config = ConfigParser.RawConfigParser() 242 | config.read(filename) 243 | 244 | # Put the credentials into a specific profile instead of clobbering 245 | # the default credentials 246 | if not config.has_section('saml'): 247 | config.add_section('saml') 248 | 249 | config.set('saml', 'output', outputformat) 250 | config.set('saml', 'region', region) 251 | config.set('saml', 'aws_access_key_id', token['Credentials']['AccessKeyId']) 252 | config.set('saml', 'aws_secret_access_key', token['Credentials']['SecretAccessKey']) 253 | config.set('saml', 'aws_session_token', token['Credentials']['SessionToken']) 254 | 255 | # Write the updated config file 256 | with open(filename, 'w+') as configfile: 257 | config.write(configfile) 258 | # save a copy of the cookies 259 | #json.dump(cookies,open('session.json','w')) 260 | cPickle.dump(cookies,open('session.pickle','w')) 261 | -------------------------------------------------------------------------------- /cli-csv-example/README.md: -------------------------------------------------------------------------------- 1 | F5 Python SDK CLI Example 2 | ================= 3 | 4 | 5 | Introduction 6 | ------------ 7 | 8 | The super_pool.py script is a very basic example of creating your own 9 | CLI utility using the [F5 Python SDK](https://github.com/F5Networks/f5-common-python) 10 | to manage LTM pool and pool members. 11 | 12 | Prerequisites 13 | ------------ 14 | 15 | * Python 16 | * [F5 Python SDK](https://github.com/F5Networks/f5-common-python) 17 | 18 | Concepts 19 | ------------ 20 | 21 | The script creates a Python object that stores the connection object to the 22 | BIG-IP 23 | ``` 24 | ... 25 | def __init__(self, host, username, password): 26 | self.mgmt = ManagementRoot(host, username, password) 27 | ... 28 | ``` 29 | This allows the connection to be re-used and provides a generic class that 30 | can be included by other Python scripts. 31 | 32 | The "```__main__```" section of the code allows it be run from the CLI and 33 | uses the argparse library to handle inputs. 34 | 35 | ``` 36 | ... 37 | if __name__ == "__main__": 38 | parser = argparse.ArgumentParser(description='Script to create a pool on a BIG-IP device') 39 | parser.add_argument("host", help="The IP/Hostname of the BIG-IP device") 40 | ... 41 | ``` 42 | 43 | One of the inputs includes specifying a CSV file that is formatted with the pool 44 | information. Completing the script to handle all possible inputs is left as 45 | an excercise. 46 | 47 | 48 | Usage 49 | ------------ 50 | 51 | ``` 52 | % python super_pool.py --help 53 | ``` 54 | 55 | 56 | ### Further Documentation 57 | 58 | * iControlREST: https://devcentral.f5.com/wiki/iControlREST.HomePage.ashx 59 | * F5 Python SDK: https://github.com/F5Networks/f5-common-python 60 | 61 | ### Authored By 62 | 63 | [Eric Chen](https://devcentral.f5.com/users/123940) | [@chen23](https://github.com/chen23) 64 | -------------------------------------------------------------------------------- /cli-csv-example/super_pool.py: -------------------------------------------------------------------------------- 1 | from f5.bigip import ManagementRoot 2 | import pprint 3 | import argparse 4 | import csv 5 | 6 | pp = pprint.PrettyPrinter(indent=3) 7 | 8 | class SuperPool(object): 9 | def __init__(self, host, username, password): 10 | self.mgmt = ManagementRoot(host, username, password) 11 | 12 | def create_pool(self, pool_name,pool_members, partition='Common'): 13 | pool_path = "/%s/%s" % (partition, pool_name) 14 | 15 | if self.mgmt.tm.ltm.pools.pool.exists(partition=partition, name=pool_name): 16 | raise Exception("Pool '%s' already exists" % pool_name) 17 | 18 | pool = self.mgmt.tm.ltm.pools.pool.create(partition=partition, name=pool_name) 19 | print "Created pool %s" % pool_path 20 | 21 | member_list = pool_members.split(',') 22 | for member in member_list: 23 | pool_member = pool.members_s.members.create(partition=partition, name=member) 24 | print " Added member %s" % member 25 | def read_pool(self, pool_name, partition='Common'): 26 | pool_path = "/%s/%s" % (partition, pool_name) 27 | 28 | if not self.mgmt.tm.ltm.pools.pool.exists(partition=partition, name=pool_name): 29 | raise Exception("Pool '%s' does not exist" % pool_name) 30 | 31 | pool = self.mgmt.tm.ltm.pools.pool.load(partition=partition, name=pool_name) 32 | print "Pool %s:" % pool_path 33 | pp.pprint(pool.raw) 34 | def update_pool(self,pool_name, attribute, value, partition='Common'): 35 | pool_path = "/%s/%s" % (partition, pool_name) 36 | 37 | if not self.mgmt.tm.ltm.pools.pool.exists(partition=partition, name=pool_name): 38 | raise Exception("Pool '%s' does not exist" % pool_name) 39 | 40 | pool = self.mgmt.tm.ltm.pools.pool.load(partition=partition, name=pool_name) 41 | pp.pprint("Current: %s=%s" % (attribute, getattr(pool, attribute))) 42 | kwargs = {attribute: value} 43 | pool.update(**kwargs) 44 | print "Updating pool %s" % pool_path 45 | pool.refresh() 46 | pp.pprint("New: %s=%s" % (attribute, getattr(pool, attribute))) 47 | def delete_pool(self, pool_name, partition='Common'): 48 | pool_path = "/%s/%s" % (partition, pool_name) 49 | 50 | if not self.mgmt.tm.ltm.pools.pool.exists(partition=partition, name=pool_name): 51 | raise Exception("Pool '%s' does not exist" % pool_name) 52 | 53 | pool = self.mgmt.tm.ltm.pools.pool.load(partition=partition, name=pool_name) 54 | pool.delete() 55 | print "Deleted pool %s" % pool_path 56 | 57 | 58 | 59 | if __name__ == "__main__": 60 | parser = argparse.ArgumentParser(description='Script to create a pool on a BIG-IP device') 61 | parser.add_argument("host", help="The IP/Hostname of the BIG-IP device") 62 | parser.add_argument("pool_name", nargs='?', help="The name of the pool") 63 | parser.add_argument("pool_members", nargs='?', help="A comma seperated string in the format :[,:]") 64 | parser.add_argument("value", nargs='?', help='optional value for update') 65 | parser.add_argument("-P", "--partition", help="The partition name", default="Common") 66 | parser.add_argument("-u", "--username", help="The BIG-IP username", default="admin") 67 | parser.add_argument("-p", "--password", help="The BIG-IP password", default="admin") 68 | parser.add_argument("-f","--file",help="CSV file input") 69 | parser.add_argument("-a","--action",help="create/read/update/delete") 70 | args = parser.parse_args() 71 | sp = SuperPool(args.host, args.username, args.password) 72 | if args.action == "create": 73 | sp.create_pool(args.pool_name, args.pool_members, args.partition) 74 | elif args.action == "read": 75 | sp.read_pool(args.pool_name, args.partition) 76 | elif args.action == "update": 77 | sp.update_pool(args.pool_name, args.pool_members, args.value, args.partition) 78 | elif args.action == "delete": 79 | sp.delete_pool(args.pool_name,args.partition) 80 | elif args.file: 81 | for row in csv.reader(open(args.file)): 82 | if row[0] == "create": 83 | sp.create_pool(row[1],row[2]) 84 | elif row[0] == "delete": 85 | sp.delete_pool(row[1]) 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /kubernetes-example/README.md: -------------------------------------------------------------------------------- 1 | Kubernetes Service Load Balancer / Ingress 2 | ========================================== 3 | 4 | Example of using Python to query the Kubernetes API and create LTM/DNS objects using iControl REST. 5 | 6 | ### Update April 2017 7 | 8 | Please see: 9 | * http://clouddocs.f5.com/containers/v1/kubernetes/ 10 | * https://github.com/F5Networks/k8s-bigip-ctlr 11 | 12 | There is now an F5 Supported Container Connector for using Kubernetes. The following is not related to the Container Connector. 13 | 14 | ### About 15 | 16 | This script will fetch data from Kubernetes to configure a BIG-IP. 17 | 18 | Related DevCentral Article: [F5 Python SDK and Kubernetes](https://devcentral.f5.com/articles/f5-python-sdk-and-kubernetes-21045) 19 | 20 | Currently will: 21 | * Retrieve Service, Ingress, Pods from Kubernetes 22 | 23 | Kubernetes Features 24 | * Service Load Balancer (L4) 25 | * Ingress Router (L7) 26 | BIG-IP Features 27 | * Supports two types of networking 28 | * Using static routes (assumes L2 adjacent) 29 | * Using VXLAN (requires flanneld on K8S / SDN on BIG-IP 30 | * Supports two types of LTM config generation 31 | * iControl REST via F5 Python SDK 32 | * [App Services's iApp 1.0](https://github.com/0xHiteshPatel/appsvcs_integration_iapp) via [F5 Python SDK](https://github.com/F5Networks/f5-common-python) > 1.0.0 33 | * Does not support Ingress Router only SLB 34 | * Generates GTM/DNS config using F5 Python iControl library 35 | 36 | ### Requirements 37 | Python 2.7 38 | 39 | Python Modules: f5-sdk, python-etcd, pykube 40 | 41 | F5 BIG-IP LTM/DNS 12.1 42 | 43 | ### Configuration 44 | 45 | Configuration is via environment variables. config-sample.sh has 46 | examples: 47 | ``` 48 | export BIGIP_HOST='10.1.1.2' 49 | export BIGIP_USER='admin' 50 | export BIGIP_PASSWD='admin' 51 | export USE_DNS='TRUE' 52 | #export NETWORK_TYPE='vxlan' 53 | #export KUBE_CONFIG='/etc/kubeconfig' 54 | # 55 | # You must set this variable to run the script 56 | # 57 | #export I_PROMISE_NOT_TO_RUN_THIS_IN_PRODUCTION='TRUE' 58 | ``` 59 | ### Authored By 60 | 61 | [Eric Chen](https://devcentral.f5.com/users/123940) | [@chen23](https://github.com/chen23) 62 | -------------------------------------------------------------------------------- /kubernetes-example/config-sample.sh: -------------------------------------------------------------------------------- 1 | export BIGIP_HOST='10.1.1.2' 2 | export BIGIP_USER='admin' 3 | export BIGIP_PASSWD='admin' 4 | export USE_DNS='TRUE' 5 | #export NETWORK_TYPE='vxlan' 6 | #export KUBE_CONFIG='/etc/kubeconfig' 7 | # 8 | # You must set this variable to run the script 9 | # 10 | #export I_PROMISE_NOT_TO_RUN_THIS_IN_PRODUCTION='TRUE' 11 | 12 | 13 | -------------------------------------------------------------------------------- /kubernetes-example/ingress-app.yaml: -------------------------------------------------------------------------------- 1 | # This Service writes the HTTP request headers out to the response. Access it 2 | # through its NodePort, LoadBalancer or Ingress endpoint. 3 | apiVersion: v1 4 | kind: Service 5 | metadata: 6 | name: echoheadersx 7 | labels: 8 | app: echoheaders 9 | spec: 10 | type: NodePort 11 | ports: 12 | - port: 80 13 | nodePort: 30301 14 | targetPort: 8080 15 | protocol: TCP 16 | name: http 17 | selector: 18 | app: echoheaders 19 | --- 20 | apiVersion: v1 21 | kind: Service 22 | metadata: 23 | name: echoheadersy 24 | labels: 25 | app: echoheaders 26 | spec: 27 | type: NodePort 28 | ports: 29 | - port: 80 30 | nodePort: 30284 31 | targetPort: 8080 32 | protocol: TCP 33 | name: http 34 | selector: 35 | app: echoheaders 36 | --- 37 | # This is a replication controller for the endpoint that services the 3 38 | # Services above. 39 | apiVersion: v1 40 | kind: ReplicationController 41 | metadata: 42 | name: echoheaders 43 | spec: 44 | replicas: 1 45 | template: 46 | metadata: 47 | labels: 48 | app: echoheaders 49 | spec: 50 | containers: 51 | - name: echoheaders 52 | image: gcr.io/google_containers/echoserver:1.4 53 | ports: 54 | - containerPort: 8080 55 | --- 56 | # This is the Ingress resource that creates a HTTP Loadbalancer configured 57 | # according to the Ingress rules. 58 | apiVersion: extensions/v1beta1 59 | kind: Ingress 60 | metadata: 61 | name: ingress 62 | annotations: 63 | f5.destination: 10.1.10.50 64 | f5.vs__ProfileHTTP: /Common/http 65 | f5.vs__AdvPolicies: /Common/ingress 66 | kubernetes.io/ingress.class: "f5.bigip" 67 | spec: 68 | backend: 69 | # Re-use echoheadersx as the default backend so we stay under the default 70 | # quota for gce BackendServices. 71 | serviceName: echoheadersx 72 | servicePort: 80 73 | rules: 74 | - host: foo.bar.com 75 | http: 76 | paths: 77 | - path: /foo 78 | backend: 79 | serviceName: echoheadersx 80 | servicePort: 80 81 | - host: bar.baz.com 82 | http: 83 | paths: 84 | - path: /bar 85 | backend: 86 | serviceName: echoheadersy 87 | servicePort: 80 88 | - path: /foo 89 | backend: 90 | serviceName: echoheadersx 91 | servicePort: 80 92 | 93 | -------------------------------------------------------------------------------- /kubernetes-example/ingress-svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: ingress-svc 5 | labels: 6 | app: ingress-svc 7 | annotations: 8 | f5.destination: 10.1.10.50 9 | f5.vs__ProfileHTTP: /Common/http 10 | f5.vs__AdvPolicies: /Common/ingress 11 | kubernetes.io/ingress.class: "f5.bigip" 12 | spec: 13 | type: NodePort 14 | ports: 15 | - port: 80 16 | nodePort: 30245 17 | targetPort: 8080 18 | protocol: TCP 19 | name: http 20 | selector: 21 | app: ingress-svc 22 | --- 23 | apiVersion: v1 24 | kind: ReplicationController 25 | metadata: 26 | name: ingress-svc 27 | spec: 28 | replicas: 1 29 | selector: 30 | app: ingress-svc 31 | template: 32 | metadata: 33 | labels: 34 | app: ingress-svc 35 | spec: 36 | terminationGracePeriodSeconds: 60 37 | containers: 38 | - name: ingress-svc 39 | # Any image is permissable as long as: 40 | # 1. It serves a 404 page at / 41 | # 2. It serves 200 on a /healthz endpoint 42 | image: gcr.io/google_containers/defaultbackend:1.0 43 | livenessProbe: 44 | httpGet: 45 | path: /healthz 46 | port: 8080 47 | scheme: HTTP 48 | initialDelaySeconds: 30 49 | timeoutSeconds: 5 50 | ports: 51 | - containerPort: 8080 52 | resources: 53 | limits: 54 | cpu: 10m 55 | memory: 20Mi 56 | requests: 57 | cpu: 10m 58 | memory: 20Mi 59 | -------------------------------------------------------------------------------- /kubernetes-example/k8s2f5.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import operator 3 | 4 | from f5.bigip import ManagementRoot 5 | from icontrol.exceptions import iControlUnexpectedHTTPError 6 | import etcd 7 | import requests 8 | 9 | import pprint 10 | import json 11 | import logging 12 | import time 13 | import os 14 | from urlparse import parse_qs, urlparse 15 | from distutils.version import LooseVersion 16 | 17 | #import backports.ssl_match_hostname 18 | 19 | import pykube 20 | 21 | # Monkey-patch match_hostname with backports's match_hostname, allowing for IP addresses 22 | # XXX: the exception that this might raise is backports.ssl_match_hostname.CertificateError 23 | ##pykube.http.requests.packages.urllib3.connection.match_hostname = backports.ssl_match_hostname.match_hostname 24 | # https://github.com/kelproject/pykube/issues/29 25 | 26 | #from pykube.config import KubeConfig 27 | #from pykube.http import HTTPClient 28 | #from pykube.objects import Pod, Service, Endpoint, Ingress 29 | 30 | 31 | logger = logging.getLogger() 32 | #logger = logging.getLogger('requests') 33 | #logger.setLevel(logging.DEBUG) 34 | # Disable alerting for self-signed certs 35 | 36 | try: 37 | requests.packages.urllib3.disable_warnings() 38 | except: 39 | pass 40 | 41 | pp = pprint.PrettyPrinter(indent=4) 42 | 43 | 44 | class KubeToBigIP(object): 45 | def __init__(self, username='admin', password='admin', host='192.168.1.245'): 46 | 47 | # Replace with the address of your BIG-IP 48 | self.mgmt_url = "https://%s" %(host) 49 | 50 | self.url = "https://%s/mgmt/tm/sys/application/service" %(host) 51 | 52 | self.mgmt = ManagementRoot(host, username, password) 53 | 54 | # breaking into iCRs, not recommended 55 | # could also use requests directly 56 | # self.s=None 57 | self.s = self.mgmt._meta_data['bigip']._meta_data['icr_session'] 58 | self.tmos_version = self._tmos_version() 59 | if LooseVersion(self.tmos_version) < LooseVersion('12.1.0'): 60 | raise Exception,"This has only been tested on 12.1." 61 | def _tmos_version(self): 62 | connect = self.mgmt._meta_data['bigip']._meta_data['icr_session'] 63 | base_uri = self.mgmt._meta_data['uri'] + 'tm/sys/' 64 | response = connect.get(base_uri) 65 | ver = response.json() 66 | 67 | version = parse_qs(urlparse(ver['selfLink']).query)['ver'][0] 68 | return version 69 | def _exists(self,exist_url): 70 | does_exist = True 71 | try: 72 | self.s.get(exist_url) 73 | except iControlUnexpectedHTTPError,e: 74 | # iCr throws an exception for 404 75 | if '404' in e.__str__(): 76 | does_exist = False 77 | return does_exist 78 | def _create(self,post_url, payload): 79 | self.s.post(post_url,data=json.dumps(payload)) 80 | 81 | def create_or_update_network(self,network_type,networks,vxlan=False,partition='Common'): 82 | vxlan_profile_name = 'vxlan-flannel' 83 | vxlan_tunnel_name = 'vxlan-flannel-tun' 84 | vxlan_key = 1 85 | # print network_type 86 | # print networks 87 | bigip_self = '10.1.20.1' 88 | if vxlan: 89 | print 'create vxlan profile' 90 | payload = { 'name': vxlan_profile_name, 91 | 'partition': partition, 92 | 'defaultsFrom': '/Common/vxlan', 93 | 'floodingType': 'none', 94 | 'port': '8472' 95 | } 96 | if self.mgmt.tm.net.tunnels_s.vxlans.vxlan.exists(name=vxlan_profile_name, partition=partition): 97 | pass 98 | # vxlan = self.mgmt.tm.net.tunnels_s.vxlans.vxlan.load(name=vxlan_profile_name, partition=partition) 99 | # vxlan.update(**payload) 100 | else: 101 | vxlan = self.mgmt.tm.net.tunnels_s.vxlans.vxlan.create(**payload) 102 | payload = { 103 | "partition": partition, 104 | "name": vxlan_tunnel_name, 105 | "key": vxlan_key, 106 | "localAddress": bigip_self, 107 | "remoteAddress": '0.0.0.0', 108 | "profile": vxlan_profile_name 109 | } 110 | if self.mgmt.tm.net.tunnels_s.tunnels.tunnel.exists(name=vxlan_tunnel_name, partition=partition): 111 | tun = self.mgmt.tm.net.tunnels_s.tunnels.tunnel.load(name=vxlan_tunnel_name, partition=partition) 112 | # print tun 113 | # tun.update(**payload) 114 | else: 115 | tun = self.mgmt.tm.net.tunnels_s.tunnels.tunnel.create(**payload) 116 | 117 | payload = { 118 | "partition": partition, 119 | "name": vxlan_tunnel_name 120 | } 121 | 122 | tun_fdb = self.mgmt.tm.net.fdb.tunnels.tunnel.load(**payload) 123 | records = [] 124 | for n in networks: 125 | value = networks[n] 126 | # print value 127 | if value['BackendType'] == 'vxlan': 128 | records.append( {'name':value['BackendData']['VtepMAC'], 'endpoint':value['PublicIP']} ) 129 | 130 | payload = { 131 | "records": records 132 | } 133 | tun_fdb.update(**payload) 134 | return records 135 | else: 136 | # just set routes 137 | sc = self.mgmt.tm.net.selfips.get_collection() 138 | my_selfips = [a.address for a in sc] 139 | # print my_selfips 140 | for subnet in networks: 141 | data = networks[subnet] 142 | 143 | if data['PublicIP'] + '/' + subnet.split('/')[-1] in my_selfips: 144 | continue 145 | 146 | name = data['PublicIP'] 147 | payload = {'name': name, 148 | 'network': subnet, 149 | 'partition':partition, 150 | 'gw':data['PublicIP']} 151 | # print payload 152 | exist_url = self.mgmt_url + '/mgmt/tm/net/route/~' + partition + '~' + name.replace('/','~') 153 | # https://github.com/F5Networks/f5-common-python/issues/543 154 | exist = self._exists(exist_url) 155 | if exist: 156 | if LooseVersion(self.tmos_version) > LooseVersion('11.6.0'): 157 | # 11.6.0 errors out 158 | # icontrol.exceptions.iControlUnexpectedHTTPError: 400 Unexpected Error: Bad Request for uri: https://10.1.1.3:443/mgmt/tm/net/route/~Common~10.1.20.101/ 159 | #Text: u'{"code":400,"message":"\\"network\\" may not be specified in the context of the \\"modify\\" command. \\"network\\" may be specified using the following commands: create, list, show","errorStack":[]}' 160 | 161 | route = self.mgmt.tm.net.routes.route.load(name=name,partition=partition) 162 | route.update(**payload) 163 | # self.s.patch(exist_url,data=json.dumps(payload)) 164 | # does not remove stale routes 165 | else: 166 | print 'creating new' 167 | route = self.mgmt.tm.net.routes.route.create(**payload) 168 | return [] 169 | 170 | def create_or_update_network_arp(self,all_ips,ip_to_mac,route_domain=1,partition='Common'): 171 | "add MAC addresses to static arp table" 172 | # print all_ips, ip_to_mac 173 | for (podIP, hostIP) in all_ips: 174 | arp = self.mgmt.tm.net.arps.arp 175 | f5_ip = "%s%%%d" %(podIP,route_domain) 176 | name = "%s_%s" %(podIP,route_domain) 177 | exist = True 178 | try: 179 | arp.exists(name=name, partition=partition) 180 | except iControlUnexpectedHTTPError,e: 181 | if "arp entry not found" in e.message: 182 | exist = False 183 | else: 184 | raise 185 | 186 | payload = { 'name': name, 187 | 'partition': partition, 188 | 'ipAddress': f5_ip, 189 | 'macAddress': ip_to_mac.get(hostIP) } 190 | if exist: 191 | arp = arp.load(name=name, partition=partition) 192 | arp.update(**payload) 193 | else: 194 | arp.create(**payload) 195 | 196 | def create_or_update_vs(self, my_vs): 197 | "create VS using iControl REST" 198 | hostname = my_vs['name'] 199 | target_port = my_vs['port'] 200 | dest = my_vs['dest'] 201 | svc_type = 'http' 202 | pool_members = my_vs['pool_members'] 203 | 204 | vs_name = "%s_%s_vs" %(hostname, target_port) 205 | pool_name = "%s_%s_pool" %(hostname, target_port) 206 | 207 | if self.mgmt.tm.ltm.pools.pool.exists(name=pool_name): 208 | pool = self.mgmt.tm.ltm.pools.pool.load(name=pool_name) 209 | else: 210 | pool = self.mgmt.tm.ltm.pools.pool.create(name=pool_name, monitor='/Common/tcp') 211 | 212 | members = pool.members_s.get_collection() 213 | 214 | existing_members = set( [m.name for m in members] ) 215 | current_members = set(['%s:%s' %(ip,port) for (ip, port) in pool_members]) 216 | 217 | add_members = current_members - existing_members 218 | remove_members = existing_members - current_members 219 | 220 | for m in add_members: 221 | member = pool.members_s.members.create(partition='Common', name=m) 222 | 223 | for m in remove_members: 224 | member = pool.members_s.members.load(partition='Common', name=m) 225 | member.delete() 226 | 227 | if not dest: 228 | return 229 | 230 | payload = { 'name': vs_name, 231 | 'destination': "%s:%s" %(dest, target_port), 232 | 'pool': '/Common/%s' %(pool_name), 233 | 'ipProtocol' : 'tcp' 234 | } 235 | # if 'vs__AdvPolicies' in my_vs: 236 | # print 'advanced policy' 237 | # payload['policies'] = my_vs['vs__AdvPolicies'] 238 | # if 'vs__ProfileHTTP' in my_vs: 239 | # payload['profiles'] = my_vs['vs__ProfileHTTP'] 240 | # print my_vs 241 | if self.mgmt.tm.ltm.virtuals.virtual.exists(name=vs_name): 242 | virtual = self.mgmt.tm.ltm.virtuals.virtual.load(name=vs_name, partition='Common') 243 | profiles = virtual.profiles_s 244 | profile_collection = profiles.get_collection() 245 | profile_names = [a.name for a in profile_collection] 246 | if 'vs__ProfileHTTP' in my_vs: 247 | if 'http' in profile_names: 248 | profile = profiles.profiles.load(name='http') 249 | profile.update(fullPath=my_vs['vs__ProfileHTTP']) 250 | else: 251 | profiles.profiles.create(name='http',fullPath=my_vs['vs__ProfileHTTP']) 252 | if 'vs__AdvPolicies' in my_vs: 253 | policy_url = "%spolicies/%s" %(virtual._meta_data['uri'],my_vs['vs__AdvPolicies'].replace('/','~')) 254 | 255 | exist = self._exists(policy_url) 256 | if not exist: 257 | print 'creating policy' 258 | policy_url = "%spolicies/" %(virtual._meta_data['uri']) 259 | self._create(policy_url,{'name':my_vs['vs__AdvPolicies'].split('/')[-1],'fullPath':my_vs['vs__AdvPolicies']}) 260 | 261 | 262 | virtual.update(**payload) 263 | else: 264 | virtual = self.mgmt.tm.ltm.virtuals.virtual.create(**payload) 265 | profiles = virtual.profiles_s 266 | if 'vs__ProfileHTTP' in my_vs: 267 | profiles.profiles.create(name='http',fullPath=my_vs['vs__ProfileHTTP']) 268 | pass 269 | def create_or_update_dns(self,my_vs,hostname,server,dc): 270 | # uses raw iControl 271 | # check for virtual server 272 | target_port = my_vs['port'] 273 | dest = my_vs['dest'] 274 | 275 | vs_name = "%s_%s_vs" %(hostname, target_port) 276 | pool_name = "%s_%s_pool" %(hostname, target_port) 277 | 278 | vs_name = "%s_%s_vs" %(hostname, target_port) 279 | pool_name = "%s_%s_%s_pool" %(hostname, target_port, dc) 280 | 281 | exist_url = self.mgmt_url + "/mgmt/tm/gtm/server/~Common~%s/virtual-servers/%s" %(server,vs_name) 282 | post_url = self.mgmt_url + "/mgmt/tm/gtm/server/~Common~%s/virtual-servers" %(server) 283 | logging.debug("%s, %s" %(exist_url,post_url)) 284 | does_exist = self._exists(exist_url) 285 | payload = {'kind':'tm:gtm:server:virtual-servers:virtual-serversstate', 286 | 'name':vs_name, 287 | 'destination':'%s:%s' %(dest,target_port), 288 | 'translationAddress':dest, 289 | 'translationPort':target_port, 290 | 'monitor':'/Common/bigip' 291 | } 292 | if not does_exist: 293 | resp = self._create(post_url,payload) 294 | does_exist = True 295 | 296 | # check for pool 297 | 298 | exist_url = self.mgmt_url + "/mgmt/tm/gtm/pool/a/~Common~%s" %(pool_name) 299 | post_url = self.mgmt_url + "/mgmt/tm/gtm/pool/a" 300 | 301 | does_exist = self._exists(exist_url) 302 | if not does_exist: 303 | self._create(post_url,{'kind':'tm:gtm:pool:a:astate', 304 | 'name':pool_name, 305 | 'members':['%s:%s' %(server,vs_name)] 306 | }) 307 | does_exist = True 308 | 309 | # check for wideip 310 | exist_url = self.mgmt_url + "/mgmt/tm/gtm/wideip/a/~Common~%s" %(hostname) 311 | post_url = self.mgmt_url + "/mgmt/tm/gtm/wideip/a" 312 | 313 | does_exist = self._exists(exist_url) 314 | 315 | if not does_exist: 316 | self._create(post_url,{'kind':'tm:gtm:wideip:a:astate', 317 | 'name': '%s' %(hostname), 318 | 'pools': [pool_name] 319 | }) 320 | does_exist = True 321 | 322 | # p[3].rules_s.get_collection()[0].actions_s.get_collection()[0].forward 323 | def create_or_update_policy(self, name, rules, iapp): 324 | policy = self.mgmt.tm.ltm.policys.policy 325 | # name = "%s_%d" %(name, time.time()) 326 | draft_policy = True 327 | 328 | if LooseVersion(self.tmos_version) < LooseVersion('12.1.0'): 329 | # draft policy introduced in version 12.1 330 | draft_policy = False 331 | 332 | if not policy.exists(name=name, partition='Common'): 333 | payload = { "strategy": "/Common/first-match", 334 | "name": name, 335 | "partition": "Common", 336 | "fullPath": "/Common/echomap", 337 | "requires": [ 338 | "http" 339 | ], 340 | "controls": [ "forwarding"] 341 | } 342 | if draft_policy: 343 | payload['legacy'] = True 344 | my_pol = policy.create(**payload) 345 | else: 346 | my_pol = policy.load(name=name,partition='Common') 347 | 348 | normalized_rules = [(a['hostname'] + a['uri'],a) for a in rules] 349 | normalized_rules.sort(lambda a,b: cmp(b[0],a[0])) 350 | all_rules = my_pol.rules_s.get_collection() 351 | #>>> a = [1,2,3,4] 352 | #>>> b = [1,2,3] 353 | #>>> a[len(b):] 354 | #[4] 355 | to_delete = all_rules[len(normalized_rules):] 356 | [a.delete() for a in to_delete] 357 | 358 | # not a safe way to update rules / ideally use draft policies from 12.1 359 | # or version number the policy and swap to be atomic 360 | for x in range(len(normalized_rules)): 361 | rule_name = "rule_%02d" %(x+1) 362 | rule = normalized_rules[x][1] 363 | payload = { 'name':rule_name 364 | } 365 | exist = True 366 | if draft_policy: 367 | payload['description'] = "%s%s -> %s:%d" %(rule['hostname'], rule['uri'], rule['backend'],rule['port']) 368 | try: 369 | my_rule = my_pol.rules_s.rules.load(name=payload['name']) 370 | except iControlUnexpectedHTTPError,e: 371 | exist = False 372 | if exist: 373 | my_rule.update(**payload) 374 | else: 375 | my_rule = my_pol.rules_s.rules.create(**payload) 376 | # match host header 377 | payload = {u'caseInsensitive': True, 378 | u'equals': True, 379 | u'external': True, 380 | u'fullPath': u'0', 381 | u'host': True, 382 | u'httpHost': True, 383 | u'index': 0, 384 | u'name': u'0', 385 | u'present': True, 386 | u'remote': True, 387 | u'request': True, 388 | u'values': [rule['hostname']]} 389 | my_rule.conditions_s.conditions.create(**payload) 390 | # match uri 391 | if a['uri'] != '/': 392 | payload = {u'caseInsensitive': True, 393 | u'external': True, 394 | u'fullPath': u'1', 395 | u'httpUri': True, 396 | u'index': 0, 397 | u'name': u'1', 398 | u'path': True, 399 | u'present': True, 400 | u'remote': True, 401 | u'request': True, 402 | u'startsWith': True, 403 | u'values': [rule['uri']]} 404 | my_rule.conditions_s.conditions.create(**payload) 405 | 406 | if iapp: 407 | pool_name = "/Common/%s_%d_app.app/%s_%d_pool" %(rule['backend'],rule['port'],rule['backend'],rule['port']) 408 | else: 409 | pool_name = "/Common/%s_%d_pool" %(rule['backend'],rule['port']) 410 | payload = { 411 | "vlanId": 0, 412 | # "timeout": 0, 413 | "forward": True, 414 | # "expirySecs": 0, 415 | "code": 0, 416 | "fullPath": "0", 417 | "name": "0", 418 | # "length": 0, 419 | # "offset": 0, 420 | "pool": pool_name, 421 | "request": True, 422 | "select": True, 423 | "status": 0 424 | } 425 | 426 | my_rule.actions_s.actions.create(**payload) 427 | 428 | def create_or_update_iapp(self, hostname,target_port,dest,svc_type,pool_members,local_traffic_policy=None): 429 | # Set iApp name and template 430 | app_name = "%s_%s_app" %(hostname, target_port) 431 | vs_name = "%s_%s_vs" %(hostname, target_port) 432 | pool_name = "%s_%s_pool" %(hostname, target_port) 433 | # template = "/Common/appsvcs_integration_v2.0dev_001" 434 | template = "/Common/appsvcs_integration_v1.0_001" 435 | app_pool_members = [] 436 | for (ip,port) in pool_members: 437 | row = {'row': [ ip, port.__str__(), '0', '1', 'enabled'] } 438 | app_pool_members.append(row) 439 | 440 | exist_url = "%s/~%s~%s.app~%s" % (self.url, 'Common', app_name, app_name) 441 | 442 | payload = { 443 | 'template': template, 444 | 'inheritedDevicegroup': 'true', 445 | 'inheritedTrafficGroup': 'true', 446 | 'kind': 'tm:sys:application:service:servicestate', 447 | 'name': app_name, 448 | 'partition': 'Common', 449 | # Pool Members 450 | 'tables': [ { 'columnNames': [ 'IPAddress', 451 | 'Port', 452 | 'ConnectionLimit', 453 | 'Ratio', 454 | 'State'], 455 | 'name': 'pool__Members', 456 | 'rows': app_pool_members 457 | }], 458 | 459 | 'variables': [ 460 | # iApp Options 461 | { 'name': 'iapp__strictUpdates', 462 | 'value': 'enabled'}, 463 | { 'name': 'iapp__appStats', 464 | 'value': 'enabled'}, 465 | { 'name': 'iapp__mode', 466 | 'value': 'auto'}, 467 | { 'name': 'iapp__routeDomain', 468 | 'value': 'auto'}, 469 | 470 | # Virtual Server & Listener Configuration 471 | { 'name': 'pool__addr', 472 | 'value': dest}, # Virtual Service Address 473 | { 'name': 'pool__mask', 474 | 'value': '255.255.255.255'}, 475 | { 'name': 'pool__Name', 476 | 'value': pool_name}, 477 | { 'name': 'pool__Description', 478 | 'value': 'pooldescr'}, 479 | { 'name': 'pool__Monitor', 480 | 'value': '/Common/http'}, 481 | { 'name': 'pool__LbMethod', 482 | 'value': 'round-robin'}, 483 | { 'name': 'pool__MemberDefaultPort', 484 | 'value': '80'}, 485 | 486 | # Virtual Server Configuration 487 | { 'name': 'vs__Name', 488 | 'value': vs_name}, 489 | { 'name': 'vs__Description', 490 | 'value': 'vsdescr'}, 491 | { 'name': 'vs__SourceAddress', 492 | 'value': '0.0.0.0/0'}, 493 | { 'name': 'vs__IpProtocol', 494 | 'value': 'tcp'}, 495 | { 'name': 'vs__ConnectionLimit', 496 | 'value': '0'}, 497 | { 'name': 'vs__ProfileClientProtocol', 498 | 'value': '/Common/tcp-wan-optimized'}, 499 | { 'name': 'vs__ProfileServerProtocol', 500 | 'value': '/Common/tcp-lan-optimized'}, 501 | { 'name': 'vs__ProfileHTTP', 502 | 'value': '/Common/http'}, 503 | { 'name': 'vs__ProfileOneConnect', 504 | 'value': ''}, 505 | { 'name': 'vs__ProfileCompression', 506 | 'value': ''}, 507 | { 'name': 'vs__ProfileDefaultPersist', 508 | 'value': ''}, 509 | { 'name': 'vs__ProfileFallbackPersist', 510 | 'value': ''}, 511 | { 'name': 'vs__SNATConfig', 512 | 'value': 'none'}, 513 | { 'name': 'vs__ProfileSecurityIPBlacklist', 514 | 'value': 'none'}, 515 | { 'name': 'vs__OptionSourcePort', 516 | 'value': 'preserve'}, 517 | { 'name': 'vs__OptionConnectionMirroring', 518 | 'value': 'disabled'}, 519 | 520 | # L4-7 Application Functionality 521 | { 'name': 'feature__statsTLS', 522 | 'value': 'disabled'}, 523 | { 'name': 'feature__statsHTTP', 524 | 'value': 'disabled'}, 525 | { 'name': 'feature__insertXForwardedFor', 526 | 'value': 'auto'}, 527 | { 'name': 'feature__sslEasyCipher', 528 | 'value': 'high'}, 529 | { 'name': 'feature__securityEnableHSTS', 530 | 'value': 'disabled'}, 531 | { 'name': 'feature__easyL4Firewall', 532 | 'value': 'auto'}, 533 | ]} 534 | ssl_variables = [{ 'name': 'vs__ProfileClientSSLCert', 535 | 'value': '/Common/default.crt'}, 536 | { 'name': 'vs__ProfileClientSSLChain', 537 | 'value': '/Common/ca-bundle.crt'}, 538 | { 'name': 'vs__ProfileClientSSLKey', 539 | 'value': '/Common/default.key'}, 540 | { 'name': 'pool__port', 541 | 'value': '443'}] 542 | nossl_variables = [{'name': 'pool__port', 543 | 'value': '80'}] 544 | just_ssl_variables = [{ 'name': 'feature__redirectToHTTPS', 545 | 'value': 'enabled'}] 546 | payload['variables'].extend(nossl_variables) 547 | # print payload 548 | # payload['variables'].extend(ssl_variables) 549 | # payload['variables'].extend(just_ssl_variables) 550 | 551 | if not self.mgmt.tm.sys.application.services.service.exists(name=app_name, partition='Common'): 552 | service = self.mgmt.tm.sys.application.services.service 553 | service.create(**payload) 554 | 555 | else: 556 | svc = self.mgmt.tm.sys.application.services.service.load(name=app_name, partition = 'Common') 557 | payload["execute-action"] = "definition" 558 | # if svc.tables != payload['tables']: 559 | # svc.update(**payload) 560 | 561 | 562 | def get_networks(): 563 | client = etcd.Client() 564 | res = client.read('/coreos.com/network/config/') 565 | my_networks = {} 566 | network_config = json.loads(res.value) 567 | res = client.get('/coreos.com/network/subnets') 568 | for subnet in [a.key for a in res.children]: 569 | res = client.read(subnet) 570 | data = json.loads(res.value) 571 | subnet_name = subnet.split('/')[-1].replace('-','/') 572 | # public_ip = data['PublicIP'] 573 | # vtep_mac_addr = data["BackendData"]["VtepMAC"] 574 | my_networks[subnet_name] = data 575 | 576 | return (network_config, my_networks) 577 | 578 | 579 | def get_services_policies(config_file="/home/user/.kube/config"): 580 | api = pykube.http.HTTPClient(pykube.config.KubeConfig.from_file(config_file)) 581 | 582 | pods = pykube.objects.Pod.objects(api).filter(namespace="default") 583 | services = pykube.objects.Service.objects(api).filter(namespace="default") 584 | ready_pods = filter(operator.attrgetter("ready"), pods) 585 | 586 | endpoints = pykube.objects.Endpoint.objects(api).filter(namespace="default") 587 | ingresses = pykube.objects.Ingress.objects(api).filter(namespace="default") 588 | 589 | my_services = {} 590 | my_policies = {} 591 | 592 | # 593 | # Grab L7 ingress 594 | # 595 | for ing in ingresses: 596 | # print ing.obj['metadata']['name'] 597 | # print ing.obj['spec']['backend']['serviceName'] 598 | # print ing.obj['spec']['backend']['servicePort'] 599 | ing_name = ing.obj['metadata']['name'] 600 | my_rules = [] 601 | for rule in ing.obj['spec']['rules']: 602 | # print rule 603 | hostname = rule['host'] 604 | for path in rule['http']['paths']: 605 | uri = path['path'] 606 | backend = path['backend']['serviceName'] 607 | port = path['backend']['servicePort'] 608 | # print hostname,uri, backend, port 609 | my_rules.append({'hostname':hostname, 610 | 'uri':uri, 611 | 'backend':backend, 612 | 'port':port}) 613 | my_policies[ing_name] = {'rules': my_rules } 614 | if 'annotations' in ing.obj['metadata']: 615 | if 'f5.destination' in ing.obj['metadata']['annotations']: 616 | my_policies[ing_name]['dest'] = ing.obj['metadata']['annotations']['f5.destination'] 617 | 618 | 619 | # foo.bar.com /foo echoheadersx 80 620 | # 621 | # Grab endpoints (internal IPs) 622 | # 623 | 624 | #for eps in endpoints: 625 | # print eps.obj['metadata']['name'] 626 | # for pod in eps.obj['subsets']: 627 | # print pod 628 | 629 | # 630 | # Grab services L4 services 631 | # 632 | for service in services: 633 | skip_service = False 634 | svc = {'pods':[]} 635 | # print service 636 | # print service.obj['status'] 637 | # print service.obj['spec']['ports'] 638 | # print service.__dict__ 639 | 640 | svc['clusterIP'] = service.obj['spec']['clusterIP'] 641 | if 'externalIPs' in service.obj['spec']: 642 | svc['loadbalancerIP'] = service.obj['spec']['externalIPs'][0] 643 | # prefer loadbalancerIP https://github.com/kubernetes/kubernetes/pull/13005 644 | if 'loadbalancerIP' in service.obj['spec']: 645 | svc['loadbalancerIP'] = service.obj['spec']['loadbalancerIP'] 646 | # fallback to clusterIP 647 | if 'loadbalancerIP' not in svc: 648 | svc['loadbalancerIP'] = svc['clusterIP'] 649 | # override with f5 variables 650 | # print service.obj['metadata'] 651 | if 'annotations' in service.obj['metadata']: 652 | if 'f5.destination' in service.obj['metadata']['annotations']: 653 | svc['loadbalancerIP'] = service.obj['metadata']['annotations']['f5.destination'] 654 | if 'kubernetes.io/ingress.class' in service.obj['metadata']['annotations']: 655 | if service.obj['metadata']['annotations']["kubernetes.io/ingress.class"] != 'f5.bigip': 656 | skip_service = True 657 | for key in service.obj['metadata']['annotations']: 658 | # print key 659 | if key.startswith('f5.vs__'): 660 | svc[key[3:]] = service.obj['metadata']['annotations'][key] 661 | 662 | if skip_service: 663 | continue 664 | 665 | svc['ports'] = service.obj['spec']['ports'] 666 | svc['targetPort'] = service.obj['spec']['ports'][0]['targetPort'] 667 | if 'selector' not in service.obj['spec']: 668 | continue 669 | svc['selector'] = service.obj['spec']['selector'] 670 | # print service.obj['spec'] 671 | svc['name'] = service.obj['metadata']['name'] 672 | svc['namespace'] = service.obj['metadata']['namespace'] 673 | svc_pods = pods.filter(namespace=svc['namespace'],selector=svc['selector']) 674 | # print svc_pods 675 | # 676 | # Grab pods (external IP) 677 | # 678 | for pod in svc_pods: 679 | # print pod.obj['metadata'] 680 | my_run = pod.obj['spec']['containers'][0]['name'] 681 | my_pod = {} 682 | my_pod['hostIP'] = pod.obj['status']['hostIP'] 683 | if 'podIP' in pod.obj['status']: 684 | my_pod['podIP'] = pod.obj['status']['podIP'] 685 | svc['pods'].append(my_pod) 686 | my_services[svc['name']] = svc 687 | return (my_services,my_policies) 688 | def get_bigip_cfg(my_services,routed=True,pool_rd=0): 689 | bigip_cfg = {} 690 | for svc in my_services: 691 | my_svc = my_services[svc] 692 | for port in my_svc['ports']: 693 | vs_name = "%s_%s_vs" %(svc,port['port']) 694 | protocol = port['protocol'] 695 | pool_name = "%s_%s_pool" %(svc,port['port']) 696 | members = [] 697 | nodes = set() 698 | for pod in my_svc['pods']: 699 | if routed: 700 | if pool_rd: 701 | member_ip = "%s%%%d" %(pod['podIP'],pool_rd) 702 | else: 703 | member_ip = pod['podIP'] 704 | member_port = port['targetPort'] 705 | else: 706 | member_ip = pod['hostIP'] 707 | member_port = port['nodePort'] 708 | nodes.add((pod['podIP'],pod['hostIP'])) 709 | member = (member_ip, member_port) 710 | members.append(member) 711 | bigip_cfg[vs_name] = {'name': svc, 712 | 'port': port['port'], 713 | 'protocol': protocol, 714 | 'pool_name': pool_name, 715 | 'pool_members': members, 716 | 'nodes': nodes} 717 | for key in my_svc: 718 | if key.startswith('vs__'): 719 | bigip_cfg[vs_name][key] = my_svc[key] 720 | if 'loadbalancerIP' in my_svc: 721 | bigip_cfg[vs_name]['dest'] = my_svc['loadbalancerIP'] 722 | else: 723 | bigip_cfg[vs_name]['dest'] = None # do not create vs 724 | return bigip_cfg 725 | 726 | if __name__ == "__main__": 727 | iapp = True 728 | dns = True 729 | routed = True 730 | vxlan = False 731 | 732 | (network_config, my_networks) = get_networks() 733 | i_promise = os.getenv('I_PROMISE_NOT_TO_RUN_THIS_IN_PRODUCTION') 734 | if not i_promise: 735 | print "ERROR: You must promise not to run this in production or acknowledge you do so at your own risk!" 736 | sys.exit(1) 737 | 738 | iapp = os.getenv('USE_IAPP') == 'TRUE' or False 739 | dns = os.getenv('USE_DNS') == 'TRUE' or False 740 | network_type = os.getenv('NETWORK_TYPE') 741 | if network_type == 'VXLAN': 742 | routed = False 743 | vxlan = True 744 | else: 745 | routed = True 746 | vxlan = False 747 | 748 | bigip_user = os.getenv('BIGIP_USER') or 'admin' 749 | bigip_password = os.getenv('BIGIP_PASSWD') or 'admin' 750 | bigip_host = os.getenv('BIGIP_HOST') or '192.168.1.245' 751 | kube_config = os.getenv('KUBE_CONFIG') or "/home/user/.kube/config" 752 | kube2bigip = KubeToBigIP(username=bigip_user, password=bigip_password, host=bigip_host) 753 | 754 | (my_services, my_policies) = get_services_policies(config_file=kube_config) 755 | 756 | if vxlan: 757 | my_vs = get_bigip_cfg(my_services,routed=routed,pool_rd=1) 758 | else: 759 | my_vs = get_bigip_cfg(my_services,routed=routed) 760 | 761 | records = kube2bigip.create_or_update_network(network_config, my_networks, vxlan=vxlan) 762 | 763 | ip_to_mac = dict([(a['endpoint'],a['name']) for a in records]) 764 | all_ips = set() 765 | 766 | # print ip_to_mac 767 | 768 | # pp.pprint(my_services) 769 | # pp.pprint(my_policies) 770 | # pp.pprint(my_vs) 771 | for vs_name in my_vs: 772 | vs = my_vs[vs_name] 773 | logging.debug(vs) 774 | all_ips.update(vs['nodes']) 775 | if 'dest' not in vs or vs['dest'] == None: 776 | continue 777 | if iapp: 778 | kube2bigip.create_or_update_iapp(vs['name'], 779 | vs['port'], 780 | vs['dest'], 781 | 'http', 782 | vs['pool_members']) 783 | else: 784 | kube2bigip.create_or_update_vs(vs) 785 | if dns: 786 | kube2bigip.create_or_update_dns(vs,"%s.%s" %(vs['name'],'f5demo.com'), 787 | 'bigip1', 788 | 'hq') 789 | if 'vs__AdvPolicies' in vs: 790 | hostnames = [a['hostname'] for a in my_policies[vs['vs__AdvPolicies'].split('/')[-1]]['rules']] 791 | for hostname in hostnames: 792 | kube2bigip.create_or_update_dns(vs,hostname, 793 | 'bigip1', 794 | 'hq') 795 | 796 | if vxlan: 797 | kube2bigip.create_or_update_network_arp(all_ips, ip_to_mac) 798 | 799 | for policy_name in my_policies: 800 | # print policy_name 801 | policy = my_policies[policy_name] 802 | # print policy 803 | kube2bigip.create_or_update_policy(policy_name,policy['rules'],iapp=iapp) 804 | # sys.exit(0) 805 | 806 | 807 | -------------------------------------------------------------------------------- /kubernetes-example/www.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | run: www 6 | name: www 7 | namespace: default 8 | spec: 9 | replicas: 3 10 | selector: 11 | matchLabels: 12 | run: www 13 | strategy: 14 | rollingUpdate: 15 | maxSurge: 1 16 | maxUnavailable: 1 17 | type: RollingUpdate 18 | template: 19 | metadata: 20 | labels: 21 | run: www 22 | spec: 23 | containers: 24 | - env: 25 | - name: F5DEMO_NODENAME 26 | value: www 27 | - name: F5DEMO_COLOR 28 | value: adafaf 29 | image: erchen/f5demo:latest 30 | imagePullPolicy: IfNotPresent 31 | name: www 32 | ports: 33 | - containerPort: 80 34 | protocol: TCP 35 | -------------------------------------------------------------------------------- /pcf-example/README.md: -------------------------------------------------------------------------------- 1 | Pivotal Cloud Foundry (PCF) Example 2 | =================================== 3 | 4 | DevCentral article: [Evolving Programmability with Pivotal Cloud Foundry](https://devcentral.f5.com/articles/evolving-programmability-with-pivotal-cloud-foundry-21723) 5 | ### About 6 | 7 | * pcf-phase1.py: create monitor, pool, pool member using iControl REST 8 | * pcf-phase2.py: phase1 + use PCF API 9 | * pcf-phase3.py: phase2 + use iWorkflow instead of iControl REST 10 | * iwf-helper.py: helper script for generating iWorkflow templates 11 | 12 | ### Requirements 13 | Python 2.7 14 | 15 | Python Modules: f5-sdk (1.3.1), cloudfoundry-client (0.0.13) 16 | 17 | F5 BIG-IP LTM 11.6.1 18 | F5 iWorkflow 2.x 19 | 20 | ### Example Inputs 21 | 22 | Create two pool members in Active/Standby with custom HTTP monitor and L7 23 | local traffic policy. 24 | 25 | ``` 26 | % python pcf-phase1.py -a create 10.1.1.245 dora.local.pcfdev.io 10.0.2.10:80,10.0.12.10:80 27 | Created pool /Common/dora.local.pcfdev.io_pool 28 | Added member 10.0.12.10:80 29 | Added member 10.0.2.10:80 30 | Created policy rule dora.local.pcfdev.io 31 | 32 | ``` 33 | Create two pool members in Active/Standby with custom HTTP monitor and L7 34 | local traffic policy using PCF API for application names. 35 | ``` 36 | % python pcf-phase2.py -a create 10.1.1.245 10.0.2.10:80,10.0.12.10:80 37 | Created pool /Common/dora.local.pcfdev.io_pool 38 | Added member 10.0.12.10:80 39 | Added member 10.0.2.10:80 40 | Created policy rule dora.local.pcfdev.io 41 | Created pool /Common/dora2.local.pcfdev.io_pool 42 | Added member 10.0.12.10:80 43 | Added member 10.0.2.10:80 44 | Created policy rule dora2.local.pcfdev.io 45 | 46 | ``` 47 | Create a template in iWorkflow (requires an existing tenant called "pcfdev_tenant"). 48 | 49 | ``` 50 | python iwf-helper.py 10.1.1.246 -a import_template \ 51 | --template dora_template_v1.0 \ 52 | --tenant pcfdev_tenant \ 53 | --file dora_template_v1.0.yaml 54 | 55 | ``` 56 | Create two pool members in Active/Standby with custom HTTP monitor and L7 57 | local traffic policy using PCF API for application names via iWorkflow. 58 | ``` 59 | % python pcf-phase3.py 10.1.1.246 10.0.2.10,10.0.12.10 \ 60 | -a create_iapp \ 61 | --iapp dora_app_v1.0 \ 62 | --service_name "dora_template_v2.0" 63 | Created iApp dora_app_v1.0 64 | ``` 65 | 66 | 67 | ### Authored By 68 | 69 | [Eric Chen](https://devcentral.f5.com/users/123940) | [@chen23](https://github.com/chen23) 70 | -------------------------------------------------------------------------------- /pcf-example/dora_template_v1.0.yaml: -------------------------------------------------------------------------------- 1 | generation: 1 2 | isF5Example: false 3 | kind: cm:cloud:provider:templates:iapp:provideriapptemplateworkerstate 4 | lastUpdateMicros: 1472266482143812 5 | overrides: 6 | tables: 7 | - columns: 8 | - {description: 'CIDR Block:', isRequired: false, name: CIDRRange, provider: ''} 9 | description: 'Security: Firewall: Static Blacklisted Addresses (CIDR Format)' 10 | name: feature__easyL4FirewallBlacklist 11 | - columns: 12 | - {description: 'CIDR Block:', isRequired: false, name: CIDRRange, provider: 0.0.0.0/0} 13 | description: 'Security: Firewall: Static Allowed Source Addresses (CIDR Format)' 14 | name: feature__easyL4FirewallSourceList 15 | - columns: 16 | - {defaultValue: '', description: 'Group:', isRequired: false, name: Group} 17 | - {defaultValue: '', description: 'Parameter:', isRequired: false, name: Parameter} 18 | - {description: 'Target:', isRequired: false, name: Target, provider: forward/request/select/pool} 19 | description: 'L7 Policy: Rules: Action' 20 | name: l7policy__rulesAction 21 | - columns: 22 | - {description: 'Case Sensitive:', isRequired: false, name: CaseSensitive, provider: 'no'} 23 | - {description: 'Condition:', isRequired: false, name: Condition, provider: equals} 24 | - {defaultValue: '', description: 'Group:', isRequired: false, name: Group} 25 | - {description: 'Missing:', isRequired: false, name: Missing, provider: 'no'} 26 | - {description: 'Negate:', isRequired: false, name: Negate, provider: 'no'} 27 | - {description: 'Operand:', isRequired: false, name: Operand, provider: http-host/request/host} 28 | - {defaultValue: '', description: 'Value:', isRequired: false, name: Value} 29 | description: 'L7 Policy: Rules: Matching' 30 | name: l7policy__rulesMatch 31 | - columns: 32 | - {description: 'Adv Options:', isRequired: false, name: AdvOptions, provider: ''} 33 | - {description: 'Connection Limit:', isRequired: false, name: ConnectionLimit, 34 | provider: '0'} 35 | - {defaultValue: '', description: 'IP/Node Name:', isRequired: false, name: IPAddress} 36 | - {defaultValue: '0', description: 'Pool Idx:', isRequired: false, name: Index, 37 | validator: NonNegativeNumber} 38 | - {description: 'Port:', isRequired: false, name: Port, provider: '80'} 39 | - {defaultValue: '0', description: 'Priority Group:', isRequired: false, name: PriorityGroup} 40 | - {description: 'Ratio:', isRequired: false, name: Ratio, provider: '1'} 41 | - {defaultValue: enabled, description: 'State:', isRequired: false, name: State} 42 | description: 'Pool: Members' 43 | name: pool__Members 44 | - columns: 45 | - {description: 'Adv Options:', isRequired: false, name: AdvOptions, provider: min-active-members=1} 46 | - {description: 'Description:', isRequired: false, name: Description, provider: ''} 47 | - {defaultValue: '0', description: 'Index:', isRequired: false, name: Index, providerType: PORT, 48 | validator: NonNegativeNumber} 49 | - {description: 'LB Method:', isRequired: false, name: LbMethod, provider: round-robin} 50 | - {defaultValue: '', description: 'Monitor(s):', isRequired: false, name: Monitor} 51 | - {defaultValue: '', description: 'Name:', isRequired: false, name: Name} 52 | description: 'Pool: Pool Table' 53 | name: pool__Pools 54 | serverTier: pcfdev-app 55 | - columns: 56 | - {description: Destination, isRequired: false, name: Destination, provider: ''} 57 | - {description: 'Listener:', isRequired: false, name: Listener, provider: ''} 58 | description: 'Virtual Server: Additional Listeners' 59 | name: vs__Listeners 60 | - columns: 61 | - {defaultValue: '0', description: 'Index:', isRequired: false, name: Index, validator: NonNegativeNumber} 62 | - {defaultValue: '', description: 'Name:', isRequired: false, name: Name} 63 | - {defaultValue: '', description: 'Options:', isRequired: false, name: Options} 64 | - {description: 'Type:', isRequired: false, name: Type, provider: http} 65 | description: 'Monitor: Monitor Table' 66 | name: monitor__Monitors 67 | vars: 68 | - {description: 'Extensions: Field 1', displayName: Field1, isRequired: false, name: extensions__Field1, 69 | provider: ''} 70 | - {description: 'Extensions: Field 2', displayName: Field2, isRequired: false, name: extensions__Field2, 71 | provider: ''} 72 | - {description: 'Extensions: Field 3', displayName: Field3, isRequired: false, name: extensions__Field3, 73 | provider: ''} 74 | - choices: 75 | - {description: auto, value: auto} 76 | - {description: base, value: base} 77 | - {description: base+ip_blacklist_block, value: base+ip_blacklist_block} 78 | - {description: base+ip_blacklist_log, value: base+ip_blacklist_log} 79 | - {description: disabled, value: disabled} 80 | description: 'Security: Firewall: Configure L4 Firewall Policy' 81 | displayName: easyL4Firewall 82 | isRequired: false 83 | name: feature__easyL4Firewall 84 | provider: auto 85 | - choices: 86 | - {description: auto, value: auto} 87 | - {description: enabled, value: enabled} 88 | - {description: disabled, value: disabled} 89 | description: 'HTTP: Insert X-Forwarded-For Header' 90 | displayName: insertXForwardedFor 91 | isRequired: false 92 | name: feature__insertXForwardedFor 93 | provider: auto 94 | - choices: 95 | - {description: auto, value: auto} 96 | - {description: enabled, value: enabled} 97 | - {description: disabled, value: disabled} 98 | description: 'HTTP: Security: Create HTTP(80)->HTTPS(443) Redirect' 99 | displayName: redirectToHTTPS 100 | isRequired: false 101 | name: feature__redirectToHTTPS 102 | provider: disabled 103 | - choices: 104 | - {description: disabled, value: disabled} 105 | - {description: enabled, value: enabled} 106 | - {description: enabled-preload, value: enabled-preload} 107 | - {description: enabled-subdomain, value: enabled-subdomain} 108 | - {description: enabled-preload-subdomain, value: enabled-preload-subdomain} 109 | description: 'HTTP: Security: Enable HTTP Strict Transport Security (only valid 110 | if ClientSSL is configured)' 111 | displayName: securityEnableHSTS 112 | isRequired: false 113 | name: feature__securityEnableHSTS 114 | provider: disabled 115 | - choices: 116 | - {description: compatible, value: compatible} 117 | - {description: medium, value: medium} 118 | - {description: high, value: high} 119 | - {description: tls_1.2, value: tls_1.2} 120 | - {description: tls_1.1+1.2, value: tls_1.1+1.2} 121 | - {description: disabled, value: disabled} 122 | description: 'TLS/SSL: Easy Cipher String (overrides VS section setting)' 123 | displayName: sslEasyCipher 124 | isRequired: false 125 | name: feature__sslEasyCipher 126 | provider: disabled 127 | - choices: 128 | - {description: auto, value: auto} 129 | - {description: enabled, value: enabled} 130 | - {description: disabled, value: disabled} 131 | description: 'HTTP: Stats Reporting' 132 | displayName: statsHTTP 133 | isRequired: false 134 | name: feature__statsHTTP 135 | provider: auto 136 | - choices: 137 | - {description: auto, value: auto} 138 | - {description: enabled, value: enabled} 139 | - {description: disabled, value: disabled} 140 | description: 'TLS/SSL: Stats Reporting' 141 | displayName: statsTLS 142 | isRequired: false 143 | name: feature__statsTLS 144 | provider: disabled 145 | - choices: 146 | - {description: preserve-bypass, value: preserve-bypass} 147 | - {description: preserve-block, value: preserve-block} 148 | - {description: redeploy-bypass, value: redeploy-bypass} 149 | - {description: redeploy-block, value: redeploy-block} 150 | description: 'iApp: APM: Deployment Mode' 151 | displayName: apmDeployMode 152 | isRequired: false 153 | name: iapp__apmDeployMode 154 | provider: preserve-bypass 155 | - choices: 156 | - {description: enabled, value: enabled} 157 | - {description: disabled, value: disabled} 158 | description: 'iApp: Statistics Handler Creation' 159 | displayName: appStats 160 | isRequired: false 161 | name: iapp__appStats 162 | provider: enabled 163 | - choices: 164 | - {description: preserve-bypass, value: preserve-bypass} 165 | - {description: preserve-block, value: preserve-block} 166 | - {description: redeploy-bypass, value: redeploy-bypass} 167 | - {description: redeploy-block, value: redeploy-block} 168 | description: 'iApp: ASM: Deployment Mode' 169 | displayName: asmDeployMode 170 | isRequired: false 171 | name: iapp__asmDeployMode 172 | provider: preserve-bypass 173 | - {description: 'iApp: Log Level', displayName: logLevel, isRequired: false, name: iapp__logLevel, 174 | provider: '7'} 175 | - {description: 'iApp: Mode', displayName: mode, isRequired: false, name: iapp__mode, 176 | provider: auto} 177 | - {description: 'iApp: Route Domain', displayName: routeDomain, isRequired: false, 178 | name: iapp__routeDomain, provider: auto} 179 | - choices: 180 | - {description: enabled, value: enabled} 181 | - {description: disabled, value: disabled} 182 | description: 'iApp: Strict Updates' 183 | displayName: strictUpdates 184 | isRequired: false 185 | name: iapp__strictUpdates 186 | provider: enabled 187 | - {description: 'L7 Policy: Default ASM Policy', displayName: defaultASM, isRequired: false, 188 | name: l7policy__defaultASM, provider: bypass} 189 | - {description: 'L7 Policy: Default L7 DoS Policy', displayName: defaultL7DOS, isRequired: false, 190 | name: l7policy__defaultL7DOS, provider: bypass} 191 | - {description: 'L7 Policy: Match Strategy', displayName: strategy, isRequired: false, 192 | name: l7policy__strategy, provider: /Common/first-match} 193 | - {description: 'Virtual Server: Default Pool Index', displayName: DefaultPoolIndex, 194 | isRequired: false, name: pool__DefaultPoolIndex, provider: '0', validator: NonNegativeNumber} 195 | - {description: 'Pool: Member Default Port', displayName: MemberDefaultPort, isRequired: false, 196 | name: pool__MemberDefaultPort, provider: '80'} 197 | - {description: 'Virtual Server: Address', displayName: addr, isRequired: true, 198 | name: pool__addr, provider: 10.1.10.100, providerType: NODE, serverTier: pcfdev-app, 199 | validator: IpAddress} 200 | - {description: 'Virtual Server: Mask', displayName: mask, isRequired: true, name: pool__mask, 201 | provider: 255.255.255.255, validator: IpAddress} 202 | - {description: 'Virtual Server: Port', displayName: port, isRequired: true, name: pool__port, 203 | provider: '80', providerType: PORT, serverTier: pcfdev-app, validator: PortNumber} 204 | - {description: 'Virtual Server: Advanced Options', displayName: AdvOptions, isRequired: false, 205 | name: vs__AdvOptions, provider: ''} 206 | - {description: 'Virtual Server: Advanced Policies', displayName: AdvPolicies, isRequired: false, 207 | name: vs__AdvPolicies, provider: ''} 208 | - {description: 'Virtual Server: Advanced Profiles', displayName: AdvProfiles, isRequired: false, 209 | name: vs__AdvProfiles, provider: ''} 210 | - {description: 'Virtual Server: Bundled Items', displayName: BundledItems, isRequired: false, 211 | name: vs__BundledItems, provider: ''} 212 | - {description: 'Virtual Server: Virtual Server Connection Limit (0=unlimited)', 213 | displayName: ConnectionLimit, isRequired: false, name: vs__ConnectionLimit, provider: '0'} 214 | - {description: 'Virtual Server: Description', displayName: Description, isRequired: false, 215 | name: vs__Description, provider: ''} 216 | - {description: 'Virtual Server: IP Protocol', displayName: IpProtocol, isRequired: false, 217 | name: vs__IpProtocol, provider: tcp} 218 | - {description: 'Virtual Server: iRules (to specify multiple iRules seperate with 219 | a comma ex: irule1,irule2,irule3)', displayName: Irules, isRequired: false, 220 | name: vs__Irules, provider: ''} 221 | - {description: 'Virtual Server: Name', displayName: Name, isRequired: false, name: vs__Name, 222 | provider: pcfdev_app} 223 | - choices: 224 | - {description: enabled, value: enabled} 225 | - {description: disabled, value: disabled} 226 | description: 'Virtual Server: Connection Mirroring' 227 | displayName: OptionConnectionMirroring 228 | isRequired: false 229 | name: vs__OptionConnectionMirroring 230 | provider: disabled 231 | - choices: 232 | - {description: preserve, value: preserve} 233 | - {description: preserve-strict, value: preserve-strict} 234 | - {description: change, value: change} 235 | description: 'Virtual Server: Source Port Behavior' 236 | displayName: OptionSourcePort 237 | isRequired: false 238 | name: vs__OptionSourcePort 239 | provider: preserve 240 | - {description: 'Virtual Server: Access Profile', displayName: ProfileAccess, isRequired: false, 241 | name: vs__ProfileAccess, provider: ''} 242 | - {description: 'Virtual Server: Analytics Profile', displayName: ProfileAnalytics, 243 | isRequired: false, name: vs__ProfileAnalytics, provider: ''} 244 | - {description: 'Virtual Server: Client-side L4 Protocol Profile', displayName: ProfileClientProtocol, 245 | isRequired: false, name: vs__ProfileClientProtocol, provider: /Common/tcp-wan-optimized} 246 | - {description: 'Virtual Server: Client SSL Profile', displayName: ProfileClientSSL, 247 | isRequired: false, name: vs__ProfileClientSSL, provider: ''} 248 | - {description: 'Virtual Server: Client SSL Advanced Options', displayName: ProfileClientSSLAdvOptions, 249 | isRequired: false, name: vs__ProfileClientSSLAdvOptions, provider: ''} 250 | - {description: 'Virtual Server: Client SSL Certificate', displayName: ProfileClientSSLCert, 251 | isRequired: false, name: vs__ProfileClientSSLCert, provider: ''} 252 | - {description: 'Virtual Server: Client SSL Certificate Chain', displayName: ProfileClientSSLChain, 253 | isRequired: false, name: vs__ProfileClientSSLChain, provider: ''} 254 | - {description: 'Virtual Server: Client SSL Cipher String', displayName: ProfileClientSSLCipherString, 255 | isRequired: false, name: vs__ProfileClientSSLCipherString, provider: ''} 256 | - {description: 'Virtual Server: Client SSL Key', displayName: ProfileClientSSLKey, 257 | isRequired: false, name: vs__ProfileClientSSLKey, provider: ''} 258 | - {description: 'Virtual Server: Compression Profile', displayName: ProfileCompression, 259 | isRequired: false, name: vs__ProfileCompression, provider: /Common/httpcompression} 260 | - {description: 'Virtual Server: Connectivity Profile', displayName: ProfileConnectivity, 261 | isRequired: false, name: vs__ProfileConnectivity, provider: ''} 262 | - {description: 'Virtual Server: Default Persistence Profile', displayName: ProfileDefaultPersist, 263 | isRequired: false, name: vs__ProfileDefaultPersist, provider: /Common/cookie} 264 | - {description: 'Virtual Server: Fallback Persistence Profile', displayName: ProfileFallbackPersist, 265 | isRequired: false, name: vs__ProfileFallbackPersist, provider: /Common/source_addr} 266 | - {description: 'Virtual Server: HTTP Profile', displayName: ProfileHTTP, isRequired: false, 267 | name: vs__ProfileHTTP, provider: /Common/http} 268 | - {description: 'Virtual Server: OneConnect Profile', displayName: ProfileOneConnect, 269 | isRequired: false, name: vs__ProfileOneConnect, provider: /Common/oneconnect} 270 | - {description: 'Virtual Server: Per-Request Profile', displayName: ProfilePerRequest, 271 | isRequired: false, name: vs__ProfilePerRequest, provider: ''} 272 | - {description: 'Virtual Server: Request Logging Profile', displayName: ProfileRequestLogging, 273 | isRequired: false, name: vs__ProfileRequestLogging, provider: ''} 274 | - {description: 'Virtual Server: Security: DoS Profile', displayName: ProfileSecurityDoS, 275 | isRequired: false, name: vs__ProfileSecurityDoS, provider: ''} 276 | - {description: 'Virtual Server: IP Blacklist Profile', displayName: ProfileSecurityIPBlacklist, 277 | isRequired: false, name: vs__ProfileSecurityIPBlacklist, provider: none} 278 | - {description: 'Virtual Server: Security Logging Profiles', displayName: ProfileSecurityLogProfiles, 279 | isRequired: false, name: vs__ProfileSecurityLogProfiles, provider: ''} 280 | - {description: 'Virtual Server: Server-side L4 Protocol Profile', displayName: ProfileServerProtocol, 281 | isRequired: false, name: vs__ProfileServerProtocol, provider: /Common/tcp-lan-optimized} 282 | - {description: 'Virtual Server: Server SSL Profile', displayName: ProfileServerSSL, 283 | isRequired: false, name: vs__ProfileServerSSL, provider: ''} 284 | - choices: 285 | - {description: disabled, value: disabled} 286 | - {description: all_vs, value: all_vs} 287 | - {description: any_vs, value: any_vs} 288 | - {description: always, value: always} 289 | description: 'Virtual Server: Route Advertisement' 290 | displayName: RouteAdv 291 | isRequired: false 292 | name: vs__RouteAdv 293 | provider: disabled 294 | - {description: 'Virtual Server: SNAT Configuration (enter SNAT pool name, ''automap'' 295 | or leave blank to disable SNAT)', displayName: SNATConfig, isRequired: false, 296 | name: vs__SNATConfig, provider: automap} 297 | - {description: 'Virtual Server: Source Address', displayName: SourceAddress, isRequired: false, 298 | name: vs__SourceAddress, provider: 0.0.0.0/0} 299 | - {description: 'Virtual Address: Advanced Options', displayName: VirtualAddrAdvOptions, 300 | isRequired: false, name: vs__VirtualAddrAdvOptions, provider: ''} 301 | - {description: Enable health and performance monitoring., displayName: app_stats, 302 | isRequired: false, name: app_stats, provider: enabled} 303 | parentReference: {link: 'https://localhost/mgmt/cm/cloud/templates/iapp/appsvcs_integration_v2.0_001'} 304 | properties: 305 | - {id: cloudConnectorReference, isRequired: true} 306 | selfLink: https://localhost/mgmt/cm/cloud/provider/templates/iapp/dora_template_v1.0 307 | templateName: dora_template_v1.0 308 | tenantTemplateReference: {link: 'https://localhost/mgmt/cm/cloud/tenant/templates/iapp/dora_template_v1.0'} 309 | -------------------------------------------------------------------------------- /pcf-example/dora_template_v2.0.yaml: -------------------------------------------------------------------------------- 1 | generation: 1 2 | isF5Example: false 3 | kind: cm:cloud:provider:templates:iapp:provideriapptemplateworkerstate 4 | lastUpdateMicros: 1472266482143812 5 | overrides: 6 | tables: 7 | - columns: 8 | - {description: 'CIDR Block:', isRequired: false, name: CIDRRange, provider: ''} 9 | description: 'Security: Firewall: Static Blacklisted Addresses (CIDR Format)' 10 | name: feature__easyL4FirewallBlacklist 11 | - columns: 12 | - {description: 'CIDR Block:', isRequired: false, name: CIDRRange, provider: 0.0.0.0/0} 13 | description: 'Security: Firewall: Static Allowed Source Addresses (CIDR Format)' 14 | name: feature__easyL4FirewallSourceList 15 | - columns: 16 | - {defaultValue: '', description: 'Group:', isRequired: false, name: Group} 17 | - {defaultValue: '', description: 'Parameter:', isRequired: false, name: Parameter} 18 | - {description: 'Target:', isRequired: false, name: Target, provider: forward/request/select/pool} 19 | description: 'L7 Policy: Rules: Action' 20 | name: l7policy__rulesAction 21 | - columns: 22 | - {description: 'Case Sensitive:', isRequired: false, name: CaseSensitive, provider: 'no'} 23 | - {description: 'Condition:', isRequired: false, name: Condition, provider: equals} 24 | - {defaultValue: '', description: 'Group:', isRequired: false, name: Group} 25 | - {description: 'Missing:', isRequired: false, name: Missing, provider: 'no'} 26 | - {description: 'Negate:', isRequired: false, name: Negate, provider: 'no'} 27 | - {description: 'Operand:', isRequired: false, name: Operand, provider: http-host/request/host} 28 | - {defaultValue: '', description: 'Value:', isRequired: false, name: Value} 29 | description: 'L7 Policy: Rules: Matching' 30 | name: l7policy__rulesMatch 31 | - columns: 32 | - {description: 'Adv Options:', isRequired: false, name: AdvOptions, provider: ''} 33 | - {description: 'Connection Limit:', isRequired: false, name: ConnectionLimit, 34 | provider: '0'} 35 | - {defaultValue: '', description: 'IP/Node Name:', isRequired: false, name: IPAddress} 36 | - {defaultValue: '0', description: 'Pool Idx:', isRequired: false, name: Index, 37 | validator: NonNegativeNumber} 38 | - {description: 'Port:', isRequired: false, name: Port, provider: '80'} 39 | - {defaultValue: '0', description: 'Priority Group:', isRequired: false, name: PriorityGroup} 40 | - {description: 'Ratio:', isRequired: false, name: Ratio, provider: '1'} 41 | - {defaultValue: enabled, description: 'State:', isRequired: false, name: State} 42 | description: 'Pool: Members' 43 | name: pool__Members 44 | - columns: 45 | - {description: 'Adv Options:', isRequired: false, name: AdvOptions, provider: min-active-members=1} 46 | - {description: 'Description:', isRequired: false, name: Description, provider: ''} 47 | - {defaultValue: '0', description: 'Index:', isRequired: false, name: Index, providerType: PORT, 48 | validator: NonNegativeNumber} 49 | - {description: 'LB Method:', isRequired: false, name: LbMethod, provider: least-connections-member} 50 | - {defaultValue: '', description: 'Monitor(s):', isRequired: false, name: Monitor} 51 | - {defaultValue: '', description: 'Name:', isRequired: false, name: Name} 52 | description: 'Pool: Pool Table' 53 | name: pool__Pools 54 | serverTier: pcfdev-app 55 | - columns: 56 | - {description: Destination, isRequired: false, name: Destination, provider: ''} 57 | - {description: 'Listener:', isRequired: false, name: Listener, provider: ''} 58 | description: 'Virtual Server: Additional Listeners' 59 | name: vs__Listeners 60 | - columns: 61 | - {defaultValue: '0', description: 'Index:', isRequired: false, name: Index, validator: NonNegativeNumber} 62 | - {defaultValue: '', description: 'Name:', isRequired: false, name: Name} 63 | - {defaultValue: '', description: 'Options:', isRequired: false, name: Options} 64 | - {description: 'Type:', isRequired: false, name: Type, provider: http} 65 | description: 'Monitor: Monitor Table' 66 | name: monitor__Monitors 67 | vars: 68 | - {description: 'Extensions: Field 1', displayName: Field1, isRequired: false, name: extensions__Field1, 69 | provider: ''} 70 | - {description: 'Extensions: Field 2', displayName: Field2, isRequired: false, name: extensions__Field2, 71 | provider: ''} 72 | - {description: 'Extensions: Field 3', displayName: Field3, isRequired: false, name: extensions__Field3, 73 | provider: ''} 74 | - choices: 75 | - {description: auto, value: auto} 76 | - {description: base, value: base} 77 | - {description: base+ip_blacklist_block, value: base+ip_blacklist_block} 78 | - {description: base+ip_blacklist_log, value: base+ip_blacklist_log} 79 | - {description: disabled, value: disabled} 80 | description: 'Security: Firewall: Configure L4 Firewall Policy' 81 | displayName: easyL4Firewall 82 | isRequired: false 83 | name: feature__easyL4Firewall 84 | provider: auto 85 | - choices: 86 | - {description: auto, value: auto} 87 | - {description: enabled, value: enabled} 88 | - {description: disabled, value: disabled} 89 | description: 'HTTP: Insert X-Forwarded-For Header' 90 | displayName: insertXForwardedFor 91 | isRequired: false 92 | name: feature__insertXForwardedFor 93 | provider: auto 94 | - choices: 95 | - {description: auto, value: auto} 96 | - {description: enabled, value: enabled} 97 | - {description: disabled, value: disabled} 98 | description: 'HTTP: Security: Create HTTP(80)->HTTPS(443) Redirect' 99 | displayName: redirectToHTTPS 100 | isRequired: false 101 | name: feature__redirectToHTTPS 102 | provider: disabled 103 | - choices: 104 | - {description: disabled, value: disabled} 105 | - {description: enabled, value: enabled} 106 | - {description: enabled-preload, value: enabled-preload} 107 | - {description: enabled-subdomain, value: enabled-subdomain} 108 | - {description: enabled-preload-subdomain, value: enabled-preload-subdomain} 109 | description: 'HTTP: Security: Enable HTTP Strict Transport Security (only valid 110 | if ClientSSL is configured)' 111 | displayName: securityEnableHSTS 112 | isRequired: false 113 | name: feature__securityEnableHSTS 114 | provider: disabled 115 | - choices: 116 | - {description: compatible, value: compatible} 117 | - {description: medium, value: medium} 118 | - {description: high, value: high} 119 | - {description: tls_1.2, value: tls_1.2} 120 | - {description: tls_1.1+1.2, value: tls_1.1+1.2} 121 | - {description: disabled, value: disabled} 122 | description: 'TLS/SSL: Easy Cipher String (overrides VS section setting)' 123 | displayName: sslEasyCipher 124 | isRequired: false 125 | name: feature__sslEasyCipher 126 | provider: disabled 127 | - choices: 128 | - {description: auto, value: auto} 129 | - {description: enabled, value: enabled} 130 | - {description: disabled, value: disabled} 131 | description: 'HTTP: Stats Reporting' 132 | displayName: statsHTTP 133 | isRequired: false 134 | name: feature__statsHTTP 135 | provider: auto 136 | - choices: 137 | - {description: auto, value: auto} 138 | - {description: enabled, value: enabled} 139 | - {description: disabled, value: disabled} 140 | description: 'TLS/SSL: Stats Reporting' 141 | displayName: statsTLS 142 | isRequired: false 143 | name: feature__statsTLS 144 | provider: disabled 145 | - choices: 146 | - {description: preserve-bypass, value: preserve-bypass} 147 | - {description: preserve-block, value: preserve-block} 148 | - {description: redeploy-bypass, value: redeploy-bypass} 149 | - {description: redeploy-block, value: redeploy-block} 150 | description: 'iApp: APM: Deployment Mode' 151 | displayName: apmDeployMode 152 | isRequired: false 153 | name: iapp__apmDeployMode 154 | provider: preserve-bypass 155 | - choices: 156 | - {description: enabled, value: enabled} 157 | - {description: disabled, value: disabled} 158 | description: 'iApp: Statistics Handler Creation' 159 | displayName: appStats 160 | isRequired: false 161 | name: iapp__appStats 162 | provider: enabled 163 | - choices: 164 | - {description: preserve-bypass, value: preserve-bypass} 165 | - {description: preserve-block, value: preserve-block} 166 | - {description: redeploy-bypass, value: redeploy-bypass} 167 | - {description: redeploy-block, value: redeploy-block} 168 | description: 'iApp: ASM: Deployment Mode' 169 | displayName: asmDeployMode 170 | isRequired: false 171 | name: iapp__asmDeployMode 172 | provider: preserve-bypass 173 | - {description: 'iApp: Log Level', displayName: logLevel, isRequired: false, name: iapp__logLevel, 174 | provider: '7'} 175 | - {description: 'iApp: Mode', displayName: mode, isRequired: false, name: iapp__mode, 176 | provider: auto} 177 | - {description: 'iApp: Route Domain', displayName: routeDomain, isRequired: false, 178 | name: iapp__routeDomain, provider: auto} 179 | - choices: 180 | - {description: enabled, value: enabled} 181 | - {description: disabled, value: disabled} 182 | description: 'iApp: Strict Updates' 183 | displayName: strictUpdates 184 | isRequired: false 185 | name: iapp__strictUpdates 186 | provider: enabled 187 | - {description: 'L7 Policy: Default ASM Policy', displayName: defaultASM, isRequired: false, 188 | name: l7policy__defaultASM, provider: bypass} 189 | - {description: 'L7 Policy: Default L7 DoS Policy', displayName: defaultL7DOS, isRequired: false, 190 | name: l7policy__defaultL7DOS, provider: bypass} 191 | - {description: 'L7 Policy: Match Strategy', displayName: strategy, isRequired: false, 192 | name: l7policy__strategy, provider: /Common/first-match} 193 | - {description: 'Virtual Server: Default Pool Index', displayName: DefaultPoolIndex, 194 | isRequired: false, name: pool__DefaultPoolIndex, provider: '0', validator: NonNegativeNumber} 195 | - {description: 'Pool: Member Default Port', displayName: MemberDefaultPort, isRequired: false, 196 | name: pool__MemberDefaultPort, provider: '80'} 197 | - {description: 'Virtual Server: Address', displayName: addr, isRequired: true, 198 | name: pool__addr, provider: 10.1.10.100, providerType: NODE, serverTier: pcfdev-app, 199 | validator: IpAddress} 200 | - {description: 'Virtual Server: Mask', displayName: mask, isRequired: true, name: pool__mask, 201 | provider: 255.255.255.255, validator: IpAddress} 202 | - {description: 'Virtual Server: Port', displayName: port, isRequired: true, name: pool__port, 203 | provider: '80', providerType: PORT, serverTier: pcfdev-app, validator: PortNumber} 204 | - {description: 'Virtual Server: Advanced Options', displayName: AdvOptions, isRequired: false, 205 | name: vs__AdvOptions, provider: ''} 206 | - {description: 'Virtual Server: Advanced Policies', displayName: AdvPolicies, isRequired: false, 207 | name: vs__AdvPolicies, provider: ''} 208 | - {description: 'Virtual Server: Advanced Profiles', displayName: AdvProfiles, isRequired: false, 209 | name: vs__AdvProfiles, provider: ''} 210 | - {description: 'Virtual Server: Bundled Items', displayName: BundledItems, isRequired: false, 211 | name: vs__BundledItems, provider: ''} 212 | - {description: 'Virtual Server: Virtual Server Connection Limit (0=unlimited)', 213 | displayName: ConnectionLimit, isRequired: false, name: vs__ConnectionLimit, provider: '0'} 214 | - {description: 'Virtual Server: Description', displayName: Description, isRequired: false, 215 | name: vs__Description, provider: ''} 216 | - {description: 'Virtual Server: IP Protocol', displayName: IpProtocol, isRequired: false, 217 | name: vs__IpProtocol, provider: tcp} 218 | - {description: 'Virtual Server: iRules (to specify multiple iRules seperate with 219 | a comma ex: irule1,irule2,irule3)', displayName: Irules, isRequired: false, 220 | name: vs__Irules, provider: ''} 221 | - {description: 'Virtual Server: Name', displayName: Name, isRequired: false, name: vs__Name, 222 | provider: pcfdev_app} 223 | - choices: 224 | - {description: enabled, value: enabled} 225 | - {description: disabled, value: disabled} 226 | description: 'Virtual Server: Connection Mirroring' 227 | displayName: OptionConnectionMirroring 228 | isRequired: false 229 | name: vs__OptionConnectionMirroring 230 | provider: disabled 231 | - choices: 232 | - {description: preserve, value: preserve} 233 | - {description: preserve-strict, value: preserve-strict} 234 | - {description: change, value: change} 235 | description: 'Virtual Server: Source Port Behavior' 236 | displayName: OptionSourcePort 237 | isRequired: false 238 | name: vs__OptionSourcePort 239 | provider: preserve 240 | - {description: 'Virtual Server: Access Profile', displayName: ProfileAccess, isRequired: false, 241 | name: vs__ProfileAccess, provider: ''} 242 | - {description: 'Virtual Server: Analytics Profile', displayName: ProfileAnalytics, 243 | isRequired: false, name: vs__ProfileAnalytics, provider: ''} 244 | - {description: 'Virtual Server: Client-side L4 Protocol Profile', displayName: ProfileClientProtocol, 245 | isRequired: false, name: vs__ProfileClientProtocol, provider: /Common/tcp-wan-optimized} 246 | - {description: 'Virtual Server: Client SSL Profile', displayName: ProfileClientSSL, 247 | isRequired: false, name: vs__ProfileClientSSL, provider: ''} 248 | - {description: 'Virtual Server: Client SSL Advanced Options', displayName: ProfileClientSSLAdvOptions, 249 | isRequired: false, name: vs__ProfileClientSSLAdvOptions, provider: ''} 250 | - {description: 'Virtual Server: Client SSL Certificate', displayName: ProfileClientSSLCert, 251 | isRequired: false, name: vs__ProfileClientSSLCert, provider: ''} 252 | - {description: 'Virtual Server: Client SSL Certificate Chain', displayName: ProfileClientSSLChain, 253 | isRequired: false, name: vs__ProfileClientSSLChain, provider: ''} 254 | - {description: 'Virtual Server: Client SSL Cipher String', displayName: ProfileClientSSLCipherString, 255 | isRequired: false, name: vs__ProfileClientSSLCipherString, provider: ''} 256 | - {description: 'Virtual Server: Client SSL Key', displayName: ProfileClientSSLKey, 257 | isRequired: false, name: vs__ProfileClientSSLKey, provider: ''} 258 | - {description: 'Virtual Server: Compression Profile', displayName: ProfileCompression, 259 | isRequired: false, name: vs__ProfileCompression, provider: /Common/httpcompression} 260 | - {description: 'Virtual Server: Connectivity Profile', displayName: ProfileConnectivity, 261 | isRequired: false, name: vs__ProfileConnectivity, provider: ''} 262 | - {description: 'Virtual Server: Default Persistence Profile', displayName: ProfileDefaultPersist, 263 | isRequired: false, name: vs__ProfileDefaultPersist, provider: /Common/cookie} 264 | - {description: 'Virtual Server: Fallback Persistence Profile', displayName: ProfileFallbackPersist, 265 | isRequired: false, name: vs__ProfileFallbackPersist, provider: /Common/source_addr} 266 | - {description: 'Virtual Server: HTTP Profile', displayName: ProfileHTTP, isRequired: false, 267 | name: vs__ProfileHTTP, provider: /Common/http} 268 | - {description: 'Virtual Server: OneConnect Profile', displayName: ProfileOneConnect, 269 | isRequired: false, name: vs__ProfileOneConnect, provider: /Common/oneconnect} 270 | - {description: 'Virtual Server: Per-Request Profile', displayName: ProfilePerRequest, 271 | isRequired: false, name: vs__ProfilePerRequest, provider: ''} 272 | - {description: 'Virtual Server: Request Logging Profile', displayName: ProfileRequestLogging, 273 | isRequired: false, name: vs__ProfileRequestLogging, provider: ''} 274 | - {description: 'Virtual Server: Security: DoS Profile', displayName: ProfileSecurityDoS, 275 | isRequired: false, name: vs__ProfileSecurityDoS, provider: ''} 276 | - {description: 'Virtual Server: IP Blacklist Profile', displayName: ProfileSecurityIPBlacklist, 277 | isRequired: false, name: vs__ProfileSecurityIPBlacklist, provider: none} 278 | - {description: 'Virtual Server: Security Logging Profiles', displayName: ProfileSecurityLogProfiles, 279 | isRequired: false, name: vs__ProfileSecurityLogProfiles, provider: ''} 280 | - {description: 'Virtual Server: Server-side L4 Protocol Profile', displayName: ProfileServerProtocol, 281 | isRequired: false, name: vs__ProfileServerProtocol, provider: /Common/tcp-lan-optimized} 282 | - {description: 'Virtual Server: Server SSL Profile', displayName: ProfileServerSSL, 283 | isRequired: false, name: vs__ProfileServerSSL, provider: ''} 284 | - choices: 285 | - {description: disabled, value: disabled} 286 | - {description: all_vs, value: all_vs} 287 | - {description: any_vs, value: any_vs} 288 | - {description: always, value: always} 289 | description: 'Virtual Server: Route Advertisement' 290 | displayName: RouteAdv 291 | isRequired: false 292 | name: vs__RouteAdv 293 | provider: disabled 294 | - {description: 'Virtual Server: SNAT Configuration (enter SNAT pool name, ''automap'' 295 | or leave blank to disable SNAT)', displayName: SNATConfig, isRequired: false, 296 | name: vs__SNATConfig, provider: automap} 297 | - {description: 'Virtual Server: Source Address', displayName: SourceAddress, isRequired: false, 298 | name: vs__SourceAddress, provider: 0.0.0.0/0} 299 | - {description: 'Virtual Address: Advanced Options', displayName: VirtualAddrAdvOptions, 300 | isRequired: false, name: vs__VirtualAddrAdvOptions, provider: ''} 301 | - {description: Enable health and performance monitoring., displayName: app_stats, 302 | isRequired: false, name: app_stats, provider: enabled} 303 | parentReference: {link: 'https://localhost/mgmt/cm/cloud/templates/iapp/appsvcs_integration_v2.0_001'} 304 | properties: 305 | - {id: cloudConnectorReference, isRequired: true} 306 | selfLink: https://localhost/mgmt/cm/cloud/provider/templates/iapp/dora_template_v2.0 307 | templateName: dora_template_v2.0 308 | tenantTemplateReference: {link: 'https://localhost/mgmt/cm/cloud/tenant/templates/iapp/dora_template_v2.0'} 309 | -------------------------------------------------------------------------------- /pcf-example/iwf-helper.py: -------------------------------------------------------------------------------- 1 | from icontrol.session import iControlRESTSession 2 | from pprint import pprint 3 | import yaml 4 | import logging 5 | import argparse 6 | 7 | #logger = logging.getLogger() 8 | #logger = logging.getLogger('requests') 9 | #logger.setLevel(logging.DEBUG) 10 | 11 | parser = argparse.ArgumentParser(description='Script to create a pool on a BIG-IP device') 12 | parser.add_argument("host", help="The IP/Hostname of the BIG-IP device",default='10.1.1.246') 13 | parser.add_argument("-a","--action",help="create/read/update/delete") 14 | parser.add_argument("--template",help="template") 15 | parser.add_argument("--tenant",help="tenant") 16 | parser.add_argument("--iapp",help="iapp") 17 | parser.add_argument("-f","--file",help="template file") 18 | args = parser.parse_args() 19 | 20 | icr_admin = iControlRESTSession('admin','admin') 21 | icr_tenant = iControlRESTSession('pcfdev','pcfdev') 22 | 23 | iwf = args.host 24 | 25 | #resp = icr_admin.get('https://%s/mgmt/cm/cloud/tenants' %(iwf)) 26 | #print pprint(resp.json()) 27 | #resp = icr_admin.get('https://%s/mgmt/cm/cloud/connectors/local' %(iwf)) 28 | #print pprint(resp.json()) 29 | if args.action == 'list_templates': 30 | resp = icr_admin.get('https://%s/mgmt/cm/cloud/provider/templates/iapp' %(iwf)) 31 | data = resp.json() 32 | for item in data['items']: 33 | print item['templateName'] 34 | if args.action == 'export_template': 35 | resp = icr_admin.get('https://%s/mgmt/cm/cloud/provider/templates/iapp/%s' %(iwf,args.template)) 36 | payload = resp.json() 37 | yaml.safe_dump(payload,open('%s.yaml' %(args.file),'w')) 38 | elif args.action == 'import_template': 39 | payload = yaml.load(open(args.file)) 40 | resp = icr_admin.post('https://%s/mgmt/cm/cloud/provider/templates/iapp' %(iwf),json=payload) 41 | data = resp.json() 42 | print pprint(data) 43 | elif args.action == 'list_iapps': 44 | resp = icr_tenant.get('https://%s/mgmt/cm/cloud/tenants/%s/services/iapp' %(iwf, args.tenant)) 45 | data = resp.json() 46 | for item in data['items']: 47 | print item['name'] 48 | 49 | elif args.action == 'export_iapp': 50 | resp = icr_tenant.get('https://%s/mgmt/cm/cloud/tenants/%s/services/iapp/%s' %(iwf,args.tenant,args.iapp)) 51 | payload= resp.json() 52 | yaml.safe_dump(payload,open(args.file,'w')) 53 | elif args.action == 'import_iapp': 54 | payload = yaml.load(open(args.file)) 55 | resp = icr_tenant.post('https://%s/mgmt/cm/cloud/tenants/%s/services/iapp' %(iwf,args.tenant),json=payload) 56 | data = resp.json() 57 | print pprint(data) 58 | elif args.action == 'update_iapp': 59 | payload = yaml.load(open(args.file)) 60 | resp = icr_tenant.put('https://%s/mgmt/cm/cloud/tenants/%s/services/iapp/%s' %(iwf,args.tenant,args.iapp),json=payload) 61 | data = resp.json() 62 | print pprint(data) 63 | elif args.action == 'delete_iapp': 64 | resp = icr_tenant.delete('https://%s/mgmt/cm/cloud/tenants/%s/services/iapp/%s' %(iwf, args.tenant, args.iapp)) 65 | print resp 66 | data = resp.json() 67 | print pprint(data) 68 | 69 | -------------------------------------------------------------------------------- /pcf-example/pcf-phase1.py: -------------------------------------------------------------------------------- 1 | from f5.bigip import ManagementRoot 2 | from f5.bigip.contexts import TransactionContextManager 3 | from cloudfoundry_client import CloudFoundryClient 4 | import pprint 5 | import argparse 6 | import csv 7 | import logging 8 | 9 | #logger = logging.getLogger() 10 | #logger = logging.getLogger('requests') 11 | #logger.setLevel(logging.DEBUG) 12 | 13 | pp = pprint.PrettyPrinter(indent=3) 14 | 15 | 16 | class CF2BIGIP(object): 17 | def __init__(self, host, username, password): 18 | self.mgmt = ManagementRoot(host, username, password) 19 | self.tx = self.mgmt.tm.transactions.transaction 20 | def create_app(self,app_name, pool_members, policy_name, partition='Common'): 21 | pool_name = "%s_pool" %(app_name) 22 | monitor_name = "%s_monitor" %(app_name) 23 | send_str = "GET /env/VCAP_APPLICATION HTTP/1.1\\r\\nhost: %s\\r\\nconnection:close\\r\\n\\r\\n" %(app_name) 24 | recv_str = app_name 25 | 26 | monitor_path = "/%s/%s" %(partition, monitor_name) 27 | 28 | with TransactionContextManager(self.tx) as api: 29 | api._meta_data['icr_session'].session.headers['X-F5-REST-Coordination-Id'] = api._meta_data['icr_session'].session.headers['X-F5-REST-Coordination-Id'].__str__() 30 | # sys.exit(0) 31 | monitor = api.tm.ltm.monitor.https.http.create(name=monitor_name, partition=partition, interval=10, timeout=31, send=send_str, recv=recv_str) 32 | pool_path = "/%s/%s" % (partition, pool_name) 33 | 34 | pool = api.tm.ltm.pools.pool.create(partition=partition, name=pool_name, minActiveMembers=1, monitor=monitor_name) 35 | print "Created pool %s" % pool_path 36 | 37 | member_list = pool_members.split(',') 38 | member_list.reverse() 39 | priority_group = 0 40 | for member in member_list: 41 | pool._meta_data['uri'] = pool._meta_data['uri'].split("/transaction/")[0] + "/ltm/pool/~%s~%s/" %(partition,pool_name) 42 | pool_member = pool.members_s.members.create(partition=partition, name=member, priorityGroup=priority_group) 43 | priority_group += 10 44 | print " Added member %s" % member 45 | 46 | policy = api.tm.ltm.policys.policy.load(name = policy_name, partition = partition) 47 | 48 | rules = policy.rules_s.get_collection() 49 | 50 | my_rule = policy.rules_s.rules.create(name=app_name) 51 | 52 | payload = {u'caseInsensitive': True, 53 | u'equals': True, 54 | u'external': True, 55 | u'fullPath': u'0', 56 | u'host': True, 57 | u'httpHost': True, 58 | u'index': 0, 59 | u'name': u'0', 60 | u'present': True, 61 | u'remote': True, 62 | u'request': True, 63 | u'values': [app_name]} 64 | 65 | my_rule._meta_data['uri'] = policy._meta_data['uri'] + 'rules/' + app_name + '/' 66 | my_rule.conditions_s.conditions.create(**payload) 67 | payload = { 68 | "vlanId": 0, 69 | "forward": True, 70 | "code": 0, 71 | "fullPath": "0", 72 | "name": "0", 73 | "pool": pool_name, 74 | "request": True, 75 | "select": True, 76 | "status": 0 77 | } 78 | my_rule.actions_s.actions.create(**payload) 79 | print "Created policy rule %s" %(app_name) 80 | 81 | def delete_app(self, app_name, policy_name, partition='Common'): 82 | pool_name = "%s_pool" %(app_name) 83 | monitor_name = "%s_monitor" %(app_name) 84 | monitor_path = "/%s/%s" %(partition, monitor_name) 85 | 86 | with TransactionContextManager(self.tx) as api: 87 | api._meta_data['icr_session'].session.headers['X-F5-REST-Coordination-Id'] = api._meta_data['icr_session'].session.headers['X-F5-REST-Coordination-Id'].__str__() 88 | policy = api.tm.ltm.policys.policy.load(name = policy_name, partition = partition) 89 | my_rule = policy.rules_s.rules.load(name=app_name) 90 | my_rule.delete() 91 | print "Deleted policy rule %s" %(app_name) 92 | pool_path = "/%s/%s" % (partition, pool_name) 93 | 94 | pool = api.tm.ltm.pools.pool.load(partition=partition, name=pool_name) 95 | pool.delete() 96 | print "Deleted pool %s" % pool_path 97 | 98 | monitor = api.tm.ltm.monitor.https.http.load(name=monitor_name, partition=partition) 99 | monitor.delete() 100 | print "Deleted monitor %s" %(monitor_path) 101 | 102 | 103 | if __name__ == "__main__": 104 | import os 105 | password = os.getenv('PASSWORD') 106 | 107 | parser = argparse.ArgumentParser(description='Script to create a pool on a BIG-IP device') 108 | parser.add_argument("host", help="The IP/Hostname of the BIG-IP device") 109 | parser.add_argument("app_name", nargs='?', help="The name of the pool") 110 | parser.add_argument("pool_members", nargs='?', help="A comma seperated string in the format :[,:]") 111 | parser.add_argument("value", nargs='?', help='optional value for update') 112 | parser.add_argument("-P", "--partition", help="The partition name", default="Common") 113 | parser.add_argument("-u", "--username", help="The BIG-IP username", default="admin") 114 | parser.add_argument("-p", "--password", help="The BIG-IP password", default="admin") 115 | parser.add_argument("-f","--file",help="CSV file input") 116 | parser.add_argument("-a","--action",help="create/read/update/delete") 117 | parser.add_argument("--policy_name",default="app_policy") 118 | 119 | args = parser.parse_args() 120 | if password: 121 | args.password = password 122 | sp = CF2BIGIP(args.host, args.username, args.password) 123 | pool_name = "%s_pool" %(args.app_name) 124 | monitor_name = "%s_monitor" %(args.app_name) 125 | 126 | if args.action == "create": 127 | 128 | send_str = "GET /env/VCAP_APPLICATION HTTP/1.1\\r\\nhost: %s\\r\\nconnection:close\\r\\n\\r\\n" %(args.app_name) 129 | recv_str = args.app_name 130 | 131 | sp.create_app(args.app_name, args.pool_members, args.policy_name) 132 | 133 | elif args.action == "delete": 134 | 135 | sp.delete_app(args.app_name, args.policy_name) 136 | 137 | elif args.file: 138 | print args.file 139 | for row in csv.reader(open(args.file)): 140 | app_name = row[1] 141 | pool_name = "%s_pool" %(app_name) 142 | monitor_name = "%s_monitor" %(app_name) 143 | if row[0] == "create": 144 | send_str = "GET /env/VCAP_APPLICATION HTTP/1.1\\r\\nhost: %s\\r\\nconnection:close\\r\\n\\r\\n" %(args.app_name) 145 | recv_str = app_name 146 | try: 147 | sp.create_app(app_name, row[2], args.policy_name) 148 | except Exception, e: 149 | print "error creating",app_name,e 150 | # really lame/dangerous rollback 151 | # row[0] = "delete" 152 | 153 | if row[0] == "delete": 154 | sp.delete_app(row[1], args.policy_name) 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /pcf-example/pcf-phase2.py: -------------------------------------------------------------------------------- 1 | from f5.bigip import ManagementRoot 2 | from f5.bigip.contexts import TransactionContextManager 3 | from cloudfoundry_client import CloudFoundryClient 4 | import pprint 5 | import argparse 6 | import csv 7 | import logging 8 | 9 | #logger = logging.getLogger() 10 | #logger = logging.getLogger('requests') 11 | #logger.setLevel(logging.DEBUG) 12 | 13 | pp = pprint.PrettyPrinter(indent=3) 14 | 15 | 16 | class CF2BIGIP(object): 17 | def __init__(self, host, username, password): 18 | self.mgmt = ManagementRoot(host, username, password) 19 | self.tx = self.mgmt.tm.transactions.transaction 20 | def create_app(self,app_name, pool_members, policy_name, partition='Common'): 21 | pool_name = "%s_pool" %(app_name) 22 | monitor_name = "%s_monitor" %(app_name) 23 | send_str = "GET /env/VCAP_APPLICATION HTTP/1.1\\r\\nhost: %s\\r\\nconnection:close\\r\\n\\r\\n" %(app_name) 24 | recv_str = app_name 25 | 26 | monitor_path = "/%s/%s" %(partition, monitor_name) 27 | 28 | with TransactionContextManager(self.tx) as api: 29 | api._meta_data['icr_session'].session.headers['X-F5-REST-Coordination-Id'] = api._meta_data['icr_session'].session.headers['X-F5-REST-Coordination-Id'].__str__() 30 | # sys.exit(0) 31 | monitor = api.tm.ltm.monitor.https.http.create(name=monitor_name, partition=partition, interval=10, timeout=31, send=send_str, recv=recv_str) 32 | pool_path = "/%s/%s" % (partition, pool_name) 33 | 34 | pool = api.tm.ltm.pools.pool.create(partition=partition, name=pool_name, minActiveMembers=1, monitor=monitor_name) 35 | print "Created pool %s" % pool_path 36 | 37 | member_list = pool_members.split(',') 38 | member_list.reverse() 39 | priority_group = 0 40 | for member in member_list: 41 | pool._meta_data['uri'] = pool._meta_data['uri'].split("/transaction/")[0] + "/ltm/pool/~%s~%s/" %(partition,pool_name) 42 | pool_member = pool.members_s.members.create(partition=partition, name=member, priorityGroup=priority_group) 43 | priority_group += 10 44 | print " Added member %s" % member 45 | 46 | policy = api.tm.ltm.policys.policy.load(name = policy_name, partition = partition) 47 | 48 | rules = policy.rules_s.get_collection() 49 | 50 | my_rule = policy.rules_s.rules.create(name=app_name) 51 | 52 | payload = {u'caseInsensitive': True, 53 | u'equals': True, 54 | u'external': True, 55 | u'fullPath': u'0', 56 | u'host': True, 57 | u'httpHost': True, 58 | u'index': 0, 59 | u'name': u'0', 60 | u'present': True, 61 | u'remote': True, 62 | u'request': True, 63 | u'values': [app_name]} 64 | 65 | my_rule._meta_data['uri'] = policy._meta_data['uri'] + 'rules/' + app_name + '/' 66 | my_rule.conditions_s.conditions.create(**payload) 67 | payload = { 68 | "vlanId": 0, 69 | "forward": True, 70 | "code": 0, 71 | "fullPath": "0", 72 | "name": "0", 73 | "pool": pool_name, 74 | "request": True, 75 | "select": True, 76 | "status": 0 77 | } 78 | my_rule.actions_s.actions.create(**payload) 79 | print "Created policy rule %s" %(app_name) 80 | 81 | def delete_app(self, app_name, policy_name, partition='Common'): 82 | pool_name = "%s_pool" %(app_name) 83 | monitor_name = "%s_monitor" %(app_name) 84 | monitor_path = "/%s/%s" %(partition, monitor_name) 85 | 86 | with TransactionContextManager(self.tx) as api: 87 | api._meta_data['icr_session'].session.headers['X-F5-REST-Coordination-Id'] = api._meta_data['icr_session'].session.headers['X-F5-REST-Coordination-Id'].__str__() 88 | policy = api.tm.ltm.policys.policy.load(name = policy_name, partition = partition) 89 | my_rule = policy.rules_s.rules.load(name=app_name) 90 | my_rule.delete() 91 | print "Deleted policy rule %s" %(app_name) 92 | pool_path = "/%s/%s" % (partition, pool_name) 93 | 94 | pool = api.tm.ltm.pools.pool.load(partition=partition, name=pool_name) 95 | pool.delete() 96 | print "Deleted pool %s" % pool_path 97 | 98 | monitor = api.tm.ltm.monitor.https.http.load(name=monitor_name, partition=partition) 99 | monitor.delete() 100 | print "Deleted monitor %s" %(monitor_path) 101 | 102 | 103 | if __name__ == "__main__": 104 | import os 105 | password = os.getenv('PASSWORD') 106 | 107 | parser = argparse.ArgumentParser(description='Script to create a pool on a BIG-IP device') 108 | parser.add_argument("host", help="The IP/Hostname of the BIG-IP device") 109 | parser.add_argument("pool_members", nargs='?', help="A comma seperated string in the format :[,:]") 110 | parser.add_argument("value", nargs='?', help='optional value for update') 111 | parser.add_argument("-P", "--partition", help="The partition name", default="Common") 112 | parser.add_argument("-u", "--username", help="The BIG-IP username", default="admin") 113 | parser.add_argument("-p", "--password", help="The BIG-IP password", default="admin") 114 | parser.add_argument("-f","--file",help="CSV file input") 115 | parser.add_argument("-a","--action",help="create/read/update/delete") 116 | parser.add_argument("--policy_name",default="app_policy") 117 | parser.add_argument("--api",default='https://api.local.pcfdev.io') 118 | parser.add_argument("--cf_username",default='admin') 119 | parser.add_argument("--cf_password",default='admin') 120 | 121 | args = parser.parse_args() 122 | if password: 123 | args.password = password 124 | sp = CF2BIGIP(args.host, args.username, args.password) 125 | 126 | 127 | skip_verification = True 128 | client = CloudFoundryClient(args.api, skip_verification=skip_verification) 129 | client.init_with_credentials(args.cf_username,args.cf_password) 130 | hosts = [(r.entity.host, r.entity.domain_guid) for r in client.route.list()] 131 | 132 | resp = client.credentials_manager._session.get("%s%s" %(args.api, "/v2/shared_domains")) 133 | jsdata = resp.json() 134 | domains = [(d['metadata']['guid'],d['entity']['name']) for d in jsdata['resources']] 135 | domain_map = dict(domains) 136 | 137 | app_vars = dict([(c.entity.name, c.entity.environment_json) for c in client.application.list()]) 138 | 139 | # apps that have a environment variable 'F5' 140 | # could also use this to extract other metadata like 141 | # F5_MONITOR_SEND = '/test HTTP/1.1\r\n...' 142 | 143 | app_names = ["%s.%s" %(h[0],domain_map.get(h[1])) for h in hosts if app_vars.get(h[0],{}).get('F5')] 144 | 145 | # all apps 146 | # app_names = ["%s.%s" %(h[0],domain_map.get(h[1])) for h in hosts] 147 | 148 | if args.action == "create": 149 | 150 | for app_name in app_names: 151 | sp.create_app(app_name, args.pool_members, args.policy_name) 152 | 153 | elif args.action == "delete": 154 | 155 | for app_name in app_names: 156 | sp.delete_app(app_name, args.policy_name) 157 | 158 | -------------------------------------------------------------------------------- /pcf-example/pcf-phase3.py: -------------------------------------------------------------------------------- 1 | from icontrol.session import iControlRESTSession 2 | from cloudfoundry_client import CloudFoundryClient 3 | import pprint 4 | import argparse 5 | import yaml 6 | import logging 7 | 8 | logger = logging.getLogger() 9 | logger = logging.getLogger('requests') 10 | logger.setLevel(logging.DEBUG) 11 | 12 | pp = pprint.PrettyPrinter(indent=3) 13 | 14 | class Pcf2Bigip(object): 15 | def __init__(self, host, username, password, admin_username, admin_password): 16 | self.iwf = iControlRESTSession(username,password) 17 | self.iwf_admin = iControlRESTSession(admin_username,admin_password) 18 | self.host = host 19 | def create_iapp(self, tenant, iapp_name, service_name, cloud_connector, app_names, pool_members): 20 | payload = { 'kind': 'cm:cloud:tenants:tenantserviceinstance', 21 | 'tenantReference': { 'link': 'https://localhost/mgmt/cm/cloud/tenants/%s' %(tenant)}, 22 | 'tenantTemplateReference': { 'link': 'https://localhost/mgmt/cm/cloud/tenant/templates/iapp/%s' %(service_name)}, 23 | 'vars': [], 24 | 'tables': [ { 'columns': ['Group', 'Parameter'], 25 | 'name': 'l7policy__rulesAction', 26 | 'rows': [] }, 27 | { 'columns': ['Group', 'Value'], 28 | 'name': 'l7policy__rulesMatch', 29 | 'rows': []}, 30 | { 'columns': ['Index', 'Name', 'Options'], 31 | 'name': 'monitor__Monitors', 32 | 'rows': []}, 33 | { 'columns': [ 'IPAddress', 34 | 'Index', 35 | 'PriorityGroup', 36 | 'State'], 37 | 'name': 'pool__Members', 38 | 'rows': []}, 39 | { 'columns': ['Index', 'Monitor', 'Name'], 40 | 'name': 'pool__Pools', 41 | 'rows': []}] } 42 | payload['name'] = iapp_name 43 | payload['properties'] = [{'id':'cloudConnectorReference', 'isRequired': False, 'value': cloud_connector}] 44 | 45 | payload = self._merge_payload(payload, service_name, app_names, pool_members) 46 | 47 | resp = self.iwf.post('https://%s/mgmt/cm/cloud/tenants/%s/services/iapp' %(self.host, tenant),json=payload) 48 | print "Created iApp %s" % iapp_name 49 | def update_iapp(self, tenant, iapp_name, service_name, app_names, pool_members): 50 | resp = self.iwf.get('https://%s/mgmt/cm/cloud/tenants/%s/services/iapp/%s' %(self.host, tenant, iapp_name)) 51 | payload = resp.json() 52 | payload = self._merge_payload(payload, service_name, app_names, pool_members) 53 | 54 | resp = self.iwf.put('https://%s/mgmt/cm/cloud/tenants/%s/services/iapp/%s' %(self.host, tenant, iapp_name),json=payload) 55 | print "Updated iApp %s" % iapp_name 56 | 57 | def update_iapp_service(self, tenant, iapp_name, service_name, app_names, pool_members): 58 | resp = self.iwf.get('https://%s/mgmt/cm/cloud/tenants/%s/services/iapp/%s' %(self.host, tenant, iapp_name)) 59 | payload = resp.json() 60 | payload = self._merge_payload(payload, service_name, app_names, pool_members) 61 | 62 | resp = self.iwf.put('https://%s/mgmt/cm/cloud/tenants/%s/services/iapp/%s' %(self.host, tenant, iapp_name),json=payload) 63 | print "Updated iApp %s Service to %s" % (iapp_name, service_name) 64 | def delete_iapp(self, iapp_name, tenant): 65 | resp = self.iwf.delete('https://%s/mgmt/cm/cloud/tenants/%s/services/iapp/%s' %(self.host, tenant, iapp_name)) 66 | print "Deleted iApp %s" % iapp_name 67 | def create_service(self, service_name, tenant, filename): 68 | payload = yaml.load(open(filename)) 69 | resp = self.iwf_admin.post('https://%s/mgmt/cm/cloud/provider/templates/iapp' %(self.host),json=payload) 70 | 71 | def delete_service(self, service_name, tenant): 72 | resp = self.iwf_admin.delete('https://%s/mgmt/cm/cloud/provider/templates/iapp/%s' %(self.host, service_name)) 73 | 74 | def _merge_payload(self,payload, service_name, app_names, pool_members): 75 | monitor_table = None 76 | member_table = None 77 | pool_table = None 78 | rule_table = None 79 | action_table = None 80 | 81 | merge_payload = {} 82 | 83 | if app_names: 84 | monitor_str = "interval=30;timeout=91;send=GET /env/VCAP_APPLICATION HTTP/1.1\\r\\nhost: %s\\r\\nconnection:close\\r\\n\\r\\n;recv=%s" 85 | monitor_table = [[str(x+1), '%s_monitor' %(app_names[x]), monitor_str %(app_names[x],app_names[x])] for x in range(num_apps)] 86 | monitor_table.insert(0,['0','_default_monitor','']) 87 | 88 | pool_table = [[str(x+1),str(x+1), '%s_pool' %(app_names[x])] for x in range(num_apps)] 89 | pool_table.insert(0,['0','0','_default_pool']) 90 | 91 | rule_table = [[str(x+1), app_names[x]] for x in range(num_apps)] 92 | action_table = [[str(x+1), '%s_pool' %(app_names[x])] for x in range(num_apps)] 93 | 94 | merge_payload = {'monitor__Monitors':monitor_table, 95 | 'pool__Pools':pool_table, 96 | 'l7policy__rulesMatch':rule_table, 97 | 'l7policy__rulesAction':action_table} 98 | 99 | if pool_members: 100 | member_list = pool_members.split(',') 101 | member_list.reverse() 102 | priority_group = 0 103 | 104 | member_table = [] 105 | for member in member_list: 106 | member_table.extend([[member, str(x+1),str(priority_group), 'enabled'] for x in range(num_apps)]) 107 | priority_group += 10 108 | member_table.insert(0,['192.168.11.11','0','0','enabled']) 109 | merge_payload['pool__Members'] = member_table 110 | 111 | if service_name: 112 | payload['tenantTemplateReference'] = {'link': 'https://localhost/mgmt/cm/cloud/tenant/templates/iapp/%s' %(service_name)} 113 | 114 | tables = {} 115 | for x in range(len(payload['tables'])): 116 | table = payload['tables'][x] 117 | tables[table['name']] = (x,table) 118 | table_value = merge_payload.get(table['name']) 119 | if table_value: 120 | payload['tables'][x]['rows'] = merge_payload[table['name']] 121 | return payload 122 | 123 | 124 | if __name__ == "__main__": 125 | import os 126 | password = os.getenv('PASSWORD') 127 | 128 | parser = argparse.ArgumentParser(description='Script to create a pool on a BIG-IP device') 129 | parser.add_argument("host", help="The IP/Hostname of the BIG-IP device") 130 | parser.add_argument("pool_members", nargs='?', help="A comma seperated string in the format ,") 131 | parser.add_argument("-t", "--tenant", help="The tenant name", default="pcfdev_tenant") 132 | parser.add_argument("-u", "--username", help="The iWorkflow username", default="pcfdev") 133 | parser.add_argument("-p", "--password", help="The iWorkflow password", default="pcfdev") 134 | parser.add_argument("--admin_username", help="The iWorkflow admin username", default="admin") 135 | parser.add_argument("--admin_password", help="The iWorkflow admin password", default="admin") 136 | 137 | parser.add_argument("-a","--action",help="create/delete/create_service/delete_service") 138 | parser.add_argument("--api",default='https://api.local.pcfdev.io') 139 | parser.add_argument("--cf_username",default='admin') 140 | parser.add_argument("--cf_password",default='admin') 141 | parser.add_argument("--iapp_name",default="dora_app_v1.0") 142 | parser.add_argument("--service_name",default="dora_template_v1.0") 143 | parser.add_argument("--cloud_connector",default="https://localhost/mgmt/cm/cloud/connectors/local/93e5ce56-37ad-47c4-99b0-514cc3de4872") 144 | 145 | 146 | args = parser.parse_args() 147 | 148 | if password: 149 | args.password = password 150 | 151 | pb = Pcf2Bigip(args.host, args.username, args.password, args.admin_username, args.admin_password) 152 | 153 | skip_verification = True 154 | client = CloudFoundryClient(args.api, skip_verification=skip_verification) 155 | client.init_with_credentials(args.cf_username,args.cf_password) 156 | hosts = [(r.entity.host, r.entity.domain_guid) for r in client.route.list()] 157 | 158 | resp = client.credentials_manager._session.get("%s%s" %(args.api, "/v2/shared_domains")) 159 | jsdata = resp.json() 160 | domains = [(d['metadata']['guid'],d['entity']['name']) for d in jsdata['resources']] 161 | domain_map = dict(domains) 162 | 163 | app_vars = dict([(c.entity.name, c.entity.environment_json) for c in client.application.list()]) 164 | 165 | # apps that have a environment variable 'F5' 166 | # could also use this to extract other metadata like 167 | # F5_MONITOR_SEND = '/test HTTP/1.1\r\n...' 168 | 169 | app_names = ["%s.%s" %(h[0],domain_map.get(h[1])) for h in hosts if app_vars.get(h[0],{}).get('F5')] 170 | 171 | # all apps 172 | # app_names = ["%s.%s" %(h[0],domain_map.get(h[1])) for h in hosts] 173 | 174 | num_apps = len(app_names) 175 | 176 | if args.action == "create_iapp": 177 | pb.create_iapp(args.tenant,args.iapp_name, args.service_name, args.cloud_connector, app_names, args.pool_members) 178 | 179 | elif args.action == "update_iapp": 180 | pb.update_iapp(args.tenant, args.iapp_name, None, app_names, args.pool_members) 181 | 182 | elif args.action == "update_iapp_service": 183 | pb.update_iapp_service(args.tenant, args.iapp_name, args.service_name, None, None) 184 | 185 | elif args.action == "delete_iapp": 186 | pb.delete_iapp(args.iapp_name, args.tenant) 187 | 188 | elif args.action == "create_service": 189 | pb.create_service(args.service_name, args.tenant, args.file) 190 | 191 | elif args.action == "delete_service": 192 | pb.delete_service(args.service_name, args.tenant) 193 | 194 | --------------------------------------------------------------------------------