btcpayserver-plugin-builder/PluginBuilder/Services/BuildService.cs
nicolas.dorier 2b51c24cdf
init
2022-11-08 10:43:22 +09:00

140 lines
5.6 KiB
C#

using System.Security.Cryptography;
using Dapper;
using Microsoft.Extensions.FileProviders;
using Newtonsoft.Json.Linq;
namespace PluginBuilder.Services
{
public class BuildServiceException : Exception
{
public BuildServiceException(string message) : base(message)
{
}
}
public class BuildService
{
public BuildService(
ILogger<BuildService> logger,
ProcessRunner processRunner,
DBConnectionFactory connectionFactory,
AzureStorageClient azureStorageClient)
{
Logger = logger;
ProcessRunner = processRunner;
ConnectionFactory = connectionFactory;
AzureStorageClient = azureStorageClient;
}
public ILogger<BuildService> Logger { get; }
public ProcessRunner ProcessRunner { get; }
public DBConnectionFactory ConnectionFactory { get; }
public AzureStorageClient AzureStorageClient { get; }
public async Task Build(PluginSlug pluginSlug, PluginBuildParameters buildParameters)
{
var fullBuildId = await CreateNewBuild(pluginSlug);
List<string> args = new List<string>();
// Create the volumes where the artifacts will be stored
args.AddRange(new[] { "volume", "create" });
args.AddRange(new[] { "--label", $"BTCPAY_PLUGIN_BUILD={fullBuildId}" });
var output = new OutputCapture();
var code = await ProcessRunner.RunAsync(new ProcessSpec()
{
Executable = "docker",
Arguments = args.ToArray(),
OutputCapture = output
}, default);
if (code != 0)
throw new BuildServiceException("docker volume create failed");
var volume = output.ToString().Trim();
args.Clear();
// Then let's build by running our image plugin-builder (built in DockerStartupHostedService)
var info = new JObject();
args.Add("run");
args.AddRange(new[] { "--env", $"GIT_REPO={buildParameters.GitRepository}" });
info["gitRepository"] = buildParameters.GitRepository;
info["dockerVolume"] = volume;
if (buildParameters.GitRef != null)
{
args.AddRange(new[] { "--env", $"GIT_REF={buildParameters.GitRef}" });
info["gitRef"] = buildParameters.GitRef;
}
if (buildParameters.PluginDirectory != null)
{
args.AddRange(new[] { "--env", $"PLUGIN_DIR={buildParameters.PluginDirectory}" });
info["pluginDir"] = buildParameters.PluginDirectory;
}
if (buildParameters.BuildConfig != null)
{
args.AddRange(new[] { "--env", $"BUILD_CONFIG={buildParameters.BuildConfig}" });
info["buildConfig"] = buildParameters.BuildConfig;
}
args.AddRange(new[] { "-v", $"{volume}:/out" });
args.AddRange(new[] { "-ti", "--rm" });
args.Add("plugin-builder");
await UpdateBuild(fullBuildId, "running", info);
JObject buildEnv;
try
{
code = await ProcessRunner.RunAsync(new ProcessSpec()
{
Executable = "docker",
Arguments = args.ToArray()
}, default);
if (code != 0)
throw new BuildServiceException("docker build failed");
string buildEnvStr = await ReadFileInVolume(volume, "build-env.json");
buildEnv = JObject.Parse(buildEnvStr);
}
catch (Exception err)
{
await UpdateBuild(fullBuildId, "failed", new JObject() { ["error"] = err.Message });
throw;
}
var pluginName = buildEnv["pluginName"]!.Value<string>();
string manifestStr = await ReadFileInVolume(volume, $"{pluginName}.btcpay.json");
var manifest = JObject.Parse(manifestStr);
await UpdateBuild(fullBuildId, "waiting-upload", buildEnv, manifest);
await UpdateBuild(fullBuildId, "uploading", null, null);
var url = await AzureStorageClient.Upload(volume, $"{pluginName}.btcpay", $"{fullBuildId}/{pluginName}.btcpay");
await UpdateBuild(fullBuildId, "uploaded", new JObject() { ["url"] = url }, null);
}
private async Task<string> ReadFileInVolume(string volume, string file)
{
var output = new OutputCapture();
// Let's read the build-env.json
int code = await ProcessRunner.RunAsync(new ProcessSpec()
{
Executable = "docker",
Arguments = new[] {
"run", "-ti", "--rm", "-v", $"{volume}:/out", "plugin-builder", "cat", $"/out/{file}" },
OutputCapture = output
}, default);
if (code != 0)
throw new BuildServiceException("docker run to read a file in volume");
return output.ToString();
}
private async Task UpdateBuild(FullBuildId fullBuildId, string newState, JObject? buildInfo, JObject? manifestInfo = null)
{
await using var connection = await ConnectionFactory.Open();
await connection.UpdateBuild(fullBuildId, newState, buildInfo, manifestInfo);
}
private async Task<FullBuildId> CreateNewBuild(PluginSlug pluginSlug)
{
await using var connection = await ConnectionFactory.Open();
return new FullBuildId(pluginSlug, await connection.NewBuild(pluginSlug));
}
}
}