Skip to content

Commit 4462912

Browse files
committed
update flex-migration warning
1 parent 74daa6c commit 4462912

3 files changed

Lines changed: 57 additions & 26 deletions

File tree

src/azure-cli/azure/cli/command_modules/appservice/_params.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -718,10 +718,10 @@ def load_arguments(self, _):
718718

719719
# Add load_to_code parameter for functionapp SSL commands that create/update certificates (Flex Consumption only)
720720
with self.argument_context('functionapp config ssl upload') as c:
721-
c.argument('load_to_code', arg_type=get_three_state_flag(), help='For Flex Consumption apps only. Make the certificate accessible to app code. When set to true, the certificate can be loaded via the WEBSITE_LOAD_CERTIFICATES app setting.')
721+
c.argument('load_to_code', arg_type=get_three_state_flag(), help='For Flex Consumption apps only. When set to true, the certificate is accessible to app code.')
722722

723723
with self.argument_context('functionapp config ssl import') as c:
724-
c.argument('load_to_code', arg_type=get_three_state_flag(), help='For Flex Consumption apps only. Make the certificate accessible to app code. When set to true, the certificate can be loaded via the WEBSITE_LOAD_CERTIFICATES app setting.')
724+
c.argument('load_to_code', arg_type=get_three_state_flag(), help='For Flex Consumption apps only. When set to true, the certificate is accessible to app code')
725725
c.argument('enable_using_msi', arg_type=get_three_state_flag(), help='For Flex Consumption apps only. Enable Key Vault access using Managed Service Identity. When set to true, the app will use its managed identity to access Key Vault instead of service principal.')
726726

727727
with self.argument_context('webapp config connection-string list') as c:

src/azure-cli/azure/cli/command_modules/appservice/custom.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,18 +1124,25 @@ def list_flex_migration_candidates(cmd):
11241124
continue
11251125

11261126
try:
1127-
if validate_flex_migration_eligibility_for_linux_consumption_app(cmd, site, flex_regions):
1127+
is_eligible, warnings = validate_flex_migration_eligibility_for_linux_consumption_app(cmd, site, flex_regions)
1128+
if is_eligible:
11281129
site_entry = {
11291130
'name': site.name,
11301131
'resource_group': site.resource_group,
11311132
}
11321133

1134+
notes = []
11331135
has_slots = len(list_slots(cmd, site.resource_group, site.name)) > 0
11341136

11351137
if has_slots:
1136-
slots_warning = (f"The site '{site.name}' has slots configured. This will not block migration, "
1137-
f"but please note that slots are not supported in Flex Consumption.")
1138-
site_entry['note'] = slots_warning
1138+
notes.append(f"The site '{site.name}' has slots configured. This will not block migration, "
1139+
f"but please note that slots are not supported in Flex Consumption.")
1140+
1141+
# Add certificate-related warnings as notes
1142+
notes.extend(warnings)
1143+
1144+
if notes:
1145+
site_entry['note'] = ' '.join(notes)
11391146

11401147
eligible_sites.append(site_entry)
11411148

@@ -1153,6 +1160,8 @@ def list_flex_migration_candidates(cmd):
11531160

11541161

11551162
def validate_flex_migration_eligibility_for_linux_consumption_app(cmd, site, flex_regions):
1163+
warnings = []
1164+
11561165
# Validating that the site is in a Flex Consumption-supported region
11571166
normalized_site_location = _normalize_location(cmd, site.location)
11581167
if normalized_site_location not in flex_regions:
@@ -1170,18 +1179,26 @@ def validate_flex_migration_eligibility_for_linux_consumption_app(cmd, site, fle
11701179
runtime_helper = _FlexFunctionAppStackRuntimeHelper(cmd, normalized_site_location, runtime)
11711180
runtime_helper.resolve(runtime, runtime_version)
11721181

1173-
# Validating that the site does not have SSL bindings configured
1182+
# Check for SSL bindings - warn but allow migration
11741183
for ssl_state in site.host_name_ssl_states or []:
11751184
if ssl_state.ssl_state != 'Disabled':
1176-
raise ValidationError("The site '{}' is using TSL/SSL certificates. "
1177-
"TSL/SSL certificates are not supported in Flex Consumption.".format(site.name))
1185+
warnings.append("The site '{}' is using TSL/SSL certificates. "
1186+
"Site-scoped TSL/SSL certificates are supported in Flex Consumption in preview. "
1187+
"Re-configure certificates after migration using the new site-scoped model. "
1188+
"Help Link: https://aka.ms/flex-site-scoped-certs-docs."
1189+
.format(site.name))
1190+
break
11781191

1179-
# Validating that the site does not have WEBSITE_LOAD_CERTIFICATES app setting configured
1192+
# Check for WEBSITE_LOAD_CERTIFICATES app setting - warn but allow migration
11801193
app_settings = get_app_settings(cmd, site.resource_group, site.name)
11811194
for setting in app_settings:
11821195
if setting['name'] == 'WEBSITE_LOAD_CERTIFICATES':
1183-
raise ValidationError("The site '{}' has the WEBSITE_LOAD_CERTIFICATES app setting configured. "
1184-
"Certificate loading is not supported in Flex Consumption.".format(site.name))
1196+
warnings.append("The site '{}' has the WEBSITE_LOAD_CERTIFICATES app setting configured. "
1197+
"Site-scoped certificate loading is supported in Flex Consumption in preview. "
1198+
"Re-configure certificates after migration using the new site-scoped model. "
1199+
"Help Link: https://aka.ms/flex-site-scoped-certs-docs."
1200+
.format(site.name))
1201+
break
11851202

11861203
# Validating that the site has triggers supported in Flex Consumption
11871204
functions = list_functions(cmd, site.resource_group, site.name)
@@ -1200,7 +1217,7 @@ def validate_flex_migration_eligibility_for_linux_consumption_app(cmd, site, fle
12001217
"Please convert these triggers to use Event Grid or replace them with Event Grid "
12011218
"triggers before migration.".format(site.name, function_list))
12021219

1203-
return True
1220+
return True, warnings
12041221

12051222

12061223
def get_storage_account_from_functionapp(cmd, resource_group_name, name):
@@ -1260,11 +1277,16 @@ def migrate_consumption_to_flex(cmd, source_resource_group, source_name, resourc
12601277
"migration is only supported for Function Apps on Linux Consumption plans."
12611278
.format(source.name))
12621279

1263-
if validate_flex_migration_eligibility_for_linux_consumption_app(cmd, source, flex_regions):
1280+
is_eligible, warnings = validate_flex_migration_eligibility_for_linux_consumption_app(cmd, source, flex_regions)
1281+
if is_eligible:
12641282
slots = list_slots(cmd, source_resource_group, source_name)
12651283
if len(slots) > 0:
12661284
print(f"The site '{source_name}' has slots configured. This will not block migration, "
12671285
f"but please note that slots are not supported in Flex Consumption.")
1286+
1287+
# Print certificate-related warnings
1288+
for warning in warnings:
1289+
logger.warning(warning)
12681290
print(f"Source app '{source_name}' is eligible for Flex Consumption migration.")
12691291

12701292
source_site_configs = get_site_configs(cmd, source_resource_group, source_name)

src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_functionapp_commands.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -816,7 +816,8 @@ class FunctionAppFlexMigrationTest(LiveScenarioTest):
816816
def test_functionapp_flex_migration_list_candidates(self, resource_group, storage_account):
817817
eligible_functionapp_name = self.create_random_name('consumption-func', 24)
818818
noneligible_slots_functionapp_name = self.create_random_name('noneligible-slots-func', 40)
819-
noneligible_cert_functionapp_name = self.create_random_name('noneligible-cert-func', 40)
819+
eligible_cert_functionapp_name = self.create_random_name('eligible-cert-func', 40)
820+
820821
slot_name = self.create_random_name(prefix='slotname', length=24)
821822

822823
self.cmd('functionapp create -g {} -n {} -c {} -s {} --os-type linux --runtime python --runtime-version 3.11 --functions-version 4'
@@ -828,31 +829,35 @@ def test_functionapp_flex_migration_list_candidates(self, resource_group, storag
828829
self.cmd('functionapp deployment slot create -g {} -n {} --slot {}'.format(resource_group, noneligible_slots_functionapp_name, slot_name))
829830

830831
self.cmd('functionapp create -g {} -n {} -c {} -s {} --os-type linux --runtime python --runtime-version 3.11 --functions-version 4'
831-
.format(resource_group, noneligible_cert_functionapp_name, FLEX_ASP_LOCATION_FUNCTIONAPP, storage_account))
832+
.format(resource_group, eligible_cert_functionapp_name, FLEX_ASP_LOCATION_FUNCTIONAPP, storage_account))
832833

833-
self.cmd('functionapp config appsettings set -g {} -n {} --settings WEBSITE_LOAD_CERTIFICATES=*'.format(resource_group, noneligible_cert_functionapp_name))
834+
self.cmd('functionapp config appsettings set -g {} -n {} --settings WEBSITE_LOAD_CERTIFICATES=*'.format(resource_group, eligible_cert_functionapp_name))
834835

835836
candidates = self.cmd('functionapp flex-migration list').get_output_in_json().get('eligible_apps', [])
836837

837838
candidate_names = [candidate.get('name') for candidate in candidates if 'name' in candidate]
838839

839840
self.assertTrue(eligible_functionapp_name in candidate_names)
840841
self.assertTrue(noneligible_slots_functionapp_name in candidate_names)
841-
self.assertTrue(noneligible_cert_functionapp_name not in candidate_names)
842+
# Apps with WEBSITE_LOAD_CERTIFICATES are now eligible with a warning note
843+
self.assertTrue(eligible_cert_functionapp_name in candidate_names)
844+
cert_candidate = next((c for c in candidates if c.get('name') == eligible_cert_functionapp_name), None)
845+
self.assertIsNotNone(cert_candidate)
846+
self.assertIn('WEBSITE_LOAD_CERTIFICATES', cert_candidate.get('note', ''))
842847

843848

844849
@ResourceGroupPreparer(location=WINDOWS_ASP_LOCATION_FUNCTIONAPP)
845850
@StorageAccountPreparer()
846851
def test_functionapp_flex_migration_private_cert_list_candidates(self, resource_group, storage_account):
847-
noneligible_privatekey_functionapp_name = self.create_random_name('noneligible-privatekey-func', 40)
852+
eligible_ssl_functionapp_name = self.create_random_name('eligible-ssl-func', 40)
848853
pfx_file = os.path.join(TEST_DIR, 'server.pfx')
849854
cert_password = 'test'
850855
cert_thumbprint = '9E9735C45C792B03B3FFCCA614852B32EE71AD6B'
851856

852857
self.cmd('functionapp create -g {} -n {} -c {} -s {} --os-type linux --runtime python --runtime-version 3.11 --functions-version 4'
853-
.format(resource_group, noneligible_privatekey_functionapp_name, WINDOWS_ASP_LOCATION_FUNCTIONAPP, storage_account))
858+
.format(resource_group, eligible_ssl_functionapp_name, WINDOWS_ASP_LOCATION_FUNCTIONAPP, storage_account))
854859

855-
self.cmd('webapp config ssl upload -g {} -n {} --certificate-file "{}" --certificate-password {} --certificate-name {}'.format(resource_group, noneligible_privatekey_functionapp_name, pfx_file, cert_password, "test123"), checks=[
860+
self.cmd('webapp config ssl upload -g {} -n {} --certificate-file "{}" --certificate-password {} --certificate-name {}'.format(resource_group, eligible_ssl_functionapp_name, pfx_file, cert_password, "test123"), checks=[
856861
JMESPathCheck('thumbprint', cert_thumbprint),
857862
JMESPathCheck('name', 'test123')
858863
])
@@ -861,20 +866,24 @@ def test_functionapp_flex_migration_private_cert_list_candidates(self, resource_
861866

862867
candidate_names = [candidate.get('name') for candidate in candidates if 'name' in candidate]
863868

864-
self.assertTrue(noneligible_privatekey_functionapp_name in candidate_names)
869+
self.assertTrue(eligible_ssl_functionapp_name in candidate_names)
865870

866-
self.cmd('webapp config ssl bind -g {} -n {} --certificate-thumbprint {} --ssl-type {}'.format(resource_group, noneligible_privatekey_functionapp_name, cert_thumbprint, 'SNI'), checks=[
871+
self.cmd('webapp config ssl bind -g {} -n {} --certificate-thumbprint {} --ssl-type {}'.format(resource_group, eligible_ssl_functionapp_name, cert_thumbprint, 'SNI'), checks=[
867872
JMESPathCheck("hostNameSslStates|[?name=='{}.azurewebsites.net']|[0].sslState".format(
868-
noneligible_privatekey_functionapp_name), 'SniEnabled'),
873+
eligible_ssl_functionapp_name), 'SniEnabled'),
869874
JMESPathCheck("hostNameSslStates|[?name=='{}.azurewebsites.net']|[0].thumbprint".format(
870-
noneligible_privatekey_functionapp_name), cert_thumbprint)
875+
eligible_ssl_functionapp_name), cert_thumbprint)
871876
])
872877

873878
candidates = self.cmd('functionapp flex-migration list').get_output_in_json().get('eligible_apps', [])
874879

875880
candidate_names = [candidate.get('name') for candidate in candidates if 'name' in candidate]
876881

877-
self.assertTrue(noneligible_privatekey_functionapp_name not in candidate_names)
882+
# Apps with SSL bindings are now eligible with a warning note
883+
self.assertTrue(eligible_ssl_functionapp_name in candidate_names)
884+
ssl_candidate = next((c for c in candidates if c.get('name') == eligible_ssl_functionapp_name), None)
885+
self.assertIsNotNone(ssl_candidate)
886+
self.assertIn('TSL/SSL certificates', ssl_candidate.get('note', ''))
878887

879888

880889
@ResourceGroupPreparer(location=FLEX_ASP_LOCATION_FUNCTIONAPP)

0 commit comments

Comments
 (0)