From eef76b0e4c7ae9a3c1585337de7d3137582b2b34 Mon Sep 17 00:00:00 2001 From: ryanbooth Date: Wed, 6 Apr 2022 08:19:01 -0700 Subject: [PATCH 1/4] build and push package to pypi --- .github/workflows/publish-packages.yml | 30 ++++++++++++++++++++++++++ setup.py | 8 +++---- 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/publish-packages.yml diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 0000000..7aa957d --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,30 @@ +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: '3.8' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -r dev-requirements.txt + - name: Build python3 package + run: python setup.py bdist_wheel + - name: Publish package to PyPi + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/setup.py b/setup.py index 3d1600a..b83b73e 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ import os from setuptools import setup, find_packages -NAME = "aos-api-client" +NAME = "apstra-api-client" VERSION = '0.1.16' @@ -17,8 +17,8 @@ setup( name=NAME, version=VERSION, - description="Apstra AOS API Client", - url="https://github.com/Apstra/aos-api-python", + description="Apstra API Client", + url="https://github.com/Apstra/apstra-api-python", author="Apstra Inc", author_email="support@apstra.com", packages=find_packages(include=["aos"]), @@ -26,5 +26,5 @@ python_requires=">=3.6", install_requires=REQUIRES, license="Proprietary", - keywords="aos apstra", + keywords="apstra", ) From cedc02ba448d67ee09579f1a047b97a8f4f8c8ec Mon Sep 17 00:00:00 2001 From: ryanbooth Date: Tue, 12 Apr 2022 10:15:27 -0500 Subject: [PATCH 2/4] package build with pypi and version/tag bump --- CONTRIBUTING.md | 2 ++ setup.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1482982..e0f0beb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,8 @@ Please report bugs as issues here on GitLab! Submit your code as a new branch based off `main` and submit a Pull Request (PR). All tests and approvals of a PR must be meet before merge. +Dont forget to update the version number in [setup.py](./setup.py) + ### Developers ### Setup Development Environment Create a virtual environment and install apstra-api-python: diff --git a/setup.py b/setup.py index b83b73e..bd09060 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ NAME = "apstra-api-client" -VERSION = '0.1.16' +VERSION = '0.2.0' REQUIRES = (["requests==2.24.0"],) From f3a7674f99524cd374a5a7b51e918c582d164512 Mon Sep 17 00:00:00 2001 From: ryanbooth Date: Wed, 13 Apr 2022 15:44:57 -0500 Subject: [PATCH 3/4] package build with pypi and version/tag bump --- CONTRIBUTING.md | 14 ++- README.md | 25 +++-- aos/blueprint.py | 21 ++-- aos/resources.py | 2 +- aos/utils.py | 2 +- .../blueprints/get_bp_tasks_complete.json | 16 +++ .../blueprints/get_bp_tasks_complete.json | 16 +++ tests/test_blueprint.py | 100 +++++++----------- tests/test_utils.py | 13 ++- 9 files changed, 126 insertions(+), 83 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e0f0beb..ba60e1c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,8 @@ Please report bugs as issues here on GitLab! Submit your code as a new branch based off `main` and submit a Pull Request (PR). All tests and approvals of a PR must be meet before merge. -Dont forget to update the version number in [setup.py](./setup.py) +Don't forget to update the version number in [setup.py](./setup.py) +following [semantic versioning](https://semver.org/) best practices. ### Developers ### Setup Development Environment @@ -41,4 +42,15 @@ black aos/ black tests/ ``` +## Building Releases +This function is currently handled by the project admin team. GitHub +releases is currently used for release management. Git tags are used to +manage release version numbers. +### steps +1. `git tag v0.x.y` +2. `git push --tags` +3. From Github validate the new tag is present with the correct commit. +4. From Github create a new release based on the new tag. +5. Github actions will then pick up the new release and trigger a release +build and publish to pypi using the tagged version number 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/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: "", + } From 7e394cfbc30d1c7e31685b3d0944718d989bf4e9 Mon Sep 17 00:00:00 2001 From: ryanbooth Date: Thu, 14 Apr 2022 10:14:29 -0500 Subject: [PATCH 4/4] Cleanup for merge --- .github/workflows/publish-packages.yml | 30 -------------------------- CONTRIBUTING.md | 15 ------------- setup.py | 8 +++---- 3 files changed, 4 insertions(+), 49 deletions(-) delete mode 100644 .github/workflows/publish-packages.yml diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml deleted file mode 100644 index 7aa957d..0000000 --- a/.github/workflows/publish-packages.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Upload Python Package - -on: - release: - types: [published] - -permissions: - contents: read - -jobs: - deploy: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: '3.8' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -r dev-requirements.txt - - name: Build python3 package - run: python setup.py bdist_wheel - - name: Publish package to PyPi - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba60e1c..521fc9e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,9 +8,6 @@ Please report bugs as issues here on GitLab! Submit your code as a new branch based off `main` and submit a Pull Request (PR). All tests and approvals of a PR must be meet before merge. -Don't forget to update the version number in [setup.py](./setup.py) -following [semantic versioning](https://semver.org/) best practices. - ### Developers ### Setup Development Environment Create a virtual environment and install apstra-api-python: @@ -42,15 +39,3 @@ black aos/ black tests/ ``` -## Building Releases -This function is currently handled by the project admin team. GitHub -releases is currently used for release management. Git tags are used to -manage release version numbers. -### steps -1. `git tag v0.x.y` -2. `git push --tags` -3. From Github validate the new tag is present with the correct commit. -4. From Github create a new release based on the new tag. -5. Github actions will then pick up the new release and trigger a release -build and publish to pypi using the tagged version number - diff --git a/setup.py b/setup.py index bd09060..3af3c7d 100644 --- a/setup.py +++ b/setup.py @@ -7,9 +7,9 @@ import os from setuptools import setup, find_packages -NAME = "apstra-api-client" +NAME = "aos-api-client" -VERSION = '0.2.0' +VERSION = '0.1.17' REQUIRES = (["requests==2.24.0"],) @@ -17,7 +17,7 @@ setup( name=NAME, version=VERSION, - description="Apstra API Client", + description="Apstra AOS API Client", url="https://github.com/Apstra/apstra-api-python", author="Apstra Inc", author_email="support@apstra.com", @@ -26,5 +26,5 @@ python_requires=">=3.6", install_requires=REQUIRES, license="Proprietary", - keywords="apstra", + keywords="aos apstra", )