From 9e96689263614faa8b6b69696f55591d9f2d56b6 Mon Sep 17 00:00:00 2001 From: Jordan Selig Date: Fri, 11 Apr 2025 11:44:31 -0400 Subject: [PATCH 1/9] remove fs validation --- .../azure/cli/command_modules/appservice/custom.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index bbd67a29ae0..a86e41780fb 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -580,14 +580,6 @@ def update_application_settings_polling(cmd, resource_group_name, name, app_sett def add_azure_storage_account(cmd, resource_group_name, name, custom_id, storage_type, account_name, share_name, access_key, mount_path=None, slot=None, slot_setting=False): AzureStorageInfoValue = cmd.get_models('AzureStorageInfoValue') - storage_client = get_mgmt_service_client(cmd.cli_ctx, StorageManagementClient) - - # Check if the file share exists - try: - storage_client.file_shares.get(resource_group_name, account_name, share_name) - except: - raise ValidationError(f"The share '{share_name}' does not exist in the storage account '{account_name}'.") - azure_storage_accounts = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'list_azure_storage_accounts', slot) From f46cfa11589f3c606162022332d51e4a201b6500 Mon Sep 17 00:00:00 2001 From: Jordan Selig Date: Tue, 25 Nov 2025 18:02:09 +0000 Subject: [PATCH 2/9] Fix list-runtimes to dynamically discover Java versions including Java 25 - Remove hardcoded Java version lists that were missing Java 25 - Add dynamic parsing of Java versions from API's runtimes array - Add _get_java_versions_from_minor_versions() for Linux Java SE containers - Add _get_java_versions_from_windows_container() for Windows containers - Add _get_java_runtimes_from_container_settings() for Linux container runtimes - Add deduplication to prevent duplicate runtime entries (e.g., JBOSSEAP) - Future-proof: new Java versions will automatically appear without code changes --- .../cli/command_modules/appservice/custom.py | 107 ++++++++++++++++-- 1 file changed, 97 insertions(+), 10 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 3c2a9506593..700c1dbbadf 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -6152,12 +6152,13 @@ def _get_raw_stacks_from_api(self): return list(self._client.provider.get_web_app_stacks(stack_os_type=None)) def _parse_raw_stacks(self, stacks): + seen_runtimes = set() # Track seen runtime display names to avoid duplicates for lang in stacks: for major_version in lang.major_versions: if self._linux: if lang.display_text.lower() == "java": continue - self._parse_major_version_linux(major_version, self._stacks) + self._parse_major_version_linux(major_version, self._stacks, seen_runtimes) if self._windows: self._parse_major_version_windows(major_version, self._stacks, self.windows_config_mappings) @@ -6302,10 +6303,91 @@ def _filter(minor_version): return cls._is_valid_runtime_setting(cls._get_runtime_setting(minor_version, linux, java)) return [m for m in major_version.minor_versions if _filter(m)] + @staticmethod + def _get_java_versions_from_minor_versions(minor_versions): + """Dynamically extract unique Java versions from minor version values. + Used for Linux Java SE containers where minor.value is like "25.0.0", "21.0.0". + Returns versions sorted in descending order (newest first).""" + java_versions = set() + for minor in minor_versions: + # minor.value is like "25.0.0", "21.0.0", "17.0.0", "11.0.0", "8.0.0" or "1.8.0" + value = minor.value + if value: + # Handle both "1.8" format and newer "25", "21" formats + if value.startswith("1.8"): + java_versions.add("1.8") + else: + # Extract major version number (e.g., "25" from "25.0.0") + major_ver = value.split('.')[0] + if major_ver.isdigit(): + java_versions.add(major_ver) + # Sort descending (newest versions first), treating "1.8" specially + return sorted(java_versions, key=lambda x: -1 if x == "1.8" else -int(x)) + + @staticmethod + def _get_java_versions_from_windows_container(container_settings): + """Dynamically extract Java versions from Windows container settings. + Looks at the 'runtimes' array in additional_properties. + Returns versions sorted in descending order (newest first).""" + java_versions = set() + additional_props = getattr(container_settings, 'additional_properties', {}) or {} + runtimes_array = additional_props.get('runtimes', []) + + for runtime_info in runtimes_array: + version = runtime_info.get('runtimeVersion') + if version: + # Normalize version: convert "1.8" to "1.8", keep others as-is + java_versions.add(version) + + # Sort descending (newest versions first), treating "1.8" specially + return sorted(java_versions, key=lambda x: -1 if x == "1.8" else -int(x)) + + @staticmethod + def _get_java_runtimes_from_container_settings(container_settings): + """Dynamically extract Java runtimes from container settings. + Prefers the 'runtimes' array from the API when available (most future-proof), + falls back to individual java*Runtime properties in additional_properties, + and finally SDK-defined properties (java8_runtime, java11_runtime). + Returns list of tuples: (runtime_name, version, is_auto_update)""" + runtimes = [] + is_auto_update = getattr(container_settings, 'is_auto_update', False) + additional_props = getattr(container_settings, 'additional_properties', {}) or {} + + # Prefer the 'runtimes' array if available (cleanest, most future-proof) + runtimes_array = additional_props.get('runtimes', []) + if runtimes_array: + for runtime_info in runtimes_array: + runtime_name = runtime_info.get('runtime') + version = runtime_info.get('runtimeVersion') + if runtime_name and version: + runtimes.append((runtime_name, version, is_auto_update)) + else: + # Fallback: Get runtimes from additional_properties (java*Runtime keys) + for key, value in additional_props.items(): + # Match pattern like "java25Runtime", "java21Runtime", etc. + if key.startswith('java') and key.endswith('Runtime') and value: + # Extract version number from key (e.g., "25" from "java25Runtime") + version = key[4:-7] # Remove "java" prefix and "Runtime" suffix + if version.isdigit(): + runtimes.append((value, version, is_auto_update)) + + # Also get runtimes from SDK-defined properties (java8_runtime, java11_runtime) + if getattr(container_settings, 'java11_runtime', None): + # Avoid duplicates if already found in additional_properties + if not any(v == "11" for _, v, _ in runtimes): + runtimes.append((container_settings.java11_runtime, "11", is_auto_update)) + if getattr(container_settings, 'java8_runtime', None): + if not any(v == "8" for _, v, _ in runtimes): + runtimes.append((container_settings.java8_runtime, "8", is_auto_update)) + + # Sort by version descending (newest first) + runtimes.sort(key=lambda x: -1 if x[1] == "8" else -int(x[1])) + return runtimes + def _parse_major_version_windows(self, major_version, parsed_results, config_mappings): java_container_minor_versions = self._get_valid_minor_versions(major_version, linux=False, java=True) if java_container_minor_versions: - javas = ["21", "17", "11", "1.8"] + # Limit to 3 containers for display if len(java_container_minor_versions) > 0: leng = len(java_container_minor_versions) if len(java_container_minor_versions) < 3 else 3 java_container_minor_versions = java_container_minor_versions[:leng] @@ -6313,6 +6395,8 @@ def _parse_major_version_windows(self, major_version, parsed_results, config_map container_settings = container.stack_settings.windows_container_settings java_container = container_settings.java_container container_version = container_settings.java_container_version + # Get Java versions from the container's runtimes array + javas = self._get_java_versions_from_windows_container(container_settings) for java in javas: runtime = self.get_windows_java_runtime( java, @@ -6380,11 +6464,13 @@ def get_windows_java_runtime(self, java_version=None, linux=False, is_auto_update=is_auto_update) - def _parse_major_version_linux(self, major_version, parsed_results): + def _parse_major_version_linux(self, major_version, parsed_results, seen_runtimes): minor_java_container_versions = self._get_valid_minor_versions(major_version, linux=True, java=True) if "SE" in major_version.display_text: - se_containers = [minor_java_container_versions[0]] - for java in ["21", "17", "11", "1.8"]: + # Dynamically get Java versions from the available minor versions + java_versions = self._get_java_versions_from_minor_versions(minor_java_container_versions) + se_containers = [minor_java_container_versions[0]] if minor_java_container_versions else [] + for java in java_versions: se_java_containers = [c for c in minor_java_container_versions if c.value.startswith(java)] se_containers = se_containers + se_java_containers[:len(se_java_containers) if len(se_java_containers) < 2 else 2] # pylint: disable=line-too-long minor_java_container_versions = se_containers @@ -6394,14 +6480,15 @@ def _parse_major_version_linux(self, major_version, parsed_results): "SE" not in major_version.display_text else len(minor_java_container_versions) for minor in minor_java_container_versions[:leng]: linux_container_settings = minor.stack_settings.linux_container_settings - runtimes = [ - (linux_container_settings.additional_properties.get("java21Runtime"), "21", linux_container_settings.is_auto_update), # pylint: disable=line-too-long - (linux_container_settings.additional_properties.get("java17Runtime"), "17", linux_container_settings.is_auto_update), # pylint: disable=line-too-long - (linux_container_settings.java11_runtime, "11", linux_container_settings.is_auto_update), - (linux_container_settings.java8_runtime, "8", linux_container_settings.is_auto_update)] + # Dynamically get all Java runtimes from container settings + runtimes = self._get_java_runtimes_from_container_settings(linux_container_settings) # Remove the JBoss'_byol' entries from the output runtimes = [(r, v, au) for (r, v, au) in runtimes if r is not None and not r.endswith("_byol")] # pylint: disable=line-too-long for runtime_name, version, auto_update in [(r, v, au) for (r, v, au) in runtimes if r is not None]: + # Skip duplicates + if runtime_name in seen_runtimes: + continue + seen_runtimes.add(runtime_name) runtime = self.Runtime(display_name=runtime_name, configs={"linux_fx_version": runtime_name}, github_actions_properties={"github_actions_version": version}, From 2977124e9f521b2ffbcb45684da1dac37ac922c7 Mon Sep 17 00:00:00 2001 From: Jordan Selig Date: Wed, 26 Nov 2025 17:59:51 +0000 Subject: [PATCH 3/9] Add robust version sorting to handle edge cases - Add _java_version_sort_key() helper to handle various version formats - Handles '1.8', '1.x', 'X.Y' (e.g., '11.0'), and plain integers - Prevents ValueError when version strings can't be directly converted to int --- .../cli/command_modules/appservice/custom.py | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 700c1dbbadf..1d0b9ffc161 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -6303,6 +6303,24 @@ def _filter(minor_version): return cls._is_valid_runtime_setting(cls._get_runtime_setting(minor_version, linux, java)) return [m for m in major_version.minor_versions if _filter(m)] + @staticmethod + def _java_version_sort_key(version): + """Sort key for Java versions. Handles formats like "25", "1.8", "11.0", etc. + Returns negative values so sorted() gives descending order (newest first).""" + if version == "1.8": + return -8 # Treat 1.8 as Java 8 + if version.startswith("1."): + # Handle legacy "1.x" format (e.g., "1.7", "1.9") + try: + return -int(version.split('.')[1]) + except (IndexError, ValueError): + return 0 + # Handle "X.Y" format (e.g., "11.0", "17.0") or plain integers ("25", "21") + try: + return -int(version.split('.')[0]) + except ValueError: + return 0 + @staticmethod def _get_java_versions_from_minor_versions(minor_versions): """Dynamically extract unique Java versions from minor version values. @@ -6321,8 +6339,8 @@ def _get_java_versions_from_minor_versions(minor_versions): major_ver = value.split('.')[0] if major_ver.isdigit(): java_versions.add(major_ver) - # Sort descending (newest versions first), treating "1.8" specially - return sorted(java_versions, key=lambda x: -1 if x == "1.8" else -int(x)) + # Sort descending (newest versions first) + return sorted(java_versions, key=_StackRuntimeHelper._java_version_sort_key) @staticmethod def _get_java_versions_from_windows_container(container_settings): @@ -6339,8 +6357,8 @@ def _get_java_versions_from_windows_container(container_settings): # Normalize version: convert "1.8" to "1.8", keep others as-is java_versions.add(version) - # Sort descending (newest versions first), treating "1.8" specially - return sorted(java_versions, key=lambda x: -1 if x == "1.8" else -int(x)) + # Sort descending (newest versions first) + return sorted(java_versions, key=_StackRuntimeHelper._java_version_sort_key) @staticmethod def _get_java_runtimes_from_container_settings(container_settings): @@ -6381,7 +6399,7 @@ def _get_java_runtimes_from_container_settings(container_settings): runtimes.append((container_settings.java8_runtime, "8", is_auto_update)) # Sort by version descending (newest first) - runtimes.sort(key=lambda x: -1 if x[1] == "8" else -int(x[1])) + runtimes.sort(key=lambda x: _StackRuntimeHelper._java_version_sort_key(x[1])) return runtimes def _parse_major_version_windows(self, major_version, parsed_results, config_mappings): From 9deca7b211755f5e386b8419f1fd8c8ee0cf93a7 Mon Sep 17 00:00:00 2001 From: Jordan Selig Date: Wed, 26 Nov 2025 18:00:58 +0000 Subject: [PATCH 4/9] Fix misleading comment about version normalization --- src/azure-cli/azure/cli/command_modules/appservice/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 1d0b9ffc161..9ec3ee9dc5f 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -6354,7 +6354,7 @@ def _get_java_versions_from_windows_container(container_settings): for runtime_info in runtimes_array: version = runtime_info.get('runtimeVersion') if version: - # Normalize version: convert "1.8" to "1.8", keep others as-is + # Add version as-is (e.g., "25", "21", "17", "11", "1.8") java_versions.add(version) # Sort descending (newest versions first) From bbe21c0e36b2f1b113bacad1546a077ae4a59172 Mon Sep 17 00:00:00 2001 From: Jordan Selig Date: Wed, 26 Nov 2025 18:02:55 +0000 Subject: [PATCH 5/9] Address code review feedback - Add explanatory comment for Linux-only deduplication logic - Add debug logging when no Java versions found in Windows container settings --- .../azure/cli/command_modules/appservice/custom.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 9ec3ee9dc5f..c7016c4f08f 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -6152,7 +6152,10 @@ def _get_raw_stacks_from_api(self): return list(self._client.provider.get_web_app_stacks(stack_os_type=None)) def _parse_raw_stacks(self, stacks): - seen_runtimes = set() # Track seen runtime display names to avoid duplicates + # Track seen runtime display names to avoid duplicates in Linux parsing. + # Linux Java containers (e.g., JBOSSEAP) can produce duplicate entries across major versions. + # Windows parsing doesn't have this issue due to its different structure. + seen_runtimes = set() for lang in stacks: for major_version in lang.major_versions: if self._linux: @@ -6415,6 +6418,9 @@ def _parse_major_version_windows(self, major_version, parsed_results, config_map container_version = container_settings.java_container_version # Get Java versions from the container's runtimes array javas = self._get_java_versions_from_windows_container(container_settings) + if not javas: + logger.debug("No Java versions found in Windows container settings for " + "container '%s' (version: '%s')", java_container, container_version) for java in javas: runtime = self.get_windows_java_runtime( java, From 74669a41b3f48e417223df2b562231cfa41361a3 Mon Sep 17 00:00:00 2001 From: Jordan Selig Date: Thu, 27 Nov 2025 20:57:39 -0500 Subject: [PATCH 6/9] Update src/azure-cli/azure/cli/command_modules/appservice/custom.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/azure-cli/azure/cli/command_modules/appservice/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index c7016c4f08f..7c8ceea1195 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -6309,7 +6309,7 @@ def _filter(minor_version): @staticmethod def _java_version_sort_key(version): """Sort key for Java versions. Handles formats like "25", "1.8", "11.0", etc. - Returns negative values so sorted() gives descending order (newest first).""" + Returns a negative integer representing the version, so sorted() produces descending order (newest first).""" if version == "1.8": return -8 # Treat 1.8 as Java 8 if version.startswith("1."): From c8c5624a784f7aa9a85a85fdceac1b80bb9d22c0 Mon Sep 17 00:00:00 2001 From: Jordan Selig Date: Thu, 27 Nov 2025 20:57:57 -0500 Subject: [PATCH 7/9] Update src/azure-cli/azure/cli/command_modules/appservice/custom.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/azure-cli/azure/cli/command_modules/appservice/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 7c8ceea1195..ca0d0cfb5e7 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -6506,7 +6506,7 @@ def _parse_major_version_linux(self, major_version, parsed_results, seen_runtime linux_container_settings = minor.stack_settings.linux_container_settings # Dynamically get all Java runtimes from container settings runtimes = self._get_java_runtimes_from_container_settings(linux_container_settings) - # Remove the JBoss'_byol' entries from the output + # Remove the 'JBoss _byol' entries from the output runtimes = [(r, v, au) for (r, v, au) in runtimes if r is not None and not r.endswith("_byol")] # pylint: disable=line-too-long for runtime_name, version, auto_update in [(r, v, au) for (r, v, au) in runtimes if r is not None]: # Skip duplicates From d574e47f372bb20f4b66c12016d92244192ce51e Mon Sep 17 00:00:00 2001 From: Jordan Selig Date: Mon, 1 Dec 2025 16:22:49 +0000 Subject: [PATCH 8/9] Address code review: use regex and min() for clarity - Use regex pattern to extract version from java*Runtime keys instead of magic string slicing - Use min() instead of ternary for limiting list length --- .../azure/cli/command_modules/appservice/custom.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index ca0d0cfb5e7..536e8888067 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -6386,11 +6386,10 @@ def _get_java_runtimes_from_container_settings(container_settings): # Fallback: Get runtimes from additional_properties (java*Runtime keys) for key, value in additional_props.items(): # Match pattern like "java25Runtime", "java21Runtime", etc. - if key.startswith('java') and key.endswith('Runtime') and value: - # Extract version number from key (e.g., "25" from "java25Runtime") - version = key[4:-7] # Remove "java" prefix and "Runtime" suffix - if version.isdigit(): - runtimes.append((value, version, is_auto_update)) + match = re.match(r'^java(\d+)Runtime$', key) + if match and value: + version = match.group(1) + runtimes.append((value, version, is_auto_update)) # Also get runtimes from SDK-defined properties (java8_runtime, java11_runtime) if getattr(container_settings, 'java11_runtime', None): @@ -6496,7 +6495,7 @@ def _parse_major_version_linux(self, major_version, parsed_results, seen_runtime se_containers = [minor_java_container_versions[0]] if minor_java_container_versions else [] for java in java_versions: se_java_containers = [c for c in minor_java_container_versions if c.value.startswith(java)] - se_containers = se_containers + se_java_containers[:len(se_java_containers) if len(se_java_containers) < 2 else 2] # pylint: disable=line-too-long + se_containers = se_containers + se_java_containers[:min(len(se_java_containers), 2)] minor_java_container_versions = se_containers if minor_java_container_versions: leng = len(minor_java_container_versions) if \ From 4adbaf27f942b76f81abd1ab66fe9b0a4e1dc726 Mon Sep 17 00:00:00 2001 From: Jordan Selig Date: Fri, 5 Dec 2025 18:42:41 +0000 Subject: [PATCH 9/9] remove response limits --- .../cli/command_modules/appservice/custom.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 536e8888067..0f305b38916 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -6407,10 +6407,6 @@ def _get_java_runtimes_from_container_settings(container_settings): def _parse_major_version_windows(self, major_version, parsed_results, config_mappings): java_container_minor_versions = self._get_valid_minor_versions(major_version, linux=False, java=True) if java_container_minor_versions: - # Limit to 3 containers for display - if len(java_container_minor_versions) > 0: - leng = len(java_container_minor_versions) if len(java_container_minor_versions) < 3 else 3 - java_container_minor_versions = java_container_minor_versions[:leng] for container in java_container_minor_versions: container_settings = container.stack_settings.windows_container_settings java_container = container_settings.java_container @@ -6429,10 +6425,6 @@ def _parse_major_version_windows(self, major_version, parsed_results, config_map parsed_results.append(runtime) else: minor_versions = self._get_valid_minor_versions(major_version, linux=False, java=False) - if "Java" in major_version.display_text: - if len(minor_versions) > 0: - leng = len(minor_versions) if len(minor_versions) < 3 else 3 - minor_versions = minor_versions[1:leng] for minor_version in minor_versions: settings = minor_version.stack_settings.windows_runtime_settings if "Java" not in minor_version.display_text: @@ -6495,13 +6487,10 @@ def _parse_major_version_linux(self, major_version, parsed_results, seen_runtime se_containers = [minor_java_container_versions[0]] if minor_java_container_versions else [] for java in java_versions: se_java_containers = [c for c in minor_java_container_versions if c.value.startswith(java)] - se_containers = se_containers + se_java_containers[:min(len(se_java_containers), 2)] + se_containers = se_containers + se_java_containers minor_java_container_versions = se_containers if minor_java_container_versions: - leng = len(minor_java_container_versions) if \ - len(minor_java_container_versions) < 3 else 3 if \ - "SE" not in major_version.display_text else len(minor_java_container_versions) - for minor in minor_java_container_versions[:leng]: + for minor in minor_java_container_versions: linux_container_settings = minor.stack_settings.linux_container_settings # Dynamically get all Java runtimes from container settings runtimes = self._get_java_runtimes_from_container_settings(linux_container_settings)