This commit is contained in:
Elias Stepanik 2023-05-29 18:42:26 +02:00
commit cd23fbe4a9
43 changed files with 1733 additions and 0 deletions

25
.dockerignore Normal file
View File

@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/

12
.run/ClearDocker.xml Normal file
View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ClearDocker" type="RunNativeExe" factoryName="Native Executable">
<option name="EXE_PATH" value="$PROJECT_DIR$/../../../../Windows/System32/bash.exe" />
<option name="PROGRAM_PARAMETERS" value="$PROJECT_DIR$/.scripts/clearDocker.sh" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<method v="2">
<option name="Build Solution" enabled="true" />
</method>
</configuration>
</component>

24
.scripts/clearDocker.sh Normal file
View File

@ -0,0 +1,24 @@
# stop all running containers
echo '####################################################'
echo 'Stopping running containers (if available)...'
echo '####################################################'
docker stop $(docker ps -aq --filter "name=functions") || true
# remove all stopped containers
echo '####################################################'
echo 'Removing containers ..'
echo '####################################################'
docker rm $(docker ps -aq --filter "name=functions") || true
# remove all images
echo '####################################################'
echo 'Removing images ...'
echo '####################################################'
docker rmi $(docker images -q --filter "reference=*functions*") || true
# remove all stray volumes if any
#echo '####################################################'
#echo 'Revoming docker container volumes (if any)'
#echo '####################################################'
#docker volume rm $(docker volume ls -q --filter "name=*kassensystembbs2*")

16
.scripts/runDocker.sh Normal file
View File

@ -0,0 +1,16 @@
echo '####################################################'
echo 'Starting Compose Containers ...'
echo '####################################################'
docker-compose up -d
echo '####################################################'
echo 'Waiting for Webserver to Start ...'
echo '####################################################'
while ! curl --fail --silent --head http://172.29.111.95:8080; do
sleep 1
done
echo '####################################################'
echo 'Open your Browser at http://172.29.111.95:8080.'
echo '####################################################'

View File

@ -0,0 +1,60 @@
using Docker.DotNet.Models;
using Functions.Services;
using Functions.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace Functions.Controllers;
[ApiController]
[Route("[controller]")]
public class FunctionController : ControllerBase
{
private readonly ILogger<TestController> _logger;
private readonly FunctionManager _functionManager;
public FunctionController(ILogger<TestController> logger, FunctionManager functionManager)
{
_logger = logger;
_functionManager = functionManager;
}
[HttpPost]
public async Task<IActionResult> CreateFunction(string functionName, string imageTag)
{
await _functionManager.CreateFunction(functionName, imageTag);
return Ok();
}
[HttpPost("{functionName}")]
public async Task<string> RunFunctionPost(string functionName,[FromBody] string text)
{
var response = await _functionManager.RunInstance(functionName,HttpMethod.Post, text);
_logger.LogInformation(functionName);
return response;
}
[HttpGet("{functionName}")]
public async Task<string> RunFunctionGet(string functionName)
{
var response = await _functionManager.RunInstance(functionName,HttpMethod.Get);
_logger.LogInformation(functionName);
return response;
}
[HttpPatch("{functionName}")]
public async Task<string> RunFunctionPatch(string functionName,[FromBody] string text)
{
var response = await _functionManager.RunInstance(functionName,HttpMethod.Patch, text);
_logger.LogInformation(functionName);
return response;
}
[HttpDelete("{functionName}/delete")]
public async Task<IActionResult> DeleteFunction(string functionName)
{
await _functionManager.DeleteFunction(functionName);
_logger.LogInformation(functionName);
return Ok();
}
}

View File

@ -0,0 +1,27 @@
using System.Collections;
using Docker.DotNet.Models;
using Functions.Services;
using Functions.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace Functions.Controllers;
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
private readonly ILogger<TestController> _logger;
private readonly IDockerManager _dockerManager;
public TestController(ILogger<TestController> logger, IDockerManager dockerManager)
{
_logger = logger;
_dockerManager = dockerManager;
}
[HttpGet(Name = "Containers")]
public async Task<IEnumerable<ContainerListResponse>> Get()
{
return await _dockerManager.GetContainers();
}
}

View File

@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore;
namespace Functions.Data.DB;
public class FunctionsContext : DbContext
{
public FunctionsContext(DbContextOptions<FunctionsContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Function>()
.HasMany(p => p.Instances)
.WithOne(s => s.Function)
.HasForeignKey(s => s.FunctionId);
modelBuilder.Entity<Function>()
.HasMany(p => p.EnvironmentVariables)
.WithOne(s => s.Function)
.HasForeignKey(s => s.FunctionId);
/*modelBuilder.Entity<Product>()
.HasMany(p => p.SellEntries)
.WithOne(s => s.Item);
modelBuilder.Entity<User>()
.HasMany(u => u.SellEntries)
.WithOne(e => e.SoldBy);
modelBuilder.Entity<User>()
.HasMany(u => u.Cart)
.WithOne(e => e.User);*/
}
public DbSet<Function> Functions { get; set; } = null!;
public DbSet<Instance> Instances { get; set; } = null!;
public DbSet<Environment> EnvironmentVariables { get; set; } = null!;
}

View File

@ -0,0 +1,45 @@
// <auto-generated />
using Functions.Data.DB;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Functions.Data.DB.Migrations
{
[DbContext(typeof(FunctionsContext))]
[Migration("20230528164833_Init")]
partial class Init
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Functions.Data.Function", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ImageTag")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Functions");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,42 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Functions.Data.DB.Migrations
{
/// <inheritdoc />
public partial class Init : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "Functions",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ImageTag = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_Functions", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Functions");
}
}
}

View File

@ -0,0 +1,81 @@
// <auto-generated />
using Functions.Data.DB;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Functions.Data.DB.Migrations
{
[DbContext(typeof(FunctionsContext))]
[Migration("20230528180737_Function")]
partial class Function
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Functions.Data.Function", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ImageTag")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Functions");
});
modelBuilder.Entity("Functions.Data.Instance", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("FunctionID")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("FunctionID");
b.ToTable("Instances");
});
modelBuilder.Entity("Functions.Data.Instance", b =>
{
b.HasOne("Functions.Data.Function", "Function")
.WithMany("Instances")
.HasForeignKey("FunctionID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Function");
});
modelBuilder.Entity("Functions.Data.Function", b =>
{
b.Navigation("Instances");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Functions.Data.DB.Migrations
{
/// <inheritdoc />
public partial class Function : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Instances",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
FunctionID = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Instances", x => x.Id);
table.ForeignKey(
name: "FK_Instances_Functions_FunctionID",
column: x => x.FunctionID,
principalTable: "Functions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_Instances_FunctionID",
table: "Instances",
column: "FunctionID");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Instances");
}
}
}

View File

@ -0,0 +1,85 @@
// <auto-generated />
using Functions.Data.DB;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Functions.Data.DB.Migrations
{
[DbContext(typeof(FunctionsContext))]
[Migration("20230528184618_Function2")]
partial class Function2
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Functions.Data.Function", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ImageTag")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Functions");
});
modelBuilder.Entity("Functions.Data.Instance", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("FunctionId")
.HasColumnType("int");
b.Property<string>("InstanceId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("FunctionId");
b.ToTable("Instances");
});
modelBuilder.Entity("Functions.Data.Instance", b =>
{
b.HasOne("Functions.Data.Function", "Function")
.WithMany("Instances")
.HasForeignKey("FunctionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Function");
});
modelBuilder.Entity("Functions.Data.Function", b =>
{
b.Navigation("Instances");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,73 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Functions.Data.DB.Migrations
{
/// <inheritdoc />
public partial class Function2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Instances_Functions_FunctionID",
table: "Instances");
migrationBuilder.RenameColumn(
name: "FunctionID",
table: "Instances",
newName: "FunctionId");
migrationBuilder.RenameIndex(
name: "IX_Instances_FunctionID",
table: "Instances",
newName: "IX_Instances_FunctionId");
migrationBuilder.AddColumn<string>(
name: "InstanceId",
table: "Instances",
type: "longtext",
nullable: false)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddForeignKey(
name: "FK_Instances_Functions_FunctionId",
table: "Instances",
column: "FunctionId",
principalTable: "Functions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Instances_Functions_FunctionId",
table: "Instances");
migrationBuilder.DropColumn(
name: "InstanceId",
table: "Instances");
migrationBuilder.RenameColumn(
name: "FunctionId",
table: "Instances",
newName: "FunctionID");
migrationBuilder.RenameIndex(
name: "IX_Instances_FunctionId",
table: "Instances",
newName: "IX_Instances_FunctionID");
migrationBuilder.AddForeignKey(
name: "FK_Instances_Functions_FunctionID",
table: "Instances",
column: "FunctionID",
principalTable: "Functions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@ -0,0 +1,122 @@
// <auto-generated />
using Functions.Data.DB;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Functions.Data.DB.Migrations
{
[DbContext(typeof(FunctionsContext))]
[Migration("20230529102304_Function3")]
partial class Function3
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Functions.Data.Environment", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("FunctionId")
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("FunctionId");
b.ToTable("EnvironmentVariables");
});
modelBuilder.Entity("Functions.Data.Function", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ImageTag")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Functions");
});
modelBuilder.Entity("Functions.Data.Instance", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("FunctionId")
.HasColumnType("int");
b.Property<string>("InstanceId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("FunctionId");
b.ToTable("Instances");
});
modelBuilder.Entity("Functions.Data.Environment", b =>
{
b.HasOne("Functions.Data.Function", "Function")
.WithMany("EnvironmentVariables")
.HasForeignKey("FunctionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Function");
});
modelBuilder.Entity("Functions.Data.Instance", b =>
{
b.HasOne("Functions.Data.Function", "Function")
.WithMany("Instances")
.HasForeignKey("FunctionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Function");
});
modelBuilder.Entity("Functions.Data.Function", b =>
{
b.Navigation("EnvironmentVariables");
b.Navigation("Instances");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,51 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Functions.Data.DB.Migrations
{
/// <inheritdoc />
public partial class Function3 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EnvironmentVariables",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Key = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Value = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
FunctionId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EnvironmentVariables", x => x.Id);
table.ForeignKey(
name: "FK_EnvironmentVariables_Functions_FunctionId",
column: x => x.FunctionId,
principalTable: "Functions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_EnvironmentVariables_FunctionId",
table: "EnvironmentVariables",
column: "FunctionId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EnvironmentVariables");
}
}
}

View File

@ -0,0 +1,119 @@
// <auto-generated />
using Functions.Data.DB;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Functions.Data.DB.Migrations
{
[DbContext(typeof(FunctionsContext))]
partial class FunctionsContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Functions.Data.Environment", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("FunctionId")
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("FunctionId");
b.ToTable("EnvironmentVariables");
});
modelBuilder.Entity("Functions.Data.Function", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ImageTag")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Functions");
});
modelBuilder.Entity("Functions.Data.Instance", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("FunctionId")
.HasColumnType("int");
b.Property<string>("InstanceId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("FunctionId");
b.ToTable("Instances");
});
modelBuilder.Entity("Functions.Data.Environment", b =>
{
b.HasOne("Functions.Data.Function", "Function")
.WithMany("EnvironmentVariables")
.HasForeignKey("FunctionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Function");
});
modelBuilder.Entity("Functions.Data.Instance", b =>
{
b.HasOne("Functions.Data.Function", "Function")
.WithMany("Instances")
.HasForeignKey("FunctionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Function");
});
modelBuilder.Entity("Functions.Data.Function", b =>
{
b.Navigation("EnvironmentVariables");
b.Navigation("Instances");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Functions.Data;
public class Environment
{
public Environment(string key, string value)
{
Key = key;
Value = value;
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
[Required]
public string Key { get; set; }
[Required]
public string Value { get; set; }
public int FunctionId { get; set; }
public Function Function { get; set; } = null!;
}

View File

@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Functions.Data;
public class Function
{
public Function(string name, string imageTag)
{
Name = name;
ImageTag = imageTag;
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string ImageTag { get; set; }
public List<Environment> EnvironmentVariables { get; set; }
public List<Instance> Instances { get; set; } = null!;
}

View File

@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Functions.Data;
public class Instance
{
public Instance(string name, string instanceId)
{
Name = name;
InstanceId = instanceId;
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
[Required]
public string InstanceId { get; set; }
[Required]
public string Name { get; set; }
public int FunctionId { get; set; }
public Function Function { get; set; }
}

20
Functions/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["Functions/Functions.csproj", "Functions/"]
RUN dotnet restore "Functions/Functions.csproj"
COPY . .
WORKDIR "/src/Functions"
RUN dotnet build "Functions.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Functions.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Functions.dll"]

View File

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
<Content Update="appsettings.Development.json">
<DependentUpon>appsettings.json</DependentUpon>
</Content>
</ItemGroup>
</Project>

59
Functions/Program.cs Normal file
View File

@ -0,0 +1,59 @@
using System.Text;
using Functions.Data.DB;
using Functions.Services;
using Functions.Services.Interfaces;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
//ASPNETCORE_AppConfig__TwilioSecret=my-secret
builder.Configuration.AddEnvironmentVariables();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var connectionString = new StringBuilder();
connectionString.Append($"server={builder.Configuration["AppConfig:DB:Server"]};");
connectionString.Append($"port={builder.Configuration["AppConfig:DB:Port"]};");
connectionString.Append($"user={builder.Configuration["AppConfig:DB:User"]};");
connectionString.Append($"password={builder.Configuration["AppConfig:DB:Password"]};");
connectionString.Append($"database={builder.Configuration["AppConfig:DB:Database"]};");
Console.WriteLine(connectionString.ToString());
var serverVersion = new MySqlServerVersion(new Version(8, 0, 31));
builder.Services.AddDbContextFactory<FunctionsContext>(
dbContextOptions => dbContextOptions
.UseMySql(connectionString.ToString(), serverVersion)
.LogTo(Console.WriteLine, LogLevel.Debug)
.EnableSensitiveDataLogging()
.EnableDetailedErrors());
builder.Services.AddTransient<IDockerManager,DockerManager>();
builder.Services.AddSingleton<FunctionManager>();
builder.Services.AddHttpClient<IExternalEndpointManager, ExternalEndpointManager>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
var dbFactory = app.Services.GetRequiredService<IDbContextFactory<FunctionsContext>>();
var db = await dbFactory.CreateDbContextAsync();
await db.Database.MigrateAsync();
app.Run();

View File

@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5051",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7122;http://localhost:5051",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,122 @@
using System.Runtime.InteropServices;
using Docker.DotNet;
using Docker.DotNet.Models;
using Functions.Services.Interfaces;
using Environment = Functions.Data.Environment;
namespace Functions.Services;
public class DockerManager : IDockerManager
{
private readonly ILogger<DockerManager> _logger;
private readonly DockerClient _docker;
public DockerManager(ILogger<DockerManager> logger)
{
_logger = logger;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_docker = new DockerClientConfiguration(new Uri("npipe://./pipe/docker_engine")).CreateClient();
}
else if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
_docker = new DockerClientConfiguration(new Uri("unix:///var/run/dockerManager.sock")).CreateClient();
}
else
{
throw new Exception("This Platform is not Supported");
}
}
public async Task<IList<ContainerListResponse>> GetContainers()
{
//TODO: Add Log Message
IList<ContainerListResponse> containers = await _docker.Containers.ListContainersAsync(
new ContainersListParameters(){
Limit = 10,
});
return containers;
}
public async Task<ContainerResponse> CreateContainer(string image, List<Environment> envList)
{
var createContainerParameters = new CreateContainerParameters()
{
Image = image,
Name = "FN-" + new Random().Next(10000, 99999),
/*Env = envList,*/ //TODO: Parse the envs
NetworkingConfig = new NetworkingConfig()
{
}
};
var container = await _docker.Containers.CreateContainerAsync(createContainerParameters);
return new ContainerResponse(createContainerParameters.Name, container.ID);
}
public async void ConnectNetwork(string name, string containerId)
{
var networkConnectParameters = new NetworkConnectParameters()
{
Container = containerId
};
await _docker.Networks.ConnectNetworkAsync(await GetNetworkId(name), networkConnectParameters);
}
public async void StartContainer(string containerId)
{
var containerStartParameters = new ContainerStartParameters();
await _docker.Containers.StartContainerAsync(containerId, containerStartParameters);
}
private async Task<string?> GetNetworkId(string name)
{
var response = await _docker.Networks.ListNetworksAsync();
foreach (var networkResponse in response)
{
if (networkResponse.Name.Equals(name))
{
return networkResponse.ID;
}
}
return null;
}
public async void DeleteContainer(string containerId)
{
var parameters = new ContainerRemoveParameters()
{
Force = true
};
await _docker.Containers.RemoveContainerAsync(containerId, parameters);
}
public async Task<bool> IsRunning(string containerId)
{
var containers = await GetContainers();
foreach (var containerListResponse in containers)
{
if (containerListResponse.ID.Equals(containerId))
{
return containerListResponse.State.Equals("Running");
}
}
return false;
}
}
public class ContainerResponse
{
public ContainerResponse(string name, string id)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Id = id ?? throw new ArgumentNullException(nameof(id));
}
public string Name { get; set; }
public string Id { get; set; }
}

View File

@ -0,0 +1,118 @@
using System.Text;
using Functions.Services.Interfaces;
namespace Functions.Services;
public class ExternalEndpointManager : IExternalEndpointManager
{
private readonly ILogger<ExternalEndpointManager> _logger;
private readonly HttpClient _httpClient;
public ExternalEndpointManager(ILogger<ExternalEndpointManager> logger, HttpClient httpClient)
{
_logger = logger;
_httpClient = httpClient;
}
public async Task<string> Get(string hostname)
{
try
{
// Send GET request to the API
HttpResponseMessage response = await _httpClient.GetAsync($"http://{hostname}");
// Ensure the response was successful
response.EnsureSuccessStatusCode();
// Read the response content as a string
string responseBody = await response.Content.ReadAsStringAsync();
// Display the response content
return responseBody;
}
catch (HttpRequestException ex)
{
// Handle any errors that occurred during the request
return "error";
}
return "error";
}
public async Task<string> Post(string hostname, string body)
{
try
{
var content = new StringContent(body, Encoding.UTF8, "application/json");
// Send GET request to the API
HttpResponseMessage response = await _httpClient.PostAsync($"http://{hostname}",content);
// Ensure the response was successful
response.EnsureSuccessStatusCode();
// Read the response content as a string
string responseBody = await response.Content.ReadAsStringAsync();
// Display the response content
return responseBody;
}
catch (HttpRequestException ex)
{
// Handle any errors that occurred during the request
return "error";
}
return "error";
}
public async Task<string> Delete(string hostname)
{
try
{
// Send GET request to the API
HttpResponseMessage response = await _httpClient.DeleteAsync($"http://{hostname}");
// Ensure the response was successful
response.EnsureSuccessStatusCode();
// Read the response content as a string
string responseBody = await response.Content.ReadAsStringAsync();
// Display the response content
return responseBody;
}
catch (HttpRequestException ex)
{
// Handle any errors that occurred during the request
return "error";
}
return "error";
}
public async Task<string> Patch(string hostname, string body)
{
try
{
var content = new StringContent(body, Encoding.UTF8, "application/json");
// Send GET request to the API
HttpResponseMessage response = await _httpClient.PatchAsync($"http://{hostname}",content);
// Ensure the response was successful
response.EnsureSuccessStatusCode();
// Read the response content as a string
string responseBody = await response.Content.ReadAsStringAsync();
// Display the response content
return responseBody;
}
catch (HttpRequestException ex)
{
// Handle any errors that occurred during the request
return "error";
}
return "error";
}
}

View File

@ -0,0 +1,99 @@
using Functions.Data;
using Functions.Data.DB;
using Functions.Services.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace Functions.Services;
public class FunctionManager
{
//TODO: Add Logging
private readonly ILogger<FunctionManager> _logger;
private readonly IDockerManager _dockerManager;
private readonly IDbContextFactory<FunctionsContext> _dbContextFactory;
private readonly IExternalEndpointManager _externalEndpointManager;
public FunctionManager(ILogger<FunctionManager> logger, IDockerManager dockerManager, IDbContextFactory<FunctionsContext> dbContextFactory, IExternalEndpointManager externalEndpointManager)
{
_logger = logger;
_dockerManager = dockerManager;
_dbContextFactory = dbContextFactory;
_externalEndpointManager = externalEndpointManager;
}
public async Task CreateFunction(string functionName, string imageTag)
{
var db = await _dbContextFactory.CreateDbContextAsync();
await db.Functions.AddAsync(new Function(functionName, imageTag));
await db.SaveChangesAsync();
}
public async Task DeleteFunction(string functionName)
{
var db = await _dbContextFactory.CreateDbContextAsync();
var function = db.Functions.Include(s => s.Instances).Include(s => s.EnvironmentVariables).First(s => s.Name.Equals(functionName));
foreach (var functionInstance in function.Instances)
{
_dockerManager.DeleteContainer(functionInstance.InstanceId);
}
db.Functions.Remove(function);
await db.SaveChangesAsync();
}
public async Task<string> RunInstance(string functionName, HttpMethod method, string body = "")
{
var db = await _dbContextFactory.CreateDbContextAsync();
var function = db.Functions.Include(s => s.Instances).Include(s => s.EnvironmentVariables).First(s => s.Name.Equals(functionName));
var container = await _dockerManager.CreateContainer(function.ImageTag,function.EnvironmentVariables);
var instance = new Instance(container.Name,container.Id);
function.Instances.Add(instance);
db.Functions.Update(function);
await db.SaveChangesAsync();
_dockerManager.ConnectNetwork("simplefunctions_functions", instance.InstanceId);
_dockerManager.StartContainer(instance.InstanceId);
//TODO: If not started delete instance
//Send Request to Container
if (method.Equals(HttpMethod.Post))
{
var message = await _externalEndpointManager.Post(instance.Name, body);
if (message.Equals("error"))
{
_dockerManager.DeleteContainer(instance.InstanceId);
return "";
}
return message;
}
if (method.Equals(HttpMethod.Get))
{
var message = await _externalEndpointManager.Get(instance.Name);
if (message.Equals("error"))
{
_dockerManager.DeleteContainer(instance.InstanceId);
return "";
}
return message;
}
if (method.Equals(HttpMethod.Patch))
{
var message = await _externalEndpointManager.Patch(instance.Name, body);
if (message.Equals("error"))
{
_dockerManager.DeleteContainer(instance.InstanceId);
return "";
}
return message;
}
return "";
}
}

View File

@ -0,0 +1,15 @@
using Docker.DotNet.Models;
using Functions.Data;
using Environment = Functions.Data.Environment;
namespace Functions.Services.Interfaces;
public interface IDockerManager
{
public Task<IList<ContainerListResponse>> GetContainers();
public Task<ContainerResponse> CreateContainer(string image, List<Environment> envList);
public void ConnectNetwork(string name, string containerId);
public void StartContainer(string containerId);
public void DeleteContainer(string containerId);
public Task<bool> IsRunning(string containerId);
}

View File

@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Mvc;
namespace Functions.Services.Interfaces;
public interface IExternalEndpointManager
{
public Task<string> Get(string hostname);
public Task<string> Post(string hostname, string body);
public Task<string> Delete(string hostname);
public Task<string> Patch(string hostname, string body);
}

View File

@ -0,0 +1,18 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AppConfig": {
"DB": {
"Server": "localhost",
"Port": "8082",
"User": "root",
"Password": "testPW",
"Database": "Functions"
}
}
}

View File

@ -0,0 +1,18 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AppConfig": {
"DB": {
"Server": "db",
"Port": "3306",
"User": "root",
"Password": "testPW",
"Database": "Functions"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

27
SimpleFunctions.sln Normal file
View File

@ -0,0 +1,27 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Functions", "Functions\Functions.csproj", "{F2CFCA0F-C615-4B7B-89D5-EB6C8EC3ACD1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A7EF8833-F6BE-4EBD-8DB1-5AA8EDB5A16F}"
ProjectSection(SolutionItems) = preProject
docker-compose.yml = docker-compose.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestFunction", "TestFunction\TestFunction.csproj", "{BC3C484C-EFD0-45E5-BE6E-9212916489E4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F2CFCA0F-C615-4B7B-89D5-EB6C8EC3ACD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2CFCA0F-C615-4B7B-89D5-EB6C8EC3ACD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2CFCA0F-C615-4B7B-89D5-EB6C8EC3ACD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2CFCA0F-C615-4B7B-89D5-EB6C8EC3ACD1}.Release|Any CPU.Build.0 = Release|Any CPU
{BC3C484C-EFD0-45E5-BE6E-9212916489E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BC3C484C-EFD0-45E5-BE6E-9212916489E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC3C484C-EFD0-45E5-BE6E-9212916489E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC3C484C-EFD0-45E5-BE6E-9212916489E4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Mvc;
namespace TestFunction.Controllers;
[ApiController]
[Route("")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
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)]
})
.ToArray();
}
}

20
TestFunction/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["TestFunction/TestFunction.csproj", "TestFunction/"]
RUN dotnet restore "TestFunction/TestFunction.csproj"
COPY . .
WORKDIR "/src/TestFunction"
RUN dotnet build "TestFunction.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "TestFunction.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "TestFunction.dll"]

25
TestFunction/Program.cs Normal file
View File

@ -0,0 +1,25 @@
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5060",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7090;http://localhost:5060",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<UserSecretsId>78de8317-ca29-42e4-8505-95b369d5f64b</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
</ItemGroup>
</Project>

View File

@ -0,0 +1,12 @@
namespace TestFunction;
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; }
}

View File

@ -0,0 +1,17 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AppConfig": {
"DB": {
"Server": "localhost",
"Port": "8082",
"User": "root",
"Password": "testPW",
"Database": "Functions"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

42
docker-compose.yml Normal file
View File

@ -0,0 +1,42 @@
version: "3.3"
volumes:
db:
networks:
functions:
services:
functions:
build: ./Functions
environment:
ASPNETCORE_AppConfig__DB__Server: "db"
ASPNETCORE_AppConfig__DB__User: "root"
ASPNETCORE_AppConfig__DB__Password: "testPW"
ASPNETCORE_AppConfig__DB__Database: "Functions"
ASPNETCORE_ENVIRONMENT: "Production"
ports:
- "8080:80"
- "8081:443"
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
- db
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- functions
db:
image: mysql:8.0.31-debian
cap_add:
- SYS_NICE
environment:
MYSQL_DATABASE: 'db'
MYSQL_ROOT_PASSWORD: 'testPW'
volumes:
- db:/var/lib/mysql
restart: unless-stopped
networks:
- functions
ports:
- "8082:3306"

7
global.json Normal file
View File

@ -0,0 +1,7 @@
{
"sdk": {
"version": "7.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}