Closed mrcloudmustache closed 3 weeks ago
acknowledged, you are correct. the filter logic has yet to be worked out and will require another glance after the coverage of the SDK is a bit better.
For now, the best way to retrieve a specific instance is by UUID, but the API does support retrieving by name so I don't see this being a challenge that we cannot overcome later.
how do you feel about this?
return a single instance of an object by name:
x = security_rules.fetch(folder='Prisma Access', name='Monitor WAN to GlobalProtect')
validate object type
type(x)
scm.models.security.security_rules.SecurityRuleResponseModel
update an attribute of the object
x.description = "ABC"
validate the model object as a Python dictionary
x.model_dump(exclude_none=True)
{'name': 'Monitor WAN to GlobalProtect',
'disabled': True,
'description': 'ABC',
'tag': [],
'from_': ['any'],
'source': ['any'],
'negate_source': False,
'source_user': ['any'],
'source_hip': ['any'],
'to_': ['any'],
'destination': ['any'],
'negate_destination': False,
'destination_hip': ['any'],
'application': ['panos-global-protect', 'ssl', 'web-browsing'],
'service': ['any'],
'category': ['any'],
'action': <Action.allow: 'allow'>,
'log_setting': 'Cortex Data Lake',
'log_end': True,
'id': '8a5cb583-85bd-45d9-be7e-2a6f79888f4c',
'folder': 'Shared'}
push the update
security_rules.update(object_id=x.id, data=x.model_dump(exclude_none=True))
get the response modeled object back
SecurityRuleResponseModel(name='Monitor WAN to GlobalProtect', disabled=True, description='ABC', tag=[], from_=['any'], source=['any'], negate_source=False, source_user=['any'], source_hip=['any'], to_=['any'], destination=['any'], negate_destination=False, destination_hip=['any'], application=['panos-global-protect', 'ssl', 'web-browsing'], service=['any'], category=['any'], action=<Action.allow: 'allow'>, profile_setting=None, log_setting='Cortex Data Lake', schedule=None, log_start=None, log_end=True, id='8a5cb583-85bd-45d9-be7e-2a6f79888f4c', folder='Shared', snippet=None, device=None)
One thing I'm not entirely sold on is the requirement to pass the updated object with model_dump(exclude_none=True)
, yes it will be what is required by the remote API model, but I feel like most people will forget how to perform this.
An alternative approach would be to have the fetch()
method return the modeled object back as a Python dictionary and simply pass that into the update object
Return the response modeled object as a Python dictionary, passing in the exclude_unset
and exclude_none
already:
response = self.api_client.get(self.ENDPOINT, params=params)
# Since response is a single object when 'name' is provided
# We can directly create the SecurityRuleResponseModel
rule = SecurityRuleResponseModel(**response)
return rule.model_dump(exclude_unset=True, exclude_none=True)
what this would look like:
return a single instance of an object by name:
x = security_rules.fetch(folder='Prisma Access', name='Monitor WAN to GlobalProtect')
validate object type
type(x)
dict
update an attribute of the object
x['description'] = "ABCDEFG"
validate the Python dictionary object
{'name': 'Monitor WAN to GlobalProtect',
'disabled': True,
'description': 'ABCDEFG',
'from_': ['any'],
'source': ['any'],
'negate_source': False,
'source_user': ['any'],
'source_hip': ['any'],
'to_': ['any'],
'destination': ['any'],
'negate_destination': False,
'destination_hip': ['any'],
'application': ['panos-global-protect', 'ssl', 'web-browsing'],
'service': ['any'],
'category': ['any'],
'action': <Action.allow: 'allow'>,
'log_setting': 'Cortex Data Lake',
'log_end': True,
'id': '8a5cb583-85bd-45d9-be7e-2a6f79888f4c',
'folder': 'Shared'}
push the update
security_rules.update(object_id=x['id'], data=x)
get the response modeled object back
SecurityRuleResponseModel(name='Monitor WAN to GlobalProtect', disabled=True, description='ABCDEFG', tag=[], from_=['any'], source=['any'], negate_source=False, source_user=['any'], source_hip=['any'], to_=['any'], destination=['any'], negate_destination=False, destination_hip=['any'], application=['panos-global-protect', 'ssl', 'web-browsing'], service=['any'], category=['any'], action=<Action.allow: 'allow'>, profile_setting=None, log_setting='Cortex Data Lake', schedule=None, log_start=None, log_end=True, id='8a5cb583-85bd-45d9-be7e-2a6f79888f4c', folder='Shared', snippet=None, device=None)
Full workflow for an address object:
In [1]: from scm.client import Scm
In [2]: from scm.config.objects import Address
In [3]: api_client = Scm(
...: client_id="example@1234567890.iam.panserviceaccount.com",
...: client_secret="12345678-abcd-1234-efgh-1234567890ab",
...: tsg_id="1234567890",
...: )
In [4]: addresses = Address(api_client)
In [5]: google_dns = addresses.fetch(folder='Prisma Access', name='Google DNS2-1-4')
In [6]: type(google_dns)
Out[6]: dict
In [7]: google_dns
Out[7]:
{'name': 'Google DNS2-1-4',
'id': '64b7218d-7658-4ee6-97c6-d8056dd74201',
'ip_netmask': '8.8.8.8',
'folder': 'Shared'}
In [8]: google_dns['description'] = 'test123'
In [9]: addresses.update(google_dns['id'], google_dns)
Out[9]: AddressResponseModel(name='Google DNS2-1-4', id='64b7218d-7658-4ee6-97c6-d8056dd74201', description='test123', tag=None, ip_netmask='8.8.8.8', ip_range=None, ip_wildcard=None, fqdn=None, folder='Shared', snippet=None, device=None)
For this release of 0.2.0, I opted to have the fetch method return a python dictionary, updated the schema to support the passing of an object ID, and use this id value to simplify the update method by simply passing in the dictionary (instead of also requiring the object id as a separate argument)
updated doc example of the sdk
updated doc example of the models
Also created base models for each configuration item, leaving the other models that inherit it for the deviations (custom schema validations, attributes)
Complete overhaul on pytests, moving to class based approach to help build boilerplate tests going forward, and complete overhaul of docs.
Overall some big changes that can be reviewed here
Problem: An empty list of addresses is returned when addresses are filtered by name
Reason: The API returns the "data" key when filtering by folder.
/config/objects/v1/addresses?folder=Shared&limit=200
The API does NOT return the "data" key when filtering by folder and name
/config/objects/v1/addresses?name=test_internal_network&folder=Shared&limit=200
https://github.com/cdot65/pan-scm-sdk/blob/5635e44c43b471a45342c8c7471b167ced7d8413/scm/config/objects/address.py#L102
Solution: Update the above code to account for both scenarios.