From 500cf072e23d9b339aa85d0e7989775422834bb3 Mon Sep 17 00:00:00 2001 From: Micah Parks <66095735+MicahParks@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:32:16 -0400 Subject: [PATCH 01/10] Allow HTTP and HTTPS schemes and attach default port if necessary --- netboxlabs/diode/sdk/client.py | 73 ++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/netboxlabs/diode/sdk/client.py b/netboxlabs/diode/sdk/client.py index 85f45b8..6932f36 100644 --- a/netboxlabs/diode/sdk/client.py +++ b/netboxlabs/diode/sdk/client.py @@ -63,15 +63,18 @@ def parse_target(target: str) -> tuple[str, str, bool]: """Parse the target into authority, path and tls_verify.""" parsed_target = urlparse(target) - if parsed_target.scheme not in ["grpc", "grpcs"]: + if parsed_target.scheme not in ["grpc", "grpcs", "http", "https"]: raise ValueError("target should start with grpc:// or grpcs://") - tls_verify = parsed_target.scheme == "grpcs" + tls_verify = parsed_target.scheme in ["grpcs", "https"] authority = parsed_target.netloc if ":" not in authority: - authority += ":443" + if parsed_target.scheme in ["grpc", "http"]: + authority += ":80" + elif parsed_target.scheme in ["grpcs", "https"]: + authority += ":443" return authority, parsed_target.path, tls_verify @@ -95,7 +98,7 @@ def _get_required_config_value(env_var_name: str, value: str | None = None) -> s def _get_optional_config_value( - env_var_name: str, value: str | None = None + env_var_name: str, value: str | None = None ) -> str | None: """Get optional config value either from provided value or environment variable.""" if value is None: @@ -114,16 +117,16 @@ class DiodeClient(DiodeClientInterface): _stub = None def __init__( - self, - target: str, - app_name: str, - app_version: str, - client_id: str | None = None, - client_secret: str | None = None, - sentry_dsn: str = None, - sentry_traces_sample_rate: float = 1.0, - sentry_profiles_sample_rate: float = 1.0, - max_auth_retries: int = 3, + self, + target: str, + app_name: str, + app_version: str, + client_id: str | None = None, + client_secret: str | None = None, + sentry_dsn: str = None, + sentry_traces_sample_rate: float = 1.0, + sentry_profiles_sample_rate: float = 1.0, + max_auth_retries: int = 3, ): """Initiate a new client.""" log_level = os.getenv(_DIODE_SDK_LOG_LEVEL_ENVVAR_NAME, "INFO").upper() @@ -248,9 +251,9 @@ def close(self): self._channel.close() def ingest( - self, - entities: Iterable[Entity | ingester_pb2.Entity | None], - stream: str | None = _DEFAULT_STREAM, + self, + entities: Iterable[Entity | ingester_pb2.Entity | None], + stream: str | None = _DEFAULT_STREAM, ) -> ingester_pb2.IngestResponse: """Ingest entities.""" for attempt in range(self._max_auth_retries): @@ -277,7 +280,7 @@ def ingest( raise RuntimeError("Max retries exceeded") def _setup_sentry( - self, dsn: str, traces_sample_rate: float, profiles_sample_rate: float + self, dsn: str, traces_sample_rate: float, profiles_sample_rate: float ): sentry_sdk.init( dsn=dsn, @@ -349,9 +352,9 @@ def __exit__(self, exc_type, exc_value, exc_traceback): """Exits the runtime context related to the channel object.""" def ingest( - self, - entities: Iterable[Entity | ingester_pb2.Entity | None], - stream: str | None = _DEFAULT_STREAM, + self, + entities: Iterable[Entity | ingester_pb2.Entity | None], + stream: str | None = _DEFAULT_STREAM, ) -> ingester_pb2.IngestResponse: """Ingest entities in dry run mode.""" request = ingester_pb2.IngestRequest( @@ -381,13 +384,13 @@ def ingest( class _DiodeAuthentication: def __init__( - self, - target: str, - path: str, - tls_verify: bool, - client_id: str, - client_secret: str, - scope: str, + self, + target: str, + path: str, + tls_verify: bool, + client_id: str, + client_secret: str, + scope: str, ): self._target = target self._tls_verify = tls_verify @@ -445,12 +448,12 @@ class _ClientCallDetails( collections.namedtuple( "_ClientCallDetails", ( - "method", - "timeout", - "metadata", - "credentials", - "wait_for_ready", - "compression", + "method", + "timeout", + "metadata", + "credentials", + "wait_for_ready", + "compression", ), ), grpc.ClientCallDetails, @@ -505,7 +508,7 @@ def intercept_unary_unary(self, continuation, client_call_details, request): return self._intercept_call(continuation, client_call_details, request) def intercept_stream_unary( - self, continuation, client_call_details, request_iterator + self, continuation, client_call_details, request_iterator ): """Intercept stream unary.""" return self._intercept_call(continuation, client_call_details, request_iterator) From 242d8cc8cd28320ad7d362ce85e9503dbad279b0 Mon Sep 17 00:00:00 2001 From: Micah Parks <66095735+MicahParks@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:05:57 -0400 Subject: [PATCH 02/10] Update target mention and error string --- README.md | 5 +++-- netboxlabs/diode/sdk/client.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4d476dc..41e69eb 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,9 @@ pip install netboxlabs-diode-sdk ### Example -* `target` should be the address of the Diode service, e.g. `grpc://localhost:8080/diode` for insecure connection - or `grpcs://example.com` for secure connection. +* `target` should be the address of the Diode service. + * Insecure connections: `grpc://localhost:8080/diode` or `http://localhost:8080/diode` + * Secure connections: `grpcs://example.com` or `https://example.com` ```python from netboxlabs.diode.sdk import DiodeClient diff --git a/netboxlabs/diode/sdk/client.py b/netboxlabs/diode/sdk/client.py index 6932f36..775891a 100644 --- a/netboxlabs/diode/sdk/client.py +++ b/netboxlabs/diode/sdk/client.py @@ -64,7 +64,7 @@ def parse_target(target: str) -> tuple[str, str, bool]: parsed_target = urlparse(target) if parsed_target.scheme not in ["grpc", "grpcs", "http", "https"]: - raise ValueError("target should start with grpc:// or grpcs://") + raise ValueError("target should start with grpc:// or grpcs:// or http:// or https://") tls_verify = parsed_target.scheme in ["grpcs", "https"] From efcf8e009cfa97e2cfd0b5f898b3223bf5536d48 Mon Sep 17 00:00:00 2001 From: Micah Parks <66095735+MicahParks@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:16:14 -0400 Subject: [PATCH 03/10] Undo auto-format --- netboxlabs/diode/sdk/client.py | 56 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/netboxlabs/diode/sdk/client.py b/netboxlabs/diode/sdk/client.py index 775891a..7da0a28 100644 --- a/netboxlabs/diode/sdk/client.py +++ b/netboxlabs/diode/sdk/client.py @@ -98,7 +98,7 @@ def _get_required_config_value(env_var_name: str, value: str | None = None) -> s def _get_optional_config_value( - env_var_name: str, value: str | None = None + env_var_name: str, value: str | None = None ) -> str | None: """Get optional config value either from provided value or environment variable.""" if value is None: @@ -117,16 +117,16 @@ class DiodeClient(DiodeClientInterface): _stub = None def __init__( - self, - target: str, - app_name: str, - app_version: str, - client_id: str | None = None, - client_secret: str | None = None, - sentry_dsn: str = None, - sentry_traces_sample_rate: float = 1.0, - sentry_profiles_sample_rate: float = 1.0, - max_auth_retries: int = 3, + self, + target: str, + app_name: str, + app_version: str, + client_id: str | None = None, + client_secret: str | None = None, + sentry_dsn: str = None, + sentry_traces_sample_rate: float = 1.0, + sentry_profiles_sample_rate: float = 1.0, + max_auth_retries: int = 3, ): """Initiate a new client.""" log_level = os.getenv(_DIODE_SDK_LOG_LEVEL_ENVVAR_NAME, "INFO").upper() @@ -251,9 +251,9 @@ def close(self): self._channel.close() def ingest( - self, - entities: Iterable[Entity | ingester_pb2.Entity | None], - stream: str | None = _DEFAULT_STREAM, + self, + entities: Iterable[Entity | ingester_pb2.Entity | None], + stream: str | None = _DEFAULT_STREAM, ) -> ingester_pb2.IngestResponse: """Ingest entities.""" for attempt in range(self._max_auth_retries): @@ -384,13 +384,13 @@ def ingest( class _DiodeAuthentication: def __init__( - self, - target: str, - path: str, - tls_verify: bool, - client_id: str, - client_secret: str, - scope: str, + self, + target: str, + path: str, + tls_verify: bool, + client_id: str, + client_secret: str, + scope: str, ): self._target = target self._tls_verify = tls_verify @@ -448,12 +448,12 @@ class _ClientCallDetails( collections.namedtuple( "_ClientCallDetails", ( - "method", - "timeout", - "metadata", - "credentials", - "wait_for_ready", - "compression", + "method", + "timeout", + "metadata", + "credentials", + "wait_for_ready", + "compression", ), ), grpc.ClientCallDetails, @@ -508,7 +508,7 @@ def intercept_unary_unary(self, continuation, client_call_details, request): return self._intercept_call(continuation, client_call_details, request) def intercept_stream_unary( - self, continuation, client_call_details, request_iterator + self, continuation, client_call_details, request_iterator ): """Intercept stream unary.""" return self._intercept_call(continuation, client_call_details, request_iterator) From 05fdc8a2649f3308ea691659c2e40cdcc4e5596d Mon Sep 17 00:00:00 2001 From: Micah Parks <66095735+MicahParks@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:16:55 -0400 Subject: [PATCH 04/10] Undo auto-format --- netboxlabs/diode/sdk/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netboxlabs/diode/sdk/client.py b/netboxlabs/diode/sdk/client.py index 7da0a28..0b55fc3 100644 --- a/netboxlabs/diode/sdk/client.py +++ b/netboxlabs/diode/sdk/client.py @@ -195,7 +195,7 @@ def __init__( if self._sentry_dsn is not None: _LOGGER.debug("Setting up Sentry") self._setup_sentry( - self._sentry_dsn, sentry_traces_sample_rate, sentry_profiles_sample_rate + self._sentry_dsn, sentry_traces_sample_rate, sentry_profiles_sample_rate ) @property From 77f989989f65761454e59790fd308dc78ccfdda6 Mon Sep 17 00:00:00 2001 From: Micah Parks <66095735+MicahParks@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:17:20 -0400 Subject: [PATCH 05/10] Undo auto-format --- netboxlabs/diode/sdk/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netboxlabs/diode/sdk/client.py b/netboxlabs/diode/sdk/client.py index 0b55fc3..c006174 100644 --- a/netboxlabs/diode/sdk/client.py +++ b/netboxlabs/diode/sdk/client.py @@ -352,9 +352,9 @@ def __exit__(self, exc_type, exc_value, exc_traceback): """Exits the runtime context related to the channel object.""" def ingest( - self, - entities: Iterable[Entity | ingester_pb2.Entity | None], - stream: str | None = _DEFAULT_STREAM, + self, + entities: Iterable[Entity | ingester_pb2.Entity | None], + stream: str | None = _DEFAULT_STREAM, ) -> ingester_pb2.IngestResponse: """Ingest entities in dry run mode.""" request = ingester_pb2.IngestRequest( From ec7ac1b651ab23246b01443bcd2c4f9fb543c566 Mon Sep 17 00:00:00 2001 From: Micah Parks <66095735+MicahParks@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:18:03 -0400 Subject: [PATCH 06/10] Undo auto-format --- netboxlabs/diode/sdk/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netboxlabs/diode/sdk/client.py b/netboxlabs/diode/sdk/client.py index c006174..0bdc423 100644 --- a/netboxlabs/diode/sdk/client.py +++ b/netboxlabs/diode/sdk/client.py @@ -195,7 +195,7 @@ def __init__( if self._sentry_dsn is not None: _LOGGER.debug("Setting up Sentry") self._setup_sentry( - self._sentry_dsn, sentry_traces_sample_rate, sentry_profiles_sample_rate + self._sentry_dsn, sentry_traces_sample_rate, sentry_profiles_sample_rate ) @property @@ -280,7 +280,7 @@ def ingest( raise RuntimeError("Max retries exceeded") def _setup_sentry( - self, dsn: str, traces_sample_rate: float, profiles_sample_rate: float + self, dsn: str, traces_sample_rate: float, profiles_sample_rate: float ): sentry_sdk.init( dsn=dsn, From df76378fccdc7a8e7d09cbdbd2c05ba6ca6c6d0a Mon Sep 17 00:00:00 2001 From: Micah Parks <66095735+MicahParks@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:32:44 -0400 Subject: [PATCH 07/10] Edit default port test --- tests/test_client.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index d2178d4..af8930c 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -103,19 +103,6 @@ def test_load_certs_returns_bytes(): """Check that _load_certs returns bytes.""" assert isinstance(_load_certs(), bytes) - -def test_parse_target_handles_http_prefix(): - """Check that parse_target raises an error when the target contains http://.""" - with pytest.raises(ValueError): - parse_target("http://localhost:8081") - - -def test_parse_target_handles_https_prefix(): - """Check that parse_target raises an error when the target contains https://.""" - with pytest.raises(ValueError): - parse_target("https://localhost:8081") - - def test_parse_target_parses_authority_correctly(): """Check that parse_target parses the authority correctly.""" authority, path, tls_verify = parse_target("grpc://localhost:8081") @@ -127,6 +114,12 @@ def test_parse_target_parses_authority_correctly(): def test_parse_target_adds_default_port_if_missing(): """Check that parse_target adds the default port if missing.""" authority, _, _ = parse_target("grpc://localhost") + assert authority == "localhost:80" + authority, _, _ = parse_target("http://localhost") + assert authority == "localhost:80" + authority, _, _ = parse_target("grpcs://localhost") + assert authority == "localhost:443" + authority, _, _ = parse_target("https://localhost") assert authority == "localhost:443" From 6685aefbcf05c17f72186bf7775d4d715c83fa38 Mon Sep 17 00:00:00 2001 From: Micah Parks <66095735+MicahParks@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:36:38 -0400 Subject: [PATCH 08/10] Add non-supported scheme test --- tests/test_client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index af8930c..987f504 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -103,6 +103,12 @@ def test_load_certs_returns_bytes(): """Check that _load_certs returns bytes.""" assert isinstance(_load_certs(), bytes) + +def test_parse_target_handles_ftp_prefix(): + """Check that parse_target raises an error when the target contains ftp://.""" + with pytest.raises(ValueError): + parse_target("ftp://localhost:8081") + def test_parse_target_parses_authority_correctly(): """Check that parse_target parses the authority correctly.""" authority, path, tls_verify = parse_target("grpc://localhost:8081") From c62264a9830752d4572a9b34f772dc2591cd3b7c Mon Sep 17 00:00:00 2001 From: Micah Parks <66095735+MicahParks@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:39:44 -0400 Subject: [PATCH 09/10] Add more tests for tls_verify --- tests/test_client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index 987f504..a265ddc 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -143,8 +143,14 @@ def test_parse_target_handles_no_path(): def test_parse_target_parses_tls_verify_correctly(): """Check that parse_target parses tls_verify correctly.""" + _, _, tls_verify = parse_target("grpc://localhost:8081") + assert tls_verify is False + _, _, tls_verify = parse_target("http://localhost:8081") + assert tls_verify is False _, _, tls_verify = parse_target("grpcs://localhost:8081") assert tls_verify is True + _, _, tls_verify = parse_target("https://localhost:8081") + assert tls_verify is True def test_get_sentry_dsn_returns_env_var_when_no_input(): From 1ef38f4a01bead52baea22adc80de6fd550d83b7 Mon Sep 17 00:00:00 2001 From: Micah Parks <66095735+MicahParks@users.noreply.github.com> Date: Mon, 11 Aug 2025 11:42:39 -0400 Subject: [PATCH 10/10] Update netboxlabs/diode/sdk/client.py Co-authored-by: Michal Fiedorowicz --- netboxlabs/diode/sdk/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netboxlabs/diode/sdk/client.py b/netboxlabs/diode/sdk/client.py index 0bdc423..045ab0d 100644 --- a/netboxlabs/diode/sdk/client.py +++ b/netboxlabs/diode/sdk/client.py @@ -64,7 +64,7 @@ def parse_target(target: str) -> tuple[str, str, bool]: parsed_target = urlparse(target) if parsed_target.scheme not in ["grpc", "grpcs", "http", "https"]: - raise ValueError("target should start with grpc:// or grpcs:// or http:// or https://") + raise ValueError("target should start with grpc://, grpcs://, http:// or https://") tls_verify = parsed_target.scheme in ["grpcs", "https"]