Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion infrastructure/afd-apim-pe/create_infrastructure.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,71 @@
import utils


def create_infrastructure(location: str, index: int, apim_sku: APIM_SKU, no_aca: bool = False) -> None:
print(f'\n🚀 Creating AFD-APIM-PE infrastructure...\n')
print(f' Infrastructure : {deployment.value}')
print(f' Index : {index}')
print(f' Resource group : {rg_name}')
print(f' Location : {rg_location}')
print(f' APIM SKU : {apim_sku.value}\n')

# 2) Set up the policy fragments
if custom_policy_fragments is None:
pfs: List[PolicyFragment] = [
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.'),
PolicyFragment('Product-Match-Any', utils.read_policy_xml(utils.determine_shared_policy_path('pf-product-match-any.xml')), 'Proceeds if any of the specified products match the context product name.'),
PolicyFragment('Remove-Request-Headers', utils.read_policy_xml(utils.determine_shared_policy_path('pf-remove-request-headers.xml')), 'Removes request headers from the incoming request.')
]
else:
pfs = custom_policy_fragments

# 3) Define the APIs
if custom_apis is None:
# Default Hello World API
pol_hello_world = utils.read_policy_xml(HELLO_WORLD_XML_POLICY_PATH)
api_hwroot_get = GET_APIOperation('This is a GET for API 1', pol_hello_world)
api_hwroot = API('hello-world', 'Hello World', '', 'This is the root API for Hello World', operations = [api_hwroot_get])
apis: List[API] = [api_hwroot]

# If Container Apps is enabled, create the ACA APIs in APIM
if use_aca:
pol_backend = utils.read_policy_xml(BACKEND_XML_POLICY_PATH)
pol_aca_backend_1 = pol_backend.format(backend_id = 'aca-backend-1')
pol_aca_backend_2 = pol_backend.format(backend_id = 'aca-backend-2')
pol_aca_backend_pool = pol_backend.format(backend_id = 'aca-backend-pool')

# API 1: Hello World (ACA Backend 1)
api_hwaca_1_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 1')
api_hwaca_1 = API('hello-world-aca-1', 'Hello World (ACA 1)', '/aca-1', 'This is the ACA API for Backend 1', pol_aca_backend_1, [api_hwaca_1_get])

# API 2: Hello World (ACA Backend 2)
api_hwaca_2_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 2')
api_hwaca_2 = API('hello-world-aca-2', 'Hello World (ACA 2)', '/aca-2', 'This is the ACA API for Backend 2', pol_aca_backend_2, [api_hwaca_2_get])

# API 3: Hello World (ACA Backend Pool)
api_hwaca_pool_get = GET_APIOperation('This is a GET for Hello World on ACA Backend Pool')
api_hwaca_pool = API('hello-world-aca-pool', 'Hello World (ACA Pool)', '/aca-pool', 'This is the ACA API for Backend Pool', pol_aca_backend_pool, [api_hwaca_pool_get])

# Add ACA APIs to the existing apis array
apis += [api_hwaca_1, api_hwaca_2, api_hwaca_pool]
else:
apis = custom_apis

# 4) Define the Bicep parameters with serialized APIs
bicep_parameters = {
'apimSku' : {'value': apim_sku.value},
'apis' : {'value': [api.to_dict() for api in apis]},
'policyFragments' : {'value': [pf.to_dict() for pf in pfs]},
'apimPublicAccess' : {'value': apim_network_mode in [APIMNetworkMode.PUBLIC, APIMNetworkMode.EXTERNAL_VNET]},
'useACA' : {'value': use_aca}
}

# 5) Change to the infrastructure directory to ensure bicep files are found
original_cwd = os.getcwd()
infra_dir = Path(__file__).parent

try:
# Create custom APIs for AFD-APIM-PE with optional Container Apps backends
custom_apis = _create_afd_specific_apis(not no_aca)
Expand Down
63 changes: 62 additions & 1 deletion infrastructure/apim-aca/create_infrastructure.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,68 @@
import utils


def create_infrastructure(location: str, index: int, apim_sku: APIM_SKU) -> None:
print(f'\n🚀 Creating APIM-ACA infrastructure...\n')
print(f' Infrastructure : {deployment.value}')
print(f' Index : {index}')
print(f' Resource group : {rg_name}')
print(f' Location : {rg_location}')
print(f' APIM SKU : {apim_sku.value}\n')

# 2) Set up the policy fragments
if custom_policy_fragments is None:
pfs: List[PolicyFragment] = [
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.'),
PolicyFragment('Product-Match-Any', utils.read_policy_xml(utils.determine_shared_policy_path('pf-product-match-any.xml')), 'Proceeds if any of the specified products match the context product name.'),
PolicyFragment('Remove-Request-Headers', utils.read_policy_xml(utils.determine_shared_policy_path('pf-remove-request-headers.xml')), 'Removes request headers from the incoming request.')
]
else:
pfs = custom_policy_fragments

# 3) Define the APIs
if custom_apis is None:
# Default APIs with Container Apps backends
pol_hello_world = utils.read_policy_xml(HELLO_WORLD_XML_POLICY_PATH)
pol_backend = utils.read_policy_xml(BACKEND_XML_POLICY_PATH)
pol_aca_backend_1 = pol_backend.format(backend_id = 'aca-backend-1')
pol_aca_backend_2 = pol_backend.format(backend_id = 'aca-backend-2')
pol_aca_backend_pool = pol_backend.format(backend_id = 'aca-backend-pool')

# Hello World (Root)
api_hwroot_get = GET_APIOperation('This is a GET for Hello World in the root', pol_hello_world)
api_hwroot = API('hello-world', 'Hello World', '', 'This is the root API for Hello World', operations = [api_hwroot_get])

# Hello World (ACA Backend 1)
api_hwaca_1_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 1')
api_hwaca_1 = API('hello-world-aca-1', 'Hello World (ACA 1)', '/aca-1', 'This is the ACA API for Backend 1', policyXml = pol_aca_backend_1, operations = [api_hwaca_1_get])

# Hello World (ACA Backend 2)
api_hwaca_2_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 2')
api_hwaca_2 = API('hello-world-aca-2', 'Hello World (ACA 2)', '/aca-2', 'This is the ACA API for Backend 2', policyXml = pol_aca_backend_2, operations = [api_hwaca_2_get])

# Hello World (ACA Backend Pool)
api_hwaca_pool_get = GET_APIOperation('This is a GET for Hello World on ACA Backend Pool')
api_hwaca_pool = API('hello-world-aca-pool', 'Hello World (ACA Pool)', '/aca-pool', 'This is the ACA API for Backend Pool', policyXml = pol_aca_backend_pool, operations = [api_hwaca_pool_get])

# APIs Array
apis: List[API] = [api_hwroot, api_hwaca_1, api_hwaca_2, api_hwaca_pool]
else:
apis = custom_apis

# 4) Define the Bicep parameters with serialized APIs
bicep_parameters = {
'apimSku' : {'value': apim_sku.value},
'apis' : {'value': [api.to_dict() for api in apis]},
'policyFragments' : {'value': [pf.to_dict() for pf in pfs]},
'revealBackendApiInfo' : {'value': reveal_backend}
}

# 5) Change to the infrastructure directory to ensure bicep files are found
original_cwd = os.getcwd()
infra_dir = Path(__file__).parent

try:
# Create custom APIs for APIM-ACA with Container Apps backends
custom_apis = _create_aca_specific_apis()
Expand Down
43 changes: 42 additions & 1 deletion infrastructure/simple-apim/create_infrastructure.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,48 @@
from infrastructures import SimpleApimInfrastructure


def create_infrastructure(location: str, index: int, apim_sku: APIM_SKU) -> None:
print(f'\n🚀 Creating Simple APIM infrastructure...\n')
print(f' Infrastructure : {deployment.value}')
print(f' Index : {index}')
print(f' Resource group : {rg_name}')
print(f' Location : {rg_location}')
print(f' APIM SKU : {apim_sku.value}\n')

# 2) Set up the policy fragments
if custom_policy_fragments is None:
pfs: List[PolicyFragment] = [
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.'),
PolicyFragment('Product-Match-Any', utils.read_policy_xml(utils.determine_shared_policy_path('pf-product-match-any.xml')), 'Proceeds if any of the specified products match the context product name.'),
PolicyFragment('Remove-Request-Headers', utils.read_policy_xml(utils.determine_shared_policy_path('pf-remove-request-headers.xml')), 'Removes request headers from the incoming request.')
]
else:
pfs = custom_policy_fragments

# 3) Define the APIs
if custom_apis is None:
# Default Hello World API
pol_hello_world = utils.read_policy_xml(HELLO_WORLD_XML_POLICY_PATH)
api_hwroot_get = GET_APIOperation('This is a GET for API 1', pol_hello_world)
api_hwroot = API('hello-world', 'Hello World', '', 'This is the root API for Hello World', operations = [api_hwroot_get])
apis: List[API] = [api_hwroot]
else:
apis = custom_apis

# 4) Define the Bicep parameters with serialized APIs
# Define the Bicep parameters with serialized APIs
bicep_parameters = {
'apimSku' : {'value': apim_sku.value},
'apis' : {'value': [api.to_dict() for api in apis]},
'policyFragments' : {'value': [pf.to_dict() for pf in pfs]}
}

# Change to the infrastructure directory to ensure bicep files are found
original_cwd = os.getcwd()
infra_dir = Path(__file__).parent

try:
result = SimpleApimInfrastructure(location, index, apim_sku).deploy_infrastructure()
sys.exit(0 if result.success else 1)
Expand Down
10 changes: 8 additions & 2 deletions samples/general/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
35 changes: 18 additions & 17 deletions samples/general/create.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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')"
]
Expand Down Expand Up @@ -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 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 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!')"
Expand All @@ -139,9 +140,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "APIM Samples Python 3.12",
"display_name": ".venv (3.12.10)",
"language": "python",
"name": "apim-samples"
"name": "python3"
},
"language_info": {
"codemirror_mode": {
Expand Down
27 changes: 27 additions & 0 deletions shared/apim-policies/api-id.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!--
This policy sample demonstrates the listing of all inbound HTTP headers, which can be a bit more straight-forward than setting up a trace.
-->
<policies>
<inbound>
<base />
<include-fragment fragment-id="Api-Id" />
<return-response>
<set-status code="200" reason="OK" />
<set-body>
@{
var apiId = context.Variables.GetValueOrDefault<string>("apiId", string.Empty);
return (string.IsNullOrEmpty(apiId)) ? "No API ID extracted." : string.Format("Extracted API ID: {0}", apiId);
}
</set-body>
</return-response>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
44 changes: 44 additions & 0 deletions shared/apim-policies/fragments/pf-api-id.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!--
Policy Fragment: Extract API ID from API Name

Tags are very useful to explicitly identify an API as API names or paths may be ambiguous and repetitive. Having explicit IDs can significantly aid with telemetry.
As there is no way yet to extract a tag associated with an API in Azure API Management using policies, putting such information in the API name is a workable alternative.
If the format is consistent, it can be parsed and used in the policies.

The format we are looking for is `<api-name> (api-246)`, where the value in parentheses starts with "api-" followed by numeric characters.

This fragment extracts the API ID from context.Api.Name and stores it in the "apiId" variable.
If the format doesn't match the expected pattern, the variable will be set to an empty string.

Expected format examples:
- "User Management API (api-246)" → extracts "api-246"
- "Order Service (api-123)" → extracts "api-123"
- "Payment Gateway (payment-456)" → ignored (doesn't start with "api-")
- "Simple API" → ignored (no parentheses)

https://learn.microsoft.com/azure/api-management/api-management-policy-expressions#ref-context-api
-->
<fragment>
<set-variable name="apiId" value="@{
// Use regex to match the pattern: (api-\d+) at the end of the string
var match = System.Text.RegularExpressions.Regex.Match(context.Api.Name, @"\(api-\d+\)$");

// Extract the content within parentheses, removing the parentheses
return (match.Success) ? match.Value.Substring(1, match.Value.Length - 2) : string.Empty;
}" />

<choose>
<when condition="@(!string.IsNullOrEmpty(context.Variables.GetValueOrDefault<string>("apiId", string.Empty)))">
<trace source="API Id Policy Fragment">
<message>@{
return "Extracted API ID: " + (string)context.Variables["apiId"];
}</message>
<metadata name="ApiId" value="@((string)context.Variables["apiId"])" />
</trace>
<!-- Set a header for downstream services to use -->
<set-header name="X-Api-Id" exists-action="override">
<value>@((string)context.Variables["apiId"])</value>
</set-header>
</when>
</choose>
</fragment>
Loading
Loading