From 180f0db3d881f78b566e693d7279c9490b44af50 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 | 171 ++++++++++++++++++ 4 files changed, 269 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 7bc6868266c..b88aacb6e55 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__) @@ -109,8 +110,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 = "-" @@ -165,10 +169,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): @@ -196,7 +200,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'") @@ -284,6 +293,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: @@ -402,8 +445,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 e0dad92e98c..71dd4cd7a68 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_help.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_help.py @@ -2586,9 +2586,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. @@ -2606,15 +2608,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 386ba088608..3c8b7bca1a4 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -82,7 +82,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, @@ -9119,6 +9119,10 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None version_used_create = _data.get('to_create') detected_version = _data.get('detected') + # 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 853eadc1edd..61df0a44db8 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 @@ -644,5 +644,176 @@ def __init__(self, status_code): self.status_code = status_code +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 + # Should not raise for static runtime + 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 + # Should not raise when version is "-" + 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 # no match => invalid combo + 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() # match found => valid + # Should not raise + validate_runtime_os_combo('python', '3.11', 'Linux', mock_helper, True) + + if __name__ == '__main__': unittest.main() From 87fe350ad6a803a9d1ca4fe72cb0ec8220fbeffb 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 b88aacb6e55..5fe3583395a 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__) @@ -172,7 +171,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): @@ -294,7 +293,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: @@ -468,10 +467,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 61df0a44db8..01cf0adabac 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 @@ -650,54 +650,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): @@ -706,26 +696,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): @@ -734,54 +720,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):