diff --git a/release_notes.md b/release_notes.md index 19cf86fa0..cfac60920 100644 --- a/release_notes.md +++ b/release_notes.md @@ -8,3 +8,4 @@ #### Changes - +- Add Dockerfile for python 3.13 local build environment (#4611) diff --git a/src/Cli/func/Azure.Functions.Cli.csproj b/src/Cli/func/Azure.Functions.Cli.csproj index 43dadc599..e416f4435 100644 --- a/src/Cli/func/Azure.Functions.Cli.csproj +++ b/src/Cli/func/Azure.Functions.Cli.csproj @@ -98,6 +98,9 @@ $(AssemblyName).Dockerfile.python3.13 + + $(AssemblyName).Dockerfile.python3.13-buildenv + $(AssemblyName).Dockerfile.javascript diff --git a/src/Cli/func/Common/Constants.cs b/src/Cli/func/Common/Constants.cs index e1e633c18..a446c0756 100644 --- a/src/Cli/func/Common/Constants.cs +++ b/src/Cli/func/Common/Constants.cs @@ -189,7 +189,6 @@ public static class DockerImages public const string LinuxPython310ImageAmd64 = "mcr.microsoft.com/azure-functions/python:4-python3.10-buildenv"; public const string LinuxPython311ImageAmd64 = "mcr.microsoft.com/azure-functions/python:4-python3.11-buildenv"; public const string LinuxPython312ImageAmd64 = "mcr.microsoft.com/azure-functions/python:4-python3.12-buildenv"; - public const string LinuxPython313ImageAmd64 = "mcr.microsoft.com/azure-functions/python:4-python3.13-buildenv"; } public static class StaticResourcesNames diff --git a/src/Cli/func/Helpers/DockerImageInfo.cs b/src/Cli/func/Helpers/DockerImageInfo.cs new file mode 100644 index 000000000..c867492ef --- /dev/null +++ b/src/Cli/func/Helpers/DockerImageInfo.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Azure.Functions.Cli.Helpers; + +public class DockerImageInfo +{ + public string ImageName { get; set; } + + public bool CanPull { get; set; } +} diff --git a/src/Cli/func/Helpers/PythonHelpers.cs b/src/Cli/func/Helpers/PythonHelpers.cs index 2afb69abe..be50a3a8d 100644 --- a/src/Cli/func/Helpers/PythonHelpers.cs +++ b/src/Cli/func/Helpers/PythonHelpers.cs @@ -362,7 +362,8 @@ public static async Task ZipToSquashfsStream(Stream stream) string containerId = null; try { - string dockerImage = await ChoosePythonBuildEnvImage(); + DockerImageInfo result = await ChoosePythonBuildEnvImage(); + string dockerImage = result.ImageName; containerId = await DockerHelpers.DockerRun(dockerImage, command: "sleep infinity"); await DockerHelpers.CopyToContainer(containerId, tmpFile, $"/file.zip"); @@ -494,14 +495,19 @@ private static async Task RestorePythonRequirementsDocker(string functionAppRoot var dockerSkipPullFlagSetting = Environment.GetEnvironmentVariable(Constants.PythonDockerImageSkipPull); var dockerRunSetting = Environment.GetEnvironmentVariable(Constants.PythonDockerRunCommand); + bool canPull = true; string dockerImage = pythonDockerImageSetting; if (string.IsNullOrEmpty(dockerImage)) { - dockerImage = await ChoosePythonBuildEnvImage(); + DockerImageInfo result = await ChoosePythonBuildEnvImage(); + dockerImage = result.ImageName; + canPull = result.CanPull; } - if (string.IsNullOrEmpty(dockerSkipPullFlagSetting) || - !(dockerSkipPullFlagSetting.Equals("true", StringComparison.OrdinalIgnoreCase) || dockerSkipPullFlagSetting == "1")) + if (canPull && + (string.IsNullOrEmpty(dockerSkipPullFlagSetting) || + !(dockerSkipPullFlagSetting.Equals("true", StringComparison.OrdinalIgnoreCase) || + dockerSkipPullFlagSetting == "1"))) { await DockerHelpers.DockerPull(dockerImage); } @@ -547,10 +553,30 @@ private static async Task RestorePythonRequirementsDocker(string functionAppRoot } } - private static async Task ChoosePythonBuildEnvImage() + private static async Task ChoosePythonBuildEnvImage() { WorkerLanguageVersionInfo workerInfo = await GetEnvironmentPythonVersion(); - return GetBuildNativeDepsEnvironmentImage(workerInfo); + var (image, isLocal) = await GetBuildNativeDepsEnvironmentImage(workerInfo); + + if (isLocal) + { + // Setup image tag and content + string imageContent = image; + image = $"azure-functions/python:4-python{workerInfo.Major}.{workerInfo.Minor}-buildenv"; + + // Prepare temporary directory for docker build context + string tempDockerfileDirecotry = Path.Combine(Path.GetTempPath(), $"{image}-docker"); + FileSystemHelpers.EnsureDirectory(tempDockerfileDirecotry); + string tempDockerfile = Path.Combine(tempDockerfileDirecotry, "Dockerfile"); + + // Write Dockerfile content to temporary file + await FileSystemHelpers.WriteAllTextToFileAsync(tempDockerfile, imageContent); + + // Build the image + await DockerHelpers.DockerBuild(image, tempDockerfileDirecotry); + } + + return new DockerImageInfo { ImageName = image, CanPull = !isLocal }; } private static string CopyToTemp(IEnumerable files, string rootPath) @@ -596,32 +622,35 @@ public static Task GetDockerInitFileContent(WorkerLanguageVersionInfo in return StaticResources.DockerfilePython37; } - private static string GetBuildNativeDepsEnvironmentImage(WorkerLanguageVersionInfo info) + // Build environment images for building native dependencies for python function apps + private static async Task<(string Image, bool IsLocal)> GetBuildNativeDepsEnvironmentImage(WorkerLanguageVersionInfo info) { if (info?.Major == 3) { switch (info?.Minor) { case 6: - return Constants.DockerImages.LinuxPython36ImageAmd64; + return (DockerImages.LinuxPython36ImageAmd64, false); case 7: - return Constants.DockerImages.LinuxPython37ImageAmd64; + return (DockerImages.LinuxPython37ImageAmd64, false); case 8: - return Constants.DockerImages.LinuxPython38ImageAmd64; + return (DockerImages.LinuxPython38ImageAmd64, false); case 9: - return Constants.DockerImages.LinuxPython39ImageAmd64; + return (DockerImages.LinuxPython39ImageAmd64, false); case 10: - return Constants.DockerImages.LinuxPython310ImageAmd64; + return (DockerImages.LinuxPython310ImageAmd64, false); case 11: - return Constants.DockerImages.LinuxPython311ImageAmd64; + return (DockerImages.LinuxPython311ImageAmd64, false); case 12: - return Constants.DockerImages.LinuxPython312ImageAmd64; + return (DockerImages.LinuxPython312ImageAmd64, false); + + // From Python 3.13 onwards, we use a Dockerfile to build the image locally case 13: - return Constants.DockerImages.LinuxPython313ImageAmd64; + return (await StaticResources.DockerfilePython313BuildEnv, true); } } - return Constants.DockerImages.LinuxPython312ImageAmd64; + return (DockerImages.LinuxPython312ImageAmd64, false); } private static bool IsVersionSupported(WorkerLanguageVersionInfo info) diff --git a/src/Cli/func/StaticResources/Dockerfile.python3.13-buildenv b/src/Cli/func/StaticResources/Dockerfile.python3.13-buildenv new file mode 100644 index 000000000..51269b31c --- /dev/null +++ b/src/Cli/func/StaticResources/Dockerfile.python3.13-buildenv @@ -0,0 +1,43 @@ +FROM mcr.microsoft.com/oryx/python:3.13-debian-bookworm-20250724.1 + +ENV LANG=C.UTF-8 \ + ACCEPT_EULA=Y \ + AzureWebJobsScriptRoot=/home/site/wwwroot \ + HOME=/home \ + FUNCTIONS_WORKER_RUNTIME=python \ + ASPNETCORE_URLS=http://+:80 \ + DOTNET_RUNNING_IN_CONTAINER=true \ + DOTNET_USE_POLLING_FILE_WATCHER=true + +# Install Python dependencies +RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor --batch --yes -o /usr/share/keyrings/microsoft-prod.gpg && \ + apt-get update && \ + apt-get install -y wget apt-transport-https curl gnupg2 locales && \ + echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections && \ + echo "deb [arch=amd64] https://packages.microsoft.com/debian/12/prod bookworm main" | tee /etc/apt/sources.list.d/mssql-release.list && \ + # Needed for libss3 and in turn MS SQL + echo 'deb http://security.debian.org/debian-security bookworm-security main' >> /etc/apt/sources.list && \ + curl https://packages.microsoft.com/config/debian/12/prod.list | tee /etc/apt/sources.list.d/mssql-release.list && \ + # install MS SQL related packages.pinned version in PR # 1012. + echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen && \ + locale-gen && \ + apt-get update && \ + # MS SQL related packages: unixodbc msodbcsql18 mssql-tools + ACCEPT_EULA=Y apt-get install -y unixodbc msodbcsql18 mssql-tools18 && \ + # OpenCV dependencies:libglib2.0-0 libsm6 libxext6 libxrender-dev xvfb + apt-get install -y libglib2.0-0 libsm6 libxext6 libxrender-dev xvfb && \ + # .NET Core dependencies: ca-certificates libc6 libgcc1 libgssapi-krb5-2 libicu72 libssl3 libstdc++6 zlib1g + # Azure ML dependencies: liblttng-ust0 + # OpenMP dependencies: libgomp1 + # binutils: binutils + apt-get install -y --no-install-recommends ca-certificates \ + libc6 libgcc1 libgssapi-krb5-2 libicu72 libssl3 libstdc++6 zlib1g && \ + apt-get install -y libglib2.0-0 libsm6 libxext6 libxrender-dev xvfb binutils \ + libgomp1 liblttng-ust1 && \ + rm -rf /var/lib/apt/lists/* + +RUN apt-get update && \ + apt-get install -y build-essential libssl-dev zlib1g-dev libbz2-dev \ + libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \ + xz-utils tk-dev libffi-dev liblzma-dev python3-openssl git unixodbc-dev dh-autoreconf \ + libcurl4-openssl-dev libssl-dev python3-dev libevent-dev python3-openssl squashfs-tools unzip diff --git a/src/Cli/func/StaticResources/StaticResources.cs b/src/Cli/func/StaticResources/StaticResources.cs index 4bd7c0317..b78699162 100644 --- a/src/Cli/func/StaticResources/StaticResources.cs +++ b/src/Cli/func/StaticResources/StaticResources.cs @@ -48,6 +48,8 @@ public static class StaticResources public static Task DockerfilePython313 => GetValue("Dockerfile.python3.13"); + public static Task DockerfilePython313BuildEnv => GetValue("Dockerfile.python3.13-buildenv"); + public static Task DockerfilePowershell7 => GetValue("Dockerfile.powershell7"); public static Task DockerfilePowershell72 => GetValue("Dockerfile.powershell7.2");