-
Couldn't load subscription status.
- Fork 712
Add support for docker static files #12265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
69e92f1
7eb2aa1
425eb8a
1a0e0a3
ec125e7
e48fb38
cc7bc8f
c22ba1c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,9 @@ | |||||
| using System.ComponentModel; | ||||||
| using System.Runtime.CompilerServices; | ||||||
| using Aspire.Hosting.ApplicationModel; | ||||||
| using Aspire.Hosting.ApplicationModel.Docker; | ||||||
| using Aspire.Hosting.Pipelines; | ||||||
| using Aspire.Hosting.Publishing; | ||||||
| using Aspire.Hosting.Python; | ||||||
| using Microsoft.Extensions.DependencyInjection; | ||||||
| using Microsoft.Extensions.Logging; | ||||||
|
|
@@ -233,6 +236,45 @@ public static IResourceBuilder<PythonAppResource> AddPythonApp( | |||||
| .WithArgs(scriptArgs); | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Adds a Uvicorn-based Python application to the distributed application builder with HTTP endpoint configuration. | ||||||
| /// </summary> | ||||||
| /// <remarks>This method configures the application to use Uvicorn as the server and exposes an HTTP | ||||||
| /// endpoint. When publishing, it sets the entry point to use the Uvicorn executable with appropriate arguments for | ||||||
| /// host and port.</remarks> | ||||||
| /// <param name="builder">The distributed application builder to which the Uvicorn application resource will be added.</param> | ||||||
| /// <param name="name">The unique name of the Uvicorn application resource.</param> | ||||||
| /// <param name="appDirectory">The directory containing the Python application files.</param> | ||||||
| /// <param name="app">The ASGI app import path which informs Uvicorn which module and variable to load as your web application. | ||||||
| /// For example, "main:app" means "main.py" file and variable named "app".</param> | ||||||
| /// <returns>A resource builder for further configuration of the Uvicorn Python application resource.</returns> | ||||||
| public static IResourceBuilder<PythonAppResource> AddUvicornApp( | ||||||
| this IDistributedApplicationBuilder builder, [ResourceName] string name, string appDirectory, string app) | ||||||
| { | ||||||
| var resourceBuilder = builder.AddPythonExecutable(name, appDirectory, "uvicorn") | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @adamint - is this going to cause a problem with VS Code debugging? aspire/src/Aspire.Hosting.Python/PythonAppResourceBuilderExtensions.cs Lines 322 to 323 in 5a87acf
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we cannot debug any python executable. You'll need to call WithVSCodeDebugSupport and in the launch configuration provide the right entrypoint and then uvicorn as the module There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We an switch to a module instead There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to switch, we just need to indicate to the AppHost to start the right module There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OR, we can switch and make it just work in all cases :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm just not sure if there are performance differences between using the uvicorn executable and running as a python module There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
| .WithHttpEndpoint(env: "PORT") | ||||||
| .WithArgs(c => | ||||||
| { | ||||||
| c.Args.Add(app); | ||||||
|
|
||||||
| c.Args.Add("--host"); | ||||||
| var endpoint = ((IResourceWithEndpoints)c.Resource).GetEndpoint("http"); | ||||||
| if (builder.ExecutionContext.IsPublishMode) | ||||||
| { | ||||||
| c.Args.Add("0.0.0.0"); | ||||||
| } | ||||||
| else | ||||||
| { | ||||||
| c.Args.Add(endpoint.EndpointAnnotation.TargetHost); | ||||||
| } | ||||||
|
|
||||||
| c.Args.Add("--port"); | ||||||
| c.Args.Add(endpoint.Property(EndpointProperty.TargetPort)); | ||||||
| }); | ||||||
|
|
||||||
| return resourceBuilder; | ||||||
| } | ||||||
|
|
||||||
| private static IResourceBuilder<PythonAppResource> AddPythonAppCore( | ||||||
| IDistributedApplicationBuilder builder, string name, string appDirectory, EntrypointType entrypointType, | ||||||
| string entrypoint, string virtualEnvironmentPath) | ||||||
|
|
@@ -465,6 +507,7 @@ private static IResourceBuilder<PythonAppResource> AddPythonAppCore( | |||||
| var runtimeBuilder = context.Builder | ||||||
| .From($"python:{pythonVersion}-slim-bookworm", "app") | ||||||
| .EmptyLine() | ||||||
| .AddStaticFiles(context.Resource, "/app") | ||||||
| .Comment("------------------------------") | ||||||
| .Comment("🚀 Runtime stage") | ||||||
| .Comment("------------------------------") | ||||||
|
|
@@ -504,9 +547,77 @@ private static IResourceBuilder<PythonAppResource> AddPythonAppCore( | |||||
| }); | ||||||
| }); | ||||||
|
|
||||||
| resourceBuilder.WithPipelineStepFactory(factoryContext => | ||||||
| { | ||||||
| List<PipelineStep> steps = []; | ||||||
| var buildStep = CreateBuildImageBuildStep($"{factoryContext.Resource.Name}-build-compute", factoryContext.Resource); | ||||||
| steps.Add(buildStep); | ||||||
|
|
||||||
| // ensure any static file references' images are built first | ||||||
| if (factoryContext.Resource.TryGetAnnotationsOfType<ContainerFilesDestinationAnnotation>(out var containerFilesAnnotations)) | ||||||
| { | ||||||
| foreach (var containerFile in containerFilesAnnotations) | ||||||
| { | ||||||
| var source = containerFile.Source; | ||||||
| var staticFileBuildStep = CreateBuildImageBuildStep($"{factoryContext.Resource.Name}-{source.Name}-build-compute", source); | ||||||
| buildStep.DependsOn(staticFileBuildStep); | ||||||
| steps.Add(staticFileBuildStep); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| return steps; | ||||||
| }); | ||||||
|
|
||||||
| return resourceBuilder; | ||||||
| } | ||||||
|
|
||||||
| private static PipelineStep CreateBuildImageBuildStep(string stepName, IResource resource) => | ||||||
| new() | ||||||
| { | ||||||
| Name = stepName, | ||||||
| Action = async ctx => | ||||||
| { | ||||||
| var containerImageBuilder = ctx.Services.GetRequiredService<IResourceContainerImageBuilder>(); | ||||||
| await containerImageBuilder.BuildImageAsync( | ||||||
| resource, | ||||||
| new ContainerBuildOptions | ||||||
| { | ||||||
| TargetPlatform = ContainerTargetPlatform.LinuxAmd64 | ||||||
| }, | ||||||
| ctx.CancellationToken).ConfigureAwait(false); | ||||||
| }, | ||||||
| Tags = [WellKnownPipelineTags.BuildCompute] | ||||||
| }; | ||||||
|
|
||||||
| private static DockerfileStage AddStaticFiles(this DockerfileStage stage, IResource resource, string rootDestinationPath) | ||||||
eerhardt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| { | ||||||
| if (resource.TryGetAnnotationsOfType<ContainerFilesDestinationAnnotation>(out var containerFilesDestinationAnnotations)) | ||||||
| { | ||||||
| foreach (var containerFileDestination in containerFilesDestinationAnnotations) | ||||||
| { | ||||||
| // get image name | ||||||
| if (!containerFileDestination.Source.TryGetContainerImageName(out var imageName)) | ||||||
| { | ||||||
| throw new InvalidOperationException("Cannot add container files: Source resource does not have a container image name."); | ||||||
| } | ||||||
|
|
||||||
| var destinationPath = containerFileDestination.DestinationPath; | ||||||
| if (!destinationPath.StartsWith('/')) | ||||||
| { | ||||||
| destinationPath = $"{rootDestinationPath}/{destinationPath}"; | ||||||
| } | ||||||
|
|
||||||
| foreach (var containerFilesSource in containerFileDestination.Source.Annotations.OfType<ContainerFilesSourceAnnotation>()) | ||||||
| { | ||||||
| stage.CopyFrom(imageName, containerFilesSource.SourcePath, destinationPath); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| stage.EmptyLine(); | ||||||
| } | ||||||
| return stage; | ||||||
| } | ||||||
|
|
||||||
| private static void ThrowIfNullOrContainsIsNullOrEmpty(string[] scriptArgs) | ||||||
| { | ||||||
| ArgumentNullException.ThrowIfNull(scriptArgs); | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| namespace Aspire.Hosting.ApplicationModel; | ||
|
|
||
| /// <summary> | ||
| /// Represents an annotation that specifies a source resource and destination path for copying container files. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This annotation is typically used in scenarios where assets, such as images or static files, | ||
| /// need to be copied from one container image to another during the build process. | ||
| /// | ||
| /// This annotation is applied to the destination resource where the source container's files will be copied to. | ||
| /// </remarks> | ||
| public sealed class ContainerFilesDestinationAnnotation : IResourceAnnotation | ||
| { | ||
| /// <summary> | ||
| /// Gets the resource that provides access to the container files to be copied. | ||
| /// </summary> | ||
| public required IResource Source { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the file system path where the container files will be copied into the destination. | ||
| /// </summary> | ||
| public required string DestinationPath { get; init; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| namespace Aspire.Hosting.ApplicationModel; | ||
|
|
||
| /// <summary> | ||
| /// Represents an annotation that associates a container file/directory with a resource. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This annotation is typically used in scenarios where assets, such as images or static files, | ||
| /// need to be copied from one container image to another during the build process. | ||
| /// | ||
| /// This annotation is applied to the source resource that produces the files. | ||
| /// </remarks> | ||
| public sealed class ContainerFilesSourceAnnotation : IResourceAnnotation | ||
eerhardt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| /// <summary> | ||
| /// Gets the file system path to the source file or directory inside the container. | ||
| /// </summary> | ||
| public required string SourcePath { get; init; } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,11 @@ public static IEnumerable<IResource> GetComputeResources(this DistributedApplica | |
| continue; | ||
| } | ||
|
|
||
| if (r.IsBuildOnlyContainer()) | ||
| { | ||
| continue; | ||
| } | ||
|
Comment on lines
+31
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels like it's in the wrong place or this method has the wrong name. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my opinion it isn't. A "build only container" (i.e. one that doesn't have an entrypoint) isn't a "Compute Resource". It can't stand alone. It doesn't have an entrypoint. |
||
|
|
||
| yield return r; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Aspire.Hosting.ApplicationModel; | ||
|
|
||
| namespace Aspire.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Represents a resource that contains files that can be copied to other resources. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Resources that implement this interface produce container images that include files | ||
| /// that can be copied into other resources. For example using Docker's COPY --from feature. | ||
| /// </remarks> | ||
| public interface IResourceWithContainerFiles : IResource | ||
| { | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll need to decide if we make this
UvicornAppResource:PythonAppResourceThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would be the advantage to doing this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can build extension methods that target unvicorn configuration. We'll want that but lets file a follow up issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#12345