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"