Skip to content

Commit 2b049c1

Browse files
Feature/create infrastructure classes (#88)
1 parent 741ab26 commit 2b049c1

File tree

17 files changed

+1746
-769
lines changed

17 files changed

+1746
-769
lines changed

infrastructure/afd-apim-pe/create.ipynb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,20 @@
2222
"import utils\n",
2323
"from apimtypes import *\n",
2424
"\n",
25-
"# User-defined parameters (change these as needed)\n",
25+
"# ------------------------------\n",
26+
"# USER CONFIGURATION\n",
27+
"# ------------------------------\n",
28+
"\n",
2629
"rg_location = 'eastus2' # Azure region for deployment\n",
2730
"index = 1 # Infrastructure index (use different numbers for multiple environments)\n",
2831
"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",
3032
"\n",
31-
"# Create an instance of the desired infrastructure\n",
33+
"\n",
34+
"\n",
35+
"# ------------------------------\n",
36+
"# SYSTEM CONFIGURATION\n",
37+
"# ------------------------------\n",
38+
"\n",
3239
"inb_helper = utils.InfrastructureNotebookHelper(rg_location, INFRASTRUCTURE.AFD_APIM_PE, index, apim_sku) \n",
3340
"success = inb_helper.create_infrastructure()\n",
3441
"\n",

infrastructure/afd-apim-pe/create_infrastructure.py

Lines changed: 48 additions & 266 deletions
Large diffs are not rendered by default.

infrastructure/apim-aca/create.ipynb

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,20 @@
2020
"import utils\n",
2121
"from apimtypes import *\n",
2222
"\n",
23-
"# User-defined parameters (change these as needed)\n",
24-
"rg_location = 'eastus2' # Azure region for deployment\n",
25-
"index = 1 # Infrastructure index (use different numbers for multiple environments)\n",
26-
"apim_sku = APIM_SKU.BASICV2 # Options: 'BASICV2', 'STANDARDV2', 'PREMIUMV2'\n",
27-
"reveal_backend = True # Set to True to reveal the backend details in the API operations\n",
23+
"# ------------------------------\n",
24+
"# USER CONFIGURATION\n",
25+
"# ------------------------------\n",
26+
"\n",
27+
"rg_location = 'eastus2' # Azure region for deployment\n",
28+
"index = 1 # Infrastructure index (use different numbers for multiple environments)\n",
29+
"apim_sku = APIM_SKU.BASICV2 # Options: 'BASICV2', 'STANDARDV2', 'PREMIUMV2'\n",
30+
"\n",
31+
"\n",
32+
"\n",
33+
"# ------------------------------\n",
34+
"# SYSTEM CONFIGURATION\n",
35+
"# ------------------------------\n",
2836
"\n",
29-
"# Create an instance of the desired infrastructure\n",
3037
"inb_helper = utils.InfrastructureNotebookHelper(rg_location, INFRASTRUCTURE.APIM_ACA, index, apim_sku) \n",
3138
"success = inb_helper.create_infrastructure()\n",
3239
"\n",
Lines changed: 44 additions & 242 deletions
Original file line numberDiff line numberDiff line change
@@ -1,274 +1,76 @@
11
"""
2-
Infrastructure creation module for APIM-ACA.
3-
4-
This module provides a reusable way to create API Management with Azure Container Apps
5-
infrastructure that can be called from notebooks or other scripts.
2+
This module provides a reusable way to create API Management with Azure Container Apps infrastructure that can be called from notebooks or other scripts.
63
"""
74

85
import sys
9-
import os
106
import argparse
11-
from pathlib import Path
7+
from apimtypes import APIM_SKU, API, GET_APIOperation, BACKEND_XML_POLICY_PATH
8+
from infrastructures import ApimAcaInfrastructure
129
import utils
13-
from apimtypes import *
14-
import json
15-
16-
def _create_apim_aca_infrastructure(
17-
rg_location: str = 'eastus2',
18-
index: int | None = None,
19-
apim_sku: APIM_SKU = APIM_SKU.BASICV2,
20-
reveal_backend: bool = True,
21-
custom_apis: list[API] | None = None,
22-
custom_policy_fragments: list[PolicyFragment] | None = None
23-
) -> utils.Output:
24-
"""
25-
Create APIM-ACA infrastructure with the specified parameters.
26-
27-
Args:
28-
rg_location (str): Azure region for deployment. Defaults to 'eastus2'.
29-
index (int | None): Index for the infrastructure. Defaults to None (no index).
30-
apim_sku (APIM_SKU): SKU for API Management. Defaults to BASICV2.
31-
reveal_backend (bool): Whether to reveal backend details in API operations. Defaults to True.
32-
custom_apis (list[API] | None): Custom APIs to deploy. If None, uses default Hello World API.
33-
custom_policy_fragments (list[PolicyFragment] | None): Custom policy fragments. If None, uses defaults.
34-
35-
Returns:
36-
utils.Output: The deployment result.
37-
"""
38-
39-
# 1) Setup deployment parameters
40-
deployment = INFRASTRUCTURE.APIM_ACA
41-
rg_name = utils.get_infra_rg_name(deployment, index)
42-
rg_tags = utils.build_infrastructure_tags(deployment)
43-
44-
print(f'\n🚀 Creating APIM-ACA infrastructure...\n')
45-
print(f' Infrastructure : {deployment.value}')
46-
print(f' Index : {index}')
47-
print(f' Resource group : {rg_name}')
48-
print(f' Location : {rg_location}')
49-
print(f' APIM SKU : {apim_sku.value}\n')
50-
51-
# 2) Set up the policy fragments
52-
if custom_policy_fragments is None:
53-
pfs: List[PolicyFragment] = [
54-
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.'),
55-
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.'),
56-
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.'),
57-
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.'),
58-
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.')
59-
]
60-
else:
61-
pfs = custom_policy_fragments
62-
63-
# 3) Define the APIs
64-
if custom_apis is None:
65-
# Default APIs with Container Apps backends
66-
pol_hello_world = utils.read_policy_xml(HELLO_WORLD_XML_POLICY_PATH)
67-
pol_backend = utils.read_policy_xml(BACKEND_XML_POLICY_PATH)
68-
pol_aca_backend_1 = pol_backend.format(backend_id = 'aca-backend-1')
69-
pol_aca_backend_2 = pol_backend.format(backend_id = 'aca-backend-2')
70-
pol_aca_backend_pool = pol_backend.format(backend_id = 'aca-backend-pool')
71-
72-
# Hello World (Root)
73-
api_hwroot_get = GET_APIOperation('This is a GET for Hello World in the root', pol_hello_world)
74-
api_hwroot = API('hello-world', 'Hello World', '', 'This is the root API for Hello World', operations = [api_hwroot_get])
75-
76-
# Hello World (ACA Backend 1)
77-
api_hwaca_1_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 1')
78-
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])
7910

80-
# Hello World (ACA Backend 2)
81-
api_hwaca_2_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 2')
82-
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])
8311

84-
# Hello World (ACA Backend Pool)
85-
api_hwaca_pool_get = GET_APIOperation('This is a GET for Hello World on ACA Backend Pool')
86-
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])
87-
88-
# APIs Array
89-
apis: List[API] = [api_hwroot, api_hwaca_1, api_hwaca_2, api_hwaca_pool]
90-
else:
91-
apis = custom_apis
92-
93-
# 4) Define the Bicep parameters with serialized APIs
94-
bicep_parameters = {
95-
'apimSku' : {'value': apim_sku.value},
96-
'apis' : {'value': [api.to_dict() for api in apis]},
97-
'policyFragments' : {'value': [pf.to_dict() for pf in pfs]},
98-
'revealBackendApiInfo' : {'value': reveal_backend}
99-
}
100-
101-
# 5) Change to the infrastructure directory to ensure bicep files are found
102-
original_cwd = os.getcwd()
103-
infra_dir = Path(__file__).parent
104-
12+
def create_infrastructure(location: str, index: int, apim_sku: APIM_SKU) -> None:
10513
try:
106-
os.chdir(infra_dir)
107-
print(f'📁 Changed working directory to: {infra_dir}')
108-
109-
# 6) Prepare deployment parameters and run directly to avoid path detection issues
110-
bicep_parameters_format = {
111-
'$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#',
112-
'contentVersion': '1.0.0.0',
113-
'parameters': bicep_parameters
114-
}
115-
116-
# Write the parameters file
117-
params_file_path = infra_dir / 'params.json'
118-
119-
with open(params_file_path, 'w') as file:
120-
file.write(json.dumps(bicep_parameters_format))
121-
122-
print(f"📝 Updated the policy XML in the bicep parameters file 'params.json'")
123-
124-
# Create the resource group if it doesn't exist
125-
utils.create_resource_group(rg_name, rg_location, rg_tags)
14+
# Create custom APIs for APIM-ACA with Container Apps backends
15+
custom_apis = _create_aca_specific_apis()
12616

127-
# Run the deployment directly
128-
main_bicep_path = infra_dir / 'main.bicep'
129-
output = utils.run(
130-
f'az deployment group create --name {deployment.value} --resource-group {rg_name} --template-file "{main_bicep_path}" --parameters "{params_file_path}" --query "properties.outputs"',
131-
f"Deployment '{deployment.value}' succeeded",
132-
f"Deployment '{deployment.value}' failed.",
133-
print_command_to_run = False
134-
)
17+
infra = ApimAcaInfrastructure(location, index, apim_sku, infra_apis = custom_apis)
18+
result = infra.deploy_infrastructure()
13519

136-
# 7) Check the deployment results and perform verification
137-
if output.success:
138-
print('\n✅ Infrastructure creation completed successfully!')
139-
if output.json_data:
140-
apim_gateway_url = output.get('apimResourceGatewayURL', 'APIM API Gateway URL', suppress_logging = True)
141-
aca_url_1 = output.get('acaUrl1', 'ACA Backend 1 URL', suppress_logging = True)
142-
aca_url_2 = output.get('acaUrl2', 'ACA Backend 2 URL', suppress_logging = True)
143-
apim_apis = output.getJson('apiOutputs', 'APIs', suppress_logging = True)
144-
145-
print(f'\n📋 Infrastructure Details:')
146-
print(f' Resource Group : {rg_name}')
147-
print(f' Location : {rg_location}')
148-
print(f' APIM SKU : {apim_sku.value}')
149-
print(f' Reveal Backend : {reveal_backend}')
150-
print(f' Gateway URL : {apim_gateway_url}')
151-
print(f' ACA Backend 1 : {aca_url_1}')
152-
print(f' ACA Backend 2 : {aca_url_2}')
153-
print(f' APIs Created : {len(apim_apis)}')
154-
155-
# Perform basic verification
156-
_verify_infrastructure(rg_name)
157-
else:
158-
print('❌ Infrastructure creation failed!')
20+
sys.exit(0 if result.success else 1)
15921

160-
return output
161-
162-
finally:
163-
# Always restore the original working directory
164-
os.chdir(original_cwd)
165-
print(f'📁 Restored working directory to: {original_cwd}')
22+
except Exception as e:
23+
print(f'\n💥 Error: {str(e)}')
24+
sys.exit(1)
25+
16626

167-
def _verify_infrastructure(rg_name: str) -> bool:
27+
def _create_aca_specific_apis() -> list[API]:
16828
"""
169-
Verify that the infrastructure was created successfully.
29+
Create APIM-ACA specific APIs with Container Apps backends.
17030
171-
Args:
172-
rg_name (str): Resource group name.
173-
17431
Returns:
175-
bool: True if verification passed, False otherwise.
32+
list[API]: List of ACA-specific APIs.
17633
"""
17734

178-
print('\n🔍 Verifying infrastructure...')
35+
# Define the APIs with Container Apps backends
36+
pol_backend = utils.read_policy_xml(BACKEND_XML_POLICY_PATH)
37+
pol_aca_backend_1 = pol_backend.format(backend_id = 'aca-backend-1')
38+
pol_aca_backend_2 = pol_backend.format(backend_id = 'aca-backend-2')
39+
pol_aca_backend_pool = pol_backend.format(backend_id = 'aca-backend-pool')
40+
41+
# API 1: Hello World (ACA Backend 1)
42+
api_hwaca_1_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 1')
43+
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])
44+
45+
# API 2: Hello World (ACA Backend 2)
46+
api_hwaca_2_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 2')
47+
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])
48+
49+
# API 3: Hello World (ACA Backend Pool)
50+
api_hwaca_pool_get = GET_APIOperation('This is a GET for Hello World on ACA Backend Pool')
51+
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])
17952

180-
try:
181-
# Check if the resource group exists
182-
if not utils.does_resource_group_exist(rg_name):
183-
print('❌ Resource group does not exist!')
184-
return False
185-
186-
print('✅ Resource group verified')
187-
188-
# Get APIM service details
189-
output = utils.run(f'az apim list -g {rg_name} --query "[0]" -o json', print_command_to_run = False, print_errors = False)
190-
191-
if output.success and output.json_data:
192-
apim_name = output.json_data.get('name')
193-
print(f'✅ APIM Service verified: {apim_name}')
194-
195-
# Get Container Apps count
196-
aca_output = utils.run(f'az containerapp list -g {rg_name} --query "length(@)"', print_command_to_run = False, print_errors = False)
197-
198-
if aca_output.success:
199-
aca_count = int(aca_output.text.strip())
200-
print(f'✅ Container Apps verified: {aca_count} app(s) created')
201-
202-
# Get API count
203-
api_output = utils.run(f'az apim api list --service-name {apim_name} -g {rg_name} --query "length(@)"',
204-
print_command_to_run = False, print_errors = False)
205-
206-
if api_output.success:
207-
api_count = int(api_output.text.strip())
208-
print(f'✅ APIs verified: {api_count} API(s) created')
209-
210-
# Test basic connectivity (optional)
211-
if api_count > 0:
212-
try:
213-
# Get subscription key for testing
214-
sub_output = utils.run(f'az apim subscription list --service-name {apim_name} -g {rg_name} --query "[0].primaryKey" -o tsv',
215-
print_command_to_run = False, print_errors = False)
216-
217-
if sub_output.success and sub_output.text.strip():
218-
print('✅ Subscription key available for API testing')
219-
except:
220-
pass
221-
222-
print('\n🎉 Infrastructure verification completed successfully!')
223-
return True
224-
225-
else:
226-
print('\n❌ APIM service not found!')
227-
return False
228-
229-
except Exception as e:
230-
print(f'\n⚠️ Verification failed with error: {str(e)}')
231-
return False
53+
return [api_hwaca_1, api_hwaca_2, api_hwaca_pool]
23254

23355
def main():
23456
"""
23557
Main entry point for command-line usage.
23658
"""
23759

238-
parser = argparse.ArgumentParser(description='Create APIM-ACA infrastructure')
239-
parser.add_argument('--location', default='eastus2', help='Azure region (default: eastus2)')
240-
parser.add_argument('--index', type=int, help='Infrastructure index')
241-
parser.add_argument('--sku', choices=['Basicv2', 'Standardv2', 'Premiumv2'], default='Basicv2', help='APIM SKU (default: Basicv2)')
242-
parser.add_argument('--no-reveal-backend', action='store_true', help='Do not reveal backend details in API operations')
243-
60+
parser = argparse.ArgumentParser(description = 'Create APIM-ACA infrastructure')
61+
parser.add_argument('--location', default = 'eastus2', help = 'Azure region (default: eastus2)')
62+
parser.add_argument('--index', type = int, help = 'Infrastructure index')
63+
parser.add_argument('--sku', choices = ['Basicv2', 'Standardv2', 'Premiumv2'], default = 'Basicv2', help = 'APIM SKU (default: Basicv2)')
24464
args = parser.parse_args()
245-
246-
# Convert SKU string to enum
247-
sku_map = {
248-
'Basicv2': APIM_SKU.BASICV2,
249-
'Standardv2': APIM_SKU.STANDARDV2,
250-
'Premiumv2': APIM_SKU.PREMIUMV2
251-
}
252-
65+
66+
# Convert SKU string to enum using the enum's built-in functionality
25367
try:
254-
result = _create_apim_aca_infrastructure(
255-
rg_location = args.location,
256-
index = args.index,
257-
apim_sku = sku_map[args.sku],
258-
reveal_backend = not args.no_reveal_backend
259-
)
260-
261-
if result.success:
262-
print('\n🎉 Infrastructure creation completed successfully!')
263-
sys.exit(0)
264-
else:
265-
print('\n💥 Infrastructure creation failed!')
266-
sys.exit(1)
267-
268-
except Exception as e:
269-
print(f'\n💥 Error: {str(e)}')
68+
apim_sku = APIM_SKU(args.sku)
69+
except ValueError:
70+
print(f"Error: Invalid SKU '{args.sku}'. Valid options are: {', '.join([sku.value for sku in APIM_SKU])}")
27071
sys.exit(1)
27172

73+
create_infrastructure(args.location, args.index, apim_sku)
27274

27375
if __name__ == '__main__':
27476
main()

infrastructure/simple-apim/create.ipynb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,20 @@
2020
"import utils\n",
2121
"from apimtypes import *\n",
2222
"\n",
23-
"# User-defined parameters (change these as needed)\n",
23+
"# ------------------------------\n",
24+
"# USER CONFIGURATION\n",
25+
"# ------------------------------\n",
26+
"\n",
2427
"rg_location = 'eastus2' # Azure region for deployment\n",
2528
"index = 1 # Infrastructure index (use different numbers for multiple environments)\n",
2629
"apim_sku = APIM_SKU.BASICV2 # Options: 'BASICV2', 'STANDARDV2', 'PREMIUMV2'\n",
2730
"\n",
28-
"# Create an instance of the desired infrastructure\n",
31+
"\n",
32+
"\n",
33+
"# ------------------------------\n",
34+
"# SYSTEM CONFIGURATION\n",
35+
"# ------------------------------\n",
36+
"\n",
2937
"inb_helper = utils.InfrastructureNotebookHelper(rg_location, INFRASTRUCTURE.SIMPLE_APIM, index, apim_sku) \n",
3038
"success = inb_helper.create_infrastructure()\n",
3139
"\n",

0 commit comments

Comments
 (0)