diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1482982..521fc9e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,4 +39,3 @@ black aos/ black tests/ ``` - diff --git a/README.md b/README.md index 287e8f1..e1b29bd 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,28 @@ -# Apstra (AOS) RestAPI Python Library - -Educational Python library with the goal to teach users how to -programmatically interact with Apstra and integrate the Apstra -Rest API. +# Apstra Rest API Python Library +## What is apstra-api-python? +The apstra-api-python library is an example python library built for +learning how to integrate with the Apsta API programmatically. This +library also includes examples to common solutions faced when building and +managing an Apstra managed fabric. ## Documentation -If you are new to Apstra, this library or looking to get a better understanding of +If you are new to Apstra, this library, or looking to get a better understanding of the apstra Rest API please start with the [documentation](https://apstra-api-python.readthedocs.io/en/latest/). The documentation includes an introduction to the Apstra API along with a catalog of working examples of common tasks within Apstra using this library. +## What apstra-api-python is not +While the apstra-api-python library can be used to build integrations with +Apstra, it is not intended to be a production-ready solution. +apstra-api-python does not include 100% coverage of the Apstra API and all +use-cases available. + +Our hope is to provide enough working examples and documentation to get a +developer started with building client libraries based on the Apstra API. + ## Looking to contribute? -Please follow the [CONTRIBUTING](CONTRIBUTING.md) instructions \ No newline at end of file +Please follow the [CONTRIBUTING](CONTRIBUTING.md) instructions to get +started. \ No newline at end of file diff --git a/aos/blueprint.py b/aos/blueprint.py index a09b3ea..67aa4f4 100644 --- a/aos/blueprint.py +++ b/aos/blueprint.py @@ -1719,7 +1719,7 @@ def create_security_zone( "label": name, "vrf_name": name, "vlan_id": vlan_id, - "vni_id": vni_id + "vni_id": vni_id, } sz_task = self.create_security_zone_from_json( @@ -1952,7 +1952,9 @@ def apply_leaf_loopback_ip_to_sz(self, bp_id: str, sz_id: str, pool_id: str): return self.rest.json_resp_put(uri=p_path, data=data) # Virtual Networks - def create_virtual_network_from_json(self, bp_id: str, virtual_network: dict): + def create_virtual_network_from_json( + self, bp_id: str, virtual_network: dict, params: dict = None + ): """ Create new virtual-network (VLAN) in a given blueprint Parameters @@ -1961,13 +1963,16 @@ def create_virtual_network_from_json(self, bp_id: str, virtual_network: dict): (str) - ID of blueprint virtual_network (dict) - VirtualNetwork object - + params + (dict) - endpoint parameters. Default None Returns ------- """ vn_path = f"/api/blueprints/{bp_id}/virtual-networks" - return self.rest.json_resp_post(uri=vn_path, data=virtual_network) + return self.rest.json_resp_post( + uri=vn_path, data=virtual_network, params=params + ) def create_virtual_network( self, @@ -2077,15 +2082,17 @@ def create_virtual_network( if untagged_ct: virt_net["create_policy_untagged"] = untagged_ct - vn = self.create_virtual_network_from_json(bp_id, virt_net) + vn_task = self.create_virtual_network_from_json( + bp_id, virt_net, params={"async": "full"} + ) logger.info(f"Creating virtual-network '{name}' in blueprint '{bp_id}'") repeat_until( - lambda: self.get_virtual_network(bp_id, vn["id"]) != NullVirtualNetwork, + lambda: self.is_task_active(bp_id, vn_task["task_id"]) is False, timeout=timeout, ) - return self.get_virtual_network(bp_id, vn["id"]) + return self.find_vn_by_name(bp_id, name) def get_all_virtual_networks(self, bp_id: str) -> List[VirtualNetwork]: """ diff --git a/aos/resources.py b/aos/resources.py index fd90d6c..965033a 100644 --- a/aos/resources.py +++ b/aos/resources.py @@ -71,7 +71,7 @@ def create( "subnets": [{"network": net} for net in subnets], "tags": tags, "display_name": name, - "id": name.replace(' ', '-'), + "id": name.replace(" ", "-"), } created = self.rest.json_resp_post("/api/resources/ip-pools", data=ip_pool) diff --git a/aos/utils.py b/aos/utils.py index f747f17..eca70b2 100644 --- a/aos/utils.py +++ b/aos/utils.py @@ -8,7 +8,7 @@ def redacted(d): - if d is None or d == '': + if d is None or d == "": return d h = d.copy() diff --git a/setup.py b/setup.py index 3d1600a..3af3c7d 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ NAME = "aos-api-client" -VERSION = '0.1.16' +VERSION = '0.1.17' REQUIRES = (["requests==2.24.0"],) @@ -18,7 +18,7 @@ name=NAME, version=VERSION, description="Apstra AOS API Client", - url="https://github.com/Apstra/aos-api-python", + url="https://github.com/Apstra/apstra-api-python", author="Apstra Inc", author_email="support@apstra.com", packages=find_packages(include=["aos"]), diff --git a/tests/fixtures/aos/3.3.0/blueprints/get_bp_tasks_complete.json b/tests/fixtures/aos/3.3.0/blueprints/get_bp_tasks_complete.json index c094b8b..a2be53e 100644 --- a/tests/fixtures/aos/3.3.0/blueprints/get_bp_tasks_complete.json +++ b/tests/fixtures/aos/3.3.0/blueprints/get_bp_tasks_complete.json @@ -47,6 +47,22 @@ "user_ip": "74.195.108.29", "type": "blueprint.facade.DELETE./security-zones/", "id": "4d167bd3-06d5-490d-9f85-4c742b9196e1" + }, + { + "status": "succeeded", + "begin_at": "2022-03-29T15:14:34.407180+0000", + "request_data": { + "url": "http://34.214.213.43:27809/api/blueprints/1347ebbd-8576-4223-a24b-f3e5c65d1124/virtual-networks?async=full", + "method": "POST" + }, + "user_id": "d3b4c28b-3e57-4638-ae21-7eb217b437c6", + "last_updated_at": "2022-03-29T15:14:34.725654+0000", + "user_name": "admin", + "created_at": "2022-03-29T15:14:34.401080+0000", + "config_last_updated_at": "2022-03-29T15:14:34.401080+0000", + "user_ip": "74.195.108.29", + "type": "blueprint.facade.POST./virtual-networks", + "id": "d20a8a56-d312-4df7-a8ec-271d8e2325d1" } ] } \ No newline at end of file diff --git a/tests/fixtures/aos/4.0.0/blueprints/get_bp_tasks_complete.json b/tests/fixtures/aos/4.0.0/blueprints/get_bp_tasks_complete.json index c094b8b..a2be53e 100644 --- a/tests/fixtures/aos/4.0.0/blueprints/get_bp_tasks_complete.json +++ b/tests/fixtures/aos/4.0.0/blueprints/get_bp_tasks_complete.json @@ -47,6 +47,22 @@ "user_ip": "74.195.108.29", "type": "blueprint.facade.DELETE./security-zones/", "id": "4d167bd3-06d5-490d-9f85-4c742b9196e1" + }, + { + "status": "succeeded", + "begin_at": "2022-03-29T15:14:34.407180+0000", + "request_data": { + "url": "http://34.214.213.43:27809/api/blueprints/1347ebbd-8576-4223-a24b-f3e5c65d1124/virtual-networks?async=full", + "method": "POST" + }, + "user_id": "d3b4c28b-3e57-4638-ae21-7eb217b437c6", + "last_updated_at": "2022-03-29T15:14:34.725654+0000", + "user_name": "admin", + "created_at": "2022-03-29T15:14:34.401080+0000", + "config_last_updated_at": "2022-03-29T15:14:34.401080+0000", + "user_ip": "74.195.108.29", + "type": "blueprint.facade.POST./virtual-networks", + "id": "d20a8a56-d312-4df7-a8ec-271d8e2325d1" } ] } \ No newline at end of file diff --git a/tests/test_blueprint.py b/tests/test_blueprint.py index ea4aa03..93e3130 100644 --- a/tests/test_blueprint.py +++ b/tests/test_blueprint.py @@ -778,11 +778,13 @@ def test_create_virtual_network( aos_logged_in, aos_session, expected_auth_headers, aos_api_version ): vn_fixture = f"aos/{aos_api_version}/blueprints/get_virtual_network_id.json" + all_fixture = f"aos/{aos_api_version}/blueprints/get_virtual_networks.json" sz_all_fixture = f"aos/{aos_api_version}/blueprints/get_security_zones.json" + task_id_fixture = f"aos/{aos_api_version}/blueprints/get_bp_task_id.json" + task_id = "d20a8a56-d312-4df7-a8ec-271d8e2325d1" bp_id = "evpn-cvx-virtual" sz_name = "blue" - sz_id = "78eff7d7-e936-4e6e-a9f7-079b9aa45f98" - vn_id = "307944e0-8aa5-4108-9253-0c453a653bde" + vn_name = "test-blue15" aos_session.add_response( "GET", @@ -794,73 +796,53 @@ def test_create_virtual_network( "POST", f"http://aos:80/api/blueprints/{bp_id}/virtual-networks", status=202, - params=None, - resp=json.dumps({"id": vn_id}), + params={"async": "full"}, + resp=json.dumps({"task_id": task_id}), ) aos_session.add_response( "GET", - f"http://aos:80/api/blueprints/{bp_id}/virtual-networks/{vn_id}", + f"http://aos:80/api/blueprints/{bp_id}/tasks/{task_id}", status=200, - resp=read_fixture(vn_fixture), + resp=read_fixture(task_id_fixture), ) - aos_logged_in.blueprint.create_virtual_network( + aos_session.add_response( + "GET", + f"http://aos:80/api/blueprints/{bp_id}/virtual-networks", + status=200, + resp=read_fixture(all_fixture), + ) + bound_to = deserialize_fixture(vn_fixture)["bound_to"] + resp = aos_logged_in.blueprint.create_virtual_network( bp_id=bp_id, - name="blue-test1", - bound_to=mock.ANY, + name=vn_name, + bound_to=bound_to, sz_name=sz_name, ) - bound_to = deserialize_fixture(vn_fixture)["bound_to"] - expected_body = { - "label": "blue-test1", - "security_zone_id": sz_id, - "vn_type": "vxlan", - "vn_id": None, - "bound_to": bound_to, - "ipv4_enabled": True, - "dhcp_service": "dhcpServiceEnabled", - "ipv4_subnet": None, - "ipv4_gateway": None, - } + vn_dict = deserialize_fixture(vn_fixture) - aos_session.request.assert_has_calls( - [ - call( - "POST", - "http://aos:80/api/aaa/login", - json=mock.ANY, - params=None, - headers=mock.ANY, - ), - call( - "GET", - f"http://aos:80/api/blueprints/{bp_id}/security-zones", - params=None, - json=None, - headers=mock.ANY, - ), - call( - "POST", - f"http://aos:80/api/blueprints/{bp_id}/virtual-networks", - params=None, - json=expected_body, - headers=mock.ANY, - ), - call( - "GET", - f"http://aos:80/api/blueprints/{bp_id}/virtual-networks/{vn_id}", - params=None, - json=None, - headers=mock.ANY, - ), - call( - "GET", - f"http://aos:80/api/blueprints/{bp_id}/virtual-networks/{vn_id}", - params=None, - json=None, - headers=mock.ANY, - ), - ] + assert resp == VirtualNetwork( + label=vn_name, + id=vn_dict["id"], + description=vn_dict["description"], + ipv4_enabled=vn_dict["ipv4_enabled"], + ipv4_subnet=vn_dict["ipv4_subnet"], + virtual_gateway_ipv4=vn_dict["virtual_gateway_ipv4"], + ipv6_enabled=vn_dict["ipv6_enabled"], + ipv6_subnet=vn_dict["ipv6_subnet"], + virtual_gateway_ipv6=vn_dict["virtual_gateway_ipv6"], + vn_id=vn_dict["vn_id"], + security_zone_id=vn_dict["security_zone_id"], + svi_ips=mock.ANY, + virtual_mac=vn_dict["virtual_mac"], + default_endpoint_tag_types={}, + endpoints=mock.ANY, + bound_to=vn_dict["bound_to"], + vn_type=vn_dict["vn_type"], + rt_policy=vn_dict["rt_policy"], + dhcp_service=vn_dict["dhcp_service"], + tagged_ct=False, + untagged_ct=False, ) diff --git a/tests/test_utils.py b/tests/test_utils.py index 0558de4..0321347 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,3 @@ - # Copyright 2020-present, Apstra, Inc. All rights reserved. # # This source code is licensed under End User License Agreement found in the @@ -13,16 +12,16 @@ def test_redacted_null(): def test_redacted_empty(): - assert redacted('') == '' + assert redacted("") == "" def test_redacted_sensitive(): for sensitive in ["password", "token", "AuthToken"]: d = { - 'usual': 'usual data', - sensitive: '{sensitive} data', + "usual": "usual data", + sensitive: "{sensitive} data", } assert redacted(d) == { - 'usual': 'usual data', - sensitive: '', - } + "usual": "usual data", + sensitive: "", + }