Improved egg importing so it should be able to import any egg now

This commit is contained in:
Masu Baumgartner 2024-05-06 18:34:50 +02:00
parent 7bd34842fa
commit 8dc37525ce

View file

@ -9,6 +9,7 @@ using Moonlight.Features.Servers.Entities;
using Moonlight.Features.Servers.Models;
using Moonlight.Features.Servers.Models.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Moonlight.Features.Servers.Helpers;
@ -30,7 +31,7 @@ public class ImageConversionHelper
.Include(x => x.Variables)
.First(x => x.Id == image.Id);
var model = new ImageJson()
{
Name = imageWithData.Name,
@ -54,7 +55,7 @@ public class ImageConversionHelper
foreach (var variable in imageWithData.Variables)
model.Variables.Add(Mapper.Map<ImageJson.ImageVariable>(variable));
foreach (var dockerImage in imageWithData.DockerImages)
model.DockerImages.Add(Mapper.Map<ImageJson.ImageDockerImage>(dockerImage));
@ -88,20 +89,21 @@ public class ImageConversionHelper
foreach (var variable in model.Variables)
result.Variables.Add(Mapper.Map<ServerImageVariable>(variable));
foreach (var dockerImage in model.DockerImages)
result.DockerImages.Add(Mapper.Map<ServerDockerImage>(dockerImage));
return Task.FromResult(result);
}
public Task<ServerImage> ImportFromEggJson(string json)
// Old import function which used the microsoft json parsing
public Task<ServerImage> ImportFromEggJson_Old(string json)
{
var fixedJson = json;
fixedJson = fixedJson.Replace("\\/", "/");
// Note: We use microsofts config system here as its dynamic and probably the best parsing method to use here
// Note: We use microsofts config system instead of newtonsoft.json as its dynamic and probably the best parsing method to use here
var eggData = new ConfigurationBuilder()
.AddJsonString(fixedJson)
.Build();
@ -114,7 +116,11 @@ public class ImageConversionHelper
result.Author = eggData["author"] ?? "Author was missing";
result.StartupCommand = eggData["startup"] ?? "Startup was missing";
result.StopCommand = eggData.GetSection("config")["stop"] ?? "Stop command was missing";
// Some weird eggs use ^^C in as a stop command, so we need to handle this as well
// because moonlight handles power signals correctly, wings does/did not
result.StopCommand = result.StopCommand.Replace("^^C", "^C");
// Startup detection
var startupDetectionData = new ConfigurationBuilder()
.AddJsonString(eggData.GetSection("config")["startup"] ?? "{}")
@ -122,14 +128,14 @@ public class ImageConversionHelper
// Node Regex: As the online detection uses regex, we want to escape any special chars from egg imports
// as eggs dont use regex and as such may contain characters which regex uses as meta characters.
// Without this escaping, many startup detection string wont work
// Without this escaping, many startup detection strings wont work
result.OnlineDetection = Regex.Escape(startupDetectionData["done"] ?? "Online detection was missing");
// Docker image method 1:
// Reference egg: https://github.com/parkervcp/eggs/blob/master/game_eggs/mindustry/egg-mindustry.json
if (eggData["image"] != null)
{
result.DockerImages.Add(new ()
result.DockerImages.Add(new()
{
Name = eggData["image"]!,
AutoPull = true,
@ -143,14 +149,14 @@ public class ImageConversionHelper
foreach (var dockerImage in dockerImagesSection.GetChildren())
{
result.DockerImages.Add(new ()
result.DockerImages.Add(new()
{
Name = dockerImage.Value ?? "Docker image name was missing",
AutoPull = true,
DisplayName = dockerImage.Key
});
}
// Docker image method 3:
// Reference egg: https://github.com/parkervcp/eggs/blob/master/game_eggs/minecraft/java/cuberite/egg-cuberite.json
var dockerImagesList = eggData.GetValue<List<string>>("images");
@ -159,7 +165,7 @@ public class ImageConversionHelper
{
foreach (var imageName in dockerImagesList)
{
result.DockerImages.Add(new ()
result.DockerImages.Add(new()
{
Name = imageName,
AutoPull = true,
@ -167,14 +173,14 @@ public class ImageConversionHelper
});
}
}
// Parse config
var parseConfigData = new ConfigurationBuilder()
.AddJsonString(eggData.GetSection("config")["files"] ?? "{}")
.Build();
var parseConfigModels = new List<ServerParseConfig>();
foreach (var fileSection in parseConfigData.GetChildren())
{
var model = new ServerParseConfig()
@ -189,20 +195,23 @@ public class ImageConversionHelper
valueWithChecks = valueWithChecks.Replace("server.build.default.port", "SERVER_PORT");
valueWithChecks = valueWithChecks.Replace("server.build.env.", "");
model.Configuration.Add(findSection.Key, valueWithChecks);
}
parseConfigModels.Add(model);
}
result.ParseConfiguration = JsonConvert.SerializeObject(parseConfigModels);
// Installation
result.InstallShell = "/bin/" + (eggData.GetSection("scripts").GetSection("installation")["entrypoint"] ?? "Install shell was missing");
result.InstallScript = eggData.GetSection("scripts").GetSection("installation")["script"] ?? "Install script was missing";
result.InstallDockerImage = eggData.GetSection("scripts").GetSection("installation")["container"] ?? "Install script was missing";
result.InstallShell = "/bin/" + (eggData.GetSection("scripts").GetSection("installation")["entrypoint"] ??
"Install shell was missing");
result.InstallScript = eggData.GetSection("scripts").GetSection("installation")["script"] ??
"Install script was missing";
result.InstallDockerImage = eggData.GetSection("scripts").GetSection("installation")["container"] ??
"Install script was missing";
// Variables
foreach (var variableSection in eggData.GetSection("variables").GetChildren())
{
@ -225,10 +234,159 @@ public class ImageConversionHelper
variable.AllowView = variableSection.GetValue<int>("user_viewable") == 1;
variable.AllowEdit = variableSection.GetValue<int>("user_editable") == 1;
}
result.Variables.Add(variable);
}
return Task.FromResult(result);
}
public Task<ServerImage> ImportFromEggJson(string json)
{
// Prepare json
var fixedJson = json;
fixedJson = fixedJson.Replace("\\/", "/");
// Prepare result object and set moonlight native fields
var result = new ServerImage();
result.AllocationsNeeded = 1;
result.AllowDockerImageChange = true;
//
var egg = JObject.Parse(fixedJson);
result.AllocationsNeeded = 1; // We cannot convert this value as its moonlight native
// Basic values
result.Name = egg["name"]?.Value<string>() ?? "Name was missing";
result.Author = egg["author"]?.Value<string>() ?? "Author was missing";
result.StartupCommand = egg["startup"]?.Value<string>() ?? "Startup was missing";
result.StopCommand = egg["config"]?["stop"]?.Value<string>() ?? "Stop command was missing";
// Some weird eggs use ^^C in as a stop command, so we need to handle this as well
// because moonlight handles power signals correctly, wings does/did not
result.StopCommand = result.StopCommand.Replace("^^C", "^C");
// Startup detection
var startup = JObject.Parse(egg["config"]?["startup"]?.Value<string>() ?? "{}");
// Node Regex: As the online detection uses regex, we want to escape any special chars from egg imports
// as eggs dont use regex and as such may contain characters which regex uses as meta characters.
// Without this escaping, many startup detection strings wont work
result.OnlineDetection = Regex.Escape(startup["done"]?.Value<string>() ?? "Online detection was missing");
// Docker images
// Docker image method 1:
// Reference egg: https://github.com/parkervcp/eggs/blob/master/game_eggs/mindustry/egg-mindustry.json
if (egg["image"] != null)
{
result.DockerImages.Add(new()
{
Name = egg["image"]?.Value<string>() ?? "Docker image not specified",
AutoPull = true,
DisplayName = egg["image"]?.Value<string>() ?? "Docker image not specified"
});
}
// Docker image method 2:
// Reference egg: https://github.com/parkervcp/eggs/blob/master/game_eggs/minecraft/java/cuberite/egg-cuberite.json
if (egg["images"] != null)
{
var images = egg["images"]?.ToObject<JArray>() ?? JArray.Parse("[]");
foreach (var imageName in images)
{
result.DockerImages.Add(new()
{
Name = imageName.Value<string>() ?? "Docker image name not found",
AutoPull = true,
DisplayName = imageName.Value<string>() ?? "Docker image name not found",
});
}
}
// Docker image method 3:
// Reference egg: https://github.com/parkervcp/eggs/blob/master/game_eggs/minecraft/java/paper/egg-paper.json
if (egg["docker_images"] != null)
{
var images = egg["docker_images"]?.ToObject<JObject>() ?? JObject.Parse("{}");
foreach (var kvp in images)
{
result.DockerImages.Add(new()
{
Name = kvp.Value?.Value<string>() ?? kvp.Key,
AutoPull = true,
DisplayName = kvp.Key
});
}
}
// Parse config
var parseConfig = JObject.Parse(egg["config"]?["files"]?.Value<string>() ?? "{}");
var parseConfigModels = new List<ServerParseConfig>();
foreach (var config in parseConfig)
{
var model = new ServerParseConfig()
{
File = config.Key,
Type = config.Value?["parser"]?.Value<string>() ?? "parser was missing"
};
if (config.Value?["find"] == null)
continue;
foreach (var findSection in config.Value!["find"]!.ToObject<JObject>() ?? JObject.Parse("{}"))
{
var valueWithChecks = findSection.Value?.Value<string>() ?? "Find value was null";
valueWithChecks = valueWithChecks.Replace("server.build.default.port", "SERVER_PORT");
valueWithChecks = valueWithChecks.Replace("server.build.env.", "");
model.Configuration.Add(findSection.Key, valueWithChecks);
}
parseConfigModels.Add(model);
}
result.ParseConfiguration = JsonConvert.SerializeObject(parseConfigModels);
// Installation
var installation = egg["scripts"]?["installation"] ?? JObject.Parse("{}");
result.InstallShell = "/bin/" + installation.Value<string>("entrypoint") ?? "Install shell was missing";
result.InstallScript = installation.Value<string>("script") ?? "Install script was missing";
result.InstallDockerImage = installation.Value<string>("container") ?? "Install container was missing";
// Variables
foreach (var variableSection in egg["variables"]?.Children() ?? JEnumerable<JToken>.Empty)
{
var variable = new ServerImageVariable()
{
DisplayName = variableSection.Value<string>("name") ?? "Name was missing",
Description = variableSection.Value<string>("description") ?? "Description was missing",
Key = variableSection.Value<string>("env_variable") ?? "Environment variable was missing",
DefaultValue = variableSection.Value<string>("default_value") ?? "Default value was missing",
};
// Check if it is a bool value
if (bool.TryParse(variableSection["user_viewable"]?.Value<string>(), out _))
{
variable.AllowView = variableSection.Value<bool>("user_viewable");
variable.AllowEdit = variableSection.Value<bool>("user_editable");
}
else
{
variable.AllowView = variableSection.Value<int>("user_viewable") == 1;
variable.AllowEdit = variableSection.Value<int>("user_editable") == 1;
}
result.Variables.Add(variable);
}
return Task.FromResult(result);
}
}