Skip to content

Commit 95a224c

Browse files
Add user-defined infrastructure creation behavior
1 parent 088ad69 commit 95a224c

5 files changed

Lines changed: 284 additions & 1 deletion

File tree

.env-example

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!-- markdownlint-disable -->
2+
# Auto-generated environment configuration example for VS Code and local tooling
3+
# Copy this file to .env and customize values, or run:
4+
# python setup/local_setup.py --generate-env
5+
#
6+
# Good to set console width to 220, 221 - whatever it takes to see the full output
7+
8+
# Console output width in characters (default: 180)
9+
APIM_SAMPLES_CONSOLE_WIDTH=180
10+
11+
# Log level for Python logging (default: INFO)
12+
# Valid values: DEBUG, INFO, WARNING, ERROR, CRITICAL
13+
APIM_SAMPLES_LOG_LEVEL=INFO
14+
15+
# Infrastructure creation behavior when desired infrastructure doesn't exist (default: ask-always)
16+
#
17+
# This controls what happens when a sample is executed and the desired infrastructure
18+
# (specified by deployment type and index) does not exist.
19+
#
20+
# Valid values:
21+
# ask-always - Always prompt the user to choose: create new or select existing (default)
22+
# create-new-always - Automatically create a new infrastructure without prompting
23+
#
24+
APIM_SAMPLES_INFRA_CREATION_BEHAVIOR=ask-always
25+
26+
# Project root directory (auto-set by local_setup.py)
27+
# PROJECT_ROOT=
28+
29+
# Python path for shared modules (auto-set by local_setup.py)
30+
# PYTHONPATH=
31+
32+
# OAuth 3rd-party sample: Spotify Client ID and Secret (optional)
33+
# See: samples/oauth-3rd-party/README.md
34+
# SPOTIFY_CLIENT_ID=
35+
# SPOTIFY_CLIENT_SECRET=

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ labs-in-progress/
2626
# Misc
2727
*.log
2828

29-
# Exclude sensitive or generated files
29+
# Exclude sensitive or generated files (except for the benign example)
3030
.env*
31+
!.env-example
3132

3233
# Coverage data and reports
3334
.coverage

setup/local_setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ def generate_env_file() -> None:
293293
managed_keys = {
294294
'APIM_SAMPLES_CONSOLE_WIDTH': existing_vars.get('APIM_SAMPLES_CONSOLE_WIDTH', '180'),
295295
'APIM_SAMPLES_LOG_LEVEL': existing_vars.get('APIM_SAMPLES_LOG_LEVEL', 'INFO'),
296+
'APIM_SAMPLES_INFRA_CREATION_BEHAVIOR': existing_vars.get('APIM_SAMPLES_INFRA_CREATION_BEHAVIOR', 'ask-always'),
296297
'PROJECT_ROOT': str(project_root),
297298
'PYTHONPATH': str(shared_python_path),
298299
'SPOTIFY_CLIENT_ID': existing_vars.get('SPOTIFY_CLIENT_ID', ''),
@@ -310,6 +311,7 @@ def generate_env_file() -> None:
310311
'',
311312
f'APIM_SAMPLES_CONSOLE_WIDTH={managed_keys["APIM_SAMPLES_CONSOLE_WIDTH"]}',
312313
f'APIM_SAMPLES_LOG_LEVEL={managed_keys["APIM_SAMPLES_LOG_LEVEL"]}',
314+
f'APIM_SAMPLES_INFRA_CREATION_BEHAVIOR={managed_keys["APIM_SAMPLES_INFRA_CREATION_BEHAVIOR"]}',
313315
f'PROJECT_ROOT={managed_keys["PROJECT_ROOT"]}',
314316
f'PYTHONPATH={managed_keys["PYTHONPATH"]}',
315317
f'SPOTIFY_CLIENT_ID={managed_keys["SPOTIFY_CLIENT_ID"]}',

shared/python/utils.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,28 @@ def _query_and_select_infrastructure(self) -> tuple[INFRASTRUCTURE | None, int |
448448

449449
print_plain('')
450450

451+
# Check the infrastructure creation behavior setting
452+
infra_creation_behavior = os.getenv('APIM_SAMPLES_INFRA_CREATION_BEHAVIOR', 'ask-always').lower()
453+
454+
# If behavior is set to always create new and there are existing options, automatically select create_new (option 1)
455+
if infra_creation_behavior == 'create-new-always' and available_options:
456+
# Option 1 is always the "create_new" option
457+
option_type, selected_infra, selected_index = display_options[0]
458+
index_suffix = f' (index: {selected_index})' if selected_index is not None else ''
459+
print_info(f'APIM_SAMPLES_INFRA_CREATION_BEHAVIOR=create-new-always: Creating new infrastructure: {selected_infra.value}{index_suffix}')
460+
461+
# Execute the infrastructure creation
462+
inb_helper = InfrastructureNotebookHelper(self.rg_location, self.deployment, selected_index, self.apim_sku)
463+
success = inb_helper.create_infrastructure(True) # Bypass infrastructure check to force creation
464+
465+
if success:
466+
index_suffix = f' (index: {selected_index})' if selected_index is not None else ''
467+
print_ok(f'Successfully created infrastructure: {selected_infra.value}{index_suffix}')
468+
return selected_infra, selected_index
469+
470+
print_error('Failed to create infrastructure.')
471+
return None, None
472+
451473
# Get user selection
452474
while True:
453475
try:

tests/python/test_utils.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3170,3 +3170,226 @@ def patched_query_and_select():
31703170

31713171
assert selected_infra == INFRASTRUCTURE.SIMPLE_APIM
31723172
assert selected_index == 2
3173+
3174+
3175+
@pytest.mark.unit
3176+
def test_query_and_select_infrastructure_create_new_always_behavior(monkeypatch, suppress_utils_console):
3177+
"""Test APIM_SAMPLES_INFRA_CREATION_BEHAVIOR=create-new-always automatically creates new infrastructure."""
3178+
nb_helper = utils.NotebookHelper(
3179+
'test-sample',
3180+
'apim-infra-simple-apim-1',
3181+
'eastus',
3182+
INFRASTRUCTURE.SIMPLE_APIM,
3183+
[INFRASTRUCTURE.SIMPLE_APIM],
3184+
)
3185+
3186+
monkeypatch.setenv('APIM_SAMPLES_INFRA_CREATION_BEHAVIOR', 'create-new-always')
3187+
3188+
monkeypatch.setattr(
3189+
az,
3190+
'find_infrastructure_instances',
3191+
lambda infra: [(INFRASTRUCTURE.SIMPLE_APIM, 5)] if infra == INFRASTRUCTURE.SIMPLE_APIM else [],
3192+
)
3193+
monkeypatch.setattr(
3194+
az,
3195+
'get_infra_rg_name',
3196+
lambda infra, index=None: f'apim-infra-{infra.value}' if index is None else f'apim-infra-{infra.value}-{index}',
3197+
)
3198+
3199+
created_helpers = []
3200+
3201+
class DummyInfraHelper:
3202+
def __init__(self, rg_location, deployment, index, apim_sku):
3203+
self.rg_location = rg_location
3204+
self.deployment = deployment
3205+
self.index = index
3206+
self.apim_sku = apim_sku
3207+
self.calls = []
3208+
created_helpers.append(self)
3209+
3210+
def create_infrastructure(self, bypass):
3211+
self.calls.append(bypass)
3212+
return True
3213+
3214+
monkeypatch.setattr(utils, 'InfrastructureNotebookHelper', DummyInfraHelper)
3215+
3216+
# User input should NOT be requested
3217+
input_called = []
3218+
3219+
def mock_input(prompt):
3220+
input_called.append(prompt)
3221+
raise AssertionError('input() should not be called when create-new-always is set')
3222+
3223+
monkeypatch.setattr('builtins.input', mock_input)
3224+
3225+
selected_infra, selected_index = nb_helper._query_and_select_infrastructure()
3226+
3227+
assert selected_infra == INFRASTRUCTURE.SIMPLE_APIM
3228+
assert selected_index == 1
3229+
assert created_helpers
3230+
assert created_helpers[0].calls == [True]
3231+
assert len(input_called) == 0 # input() should not have been called
3232+
3233+
3234+
@pytest.mark.unit
3235+
def test_query_and_select_infrastructure_ask_always_behavior_default(monkeypatch, suppress_utils_console):
3236+
"""Test APIM_SAMPLES_INFRA_CREATION_BEHAVIOR=ask-always (default) prompts user."""
3237+
nb_helper = utils.NotebookHelper(
3238+
'test-sample',
3239+
'apim-infra-simple-apim-1',
3240+
'eastus',
3241+
INFRASTRUCTURE.SIMPLE_APIM,
3242+
[INFRASTRUCTURE.SIMPLE_APIM],
3243+
)
3244+
3245+
# Default behavior (or explicitly set to ask-always)
3246+
monkeypatch.setenv('APIM_SAMPLES_INFRA_CREATION_BEHAVIOR', 'ask-always')
3247+
3248+
monkeypatch.setattr(
3249+
az,
3250+
'find_infrastructure_instances',
3251+
lambda infra: [(INFRASTRUCTURE.SIMPLE_APIM, 5)] if infra == INFRASTRUCTURE.SIMPLE_APIM else [],
3252+
)
3253+
monkeypatch.setattr(
3254+
az,
3255+
'get_infra_rg_name',
3256+
lambda infra, index=None: f'apim-infra-{infra.value}' if index is None else f'apim-infra-{infra.value}-{index}',
3257+
)
3258+
3259+
# User selects existing infrastructure (option 2)
3260+
monkeypatch.setattr('builtins.input', lambda prompt: '2')
3261+
3262+
selected_infra, selected_index = nb_helper._query_and_select_infrastructure()
3263+
3264+
assert selected_infra == INFRASTRUCTURE.SIMPLE_APIM
3265+
assert selected_index == 5
3266+
3267+
3268+
@pytest.mark.unit
3269+
def test_query_and_select_infrastructure_invalid_behavior_defaults_to_ask(monkeypatch, suppress_utils_console):
3270+
"""Test invalid APIM_SAMPLES_INFRA_CREATION_BEHAVIOR value defaults to ask-always."""
3271+
nb_helper = utils.NotebookHelper(
3272+
'test-sample',
3273+
'apim-infra-simple-apim-1',
3274+
'eastus',
3275+
INFRASTRUCTURE.SIMPLE_APIM,
3276+
[INFRASTRUCTURE.SIMPLE_APIM],
3277+
)
3278+
3279+
# Set to invalid value
3280+
monkeypatch.setenv('APIM_SAMPLES_INFRA_CREATION_BEHAVIOR', 'invalid-value')
3281+
3282+
monkeypatch.setattr(
3283+
az,
3284+
'find_infrastructure_instances',
3285+
lambda infra: [(INFRASTRUCTURE.SIMPLE_APIM, 5)] if infra == INFRASTRUCTURE.SIMPLE_APIM else [],
3286+
)
3287+
monkeypatch.setattr(
3288+
az,
3289+
'get_infra_rg_name',
3290+
lambda infra, index=None: f'apim-infra-{infra.value}' if index is None else f'apim-infra-{infra.value}-{index}',
3291+
)
3292+
3293+
# User selects existing infrastructure (option 2) - should be prompted
3294+
monkeypatch.setattr('builtins.input', lambda prompt: '2')
3295+
3296+
selected_infra, selected_index = nb_helper._query_and_select_infrastructure()
3297+
3298+
# Should prompt user (not auto-create) because invalid value defaults to ask-always
3299+
assert selected_infra == INFRASTRUCTURE.SIMPLE_APIM
3300+
assert selected_index == 5
3301+
3302+
3303+
@pytest.mark.unit
3304+
def test_query_and_select_infrastructure_create_new_always_no_existing_options(monkeypatch, suppress_utils_console):
3305+
"""Test create-new-always when no existing infrastructures found (auto-create path)."""
3306+
nb_helper = utils.NotebookHelper(
3307+
'test-sample',
3308+
'apim-infra-simple-apim',
3309+
'eastus',
3310+
INFRASTRUCTURE.SIMPLE_APIM,
3311+
[INFRASTRUCTURE.SIMPLE_APIM],
3312+
)
3313+
3314+
monkeypatch.setenv('APIM_SAMPLES_INFRA_CREATION_BEHAVIOR', 'create-new-always')
3315+
3316+
monkeypatch.setattr(az, 'find_infrastructure_instances', lambda infra: [])
3317+
monkeypatch.setattr(
3318+
az,
3319+
'get_infra_rg_name',
3320+
lambda infra, index=None: f'apim-infra-{infra.value}' if index is None else f'apim-infra-{infra.value}-{index}',
3321+
)
3322+
3323+
created_helpers = []
3324+
3325+
class DummyInfraHelper:
3326+
def __init__(self, rg_location, deployment, index, apim_sku):
3327+
self.rg_location = rg_location
3328+
self.deployment = deployment
3329+
self.index = index
3330+
self.apim_sku = apim_sku
3331+
self.calls = []
3332+
created_helpers.append(self)
3333+
3334+
def create_infrastructure(self, bypass):
3335+
self.calls.append(bypass)
3336+
return True
3337+
3338+
monkeypatch.setattr(utils, 'InfrastructureNotebookHelper', DummyInfraHelper)
3339+
3340+
selected_infra, selected_index = nb_helper._query_and_select_infrastructure()
3341+
3342+
# Should create infrastructure automatically
3343+
assert selected_infra == INFRASTRUCTURE.SIMPLE_APIM
3344+
assert selected_index is None
3345+
assert created_helpers
3346+
assert created_helpers[0].calls == [True]
3347+
3348+
3349+
@pytest.mark.unit
3350+
def test_query_and_select_infrastructure_create_new_always_failure(monkeypatch, suppress_utils_console):
3351+
"""Test create-new-always when infrastructure creation fails."""
3352+
nb_helper = utils.NotebookHelper(
3353+
'test-sample',
3354+
'apim-infra-simple-apim-1',
3355+
'eastus',
3356+
INFRASTRUCTURE.SIMPLE_APIM,
3357+
[INFRASTRUCTURE.SIMPLE_APIM],
3358+
)
3359+
3360+
monkeypatch.setenv('APIM_SAMPLES_INFRA_CREATION_BEHAVIOR', 'create-new-always')
3361+
3362+
monkeypatch.setattr(
3363+
az,
3364+
'find_infrastructure_instances',
3365+
lambda infra: [(INFRASTRUCTURE.SIMPLE_APIM, 5)] if infra == INFRASTRUCTURE.SIMPLE_APIM else [],
3366+
)
3367+
monkeypatch.setattr(
3368+
az,
3369+
'get_infra_rg_name',
3370+
lambda infra, index=None: f'apim-infra-{infra.value}' if index is None else f'apim-infra-{infra.value}-{index}',
3371+
)
3372+
3373+
created_helpers = []
3374+
3375+
class DummyInfraHelper:
3376+
def __init__(self, rg_location, deployment, index, apim_sku):
3377+
self.rg_location = rg_location
3378+
self.deployment = deployment
3379+
self.index = index
3380+
self.apim_sku = apim_sku
3381+
self.calls = []
3382+
created_helpers.append(self)
3383+
3384+
def create_infrastructure(self, bypass):
3385+
self.calls.append(bypass)
3386+
return False # Simulate failure
3387+
3388+
monkeypatch.setattr(utils, 'InfrastructureNotebookHelper', DummyInfraHelper)
3389+
3390+
selected_infra, selected_index = nb_helper._query_and_select_infrastructure()
3391+
3392+
assert selected_infra is None
3393+
assert selected_index is None
3394+
assert created_helpers
3395+
assert created_helpers[0].calls == [True]

0 commit comments

Comments
 (0)