Skip to content

Commit 81d515e

Browse files
Milestone 1.8.0 (#81)
1 parent c7059e1 commit 81d515e

File tree

19 files changed

+2210
-1029
lines changed

19 files changed

+2210
-1029
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ _Try it out, learn from it, apply it in your setups._
3838

3939
---
4040

41-
## 🚀 Getting Started
41+
## 🏗️ Infrastructure Setup
4242

4343
### Quick Start Options
4444

@@ -159,11 +159,13 @@ For detailed troubleshooting of setup issues, see [Import Troubleshooting Guide]
159159

160160
📘 **For comprehensive troubleshooting including deployment errors, authentication issues, and more, see our main [Troubleshooting Guide](TROUBLESHOOTING.md).**
161161

162-
### ▶️ Running a Sample
162+
## 🚀 Running a Sample
163163

164-
1. Locate the specific sample's `create.ipynb` file and adjust the parameters under the `User-defined Parameters` header as you see fit.
165-
1. Ensure that the specified infrastructure already exists in your subscription. If not, proceed to the desired infrastructure folder and execute its `create.ipynb` file. Wait until this completes before continuing.
166-
1. Execute the sample's `create.ipynb` file.
164+
1. Open the desired sample's `create.ipynb` file.
165+
1. Optional: Adjust the parameters under the `User-defined Parameters` header, if desired.
166+
1. Execute the `create.ipynb` Jupyter notebook via `Run All`.
167+
168+
> A supported infrastructure does not yet need to exist before the sample is executed. The notebook will determine the current state and present you with options to create or select a supported infrastructure, if necessary.
167169
168170
Now that infrastructure and sample have been stood up, you can experiment with the policies, make requests against APIM, etc.
169171

infrastructure/afd-apim-pe/create.ipynb

Lines changed: 18 additions & 279 deletions
Original file line numberDiff line numberDiff line change
@@ -4,211 +4,13 @@
44
"cell_type": "markdown",
55
"metadata": {},
66
"source": [
7-
"### 🛠️ 1. Initialize notebook variables\n",
7+
"### 🛠️ Configure Infrastructure Parameters & Create the Infrastructure\n",
88
"\n",
9-
"Configures everything that's needed for deployment. \n",
9+
"Set your desired parameters for the AFD-APIM-PE infrastructure deployment.\n",
1010
"\n",
11-
"❗️ **Modify entries under _1) User-defined parameters_**."
12-
]
13-
},
14-
{
15-
"cell_type": "code",
16-
"execution_count": null,
17-
"metadata": {},
18-
"outputs": [],
19-
"source": [
20-
"import utils\n",
21-
"from apimtypes import *\n",
22-
"\n",
23-
"# 1) User-defined parameters (change these as needed)\n",
24-
"rg_location = 'eastus2'\n",
25-
"index = 1\n",
26-
"apim_sku = APIM_SKU.STANDARDV2\n",
27-
"deployment = INFRASTRUCTURE.AFD_APIM_PE\n",
28-
"use_ACA = True\n",
29-
"reveal_backend = True # Set to True to reveal the backend details in the API operations\n",
30-
"\n",
31-
"# 2) Service-defined parameters (please do not change these unless you know what you're doing)\n",
32-
"rg_name = utils.get_infra_rg_name(deployment, index)\n",
33-
"rg_tags = utils.build_infrastructure_tags(deployment)\n",
34-
"apim_network_mode = APIMNetworkMode.EXTERNAL_VNET\n",
35-
"\n",
36-
"# 3) Set up the policy fragments\n",
37-
"pfs: List[PolicyFragment] = [\n",
38-
" 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.'),\n",
39-
" 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.'),\n",
40-
" 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.'),\n",
41-
" 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.'),\n",
42-
" 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.')\n",
43-
"]\n",
44-
"\n",
45-
"# 4) Define the APIs and their operations and policies\n",
46-
"\n",
47-
"# Policies\n",
48-
"pol_hello_world = utils.read_policy_xml(HELLO_WORLD_XML_POLICY_PATH)\n",
49-
"\n",
50-
"# Hello World (Root)\n",
51-
"api_hwroot_get = GET_APIOperation('This is a GET for API 1', pol_hello_world)\n",
52-
"api_hwroot = API('hello-world', 'Hello World', '', 'This is the root API for Hello World', operations = [api_hwroot_get])\n",
53-
"\n",
54-
"apis: List[API] = [api_hwroot]\n",
55-
"\n",
56-
"# If Container Apps is enabled, create the ACA APIs in APIM\n",
57-
"if use_ACA:\n",
58-
" utils.print_info('ACA APIs will be created.')\n",
59-
"\n",
60-
" pol_backend = utils.read_policy_xml(BACKEND_XML_POLICY_PATH)\n",
61-
" pol_aca_backend_1 = pol_backend.format(backend_id = 'aca-backend-1')\n",
62-
" pol_aca_backend_2 = pol_backend.format(backend_id = 'aca-backend-2')\n",
63-
" pol_aca_backend_pool = pol_backend.format(backend_id = 'aca-backend-pool')\n",
64-
"\n",
65-
" # Hello World (ACA Backend 1)\n",
66-
" api_hwaca_1_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 1')\n",
67-
" 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])\n",
68-
"\n",
69-
" # Hello World (ACA Backend 2)\n",
70-
" api_hwaca_2_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 2')\n",
71-
" 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])\n",
72-
"\n",
73-
" # Hello World (ACA Backend Pool)\n",
74-
" api_hwaca_pool_get = GET_APIOperation('This is a GET for Hello World on ACA Backend Pool')\n",
75-
" 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])\n",
76-
"\n",
77-
" # Add ACA APIs to the existing apis array\n",
78-
" apis += [api_hwaca_1, api_hwaca_2, api_hwaca_pool]\n",
79-
"\n",
80-
"utils.print_ok('Notebook initialized')"
81-
]
82-
},
83-
{
84-
"cell_type": "markdown",
85-
"metadata": {},
86-
"source": [
87-
"### 🚀 2. Create deployment using Bicep\n",
88-
"\n",
89-
"Creates the bicep deployment into the previously-specified resource group. A bicep parameters file will be created prior to execution."
90-
]
91-
},
92-
{
93-
"cell_type": "code",
94-
"execution_count": null,
95-
"metadata": {},
96-
"outputs": [],
97-
"source": [
98-
"import utils\n",
99-
"from apimtypes import *\n",
100-
"\n",
101-
"# 1) Define the Bicep parameters with serialized APIs and networking mode\n",
102-
"bicep_parameters = {\n",
103-
" 'apimSku' : {'value': apim_sku.value},\n",
104-
" 'apis' : {'value': [api.to_dict() for api in apis]},\n",
105-
" 'policyFragments' : {'value': [pf.to_dict() for pf in pfs]},\n",
106-
" 'apimPublicAccess' : {'value': apim_network_mode in [APIMNetworkMode.PUBLIC, APIMNetworkMode.EXTERNAL_VNET]},\n",
107-
" 'useACA' : {'value': use_ACA}\n",
108-
"}\n",
109-
"\n",
110-
"# 2) Run the deployment\n",
111-
"output = utils.create_bicep_deployment_group(rg_name, rg_location, deployment, bicep_parameters, rg_tags = rg_tags)\n",
112-
"\n",
113-
"# 3) Print a deployment summary, if successful; otherwise, exit with an error\n",
114-
"if not output.success:\n",
115-
" raise SystemExit('Deployment failed')\n",
116-
"\n",
117-
"if output.success and output.json_data:\n",
118-
" apim_service_id = output.get('apimServiceId', 'APIM Service Id')\n",
119-
" apim_gateway_url = output.get('apimResourceGatewayURL', 'APIM API Gateway URL')\n",
120-
" afd_endpoint_url = output.get('fdeSecureUrl', 'Front Door Endpoint URL')\n",
121-
" apim_apis = output.getJson('apiOutputs', 'APIs')\n",
122-
"\n",
123-
"utils.print_ok('Deployment completed')\n"
124-
]
125-
},
126-
{
127-
"cell_type": "markdown",
128-
"metadata": {},
129-
"source": [
130-
"### 🔗 3. Approve Front Door private link connection to APIM\n",
131-
"\n",
132-
"In the deployed Bicep template, Azure Front Door will establish a private link connection to the API Management service. This connection should be approved. Run the following command to approve the connection."
133-
]
134-
},
135-
{
136-
"cell_type": "code",
137-
"execution_count": null,
138-
"metadata": {},
139-
"outputs": [],
140-
"source": [
141-
"import utils\n",
11+
"❗️ **Modify entries under _User-defined parameters_**.\n",
14212
"\n",
143-
"# Get all pending private endpoint connections as JSON\n",
144-
"output = utils.run(f\"az network private-endpoint-connection list --id {apim_service_id} --query \\\"[?contains(properties.privateLinkServiceConnectionState.status, 'Pending')]\\\" -o json\")\n",
145-
"\n",
146-
"# Handle both a single object and a list of objects\n",
147-
"pending_connections = output.json_data if output.success and output.is_json else []\n",
148-
"\n",
149-
"if isinstance(pending_connections, dict):\n",
150-
" pending_connections = [pending_connections]\n",
151-
"\n",
152-
"total = len(pending_connections)\n",
153-
"utils.print_info(f\"Found {total} pending private link service connection(s).\")\n",
154-
"\n",
155-
"if total > 0:\n",
156-
" for i, conn in enumerate(pending_connections, 1):\n",
157-
" conn_id = conn.get('id')\n",
158-
" conn_name = conn.get('name', '<unknown>')\n",
159-
" utils.print_info(f\"{i}/{total}: {conn_name}\", True)\n",
160-
"\n",
161-
" approve_result = utils.run(\n",
162-
" f\"az network private-endpoint-connection approve --id {conn_id} --description 'Approved'\",\n",
163-
" f\"Private Link Connection approved: {conn_name}\",\n",
164-
" f\"Failed to approve Private Link Connection: {conn_name}\"\n",
165-
" )\n",
166-
"\n",
167-
" utils.print_ok('Private link approvals completed')\n",
168-
"else:\n",
169-
" utils.print_info('No pending private link service connection was found. There is nothing to approve.')"
170-
]
171-
},
172-
{
173-
"cell_type": "markdown",
174-
"metadata": {},
175-
"source": [
176-
"### ✅ 4. Verify API Request Success via API Management\n",
177-
"\n",
178-
"As we have not yet disabled public access to APIM, this request should succeed with a **200**."
179-
]
180-
},
181-
{
182-
"cell_type": "code",
183-
"execution_count": null,
184-
"metadata": {},
185-
"outputs": [],
186-
"source": [
187-
"import utils\n",
188-
"from apimrequests import ApimRequests\n",
189-
"from apimtesting import ApimTesting\n",
190-
"\n",
191-
"tests = ApimTesting(\"AFD-APIM-PE Tests (Pre-Lockdown)\", deployment, deployment)\n",
192-
"\n",
193-
"api_subscription_key = apim_apis[0]['subscriptionPrimaryKey']\n",
194-
"reqs = ApimRequests(apim_gateway_url, api_subscription_key)\n",
195-
"\n",
196-
"utils.print_message('Calling Hello World (Root) API via API Management Gateway URL. Expect 200 (if run before disabling API Management public network access).')\n",
197-
"output = reqs.singleGet('/')\n",
198-
"tests.verify(output, 'Hello World from API Management!')\n",
199-
"\n",
200-
"tests.print_summary()\n",
201-
"\n",
202-
"utils.print_ok('API request via API Management completed')"
203-
]
204-
},
205-
{
206-
"cell_type": "markdown",
207-
"metadata": {},
208-
"source": [
209-
"### 🔒 5. Disabling API Management public network access\n",
210-
"\n",
211-
"The initial `APIM` service deployment above cannot disable public network access. It must be disabled subsequently below."
13+
"**Note:** This infrastructure includes Azure Front Door with API Management using private endpoints. The creation process includes two phases: initial deployment with public access, private link approval, and then disabling public access."
21214
]
21315
},
21416
{
@@ -220,84 +22,21 @@
22022
"import utils\n",
22123
"from apimtypes import *\n",
22224
"\n",
223-
"# 1) Update the Bicep parameters to disable public access to APIM (we only want private endpoint ingress)\n",
224-
"bicep_parameters['apimPublicAccess']['value'] = False\n",
25+
"# User-defined parameters (change these as needed)\n",
26+
"rg_location = 'eastus2' # Azure region for deployment\n",
27+
"index = 1 # Infrastructure index (use different numbers for multiple environments)\n",
28+
"apim_sku = APIM_SKU.STANDARDV2 # Options: 'STANDARDV2', 'PREMIUMV2' (Basic not supported for private endpoints)\n",
29+
"use_aca = True # Include Azure Container Apps backends\n",
22530
"\n",
226-
"# 2) Run the deployment\n",
227-
"output = utils.create_bicep_deployment_group(rg_name, rg_location, deployment, bicep_parameters)\n",
228-
"\n",
229-
"# 3) Print a single, clear deployment summary if successful\n",
230-
"if not output.success:\n",
231-
" raise SystemExit('Deployment failed')\n",
232-
" \n",
233-
"if output.success and output.json_data:\n",
234-
" afd_endpoint_url = output.get('fdeSecureUrl', 'Front Door Endpoint URL')\n",
235-
" apim_gateway_url = output.get('apimResourceGatewayURL', 'APIM API Gateway URL')\n",
236-
" apim_apis = output.getJson('apiOutputs', 'APIs')\n",
237-
"\n",
238-
"utils.print_ok('Deployment completed')\n"
239-
]
240-
},
241-
{
242-
"cell_type": "markdown",
243-
"metadata": {},
244-
"source": [
245-
"### ✅ 6. Verify API Request Success via Azure Front Door & Failure with API Management\n",
31+
"# Create an instance of the desired infrastructure\n",
32+
"inb_helper = utils.InfrastructureNotebookHelper(rg_location, INFRASTRUCTURE.AFD_APIM_PE, index, apim_sku) \n",
33+
"success = inb_helper.create_infrastructure()\n",
24634
"\n",
247-
"At this time only requests through Front Door should be successful and return a **200**. Requests to APIM that worked previously should result in a **403**."
248-
]
249-
},
250-
{
251-
"cell_type": "code",
252-
"execution_count": null,
253-
"metadata": {},
254-
"outputs": [],
255-
"source": [
256-
"import utils\n",
257-
"from apimrequests import ApimRequests\n",
258-
"from apimtesting import ApimTesting\n",
259-
"\n",
260-
"tests = ApimTesting(\"AFD-APIM-PE Tests (Post-Lockdown)\", deployment, deployment)\n",
261-
"\n",
262-
"api_subscription_key = apim_apis[0]['subscriptionPrimaryKey']\n",
263-
"reqsApim = ApimRequests(apim_gateway_url, api_subscription_key)\n",
264-
"reqsAfd = ApimRequests(afd_endpoint_url, api_subscription_key)\n",
265-
"\n",
266-
"# 1) Unsuccessful call to APIM Gateway URL (should fail with 403 Forbidden)\n",
267-
"output = reqsApim.singleGet('/', msg = '1) Calling Hello World (Root) API via API Management Gateway URL. Expect 403 as APIM public access is disabled now.')\n",
268-
"outputJson = utils.get_json(output)\n",
269-
"tests.verify(outputJson['statusCode'], 403)\n",
270-
"\n",
271-
"# 2) Successful call to Front Door (200)\n",
272-
"output = reqsAfd.singleGet('/', msg = '2) Calling Hello World (Root) API via Azure Front Door. Expect 200.')\n",
273-
"tests.verify(output, 'Hello World from API Management!')\n",
274-
"\n",
275-
"# 3) Successful calls to Front Door -> APIM -> ACA (200)\n",
276-
"if use_ACA:\n",
277-
" reqsAfd = ApimRequests(afd_endpoint_url, apim_apis[1]['subscriptionPrimaryKey'])\n",
278-
" output = reqsAfd.singleGet('/aca-1', msg = '3) Calling Hello World (ACA 1) API via Azure Front Door. Expect 200.')\n",
279-
" tests.verify(output, 'Hello World!')\n",
280-
"\n",
281-
" reqsAfd = ApimRequests(afd_endpoint_url, apim_apis[2]['subscriptionPrimaryKey'])\n",
282-
" output = reqsAfd.singleGet('/aca-2', msg = '4) Calling Hello World (ACA 2) API via Azure Front Door. Expect 200.')\n",
283-
" tests.verify(output, 'Hello World!')\n",
284-
"\n",
285-
" reqsAfd = ApimRequests(afd_endpoint_url, apim_apis[3]['subscriptionPrimaryKey'])\n",
286-
" output = reqsAfd.singleGet('/aca-pool', msg = '5) Calling Hello World (ACA Pool) API via Azure Front Door. Expect 200.')\n",
287-
" tests.verify(output, 'Hello World!')\n",
35+
"if success:\n",
36+
" utils.print_ok('Infrastructure creation completed successfully!')\n",
28837
"else:\n",
289-
" utils.print_message('ACA APIs were not created. Skipping ACA API calls.', blank_above = True)\n",
290-
"\n",
291-
"# 4) Unsuccessful call to Front Door without API subscription key (should fail with 401 Unauthorized)\n",
292-
"reqsNoApiSubscription = ApimRequests(afd_endpoint_url)\n",
293-
"output = reqsNoApiSubscription.singleGet('/', msg = 'Calling Hello World (Root) API without API subscription key. Expect 401.')\n",
294-
"outputJson = utils.get_json(output)\n",
295-
"tests.verify(outputJson['statusCode'], 401)\n",
296-
"tests.verify(outputJson['message'], 'Access denied due to missing subscription key. Make sure to include subscription key when making requests to an API.')\n",
297-
"\n",
298-
"tests.print_summary()\n",
299-
"\n",
300-
"utils.print_ok('All done!')"
38+
" print(\"❌ Infrastructure creation failed!\")\n",
39+
" raise SystemExit(1)"
30140
]
30241
},
30342
{
@@ -313,9 +52,9 @@
31352
],
31453
"metadata": {
31554
"kernelspec": {
316-
"display_name": "APIM Samples Python 3.12",
55+
"display_name": ".venv (3.12.10)",
31756
"language": "python",
318-
"name": "apim-samples"
57+
"name": "python3"
31958
},
32059
"language_info": {
32160
"codemirror_mode": {

0 commit comments

Comments
 (0)