From 9dbee9cf33be501bc74f1165ae2ef8f0a710259a Mon Sep 17 00:00:00 2001 From: Jordan Selig Date: Thu, 26 Mar 2026 11:30:47 -0400 Subject: [PATCH 1/2] [App Service] Fix #25743, #25597, #30756, #25129, #25905: `az webapp up`: detection and validation improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #25743: Enhance OS auto-detection from runtime (Python/Node/dotnetcore/dotnet → Linux, ASP.NET → Windows) - #25597: Add pre-validation of runtime+OS combo before resource creation with helpful error messages - #30756: Add Python version detection from runtime.txt and .python-version files - #25129: Auto-detect static HTML sites without --html flag; default HTML to Linux - #25905: Improve help text with more examples and accurate OS defaults documentation Also fixes shtml glob pattern (was *shtml. instead of *.shtml). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../appservice/_create_util.py | 88 +++++++-- .../cli/command_modules/appservice/_help.py | 19 +- .../cli/command_modules/appservice/custom.py | 6 +- .../latest/test_webapp_commands_thru_mock.py | 168 ++++++++++++++++++ 4 files changed, 266 insertions(+), 15 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py b/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py index cd282b66750..d0af1f377e0 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py @@ -15,7 +15,8 @@ from ._constants import (NETCORE_RUNTIME_NAME, NODE_RUNTIME_NAME, ASPDOTNET_RUNTIME_NAME, STATIC_RUNTIME_NAME, PYTHON_RUNTIME_NAME, LINUX_SKU_DEFAULT, OS_DEFAULT, DOTNET_RUNTIME_NAME, - DOTNET_TARGET_FRAMEWORK_REGEX, GENERATE_RANDOM_APP_NAMES, DOTNET_REFERENCES_DIR_IN_ZIP) + DOTNET_TARGET_FRAMEWORK_REGEX, GENERATE_RANDOM_APP_NAMES, DOTNET_REFERENCES_DIR_IN_ZIP, + LINUX_OS_NAME) from .utils import get_resource_if_exists logger = get_logger(__name__) @@ -113,8 +114,11 @@ def get_runtime_version_details(file_path, lang_name, stack_helper, is_linux=Fal version_detected = parse_node_version(file_path)[0] version_to_create = detect_node_version_tocreate(version_detected, versions, default_version) elif lang_name.lower() == PYTHON_RUNTIME_NAME: - version_detected = "-" - version_to_create = default_version + version_detected = _detect_python_version(file_path) + if version_detected != "-" and version_detected in versions: + version_to_create = version_detected + else: + version_to_create = default_version elif lang_name.lower() == STATIC_RUNTIME_NAME: version_detected = "-" version_to_create = "-" @@ -169,10 +173,10 @@ def get_lang_from_content(src_path, html=False, is_linux=False): import fnmatch for _dirpath, _dirnames, files in os.walk(src_path): for file in files: - if html and (fnmatch.fnmatch(file, "*.html") or fnmatch.fnmatch(file, "*.htm") or - fnmatch.fnmatch(file, "*shtml.")): - static_html_file = os.path.join(src_path, file) - break + if fnmatch.fnmatch(file, "*.html") or fnmatch.fnmatch(file, "*.htm") or \ + fnmatch.fnmatch(file, "*.shtml"): + if not static_html_file: + static_html_file = os.path.join(src_path, file) if fnmatch.fnmatch(file, "*.csproj"): package_netcore_file = os.path.join(src_path, file) if not os.path.isfile(package_netcore_file): @@ -200,7 +204,12 @@ def get_lang_from_content(src_path, html=False, is_linux=False): runtime_details_dict['language'] = runtime_lang runtime_details_dict['file_loc'] = package_netcore_file runtime_details_dict['default_sku'] = 'F1' - else: # TODO: Update the doc when the detection logic gets updated + elif static_html_file: + # Auto-detect static HTML even without --html flag + runtime_details_dict['language'] = STATIC_RUNTIME_NAME + runtime_details_dict['file_loc'] = static_html_file + runtime_details_dict['default_sku'] = 'F1' + else: raise CLIError("Could not auto-detect the runtime stack of your app.\n" "HINT: Are you in the right folder?\n" "For more information, see 'https://go.microsoft.com/fwlink/?linkid=2109470'") @@ -288,6 +297,40 @@ def parse_node_version(file_path): return version_detected or ['0.0'] +def _detect_python_version(file_path): + """Detect Python version from runtime.txt, .python-version, or Dockerfile in the project directory.""" + import re + src_dir = os.path.dirname(file_path) if file_path else '' + if not src_dir: + return "-" + + # Check runtime.txt (used by Azure/Heroku: "python-3.11.4") + runtime_txt = os.path.join(src_dir, 'runtime.txt') + if os.path.isfile(runtime_txt): + try: + with open(runtime_txt) as f: + content = f.read().strip().lower() + match = re.search(r'python-(\d+\.\d+)', content) + if match: + return match.group(1) + except Exception: # pylint: disable=broad-except + pass + + # Check .python-version (used by pyenv: "3.11.4" or "3.11") + python_version_file = os.path.join(src_dir, '.python-version') + if os.path.isfile(python_version_file): + try: + with open(python_version_file) as f: + content = f.read().strip() + match = re.match(r'^(\d+\.\d+)', content) + if match: + return match.group(1) + except Exception: # pylint: disable=broad-except + pass + + return "-" + + def detect_dotnet_version_tocreate(detected_ver, default_version, versions_list): min_ver = versions_list[0] if detected_ver in versions_list: @@ -406,8 +449,33 @@ def detect_os_from_src(src_dir, html=False, runtime=None): language = runtime.split(_StackRuntimeHelper.DEFAULT_DELIMETER)[0] else: language = get_lang_from_content(src_dir, html).get('language') - return "Linux" if language is not None and language.lower() == NODE_RUNTIME_NAME \ - or language.lower() == PYTHON_RUNTIME_NAME else OS_DEFAULT + if language is None: + return OS_DEFAULT + lang_lower = language.lower() + # Python and Node are Linux-first; .NET Core / modern dotnet also default to Linux + if lang_lower in (NODE_RUNTIME_NAME, PYTHON_RUNTIME_NAME, NETCORE_RUNTIME_NAME, DOTNET_RUNTIME_NAME): + return "Linux" + # Static HTML sites can run on Linux via Node + if lang_lower == STATIC_RUNTIME_NAME: + return "Linux" + return OS_DEFAULT + + +def validate_runtime_os_combo(language, version_used_create, os_name, stack_helper, is_linux): + """Validate that the runtime+OS combination is supported before creating resources. + Raises ValidationError with a helpful message if the combination is invalid.""" + from azure.cli.core.azclierror import ValidationError + if not language or language.lower() == STATIC_RUNTIME_NAME: + return # static doesn't need runtime validation + runtime_version = "{}|{}".format(language, version_used_create) if version_used_create != "-" else None + if runtime_version: + match = stack_helper.resolve(runtime_version, is_linux) + if not match: + raise ValidationError( + "The runtime '{}' is not supported on {}. " + "Please check supported runtimes with: 'az webapp list-runtimes --os {}'.\n" + "HINT: Try a different --os-type or --runtime value.".format( + runtime_version, os_name, os_name)) def get_plan_to_use(cmd, user, loc, sku, create_rg, resource_group_name, client, is_linux=False, plan=None): diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_help.py b/src/azure-cli/azure/cli/command_modules/appservice/_help.py index d4b4e4e3693..410000f7dae 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_help.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_help.py @@ -2620,9 +2620,11 @@ type: command short-summary: > Create a webapp and deploy code from a local workspace to the app. The command is required to run from the folder - where the code is present. Current support includes Node, Python, .NET Core and ASP.NET. Node, - Python apps are created as Linux apps. .Net Core, ASP.NET, and static HTML apps are created as Windows apps. - Append the html flag to deploy as a static HTML app. + where the code is present. Current support includes Node, Python, .NET Core and ASP.NET. Node, Python, and .NET Core + apps are created as Linux apps. ASP.NET apps are created as Windows apps. + Static HTML sites are auto-detected and deployed as Linux apps; use the --html flag to force HTML detection. + The runtime and OS are auto-detected from source files but can be overridden with --runtime and --os-type. + The runtime version is read from project files (runtime.txt, .python-version, package.json engines, *.csproj). Each time the command is successfully run, default argument values for resource group, sku, location, plan, and name are saved for the current directory. These defaults are then used for any arguments not provided on subsequent runs of the command in the same directory. Use 'az configure' to manage defaults. Run this command with the --debug parameter to see the API calls and parameters values being used. @@ -2650,15 +2652,24 @@ - name: Create a web app with a specified name and a Java 11 runtime text: > az webapp up -n MyUniqueAppName --runtime "java:11:Java SE:11" + - name: Deploy a Python app (auto-detected from requirements.txt) to a Linux app + text: > + az webapp up -n MyPythonApp + - name: Deploy a Node.js app with a specific runtime version + text: > + az webapp up -n MyNodeApp --runtime "node|18-lts" - name: Create a web app in a specific region, by running the command from the folder where the code to be deployed exists. text: > az webapp up -l locationName - name: Create a web app and enable log streaming after the deployment operation is complete. This will enable the default configuration required to enable log streaming. text: > az webapp up --logs - - name: Create a web app and deploy as a static HTML app. + - name: Deploy a static HTML site (auto-detected or forced with --html) text: > az webapp up --html + - name: Deploy a .NET app and explicitly set the OS type + text: > + az webapp up -n MyDotnetApp --os-type Linux --runtime "dotnetcore|8.0" - name: Create a web app with a specified domain name scope for unique hostname generation text: > az webapp up -n MyUniqueAppName --domain-name-scope TenantReuse 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 99d473c4b7d..cb997e68ad7 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -85,7 +85,7 @@ get_plan_to_use, get_lang_from_content, get_rg_to_use, get_sku_to_use, detect_os_from_src, get_current_stack_from_runtime, generate_default_app_name, get_or_create_default_workspace, get_or_create_default_resource_group, - get_workspace) + get_workspace, validate_runtime_os_combo) from ._constants import (FUNCTIONS_STACKS_API_KEYS, FUNCTIONS_LINUX_RUNTIME_VERSION_REGEX, FUNCTIONS_WINDOWS_RUNTIME_VERSION_REGEX, PUBLIC_CLOUD, LINUX_GITHUB_ACTIONS_WORKFLOW_TEMPLATE_PATH, WINDOWS_GITHUB_ACTIONS_WORKFLOW_TEMPLATE_PATH, @@ -9873,6 +9873,10 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None else: logger.warning("No --runtime specified. Using %s version: %s.", language, version_used_create) + # Pre-validate runtime+OS combo before creating any resources (#25597) + if _create_new_app: + validate_runtime_os_combo(language, version_used_create, os_name, helper, _is_linux) + runtime_version = "{}|{}".format(language, version_used_create) if \ version_used_create != "-" else version_used_create site_config = None diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py index 32f3e5325da..44cd6a0bea5 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py @@ -703,5 +703,173 @@ def test_default_sku_is_p0v3_when_not_specified(self, mock_location, mock_client self.assertIn('P0V3', str(call_kwargs)) +class TestDetectOsFromSrc(unittest.TestCase): + """Tests for detect_os_from_src improvements (#25743)""" + + def test_python_detected_as_linux(self): + from azure.cli.command_modules.appservice._create_util import detect_os_from_src + import tempfile + tmp = tempfile.mkdtemp() + with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: + f.write('flask\n') + result = detect_os_from_src(tmp) + self.assertEqual(result, "Linux") + import shutil + shutil.rmtree(tmp) + + def test_node_detected_as_linux(self): + from azure.cli.command_modules.appservice._create_util import detect_os_from_src + import tempfile + import json + tmp = tempfile.mkdtemp() + with open(os.path.join(tmp, 'package.json'), 'w') as f: + json.dump({"name": "test", "version": "1.0.0"}, f) + result = detect_os_from_src(tmp) + self.assertEqual(result, "Linux") + import shutil + shutil.rmtree(tmp) + + def test_html_detected_as_linux(self): + from azure.cli.command_modules.appservice._create_util import detect_os_from_src + import tempfile + tmp = tempfile.mkdtemp() + with open(os.path.join(tmp, 'index.html'), 'w') as f: + f.write('') + result = detect_os_from_src(tmp, html=True) + self.assertEqual(result, "Linux") + import shutil + shutil.rmtree(tmp) + + def test_runtime_override_python(self): + from azure.cli.command_modules.appservice._create_util import detect_os_from_src + import tempfile + tmp = tempfile.mkdtemp() + result = detect_os_from_src(tmp, runtime="python|3.11") + self.assertEqual(result, "Linux") + import shutil + shutil.rmtree(tmp) + + def test_runtime_override_aspnet(self): + from azure.cli.command_modules.appservice._create_util import detect_os_from_src + import tempfile + tmp = tempfile.mkdtemp() + result = detect_os_from_src(tmp, runtime="aspnet|4.8") + self.assertEqual(result, "Windows") + import shutil + shutil.rmtree(tmp) + + +class TestGetLangFromContent(unittest.TestCase): + """Tests for static HTML auto-detection (#25129)""" + + def test_html_autodetected_without_flag(self): + from azure.cli.command_modules.appservice._create_util import get_lang_from_content + import tempfile + tmp = tempfile.mkdtemp() + with open(os.path.join(tmp, 'index.html'), 'w') as f: + f.write('') + result = get_lang_from_content(tmp, html=False) + self.assertEqual(result['language'], 'static') + import shutil + shutil.rmtree(tmp) + + def test_python_takes_precedence_over_html(self): + from azure.cli.command_modules.appservice._create_util import get_lang_from_content + import tempfile + tmp = tempfile.mkdtemp() + with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: + f.write('flask\n') + with open(os.path.join(tmp, 'index.html'), 'w') as f: + f.write('') + result = get_lang_from_content(tmp, html=False) + self.assertEqual(result['language'], 'python') + import shutil + shutil.rmtree(tmp) + + +class TestDetectPythonVersion(unittest.TestCase): + """Tests for Python version detection from project files (#30756)""" + + def test_detect_from_runtime_txt(self): + from azure.cli.command_modules.appservice._create_util import _detect_python_version + import tempfile + tmp = tempfile.mkdtemp() + with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: + f.write('flask\n') + with open(os.path.join(tmp, 'runtime.txt'), 'w') as f: + f.write('python-3.11.4\n') + result = _detect_python_version(os.path.join(tmp, 'requirements.txt')) + self.assertEqual(result, '3.11') + import shutil + shutil.rmtree(tmp) + + def test_detect_from_python_version_file(self): + from azure.cli.command_modules.appservice._create_util import _detect_python_version + import tempfile + tmp = tempfile.mkdtemp() + with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: + f.write('flask\n') + with open(os.path.join(tmp, '.python-version'), 'w') as f: + f.write('3.10.2\n') + result = _detect_python_version(os.path.join(tmp, 'requirements.txt')) + self.assertEqual(result, '3.10') + import shutil + shutil.rmtree(tmp) + + def test_no_version_file_returns_dash(self): + from azure.cli.command_modules.appservice._create_util import _detect_python_version + import tempfile + tmp = tempfile.mkdtemp() + with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: + f.write('flask\n') + result = _detect_python_version(os.path.join(tmp, 'requirements.txt')) + self.assertEqual(result, '-') + import shutil + shutil.rmtree(tmp) + + def test_runtime_txt_takes_precedence(self): + from azure.cli.command_modules.appservice._create_util import _detect_python_version + import tempfile + tmp = tempfile.mkdtemp() + with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: + f.write('flask\n') + with open(os.path.join(tmp, 'runtime.txt'), 'w') as f: + f.write('python-3.12.0\n') + with open(os.path.join(tmp, '.python-version'), 'w') as f: + f.write('3.10\n') + result = _detect_python_version(os.path.join(tmp, 'requirements.txt')) + self.assertEqual(result, '3.12') + import shutil + shutil.rmtree(tmp) + + +class TestValidateRuntimeOsCombo(unittest.TestCase): + """Tests for runtime+OS pre-validation (#25597)""" + + def test_static_runtime_skips_validation(self): + from azure.cli.command_modules.appservice._create_util import validate_runtime_os_combo + validate_runtime_os_combo('static', '-', 'Linux', None, True) + + def test_no_version_skips_validation(self): + from azure.cli.command_modules.appservice._create_util import validate_runtime_os_combo + validate_runtime_os_combo('python', '-', 'Linux', None, True) + + def test_invalid_combo_raises_error(self): + from azure.cli.command_modules.appservice._create_util import validate_runtime_os_combo + from azure.cli.core.azclierror import ValidationError + + mock_helper = mock.MagicMock() + mock_helper.resolve.return_value = None + with self.assertRaises(ValidationError): + validate_runtime_os_combo('python', '3.11', 'Windows', mock_helper, False) + + def test_valid_combo_passes(self): + from azure.cli.command_modules.appservice._create_util import validate_runtime_os_combo + + mock_helper = mock.MagicMock() + mock_helper.resolve.return_value = mock.MagicMock() + validate_runtime_os_combo('python', '3.11', 'Linux', mock_helper, True) + + if __name__ == '__main__': unittest.main() From 840fac094d8935e91b22a5da4ec4dea181b18280 Mon Sep 17 00:00:00 2001 From: Jordan Selig Date: Thu, 26 Mar 2026 15:21:53 -0400 Subject: [PATCH 2/2] Fix review comments: unused import, HTML path, docstring, test cleanup, lint - Remove unused LINUX_OS_NAME import (_create_util.py) - Fix static HTML path: use _dirpath instead of src_path in os.path.join - Update _detect_python_version docstring to match implementation - Fix duplicate string formatting argument (pylint W1308) - Replace tempfile.mkdtemp()+shutil.rmtree() with TemporaryDirectory() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../appservice/_create_util.py | 13 +- .../latest/test_webapp_commands_thru_mock.py | 144 ++++++++---------- 2 files changed, 67 insertions(+), 90 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py b/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py index d0af1f377e0..453df899579 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py @@ -15,8 +15,7 @@ from ._constants import (NETCORE_RUNTIME_NAME, NODE_RUNTIME_NAME, ASPDOTNET_RUNTIME_NAME, STATIC_RUNTIME_NAME, PYTHON_RUNTIME_NAME, LINUX_SKU_DEFAULT, OS_DEFAULT, DOTNET_RUNTIME_NAME, - DOTNET_TARGET_FRAMEWORK_REGEX, GENERATE_RANDOM_APP_NAMES, DOTNET_REFERENCES_DIR_IN_ZIP, - LINUX_OS_NAME) + DOTNET_TARGET_FRAMEWORK_REGEX, GENERATE_RANDOM_APP_NAMES, DOTNET_REFERENCES_DIR_IN_ZIP) from .utils import get_resource_if_exists logger = get_logger(__name__) @@ -176,7 +175,7 @@ def get_lang_from_content(src_path, html=False, is_linux=False): if fnmatch.fnmatch(file, "*.html") or fnmatch.fnmatch(file, "*.htm") or \ fnmatch.fnmatch(file, "*.shtml"): if not static_html_file: - static_html_file = os.path.join(src_path, file) + static_html_file = os.path.join(_dirpath, file) if fnmatch.fnmatch(file, "*.csproj"): package_netcore_file = os.path.join(src_path, file) if not os.path.isfile(package_netcore_file): @@ -298,7 +297,7 @@ def parse_node_version(file_path): def _detect_python_version(file_path): - """Detect Python version from runtime.txt, .python-version, or Dockerfile in the project directory.""" + """Detect Python version from runtime.txt or .python-version in the project directory.""" import re src_dir = os.path.dirname(file_path) if file_path else '' if not src_dir: @@ -472,10 +471,10 @@ def validate_runtime_os_combo(language, version_used_create, os_name, stack_help match = stack_helper.resolve(runtime_version, is_linux) if not match: raise ValidationError( - "The runtime '{}' is not supported on {}. " - "Please check supported runtimes with: 'az webapp list-runtimes --os {}'.\n" + "The runtime '{}' is not supported on {os_name}. " + "Please check supported runtimes with: 'az webapp list-runtimes --os {os_name}'.\n" "HINT: Try a different --os-type or --runtime value.".format( - runtime_version, os_name, os_name)) + runtime_version, os_name=os_name)) def get_plan_to_use(cmd, user, loc, sku, create_rg, resource_group_name, client, is_linux=False, plan=None): diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py index 44cd6a0bea5..ec75b85cd8c 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py @@ -709,54 +709,44 @@ class TestDetectOsFromSrc(unittest.TestCase): def test_python_detected_as_linux(self): from azure.cli.command_modules.appservice._create_util import detect_os_from_src import tempfile - tmp = tempfile.mkdtemp() - with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: - f.write('flask\n') - result = detect_os_from_src(tmp) - self.assertEqual(result, "Linux") - import shutil - shutil.rmtree(tmp) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: + f.write('flask\n') + result = detect_os_from_src(tmp) + self.assertEqual(result, "Linux") def test_node_detected_as_linux(self): from azure.cli.command_modules.appservice._create_util import detect_os_from_src import tempfile import json - tmp = tempfile.mkdtemp() - with open(os.path.join(tmp, 'package.json'), 'w') as f: - json.dump({"name": "test", "version": "1.0.0"}, f) - result = detect_os_from_src(tmp) - self.assertEqual(result, "Linux") - import shutil - shutil.rmtree(tmp) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'package.json'), 'w') as f: + json.dump({"name": "test", "version": "1.0.0"}, f) + result = detect_os_from_src(tmp) + self.assertEqual(result, "Linux") def test_html_detected_as_linux(self): from azure.cli.command_modules.appservice._create_util import detect_os_from_src import tempfile - tmp = tempfile.mkdtemp() - with open(os.path.join(tmp, 'index.html'), 'w') as f: - f.write('') - result = detect_os_from_src(tmp, html=True) - self.assertEqual(result, "Linux") - import shutil - shutil.rmtree(tmp) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'index.html'), 'w') as f: + f.write('') + result = detect_os_from_src(tmp, html=True) + self.assertEqual(result, "Linux") def test_runtime_override_python(self): from azure.cli.command_modules.appservice._create_util import detect_os_from_src import tempfile - tmp = tempfile.mkdtemp() - result = detect_os_from_src(tmp, runtime="python|3.11") - self.assertEqual(result, "Linux") - import shutil - shutil.rmtree(tmp) + with tempfile.TemporaryDirectory() as tmp: + result = detect_os_from_src(tmp, runtime="python|3.11") + self.assertEqual(result, "Linux") def test_runtime_override_aspnet(self): from azure.cli.command_modules.appservice._create_util import detect_os_from_src import tempfile - tmp = tempfile.mkdtemp() - result = detect_os_from_src(tmp, runtime="aspnet|4.8") - self.assertEqual(result, "Windows") - import shutil - shutil.rmtree(tmp) + with tempfile.TemporaryDirectory() as tmp: + result = detect_os_from_src(tmp, runtime="aspnet|4.8") + self.assertEqual(result, "Windows") class TestGetLangFromContent(unittest.TestCase): @@ -765,26 +755,22 @@ class TestGetLangFromContent(unittest.TestCase): def test_html_autodetected_without_flag(self): from azure.cli.command_modules.appservice._create_util import get_lang_from_content import tempfile - tmp = tempfile.mkdtemp() - with open(os.path.join(tmp, 'index.html'), 'w') as f: - f.write('') - result = get_lang_from_content(tmp, html=False) - self.assertEqual(result['language'], 'static') - import shutil - shutil.rmtree(tmp) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'index.html'), 'w') as f: + f.write('') + result = get_lang_from_content(tmp, html=False) + self.assertEqual(result['language'], 'static') def test_python_takes_precedence_over_html(self): from azure.cli.command_modules.appservice._create_util import get_lang_from_content import tempfile - tmp = tempfile.mkdtemp() - with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: - f.write('flask\n') - with open(os.path.join(tmp, 'index.html'), 'w') as f: - f.write('') - result = get_lang_from_content(tmp, html=False) - self.assertEqual(result['language'], 'python') - import shutil - shutil.rmtree(tmp) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: + f.write('flask\n') + with open(os.path.join(tmp, 'index.html'), 'w') as f: + f.write('') + result = get_lang_from_content(tmp, html=False) + self.assertEqual(result['language'], 'python') class TestDetectPythonVersion(unittest.TestCase): @@ -793,54 +779,46 @@ class TestDetectPythonVersion(unittest.TestCase): def test_detect_from_runtime_txt(self): from azure.cli.command_modules.appservice._create_util import _detect_python_version import tempfile - tmp = tempfile.mkdtemp() - with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: - f.write('flask\n') - with open(os.path.join(tmp, 'runtime.txt'), 'w') as f: - f.write('python-3.11.4\n') - result = _detect_python_version(os.path.join(tmp, 'requirements.txt')) - self.assertEqual(result, '3.11') - import shutil - shutil.rmtree(tmp) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: + f.write('flask\n') + with open(os.path.join(tmp, 'runtime.txt'), 'w') as f: + f.write('python-3.11.4\n') + result = _detect_python_version(os.path.join(tmp, 'requirements.txt')) + self.assertEqual(result, '3.11') def test_detect_from_python_version_file(self): from azure.cli.command_modules.appservice._create_util import _detect_python_version import tempfile - tmp = tempfile.mkdtemp() - with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: - f.write('flask\n') - with open(os.path.join(tmp, '.python-version'), 'w') as f: - f.write('3.10.2\n') - result = _detect_python_version(os.path.join(tmp, 'requirements.txt')) - self.assertEqual(result, '3.10') - import shutil - shutil.rmtree(tmp) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: + f.write('flask\n') + with open(os.path.join(tmp, '.python-version'), 'w') as f: + f.write('3.10.2\n') + result = _detect_python_version(os.path.join(tmp, 'requirements.txt')) + self.assertEqual(result, '3.10') def test_no_version_file_returns_dash(self): from azure.cli.command_modules.appservice._create_util import _detect_python_version import tempfile - tmp = tempfile.mkdtemp() - with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: - f.write('flask\n') - result = _detect_python_version(os.path.join(tmp, 'requirements.txt')) - self.assertEqual(result, '-') - import shutil - shutil.rmtree(tmp) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: + f.write('flask\n') + result = _detect_python_version(os.path.join(tmp, 'requirements.txt')) + self.assertEqual(result, '-') def test_runtime_txt_takes_precedence(self): from azure.cli.command_modules.appservice._create_util import _detect_python_version import tempfile - tmp = tempfile.mkdtemp() - with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: - f.write('flask\n') - with open(os.path.join(tmp, 'runtime.txt'), 'w') as f: - f.write('python-3.12.0\n') - with open(os.path.join(tmp, '.python-version'), 'w') as f: - f.write('3.10\n') - result = _detect_python_version(os.path.join(tmp, 'requirements.txt')) - self.assertEqual(result, '3.12') - import shutil - shutil.rmtree(tmp) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'requirements.txt'), 'w') as f: + f.write('flask\n') + with open(os.path.join(tmp, 'runtime.txt'), 'w') as f: + f.write('python-3.12.0\n') + with open(os.path.join(tmp, '.python-version'), 'w') as f: + f.write('3.10\n') + result = _detect_python_version(os.path.join(tmp, 'requirements.txt')) + self.assertEqual(result, '3.12') class TestValidateRuntimeOsCombo(unittest.TestCase):