|
1 | 1 | """ |
2 | | -Infrastructure creation module for AFD-APIM-PE. |
3 | | -
|
4 | | -This module provides a reusable way to create Azure Front Door with API Management |
5 | | -(Private Endpoint) infrastructure that can be called from notebooks or other scripts. |
| 2 | +This module provides a reusable way to create Azure Front Door with API Management (Private Endpoint) infrastructure that can be called from notebooks or other scripts. |
6 | 3 | """ |
7 | 4 |
|
8 | 5 | import sys |
9 | | -import os |
10 | 6 | import argparse |
11 | | -from pathlib import Path |
| 7 | +from apimtypes import APIM_SKU, API, GET_APIOperation, BACKEND_XML_POLICY_PATH |
| 8 | +from infrastructures import AfdApimAcaInfrastructure |
12 | 9 | import utils |
13 | | -from apimtypes import * |
14 | | -import json |
15 | 10 |
|
16 | | -def _create_afd_apim_pe_infrastructure( |
17 | | - rg_location: str = 'eastus2', |
18 | | - index: int | None = None, |
19 | | - apim_sku: APIM_SKU = APIM_SKU.STANDARDV2, |
20 | | - use_aca: bool = True, |
21 | | - custom_apis: list[API] | None = None, |
22 | | - custom_policy_fragments: list[PolicyFragment] | None = None |
23 | | -) -> utils.Output: |
24 | | - """ |
25 | | - Create AFD-APIM-PE 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 STANDARDV2. |
31 | | - use_aca (bool): Whether to include Azure Container Apps. 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.AFD_APIM_PE |
41 | | - rg_name = utils.get_infra_rg_name(deployment, index) |
42 | | - rg_tags = utils.build_infrastructure_tags(deployment) |
43 | | - apim_network_mode = APIMNetworkMode.EXTERNAL_VNET |
44 | 11 |
|
45 | 12 | print(f'\n🚀 Creating AFD-APIM-PE infrastructure...\n') |
46 | 13 | print(f' Infrastructure : {deployment.value}') |
@@ -108,194 +75,72 @@ def _create_afd_apim_pe_infrastructure( |
108 | 75 | infra_dir = Path(__file__).parent |
109 | 76 |
|
110 | 77 | try: |
111 | | - os.chdir(infra_dir) |
112 | | - print(f'📁 Changed working directory to: {infra_dir}') |
113 | | - |
114 | | - # 6) Create the resource group if it doesn't exist |
115 | | - utils.create_resource_group(rg_name, rg_location, rg_tags) |
| 78 | + # Create custom APIs for AFD-APIM-PE with optional Container Apps backends |
| 79 | + custom_apis = _create_afd_specific_apis(not no_aca) |
116 | 80 |
|
117 | | - # 7) First deployment with public access enabled |
118 | | - print('\n🚀 Phase 1: Creating infrastructure with public access enabled...') |
119 | | - output = utils.create_bicep_deployment_group(rg_name, rg_location, deployment, bicep_parameters) |
| 81 | + infra = AfdApimAcaInfrastructure(location, index, apim_sku, infra_apis = custom_apis) |
| 82 | + result = infra.deploy_infrastructure() |
120 | 83 |
|
121 | | - if not output.success: |
122 | | - print('❌ Phase 1 deployment failed!') |
123 | | - return output |
124 | | - |
125 | | - # Extract service details for private link approval |
126 | | - if output.json_data: |
127 | | - apim_service_id = output.get('apimServiceId', 'APIM Service Id', suppress_logging = True) |
128 | | - |
129 | | - print('✅ Phase 1 deployment completed successfully!') |
130 | | - |
131 | | - # 8) Approve private link connections |
132 | | - print('\n🔗 Approving Front Door private link connections...') |
133 | | - _approve_private_link_connections(apim_service_id) |
134 | | - |
135 | | - # 9) Second deployment to disable public access |
136 | | - print('\n🔒 Phase 2: Disabling APIM public access...') |
137 | | - bicep_parameters['apimPublicAccess']['value'] = False |
| 84 | + sys.exit(0 if result.success else 1) |
138 | 85 |
|
139 | | - output = utils.create_bicep_deployment_group(rg_name, rg_location, deployment, bicep_parameters) |
140 | | - |
141 | | - if output.success: |
142 | | - print('\n✅ Infrastructure creation completed successfully!') |
143 | | - if output.json_data: |
144 | | - apim_gateway_url = output.get('apimResourceGatewayURL', 'APIM API Gateway URL', suppress_logging = True) |
145 | | - afd_endpoint_url = output.get('fdeSecureUrl', 'Front Door Endpoint URL', suppress_logging = True) |
146 | | - apim_apis = output.getJson('apiOutputs', 'APIs', suppress_logging = True) |
147 | | - |
148 | | - print(f'\n📋 Infrastructure Details:') |
149 | | - print(f' Resource Group : {rg_name}') |
150 | | - print(f' Location : {rg_location}') |
151 | | - print(f' APIM SKU : {apim_sku.value}') |
152 | | - print(f' Use ACA : {use_aca}') |
153 | | - print(f' Gateway URL : {apim_gateway_url}') |
154 | | - print(f' Front Door URL : {afd_endpoint_url}') |
155 | | - print(f' APIs Created : {len(apim_apis)}') |
156 | | - |
157 | | - # Perform basic verification |
158 | | - _verify_infrastructure(rg_name, use_aca) |
159 | | - else: |
160 | | - print('❌ Phase 2 deployment failed!') |
161 | | - |
162 | | - return output |
163 | | - |
164 | | - finally: |
165 | | - # Always restore the original working directory |
166 | | - os.chdir(original_cwd) |
167 | | - print(f'📁 Restored working directory to: {original_cwd}') |
168 | | - |
169 | | -def _approve_private_link_connections(apim_service_id: str) -> None: |
170 | | - """ |
171 | | - Approve pending private link connections for the APIM service. |
172 | | - |
173 | | - Args: |
174 | | - apim_service_id (str): The resource ID of the APIM service. |
175 | | - """ |
176 | | - |
177 | | - # Get all pending private endpoint connections as JSON |
178 | | - output = utils.run(f"az network private-endpoint-connection list --id {apim_service_id} --query \"[?contains(properties.privateLinkServiceConnectionState.status, 'Pending')]\" -o json", print_command_to_run = False) |
179 | | - |
180 | | - # Handle both a single object and a list of objects |
181 | | - pending_connections = output.json_data if output.success and output.is_json else [] |
182 | | - |
183 | | - if isinstance(pending_connections, dict): |
184 | | - pending_connections = [pending_connections] |
185 | | - |
186 | | - total = len(pending_connections) |
187 | | - print(f'Found {total} pending private link service connection(s).') |
188 | | - |
189 | | - if total > 0: |
190 | | - for i, conn in enumerate(pending_connections, 1): |
191 | | - conn_id = conn.get('id') |
192 | | - conn_name = conn.get('name', '<unknown>') |
193 | | - print(f' {i}/{total}: Approving {conn_name}') |
| 86 | + except Exception as e: |
| 87 | + print(f'\n💥 Error: {str(e)}') |
| 88 | + sys.exit(1) |
194 | 89 |
|
195 | | - approve_result = utils.run( |
196 | | - f"az network private-endpoint-connection approve --id {conn_id} --description 'Approved'", |
197 | | - f'Private Link Connection approved: {conn_name}', |
198 | | - f'Failed to approve Private Link Connection: {conn_name}', |
199 | | - print_command_to_run = False |
200 | | - ) |
201 | 90 |
|
202 | | - print('✅ Private link approvals completed') |
203 | | - else: |
204 | | - print('No pending private link service connections found. Nothing to approve.') |
205 | | - |
206 | | -def _verify_infrastructure(rg_name: str, use_aca: bool) -> bool: |
| 91 | +def _create_afd_specific_apis(use_aca: bool = True) -> list[API]: |
207 | 92 | """ |
208 | | - Verify that the infrastructure was created successfully. |
| 93 | + Create AFD-APIM-PE specific APIs with optional Container Apps backends. |
209 | 94 | |
210 | 95 | Args: |
211 | | - rg_name (str): Resource group name. |
212 | | - use_aca (bool): Whether Container Apps were included. |
| 96 | + use_aca (bool): Whether to include Azure Container Apps backends. Defaults to true. |
213 | 97 | |
214 | 98 | Returns: |
215 | | - bool: True if verification passed, False otherwise. |
| 99 | + list[API]: List of AFD-specific APIs. |
216 | 100 | """ |
217 | 101 |
|
218 | | - print('\n🔍 Verifying infrastructure...') |
| 102 | + # If Container Apps is enabled, create the ACA APIs in APIM |
| 103 | + if use_aca: |
| 104 | + pol_backend = utils.read_policy_xml(BACKEND_XML_POLICY_PATH) |
| 105 | + pol_aca_backend_1 = pol_backend.format(backend_id = 'aca-backend-1') |
| 106 | + pol_aca_backend_2 = pol_backend.format(backend_id = 'aca-backend-2') |
| 107 | + pol_aca_backend_pool = pol_backend.format(backend_id = 'aca-backend-pool') |
| 108 | + |
| 109 | + # API 1: Hello World (ACA Backend 1) |
| 110 | + api_hwaca_1_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 1') |
| 111 | + 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]) |
| 112 | + |
| 113 | + # API 2: Hello World (ACA Backend 2) |
| 114 | + api_hwaca_2_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 2') |
| 115 | + 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]) |
| 116 | + |
| 117 | + # API 3: Hello World (ACA Backend Pool) |
| 118 | + api_hwaca_pool_get = GET_APIOperation('This is a GET for Hello World on ACA Backend Pool') |
| 119 | + 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]) |
| 120 | + |
| 121 | + return [api_hwaca_1, api_hwaca_2, api_hwaca_pool] |
219 | 122 |
|
220 | | - try: |
221 | | - # Check if the resource group exists |
222 | | - if not utils.does_resource_group_exist(rg_name): |
223 | | - print('❌ Resource group does not exist!') |
224 | | - return False |
225 | | - |
226 | | - print('✅ Resource group verified') |
227 | | - |
228 | | - # Get APIM service details |
229 | | - output = utils.run(f'az apim list -g {rg_name} --query "[0]" -o json', print_command_to_run = False, print_errors = False) |
230 | | - |
231 | | - if output.success and output.json_data: |
232 | | - apim_name = output.json_data.get('name') |
233 | | - print(f'✅ APIM Service verified: {apim_name}') |
234 | | - |
235 | | - # Check Front Door |
236 | | - afd_output = utils.run(f'az afd profile list -g {rg_name} --query "[0]" -o json', print_command_to_run = False, print_errors = False) |
237 | | - |
238 | | - if afd_output.success and afd_output.json_data: |
239 | | - afd_name = afd_output.json_data.get('name') |
240 | | - print(f'✅ Azure Front Door verified: {afd_name}') |
241 | | - |
242 | | - # Check Container Apps if enabled |
243 | | - if use_aca: |
244 | | - aca_output = utils.run(f'az containerapp list -g {rg_name} --query "length(@)"', print_command_to_run = False, print_errors = False) |
245 | | - |
246 | | - if aca_output.success: |
247 | | - aca_count = int(aca_output.text.strip()) |
248 | | - print(f'✅ Container Apps verified: {aca_count} app(s) created') |
249 | | - |
250 | | - print('\n🎉 Infrastructure verification completed successfully!') |
251 | | - return True |
252 | | - |
253 | | - else: |
254 | | - print('\n❌ APIM service not found!') |
255 | | - return False |
256 | | - |
257 | | - except Exception as e: |
258 | | - print(f'\n⚠️ Verification failed with error: {str(e)}') |
259 | | - return False |
260 | | - |
| 123 | + return [] |
261 | 124 | def main(): |
262 | 125 | """ |
263 | 126 | Main entry point for command-line usage. |
264 | 127 | """ |
265 | 128 |
|
266 | | - parser = argparse.ArgumentParser(description='Create AFD-APIM-PE infrastructure') |
267 | | - parser.add_argument('--location', default='eastus2', help='Azure region (default: eastus2)') |
268 | | - parser.add_argument('--index', type=int, help='Infrastructure index') |
269 | | - parser.add_argument('--sku', choices=['Standardv2', 'Premiumv2'], default='Standardv2', help='APIM SKU (default: Standardv2)') |
270 | | - parser.add_argument('--no-aca', action='store_true', help='Disable Azure Container Apps') |
271 | | - |
| 129 | + parser = argparse.ArgumentParser(description = 'Create AFD-APIM-PE infrastructure') |
| 130 | + parser.add_argument('--location', default = 'eastus2', help = 'Azure region (default: eastus2)') |
| 131 | + parser.add_argument('--index', type = int, help = 'Infrastructure index') |
| 132 | + parser.add_argument('--sku', choices = ['Standardv2', 'Premiumv2'], default = 'Standardv2', help = 'APIM SKU (default: Standardv2)') |
| 133 | + parser.add_argument('--no-aca', action = 'store_true', help = 'Disable Azure Container Apps') |
272 | 134 | args = parser.parse_args() |
273 | | - |
274 | | - # Convert SKU string to enum |
275 | | - sku_map = { |
276 | | - 'Standardv2': APIM_SKU.STANDARDV2, |
277 | | - 'Premiumv2': APIM_SKU.PREMIUMV2 |
278 | | - } |
279 | | - |
| 135 | + |
| 136 | + # Convert SKU string to enum using the enum's built-in functionality |
280 | 137 | try: |
281 | | - result = _create_afd_apim_pe_infrastructure( |
282 | | - rg_location = args.location, |
283 | | - index = args.index, |
284 | | - apim_sku = sku_map[args.sku], |
285 | | - use_aca = not args.no_aca |
286 | | - ) |
287 | | - |
288 | | - if result.success: |
289 | | - print('\n🎉 Infrastructure creation completed successfully!') |
290 | | - sys.exit(0) |
291 | | - else: |
292 | | - print('\n💥 Infrastructure creation failed!') |
293 | | - sys.exit(1) |
294 | | - |
295 | | - except Exception as e: |
296 | | - print(f'\n💥 Error: {str(e)}') |
| 138 | + apim_sku = APIM_SKU(args.sku) |
| 139 | + except ValueError: |
| 140 | + print(f"Error: Invalid SKU '{args.sku}'. Valid options are: {', '.join([sku.value for sku in APIM_SKU])}") |
297 | 141 | sys.exit(1) |
298 | 142 |
|
| 143 | + create_infrastructure(args.location, args.index, apim_sku, args.no_aca) |
299 | 144 |
|
300 | 145 | if __name__ == '__main__': |
301 | 146 | main() |
0 commit comments