From 62ef067ae25f23a41a34eb0556572dcd8caed6b3 Mon Sep 17 00:00:00 2001 From: Adam Kirchberger <24639394+adamkirchberger@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:18:05 +0000 Subject: [PATCH 1/4] feat: discovered hosts returned as dict (#25) * feat: discovered hosts returned as dict --- nectl/configs/cli.py | 10 +++--- nectl/datatree/cli.py | 8 +++-- nectl/datatree/hosts.py | 22 ++++++------- nectl/nectl.py | 8 +++-- tests/integration/test_hosts_discovery.py | 40 +++++++++++++++-------- 5 files changed, 53 insertions(+), 35 deletions(-) diff --git a/nectl/configs/cli.py b/nectl/configs/cli.py index c781909..f31aea4 100644 --- a/nectl/configs/cli.py +++ b/nectl/configs/cli.py @@ -60,7 +60,7 @@ def render_cmd( role=role, deployment_group=deployment_group, ) - nectl.render_configs(hosts=hosts) + nectl.render_configs(hosts=hosts.values()) except (DiscoveryError, RenderError) as e: print(f"Error: {e}") sys.exit(1) @@ -103,7 +103,7 @@ def diff_cmd( deployment_group=deployment_group, ) nectl.diff_configs( - hosts=hosts, + hosts=hosts.values(), username=username, password=password, ssh_private_key_file=ssh_key, @@ -158,7 +158,7 @@ def apply_cmd( ) print("Applying config to:") - print("\n".join([f"- {host.id}" for host in hosts])) + print("\n".join([f"- {host}" for host in hosts.keys()])) if not assumeyes: click.confirm( @@ -167,7 +167,7 @@ def apply_cmd( ) nectl.apply_configs( - hosts=hosts, + hosts=hosts.values(), username=username, password=password, ssh_private_key_file=ssh_key, @@ -214,7 +214,7 @@ def get_cmd( deployment_group=deployment_group, ) nectl.get_configs( - hosts=hosts, + hosts=hosts.values(), username=username, password=password, ssh_private_key_file=ssh_key, diff --git a/nectl/datatree/cli.py b/nectl/datatree/cli.py index 0adfe16..55f0352 100644 --- a/nectl/datatree/cli.py +++ b/nectl/datatree/cli.py @@ -74,11 +74,13 @@ def list_hosts_cmd( sys.exit(1) if output == "json": - print(json.dumps({h.id: h.dict() for h in hosts}, indent=4, default=str)) + print( + json.dumps({h.id: h.dict() for h in hosts.values()}, indent=4, default=str) + ) else: print( tabulate( - [h.dict() for h in hosts], + [h.dict() for h in hosts.values()], headers="keys", tablefmt="psql", ) @@ -118,7 +120,7 @@ def get_facts_cmd( print(f"Error: {e}") sys.exit(1) - host_facts = {host.id: host.facts for host in hosts} + host_facts = {host.id: host.facts for host in hosts.values()} if not check: print(facts_to_json_string(host_facts)) diff --git a/nectl/datatree/hosts.py b/nectl/datatree/hosts.py index 0776370..8446551 100644 --- a/nectl/datatree/hosts.py +++ b/nectl/datatree/hosts.py @@ -165,7 +165,7 @@ def get_filtered_hosts( site: str = None, role: str = None, deployment_group: str = None, -) -> List[Host]: +) -> Dict[str, Host]: """ Returns a list of filtered hosts @@ -178,7 +178,7 @@ def get_filtered_hosts( deployment_group (str): filter by deployment group. Returns: - List[Host]: list of discovered hosts. + Dict[str, Host]: discovered host instances mapped by host ID. Raises: DiscoveryError: if hosts cannot be successfully discovered. @@ -205,18 +205,18 @@ def is_match(host: Host) -> bool: return False return True - hosts = [] + hosts = {} # Loop hosts - for host in get_all_hosts(settings=settings): + for host in get_all_hosts(settings=settings).values(): # Check for match against filters if is_match(host): # Add host - hosts.append(host) + hosts[host.id] = host logger.info( f"filter matched {len(hosts)} hosts: " - f"{','.join([host.id for host in hosts])}" + f"{','.join([host for host in hosts.keys()])}" ) if len(hosts) == 0: @@ -251,7 +251,7 @@ def _get_host_datatree_path_vars(host_path: str, datatree_dirname: str) -> dict: ] # Extract host module import path - m = re.match(re.compile(fr".*({datatree_dirname}\/.*?)(\.py)?$"), host_path) + m = re.match(re.compile(rf".*({datatree_dirname}\/.*?)(\.py)?$"), host_path) if m: try: # Import host module @@ -269,7 +269,7 @@ def _get_host_datatree_path_vars(host_path: str, datatree_dirname: str) -> dict: return {} -def get_all_hosts(settings: Settings) -> List[Host]: +def get_all_hosts(settings: Settings) -> Dict[str, Host]: """ Returns list of all discovered hosts from datatree. @@ -277,12 +277,12 @@ def get_all_hosts(settings: Settings) -> List[Host]: settings (Settings): config settings. Returns: - List[Host]: list of discovered hosts. + Dict[str, Host]: discovered host instances mapped by host ID. Raises: DiscoveryError: if hosts cannot be successfully discovered. """ - hosts: List[Host] = [] + hosts: Dict[str, Host] = {} hostname: str = "" site: Optional[str] = None customer: Optional[str] = None @@ -359,7 +359,7 @@ def get_all_hosts(settings: Settings) -> List[Host]: **host_vars, ) logger.debug(f"found host '{new_host.id}' in: {host_dir}") - hosts.append(new_host) + hosts[new_host.id] = new_host dur = f"{time.perf_counter()-ts_start:0.4f}" logger.info(f"finished discovery of {len(hosts)} hosts ({dur}s)") diff --git a/nectl/nectl.py b/nectl/nectl.py index 6090fc0..a523649 100644 --- a/nectl/nectl.py +++ b/nectl/nectl.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Nectl. If not, see . -from typing import Optional, List +from typing import Optional, List, Dict from .logging import get_logger from .settings import load_settings, Settings @@ -38,7 +38,6 @@ def __init__( """ self.settings = settings if settings else load_settings(filepath=kit_filepath) - # TODO: return dict of hosts instead def get_hosts( self, hostname: Optional[str] = None, @@ -46,7 +45,7 @@ def get_hosts( site: Optional[str] = None, role: Optional[str] = None, deployment_group: Optional[str] = None, - ) -> List[Host]: + ) -> Dict[str, Host]: """ Get hosts from datatree that match supplied filter parameters. @@ -57,6 +56,9 @@ def get_hosts( role (str): optional role to filter by. deployment_group (str): optional deployment_group to filter by. + Returns: + Dict[str, Host]: discovered host instances mapped by host ID. + Raises: DiscoveryError: when an error has been encountered during data tree discovery. """ diff --git a/tests/integration/test_hosts_discovery.py b/tests/integration/test_hosts_discovery.py index 4660c53..93751ab 100644 --- a/tests/integration/test_hosts_discovery.py +++ b/tests/integration/test_hosts_discovery.py @@ -30,10 +30,13 @@ def test_should_return_8_hosts_when_getting_all_hosts(mock_settings): # WHEN fetching all hosts hosts = get_all_hosts(settings=settings) - # THEN expect each host to be of Host type - for host in hosts: + for host_id, host in hosts.items(): + # THEN expect each host to be of Host type assert isinstance(host, Host), host + # THEN expect host ID key + assert host_id == host.id + # THEN expect to have 8 hosts assert len(hosts) == 8 @@ -99,7 +102,7 @@ def test_should_return_no_customer_when_when_getting_all_hosts_and_no_customer_r settings.hosts_customer_regex = None # WHEN fetching all hosts - hosts = get_all_hosts(settings=settings) + hosts = list(get_all_hosts(settings=settings).values()) # THEN expect site to be set assert hosts[0].site is not None @@ -119,7 +122,7 @@ def test_should_return_no_customer_and_site_when_when_getting_all_hosts_and_no_c settings.hosts_site_regex = None # WHEN fetching all hosts - hosts = get_all_hosts(settings=settings) + hosts = list(get_all_hosts(settings=settings).values()) # THEN expect customer to be none assert hosts[0].customer is None @@ -128,7 +131,7 @@ def test_should_return_no_customer_and_site_when_when_getting_all_hosts_and_no_c assert hosts[0].customer is None -def test_should_return_empty_list_when_getting_filtered_hosts_that_dont_exist( +def test_should_return_empty_dict_when_getting_filtered_hosts_that_dont_exist( mock_settings, ): # GIVEN settings using mock kit @@ -143,8 +146,8 @@ def test_should_return_empty_list_when_getting_filtered_hosts_that_dont_exist( # WHEN fetching hosts and using filters hosts = get_filtered_hosts(settings=settings, site=site, customer=customer) - # THEN expect result to be empty list - assert hosts == [] + # THEN expect result to be empty dict + assert hosts == {} def test_should_return_2_hosts_when_getting_filtered_hosts_by_site_and_customer( @@ -165,10 +168,13 @@ def test_should_return_2_hosts_when_getting_filtered_hosts_by_site_and_customer( # THEN expect to have 2 hosts assert len(hosts) == 2 - for host in hosts: + for host_id, host in hosts.items(): # THEN expect each result to be of Host type assert isinstance(host, Host), host + # THEN expect host ID key + assert host_id == host.id + # THEN expect host customer assert host.customer == customer @@ -194,10 +200,13 @@ def test_should_return_2_hosts_when_getting_filtered_hosts_by_site_and_role( # THEN expect to have 2 hosts assert len(hosts) == 2 - for host in hosts: + for host_id, host in hosts.items(): # THEN expect each result to be of Host type assert isinstance(host, Host), host + # THEN expect host ID key + assert host_id == host.id + # THEN expect host role assert host.role == role @@ -225,10 +234,13 @@ def test_should_return_2_hosts_when_getting_filtered_hosts_by_customer_and_deplo # THEN expect to have 2 hosts assert len(hosts) == 2 - for host in hosts: + for host_id, host in hosts.items(): # THEN expect each result to be of Host type assert isinstance(host, Host), host + # THEN expect host ID key + assert host_id == host.id + # THEN expect host customer assert host.customer == customer @@ -252,8 +264,10 @@ def test_should_return_host_properties_when_getting_filtered_hosts_by_site_and_c hostname = "core0" # WHEN fetching hosts and using filters - hosts = get_filtered_hosts( - settings=settings, site=site, customer=customer, hostname=hostname + hosts = list( + get_filtered_hosts( + settings=settings, site=site, customer=customer, hostname=hostname + ).values() ) # THEN expect one host @@ -299,7 +313,7 @@ def test_should_return_hosts_when_getting_all_hosts_that_are_not_directories(tmp ) # WHEN fetching all hosts - hosts = get_all_hosts(settings=settings) + hosts = list(get_all_hosts(settings=settings).values()) # THEN expect total hosts assert len(hosts) == 1 From 7f541e529d61a9b801b050511ec075b6f84ba581 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 30 Oct 2023 11:19:25 +0000 Subject: [PATCH 2/4] Automatically generated by python-semantic-release --- CHANGELOG.md | 9 +++++++++ pyproject.toml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e927724..06d3434 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ +## v0.18.0 (2023-10-30) + +### Feature + +* feat: discovered hosts returned as dict (#25) + +* feat: discovered hosts returned as dict ([`62ef067`](https://github.com/adamkirchberger/nectl/commit/62ef067ae25f23a41a34eb0556572dcd8caed6b3)) + + ## v0.17.1 (2023-10-24) ### Fix diff --git a/pyproject.toml b/pyproject.toml index 23d8e5d..12b0935 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nectl" -version = "0.17.1" +version = "0.18.0" description = "An end-to-end Python-based Infrastructure as Code framework for network automation and orchestration." authors = ["Adam Kirchberger "] readme = "README.md" From 8c6d0b6231bfaf19a41807627c1e0537a2d540ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 12:08:14 +0000 Subject: [PATCH 3/4] fix(deps): bump cryptography from 41.0.3 to 41.0.4 (#20) Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.3 to 41.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.3...41.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Adam Kirchberger <24639394+adamkirchberger@users.noreply.github.com> From 2ba588c684ae3c72080f49820f5b753fc4d1643b Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 30 Oct 2023 12:09:40 +0000 Subject: [PATCH 4/4] Automatically generated by python-semantic-release --- CHANGELOG.md | 21 +++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06d3434..b63a3b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ +## v0.18.1 (2023-10-30) + +### Fix + +* fix(deps): bump cryptography from 41.0.3 to 41.0.4 (#20) + +Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.3 to 41.0.4. +- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) +- [Commits](https://github.com/pyca/cryptography/compare/41.0.3...41.0.4) + +--- +updated-dependencies: +- dependency-name: cryptography + dependency-type: indirect +... + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> +Co-authored-by: Adam Kirchberger <24639394+adamkirchberger@users.noreply.github.com> ([`8c6d0b6`](https://github.com/adamkirchberger/nectl/commit/8c6d0b6231bfaf19a41807627c1e0537a2d540ab)) + + ## v0.18.0 (2023-10-30) ### Feature diff --git a/pyproject.toml b/pyproject.toml index 12b0935..30110f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nectl" -version = "0.18.0" +version = "0.18.1" description = "An end-to-end Python-based Infrastructure as Code framework for network automation and orchestration." authors = ["Adam Kirchberger "] readme = "README.md"