diff --git a/samples/general/README.md b/samples/general/README.md
index 447d215..a2ba8e2 100644
--- a/samples/general/README.md
+++ b/samples/general/README.md
@@ -12,8 +12,14 @@ Sets up a simple APIM instance with a variety of policies to experiment.
1. Become proficient with how policies operate.
1. Gain confidence in setting up and configuring policies appropriately.
+## 🔗 APIs
+
+| API Name | What does it do? |
+|:------------------------------|:----------------------------------------------------------------------------------------------------------------------------------|
+| Request Headers | Returns a list of the request headers sent to APIM. Useful for debugging. |
+| API ID | Returns the ID of an API as per a predefined standard such as `api-123`. Useful for explicitly identifying an API in telemetry. |
+
## ⚙️ Configuration
1. Decide which of the [Infrastructure Architectures](../../README.md#infrastructure-architectures) you wish to use.
- 1. If the infrastructure _does not_ yet exist, navigate to the desired [infrastructure](../../infrastructure/) folder and follow its README.md.
- 1. If the infrastructure _does_ exist, adjust the `user-defined parameters` in the _Initialize notebook variables_ below. Please ensure that all parameters match your infrastructure.
+1. Press `Run All` in this sample's `create.ipynb` notebook.
\ No newline at end of file
diff --git a/samples/general/create.ipynb b/samples/general/create.ipynb
index 81493c6..6a0e540 100644
--- a/samples/general/create.ipynb
+++ b/samples/general/create.ipynb
@@ -42,25 +42,20 @@
"\n",
"# Define the APIs and their operations and policies\n",
"\n",
- "# API 1\n",
- "api1_path = f'{api_prefix}api1'\n",
- "api1_get = GET_APIOperation('This is a GET for API 1')\n",
- "api1_post = POST_APIOperation('This is a POST for API 1')\n",
- "api1 = API(api1_path, 'API 1', api1_path, 'This is API 1', operations = [api1_get, api1_post], tags = tags)\n",
- "\n",
- "# API 2\n",
- "api2_path = f'{api_prefix}api2'\n",
- "api2_post = POST_APIOperation('This is a POST for API 2')\n",
- "api2 = API(api2_path, 'API 2', api2_path, 'This is API 2', operations = [api2_post], tags = tags)\n",
- "\n",
- "# API 3: Request Headers\n",
+ "# API 1: Request Headers\n",
"rh_path = f'{api_prefix}request-headers'\n",
"pol_request_headers_get = utils.read_policy_xml(REQUEST_HEADERS_XML_POLICY_PATH)\n",
"request_headers_get = GET_APIOperation('Gets the request headers for the current request and returns them. Great for troubleshooting.', pol_request_headers_get)\n",
"request_headers = API(rh_path, 'Request Headers', rh_path, 'API for request headers', operations = [request_headers_get], tags = tags)\n",
"\n",
+ "# API 2: API ID: Extract and trace API Identifier\n",
+ "api_id_path = f'{api_prefix}api-id'\n",
+ "pol_api_id_get = utils.read_policy_xml(API_ID_XML_POLICY_PATH)\n",
+ "api_id_get = GET_APIOperation('Gets the API identifier for the current request and traces it', pol_api_id_get)\n",
+ "api_id = API(api_id_path, 'API Identifier (api-42)', api_id_path, 'API for extracting and tracing API identifier', operations = [api_id_get], tags = tags)\n",
+ "\n",
"# APIs Array\n",
- "apis: List[API] = [api1, api2, request_headers]\n",
+ "apis: List[API] = [request_headers, api_id]\n",
"\n",
"utils.print_ok('Notebook initialized')"
]
@@ -121,16 +116,22 @@
"\n",
"# Initialize testing framework\n",
"tests = ApimTesting(\"General Sample Tests\", sample_folder, nb_helper.deployment)\n",
- "api_subscription_key = apim_apis[2]['subscriptionPrimaryKey']\n",
"\n",
"# Preflight: Check if the infrastructure architecture deployment uses Azure Front Door. If so, assume that APIM is not directly accessible and use the Front Door URL instead.\n",
"endpoint_url = utils.test_url_preflight_check(deployment, rg_name, apim_gateway_url)\n",
"\n",
"# 1) Request Headers\n",
+ "api_subscription_key = apim_apis[0]['subscriptionPrimaryKey']\n",
"reqs = ApimRequests(endpoint_url, api_subscription_key)\n",
- "output = reqs.singleGet('/request-headers', msg = 'Calling Request Headers API via Azure Front Door. Expect 200.')\n",
+ "output = reqs.singleGet('/request-headers', msg = 'Calling the Request Headers API. Expect 200.')\n",
"tests.verify('Host:' in output, True)\n",
"\n",
+ "# 2) API ID\n",
+ "api_subscription_key = apim_apis[1]['subscriptionPrimaryKey']\n",
+ "reqs = ApimRequests(endpoint_url, api_subscription_key)\n",
+ "output = reqs.singleGet('/api-id', msg = 'Calling the API ID API. Expect 200.')\n",
+ "tests.verify(output, 'Extracted API ID: api-42')\n",
+ "\n",
"tests.print_summary()\n",
"\n",
"utils.print_ok('All done!')"
diff --git a/shared/apim-policies/api-id.xml b/shared/apim-policies/api-id.xml
new file mode 100644
index 0000000..f7191a0
--- /dev/null
+++ b/shared/apim-policies/api-id.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+ @{
+ var apiId = context.Variables.GetValueOrDefault("apiId", string.Empty);
+ return (string.IsNullOrEmpty(apiId)) ? "No API ID extracted." : string.Format("Extracted API ID: {0}", apiId);
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/apim-policies/fragments/pf-api-id.xml b/shared/apim-policies/fragments/pf-api-id.xml
new file mode 100644
index 0000000..04bfd2a
--- /dev/null
+++ b/shared/apim-policies/fragments/pf-api-id.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+ @{
+ return "Extracted API ID: " + (string)context.Variables["apiId"];
+ }
+
+
+
+
+ @((string)context.Variables["apiId"])
+
+
+
+
\ No newline at end of file
diff --git a/shared/python/apimtesting.py b/shared/python/apimtesting.py
index 23304f0..c4babf6 100644
--- a/shared/python/apimtesting.py
+++ b/shared/python/apimtesting.py
@@ -51,6 +51,7 @@ def verify(self, value: any, expected: any) -> bool:
bool: True, if the assertion passes; otherwise, False.
"""
try:
+ print(f'🔍 Assert that [{value}] matches [{expected}].')
self.total_tests += 1
assert value == expected, f'Value [{value}] does not match expected [{expected}]'
self.tests_passed += 1
diff --git a/shared/python/apimtypes.py b/shared/python/apimtypes.py
index 355bf9c..d3b0061 100644
--- a/shared/python/apimtypes.py
+++ b/shared/python/apimtypes.py
@@ -36,10 +36,11 @@ def _get_project_root() -> Path:
_SHARED_XML_POLICY_BASE_PATH = _PROJECT_ROOT / 'shared' / 'apim-policies'
# Policy file paths (now absolute and platform-independent)
-DEFAULT_XML_POLICY_PATH = str(_SHARED_XML_POLICY_BASE_PATH / 'default.xml')
-HELLO_WORLD_XML_POLICY_PATH = str(_SHARED_XML_POLICY_BASE_PATH / 'hello-world.xml')
-REQUEST_HEADERS_XML_POLICY_PATH = str(_SHARED_XML_POLICY_BASE_PATH / 'request-headers.xml')
-BACKEND_XML_POLICY_PATH = str(_SHARED_XML_POLICY_BASE_PATH / 'backend.xml')
+DEFAULT_XML_POLICY_PATH = str(_SHARED_XML_POLICY_BASE_PATH / 'default.xml')
+HELLO_WORLD_XML_POLICY_PATH = str(_SHARED_XML_POLICY_BASE_PATH / 'hello-world.xml')
+REQUEST_HEADERS_XML_POLICY_PATH = str(_SHARED_XML_POLICY_BASE_PATH / 'request-headers.xml')
+BACKEND_XML_POLICY_PATH = str(_SHARED_XML_POLICY_BASE_PATH / 'backend.xml')
+API_ID_XML_POLICY_PATH = str(_SHARED_XML_POLICY_BASE_PATH / 'api-id.xml')
SUBSCRIPTION_KEY_PARAMETER_NAME = 'api-key'
SLEEP_TIME_BETWEEN_REQUESTS_MS = 50
diff --git a/shared/python/infrastructures.py b/shared/python/infrastructures.py
index d76f25e..b6948da 100644
--- a/shared/python/infrastructures.py
+++ b/shared/python/infrastructures.py
@@ -59,6 +59,7 @@ def _define_policy_fragments(self) -> List[PolicyFragment]:
# The base policy fragments common to all infrastructures
self.base_pfs = [
+ PolicyFragment('Api-Id', utils.read_policy_xml(utils.determine_shared_policy_path('pf-api-id.xml')), 'Extracts a specific API identifier for tracing.'),
PolicyFragment('AuthZ-Match-All', utils.read_policy_xml(utils.determine_shared_policy_path('pf-authz-match-all.xml')), 'Authorizes if all of the specified roles match the JWT role claims.'),
PolicyFragment('AuthZ-Match-Any', utils.read_policy_xml(utils.determine_shared_policy_path('pf-authz-match-any.xml')), 'Authorizes if any of the specified roles match the JWT role claims.'),
PolicyFragment('Http-Response-200', utils.read_policy_xml(utils.determine_shared_policy_path('pf-http-response-200.xml')), 'Returns a 200 OK response for the current HTTP method.'),
diff --git a/shared/python/utils.py b/shared/python/utils.py
index 4ece227..c02229c 100644
--- a/shared/python/utils.py
+++ b/shared/python/utils.py
@@ -473,7 +473,7 @@ def _query_and_select_infrastructure(self) -> tuple[INFRASTRUCTURE | None, int |
option_counter += 1
else:
print_warning('No existing supported infrastructures found.')
- print_info(f'🚀 Automatically proceeding to create new infrastructure: {self.deployment.value}')
+ print_info(f'Automatically proceeding to create new infrastructure: {self.deployment.value}')
# Automatically create the desired infrastructure without user confirmation
selected_index = self._get_current_index()
diff --git a/tests/python/test_infrastructures.py b/tests/python/test_infrastructures.py
index 0afcabf..e1e1639 100644
--- a/tests/python/test_infrastructures.py
+++ b/tests/python/test_infrastructures.py
@@ -116,7 +116,7 @@ def test_infrastructure_creation_with_custom_policy_fragments(mock_utils, mock_p
pfs = infra._define_policy_fragments()
# Should have base policy fragments + custom ones
- assert len(pfs) == 7 # 5 base + 2 custom
+ assert len(pfs) == 8 # 6 base + 2 custom
assert any(pf.name == 'Test-Fragment-1' for pf in pfs)
assert any(pf.name == 'Test-Fragment-2' for pf in pfs)
assert any(pf.name == 'AuthZ-Match-All' for pf in pfs)
@@ -225,8 +225,8 @@ def test_define_policy_fragments_with_none_input(mock_utils):
pfs = infra._define_policy_fragments()
# Should only have base policy fragments
- assert len(pfs) == 5
- assert all(pf.name in ['AuthZ-Match-All', 'AuthZ-Match-Any', 'Http-Response-200', 'Product-Match-Any', 'Remove-Request-Headers'] for pf in pfs)
+ assert len(pfs) == 6
+ assert all(pf.name in ['Api-Id', 'AuthZ-Match-All', 'AuthZ-Match-Any', 'Http-Response-200', 'Product-Match-Any', 'Remove-Request-Headers'] for pf in pfs)
@pytest.mark.unit
def test_define_policy_fragments_with_custom_input(mock_utils, mock_policy_fragments):
@@ -242,7 +242,7 @@ def test_define_policy_fragments_with_custom_input(mock_utils, mock_policy_fragm
pfs = infra._define_policy_fragments()
# Should have base + custom policy fragments
- assert len(pfs) == 7 # 5 base + 2 custom
+ assert len(pfs) == 8 # 6 base + 2 custom
fragment_names = [pf.name for pf in infra.pfs]
assert 'Test-Fragment-1' in fragment_names
assert 'Test-Fragment-2' in fragment_names
@@ -319,7 +319,7 @@ def test_define_bicep_parameters(mock_utils):
assert 'policyFragments' in bicep_params
assert isinstance(bicep_params['policyFragments']['value'], list)
- assert len(bicep_params['policyFragments']['value']) == 5 # base policy fragments
+ assert len(bicep_params['policyFragments']['value']) == 6 # base policy fragments
# ------------------------------
@@ -769,8 +769,8 @@ def test_infrastructure_end_to_end_simple(mock_utils):
# Verify all components are created correctly
assert infra.infra == INFRASTRUCTURE.SIMPLE_APIM
- assert len(infra.base_pfs) == 5
- assert len(infra.pfs) == 5
+ assert len(infra.base_pfs) == 6
+ assert len(infra.pfs) == 6
assert len(infra.base_apis) == 1
assert len(infra.apis) == 1
@@ -778,7 +778,7 @@ def test_infrastructure_end_to_end_simple(mock_utils):
bicep_params = infra._define_bicep_parameters()
assert bicep_params['apimSku']['value'] == 'Developer'
assert len(bicep_params['apis']['value']) == 1
- assert len(bicep_params['policyFragments']['value']) == 5
+ assert len(bicep_params['policyFragments']['value']) == 6
@pytest.mark.unit
def test_infrastructure_with_all_custom_components(mock_utils, mock_policy_fragments, mock_apis):
@@ -798,8 +798,8 @@ def test_infrastructure_with_all_custom_components(mock_utils, mock_policy_fragm
infra._define_apis()
# Verify all components are combined correctly
- assert len(infra.base_pfs) == 5
- assert len(infra.pfs) == 7 # 5 base + 2 custom
+ assert len(infra.base_pfs) == 6
+ assert len(infra.pfs) == 8 # 6 base + 2 custom
assert len(infra.base_apis) == 1
assert len(infra.apis) == 3 # 1 base + 2 custom
@@ -807,7 +807,7 @@ def test_infrastructure_with_all_custom_components(mock_utils, mock_policy_fragm
bicep_params = infra._define_bicep_parameters()
assert bicep_params['apimSku']['value'] == 'Premium'
assert len(bicep_params['apis']['value']) == 3
- assert len(bicep_params['policyFragments']['value']) == 7
+ assert len(bicep_params['policyFragments']['value']) == 8
# ------------------------------
@@ -856,7 +856,7 @@ def test_infrastructure_empty_custom_lists(mock_utils):
infra._define_apis()
# Empty lists should behave the same as None
- assert len(infra.pfs) == 5 # Only base policy fragments
+ assert len(infra.pfs) == 6 # Only base policy fragments
assert len(infra.apis) == 1 # Only base APIs
@pytest.mark.unit
@@ -927,6 +927,7 @@ def test_policy_fragment_creation_robustness(mock_utils):
'',
'',
'',
+ '', # Added for the new Api-Id policy fragment
''
]