-
Notifications
You must be signed in to change notification settings - Fork 1
Setup .NET template #68
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 3 commits
f69be3a
4ea9c93
95e1f1a
e7946e9
cf86730
18cc2e1
2edfa6a
1a68e88
2f1779e
8663db2
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 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -208,8 +208,8 @@ func TestGenerateDatabaseStatefulSet(t *testing.T) { | |||||||||
| } | ||||||||||
|
|
||||||||||
| container := containers[0] | ||||||||||
| if container.Image != "postgres:16" { | ||||||||||
| t.Errorf("Expected image %q, got %q", "postgres:16", container.Image) | ||||||||||
| if container.Image != "postgres:18" { | ||||||||||
| t.Errorf("Expected image %q, got %q", "postgres:18", container.Image) | ||||||||||
|
||||||||||
| if container.Image != "postgres:18" { | |
| t.Errorf("Expected image %q, got %q", "postgres:18", container.Image) | |
| if container.Image != "postgres:16" { | |
| t.Errorf("Expected image %q, got %q", "postgres:16", container.Image) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # .NET Web API Backstage Template | ||
|
|
||
| This template scaffolds an ASP.NET Core Web API that is ready for Helios GitOps deployment. | ||
|
|
||
| Generated source repository includes: | ||
|
|
||
| - ASP.NET Core Web API project targeting .NET 10 | ||
| - Multi-stage Dockerfile | ||
| - docker-compose setup for local development with PostgreSQL | ||
| - Environment-driven database configuration using `DB_HOST`, `DB_USER`, `DB_PASSWORD`, and `DB_NAME` | ||
|
|
||
| Generated GitOps repository includes: | ||
|
|
||
| - HeliosApp manifest with web-service + database trait |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| apiVersion: app.helios.io/v1alpha1 | ||
| kind: HeliosApp | ||
| metadata: | ||
| name: ${{ values.name }} | ||
| namespace: default | ||
| spec: | ||
|
|
||
| description: "Managed by Helios Operator" | ||
| owner: ${{ values.owner }} | ||
|
|
||
| imageRepo: ${{ values.dockerOrg }}/${{ values.repoName }} | ||
| gitRepo: "${{ values.sourceRepo }}" | ||
| gitopsRepo: ${{ values.gitopsRepo }} | ||
| gitopsPath: "apps/${{ values.name }}" | ||
| port: ${{ values.port }} | ||
| replicas: 1 | ||
| testCommand: ${{ values.testCommand }} | ||
|
|
||
| webhookSecret: "git-credentials-${{ values.name }}" | ||
| gitopsSecretRef: "git-credentials-${{ values.name }}" | ||
|
|
||
| components: | ||
| - name: ${{ values.name }}-backend | ||
| type: web-service | ||
| properties: | ||
| image: ${{ values.dockerOrg }}/${{ values.repoName }}:latest | ||
| port: ${{ values.port }} | ||
| replicas: 1 | ||
| env: | ||
| - name: ASPNETCORE_ENVIRONMENT | ||
| value: "Production" | ||
| traits: | ||
| - type: service | ||
| properties: | ||
| port: ${{ values.port }} | ||
| targetPort: ${{ values.port }} | ||
| - type: database | ||
| properties: | ||
| dbType: ${{ values.dbType | dump }} | ||
| dbName: ${{ values.dbName | dump }} | ||
| version: ${{ values.dbVersion | dump }} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| bin/ | ||
| obj/ | ||
| .git/ | ||
| .vscode/ | ||
| *.user | ||
| *.suo |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # Database credentials (injected by Helios Operator) | ||
| DB_HOST=localhost | ||
| DB_USER=postgres | ||
| DB_PASSWORD=postgres | ||
| DB_NAME=${{ values.name }}-db | ||
|
|
||
| # Legacy compatibility fallback used by some templates/operators | ||
| DB_PASS=postgres | ||
|
|
||
| # Application | ||
| PORT=${{ values.port }} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| bin/ | ||
| obj/ | ||
| .vs/ | ||
| *.user | ||
| *.suo |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| using Microsoft.AspNetCore.Mvc; | ||
|
|
||
| namespace DotNetApi.Controllers; | ||
|
|
||
| [ApiController] | ||
| [Route("[controller]")] | ||
| public class WeatherForecastController : ControllerBase | ||
| { | ||
| private static readonly string[] Summaries = | ||
| { | ||
| "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" | ||
| }; | ||
|
|
||
| [HttpGet] | ||
| public IEnumerable<WeatherForecast> Get() | ||
| { | ||
| return Enumerable.Range(1, 5).Select(index => new WeatherForecast | ||
| { | ||
| Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), | ||
| TemperatureC = Random.Shared.Next(-20, 55), | ||
| Summary = Summaries[Random.Shared.Next(Summaries.Length)] | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| public class WeatherForecast | ||
| { | ||
| public DateOnly Date { get; set; } | ||
|
|
||
| public int TemperatureC { get; set; } | ||
|
|
||
| public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); | ||
|
|
||
| public string? Summary { get; set; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build | ||
| WORKDIR /src | ||
|
|
||
| COPY DotNetApi.csproj ./ | ||
| RUN dotnet restore DotNetApi.csproj | ||
|
|
||
| COPY . . | ||
| RUN dotnet publish DotNetApi.csproj -c Release -o /app/publish /p:UseAppHost=false | ||
|
|
||
| FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime | ||
| WORKDIR /app | ||
| COPY --from=build /app/publish . | ||
|
|
||
| ENV ASPNETCORE_URLS=http://+:${{ values.port }} | ||
| EXPOSE ${{ values.port }} | ||
|
|
||
| ENTRYPOINT ["dotnet", "DotNetApi.dll"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||
| <PropertyGroup> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| <Nullable>enable</Nullable> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" /> | ||
| </ItemGroup> | ||
| </Project> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using Microsoft.AspNetCore.Mvc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var builder = WebApplication.CreateBuilder(args); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.Services.AddControllers(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.Services.AddEndpointsApiExplorer(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.Services.AddSwaggerGen(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var dbHost = Environment.GetEnvironmentVariable("DB_HOST") ?? "localhost"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var dbUser = Environment.GetEnvironmentVariable("DB_USER") ?? "postgres"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var dbPassword = Environment.GetEnvironmentVariable("DB_PASSWORD") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ?? Environment.GetEnvironmentVariable("DB_PASS") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ?? "postgres"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var dbName = Environment.GetEnvironmentVariable("DB_NAME") ?? "${{ values.name }}-db"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var connectionTemplate = builder.Configuration.GetConnectionString("DefaultConnection") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ?? "Host={DB_HOST};Username={DB_USER};Password={DB_PASSWORD};Database={DB_NAME}"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var connectionString = connectionTemplate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .Replace("{DB_HOST}", dbHost, StringComparison.Ordinal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .Replace("{DB_USER}", dbUser, StringComparison.Ordinal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .Replace("{DB_PASSWORD}", dbPassword, StringComparison.Ordinal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .Replace("{DB_NAME}", dbName, StringComparison.Ordinal); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.Configuration["ConnectionStrings:DefaultConnection"] = connectionString; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var app = builder.Build(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (app.Environment.IsDevelopment()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.UseSwagger(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.UseSwaggerUI(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.UseHttpsRedirection(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.MapControllers(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.MapGet("/health", () => Results.Ok(new { status = "ok" })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.MapGet("/database/config", ([FromServices] IConfiguration config) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Results.Ok(new | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Host = dbHost, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| User = dbUser, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Database = dbName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ConnectionString = config.GetConnectionString("DefaultConnection") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |
| app.UseHttpsRedirection(); | |
| app.MapControllers(); | |
| app.MapGet("/health", () => Results.Ok(new { status = "ok" })); | |
| app.MapGet("/database/config", ([FromServices] IConfiguration config) => | |
| Results.Ok(new | |
| { | |
| Host = dbHost, | |
| User = dbUser, | |
| Database = dbName, | |
| ConnectionString = config.GetConnectionString("DefaultConnection") | |
| })); | |
| app.MapGet("/database/config", () => | |
| Results.Ok(new | |
| { | |
| Host = dbHost, | |
| User = dbUser, | |
| Database = dbName | |
| })); | |
| } | |
| app.UseHttpsRedirection(); | |
| app.MapControllers(); | |
| app.MapGet("/health", () => Results.Ok(new { status = "ok" })); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "$schema": "https://json.schemastore.org/launchsettings.json", | ||
| "profiles": { | ||
| "http": { | ||
| "commandName": "Project", | ||
| "launchBrowser": true, | ||
| "launchUrl": "swagger", | ||
| "applicationUrl": "http://localhost:${{ values.port }}", | ||
| "environmentVariables": { | ||
| "ASPNETCORE_ENVIRONMENT": "Development" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # ${{ values.name }} (.NET Web API) | ||
|
|
||
| This project is generated by the Helios Backstage .NET template. | ||
|
|
||
| ## Local Development | ||
|
|
||
| 1. Start API + PostgreSQL: | ||
|
|
||
| ```bash | ||
| docker compose up --build | ||
| ``` | ||
|
|
||
| 2. Open Swagger: | ||
|
|
||
| http://localhost:${{ values.port }}/swagger | ||
|
|
||
| ## Environment Variables | ||
|
|
||
| - DB_HOST | ||
| - DB_USER | ||
| - DB_PASSWORD | ||
| - DB_NAME | ||
|
|
||
| The API builds `ConnectionStrings:DefaultConnection` at startup from these values. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "Logging": { | ||
| "LogLevel": { | ||
| "Default": "Debug", | ||
| "Microsoft.AspNetCore": "Warning" | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "Logging": { | ||
| "LogLevel": { | ||
| "Default": "Information", | ||
| "Microsoft.AspNetCore": "Warning" | ||
| } | ||
| }, | ||
| "AllowedHosts": "*", | ||
| "ConnectionStrings": { | ||
| "DefaultConnection": "Host={DB_HOST};Username={DB_USER};Password={DB_PASSWORD};Database={DB_NAME}" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| apiVersion: backstage.io/v1alpha1 | ||
| kind: Component | ||
| metadata: | ||
| name: ${{ values.name }} | ||
| description: ${{ values.description | dump }} | ||
| annotations: | ||
| gitea.org/repo-url: http://localhost:3030/${{ values.owner }}/${{ values.name }} | ||
| backstage.io/techdocs-ref: dir:. | ||
| backstage.io/kubernetes-id: ${{ values.name }} | ||
| backstage.io/kubernetes-label-selector: app.kubernetes.io/name=${{ values.name }} | ||
| backstage.io/kubernetes-namespace: default | ||
| janus-idp.io/tekton: ${{ values.name }} | ||
| tekton.dev/ci-cd: "true" | ||
| argocd/app-name: ${{ values.name }}-argocd | ||
| spec: | ||
| type: service | ||
| lifecycle: production | ||
| owner: ${{ values.owner }} |
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.
ReconcileInstancesuses the database traitversion("16" in this test app) to set the StatefulSet image, but this test expectspostgres:18. Align the expected image (and theexistingStsseed image) with the trait version, or update the trait version if the default is being bumped.