From 77a6339dc544b0ef22fd4a91ff48ce0822337d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Wed, 16 Sep 2026 10:04:37 +0200 Subject: [PATCH 1/7] feat(credentials): add credentials types and utils (#7850) --- pyoaev/contracts/contract_config.py | 16 +++++++ pyoaev/credential/__init__.py | 16 +++++++ pyoaev/credential/types.py | 16 +++++++ .../test_contract_referenced_credential.py | 48 +++++++++++++++++++ test/credential/__init__.py | 0 test/credential/test_credential_types.py | 29 +++++++++++ test/credential/test_credential_utils.py | 45 +++++++++++++++++ 7 files changed, 170 insertions(+) create mode 100644 pyoaev/credential/__init__.py create mode 100644 pyoaev/credential/types.py create mode 100644 test/contracts/test_contract_referenced_credential.py create mode 100644 test/credential/__init__.py create mode 100644 test/credential/test_credential_types.py create mode 100644 test/credential/test_credential_utils.py diff --git a/pyoaev/contracts/contract_config.py b/pyoaev/contracts/contract_config.py index 1fead7d0..a4373329 100644 --- a/pyoaev/contracts/contract_config.py +++ b/pyoaev/contracts/contract_config.py @@ -5,6 +5,7 @@ from typing import Dict, List, Optional from pyoaev import utils +from pyoaev.credential.types import CredentialType from pyoaev.contracts.contract_utils import ContractCardinality, ContractVariable from pyoaev.contracts.variable_helper import VariableHelper @@ -31,11 +32,13 @@ class ContractFieldType(str, Enum): AssetGroup: str = "asset-group" AiTarget: str = "ai-target" Payload: str = "payload" + CredentialReference: str = "credential-reference" class ContractFieldKey(str, Enum): Asset: str = "assets" AssetGroup: str = "asset_groups" + CredentialReference: str = "credential_reference" class ContractOutputType(str, Enum): @@ -365,6 +368,19 @@ def get_type(self) -> str: return ContractFieldType.Attachment.value +@dataclass +class ContractReferencedCredential(ContractElement): + key: str = field(default=ContractFieldKey.CredentialReference.value, init=False) + label: str = "Select a credential reference" + mandatory: bool = True + credential_reference_type: Optional[CredentialType] = None + multiple: bool = False + + @property + def get_type(self) -> str: + return ContractFieldType.CredentialReference.value + + @dataclass class ContractExpectations(ContractCardinalityElement): cardinality: str = ContractCardinality.Multiple diff --git a/pyoaev/credential/__init__.py b/pyoaev/credential/__init__.py new file mode 100644 index 00000000..447600db --- /dev/null +++ b/pyoaev/credential/__init__.py @@ -0,0 +1,16 @@ +from .types import CredentialType + + +def build_single_referenced_credential_element(provider_name: str): + from .utils import ( + build_single_referenced_credential_element as _build_single_referenced_credential_element, + ) + + return _build_single_referenced_credential_element(provider_name) + +__all__ = [ + "CredentialType", + "build_single_referenced_credential_element", +] + + diff --git a/pyoaev/credential/types.py b/pyoaev/credential/types.py new file mode 100644 index 00000000..f2600a4d --- /dev/null +++ b/pyoaev/credential/types.py @@ -0,0 +1,16 @@ +"""Credential reference types shared with the OpenAEV platform. + +This enum is the Python mirror of the OpenAEV backend credential-type enum +used by credential secret references. Values must stay label-for-label in sync +with the platform so contracts can declare credential references without any +translation layer. +""" + +from enum import Enum + + +class CredentialType(str, Enum): + IDENTITY = "IDENTITY" + CLOUD_AWS = "CLOUD_AWS" + CLOUD_AZURE = "CLOUD_AZURE" + CLOUD_GCP = "CLOUD_GCP" diff --git a/test/contracts/test_contract_referenced_credential.py b/test/contracts/test_contract_referenced_credential.py new file mode 100644 index 00000000..66170292 --- /dev/null +++ b/test/contracts/test_contract_referenced_credential.py @@ -0,0 +1,48 @@ +import json +import unittest + +from pyoaev import utils +from pyoaev.contracts.contract_config import ( + ContractFieldKey, + ContractFieldType, + ContractReferencedCredential, +) +from pyoaev.credential.types import CredentialType + + +def _serialize(field): + return json.loads(json.dumps(field, cls=utils.EnhancedJSONEncoder)) + + +class ContractReferencedCredentialTest(unittest.TestCase): + def test_defaults_match_platform_contract_expectations(self): + field = ContractReferencedCredential() + serialized = _serialize(field) + + self.assertEqual(field.key, ContractFieldKey.CredentialReference.value) + self.assertEqual(field.label, "Select a credential reference") + self.assertTrue(field.mandatory) + self.assertFalse(field.multiple) + self.assertIsNone(field.credential_reference_type) + self.assertEqual(field.type, ContractFieldType.CredentialReference.value) + self.assertEqual(serialized["key"], "credential_reference") + self.assertEqual(serialized["type"], "credential-reference") + self.assertEqual(serialized["label"], "Select a credential reference") + self.assertTrue(serialized["mandatory"]) + self.assertFalse(serialized["multiple"]) + self.assertIsNone(serialized["credential_reference_type"]) + + def test_explicit_credential_type_serializes_to_platform_label(self): + field = ContractReferencedCredential( + credential_reference_type=CredentialType.CLOUD_AZURE + ) + + self.assertEqual(field.credential_reference_type, CredentialType.CLOUD_AZURE) + self.assertEqual( + _serialize(field)["credential_reference_type"], "CLOUD_AZURE" + ) + + +if __name__ == "__main__": + unittest.main() + diff --git a/test/credential/__init__.py b/test/credential/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/credential/test_credential_types.py b/test/credential/test_credential_types.py new file mode 100644 index 00000000..378fe233 --- /dev/null +++ b/test/credential/test_credential_types.py @@ -0,0 +1,29 @@ +import unittest + +from pyoaev.credential.types import CredentialType + + +class CredentialTypeTest(unittest.TestCase): + def test_every_value_matches_the_supported_platform_labels(self): + expected_labels = { + "IDENTITY", + "CLOUD_AWS", + "CLOUD_AZURE", + "CLOUD_GCP", + } + actual_labels = {member.value for member in CredentialType} + self.assertEqual(actual_labels, expected_labels) + + def test_identity_wire_label(self): + self.assertEqual(CredentialType.IDENTITY.value, "IDENTITY") + self.assertEqual(CredentialType.IDENTITY, "IDENTITY") + + def test_aws_wire_label(self): + self.assertEqual(CredentialType.CLOUD_AWS.value, "CLOUD_AWS") + self.assertEqual(CredentialType.CLOUD_AWS, "CLOUD_AWS") + + +if __name__ == "__main__": + unittest.main() + + diff --git a/test/credential/test_credential_utils.py b/test/credential/test_credential_utils.py new file mode 100644 index 00000000..79bea309 --- /dev/null +++ b/test/credential/test_credential_utils.py @@ -0,0 +1,45 @@ +import json +import unittest + +from pyoaev import utils +from pyoaev.contracts.contract_config import ContractReferencedCredential +from pyoaev.credential.types import CredentialType +from pyoaev.credential.utils import build_single_referenced_credential_element + + +def _serialize(field): + return json.loads(json.dumps(field, cls=utils.EnhancedJSONEncoder)) + + +class CredentialUtilsTest(unittest.TestCase): + def test_aws_provider_maps_to_aws_credential_reference(self): + field = build_single_referenced_credential_element("aws") + + self.assertIsInstance(field, ContractReferencedCredential) + self.assertEqual(field.credential_reference_type, CredentialType.CLOUD_AWS) + + def test_eks_provider_maps_to_aws_credential_reference(self): + field = build_single_referenced_credential_element("eks") + + self.assertEqual(field.credential_reference_type, CredentialType.CLOUD_AWS) + + def test_azure_provider_maps_to_azure_credential_reference(self): + field = build_single_referenced_credential_element("azure") + + self.assertEqual(field.credential_reference_type, CredentialType.CLOUD_AZURE) + + def test_gcp_provider_maps_to_gcp_credential_reference(self): + field = build_single_referenced_credential_element("gcp") + + self.assertEqual(field.credential_reference_type, CredentialType.CLOUD_GCP) + + def test_unknown_provider_leaves_credential_type_empty(self): + field = build_single_referenced_credential_element("openstack") + + self.assertIsNone(field.credential_reference_type) + self.assertIsNone(_serialize(field)["credential_reference_type"]) + + +if __name__ == "__main__": + unittest.main() + From 356434767394ab615a63bd3f17d372bb1c358daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Wed, 16 Sep 2026 10:18:11 +0200 Subject: [PATCH 2/7] feat(credentials): increase coverage (#7850) --- .../test_contract_referenced_credential.py | 9 +++++++++ test/credential/test_credential_types.py | 5 +++++ test/credential/test_credential_utils.py | 16 ++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/test/contracts/test_contract_referenced_credential.py b/test/contracts/test_contract_referenced_credential.py index 66170292..5c5d1d85 100644 --- a/test/contracts/test_contract_referenced_credential.py +++ b/test/contracts/test_contract_referenced_credential.py @@ -42,7 +42,16 @@ def test_explicit_credential_type_serializes_to_platform_label(self): _serialize(field)["credential_reference_type"], "CLOUD_AZURE" ) + def test_identity_credential_type_serializes_to_platform_label(self): + field = ContractReferencedCredential( + credential_reference_type=CredentialType.IDENTITY + ) + + self.assertEqual(field.credential_reference_type, CredentialType.IDENTITY) + self.assertEqual(_serialize(field)["credential_reference_type"], "IDENTITY") + if __name__ == "__main__": unittest.main() + diff --git a/test/credential/test_credential_types.py b/test/credential/test_credential_types.py index 378fe233..bc636370 100644 --- a/test/credential/test_credential_types.py +++ b/test/credential/test_credential_types.py @@ -1,5 +1,6 @@ import unittest +from pyoaev.credential import CredentialType as PublicCredentialType from pyoaev.credential.types import CredentialType @@ -18,6 +19,9 @@ def test_identity_wire_label(self): self.assertEqual(CredentialType.IDENTITY.value, "IDENTITY") self.assertEqual(CredentialType.IDENTITY, "IDENTITY") + def test_package_re_exports_credential_type(self): + self.assertIs(PublicCredentialType, CredentialType) + def test_aws_wire_label(self): self.assertEqual(CredentialType.CLOUD_AWS.value, "CLOUD_AWS") self.assertEqual(CredentialType.CLOUD_AWS, "CLOUD_AWS") @@ -27,3 +31,4 @@ def test_aws_wire_label(self): unittest.main() + diff --git a/test/credential/test_credential_utils.py b/test/credential/test_credential_utils.py index 79bea309..9895b4f4 100644 --- a/test/credential/test_credential_utils.py +++ b/test/credential/test_credential_utils.py @@ -3,6 +3,9 @@ from pyoaev import utils from pyoaev.contracts.contract_config import ContractReferencedCredential +from pyoaev.credential import ( + build_single_referenced_credential_element as public_build_single_referenced_credential_element, +) from pyoaev.credential.types import CredentialType from pyoaev.credential.utils import build_single_referenced_credential_element @@ -33,6 +36,18 @@ def test_gcp_provider_maps_to_gcp_credential_reference(self): self.assertEqual(field.credential_reference_type, CredentialType.CLOUD_GCP) + def test_provider_mapping_is_case_insensitive(self): + field = build_single_referenced_credential_element("AWS") + + self.assertEqual(field.credential_reference_type, CredentialType.CLOUD_AWS) + + def test_package_public_helper_builds_the_same_field(self): + field = public_build_single_referenced_credential_element("azure") + + self.assertIsInstance(field, ContractReferencedCredential) + self.assertEqual(field.credential_reference_type, CredentialType.CLOUD_AZURE) + self.assertEqual(_serialize(field)["credential_reference_type"], "CLOUD_AZURE") + def test_unknown_provider_leaves_credential_type_empty(self): field = build_single_referenced_credential_element("openstack") @@ -43,3 +58,4 @@ def test_unknown_provider_leaves_credential_type_empty(self): if __name__ == "__main__": unittest.main() + From 7fc7e2d4de310e3249093ba857d6576203aaa23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Wed, 16 Sep 2026 10:22:33 +0200 Subject: [PATCH 3/7] feat(credentials): add utils file (#7850) --- pyoaev/credential/utils.py | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 pyoaev/credential/utils.py diff --git a/pyoaev/credential/utils.py b/pyoaev/credential/utils.py new file mode 100644 index 00000000..7ce28d54 --- /dev/null +++ b/pyoaev/credential/utils.py @@ -0,0 +1,40 @@ +"""Helpers centralizing how injectors declare credential-reference fields. + +Injectors can call ``build_single_referenced_credential_element`` instead of +re-implementing provider-to-credential-type mapping in each project. This keeps +contract generation consistent across the Python ecosystem and aligns the +serialized contract payload with the OpenAEV platform's expected values. +""" + +from typing import Optional + +from pyoaev.contracts.contract_config import ContractReferencedCredential +from pyoaev.credential.types import CredentialType + + +_PROVIDER_TO_CREDENTIAL_TYPE = { + "aws": CredentialType.CLOUD_AWS, + "eks": CredentialType.CLOUD_AWS, + "azure": CredentialType.CLOUD_AZURE, + "gcp": CredentialType.CLOUD_GCP, +} + + +def _resolve_credential_type(provider_name: str) -> Optional[CredentialType]: + normalized_provider = provider_name.casefold() + return _PROVIDER_TO_CREDENTIAL_TYPE.get(normalized_provider) + + +def build_single_referenced_credential_element( + provider_name: str, +) -> ContractReferencedCredential: + """Build a credential-reference field for a provider-specific contract. + + This is the centralized entry point injectors should use when they need the + OpenAEV inject form to ask for one referenced credential. The helper keeps + provider-to-``CredentialType`` mapping consistent across injector projects. + """ + + return ContractReferencedCredential( + credential_reference_type=_resolve_credential_type(provider_name) + ) From dfcf539148777627888750e0f8e0f79bfc81117e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Wed, 16 Sep 2026 10:26:40 +0200 Subject: [PATCH 4/7] feat(credentials): refactor (#7850) --- pyoaev/credential/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyoaev/credential/utils.py b/pyoaev/credential/utils.py index 7ce28d54..8e2c4add 100644 --- a/pyoaev/credential/utils.py +++ b/pyoaev/credential/utils.py @@ -28,7 +28,8 @@ def _resolve_credential_type(provider_name: str) -> Optional[CredentialType]: def build_single_referenced_credential_element( provider_name: str, ) -> ContractReferencedCredential: - """Build a credential-reference field for a provider-specific contract. + """Build a credential-reference field for a provider-specific contract + with multiple value at False. This is the centralized entry point injectors should use when they need the OpenAEV inject form to ask for one referenced credential. The helper keeps From 20d547d2f3248d08f6a96d80f85ddc018a048d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Thu, 17 Sep 2026 09:13:33 +0200 Subject: [PATCH 5/7] feat(stratus): change default multiple value (#7850) --- pyoaev/contracts/contract_config.py | 2 +- pyoaev/credential/utils.py | 3 ++- test/contracts/test_contract_referenced_credential.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pyoaev/contracts/contract_config.py b/pyoaev/contracts/contract_config.py index a4373329..cc7695b8 100644 --- a/pyoaev/contracts/contract_config.py +++ b/pyoaev/contracts/contract_config.py @@ -374,7 +374,7 @@ class ContractReferencedCredential(ContractElement): label: str = "Select a credential reference" mandatory: bool = True credential_reference_type: Optional[CredentialType] = None - multiple: bool = False + multiple: bool = True @property def get_type(self) -> str: diff --git a/pyoaev/credential/utils.py b/pyoaev/credential/utils.py index 8e2c4add..8f07c98d 100644 --- a/pyoaev/credential/utils.py +++ b/pyoaev/credential/utils.py @@ -37,5 +37,6 @@ def build_single_referenced_credential_element( """ return ContractReferencedCredential( - credential_reference_type=_resolve_credential_type(provider_name) + credential_reference_type=_resolve_credential_type(provider_name), + multiple=False ) diff --git a/test/contracts/test_contract_referenced_credential.py b/test/contracts/test_contract_referenced_credential.py index 5c5d1d85..9ecf79ec 100644 --- a/test/contracts/test_contract_referenced_credential.py +++ b/test/contracts/test_contract_referenced_credential.py @@ -22,14 +22,14 @@ def test_defaults_match_platform_contract_expectations(self): self.assertEqual(field.key, ContractFieldKey.CredentialReference.value) self.assertEqual(field.label, "Select a credential reference") self.assertTrue(field.mandatory) - self.assertFalse(field.multiple) + self.assertTrue(field.multiple) self.assertIsNone(field.credential_reference_type) self.assertEqual(field.type, ContractFieldType.CredentialReference.value) self.assertEqual(serialized["key"], "credential_reference") self.assertEqual(serialized["type"], "credential-reference") self.assertEqual(serialized["label"], "Select a credential reference") self.assertTrue(serialized["mandatory"]) - self.assertFalse(serialized["multiple"]) + self.assertTrue(serialized["multiple"]) self.assertIsNone(serialized["credential_reference_type"]) def test_explicit_credential_type_serializes_to_platform_label(self): From 94a5ecbd5b7460f1025545dc03bc55c1f9e5eb0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Thu, 17 Sep 2026 14:24:59 +0200 Subject: [PATCH 6/7] feat(credentials): fix pr feedbacks (#7850) --- pyoaev/contracts/contract_config.py | 2 +- pyoaev/credential/utils.py | 3 +-- test/credential/test_credential_utils.py | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyoaev/contracts/contract_config.py b/pyoaev/contracts/contract_config.py index cc7695b8..d00483a0 100644 --- a/pyoaev/contracts/contract_config.py +++ b/pyoaev/contracts/contract_config.py @@ -5,9 +5,9 @@ from typing import Dict, List, Optional from pyoaev import utils -from pyoaev.credential.types import CredentialType from pyoaev.contracts.contract_utils import ContractCardinality, ContractVariable from pyoaev.contracts.variable_helper import VariableHelper +from pyoaev.credential.types import CredentialType class SupportedLanguage(str, Enum): diff --git a/pyoaev/credential/utils.py b/pyoaev/credential/utils.py index 8f07c98d..9d10eab5 100644 --- a/pyoaev/credential/utils.py +++ b/pyoaev/credential/utils.py @@ -11,7 +11,6 @@ from pyoaev.contracts.contract_config import ContractReferencedCredential from pyoaev.credential.types import CredentialType - _PROVIDER_TO_CREDENTIAL_TYPE = { "aws": CredentialType.CLOUD_AWS, "eks": CredentialType.CLOUD_AWS, @@ -29,7 +28,7 @@ def build_single_referenced_credential_element( provider_name: str, ) -> ContractReferencedCredential: """Build a credential-reference field for a provider-specific contract - with multiple value at False. + with ``multiple`` value at ``False``. This is the centralized entry point injectors should use when they need the OpenAEV inject form to ask for one referenced credential. The helper keeps diff --git a/test/credential/test_credential_utils.py b/test/credential/test_credential_utils.py index 9895b4f4..f61166d5 100644 --- a/test/credential/test_credential_utils.py +++ b/test/credential/test_credential_utils.py @@ -47,6 +47,7 @@ def test_package_public_helper_builds_the_same_field(self): self.assertIsInstance(field, ContractReferencedCredential) self.assertEqual(field.credential_reference_type, CredentialType.CLOUD_AZURE) self.assertEqual(_serialize(field)["credential_reference_type"], "CLOUD_AZURE") + self.assertFalse(field.multiple) def test_unknown_provider_leaves_credential_type_empty(self): field = build_single_referenced_credential_element("openstack") From e0486b254487b10ef3eb7e6cde6341e9a6db0d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Thu, 17 Sep 2026 14:32:44 +0200 Subject: [PATCH 7/7] feat(credentials): fix black (#7850) --- pyoaev/credential/__init__.py | 3 +-- pyoaev/credential/utils.py | 2 +- test/contracts/test_contract_referenced_credential.py | 6 +----- test/credential/test_credential_types.py | 3 --- test/credential/test_credential_utils.py | 2 -- 5 files changed, 3 insertions(+), 13 deletions(-) diff --git a/pyoaev/credential/__init__.py b/pyoaev/credential/__init__.py index 447600db..5086830e 100644 --- a/pyoaev/credential/__init__.py +++ b/pyoaev/credential/__init__.py @@ -8,9 +8,8 @@ def build_single_referenced_credential_element(provider_name: str): return _build_single_referenced_credential_element(provider_name) + __all__ = [ "CredentialType", "build_single_referenced_credential_element", ] - - diff --git a/pyoaev/credential/utils.py b/pyoaev/credential/utils.py index 9d10eab5..3975e71f 100644 --- a/pyoaev/credential/utils.py +++ b/pyoaev/credential/utils.py @@ -37,5 +37,5 @@ def build_single_referenced_credential_element( return ContractReferencedCredential( credential_reference_type=_resolve_credential_type(provider_name), - multiple=False + multiple=False, ) diff --git a/test/contracts/test_contract_referenced_credential.py b/test/contracts/test_contract_referenced_credential.py index 9ecf79ec..05e43758 100644 --- a/test/contracts/test_contract_referenced_credential.py +++ b/test/contracts/test_contract_referenced_credential.py @@ -38,9 +38,7 @@ def test_explicit_credential_type_serializes_to_platform_label(self): ) self.assertEqual(field.credential_reference_type, CredentialType.CLOUD_AZURE) - self.assertEqual( - _serialize(field)["credential_reference_type"], "CLOUD_AZURE" - ) + self.assertEqual(_serialize(field)["credential_reference_type"], "CLOUD_AZURE") def test_identity_credential_type_serializes_to_platform_label(self): field = ContractReferencedCredential( @@ -53,5 +51,3 @@ def test_identity_credential_type_serializes_to_platform_label(self): if __name__ == "__main__": unittest.main() - - diff --git a/test/credential/test_credential_types.py b/test/credential/test_credential_types.py index bc636370..ed3dca2b 100644 --- a/test/credential/test_credential_types.py +++ b/test/credential/test_credential_types.py @@ -29,6 +29,3 @@ def test_aws_wire_label(self): if __name__ == "__main__": unittest.main() - - - diff --git a/test/credential/test_credential_utils.py b/test/credential/test_credential_utils.py index f61166d5..d44fcc1f 100644 --- a/test/credential/test_credential_utils.py +++ b/test/credential/test_credential_utils.py @@ -58,5 +58,3 @@ def test_unknown_provider_leaves_credential_type_empty(self): if __name__ == "__main__": unittest.main() - -