From af3ebd5aa90529e617e88580e5f936f73a01c85e Mon Sep 17 00:00:00 2001 From: PSanetra Date: Wed, 2 Oct 2024 16:32:46 +0200 Subject: [PATCH 1/9] feat: Support standard environment variables Closes #81 --- pyzeebe/channel/insecure_channel.py | 4 +- pyzeebe/channel/oauth_channel.py | 118 +++++++--- pyzeebe/channel/secure_channel.py | 4 +- pyzeebe/channel/utils.py | 139 ++++++++++- tests/unit/channel/insecure_channel_test.py | 4 +- tests/unit/channel/secure_channel_test.py | 4 +- tests/unit/channel/utils_test.py | 246 +++++++++++++++++++- 7 files changed, 462 insertions(+), 57 deletions(-) diff --git a/pyzeebe/channel/insecure_channel.py b/pyzeebe/channel/insecure_channel.py index 846cff72..fb269193 100644 --- a/pyzeebe/channel/insecure_channel.py +++ b/pyzeebe/channel/insecure_channel.py @@ -3,7 +3,7 @@ import grpc from pyzeebe.channel.channel_options import get_channel_options -from pyzeebe.channel.utils import create_address +from pyzeebe.channel.utils import get_zeebe_address from pyzeebe.types import ChannelArgumentType @@ -22,5 +22,5 @@ def create_insecure_channel( Returns: grpc.aio.Channel: A GRPC Channel connected to the Zeebe gateway. """ - grpc_address = create_address(grpc_address=grpc_address) + grpc_address = get_zeebe_address(grpc_address=grpc_address) return grpc.aio.insecure_channel(target=grpc_address, options=get_channel_options(channel_options)) diff --git a/pyzeebe/channel/oauth_channel.py b/pyzeebe/channel/oauth_channel.py index d54746a4..3b78b527 100644 --- a/pyzeebe/channel/oauth_channel.py +++ b/pyzeebe/channel/oauth_channel.py @@ -5,18 +5,28 @@ import grpc from pyzeebe.channel.channel_options import get_channel_options +from pyzeebe.channel.utils import ( + get_camunda_client_id, + get_camunda_client_secret, + get_camunda_cloud_hostname, + get_camunda_cluster_id, + get_camunda_cluster_region, + get_camunda_oauth_url, + get_camunda_token_audience, + get_zeebe_address, +) from pyzeebe.credentials.oauth import Oauth2ClientCredentialsMetadataPlugin from pyzeebe.types import ChannelArgumentType def create_oauth2_client_credentials_channel( - grpc_address: str, - client_id: str, - client_secret: str, - authorization_server: str, + grpc_address: str | None = None, + client_id: str | None = None, + client_secret: str | None = None, + authorization_server: str | None = None, scope: str | None = None, audience: str | None = None, - channel_credentials: grpc.ChannelCredentials | None = None, + channel_credentials: grpc.ChannelCredentials = grpc.ssl_channel_credentials(), channel_options: ChannelArgumentType | None = None, leeway: int = 60, expire_in: int | None = None, @@ -27,36 +37,47 @@ def create_oauth2_client_credentials_channel( - https://datatracker.ietf.org/doc/html/rfc6749#section-11.2.2 Args: - grpc_address (str): Zeebe Gateway Address. - - client_id (str): The client id. - client_secret (str): The client secret. - authorization_server (str): The authorization server issuing access tokens + grpc_address (str | None): Zeebe Gateway Address. + Defaults to value from ZEEBE_ADDRESS environment variable + or "{CAMUNDA_CLUSTER_ID}.{CAMUNDA_CLUSTER_REGION}.zeebe.camunda.io:443" + or "localhost:26500". + client_id (str | None): The client id. + Defaults to value from CAMUNDA_CLIENT_ID or ZEEBE_CLIENT_ID environment variable + client_secret (str | None): The client secret. + Defaults to value from CAMUNDA_CLIENT_SECRET or ZEEBE_CLIENT_SECRET environment variable + authorization_server (str | None): The authorization server issuing access tokens to the client after successfully authenticating the client. - scope (Optional[str]): The scope of the access request. Defaults to None. - audience (Optional[str]): The audience for authentication. Defaults to None. + Defaults to value from CAMUNDA_OAUTH_URL or ZEEBE_AUTHORIZATION_SERVER_URL environment variable + scope (str | None): The scope of the access request. + audience (str | None): The audience for authentication. + Defaults to value from CAMUNDA_TOKEN_AUDIENCE or ZEEBE_TOKEN_AUDIENCE environment variable channel_credentials (grpc.ChannelCredentials): The gRPC channel credentials. Defaults to grpc.ssl_channel_credentials(). - channel_options (Optional[ChannelArgumentType]): Additional options for the gRPC channel. + channel_options (ChannelArgumentType | None): Additional options for the gRPC channel. Defaults to None. See https://grpc.github.io/grpc/python/glossary.html#term-channel_arguments leeway (int): The number of seconds to consider the token as expired before the actual expiration time. Defaults to 60. - expire_in (Optional[int]): The number of seconds the token is valid for. Defaults to None. + expire_in (int | None): The number of seconds the token is valid for. Defaults to None. Should only be used if the token does not contain an "expires_in" attribute. Returns: grpc.aio.Channel: A gRPC channel connected to the Zeebe Gateway. """ + authorization_server = get_camunda_oauth_url(authorization_server) + + if not authorization_server: + raise ValueError("ZEEBE_AUTHORIZATION_SERVER_URL is not configured") + oauth2_client_credentials = Oauth2ClientCredentialsMetadataPlugin( - client_id=client_id, - client_secret=client_secret, - authorization_server=authorization_server, + client_id=get_camunda_client_id(client_id), + client_secret=get_camunda_client_secret(client_secret), + authorization_server=authorization_server or "", scope=scope, - audience=audience, + audience=get_camunda_token_audience(audience), leeway=leeway, expire_in=expire_in, ) @@ -67,21 +88,23 @@ def create_oauth2_client_credentials_channel( ) channel: grpc.aio.Channel = grpc.aio.secure_channel( - target=grpc_address, credentials=composite_credentials, options=get_channel_options(channel_options) + target=get_zeebe_address(grpc_address), + credentials=composite_credentials, + options=get_channel_options(channel_options), ) return channel def create_camunda_cloud_channel( - client_id: str, - client_secret: str, - cluster_id: str, - region: str = "bru-2", - authorization_server: str = "https://login.cloud.camunda.io/oauth/token", + client_id: str | None = None, + client_secret: str | None = None, + cluster_id: str | None = None, + region: str | None = None, scope: str | None = None, - audience: str | None = "zeebe.camunda.io", - channel_credentials: grpc.ChannelCredentials | None = None, + authorization_server: str | None = None, + audience: str | None = None, + channel_credentials: grpc.ChannelCredentials = grpc.ssl_channel_credentials(), channel_options: ChannelArgumentType | None = None, leeway: int = 60, expire_in: int | None = None, @@ -89,32 +112,47 @@ def create_camunda_cloud_channel( """Create a gRPC channel for connecting to Camunda 8 Cloud (SaaS). Args: - client_id (str): The client id. - client_secret (str): The client secret. - cluster_id (str): The ID of the cluster to connect to. - region (Optional[str]): The region of the cluster. Defaults to "bru-2". - authorization_server (Optional[str]): The authorization server issuing access tokens + client_id (str | None): The client id. + Defaults to value from CAMUNDA_CLIENT_ID or ZEEBE_CLIENT_ID environment variable. + client_secret (str | None): The client secret. + Defaults to value from CAMUNDA_CLIENT_SECRET or ZEEBE_CLIENT_SECRET environment variable. + cluster_id (str | None): The ID of the cluster to connect to. + Defaults to value from CAMUNDA_CLUSTER_ID environment variable. + region (str | None): The region of the cluster. + Defaults to value from CAMUNDA_CLUSTER_REGION environment variable or 'bru-2'. + scope (str | None): The scope of the access request. + authorization_server (str | None): The authorization server issuing access tokens to the client after successfully authenticating the client. - Defaults to "https://login.cloud.camunda.io/oauth/token". - scope (Optional[str]): The scope of the access request. Can be set to CAMUNDA_CLUSTER_ID. Defaults to None. - audience (Optional[str]): The audience for authentication. Defaults to "zeebe.camunda.io". + Defaults to value from CAMUNDA_OAUTH_URL + or ZEEBE_AUTHORIZATION_SERVER_URL environment variable + or "https://login.cloud.camunda.io/oauth/token". + audience (str | None): The audience for authentication. + Defaults to value from CAMUNDA_TOKEN_AUDIENCE + or ZEEBE_TOKEN_AUDIENCE environment variable + or "{cluster_id}.{region}.zeebe.camunda.io" + or "zeebe.camunda.io". channel_credentials (grpc.ChannelCredentials): The gRPC channel credentials. Defaults to grpc.ssl_channel_credentials(). - channel_options (Optional[ChannelArgumentType]): Additional options for the gRPC channel. + channel_options (ChannelArgumentType | None): Additional options for the gRPC channel. Defaults to None. See https://grpc.github.io/grpc/python/glossary.html#term-channel_arguments leeway (int): The number of seconds to consider the token as expired before the actual expiration time. Defaults to 60. - expire_in (Optional[int]): The number of seconds the token is valid for. Defaults to None. + expire_in (int | None): The number of seconds the token is valid for. Defaults to None. Should only be used if the token does not contain an "expires_in" attribute. Returns: grpc.aio.Channel: The gRPC channel for connecting to Camunda Cloud. """ - grpc_address = f"{cluster_id}.{region}.zeebe.camunda.io:443" + client_id = get_camunda_client_id(client_id) + client_secret = get_camunda_client_secret(client_secret) + audience = get_camunda_token_audience(audience) or "zeebe.camunda.io" + authorization_server = get_camunda_oauth_url(authorization_server) or "https://login.cloud.camunda.io/oauth/token" + cluster_id = get_camunda_cluster_id(cluster_id) + region = get_camunda_cluster_region(region) oauth2_client_credentials = Oauth2ClientCredentialsMetadataPlugin( client_id=client_id, @@ -141,6 +179,12 @@ def create_camunda_cloud_channel( channel_credentials or grpc.ssl_channel_credentials(), call_credentials ) + grpc_hostname = get_camunda_cloud_hostname(cluster_id, region) + if grpc_hostname: + grpc_address = f"{grpc_hostname}:443" + else: + grpc_address = get_zeebe_address(None) + channel: grpc.aio.Channel = grpc.aio.secure_channel( target=grpc_address, credentials=composite_credentials, options=get_channel_options(channel_options) ) diff --git a/pyzeebe/channel/secure_channel.py b/pyzeebe/channel/secure_channel.py index e28b4f91..ebafdf26 100644 --- a/pyzeebe/channel/secure_channel.py +++ b/pyzeebe/channel/secure_channel.py @@ -3,7 +3,7 @@ import grpc from pyzeebe.channel.channel_options import get_channel_options -from pyzeebe.channel.utils import create_address +from pyzeebe.channel.utils import get_zeebe_address from pyzeebe.types import ChannelArgumentType @@ -26,7 +26,7 @@ def create_secure_channel( Returns: grpc.aio.Channel: A GRPC Channel connected to the Zeebe gateway. """ - grpc_address = create_address(grpc_address=grpc_address) + grpc_address = get_zeebe_address(grpc_address=grpc_address) credentials = channel_credentials or grpc.ssl_channel_credentials() return grpc.aio.secure_channel( target=grpc_address, credentials=credentials, options=get_channel_options(channel_options) diff --git a/pyzeebe/channel/utils.py b/pyzeebe/channel/utils.py index 7b81909d..cffbdb26 100644 --- a/pyzeebe/channel/utils.py +++ b/pyzeebe/channel/utils.py @@ -5,9 +5,140 @@ DEFAULT_ZEEBE_ADDRESS = "localhost:26500" -def create_address( - grpc_address: str | None = None, -) -> str: +def get_zeebe_address(grpc_address: str | None = None) -> str: + """ + Args: + grpc_address (str, optional): zeebe grpc server address. + + Returns: + str: The zeebe grpc server address. + Default: Value from ZEEBE_ADDRESS environment variable + or "{CAMUNDA_CLUSTER_ID}.{CAMUNDA_CLUSTER_REGION}.zeebe.camunda.io" + or "localhost:26500" + """ + if grpc_address: return grpc_address - return os.getenv("ZEEBE_ADDRESS", DEFAULT_ZEEBE_ADDRESS) + + camunda_cloud_address = None + camunda_cloud_hostname = get_camunda_cloud_hostname(None, None) + + if camunda_cloud_hostname: + camunda_cloud_address = f"{camunda_cloud_hostname}:443" + + return os.getenv("ZEEBE_ADDRESS", camunda_cloud_address or DEFAULT_ZEEBE_ADDRESS) + + +def get_camunda_oauth_url(oauth_url: str | None) -> str | None: + """ + Args: + oauth_url (str, optional): The camunda platform authorization server url provided as parameter. + + Returns: + str: The camunda platform authorization server url. + Default: Value from CAMUNDA_OAUTH_URL or ZEEBE_AUTHORIZATION_SERVER_URL environment variable + """ + return oauth_url or os.getenv("CAMUNDA_OAUTH_URL", os.getenv("ZEEBE_AUTHORIZATION_SERVER_URL")) + + +def get_camunda_client_id(client_id: str | None) -> str: + """ + Args: + client_id (str, optional): The client id provided as parameter. + + Returns: + str: The client id. + Default: Value from CAMUNDA_CLIENT_ID or ZEEBE_CLIENT_ID environment variable + """ + ret = client_id or os.getenv("CAMUNDA_CLIENT_ID", os.getenv("ZEEBE_CLIENT_ID")) + + if not ret: + raise ValueError( + "parameter client_id or one of the environment variables CAMUNDA_CLIENT_ID or ZEEBE_CLIENT_ID must be provided!" + ) + + return ret + + +def get_camunda_client_secret(client_secret: str | None) -> str: + """ + Args: + client_secret (str, optional): The client secret provided as parameter. + + Returns: + str: The client secret. + Default: Value from CAMUNDA_CLIENT_SECRET or ZEEBE_CLIENT_SECRET environment variable + """ + ret = client_secret or os.getenv("CAMUNDA_CLIENT_SECRET") or os.getenv("ZEEBE_CLIENT_SECRET") + + if not ret: + raise ValueError( + "parameter client_secret or one of the environment variables CAMUNDA_CLIENT_SECRET or ZEEBE_CLIENT_SECRET must be provided!" + ) + + return ret + + +def get_camunda_cluster_id(cluster_id: str | None) -> str | None: + """ + Args: + cluster_id (str, optional): The camunda cluster id provided as parameter. + + Returns: + str: The camunda cluster id. + Default: Value from CAMUNDA_CLUSTER_ID environment variable + """ + + return cluster_id or os.getenv("CAMUNDA_CLUSTER_ID") + + +def get_camunda_cluster_region(cluster_region: str | None) -> str: + """ + Args: + cluster_region (str, optional): The camunda cluster region provided as parameter. + + Returns: + str: The camunda cluster region. + Default: Value from CAMUNDA_CLUSTER_REGION environment variable or 'bru-2' + """ + + return cluster_region or os.getenv("CAMUNDA_CLUSTER_REGION") or "bru-2" + + +def get_camunda_token_audience(token_audience: str | None) -> str | None: + """ + Args: + token_audience (str, optional): The token audience provided as parameter. + + Returns: + str: The token audience. + Default: Value from CAMUNDA_TOKEN_AUDIENCE + or ZEEBE_TOKEN_AUDIENCE environment variable + or camunda cloud token audience if camunda cluster_id is available + """ + + return ( + token_audience + or os.getenv("CAMUNDA_TOKEN_AUDIENCE") + or os.getenv("ZEEBE_TOKEN_AUDIENCE") + or get_camunda_cloud_hostname(None, None) + ) + + +def get_camunda_cloud_hostname(cluster_id: str | None, cluster_region: str | None) -> str | None: + """ + Args: + cluster_id (str, optional): The camunda cluster id provided as parameter. + cluster_region (str, optional): The camunda cluster region provided as parameter. + + Returns: + str: The token audience for camunda cloud or none if cluster_id or cluster_region is not provided. + """ + + cluster_id = get_camunda_cluster_id(cluster_id) + cluster_region = get_camunda_cluster_region(cluster_region) + + if (not cluster_id) or (not cluster_region): + return None + + return f"{cluster_id}.{cluster_region}.zeebe.camunda.io" diff --git a/tests/unit/channel/insecure_channel_test.py b/tests/unit/channel/insecure_channel_test.py index e76fe6ae..633286b3 100644 --- a/tests/unit/channel/insecure_channel_test.py +++ b/tests/unit/channel/insecure_channel_test.py @@ -5,7 +5,7 @@ from pyzeebe import create_insecure_channel from pyzeebe.channel.channel_options import get_channel_options -from pyzeebe.channel.utils import create_address +from pyzeebe.channel.utils import get_zeebe_address class TestCreateInsecureChannel: @@ -29,4 +29,4 @@ def test_uses_default_address(self, insecure_channel_mock: Mock): create_insecure_channel() insecure_channel_call = insecure_channel_mock.mock_calls[0] - assert insecure_channel_call.kwargs["target"] == create_address() + assert insecure_channel_call.kwargs["target"] == get_zeebe_address() diff --git a/tests/unit/channel/secure_channel_test.py b/tests/unit/channel/secure_channel_test.py index 181f5f80..1ed667b7 100644 --- a/tests/unit/channel/secure_channel_test.py +++ b/tests/unit/channel/secure_channel_test.py @@ -5,7 +5,7 @@ from pyzeebe import create_secure_channel from pyzeebe.channel.channel_options import get_channel_options -from pyzeebe.channel.utils import create_address +from pyzeebe.channel.utils import get_zeebe_address class TestCreateSecureChannel: @@ -35,4 +35,4 @@ def test_uses_default_address(self, secure_channel_mock: Mock): create_secure_channel() secure_channel_call = secure_channel_mock.mock_calls[0] - assert secure_channel_call.kwargs["target"] == create_address() + assert secure_channel_call.kwargs["target"] == get_zeebe_address() diff --git a/tests/unit/channel/utils_test.py b/tests/unit/channel/utils_test.py index f4d0a9ef..36605d8e 100644 --- a/tests/unit/channel/utils_test.py +++ b/tests/unit/channel/utils_test.py @@ -1,25 +1,255 @@ import os +from unittest.mock import patch from uuid import uuid4 -from pyzeebe.channel.utils import DEFAULT_ZEEBE_ADDRESS, create_address +import pytest +from pyzeebe.channel.utils import ( + DEFAULT_ZEEBE_ADDRESS, + get_camunda_client_id, + get_camunda_client_secret, + get_camunda_cloud_hostname, + get_camunda_cluster_id, + get_camunda_cluster_region, + get_camunda_oauth_url, + get_camunda_token_audience, + get_zeebe_address, +) -class TestCreateAddress: + +class TestGetZeebeAddress: def test_returns_passed_address(self): address = str(uuid4()) - assert address == create_address(address) + assert address == get_zeebe_address(address) def test_returns_default_address(self): - address = create_address() + address = get_zeebe_address() assert address == DEFAULT_ZEEBE_ADDRESS + @patch.dict( + os.environ, + { + "ZEEBE_ADDRESS": "ZEEBE_ADDRESS", + "CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", + "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION", + }, + ) def test_returns_env_var_if_provided(self): - zeebe_address = str(uuid4()) - os.environ["ZEEBE_ADDRESS"] = zeebe_address + address = get_zeebe_address() + + assert address == "ZEEBE_ADDRESS" + + @patch.dict( + os.environ, {"CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION"} + ) + def test_returns_cloud_address_if_cluster_id_env_var_provided(self): + address = get_zeebe_address() + + assert address == "CAMUNDA_CLUSTER_ID.CAMUNDA_CLUSTER_REGION.zeebe.camunda.io:443" + + +class TestGetCamundaOauthUrl: + @patch.dict( + os.environ, + {"CAMUNDA_OAUTH_URL": "CAMUNDA_OAUTH_URL", "ZEEBE_AUTHORIZATION_SERVER_URL": "ZEEBE_AUTHORIZATION_SERVER_URL"}, + ) + def test_param_has_highest_priority(self): + result = get_camunda_oauth_url("oauth_url") + + assert result == "oauth_url" + + @patch.dict( + os.environ, + {"CAMUNDA_OAUTH_URL": "CAMUNDA_OAUTH_URL", "ZEEBE_AUTHORIZATION_SERVER_URL": "ZEEBE_AUTHORIZATION_SERVER_URL"}, + ) + def test_camunda_oauth_url_has_second_highest_priority(self): + result = get_camunda_oauth_url(None) + + assert result == "CAMUNDA_OAUTH_URL" + + @patch.dict(os.environ, {"ZEEBE_AUTHORIZATION_SERVER_URL": "ZEEBE_AUTHORIZATION_SERVER_URL"}) + def test_zeebe_authorization_server_url_has_third_highest_priority(self): + result = get_camunda_oauth_url(None) + + assert result == "ZEEBE_AUTHORIZATION_SERVER_URL" + + @patch.dict(os.environ, {}) + def test_none_has_fourth_highest_priority(self): + result = get_camunda_oauth_url(None) + assert result is None + + +class TestGetCamundaClientId: + @patch.dict(os.environ, {"CAMUNDA_CLIENT_ID": "CAMUNDA_CLIENT_ID", "ZEEBE_CLIENT_ID": "ZEEBE_CLIENT_ID"}) + def test_is_calculated_from_parameters_as_highest_priority(self): + result = get_camunda_client_id("client_id_param") + + assert result == "client_id_param" + + @patch.dict(os.environ, {"CAMUNDA_CLIENT_ID": "CAMUNDA_CLIENT_ID", "ZEEBE_CLIENT_ID": "ZEEBE_CLIENT_ID"}) + def test_is_calculated_from_camunda_environment_variable_as_second_priority(self): + result = get_camunda_client_id(None) + + assert result == "CAMUNDA_CLIENT_ID" + + @patch.dict(os.environ, {"ZEEBE_CLIENT_ID": "ZEEBE_CLIENT_ID"}) + def test_is_calculated_from_zeebe_environment_variable_as_third_priority(self): + result = get_camunda_client_id(None) + + assert result == "ZEEBE_CLIENT_ID" + + @patch.dict(os.environ, {}) + def test_throw_exception_if_not_configured(self): + with pytest.raises(ValueError): + get_camunda_client_id(None) + + +class TestGetCamundaClientSecret: + @patch.dict( + os.environ, {"CAMUNDA_CLIENT_SECRET": "CAMUNDA_CLIENT_SECRET", "ZEEBE_CLIENT_SECRET": "ZEEBE_CLIENT_SECRET"} + ) + def test_is_calculated_from_parameters_as_highest_priority(self): + result = get_camunda_client_secret("client_secret_param") + + assert result == "client_secret_param" + + @patch.dict( + os.environ, {"CAMUNDA_CLIENT_SECRET": "CAMUNDA_CLIENT_SECRET", "ZEEBE_CLIENT_SECRET": "ZEEBE_CLIENT_SECRET"} + ) + def test_is_calculated_from_camunda_environment_variable_as_second_priority(self): + result = get_camunda_client_secret(None) + + assert result == "CAMUNDA_CLIENT_SECRET" + + @patch.dict(os.environ, {"ZEEBE_CLIENT_SECRET": "ZEEBE_CLIENT_SECRET"}) + def test_is_calculated_from_zeebe_environment_variable_as_third_priority(self): + result = get_camunda_client_secret(None) + + assert result == "ZEEBE_CLIENT_SECRET" + + @patch.dict(os.environ, {}) + def test_throw_exception_if_not_configured(self): + with pytest.raises(ValueError): + get_camunda_client_secret(None) + + +class TestGetCamundaCloudClusterId: + @patch.dict(os.environ, {"CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID"}) + def test_is_calculated_from_parameters_as_highest_priority(self): + result = get_camunda_cluster_id("cluster_id_param") + + assert result == "cluster_id_param" + + @patch.dict(os.environ, {"CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID"}) + def test_is_calculated_from_camunda_environment_variable_as_second_priority(self): + result = get_camunda_cluster_id(None) + + assert result == "CAMUNDA_CLUSTER_ID" + + @patch.dict(os.environ, {}) + def test_none_has_third_highest_priority(self): + result = get_camunda_cluster_id(None) + assert result is None + + +class TestGetCamundaCloudClusterRegion: + @patch.dict(os.environ, {"CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION"}) + def test_is_calculated_from_parameters_as_highest_priority(self): + result = get_camunda_cluster_region("cluster_region_param") + + assert result == "cluster_region_param" + + @patch.dict(os.environ, {"CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION"}) + def test_is_calculated_from_camunda_environment_variable_as_second_priority(self): + result = get_camunda_cluster_region(None) + + assert result == "CAMUNDA_CLUSTER_REGION" + + @patch.dict(os.environ, {}) + def test_bru_2_has_third_highest_priority(self): + result = get_camunda_cluster_region(None) + assert result == "bru-2" + + +class TestGetCamundaTokenAudience: + @patch.dict( + os.environ, + { + "CAMUNDA_TOKEN_AUDIENCE": "CAMUNDA_TOKEN_AUDIENCE", + "ZEEBE_TOKEN_AUDIENCE": "ZEEBE_TOKEN_AUDIENCE", + "CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", + "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION", + }, + ) + def test_is_calculated_from_parameters_as_highest_priority(self): + result = get_camunda_token_audience("token_audience_param") + + assert result == "token_audience_param" + + @patch.dict( + os.environ, + { + "CAMUNDA_TOKEN_AUDIENCE": "CAMUNDA_TOKEN_AUDIENCE", + "ZEEBE_TOKEN_AUDIENCE": "ZEEBE_TOKEN_AUDIENCE", + "CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", + "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION", + }, + ) + def test_is_calculated_from_camunda_token_audience_as_second_highest_priority(self): + result = get_camunda_token_audience(None) + + assert result == "CAMUNDA_TOKEN_AUDIENCE" + + @patch.dict( + os.environ, + { + "ZEEBE_TOKEN_AUDIENCE": "ZEEBE_TOKEN_AUDIENCE", + "CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", + "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION", + }, + ) + def test_is_calculated_from_zeebe_token_audience_as_third_highest_priority(self): + result = get_camunda_token_audience(None) + + assert result == "ZEEBE_TOKEN_AUDIENCE" + + @patch.dict( + os.environ, {"CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION"} + ) + def test_is_calculated_from_camunda_cloud_hostname_as_fourth_highest_priority(self): + result = get_camunda_token_audience(None) + + assert result == "CAMUNDA_CLUSTER_ID.CAMUNDA_CLUSTER_REGION.zeebe.camunda.io" + + @patch.dict(os.environ, {}) + def test_is_none_as_fifth_highest_priority(self): + result = get_camunda_token_audience(None) + + assert result is None + + +class TestGetCamundaCloudHostname: + @patch.dict( + os.environ, {"CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION"} + ) + def test_is_calculated_from_parameters_as_highest_priority(self): + result = get_camunda_cloud_hostname("cluster_id_param", "camunda_region_param") + + assert result == f"cluster_id_param.camunda_region_param.zeebe.camunda.io" + + @patch.dict( + os.environ, {"CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION"} + ) + def test_is_calculated_from_environment_variables_as_second_priority(self): + result = get_camunda_cloud_hostname(None, None) + + assert result == f"CAMUNDA_CLUSTER_ID.CAMUNDA_CLUSTER_REGION.zeebe.camunda.io" - address = create_address() + @patch.dict(os.environ, {}) + def test_returns_none_by_default(self): + result = get_camunda_cloud_hostname(None, None) - assert address == zeebe_address + assert result is None From a69e8272d06a779f3025d48935113c72a56fea9c Mon Sep 17 00:00:00 2001 From: PSanetra Date: Mon, 21 Oct 2024 12:07:23 +0200 Subject: [PATCH 2/9] fix: Apply suggestions from #509 BREAKING CHANGES: Default values for arguments of create_oauth2_client_credentials_channel() and create_camunda_cloud_channel() have changed to Unset. Also order of create_camunda_cloud_channel() arguments was changed. --- pyzeebe/channel/insecure_channel.py | 2 +- pyzeebe/channel/oauth_channel.py | 125 +++++++++++-------- pyzeebe/channel/secure_channel.py | 2 +- pyzeebe/channel/utils.py | 147 ++++++++++------------ pyzeebe/types.py | 1 + tests/unit/channel/utils_test.py | 183 +++++++++------------------- 6 files changed, 202 insertions(+), 258 deletions(-) diff --git a/pyzeebe/channel/insecure_channel.py b/pyzeebe/channel/insecure_channel.py index fb269193..6a990840 100644 --- a/pyzeebe/channel/insecure_channel.py +++ b/pyzeebe/channel/insecure_channel.py @@ -22,5 +22,5 @@ def create_insecure_channel( Returns: grpc.aio.Channel: A GRPC Channel connected to the Zeebe gateway. """ - grpc_address = get_zeebe_address(grpc_address=grpc_address) + grpc_address = grpc_address or get_zeebe_address() return grpc.aio.insecure_channel(target=grpc_address, options=get_channel_options(channel_options)) diff --git a/pyzeebe/channel/oauth_channel.py b/pyzeebe/channel/oauth_channel.py index 3b78b527..65994786 100644 --- a/pyzeebe/channel/oauth_channel.py +++ b/pyzeebe/channel/oauth_channel.py @@ -6,9 +6,9 @@ from pyzeebe.channel.channel_options import get_channel_options from pyzeebe.channel.utils import ( + get_camunda_address, get_camunda_client_id, get_camunda_client_secret, - get_camunda_cloud_hostname, get_camunda_cluster_id, get_camunda_cluster_region, get_camunda_oauth_url, @@ -16,16 +16,16 @@ get_zeebe_address, ) from pyzeebe.credentials.oauth import Oauth2ClientCredentialsMetadataPlugin -from pyzeebe.types import ChannelArgumentType +from pyzeebe.types import ChannelArgumentType, Unset def create_oauth2_client_credentials_channel( - grpc_address: str | None = None, - client_id: str | None = None, - client_secret: str | None = None, - authorization_server: str | None = None, - scope: str | None = None, - audience: str | None = None, + grpc_address: str = Unset, + client_id: str = Unset, + client_secret: str = Unset, + authorization_server: str = Unset, + scope: str | None = Unset, + audience: str | None = Unset, channel_credentials: grpc.ChannelCredentials = grpc.ssl_channel_credentials(), channel_options: ChannelArgumentType | None = None, leeway: int = 60, @@ -33,23 +33,23 @@ def create_oauth2_client_credentials_channel( ) -> grpc.aio.Channel: """Create a gRPC channel for connecting to Camunda 8 (Self-Managed) with OAuth2ClientCredentials. - - https://oauth.net/2/grant-types/client-credentials/ - - https://datatracker.ietf.org/doc/html/rfc6749#section-11.2.2 + https://oauth.net/2/grant-types/client-credentials/ + https://datatracker.ietf.org/doc/html/rfc6749#section-11.2.2 Args: - grpc_address (str | None): Zeebe Gateway Address. + grpc_address (str, optional): Zeebe Gateway Address. Defaults to value from ZEEBE_ADDRESS environment variable or "{CAMUNDA_CLUSTER_ID}.{CAMUNDA_CLUSTER_REGION}.zeebe.camunda.io:443" or "localhost:26500". - client_id (str | None): The client id. + client_id (str, optional): The client id. Defaults to value from CAMUNDA_CLIENT_ID or ZEEBE_CLIENT_ID environment variable - client_secret (str | None): The client secret. + client_secret (str, optional): The client secret. Defaults to value from CAMUNDA_CLIENT_SECRET or ZEEBE_CLIENT_SECRET environment variable - authorization_server (str | None): The authorization server issuing access tokens + authorization_server (str, optional): The authorization server issuing access tokens to the client after successfully authenticating the client. Defaults to value from CAMUNDA_OAUTH_URL or ZEEBE_AUTHORIZATION_SERVER_URL environment variable - scope (str | None): The scope of the access request. - audience (str | None): The audience for authentication. + scope (str | None, optional): The scope of the access request. + audience (str | None, optional): The audience for authentication. Defaults to value from CAMUNDA_TOKEN_AUDIENCE or ZEEBE_TOKEN_AUDIENCE environment variable channel_credentials (grpc.ChannelCredentials): The gRPC channel credentials. @@ -65,19 +65,35 @@ def create_oauth2_client_credentials_channel( Returns: grpc.aio.Channel: A gRPC channel connected to the Zeebe Gateway. + + Raises: + InvalidOAuthCredentialsError: One of the provided camunda credentials is not correct """ - authorization_server = get_camunda_oauth_url(authorization_server) + if grpc_address is Unset: + grpc_address = get_zeebe_address() + + if client_id is Unset: + client_id = get_camunda_client_id() + + if client_secret is Unset: + client_secret = get_camunda_client_secret() + + if authorization_server is Unset: + authorization_server = get_camunda_oauth_url() - if not authorization_server: - raise ValueError("ZEEBE_AUTHORIZATION_SERVER_URL is not configured") + if scope is Unset: + scope = None + + if audience is Unset: + audience = get_camunda_token_audience() oauth2_client_credentials = Oauth2ClientCredentialsMetadataPlugin( - client_id=get_camunda_client_id(client_id), - client_secret=get_camunda_client_secret(client_secret), - authorization_server=authorization_server or "", + client_id=client_id, + client_secret=client_secret, + authorization_server=authorization_server, scope=scope, - audience=get_camunda_token_audience(audience), + audience=audience, leeway=leeway, expire_in=expire_in, ) @@ -88,7 +104,7 @@ def create_oauth2_client_credentials_channel( ) channel: grpc.aio.Channel = grpc.aio.secure_channel( - target=get_zeebe_address(grpc_address), + target=grpc_address, credentials=composite_credentials, options=get_channel_options(channel_options), ) @@ -97,13 +113,13 @@ def create_oauth2_client_credentials_channel( def create_camunda_cloud_channel( - client_id: str | None = None, - client_secret: str | None = None, - cluster_id: str | None = None, - region: str | None = None, - scope: str | None = None, - authorization_server: str | None = None, - audience: str | None = None, + client_id: str = Unset, + client_secret: str = Unset, + cluster_id: str = Unset, + region: str = Unset, + authorization_server: str = Unset, + scope: str | None = Unset, + audience: str | None = Unset, channel_credentials: grpc.ChannelCredentials = grpc.ssl_channel_credentials(), channel_options: ChannelArgumentType | None = None, leeway: int = 60, @@ -112,24 +128,23 @@ def create_camunda_cloud_channel( """Create a gRPC channel for connecting to Camunda 8 Cloud (SaaS). Args: - client_id (str | None): The client id. + client_id (str, optional): The client id. Defaults to value from CAMUNDA_CLIENT_ID or ZEEBE_CLIENT_ID environment variable. - client_secret (str | None): The client secret. + client_secret (str, optional): The client secret. Defaults to value from CAMUNDA_CLIENT_SECRET or ZEEBE_CLIENT_SECRET environment variable. - cluster_id (str | None): The ID of the cluster to connect to. + cluster_id (str, optional): The ID of the cluster to connect to. Defaults to value from CAMUNDA_CLUSTER_ID environment variable. - region (str | None): The region of the cluster. + region (str, optional): The region of the cluster. Defaults to value from CAMUNDA_CLUSTER_REGION environment variable or 'bru-2'. - scope (str | None): The scope of the access request. - authorization_server (str | None): The authorization server issuing access tokens + authorization_server (str, optional): The authorization server issuing access tokens to the client after successfully authenticating the client. Defaults to value from CAMUNDA_OAUTH_URL or ZEEBE_AUTHORIZATION_SERVER_URL environment variable or "https://login.cloud.camunda.io/oauth/token". - audience (str | None): The audience for authentication. + scope (str | None, optional): The scope of the access request. + audience (str | None, optional): The audience for authentication. Defaults to value from CAMUNDA_TOKEN_AUDIENCE or ZEEBE_TOKEN_AUDIENCE environment variable - or "{cluster_id}.{region}.zeebe.camunda.io" or "zeebe.camunda.io". channel_credentials (grpc.ChannelCredentials): The gRPC channel credentials. @@ -147,12 +162,26 @@ def create_camunda_cloud_channel( grpc.aio.Channel: The gRPC channel for connecting to Camunda Cloud. """ - client_id = get_camunda_client_id(client_id) - client_secret = get_camunda_client_secret(client_secret) - audience = get_camunda_token_audience(audience) or "zeebe.camunda.io" - authorization_server = get_camunda_oauth_url(authorization_server) or "https://login.cloud.camunda.io/oauth/token" - cluster_id = get_camunda_cluster_id(cluster_id) - region = get_camunda_cluster_region(region) + if client_id is Unset: + client_id = get_camunda_client_id() + + if client_secret is Unset: + client_secret = get_camunda_client_secret() + + if cluster_id is Unset: + cluster_id = get_camunda_cluster_id() + + if region is Unset: + region = get_camunda_cluster_region("bru-2") + + if authorization_server is Unset: + authorization_server = get_camunda_oauth_url("https://login.cloud.camunda.io/oauth/token") + + if scope is Unset: + scope = None + + if audience is Unset: + audience = get_camunda_token_audience("zeebe.camunda.io") oauth2_client_credentials = Oauth2ClientCredentialsMetadataPlugin( client_id=client_id, @@ -179,11 +208,7 @@ def create_camunda_cloud_channel( channel_credentials or grpc.ssl_channel_credentials(), call_credentials ) - grpc_hostname = get_camunda_cloud_hostname(cluster_id, region) - if grpc_hostname: - grpc_address = f"{grpc_hostname}:443" - else: - grpc_address = get_zeebe_address(None) + grpc_address = get_camunda_address(cluster_id=cluster_id, cluster_region=region) channel: grpc.aio.Channel = grpc.aio.secure_channel( target=grpc_address, credentials=composite_credentials, options=get_channel_options(channel_options) diff --git a/pyzeebe/channel/secure_channel.py b/pyzeebe/channel/secure_channel.py index ebafdf26..17396a36 100644 --- a/pyzeebe/channel/secure_channel.py +++ b/pyzeebe/channel/secure_channel.py @@ -26,7 +26,7 @@ def create_secure_channel( Returns: grpc.aio.Channel: A GRPC Channel connected to the Zeebe gateway. """ - grpc_address = get_zeebe_address(grpc_address=grpc_address) + grpc_address = grpc_address or get_zeebe_address() credentials = channel_credentials or grpc.ssl_channel_credentials() return grpc.aio.secure_channel( target=grpc_address, credentials=credentials, options=get_channel_options(channel_options) diff --git a/pyzeebe/channel/utils.py b/pyzeebe/channel/utils.py index cffbdb26..e218982b 100644 --- a/pyzeebe/channel/utils.py +++ b/pyzeebe/channel/utils.py @@ -5,140 +5,123 @@ DEFAULT_ZEEBE_ADDRESS = "localhost:26500" -def get_zeebe_address(grpc_address: str | None = None) -> str: +def get_zeebe_address(default_value: str | None = None) -> str: """ Args: - grpc_address (str, optional): zeebe grpc server address. - - Returns: - str: The zeebe grpc server address. - Default: Value from ZEEBE_ADDRESS environment variable - or "{CAMUNDA_CLUSTER_ID}.{CAMUNDA_CLUSTER_REGION}.zeebe.camunda.io" - or "localhost:26500" + default_value (str, optional): Default value to be used if no other value was discovered. + Returns: Value from ZEEBE_ADDRESS environment variable + or provided default_value + or "localhost:26500" """ - if grpc_address: - return grpc_address - - camunda_cloud_address = None - camunda_cloud_hostname = get_camunda_cloud_hostname(None, None) - - if camunda_cloud_hostname: - camunda_cloud_address = f"{camunda_cloud_hostname}:443" + return os.getenv("ZEEBE_ADDRESS") or default_value or DEFAULT_ZEEBE_ADDRESS - return os.getenv("ZEEBE_ADDRESS", camunda_cloud_address or DEFAULT_ZEEBE_ADDRESS) - -def get_camunda_oauth_url(oauth_url: str | None) -> str | None: +def get_camunda_oauth_url(default_value: str | None = None) -> str: """ Args: - oauth_url (str, optional): The camunda platform authorization server url provided as parameter. - - Returns: - str: The camunda platform authorization server url. - Default: Value from CAMUNDA_OAUTH_URL or ZEEBE_AUTHORIZATION_SERVER_URL environment variable + default_value (str, optional): Default value to be used if no other value was discovered. + Returns: The camunda platform authorization server url. + Default: Value from CAMUNDA_OAUTH_URL + or ZEEBE_AUTHORIZATION_SERVER_URL environment variable + or provided default_value """ - return oauth_url or os.getenv("CAMUNDA_OAUTH_URL", os.getenv("ZEEBE_AUTHORIZATION_SERVER_URL")) + r = os.getenv("CAMUNDA_OAUTH_URL") or os.getenv("ZEEBE_AUTHORIZATION_SERVER_URL") or default_value + if r is None: + raise EnvironmentError("No CAMUNDA_OAUTH_URL or ZEEBE_AUTHORIZATION_SERVER_URL provided!") -def get_camunda_client_id(client_id: str | None) -> str: - """ - Args: - client_id (str, optional): The client id provided as parameter. + return r - Returns: - str: The client id. + +def get_camunda_client_id() -> str: + """ + Returns: The client id. Default: Value from CAMUNDA_CLIENT_ID or ZEEBE_CLIENT_ID environment variable """ - ret = client_id or os.getenv("CAMUNDA_CLIENT_ID", os.getenv("ZEEBE_CLIENT_ID")) + r = os.getenv("CAMUNDA_CLIENT_ID") or os.getenv("ZEEBE_CLIENT_ID") - if not ret: - raise ValueError( - "parameter client_id or one of the environment variables CAMUNDA_CLIENT_ID or ZEEBE_CLIENT_ID must be provided!" - ) + if r is None: + raise EnvironmentError("No CAMUNDA_CLIENT_ID or ZEEBE_CLIENT_ID provided!") - return ret + return r -def get_camunda_client_secret(client_secret: str | None) -> str: +def get_camunda_client_secret() -> str: """ - Args: - client_secret (str, optional): The client secret provided as parameter. - Returns: str: The client secret. Default: Value from CAMUNDA_CLIENT_SECRET or ZEEBE_CLIENT_SECRET environment variable """ - ret = client_secret or os.getenv("CAMUNDA_CLIENT_SECRET") or os.getenv("ZEEBE_CLIENT_SECRET") - if not ret: - raise ValueError( - "parameter client_secret or one of the environment variables CAMUNDA_CLIENT_SECRET or ZEEBE_CLIENT_SECRET must be provided!" - ) + r = os.getenv("CAMUNDA_CLIENT_SECRET") or os.getenv("ZEEBE_CLIENT_SECRET") - return ret + if r is None: + raise EnvironmentError("No CAMUNDA_CLIENT_SECRET or ZEEBE_CLIENT_SECRET provided!") + return r -def get_camunda_cluster_id(cluster_id: str | None) -> str | None: - """ - Args: - cluster_id (str, optional): The camunda cluster id provided as parameter. - Returns: - str: The camunda cluster id. +def get_camunda_cluster_id() -> str: + """ + Returns: The camunda cluster id. Default: Value from CAMUNDA_CLUSTER_ID environment variable """ - return cluster_id or os.getenv("CAMUNDA_CLUSTER_ID") + r = os.getenv("CAMUNDA_CLUSTER_ID") + + if r is None: + raise EnvironmentError("No CAMUNDA_CLUSTER_ID provided!") + return r -def get_camunda_cluster_region(cluster_region: str | None) -> str: + +def get_camunda_cluster_region(default_value: str | None = None) -> str: """ Args: - cluster_region (str, optional): The camunda cluster region provided as parameter. - - Returns: - str: The camunda cluster region. - Default: Value from CAMUNDA_CLUSTER_REGION environment variable or 'bru-2' + default_value (str, optional): Default value to be used if no other value was discovered. + Returns: The camunda cluster region. + Default: Value from CAMUNDA_CLUSTER_REGION environment variable """ - return cluster_region or os.getenv("CAMUNDA_CLUSTER_REGION") or "bru-2" + r = os.getenv("CAMUNDA_CLUSTER_REGION") or default_value + if r is None: + raise EnvironmentError("No CAMUNDA_CLUSTER_REGION provided!") -def get_camunda_token_audience(token_audience: str | None) -> str | None: + return r + + +def get_camunda_token_audience(default_value: str | None = None) -> str: """ Args: - token_audience (str, optional): The token audience provided as parameter. - - Returns: - str: The token audience. + default_value (str, optional): Default value to be used if no other value was discovered. + Returns: The token audience. Default: Value from CAMUNDA_TOKEN_AUDIENCE or ZEEBE_TOKEN_AUDIENCE environment variable - or camunda cloud token audience if camunda cluster_id is available + or provided default_value """ + r = os.getenv("CAMUNDA_TOKEN_AUDIENCE") or os.getenv("ZEEBE_TOKEN_AUDIENCE") or default_value - return ( - token_audience - or os.getenv("CAMUNDA_TOKEN_AUDIENCE") - or os.getenv("ZEEBE_TOKEN_AUDIENCE") - or get_camunda_cloud_hostname(None, None) - ) + if r is None: + raise EnvironmentError("No CAMUNDA_TOKEN_AUDIENCE or ZEEBE_TOKEN_AUDIENCE provided!") + return r -def get_camunda_cloud_hostname(cluster_id: str | None, cluster_region: str | None) -> str | None: + +def get_camunda_address(cluster_id: str | None = None, cluster_region: str | None = None) -> str: """ Args: cluster_id (str, optional): The camunda cluster id provided as parameter. + Default: Value from CAMUNDA_CLUSTER_ID environment variable or None cluster_region (str, optional): The camunda cluster region provided as parameter. - + Default: Value from CAMUNDA_CLUSTER_REGION environment variable. Returns: - str: The token audience for camunda cloud or none if cluster_id or cluster_region is not provided. + str: The Camunda Cloud grpc server address. """ - cluster_id = get_camunda_cluster_id(cluster_id) - cluster_region = get_camunda_cluster_region(cluster_region) - - if (not cluster_id) or (not cluster_region): - return None + if (cluster_id is None) or (cluster_region is None): + raise EnvironmentError("The cluster_id and cluster_region must be provided!") - return f"{cluster_id}.{cluster_region}.zeebe.camunda.io" + # if (cluster_id is not None) and (cluster_region is not None): # and + return f"{cluster_id}.{cluster_region}.zeebe.camunda.io:443" diff --git a/pyzeebe/types.py b/pyzeebe/types.py index 6191be46..696fab30 100644 --- a/pyzeebe/types.py +++ b/pyzeebe/types.py @@ -5,5 +5,6 @@ Headers: TypeAlias = Mapping[str, Any] Variables: TypeAlias = Mapping[str, Any] +Unset = str("UNSET") ChannelArgumentType: TypeAlias = Sequence[tuple[str, Any]] diff --git a/tests/unit/channel/utils_test.py b/tests/unit/channel/utils_test.py index 36605d8e..0a6537d3 100644 --- a/tests/unit/channel/utils_test.py +++ b/tests/unit/channel/utils_test.py @@ -6,9 +6,9 @@ from pyzeebe.channel.utils import ( DEFAULT_ZEEBE_ADDRESS, + get_camunda_address, get_camunda_client_id, get_camunda_client_secret, - get_camunda_cloud_hostname, get_camunda_cluster_id, get_camunda_cluster_region, get_camunda_oauth_url, @@ -31,147 +31,109 @@ def test_returns_default_address(self): @patch.dict( os.environ, - { - "ZEEBE_ADDRESS": "ZEEBE_ADDRESS", - "CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", - "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION", - }, + {"ZEEBE_ADDRESS": "ZEEBE_ADDRESS"}, ) def test_returns_env_var_if_provided(self): - address = get_zeebe_address() + address = get_zeebe_address("zeebe_address") assert address == "ZEEBE_ADDRESS" - @patch.dict( - os.environ, {"CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION"} - ) - def test_returns_cloud_address_if_cluster_id_env_var_provided(self): - address = get_zeebe_address() - - assert address == "CAMUNDA_CLUSTER_ID.CAMUNDA_CLUSTER_REGION.zeebe.camunda.io:443" - class TestGetCamundaOauthUrl: @patch.dict( os.environ, {"CAMUNDA_OAUTH_URL": "CAMUNDA_OAUTH_URL", "ZEEBE_AUTHORIZATION_SERVER_URL": "ZEEBE_AUTHORIZATION_SERVER_URL"}, ) - def test_param_has_highest_priority(self): + def test_camunda_oauth_url_has_highest_priority(self): result = get_camunda_oauth_url("oauth_url") - assert result == "oauth_url" - - @patch.dict( - os.environ, - {"CAMUNDA_OAUTH_URL": "CAMUNDA_OAUTH_URL", "ZEEBE_AUTHORIZATION_SERVER_URL": "ZEEBE_AUTHORIZATION_SERVER_URL"}, - ) - def test_camunda_oauth_url_has_second_highest_priority(self): - result = get_camunda_oauth_url(None) - assert result == "CAMUNDA_OAUTH_URL" @patch.dict(os.environ, {"ZEEBE_AUTHORIZATION_SERVER_URL": "ZEEBE_AUTHORIZATION_SERVER_URL"}) - def test_zeebe_authorization_server_url_has_third_highest_priority(self): - result = get_camunda_oauth_url(None) + def test_zeebe_authorization_server_url_has_second_highest_priority(self): + result = get_camunda_oauth_url("oauth_url") assert result == "ZEEBE_AUTHORIZATION_SERVER_URL" + def test_param_has_lowest_priority(self): + result = get_camunda_oauth_url("oauth_url") + + assert result == "oauth_url" + @patch.dict(os.environ, {}) def test_none_has_fourth_highest_priority(self): - result = get_camunda_oauth_url(None) - assert result is None + with pytest.raises(EnvironmentError): + get_camunda_oauth_url() class TestGetCamundaClientId: @patch.dict(os.environ, {"CAMUNDA_CLIENT_ID": "CAMUNDA_CLIENT_ID", "ZEEBE_CLIENT_ID": "ZEEBE_CLIENT_ID"}) - def test_is_calculated_from_parameters_as_highest_priority(self): - result = get_camunda_client_id("client_id_param") - - assert result == "client_id_param" - - @patch.dict(os.environ, {"CAMUNDA_CLIENT_ID": "CAMUNDA_CLIENT_ID", "ZEEBE_CLIENT_ID": "ZEEBE_CLIENT_ID"}) - def test_is_calculated_from_camunda_environment_variable_as_second_priority(self): - result = get_camunda_client_id(None) + def test_is_calculated_from_camunda_environment_variable_as_highest_priority(self): + result = get_camunda_client_id() assert result == "CAMUNDA_CLIENT_ID" @patch.dict(os.environ, {"ZEEBE_CLIENT_ID": "ZEEBE_CLIENT_ID"}) - def test_is_calculated_from_zeebe_environment_variable_as_third_priority(self): - result = get_camunda_client_id(None) + def test_is_calculated_from_zeebe_environment_variable_as_second_priority(self): + result = get_camunda_client_id() assert result == "ZEEBE_CLIENT_ID" @patch.dict(os.environ, {}) def test_throw_exception_if_not_configured(self): - with pytest.raises(ValueError): - get_camunda_client_id(None) + with pytest.raises(EnvironmentError): + get_camunda_client_id() class TestGetCamundaClientSecret: @patch.dict( os.environ, {"CAMUNDA_CLIENT_SECRET": "CAMUNDA_CLIENT_SECRET", "ZEEBE_CLIENT_SECRET": "ZEEBE_CLIENT_SECRET"} ) - def test_is_calculated_from_parameters_as_highest_priority(self): - result = get_camunda_client_secret("client_secret_param") - - assert result == "client_secret_param" - - @patch.dict( - os.environ, {"CAMUNDA_CLIENT_SECRET": "CAMUNDA_CLIENT_SECRET", "ZEEBE_CLIENT_SECRET": "ZEEBE_CLIENT_SECRET"} - ) - def test_is_calculated_from_camunda_environment_variable_as_second_priority(self): - result = get_camunda_client_secret(None) + def test_is_calculated_from_camunda_environment_variable_as_highest_priority(self): + result = get_camunda_client_secret() assert result == "CAMUNDA_CLIENT_SECRET" @patch.dict(os.environ, {"ZEEBE_CLIENT_SECRET": "ZEEBE_CLIENT_SECRET"}) - def test_is_calculated_from_zeebe_environment_variable_as_third_priority(self): - result = get_camunda_client_secret(None) + def test_is_calculated_from_zeebe_environment_variable_as_second_priority(self): + result = get_camunda_client_secret() assert result == "ZEEBE_CLIENT_SECRET" @patch.dict(os.environ, {}) def test_throw_exception_if_not_configured(self): - with pytest.raises(ValueError): - get_camunda_client_secret(None) + with pytest.raises(EnvironmentError): + get_camunda_client_secret() class TestGetCamundaCloudClusterId: @patch.dict(os.environ, {"CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID"}) - def test_is_calculated_from_parameters_as_highest_priority(self): - result = get_camunda_cluster_id("cluster_id_param") - - assert result == "cluster_id_param" - - @patch.dict(os.environ, {"CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID"}) - def test_is_calculated_from_camunda_environment_variable_as_second_priority(self): - result = get_camunda_cluster_id(None) + def test_is_calculated_from_camunda_environment_variable_as_highest_priority(self): + result = get_camunda_cluster_id() assert result == "CAMUNDA_CLUSTER_ID" - @patch.dict(os.environ, {}) - def test_none_has_third_highest_priority(self): - result = get_camunda_cluster_id(None) - assert result is None + def test_environment_error(self): + with pytest.raises(EnvironmentError): + get_camunda_cluster_id() class TestGetCamundaCloudClusterRegion: @patch.dict(os.environ, {"CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION"}) - def test_is_calculated_from_parameters_as_highest_priority(self): + def test_is_calculated_from_camunda_environment_variable_as_highest_priority(self): result = get_camunda_cluster_region("cluster_region_param") - assert result == "cluster_region_param" + assert result == "CAMUNDA_CLUSTER_REGION" - @patch.dict(os.environ, {"CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION"}) - def test_is_calculated_from_camunda_environment_variable_as_second_priority(self): - result = get_camunda_cluster_region(None) + def test_is_calculated_from_default_value_parameter_as_second_priority(self): + result = get_camunda_cluster_region("cluster_region_param") - assert result == "CAMUNDA_CLUSTER_REGION" + assert result == "cluster_region_param" @patch.dict(os.environ, {}) - def test_bru_2_has_third_highest_priority(self): - result = get_camunda_cluster_region(None) - assert result == "bru-2" + def test_raises_environment_error_if_no_default_value_as_fallback_provided(self): + with pytest.raises(EnvironmentError): + get_camunda_cluster_region() class TestGetCamundaTokenAudience: @@ -180,76 +142,49 @@ class TestGetCamundaTokenAudience: { "CAMUNDA_TOKEN_AUDIENCE": "CAMUNDA_TOKEN_AUDIENCE", "ZEEBE_TOKEN_AUDIENCE": "ZEEBE_TOKEN_AUDIENCE", - "CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", - "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION", }, ) - def test_is_calculated_from_parameters_as_highest_priority(self): + def test_is_calculated_from_camunda_token_audience_as_highest_priority(self): result = get_camunda_token_audience("token_audience_param") - assert result == "token_audience_param" - - @patch.dict( - os.environ, - { - "CAMUNDA_TOKEN_AUDIENCE": "CAMUNDA_TOKEN_AUDIENCE", - "ZEEBE_TOKEN_AUDIENCE": "ZEEBE_TOKEN_AUDIENCE", - "CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", - "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION", - }, - ) - def test_is_calculated_from_camunda_token_audience_as_second_highest_priority(self): - result = get_camunda_token_audience(None) - assert result == "CAMUNDA_TOKEN_AUDIENCE" @patch.dict( os.environ, { "ZEEBE_TOKEN_AUDIENCE": "ZEEBE_TOKEN_AUDIENCE", - "CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", - "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION", }, ) - def test_is_calculated_from_zeebe_token_audience_as_third_highest_priority(self): - result = get_camunda_token_audience(None) + def test_is_calculated_from_zeebe_token_audience_as_second_highest_priority(self): + result = get_camunda_token_audience("token_audience_param") assert result == "ZEEBE_TOKEN_AUDIENCE" - @patch.dict( - os.environ, {"CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION"} - ) - def test_is_calculated_from_camunda_cloud_hostname_as_fourth_highest_priority(self): - result = get_camunda_token_audience(None) + def test_is_calculated_from_camunda_token_audience_as_third_highest_priority(self): + result = get_camunda_token_audience("token_audience_param") - assert result == "CAMUNDA_CLUSTER_ID.CAMUNDA_CLUSTER_REGION.zeebe.camunda.io" + assert result == "token_audience_param" @patch.dict(os.environ, {}) - def test_is_none_as_fifth_highest_priority(self): - result = get_camunda_token_audience(None) - - assert result is None + def test_raises_environment_error_as_fourth_highest_priority(self): + with pytest.raises(EnvironmentError): + get_camunda_token_audience() -class TestGetCamundaCloudHostname: - @patch.dict( - os.environ, {"CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION"} - ) +class TestGetCamundaAddress: def test_is_calculated_from_parameters_as_highest_priority(self): - result = get_camunda_cloud_hostname("cluster_id_param", "camunda_region_param") + result = get_camunda_address("cluster_id_param", "camunda_region_param") - assert result == f"cluster_id_param.camunda_region_param.zeebe.camunda.io" + assert result == f"cluster_id_param.camunda_region_param.zeebe.camunda.io:443" - @patch.dict( - os.environ, {"CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION"} - ) - def test_is_calculated_from_environment_variables_as_second_priority(self): - result = get_camunda_cloud_hostname(None, None) + def test_raises_error_if_cluster_id_is_none(self): + with pytest.raises(EnvironmentError): + get_camunda_address(None, "camunda_region_param") - assert result == f"CAMUNDA_CLUSTER_ID.CAMUNDA_CLUSTER_REGION.zeebe.camunda.io" - - @patch.dict(os.environ, {}) - def test_returns_none_by_default(self): - result = get_camunda_cloud_hostname(None, None) + def test_raises_error_if_cluster_region_is_none(self): + with pytest.raises(EnvironmentError): + get_camunda_address("cluster_id_param", None) - assert result is None + def test_raises_error_if_all_args_are_none(self): + with pytest.raises(EnvironmentError): + get_camunda_address(None, None) From 0b2e3d86246b1b688ba2dec773bfc802243b14db Mon Sep 17 00:00:00 2001 From: PSanetra Date: Mon, 21 Oct 2024 12:20:26 +0200 Subject: [PATCH 3/9] feat: Introduce `Unset` default value in `create_secure_channel` and `create_insecure_channel` --- pyzeebe/channel/insecure_channel.py | 8 +++++--- pyzeebe/channel/secure_channel.py | 9 ++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pyzeebe/channel/insecure_channel.py b/pyzeebe/channel/insecure_channel.py index 6a990840..39e86360 100644 --- a/pyzeebe/channel/insecure_channel.py +++ b/pyzeebe/channel/insecure_channel.py @@ -4,11 +4,11 @@ from pyzeebe.channel.channel_options import get_channel_options from pyzeebe.channel.utils import get_zeebe_address -from pyzeebe.types import ChannelArgumentType +from pyzeebe.types import ChannelArgumentType, Unset def create_insecure_channel( - grpc_address: str | None = None, channel_options: ChannelArgumentType | None = None + grpc_address: str = Unset, channel_options: ChannelArgumentType | None = None ) -> grpc.aio.Channel: """ Create an insecure channel @@ -22,5 +22,7 @@ def create_insecure_channel( Returns: grpc.aio.Channel: A GRPC Channel connected to the Zeebe gateway. """ - grpc_address = grpc_address or get_zeebe_address() + if grpc_address is Unset: + grpc_address = get_zeebe_address() + return grpc.aio.insecure_channel(target=grpc_address, options=get_channel_options(channel_options)) diff --git a/pyzeebe/channel/secure_channel.py b/pyzeebe/channel/secure_channel.py index 17396a36..5ffb53da 100644 --- a/pyzeebe/channel/secure_channel.py +++ b/pyzeebe/channel/secure_channel.py @@ -4,11 +4,11 @@ from pyzeebe.channel.channel_options import get_channel_options from pyzeebe.channel.utils import get_zeebe_address -from pyzeebe.types import ChannelArgumentType +from pyzeebe.types import ChannelArgumentType, Unset def create_secure_channel( - grpc_address: str | None = None, + grpc_address: str = Unset, channel_options: ChannelArgumentType | None = None, channel_credentials: grpc.ChannelCredentials | None = None, ) -> grpc.aio.Channel: @@ -26,7 +26,10 @@ def create_secure_channel( Returns: grpc.aio.Channel: A GRPC Channel connected to the Zeebe gateway. """ - grpc_address = grpc_address or get_zeebe_address() + + if grpc_address is Unset: + grpc_address = get_zeebe_address() + credentials = channel_credentials or grpc.ssl_channel_credentials() return grpc.aio.secure_channel( target=grpc_address, credentials=credentials, options=get_channel_options(channel_options) From 4d6cb0ce0c9ec00e59f1b5290cf89de76ae7a239 Mon Sep 17 00:00:00 2001 From: PSanetra Date: Tue, 29 Oct 2024 11:42:04 +0100 Subject: [PATCH 4/9] docs: Add docs for channel configuration using environment variables --- docs/channels.rst | 1 + docs/channels_configuration.rst | 82 +++++++++++++++++++++++++++++++++ docs/channels_quickstart.rst | 10 ++-- 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 docs/channels_configuration.rst diff --git a/docs/channels.rst b/docs/channels.rst index 7a5cbbf4..0d45ebcc 100644 --- a/docs/channels.rst +++ b/docs/channels.rst @@ -6,4 +6,5 @@ Channels :name: channels Quickstart + Configuration Reference diff --git a/docs/channels_configuration.rst b/docs/channels_configuration.rst new file mode 100644 index 00000000..73fa079c --- /dev/null +++ b/docs/channels_configuration.rst @@ -0,0 +1,82 @@ +====================== +Channels Configuration +====================== + +This document describes the environment variables and configurations used for establishing different gRPC channel connections to Camunda (Zeebe) instances, either with or without authentication. + +Environment Variables +--------------------- + +The following environment variables are used to configure channels. The variables are grouped according to their relevance and usage context in each type of channel. + +These variables are only considered if a corresponding argument was not passed (Unset) during initialization of a channel. + +Common +~~~~~~~~~~~~~~~~ + +This variables is used across all types of channels: + +- **ZEEBE_ADDRESS** + + - **Description**: The default address of the Zeebe Gateway. + - **Usage**: Used in both secure and insecure channel configurations. + - **Default**: `localhost:26500` + +Common OAuth2 Variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These variables are specifically for connecting to generic OAuth2 or Camunda Cloud instances. + +- **CAMUNDA_CLIENT_ID** / **ZEEBE_CLIENT_ID** + + - **Description**: The client ID required for OAuth2 client credential authentication. + - **Usage**: Required for OAuth2 and Camunda Cloud channels. + +- **CAMUNDA_CLIENT_SECRET** / **ZEEBE_CLIENT_SECRET** + + - **Description**: The client secret for the OAuth2 client. + - **Usage**: Required for OAuth2 and Camunda Cloud channels. + +OAuth2 Variables (Self-Managed) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These variables are primarily used for OAuth2 authentication in self-managed Camunda 8 instances. + +- **CAMUNDA_OAUTH_URL** / **ZEEBE_AUTHORIZATION_SERVER_URL** + + - **Description**: Specifies the URL of the authorization server issuing access tokens to the client. + - **Usage**: Required if channel initialization argument was not specified. + +- **CAMUNDA_TOKEN_AUDIENCE** / **ZEEBE_TOKEN_AUDIENCE** + + - **Description**: Specifies the audience for the OAuth2 token. + - **Usage**: Used when creating OAuth2 or Camunda Cloud channels. + - **Default**: None if not provided. + +Camunda Cloud Variables (SaaS) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These variables are specifically for connecting to Camunda Cloud instances. + +- **CAMUNDA_OAUTH_URL** / **ZEEBE_AUTHORIZATION_SERVER_URL** + + - **Description**: Specifies the URL of the authorization server issuing access tokens to the client. + - **Usage**: Used in the OAuth2 and Camunda Cloud channel configurations. + - **Default**: `"https://login.cloud.camunda.io/oauth/token"` if not specified. + +- **CAMUNDA_CLUSTER_ID** + + - **Description**: The unique identifier for the Camunda Cloud cluster to connect to. + - **Usage**: Required if channel initialization argument was not specified. + +- **CAMUNDA_CLUSTER_REGION** + + - **Description**: The region where the Camunda Cloud cluster is hosted. + - **Usage**: Required for Camunda Cloud channels. + - **Default**: `"bru-2"` if not provided. + +- **CAMUNDA_TOKEN_AUDIENCE** / **ZEEBE_TOKEN_AUDIENCE** + + - **Description**: Specifies the audience for the OAuth2 token. + - **Usage**: Used when creating OAuth2 or Camunda Cloud channels. + - **Default**: `"zeebe.camunda.io"` if not provided. diff --git a/docs/channels_quickstart.rst b/docs/channels_quickstart.rst index e725c85c..95f22e89 100644 --- a/docs/channels_quickstart.rst +++ b/docs/channels_quickstart.rst @@ -19,10 +19,6 @@ This Channel can be configured with the parameters `channel_credentials` and `ch You can override the default `channel_options` by passing e.g. `channel_options = (("grpc.keepalive_time_ms", 60_000),)` - for a keepalive time of 60 seconds. - -Pyzeebe provides a couple standard ways to achieve this: - - Insecure -------- @@ -151,6 +147,12 @@ Example: This method use the :py:class:`.Oauth2ClientCredentialsMetadataPlugin` under the hood. +Configuration +------------- + +It is possible to omit any arguments to the channel initialization functions and instead provide environment variables. +See :doc:`Channels Configuration ` for additional details. + Custom Oauth2 Authorization Flow --------------------------------- From 7719bdd06060ae443d912223a198f5d0a71861ac Mon Sep 17 00:00:00 2001 From: PSanetra Date: Tue, 29 Oct 2024 11:54:40 +0100 Subject: [PATCH 5/9] test: Add tests using environment variables on channel initializations --- tests/unit/channel/insecure_channel_test.py | 11 +++++++ tests/unit/channel/oauth_channel_test.py | 36 +++++++++++++++++++++ tests/unit/channel/secure_channel_test.py | 11 +++++++ 3 files changed, 58 insertions(+) diff --git a/tests/unit/channel/insecure_channel_test.py b/tests/unit/channel/insecure_channel_test.py index 633286b3..35a07d83 100644 --- a/tests/unit/channel/insecure_channel_test.py +++ b/tests/unit/channel/insecure_channel_test.py @@ -1,3 +1,4 @@ +import os from unittest.mock import Mock, patch import grpc @@ -30,3 +31,13 @@ def test_uses_default_address(self, insecure_channel_mock: Mock): insecure_channel_call = insecure_channel_mock.mock_calls[0] assert insecure_channel_call.kwargs["target"] == get_zeebe_address() + + @patch.dict( + os.environ, + {"ZEEBE_ADDRESS": "ZEEBE_ADDRESS"}, + ) + def test_uses_zeebe_address_environment_variable(self, insecure_channel_mock: Mock): + create_insecure_channel() + + insecure_channel_call = insecure_channel_mock.mock_calls[0] + assert insecure_channel_call.kwargs["target"] == "ZEEBE_ADDRESS" diff --git a/tests/unit/channel/oauth_channel_test.py b/tests/unit/channel/oauth_channel_test.py index 19e122c3..cc6a6b9d 100644 --- a/tests/unit/channel/oauth_channel_test.py +++ b/tests/unit/channel/oauth_channel_test.py @@ -1,3 +1,4 @@ +import os from unittest import mock import grpc @@ -42,6 +43,24 @@ def test_create_oauth2_client_credentials_channel( assert isinstance(channel, grpc.aio.Channel) +@mock.patch.dict( + os.environ, + { + "ZEEBE_ADDRESS": "ZEEBE_ADDRESS", + "CAMUNDA_CLIENT_ID": "CAMUNDA_CLIENT_ID", + "CAMUNDA_CLIENT_SECRET": "CAMUNDA_CLIENT_SECRET", + "CAMUNDA_OAUTH_URL": "CAMUNDA_OAUTH_URL", + "CAMUNDA_TOKEN_AUDIENCE": "CAMUNDA_TOKEN_AUDIENCE", + }, +) +def test_create_oauth2_client_credentials_channel_using_environment_variables( + mock_oauth2metadataplugin, +): + channel = create_oauth2_client_credentials_channel() + + assert isinstance(channel, grpc.aio.Channel) + + def test_create_camunda_cloud_channel( mock_oauth2metadataplugin, ): @@ -68,3 +87,20 @@ def test_create_camunda_cloud_channel( ) assert isinstance(channel, grpc.aio.Channel) + + +@mock.patch.dict( + os.environ, + { + "CAMUNDA_CLUSTER_ID": "CAMUNDA_CLUSTER_ID", + "CAMUNDA_CLUSTER_REGION": "CAMUNDA_CLUSTER_REGION", + "CAMUNDA_CLIENT_ID": "CAMUNDA_CLIENT_ID", + "CAMUNDA_CLIENT_SECRET": "CAMUNDA_CLIENT_SECRET", + }, +) +def test_create_camunda_cloud_channel_using_environment_variables( + mock_oauth2metadataplugin, +): + channel = create_camunda_cloud_channel() + + assert isinstance(channel, grpc.aio.Channel) diff --git a/tests/unit/channel/secure_channel_test.py b/tests/unit/channel/secure_channel_test.py index 1ed667b7..d9bf419c 100644 --- a/tests/unit/channel/secure_channel_test.py +++ b/tests/unit/channel/secure_channel_test.py @@ -1,3 +1,4 @@ +import os from unittest.mock import Mock, patch import grpc @@ -36,3 +37,13 @@ def test_uses_default_address(self, secure_channel_mock: Mock): secure_channel_call = secure_channel_mock.mock_calls[0] assert secure_channel_call.kwargs["target"] == get_zeebe_address() + + @patch.dict( + os.environ, + {"ZEEBE_ADDRESS": "ZEEBE_ADDRESS"}, + ) + def test_uses_zeebe_address_environment_variable(self, secure_channel_mock: Mock): + create_secure_channel() + + secure_channel_call = secure_channel_mock.mock_calls[0] + assert secure_channel_call.kwargs["target"] == "ZEEBE_ADDRESS" From 50daad07004ceddd1bcf8ba9c4b80bc34334c912 Mon Sep 17 00:00:00 2001 From: Philip Sanetra Date: Thu, 31 Oct 2024 13:33:26 +0100 Subject: [PATCH 6/9] docs: Apply PR suggestions Co-authored-by: Felix Schneider <77450740+felicijus@users.noreply.github.com> --- docs/channels_configuration.rst | 138 ++++++++++++++++++++------------ docs/channels_reference.rst | 20 +++++ pyzeebe/channel/utils.py | 112 ++++++++++++++++---------- 3 files changed, 179 insertions(+), 91 deletions(-) diff --git a/docs/channels_configuration.rst b/docs/channels_configuration.rst index 73fa079c..7f3a57c5 100644 --- a/docs/channels_configuration.rst +++ b/docs/channels_configuration.rst @@ -11,72 +11,110 @@ The following environment variables are used to configure channels. The variable These variables are only considered if a corresponding argument was not passed (Unset) during initialization of a channel. -Common -~~~~~~~~~~~~~~~~ +Common Variables +---------------- This variables is used across all types of channels: -- **ZEEBE_ADDRESS** - - - **Description**: The default address of the Zeebe Gateway. - - **Usage**: Used in both secure and insecure channel configurations. - - **Default**: `localhost:26500` +**ZEEBE_ADDRESS** + :Description: + The default address of the Zeebe Gateway. + + :Usage: + Used in both secure and insecure channel configurations. + :func:`pyzeebe.create_insecure_channel` and :func:`pyzeebe.create_secure_channel`. + + :Default: + `"localhost:26500"` Common OAuth2 Variables -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +----------------------- These variables are specifically for connecting to generic OAuth2 or Camunda Cloud instances. -- **CAMUNDA_CLIENT_ID** / **ZEEBE_CLIENT_ID** - - - **Description**: The client ID required for OAuth2 client credential authentication. - - **Usage**: Required for OAuth2 and Camunda Cloud channels. - -- **CAMUNDA_CLIENT_SECRET** / **ZEEBE_CLIENT_SECRET** - - - **Description**: The client secret for the OAuth2 client. - - **Usage**: Required for OAuth2 and Camunda Cloud channels. +**CAMUNDA_CLIENT_ID** / **ZEEBE_CLIENT_ID** + :Description: + The client ID required for OAuth2 client credential authentication. + + :Usage: + Required for OAuth2 and Camunda Cloud channels. + :func:`pyzeebe.create_oauth2_client_credentials_channel` and :func:`pyzeebe.create_camunda_cloud_channel`. + +**CAMUNDA_CLIENT_SECRET** / **ZEEBE_CLIENT_SECRET** + :Description: + The client secret for the OAuth2 client. + + :Usage: + Required for OAuth2 and Camunda Cloud channels. + :func:`pyzeebe.create_oauth2_client_credentials_channel` and :func:`pyzeebe.create_camunda_cloud_channel`. OAuth2 Variables (Self-Managed) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------- These variables are primarily used for OAuth2 authentication in self-managed Camunda 8 instances. -- **CAMUNDA_OAUTH_URL** / **ZEEBE_AUTHORIZATION_SERVER_URL** - - - **Description**: Specifies the URL of the authorization server issuing access tokens to the client. - - **Usage**: Required if channel initialization argument was not specified. - -- **CAMUNDA_TOKEN_AUDIENCE** / **ZEEBE_TOKEN_AUDIENCE** - - - **Description**: Specifies the audience for the OAuth2 token. - - **Usage**: Used when creating OAuth2 or Camunda Cloud channels. - - **Default**: None if not provided. +**CAMUNDA_OAUTH_URL** / **ZEEBE_AUTHORIZATION_SERVER_URL** + :Description: + Specifies the URL of the authorization server issuing access tokens to the client. + + :Usage: + Required if channel initialization argument was not specified. + :func:`pyzeebe.create_oauth2_client_credentials_channel`. + +**CAMUNDA_TOKEN_AUDIENCE** / **ZEEBE_TOKEN_AUDIENCE** + :Description: + Specifies the audience for the OAuth2 token. + + :Usage: + Used when creating OAuth2 or Camunda Cloud channels. + + :Default: + None if not provided. + :func:`pyzeebe.create_oauth2_client_credentials_channel`. Camunda Cloud Variables (SaaS) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------ These variables are specifically for connecting to Camunda Cloud instances. -- **CAMUNDA_OAUTH_URL** / **ZEEBE_AUTHORIZATION_SERVER_URL** - - - **Description**: Specifies the URL of the authorization server issuing access tokens to the client. - - **Usage**: Used in the OAuth2 and Camunda Cloud channel configurations. - - **Default**: `"https://login.cloud.camunda.io/oauth/token"` if not specified. - -- **CAMUNDA_CLUSTER_ID** - - - **Description**: The unique identifier for the Camunda Cloud cluster to connect to. - - **Usage**: Required if channel initialization argument was not specified. - -- **CAMUNDA_CLUSTER_REGION** - - - **Description**: The region where the Camunda Cloud cluster is hosted. - - **Usage**: Required for Camunda Cloud channels. - - **Default**: `"bru-2"` if not provided. - -- **CAMUNDA_TOKEN_AUDIENCE** / **ZEEBE_TOKEN_AUDIENCE** +**CAMUNDA_OAUTH_URL** / **ZEEBE_AUTHORIZATION_SERVER_URL** + :Description: + Specifies the URL of the authorization server issuing access tokens to the client. + + :Usage: + Used in the OAuth2 and Camunda Cloud channel configurations. + + :Default: + `"https://login.cloud.camunda.io/oauth/token"` if not specified. + :func:`pyzeebe.create_camunda_cloud_channel`. + +**CAMUNDA_CLUSTER_ID** + :Description: + The unique identifier for the Camunda Cloud cluster to connect to. + + :Usage: + Required if channel initialization argument was not specified. + :func:`pyzeebe.create_camunda_cloud_channel`. + +**CAMUNDA_CLUSTER_REGION** + :Description: + The region where the Camunda Cloud cluster is hosted. + + :Usage: + Required for Camunda Cloud channels. + + :Default: + `"bru-2"` if not provided. + :func:`pyzeebe.create_camunda_cloud_channel`. + +**CAMUNDA_TOKEN_AUDIENCE** / **ZEEBE_TOKEN_AUDIENCE** + :Description: + Specifies the audience for the OAuth2 token. + + :Usage: + Used when creating OAuth2 or Camunda Cloud channels. + + :Default: + `"zeebe.camunda.io"` if not provided. + :func:`pyzeebe.create_camunda_cloud_channel`. - - **Description**: Specifies the audience for the OAuth2 token. - - **Usage**: Used when creating OAuth2 or Camunda Cloud channels. - - **Default**: `"zeebe.camunda.io"` if not provided. diff --git a/docs/channels_reference.rst b/docs/channels_reference.rst index f9d26799..61e14dc3 100644 --- a/docs/channels_reference.rst +++ b/docs/channels_reference.rst @@ -26,3 +26,23 @@ Credentials :members: :special-members: :private-members: + + +Utilities (Environment) +----------------------- + +.. autofunction:: pyzeebe.channel.utils.get_zeebe_address + +.. autofunction:: pyzeebe.channel.utils.get_camunda_oauth_url + +.. autofunction:: pyzeebe.channel.utils.get_camunda_client_id + +.. autofunction:: pyzeebe.channel.utils.get_camunda_client_secret + +.. autofunction:: pyzeebe.channel.utils.get_camunda_cluster_id + +.. autofunction:: pyzeebe.channel.utils.get_camunda_cluster_region + +.. autofunction:: pyzeebe.channel.utils.get_camunda_token_audience + +.. autofunction:: pyzeebe.channel.utils.get_camunda_address diff --git a/pyzeebe/channel/utils.py b/pyzeebe/channel/utils.py index e218982b..e770e635 100644 --- a/pyzeebe/channel/utils.py +++ b/pyzeebe/channel/utils.py @@ -5,28 +5,33 @@ DEFAULT_ZEEBE_ADDRESS = "localhost:26500" -def get_zeebe_address(default_value: str | None = None) -> str: +def get_zeebe_address(default: str = None) -> str: """ + Get the Zeebe Gateway Address. + Args: - default_value (str, optional): Default value to be used if no other value was discovered. - Returns: Value from ZEEBE_ADDRESS environment variable - or provided default_value - or "localhost:26500" - """ + default (str, optional): Default value to be used if no other value was discovered. - return os.getenv("ZEEBE_ADDRESS") or default_value or DEFAULT_ZEEBE_ADDRESS + Returns: + str: ZEEBE_ADDRESS environment variable or provided default or "localhost:26500" + """ + return os.getenv("ZEEBE_ADDRESS") or default or DEFAULT_ZEEBE_ADDRESS -def get_camunda_oauth_url(default_value: str | None = None) -> str: +def get_camunda_oauth_url(default: str = None) -> str: """ + Get the Camunda OAuth URL or Zeebe Authorization Server URL. + Args: - default_value (str, optional): Default value to be used if no other value was discovered. - Returns: The camunda platform authorization server url. - Default: Value from CAMUNDA_OAUTH_URL - or ZEEBE_AUTHORIZATION_SERVER_URL environment variable - or provided default_value + default (str, optional): Default value to be used if no other value was discovered. + + Returns: + str: CAMUNDA_OAUTH_URL or ZEEBE_AUTHORIZATION_SERVER_URL environment variable or provided default + + Raises: + EnvironmentError: If neither CAMUNDA_OAUTH_URL nor ZEEBE_AUTHORIZATION_SERVER_URL is provided. """ - r = os.getenv("CAMUNDA_OAUTH_URL") or os.getenv("ZEEBE_AUTHORIZATION_SERVER_URL") or default_value + r = os.getenv("CAMUNDA_OAUTH_URL") or os.getenv("ZEEBE_AUTHORIZATION_SERVER_URL") or default if r is None: raise EnvironmentError("No CAMUNDA_OAUTH_URL or ZEEBE_AUTHORIZATION_SERVER_URL provided!") @@ -36,8 +41,13 @@ def get_camunda_oauth_url(default_value: str | None = None) -> str: def get_camunda_client_id() -> str: """ - Returns: The client id. - Default: Value from CAMUNDA_CLIENT_ID or ZEEBE_CLIENT_ID environment variable + Get the Camunda Client ID. + + Returns: + str: CAMUNDA_CLIENT_ID or ZEEBE_CLIENT_ID environment variable + + Raises: + EnvironmentError: If neither CAMUNDA_CLIENT_ID nor ZEEBE_CLIENT_ID is provided. """ r = os.getenv("CAMUNDA_CLIENT_ID") or os.getenv("ZEEBE_CLIENT_ID") @@ -49,11 +59,14 @@ def get_camunda_client_id() -> str: def get_camunda_client_secret() -> str: """ + Get the Camunda Client Secret. + Returns: - str: The client secret. - Default: Value from CAMUNDA_CLIENT_SECRET or ZEEBE_CLIENT_SECRET environment variable - """ + str: CAMUNDA_CLIENT_SECRET or ZEEBE_CLIENT_SECRET environment variable + Raises: + EnvironmentError: If neither CAMUNDA_CLIENT_SECRET nor ZEEBE_CLIENT_SECRET is provided. + """ r = os.getenv("CAMUNDA_CLIENT_SECRET") or os.getenv("ZEEBE_CLIENT_SECRET") if r is None: @@ -64,10 +77,14 @@ def get_camunda_client_secret() -> str: def get_camunda_cluster_id() -> str: """ - Returns: The camunda cluster id. - Default: Value from CAMUNDA_CLUSTER_ID environment variable - """ + Get the Camunda Cluster ID. + Returns: + str: CAMUNDA_CLUSTER_ID environment variable + + Raises: + EnvironmentError: If CAMUNDA_CLUSTER_ID is not provided. + """ r = os.getenv("CAMUNDA_CLUSTER_ID") if r is None: @@ -76,15 +93,20 @@ def get_camunda_cluster_id() -> str: return r -def get_camunda_cluster_region(default_value: str | None = None) -> str: +def get_camunda_cluster_region(default: str = None) -> str: """ + Get the Camunda Cluster Region. + Args: - default_value (str, optional): Default value to be used if no other value was discovered. - Returns: The camunda cluster region. - Default: Value from CAMUNDA_CLUSTER_REGION environment variable - """ + default (str, optional): Default value to be used if no other value was discovered. - r = os.getenv("CAMUNDA_CLUSTER_REGION") or default_value + Returns: + str: CAMUNDA_CLUSTER_REGION environment variable or provided default + + Raises: + EnvironmentError: If CAMUNDA_CLUSTER_REGION is not provided. + """ + r = os.getenv("CAMUNDA_CLUSTER_REGION") or default if r is None: raise EnvironmentError("No CAMUNDA_CLUSTER_REGION provided!") @@ -92,16 +114,20 @@ def get_camunda_cluster_region(default_value: str | None = None) -> str: return r -def get_camunda_token_audience(default_value: str | None = None) -> str: +def get_camunda_token_audience(default: str = None) -> str: """ + Get the Camunda Token Audience. + Args: - default_value (str, optional): Default value to be used if no other value was discovered. - Returns: The token audience. - Default: Value from CAMUNDA_TOKEN_AUDIENCE - or ZEEBE_TOKEN_AUDIENCE environment variable - or provided default_value + default (str, optional): Default value to be used if no other value was discovered. + + Returns: + str: CAMUNDA_TOKEN_AUDIENCE or ZEEBE_TOKEN_AUDIENCE environment variable or provided default + + Raises: + EnvironmentError: If neither CAMUNDA_TOKEN_AUDIENCE nor ZEEBE_TOKEN_AUDIENCE is provided. """ - r = os.getenv("CAMUNDA_TOKEN_AUDIENCE") or os.getenv("ZEEBE_TOKEN_AUDIENCE") or default_value + r = os.getenv("CAMUNDA_TOKEN_AUDIENCE") or os.getenv("ZEEBE_TOKEN_AUDIENCE") or default if r is None: raise EnvironmentError("No CAMUNDA_TOKEN_AUDIENCE or ZEEBE_TOKEN_AUDIENCE provided!") @@ -111,17 +137,21 @@ def get_camunda_token_audience(default_value: str | None = None) -> str: def get_camunda_address(cluster_id: str | None = None, cluster_region: str | None = None) -> str: """ + Get the Camunda Cloud gRPC server address. + Args: - cluster_id (str, optional): The camunda cluster id provided as parameter. - Default: Value from CAMUNDA_CLUSTER_ID environment variable or None - cluster_region (str, optional): The camunda cluster region provided as parameter. + cluster_id (str, optional): The Camunda cluster ID provided as parameter. + Default: Value from CAMUNDA_CLUSTER_ID environment variable or None. + cluster_region (str, optional): The Camunda cluster region provided as parameter. Default: Value from CAMUNDA_CLUSTER_REGION environment variable. + Returns: - str: The Camunda Cloud grpc server address. - """ + str: The Camunda Cloud gRPC server address. + Raises: + EnvironmentError: If either cluster_id or cluster_region is not provided. + """ if (cluster_id is None) or (cluster_region is None): raise EnvironmentError("The cluster_id and cluster_region must be provided!") - # if (cluster_id is not None) and (cluster_region is not None): # and return f"{cluster_id}.{cluster_region}.zeebe.camunda.io:443" From 58f740455e7b3800387a4fa429b54f4196fcaa77 Mon Sep 17 00:00:00 2001 From: Philip Sanetra Date: Thu, 31 Oct 2024 14:37:10 +0100 Subject: [PATCH 7/9] Update docs/channels_configuration.rst Co-authored-by: Felix Schneider <77450740+felicijus@users.noreply.github.com> --- docs/channels_configuration.rst | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/docs/channels_configuration.rst b/docs/channels_configuration.rst index 7f3a57c5..ddeab0ce 100644 --- a/docs/channels_configuration.rst +++ b/docs/channels_configuration.rst @@ -22,10 +22,11 @@ This variables is used across all types of channels: :Usage: Used in both secure and insecure channel configurations. - :func:`pyzeebe.create_insecure_channel` and :func:`pyzeebe.create_secure_channel`. + :func:`pyzeebe.create_insecure_channel` + :func:`pyzeebe.create_secure_channel` :Default: - `"localhost:26500"` + ``"localhost:26500"`` Common OAuth2 Variables ----------------------- @@ -38,7 +39,8 @@ These variables are specifically for connecting to generic OAuth2 or Camunda Clo :Usage: Required for OAuth2 and Camunda Cloud channels. - :func:`pyzeebe.create_oauth2_client_credentials_channel` and :func:`pyzeebe.create_camunda_cloud_channel`. + :func:`pyzeebe.create_oauth2_client_credentials_channel` + :func:`pyzeebe.create_camunda_cloud_channel` **CAMUNDA_CLIENT_SECRET** / **ZEEBE_CLIENT_SECRET** :Description: @@ -46,7 +48,8 @@ These variables are specifically for connecting to generic OAuth2 or Camunda Clo :Usage: Required for OAuth2 and Camunda Cloud channels. - :func:`pyzeebe.create_oauth2_client_credentials_channel` and :func:`pyzeebe.create_camunda_cloud_channel`. + :func:`pyzeebe.create_oauth2_client_credentials_channel` + :func:`pyzeebe.create_camunda_cloud_channel` OAuth2 Variables (Self-Managed) ------------------------------- @@ -59,7 +62,7 @@ These variables are primarily used for OAuth2 authentication in self-managed Cam :Usage: Required if channel initialization argument was not specified. - :func:`pyzeebe.create_oauth2_client_credentials_channel`. + :func:`pyzeebe.create_oauth2_client_credentials_channel` **CAMUNDA_TOKEN_AUDIENCE** / **ZEEBE_TOKEN_AUDIENCE** :Description: @@ -67,10 +70,10 @@ These variables are primarily used for OAuth2 authentication in self-managed Cam :Usage: Used when creating OAuth2 or Camunda Cloud channels. + :func:`pyzeebe.create_oauth2_client_credentials_channel` :Default: - None if not provided. - :func:`pyzeebe.create_oauth2_client_credentials_channel`. + ``None`` if not provided. Camunda Cloud Variables (SaaS) ------------------------------ @@ -83,10 +86,10 @@ These variables are specifically for connecting to Camunda Cloud instances. :Usage: Used in the OAuth2 and Camunda Cloud channel configurations. + :func:`pyzeebe.create_camunda_cloud_channel` :Default: - `"https://login.cloud.camunda.io/oauth/token"` if not specified. - :func:`pyzeebe.create_camunda_cloud_channel`. + ``"https://login.cloud.camunda.io/oauth/token"`` if not specified. **CAMUNDA_CLUSTER_ID** :Description: @@ -94,7 +97,7 @@ These variables are specifically for connecting to Camunda Cloud instances. :Usage: Required if channel initialization argument was not specified. - :func:`pyzeebe.create_camunda_cloud_channel`. + :func:`pyzeebe.create_camunda_cloud_channel` **CAMUNDA_CLUSTER_REGION** :Description: @@ -102,10 +105,10 @@ These variables are specifically for connecting to Camunda Cloud instances. :Usage: Required for Camunda Cloud channels. + :func:`pyzeebe.create_camunda_cloud_channel` :Default: - `"bru-2"` if not provided. - :func:`pyzeebe.create_camunda_cloud_channel`. + ``"bru-2"`` if not provided. **CAMUNDA_TOKEN_AUDIENCE** / **ZEEBE_TOKEN_AUDIENCE** :Description: @@ -113,8 +116,8 @@ These variables are specifically for connecting to Camunda Cloud instances. :Usage: Used when creating OAuth2 or Camunda Cloud channels. + :func:`pyzeebe.create_camunda_cloud_channel` :Default: - `"zeebe.camunda.io"` if not provided. - :func:`pyzeebe.create_camunda_cloud_channel`. + ``"zeebe.camunda.io"`` if not provided. From f951a9a4ff304888877943136c968bead80049e4 Mon Sep 17 00:00:00 2001 From: PSanetra Date: Thu, 7 Nov 2024 09:57:10 +0100 Subject: [PATCH 8/9] chore: Fix linting issues and apply PR suggestion --- pyzeebe/channel/utils.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pyzeebe/channel/utils.py b/pyzeebe/channel/utils.py index e770e635..57fbe015 100644 --- a/pyzeebe/channel/utils.py +++ b/pyzeebe/channel/utils.py @@ -5,7 +5,7 @@ DEFAULT_ZEEBE_ADDRESS = "localhost:26500" -def get_zeebe_address(default: str = None) -> str: +def get_zeebe_address(default: str | None = None) -> str: """ Get the Zeebe Gateway Address. @@ -18,7 +18,7 @@ def get_zeebe_address(default: str = None) -> str: return os.getenv("ZEEBE_ADDRESS") or default or DEFAULT_ZEEBE_ADDRESS -def get_camunda_oauth_url(default: str = None) -> str: +def get_camunda_oauth_url(default: str | None = None) -> str: """ Get the Camunda OAuth URL or Zeebe Authorization Server URL. @@ -93,7 +93,7 @@ def get_camunda_cluster_id() -> str: return r -def get_camunda_cluster_region(default: str = None) -> str: +def get_camunda_cluster_region(default: str | None = None) -> str: """ Get the Camunda Cluster Region. @@ -114,7 +114,7 @@ def get_camunda_cluster_region(default: str = None) -> str: return r -def get_camunda_token_audience(default: str = None) -> str: +def get_camunda_token_audience(default: str | None = None) -> str: """ Get the Camunda Token Audience. @@ -141,9 +141,7 @@ def get_camunda_address(cluster_id: str | None = None, cluster_region: str | Non Args: cluster_id (str, optional): The Camunda cluster ID provided as parameter. - Default: Value from CAMUNDA_CLUSTER_ID environment variable or None. cluster_region (str, optional): The Camunda cluster region provided as parameter. - Default: Value from CAMUNDA_CLUSTER_REGION environment variable. Returns: str: The Camunda Cloud gRPC server address. From 84c4162b3fdb489b2e66ea02301da54698489fd8 Mon Sep 17 00:00:00 2001 From: PSanetra Date: Fri, 8 Nov 2024 10:02:47 +0100 Subject: [PATCH 9/9] docs: Fix docs indentation for `make html` --- pyzeebe/channel/oauth_channel.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyzeebe/channel/oauth_channel.py b/pyzeebe/channel/oauth_channel.py index 65994786..a4382cb4 100644 --- a/pyzeebe/channel/oauth_channel.py +++ b/pyzeebe/channel/oauth_channel.py @@ -39,8 +39,8 @@ def create_oauth2_client_credentials_channel( Args: grpc_address (str, optional): Zeebe Gateway Address. Defaults to value from ZEEBE_ADDRESS environment variable - or "{CAMUNDA_CLUSTER_ID}.{CAMUNDA_CLUSTER_REGION}.zeebe.camunda.io:443" - or "localhost:26500". + or "{CAMUNDA_CLUSTER_ID}.{CAMUNDA_CLUSTER_REGION}.zeebe.camunda.io:443" + or "localhost:26500". client_id (str, optional): The client id. Defaults to value from CAMUNDA_CLIENT_ID or ZEEBE_CLIENT_ID environment variable client_secret (str, optional): The client secret. @@ -139,13 +139,13 @@ def create_camunda_cloud_channel( authorization_server (str, optional): The authorization server issuing access tokens to the client after successfully authenticating the client. Defaults to value from CAMUNDA_OAUTH_URL - or ZEEBE_AUTHORIZATION_SERVER_URL environment variable - or "https://login.cloud.camunda.io/oauth/token". + or ZEEBE_AUTHORIZATION_SERVER_URL environment variable + or "https://login.cloud.camunda.io/oauth/token". scope (str | None, optional): The scope of the access request. audience (str | None, optional): The audience for authentication. Defaults to value from CAMUNDA_TOKEN_AUDIENCE - or ZEEBE_TOKEN_AUDIENCE environment variable - or "zeebe.camunda.io". + or ZEEBE_TOKEN_AUDIENCE environment variable + or "zeebe.camunda.io". channel_credentials (grpc.ChannelCredentials): The gRPC channel credentials. Defaults to grpc.ssl_channel_credentials().