From e28b3a3d416e7d51eb3b7fb7a891529d1cb33d6a Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Tue, 25 Apr 2023 11:14:04 +0100 Subject: [PATCH 01/11] Added some additional reference handling --- python/avi/sdk/samples/clone_vs.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/python/avi/sdk/samples/clone_vs.py b/python/avi/sdk/samples/clone_vs.py index 3d3f8ca1fe..b9706f73c9 100644 --- a/python/avi/sdk/samples/clone_vs.py +++ b/python/avi/sdk/samples/clone_vs.py @@ -30,7 +30,7 @@ urllib3.disable_warnings() -AVICLONE_VERSION = [2, 0, 2] +AVICLONE_VERSION = [2, 0, 3] # Try to obtain the terminal width to allow spprint() to wrap output neatly. # If unable to determine, assume terminal width is 70 characters @@ -67,10 +67,17 @@ class AviClone: 'pool-autoscalepolicy': 'autoscale_policy_ref'} VALID_DATASCRIPT_REF_OBJECTS = { 'ds-ipgroup': 'ipgroup_refs', - 'ds-stringgroup': 'string_group_refs'} + 'ds-stringgroup': 'string_group_refs', + 'ds-protocolparser': 'protocol_parser_refs', + 'ds-sslprofile': 'ssl_profile_refs', + 'ds-pkiprofile': 'pki_profile_refs', + 'ds-geodb': 'geo_db_ref', + 'ds-ipreputation': 'ip_reputation_db_ref'} VALID_POLICYSET_REF_OBJECTS = { 'policy-ipgroup': 'group_refs', - 'policy-stringgroup': 'string_group_refs'} + 'policy-stringgroup': 'string_group_refs', + 'policy-geodb': 'geo_db_ref', + 'policy-ipreputation': 'ip_reputation_db_ref'} VALID_VS_REF_OBJECTS = { 'vs-appprofile': 'application_profile_ref', 'vs-networkprofile': 'network_profile_ref', @@ -102,7 +109,9 @@ class AviClone: VALID_WAFPOLICY_REF_OBJECTS = {'waf-profile': 'waf_profile_ref', 'waf-crs': 'waf_crs_ref', 'positive-security-model': - 'positive_security_model/group_refs'} + 'positive_security_model/group_refs', + 'waf-appsignatures': + 'application_signatures/provider_ref'} VALID_WAFPOLICYPSMGROUP_REF_OBJECTS = {'wafpsm-stringgroup': 'match_value_string_group_ref'} VALID_SSLCERT_REF_OBJECTS = { From 99fc49cd0d038eab577f8fbdf37cc8f0809b5977 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Tue, 25 Apr 2023 11:14:55 +0100 Subject: [PATCH 02/11] Update README --- python/avi/sdk/samples/clone_vs.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/avi/sdk/samples/clone_vs.md b/python/avi/sdk/samples/clone_vs.md index 9f76d42525..f1372a236b 100644 --- a/python/avi/sdk/samples/clone_vs.md +++ b/python/avi/sdk/samples/clone_vs.md @@ -300,3 +300,7 @@ Changelog: 2.0.2: * Added support for flexibly handling specification of pool placement networks for cloned pools + +2.0.3: + +* Added some additional reference handling for less-common DataScript and WAF Profile configurations From ff340e23bf7e645433daa0dea9f55de6de808356 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Wed, 10 May 2023 14:05:07 +0100 Subject: [PATCH 03/11] Correct typo --- python/avi/sdk/samples/clone_vs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/avi/sdk/samples/clone_vs.md b/python/avi/sdk/samples/clone_vs.md index f1372a236b..06b8db4b09 100644 --- a/python/avi/sdk/samples/clone_vs.md +++ b/python/avi/sdk/samples/clone_vs.md @@ -212,7 +212,7 @@ If cloning a Virtual Service between Controllers or to a different tenant, the d A WAF Policy and its referenced PSM groups can be forced cloned using the -fc flag. This supports the scenarios where the cloned VS should have its own WAF Policy rather than sharing the same WAF policy (including the case where learning is enabled). -In this case, if learning is enabled in the source WAF Policy, it will remain enabled in the cloned WAF Policy resulting in indepdent learning for the cloned VS. +In this case, if learning is enabled in the source WAF Policy, it will remain enabled in the cloned WAF Policy resulting in independent learning for the cloned VS. The below example clones a VS and forces cloning of the WAF Policy and any PSM groups also. From 8d9753846f7422fb76dd272e04b6734b4dfacf35 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Fri, 19 May 2023 14:13:15 +0100 Subject: [PATCH 04/11] Some usage clarifications in the README --- python/avi/sdk/samples/clone_vs.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/python/avi/sdk/samples/clone_vs.md b/python/avi/sdk/samples/clone_vs.md index 06b8db4b09..55ffb15042 100644 --- a/python/avi/sdk/samples/clone_vs.md +++ b/python/avi/sdk/samples/clone_vs.md @@ -72,18 +72,20 @@ Specify the VIP as a subnet/mask. This must match an auto-allocation subnet in I If the source VIP was auto-allocated, the target can simply inherit the auto-allocation network: -> -v * +> -v auto (or -v *) + +(Note: On Linux/Unix/Mac systems, use the `auto` option as `*` would need to be escaped to prevent it being treated as a filename glob) ### Specifying public/elastic/floating IP for clouds that support this (e.g. public clouds, OpenStack) -Separate the public/floating IP using a `;`. A static public/floating IP can be specified explicitly, or o auto-allocate a public IP, use the `auto` keyword: +Separate the public/floating IP using a `;`. A static public/floating IP can be specified explicitly, or to auto-allocate a public IP, use the `auto` keyword: > -v 10.10.10.0/24;203.0.113.100 > -v 10.10.10.0/24;auto ### Avi Internal IPAM -When using Avi Internal IPAM for auto-allocation, it may be necessary in some clouds (e.g. NSX-T Cloud) to supply the `-int` parameter to ensure the VsVip is populated with all the correct fields. Other clouds (e.g. vCenter Cloud) are more forgiving and usually work without specifying this parameter. +When using Avi Internal IPAM for auto-allocation, it may be necessary in some clouds (e.g. NSX-T Cloud) to supply the `-int` parameter to ensure the VsVip is populated with all the correct fields. Other clouds (e.g. vCenter Cloud) are more forgiving and generally work without specifying this parameter if there is only a single IPAM subnet specified. ## Special flags @@ -216,13 +218,13 @@ In this case, if learning is enabled in the source WAF Policy, it will remain en The below example clones a VS and forces cloning of the WAF Policy and any PSM groups also. -> clone_vs.py -c controller1.acme.com -fc vs-wafpolicy,positive-security-model vs example cloned-example -v * +> clone_vs.py -c controller1.acme.com -fc vs-wafpolicy,positive-security-model vs example cloned-example -v auto ### Disabling learning in the cloned WAF Policy It may desirable to disable learning for the cloned WAF Policy and its referenced PSM groups, for example if the source Virtual Service was used for learning and the cloned Virtual Service will be an instance of the same application, but independent learning is not desired. This can be achieved with the option `-flags disablelearning`: -> clone_vs.py -c controller1.acme.com -fc vs-wafpolicy,positive-security-model -flags disablelearning vs example cloned-example -v * +> clone_vs.py -c controller1.acme.com -fc vs-wafpolicy,positive-security-model -flags disablelearning vs example cloned-example -v auto This flag can also be used when cloning a WAF Policy individually: From 3d4913905eb3cd397978a03a4f7031ed626d4f69 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Wed, 9 Aug 2023 20:45:25 +0100 Subject: [PATCH 05/11] Fixed handling of VRF references in certain NSX-T Cloud scenarios --- python/avi/sdk/samples/clone_vs.md | 4 ++++ python/avi/sdk/samples/clone_vs.py | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/python/avi/sdk/samples/clone_vs.md b/python/avi/sdk/samples/clone_vs.md index 55ffb15042..89a22756f0 100644 --- a/python/avi/sdk/samples/clone_vs.md +++ b/python/avi/sdk/samples/clone_vs.md @@ -306,3 +306,7 @@ Changelog: 2.0.3: * Added some additional reference handling for less-common DataScript and WAF Profile configurations + +2.0.4: + +* Fixed VRF handling for cloning to/from NSX-T Cloud in certain scenarios diff --git a/python/avi/sdk/samples/clone_vs.py b/python/avi/sdk/samples/clone_vs.py index b9706f73c9..0eac255b9e 100644 --- a/python/avi/sdk/samples/clone_vs.py +++ b/python/avi/sdk/samples/clone_vs.py @@ -30,7 +30,7 @@ urllib3.disable_warnings() -AVICLONE_VERSION = [2, 0, 3] +AVICLONE_VERSION = [2, 0, 4] # Try to obtain the terminal width to allow spprint() to wrap output neatly. # If unable to determine, assume terminal width is 70 characters @@ -575,9 +575,11 @@ def _processobject_pool(self, obj, force_clone): # Remove tier1_lr in case source was NSX-T Cloud with # overlay obj['vrf_ref'] = self.ov_obj['url'] + obj.pop('tier1_lr', None) elif self.other_vrf: # Case where target is NSX-T Cloud with overlay obj['tier1_lr'] = self.other_vrf + obj.pop('vrf_ref', None) try: valid_ref_objects = self.VALID_POOL_REF_OBJECTS @@ -2168,7 +2170,7 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, if self.oc_obj: v_obj['cloud_ref'] = self.oc_obj['url'] - if vsvip_obj: + if vsvip_obj and not(manual_vsvip): vsvip_obj['cloud_ref'] = self.oc_obj['url'] v_obj.pop('cloud_type', None) @@ -2185,17 +2187,19 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, # If moving to a different cloud, Virtual Service will be moved # to the default global VRF in the target cloud unless a # specific target VRF is specified. - if vsvip_obj: + if vsvip_obj and not(manual_vsvip): vsvip_obj.pop('vrf_context_ref', None) vsvip_obj.pop('tier1_lr', None) # Update VRF or T1_LR reference if a target VRF is specified - if vsvip_obj: + if vsvip_obj and not(manual_vsvip): if self.ov_obj: vsvip_obj['vrf_context_ref'] = self.ov_obj['url'] + vsvip_obj.pop('tier1_lr', None) elif self.other_vrf: # Case where target is NSX-T Cloud with overlay vsvip_obj['tier1_lr'] = self.other_vrf + vsvip_obj.pop('vrf_context_ref', None) if new_segroup: # Locate SE group by name in the appropriate cloud @@ -2342,9 +2346,13 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, v_obj['vsvip_ref'] = new_vsvip_obj['url'] # Set VS VRF Context to match VsVip VRF Context - v_obj['vrf_context_ref'] = new_vsvip_obj['vrf_context_ref'] + if 'tier1_lr' in vsvip_obj: + # If target is NSX-T Cloud with overlay, allow + # Controller to manage VS's VRF from VsVip tier_1 config + v_obj.pop('vrf_context_ref', None) + # Try to create the new VS (possibly in a different tenant to the # source) From b522aea76ec48c37aec5a6bfa9c2f385af4ca472 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Wed, 30 Aug 2023 12:20:07 +0100 Subject: [PATCH 06/11] Fix VRF handling with manual VsVip --- python/avi/sdk/samples/clone_vs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/avi/sdk/samples/clone_vs.py b/python/avi/sdk/samples/clone_vs.py index 0eac255b9e..f0ded4af1d 100644 --- a/python/avi/sdk/samples/clone_vs.py +++ b/python/avi/sdk/samples/clone_vs.py @@ -30,7 +30,7 @@ urllib3.disable_warnings() -AVICLONE_VERSION = [2, 0, 4] +AVICLONE_VERSION = [2, 0, 5] # Try to obtain the terminal width to allow spprint() to wrap output neatly. # If unable to determine, assume terminal width is 70 characters @@ -2323,6 +2323,7 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, if vsvip_obj: if manual_vsvip: v_obj['vsvip_ref'] = vsvip_obj['url'] + v_obj['vrf_context_ref'] = vsvip_obj['vrf_context_ref'] else: # Create new vsvip object From e15b85b6e68ae3455be4e70946b9887a2077a3e9 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Wed, 30 Aug 2023 12:20:31 +0100 Subject: [PATCH 07/11] Update clone_vs.md --- python/avi/sdk/samples/clone_vs.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/avi/sdk/samples/clone_vs.md b/python/avi/sdk/samples/clone_vs.md index 89a22756f0..f76367c689 100644 --- a/python/avi/sdk/samples/clone_vs.md +++ b/python/avi/sdk/samples/clone_vs.md @@ -310,3 +310,7 @@ Changelog: 2.0.4: * Fixed VRF handling for cloning to/from NSX-T Cloud in certain scenarios + +2.0.5: + +* Fixed VRF handling with manual VsVip From 897260876e0edbb3ffee5eb2ff5706dc2b252d70 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Thu, 1 Feb 2024 18:07:45 +0000 Subject: [PATCH 08/11] Update clone_vs to version 2.0.6 --- python/avi/sdk/samples/clone_vs.md | 10 +++ python/avi/sdk/samples/clone_vs.py | 140 ++++++++++++++++++++--------- 2 files changed, 110 insertions(+), 40 deletions(-) diff --git a/python/avi/sdk/samples/clone_vs.md b/python/avi/sdk/samples/clone_vs.md index f76367c689..fbe6a54e9d 100644 --- a/python/avi/sdk/samples/clone_vs.md +++ b/python/avi/sdk/samples/clone_vs.md @@ -314,3 +314,13 @@ Changelog: 2.0.5: * Fixed VRF handling with manual VsVip + +2.0.6: + +* Changed handling of Pool and PoolGroup references in VsDataScriptSet + * Will now try to preserve the names of Pools and PoolGroups when cloning a VsDataScriptSet if possible (e.g. when cloning to a different tenant/Controller) + * If name preservation is not possible, unique names will now be generated from the old Pool/PoolGroup name (previous behaviour was to generate names based on VsDataScript name) + * Will generate a warning that DataScript code changes may be needed, for example because a Pool or PoolGroup name could not be preserved +* Changed handling of PoolGroup cloning + * Will now try to preserve the names of Pools when cloning a PoolGroup if possible (e.g. when cloning to a different tenant/Controller) + * If name preservation is not possible, unique names will now be generated from the old Pool name (previous behaviour was to generate names based on PoolGroup name) diff --git a/python/avi/sdk/samples/clone_vs.py b/python/avi/sdk/samples/clone_vs.py index f0ded4af1d..f7f5601f1e 100644 --- a/python/avi/sdk/samples/clone_vs.py +++ b/python/avi/sdk/samples/clone_vs.py @@ -30,7 +30,7 @@ urllib3.disable_warnings() -AVICLONE_VERSION = [2, 0, 5] +AVICLONE_VERSION = [2, 0, 6] # Try to obtain the terminal width to allow spprint() to wrap output neatly. # If unable to determine, assume terminal width is 70 characters @@ -340,7 +340,8 @@ def get_new_name(self, object_type, new_name, force_unique_name=False): return new_name def clone_object(self, old_name, new_name, object_type=None, - force_clone=None, force_unique_name=False): + force_clone=None, force_unique_name=False, + old_obj=None): """ Clones an object other than a Virtual Service or GSLB Service @@ -350,14 +351,18 @@ def clone_object(self, old_name, new_name, object_type=None, Returns a tuple: json representation of the cloned object, list of additional objects created if any and any warnings generated - :param old_name: Name of existing object (name or uri) + :param old_name: Name of existing object (name or uri), or None if + existing object is being passed via old_obj :param new_name: New name for cloned object - :param object_type: Type of object (or None to infer from name) + :param object_type: Type of object (or None to infer from old_name or + old_obj) :param force_clone: List of referenced object attributes to forcibly clone rather than re-use (for example health_monitor_refs) :param force_unique_name: Resolve destination name conflicts by appending an index number + :param old_obj: Existing object to clone - can be specified instead + of passing old_name if the object is already available :return: tuple - json representation of the cloned object, list of additional objects created if any :rtype: tuple @@ -368,35 +373,50 @@ def clone_object(self, old_name, new_name, object_type=None, created_objs = [] warnings = [] - if not object_type: - # If object_type is not specified, assume the old_name is in - # form object_type/uuid - if '/' in old_name: - object_type = old_name.split('/')[0] - else: - raise ValueError('Unable to determine object type for %s' - % object_type) - - logger.debug('Cloning %s "%s" to "%s"', object_type, - old_name, new_name) - - if old_name.startswith(object_type + '/'): - old_obj = self.api.get(old_name, tenant_uuid=self.tenant_uuid, - params=('export_key=true' - if object_type == 'sslkeyandcertificate' - else None)).json() + if old_obj: old_name = old_obj['name'] + if not object_type: + if 'url' in old_obj: + object_type = old_obj['url'].split('/api/')[1].split('/')[0] + else: + raise ValueError('Unable to determine object type for %s' + % old_name) else: - old_obj = self.api.get_object_by_name(object_type, old_name, - tenant_uuid=self.tenant_uuid, - params=({'export_key': True} - if object_type == 'sslkeyandcertificate' - else None)) + if not object_type: + # If object_type is not specified, assume the old_name is in + # form object_type/uuid + if '/' in old_name: + object_type = old_name.split('/')[0] + else: + raise ValueError('Unable to determine object type for %s' + % old_name) + + if old_name.startswith(object_type + '/'): + old_obj = self.api.get(old_name, tenant_uuid=self.tenant_uuid, + params=('export_key=true' + if object_type == 'sslkeyandcertificate' + else None)).json() + old_name = old_obj['name'] + else: + old_obj = self.api.get_object_by_name(object_type, old_name, + tenant_uuid=self.tenant_uuid, + params=({'export_key': True} + if object_type == 'sslkeyandcertificate' + else None)) if not old_obj: raise Exception('Object of type %s named %s could not be found' % (object_type, old_name)) + if not new_name: + # If new_name is not specified, assume new object should use the + # name of the source object (force_unique_name may still cause + # the new name to then be uniquified) + new_name = old_name + + logger.debug('Cloning %s "%s" to "%s"', object_type, + old_name, new_name) + new_name = self.get_new_name(object_type, new_name, force_unique_name) # Remove unique attributes and rename object @@ -611,8 +631,6 @@ def _processobject_poolgroup(self, obj, force_clone): logger.debug('Running _processobject_poolgroup') - new_pool_group_name = obj['name'] - created_objs = [] warnings = [] @@ -622,11 +640,9 @@ def _processobject_poolgroup(self, obj, force_clone): for member in obj['members']: if 'pool_ref' in member: p_path = member['pool_ref'].split('/api/')[1] - new_pool_name = '-'.join([new_pool_group_name, - 'pool', str(count)]) p_obj, p_created_objs, p_warnings = self.clone_object( - old_name=p_path, new_name=new_pool_name, + old_name=p_path, new_name=None, force_clone=force_clone, force_unique_name=True) @@ -897,11 +913,17 @@ def _processobject_sslkeyandcertificate(self, obj, force_clone): # Remove cross-tenant references logger.debug('Removing ca_certs references') obj.pop('ca_certs', None) - obj.pop('key_base64', None) - obj.pop('certificate_base64', None) + obj.pop('key_base64', None) + obj.pop('certificate_base64', None) obj.pop('ocsp_error_status', None) + if obj.get('key', '') == '': + # Workaround for an issue where CA certs may return a value + # of for a key even with export_key=true and even + # though there is no key. + obj.pop('key', None) + if 'key_passphrase' in obj: old_name = obj['_clonevs_old_name'] obj['key_passphrase'] = self.ssl_key_pps.get(old_name, '') @@ -1080,7 +1102,7 @@ def _processobject_vsdatascriptset(self, obj, force_clone): logger.debug('Running _process_vsdatascriptset') - new_vsdatascriptset_name = obj['name'] + vsdatascriptset_name = obj['name'] created_objs = [] warnings = [] @@ -1095,15 +1117,31 @@ def _processobject_vsdatascriptset(self, obj, force_clone): p_obj_url = self.clone_track[pool_ref] logger.debug('Reusing previously cloned object %s', p_obj_url) + warnings.append('VsDataScriptSet "%s" may require ' + 'code changes due to re-use of cloned ' + 'Pool.' % vsdatascriptset_name) else: # Otherwise, clone the pool p_path = pool_ref.split('/api/')[1] - p_name = '-'.join([obj['name'], 'pool']) + old_pobj = self.api.get(p_path, + tenant_uuid=self.tenant_uuid).json() + old_pname = old_pobj['name'] + p_obj, p_created_objs, p_warnings = self.clone_object( - old_name=p_path, new_name=p_name, + old_name=old_pname, new_name=old_pname, force_clone=force_clone, - force_unique_name=True) + force_unique_name=True, + old_obj=old_pobj) + + new_pname = p_obj['name'] + + if old_pname != new_pname: + warnings.append('VsDataScriptSet "%s" may require ' + 'code changes due to cloned pool ' + 'name changing from "%s" to "%s".' + % (vsdatascriptset_name, + old_pname, new_pname)) created_objs.append(p_obj) created_objs.extend(p_created_objs) @@ -1121,14 +1159,31 @@ def _processobject_vsdatascriptset(self, obj, force_clone): pg_obj_url = self.clone_track[pool_group_ref] logger.debug('Reusing previously cloned object %s', pg_obj_url) + warnings.append('VsDataScriptSet "%s" may require ' + 'code changes due to re-use of cloned ' + 'PoolGroup.' % vsdatascriptset_name) else: pg_path = pool_group_ref.split('/api/')[1] - pg_name = '-'.join([obj['name'], 'poolgroup']) + old_pgobj = self.api.get(pg_path, + tenant_uuid=self.tenant_uuid).json() + old_pgname = old_pgobj['name'] + (pg_obj, pg_created_objs, pg_warnings) = self.clone_object( - old_name=pg_path, new_name=pg_name, + old_name=old_pgname, new_name=old_pgname, force_clone=force_clone, - force_unique_name=True) + force_unique_name=True, + old_obj=old_pgobj) + + new_pgname = pg_obj['name'] + + if old_pgname != new_pgname: + warnings.append('VsDataScriptSet "%s" may require ' + 'code changes due to cloned ' + 'poolgroup name changing from ' + '"%s" to "%s".' + % (vsdatascriptset_name, + old_pgname, new_pgname)) created_objs.append(pg_obj) created_objs.extend(pg_created_objs) @@ -1146,6 +1201,11 @@ def _processobject_vsdatascriptset(self, obj, force_clone): refs=valid_ref_objects, force_clone=force_clone) + if new_objs: + warnings.append('VsDataScriptSet "%s" may require code changes ' + 'due to one or more cloned references.' + % vsdatascriptset_name) + created_objs.extend(new_objs) warnings.extend(new_warnings) From 4dd2f68b6b2f3c73ca65273cbc37d490afa57154 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Wed, 1 May 2024 18:14:27 +0100 Subject: [PATCH 09/11] Added healthmonitor cloning support --- python/avi/sdk/samples/clone_vs.md | 4 +++ python/avi/sdk/samples/clone_vs.py | 46 +++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/python/avi/sdk/samples/clone_vs.md b/python/avi/sdk/samples/clone_vs.md index fbe6a54e9d..06ee02ad0a 100644 --- a/python/avi/sdk/samples/clone_vs.md +++ b/python/avi/sdk/samples/clone_vs.md @@ -324,3 +324,7 @@ Changelog: * Changed handling of PoolGroup cloning * Will now try to preserve the names of Pools when cloning a PoolGroup if possible (e.g. when cloning to a different tenant/Controller) * If name preservation is not possible, unique names will now be generated from the old Pool name (previous behaviour was to generate names based on PoolGroup name) + +2.0.7: + +* Add support for cloning HealthMonitors with SSL attributes or authentication attributes (authentication attributes must be manually re-entered) diff --git a/python/avi/sdk/samples/clone_vs.py b/python/avi/sdk/samples/clone_vs.py index f7f5601f1e..a34ef681ad 100644 --- a/python/avi/sdk/samples/clone_vs.py +++ b/python/avi/sdk/samples/clone_vs.py @@ -30,7 +30,7 @@ urllib3.disable_warnings() -AVICLONE_VERSION = [2, 0, 6] +AVICLONE_VERSION = [2, 0, 7] # Try to obtain the terminal width to allow spprint() to wrap output neatly. # If unable to determine, assume terminal width is 70 characters @@ -119,6 +119,12 @@ class AviClone: 'ssl-hsmgroup': 'hardwaresecuritymodulegroup_ref'} VALID_SSOPOLICY_REF_OBJECTS = { 'sso-authprofile': 'authentication_policy/default_auth_profile_ref'} + VALID_HEALTHMONITOR_REF_OBJECTS = { + 'hm-sslprofile': 'https_monitor/ssl_attributes/ssl_profile_ref', + 'hm-pkiprofile': 'https_monitor/ssl_attributes/pki_profile_ref', + 'hm-sslkeyandcertificate': + 'https_monitor/ssl_attributes/ssl_key_and_certificate_ref' + } def __init__(self, source_api, dest_api=None, flags=None, tenant=None, other_tenant=None, other_cloud=None, @@ -820,6 +826,44 @@ def _processobject_ssopolicy(self, obj, force_clone): return created_objs, warnings + def _processobject_healthmonitor(self, obj, force_clone): + """ + Performs healthmonitor-specific manipulations on the cloned + object + """ + + logger.debug('Running _process_healthmonitor') + + created_objs = [] + warnings = [] + + if 'authentication' in obj: + obj['authentication']['username'] = 'placeholder' + obj['authentication']['password'] = 'placeholder' + warnings.append('The authentication username and password ' + 'referenced in healthmonitor %s cannot be cloned ' + 'and must be re-entered manually.' % obj['name']) + + try: + valid_ref_objects = self.VALID_HEALTHMONITOR_REF_OBJECTS + + # Process generic references, re-using or cloning referenced + # objects as necessary + + created_objs, warnings = self._process_refs(parent_obj=obj, + refs=valid_ref_objects, + force_clone=force_clone) + + except Exception as ex: + # If an exception occurred, delete any intermediate objects we + # have created + + self.delete_objects(created_objs) + + raise + + return created_objs, warnings + def _processobject_wafpolicy(self, obj, force_clone): """ Performs wafpolicy-specific manipulations on the cloned From 9774e5bab51ecda1add066dcea1a4809f54f78b0 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Fri, 3 May 2024 17:39:50 +0100 Subject: [PATCH 10/11] Add support for cloning OAuth2 configs --- python/avi/sdk/samples/clone_vs.md | 2 + python/avi/sdk/samples/clone_vs.py | 75 +++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/python/avi/sdk/samples/clone_vs.md b/python/avi/sdk/samples/clone_vs.md index 06ee02ad0a..fbc81b7b7a 100644 --- a/python/avi/sdk/samples/clone_vs.md +++ b/python/avi/sdk/samples/clone_vs.md @@ -328,3 +328,5 @@ Changelog: 2.0.7: * Add support for cloning HealthMonitors with SSL attributes or authentication attributes (authentication attributes must be manually re-entered) +* Add support for cloning AuthProfiles for OAuth2 +* Add support for cloning VS with OAuth2 SSO configuration diff --git a/python/avi/sdk/samples/clone_vs.py b/python/avi/sdk/samples/clone_vs.py index a34ef681ad..ee0dec7ab0 100644 --- a/python/avi/sdk/samples/clone_vs.py +++ b/python/avi/sdk/samples/clone_vs.py @@ -91,6 +91,7 @@ class AviClone: 'vs-wafpolicy': 'waf_policy_ref', 'vs-rewritablecontent': 'content_rewrite/rewritable_content_ref', 'vs-authprofile': 'client_auth/auth_profile_ref', + 'vs-ssoauthprofile': 'sso_policy/default_auth_profile_ref', 'vs-ssopolicy': 'sso_policy_ref', 'vs-botpolicy': 'bot_policy_ref'} VALID_GS_REF_OBJECTS = { @@ -106,19 +107,29 @@ class AviClone: 'http_profile/compression_profile/compressible_content_ref', 'appprofile-compressibleipaddrgroup': 'ip_addrs_ref', 'appprofile-compressibledevices': 'devices_ref'} - VALID_WAFPOLICY_REF_OBJECTS = {'waf-profile': 'waf_profile_ref', - 'waf-crs': 'waf_crs_ref', - 'positive-security-model': - 'positive_security_model/group_refs', - 'waf-appsignatures': - 'application_signatures/provider_ref'} - VALID_WAFPOLICYPSMGROUP_REF_OBJECTS = {'wafpsm-stringgroup': - 'match_value_string_group_ref'} + VALID_WAFPOLICY_REF_OBJECTS = { + 'waf-profile': 'waf_profile_ref', + 'waf-crs': 'waf_crs_ref', + 'positive-security-model': 'positive_security_model/group_refs', + 'waf-appsignatures': 'application_signatures/provider_ref' + } + VALID_WAFPOLICYPSMGROUP_REF_OBJECTS = { + 'wafpsm-stringgroup': 'match_value_string_group_ref' + } VALID_SSLCERT_REF_OBJECTS = { 'ssl-certmgmt': 'certificate_management_profile_ref', - 'ssl-hsmgroup': 'hardwaresecuritymodulegroup_ref'} + 'ssl-hsmgroup': 'hardwaresecuritymodulegroup_ref' + } VALID_SSOPOLICY_REF_OBJECTS = { - 'sso-authprofile': 'authentication_policy/default_auth_profile_ref'} + 'sso-authprofile': 'authentication_policy/default_auth_profile_ref' + } + VALID_OAUTHSETTINGS_REF_OBJECTS = { + 'oauth-authprofile': 'auth_profile_ref' + } + VALID_AUTHPROFILE_REF_OBJECTS = { + 'authprofile-pool': 'oauth_profile/pool_ref', + 'authprofile-jwtprofile': 'jwt_profile_ref' + } VALID_HEALTHMONITOR_REF_OBJECTS = { 'hm-sslprofile': 'https_monitor/ssl_attributes/ssl_profile_ref', 'hm-pkiprofile': 'https_monitor/ssl_attributes/pki_profile_ref', @@ -793,6 +804,24 @@ def _processobject_authprofile(self, obj, force_clone): '%s cannot be cloned and must be re-entered ' 'manually.' % obj['name']) + try: + valid_ref_objects = self.VALID_AUTHPROFILE_REF_OBJECTS + + # Process generic references, re-using or cloning referenced + # objects as necessary + + created_objs, warnings = self._process_refs(parent_obj=obj, + refs=valid_ref_objects, + force_clone=force_clone) + + except Exception as ex: + # If an exception occurred, delete any intermediate objects we + # have created + + self.delete_objects(created_objs) + + raise + return created_objs, warnings def _processobject_ssopolicy(self, obj, force_clone): @@ -2269,6 +2298,27 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, warnings.append('VS has a SAML configuration that will need to ' 'be manually updated.') + # Handle OAuth Configuration + + if 'oauth_settings' in v_obj.get('oauth_vs_config', {}): + oauth_settings = v_obj['oauth_vs_config']['oauth_settings'] + valid_ref_objects = self.VALID_OAUTHSETTINGS_REF_OBJECTS + for oauth_setting in oauth_settings: + + (oa_created_objs, + oa_warnings) = self._process_refs(parent_obj=oauth_setting, + refs=valid_ref_objects, + force_clone=force_clone) + created_objs.extend(oa_created_objs) + warnings.extend(oa_warnings) + + app_settings = oauth_setting.get('app_settings', {}) + if app_settings.get('client_secret', None): + app_settings['client_secret'] = 'placeholder' + warnings.append('VS has an OAuth App Client Secret ' + 'that will need to be manually ' + 'updated.') + # (Try to!) move the new Virtual Service and VsVip # to a different cloud @@ -2525,7 +2575,10 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, set(AviClone.VALID_POLICYSET_REF_OBJECTS.keys()) | set(AviClone.VALID_DATASCRIPT_REF_OBJECTS.keys()) | set(AviClone.VALID_APPLICATIONPROFILE_REF_OBJECTS.keys()) | - set(AviClone.VALID_SSLCERT_REF_OBJECTS.keys())) + set(AviClone.VALID_SSLCERT_REF_OBJECTS.keys()) | + set(AviClone.VALID_SSOPOLICY_REF_OBJECTS.keys()) | + set(AviClone.VALID_HEALTHMONITOR_REF_OBJECTS.keys()) | + set(AviClone.VALID_OAUTHSETTINGS_REF_OBJECTS.keys())) parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, From eedc9ec96eeaf8cc1bf27f05520d8e260d1a4fab Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Mon, 6 May 2024 16:37:57 +0100 Subject: [PATCH 11/11] Additional VS cloning enhancements --- python/avi/sdk/samples/clone_vs.md | 2 + python/avi/sdk/samples/clone_vs.py | 67 ++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/python/avi/sdk/samples/clone_vs.md b/python/avi/sdk/samples/clone_vs.md index fbc81b7b7a..9d9b22a4d0 100644 --- a/python/avi/sdk/samples/clone_vs.md +++ b/python/avi/sdk/samples/clone_vs.md @@ -330,3 +330,5 @@ Changelog: * Add support for cloning HealthMonitors with SSL attributes or authentication attributes (authentication attributes must be manually re-entered) * Add support for cloning AuthProfiles for OAuth2 * Add support for cloning VS with OAuth2 SSO configuration +* Add support for cloning VS with service-level network/application profile overrides +* Add support for cloning DNS VS with Topology Policies diff --git a/python/avi/sdk/samples/clone_vs.py b/python/avi/sdk/samples/clone_vs.py index ee0dec7ab0..f2172c1a71 100644 --- a/python/avi/sdk/samples/clone_vs.py +++ b/python/avi/sdk/samples/clone_vs.py @@ -64,7 +64,8 @@ class AviClone: 'pool-pkiprofile': 'pki_profile_ref', 'pool-sslcert': 'ssl_key_and_certificate_ref', 'pool-analyticsprofile': 'analytics_profile_ref', - 'pool-autoscalepolicy': 'autoscale_policy_ref'} + 'pool-autoscalepolicy': 'autoscale_policy_ref' + } VALID_DATASCRIPT_REF_OBJECTS = { 'ds-ipgroup': 'ipgroup_refs', 'ds-stringgroup': 'string_group_refs', @@ -72,12 +73,14 @@ class AviClone: 'ds-sslprofile': 'ssl_profile_refs', 'ds-pkiprofile': 'pki_profile_refs', 'ds-geodb': 'geo_db_ref', - 'ds-ipreputation': 'ip_reputation_db_ref'} + 'ds-ipreputation': 'ip_reputation_db_ref' + } VALID_POLICYSET_REF_OBJECTS = { 'policy-ipgroup': 'group_refs', 'policy-stringgroup': 'string_group_refs', 'policy-geodb': 'geo_db_ref', - 'policy-ipreputation': 'ip_reputation_db_ref'} + 'policy-ipreputation': 'ip_reputation_db_ref' + } VALID_VS_REF_OBJECTS = { 'vs-appprofile': 'application_profile_ref', 'vs-networkprofile': 'network_profile_ref', @@ -93,7 +96,12 @@ class AviClone: 'vs-authprofile': 'client_auth/auth_profile_ref', 'vs-ssoauthprofile': 'sso_policy/default_auth_profile_ref', 'vs-ssopolicy': 'sso_policy_ref', - 'vs-botpolicy': 'bot_policy_ref'} + 'vs-botpolicy': 'bot_policy_ref' + } + VALID_VS_OVERRIDE_REF_OBJECTS = { + 'vsoverride-appprofile': 'override_application_profile_ref', + 'vsoverride-networkprofile': 'override_network_profile_ref', + } VALID_GS_REF_OBJECTS = { 'gs-persistency': 'application_persistence_profile_ref', 'gs-healthmonitor': 'health_monitor_refs' @@ -2270,6 +2278,18 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, v_obj['pool_group_ref'] = pg_obj['url'] + # Handle service overrides + + if 'services' in v_obj: + valid_s_ref_objects = self.VALID_VS_OVERRIDE_REF_OBJECTS + for service in v_obj['services']: + (s_created_objs, + s_warnings) = self._process_refs(parent_obj=service, + refs=valid_s_ref_objects, + force_clone=force_clone) + created_objs.extend(s_created_objs) + warnings.extend(s_warnings) + # Remove unique atributes and rename v_obj.pop('uuid', None) @@ -2302,12 +2322,12 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, if 'oauth_settings' in v_obj.get('oauth_vs_config', {}): oauth_settings = v_obj['oauth_vs_config']['oauth_settings'] - valid_ref_objects = self.VALID_OAUTHSETTINGS_REF_OBJECTS + valid_o_ref_objects = self.VALID_OAUTHSETTINGS_REF_OBJECTS for oauth_setting in oauth_settings: (oa_created_objs, oa_warnings) = self._process_refs(parent_obj=oauth_setting, - refs=valid_ref_objects, + refs=valid_o_ref_objects, force_clone=force_clone) created_objs.extend(oa_created_objs) warnings.extend(oa_warnings) @@ -2397,24 +2417,26 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, created_objs.extend(ps_created_objs) warnings.extend(ps_warnings) - # Clone any DNS policy sets referenced in the VS + # Clone any DNS / Topology Policy sets referenced in the VS - if 'dns_policies' in v_obj: - for polset in v_obj['dns_policies']: - ps_path = polset['dns_policy_ref'].split('/api/')[1] - ps_name = '-'.join([new_vs_name, - (c_obj['name'] - if self.oc_obj is None - else self.oc_obj['name']), - 'DNS-Policy']) - ps_obj, ps_created_objs, ps_warnings = self.clone_object( - old_name=ps_path, new_name=ps_name, - force_clone=force_clone, force_unique_name=True) + dns_topo_policies = (v_obj.get('dns_policies', []) + + v_obj.get('topology_policies', [])) - polset['dns_policy_ref'] = ps_obj['url'] - created_objs.append(ps_obj) - created_objs.extend(ps_created_objs) - warnings.extend(ps_warnings) + for polset in dns_topo_policies: + ps_path = polset['dns_policy_ref'].split('/api/')[1] + ps_name = '-'.join([new_vs_name, + (c_obj['name'] + if self.oc_obj is None + else self.oc_obj['name']), + 'DNS-Policy']) + ps_obj, ps_created_objs, ps_warnings = self.clone_object( + old_name=ps_path, new_name=ps_name, + force_clone=force_clone, force_unique_name=True) + + polset['dns_policy_ref'] = ps_obj['url'] + created_objs.append(ps_obj) + created_objs.extend(ps_created_objs) + warnings.extend(ps_warnings) # Clone network security policy referenced in the VS @@ -2568,6 +2590,7 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, valid_refs = sorted( set(AviClone.VALID_VS_REF_OBJECTS.keys()) | + set(AviClone.VALID_VS_OVERRIDE_REF_OBJECTS.keys()) | set(AviClone.VALID_GS_REF_OBJECTS.keys()) | set(AviClone.VALID_WAFPOLICY_REF_OBJECTS.keys()) | set(AviClone.VALID_WAFPOLICYPSMGROUP_REF_OBJECTS.keys()) |