@@ -339,3 +339,286 @@ Key points:
339339### Auto-Generated `api` Folder Warning
340340
341341Do NOT create or manually edit an `api` folder or any files within it for hosting integrations. Files under paths like `src/CommunityToolkit.Aspire.Hosting.[HostingName]/api/` are generated automatically (e.g. by source generators or build tooling ). Manual changes will be overwritten and should instead be implemented in normal source files outside `api `. If you need new generated capabilities , extend the generator or add new partial types outside the `api ` directory .
342+
343+ ## Language-Based Hosting Integrations
344+
345+ When creating hosting integrations for programming languages (e.g., Golang, Rust, Python, Node.js, Java), there are additional capabilities and patterns to consider beyond basic executable hosting :
346+
347+ ### Package Manager Support
348+
349+ Language - based hosting integrations should support the ecosystem 's package managers and dependency management:
350+
351+ - ** Build Tags / Features ** : Allow users to specify conditional compilation flags (e .g ., Go build tags : `buildTags : [" dev" , " production" ]`)
352+ - ** Dependency Installation ** : Optionally support automatic dependency installation before running (e .g ., `go mod download `, `npm install `)
353+ - ** Executable Path Flexibility ** : Support both default entry points (e .g ., `" ." ` for Go ) and custom paths (e .g ., `" ./cmd/server" `)
354+
355+ Example from Golang integration :
356+
357+ ```csharp
358+ public static IResourceBuilder < GolangAppExecutableResource > AddGolangApp (
359+ this IDistributedApplicationBuilder builder ,
360+ [ResourceName ] string name ,
361+ string workingDirectory ,
362+ string executable ,
363+ string []? args = null ,
364+ string []? buildTags = null )
365+ {
366+ var allArgs = new List <string > { " run" };
367+
368+ if (buildTags is { Length : > 0 })
369+ {
370+ allArgs .Add (" -tags" );
371+ allArgs .Add (string .Join (" ," , buildTags ));
372+ }
373+
374+ allArgs .Add (executable );
375+ // ... rest of implementation
376+ }
377+ ```
378+
379+ ### Publish as Container (Multi-Stage Builds)
380+
381+ Language-based integrations should automatically generate optimized Dockerfiles using multi-stage builds when publishing:
382+
383+ - ** Build Stage** : Uses the language's official SDK image to compile/build the application
384+ - ** Runtime Stage** : Uses a minimal base image (e.g., Alpine Linux) for smaller final images
385+ - ** Security** : Install necessary certificates (e.g., CA certificates for HTTPS support)
386+ - ** Optimization** : Disable unnecessary features in build (e.g., ` CGO_ENABLED=0 ` for Go)
387+
388+ Example from Golang integration:
389+
390+ ``` csharp
391+ private static IResourceBuilder < GolangAppExecutableResource > PublishAsGolangDockerfile (
392+ this IResourceBuilder < GolangAppExecutableResource > builder ,
393+ string workingDirectory ,
394+ string executable ,
395+ string []? buildTags )
396+ {
397+ const string DefaultAlpineVersion = " 3.21" ;
398+
399+ return builder .PublishAsDockerFile (publish =>
400+ {
401+ publish .WithDockerfileBuilder (workingDirectory , context =>
402+ {
403+ var buildArgs = new List <string > { " build" , " -o" , " server" };
404+
405+ if (buildTags is { Length : > 0 })
406+ {
407+ buildArgs .Add (" -tags" );
408+ buildArgs .Add (string .Join (" ," , buildTags ));
409+ }
410+
411+ buildArgs .Add (executable );
412+
413+ // Get custom base image from annotation, if present
414+ context .Resource .TryGetLastAnnotation <DockerfileBaseImageAnnotation >(out var baseImageAnnotation );
415+ var goVersion = baseImageAnnotation ? .BuildImage ?? GetDefaultGoBaseImage (workingDirectory , context .Services );
416+
417+ var buildStage = context .Builder
418+ .From (goVersion , " builder" )
419+ .WorkDir (" /build" )
420+ .Copy (" ." , " ./" )
421+ .Run (string .Join (" " , [" CGO_ENABLED=0" , " go" , .. buildArgs ]));
422+
423+ var runtimeImage = baseImageAnnotation ? .RuntimeImage ?? $" alpine:{DefaultAlpineVersion }" ;
424+
425+ context .Builder
426+ .From (runtimeImage )
427+ .Run (" apk --no-cache add ca-certificates" )
428+ .WorkDir (" /app" )
429+ .CopyFrom (buildStage .StageName ! , " /build/server" , " /app/server" )
430+ .Entrypoint ([" /app/server" ]);
431+ });
432+ });
433+ }
434+ ```
435+
436+ ### TLS/HTTPS Support
437+
438+ For language integrations that may need secure connections:
439+
440+ - ** CA Certificates** : Install CA certificates in runtime image for HTTPS client requests
441+ - ** Runtime Configuration** : Ensure the generated container supports TLS connections (e.g., ` apk --no-cache add ca-certificates ` )
442+
443+ Example from Golang Dockerfile generation:
444+
445+ ``` csharp
446+ context .Builder
447+ .From (runtimeImage )
448+ .Run (" apk --no-cache add ca-certificates" ) // Enables HTTPS support
449+ .WorkDir (" /app" )
450+ // ... rest of configuration
451+ ```
452+
453+ ### Version Detection
454+
455+ Automatically detect and use the appropriate language version from project files:
456+
457+ - ** Project Files** : Parse version from language-specific files (e.g., ` go.mod ` , ` package.json ` , ` Cargo.toml ` )
458+ - ** Installed Toolchain** : Fall back to the installed language toolchain version
459+ - ** Default Version** : Use a sensible default if detection fails
460+
461+ Example from Golang integration:
462+
463+ ``` csharp
464+ internal static string ? DetectGoVersion (string workingDirectory , ILogger logger )
465+ {
466+ // Check go.mod file
467+ var goModPath = Path .Combine (workingDirectory , " go.mod" );
468+ if (File .Exists (goModPath ))
469+ {
470+ try
471+ {
472+ var goModContent = File .ReadAllText (goModPath );
473+ // Look for "go X.Y" or "go X.Y.Z" line in go.mod
474+ var match = Regex .Match (goModContent , @" ^\s*go\s+(\d+\.\d+(?:\.\d+)?)" , RegexOptions .Multiline );
475+ if (match .Success )
476+ {
477+ var version = match .Groups [1 ].Value ;
478+ // Extract major.minor (e.g., "1.22" from "1.22.3")
479+ var versionParts = version .Split ('.' );
480+ if (versionParts .Length >= 2 )
481+ {
482+ var majorMinor = $" {versionParts [0 ]}.{versionParts [1 ]}" ;
483+ logger .LogDebug (" Detected Go version {Version} from go.mod file" , majorMinor );
484+ return majorMinor ;
485+ }
486+ }
487+ }
488+ catch (IOException ex )
489+ {
490+ logger .LogDebug (ex , " Failed to parse go.mod file due to IO error" );
491+ }
492+ }
493+
494+ // Try to detect from installed Go toolchain
495+ try
496+ {
497+ var startInfo = new ProcessStartInfo
498+ {
499+ FileName = " go" ,
500+ Arguments = " version" ,
501+ RedirectStandardOutput = true ,
502+ RedirectStandardError = true ,
503+ UseShellExecute = false ,
504+ CreateNoWindow = true
505+ };
506+
507+ using var process = Process .Start (startInfo );
508+ if (process != null )
509+ {
510+ var output = process .StandardOutput .ReadToEnd ();
511+ process .WaitForExit ();
512+
513+ if (process .ExitCode == 0 )
514+ {
515+ var match = Regex .Match (output , @" go version go(\d+\.\d+)" );
516+ if (match .Success )
517+ {
518+ var version = match .Groups [1 ].Value ;
519+ logger .LogDebug (" Detected Go version {Version} from installed toolchain" , version );
520+ return version ;
521+ }
522+ }
523+ }
524+ }
525+ catch (Exception ex )
526+ {
527+ logger .LogDebug (ex , " Failed to detect Go version from installed toolchain" );
528+ }
529+
530+ logger .LogDebug (" No Go version detected, will use default version" );
531+ return null ;
532+ }
533+ ```
534+
535+ ### Optimizing and Securing Container Images
536+
537+ Allow users to customize base images for both build and runtime stages:
538+
539+ - ** Customizable Build Image** : Let users override the builder/SDK image (e.g., ` golang:1.22-alpine ` instead of ` golang:1.22 ` )
540+ - ** Customizable Runtime Image** : Let users override the runtime image (e.g., ` alpine:3.20 ` instead of ` alpine:3.21 ` )
541+ - ** Annotation-Based Configuration** : Use annotations to store custom base image settings
542+
543+ Example annotation and extension method:
544+
545+ ``` csharp
546+ // Annotation to store custom base images
547+ internal sealed record DockerfileBaseImageAnnotation : IResourceAnnotation
548+ {
549+ public string ? BuildImage { get ; init ; }
550+ public string ? RuntimeImage { get ; init ; }
551+ }
552+
553+ // Extension method to configure base images
554+ public static IResourceBuilder < TResource > WithDockerfileBaseImage <TResource >(
555+ this IResourceBuilder < TResource > builder ,
556+ string ? buildImage = null ,
557+ string ? runtimeImage = null )
558+ where TResource : IResource
559+ {
560+ return builder .WithAnnotation (new DockerfileBaseImageAnnotation
561+ {
562+ BuildImage = buildImage ,
563+ RuntimeImage = runtimeImage
564+ });
565+ }
566+ ```
567+
568+ Usage example:
569+
570+ ``` csharp
571+ var golang = builder .AddGolangApp (" golang" , " ../gin-api" )
572+ .WithHttpEndpoint (env : " PORT" )
573+ .WithDockerfileBaseImage (
574+ buildImage : " golang:1.22-alpine" ,
575+ runtimeImage : " alpine:3.20" );
576+ ```
577+
578+ ### Documentation Requirements
579+
580+ For language-based integrations, the README.md should include:
581+
582+ - ** Publishing Section** : Explain automatic Dockerfile generation
583+ - ** Version Detection** : Document how version detection works
584+ - ** Customization Options** : Show how to customize base images
585+ - ** TLS Support** : Note that CA certificates are included for HTTPS
586+ - ** Build Options** : Document package manager flags, build tags, etc.
587+
588+ Example README structure:
589+
590+ ``` markdown
591+ ## Publishing
592+
593+ When publishing your Aspire application, the [Language] resource automatically generates a multi-stage Dockerfile for containerization.
594+
595+ ### Automatic Version Detection
596+
597+ The integration automatically detects the [Language] version to use by:
598+ 1. Checking the [project file] for the version directive
599+ 2. Falling back to the installed toolchain version
600+ 3. Using [version] as the default if no version is detected
601+
602+ ### Customizing Base Images
603+
604+ You can customize the base images used in the Dockerfile:
605+
606+ [code example]
607+
608+ ### Generated Dockerfile
609+
610+ The automatically generated Dockerfile:
611+ - Uses the detected or default [Language] version as the build stage
612+ - Uses a minimal runtime image for a smaller final image
613+ - Installs CA certificates for HTTPS support
614+ - Respects build options if specified
615+ ```
616+
617+ ### Testing Considerations
618+
619+ When testing language-based integrations:
620+
621+ - ** Unit Tests** : Verify build arguments, version detection logic, and annotation handling
622+ - ** Integration Tests** : Test the full publishing workflow if possible
623+ - ** Version Detection Tests** : Mock file system and process execution to test version detection
624+ - ** Dockerfile Generation** : Verify the generated Dockerfile structure matches expectations
0 commit comments