Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,3 @@ black aos/
black tests/
```


25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Please follow the [CONTRIBUTING](CONTRIBUTING.md) instructions to get
started.
21 changes: 14 additions & 7 deletions aos/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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]:
"""
Expand Down
2 changes: 1 addition & 1 deletion aos/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion aos/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


def redacted(d):
if d is None or d == '':
if d is None or d == "":
return d

h = d.copy()
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

NAME = "aos-api-client"

VERSION = '0.1.16'
VERSION = '0.1.17'


REQUIRES = (["requests==2.24.0"],)
Expand All @@ -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"]),
Expand Down
16 changes: 16 additions & 0 deletions tests/fixtures/aos/3.3.0/blueprints/get_bp_tasks_complete.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@
"user_ip": "74.195.108.29",
"type": "blueprint.facade.DELETE./security-zones/<security_zone_id>",
"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"
}
]
}
16 changes: 16 additions & 0 deletions tests/fixtures/aos/4.0.0/blueprints/get_bp_tasks_complete.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@
"user_ip": "74.195.108.29",
"type": "blueprint.facade.DELETE./security-zones/<security_zone_id>",
"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"
}
]
}
100 changes: 41 additions & 59 deletions tests/test_blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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,
)


Expand Down
13 changes: 6 additions & 7 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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: '<REDACTED>',
}
"usual": "usual data",
sensitive: "<REDACTED>",
}