├── 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 |
--------------------------------------------------------------------------------