Added "RELEASE" StageCommand

master v0.4.0
Harald Wolff 2020-12-03 22:58:35 +01:00
parent deebca6a05
commit 21b53b137c
16 changed files with 221 additions and 93 deletions

View File

@ -39,7 +39,7 @@
{ {
"name": "push", "name": "push",
"commands": [ "commands": [
"SH dotnet nuget push .build/ln.build.*.nupkg -s $NUGET_SOURCE -k $NUGET_APIKEY", "SH for NUPKG in .build/ln.build.*.nupkg; do dotnet nuget push $NUPKG -s $NUGET_SOURCE -k $NUGET_APIKEY; done",
"RELEASE .build/linux-x64/ln.build.server=ln.build.server-linux-amd64" "RELEASE .build/linux-x64/ln.build.server=ln.build.server-linux-amd64"
], ],
"secrets": { "secrets": {

View File

@ -22,6 +22,9 @@ namespace ln.build.server
[StaticArgument( LongOption = "build")] [StaticArgument( LongOption = "build")]
public static string BuildPath { get; set; } public static string BuildPath { get; set; }
[StaticArgument( LongOption = "build-secret")]
public static string BuildSecret { get; set; }
static void Main(string[] args) static void Main(string[] args)
{ {
ArgumentContainer ac = new ArgumentContainer(typeof(Program)); ArgumentContainer ac = new ArgumentContainer(typeof(Program));
@ -34,7 +37,7 @@ namespace ln.build.server
if (BuildPath != null) if (BuildPath != null)
{ {
CIJob job = new CIJob(CIService,null); CIJob job = new CIJob(CIService,null, (BuildSecret != null) ? CIService.GetSecretStorage(BuildSecret) : null);
job.WorkingDirectory = BuildPath; job.WorkingDirectory = BuildPath;
job.RunJob(); job.RunJob();
} else { } else {

View File

@ -6,7 +6,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<Version>0.3.0</Version> <Version>0.4.0</Version>
<Authors>Harald Wolff-Thobaben</Authors> <Authors>Harald Wolff-Thobaben</Authors>
<Company>l--n.de</Company> <Company>l--n.de</Company>
<Description>A simple build server scheduling builds triggered via web-hooks</Description> <Description>A simple build server scheduling builds triggered via web-hooks</Description>

View File

@ -40,6 +40,7 @@ namespace ln.build
:this(ciService, repository) :this(ciService, repository)
{ {
SecretStorage = secretStorage; SecretStorage = secretStorage;
Environment.SecretStorage = secretStorage;
} }
public CIJob(CIService ciService, Repository repository) public CIJob(CIService ciService, Repository repository)
{ {
@ -52,7 +53,7 @@ namespace ln.build
Logger = new Logger(new FileLogger(Path.Combine(ciService.ReportsDirectory, JobID, "build.log"))); Logger = new Logger(new FileLogger(Path.Combine(ciService.ReportsDirectory, JobID, "build.log")));
Logger.Backends.Add(Logger.ConsoleLogger); Logger.Backends.Add(Logger.ConsoleLogger);
Environment = new CommandEnvironment(); Environment = new CommandEnvironment(){ CIJob = this };
} }
public Stream GetLogStream(string name) public Stream GetLogStream(string name)

View File

@ -6,6 +6,7 @@ using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using ln.application; using ln.application;
using ln.build.repositories; using ln.build.repositories;
using ln.build.secrets;
using ln.http; using ln.http;
using ln.http.router; using ln.http.router;
using ln.json; using ln.json;
@ -131,6 +132,8 @@ namespace ln.build
hookRouter.AddSimpleRoute(String.Format("/{0}", name), (HttpRoutingContext context,HttpRequest request) => webHookHandler(this, request)); hookRouter.AddSimpleRoute(String.Format("/{0}", name), (HttpRoutingContext context,HttpRequest request) => webHookHandler(this, request));
} }
public SecretStorage GetSecretStorage(string secretName) => new SecretStorage(Path.Combine(BaseDirectory,"secrets", String.Format("{0}.json", secretName)));
public void Enqueue(CIJob job) public void Enqueue(CIJob job)
{ {

View File

@ -47,12 +47,30 @@ namespace ln.build
} }
return httpResponse.StatusCode; return httpResponse.StatusCode;
} }
public HttpStatusCode PostContent(HttpContent content, out JSONValue response,params string[] path)
{
HttpResponseMessage httpResponse = HttpClient.PostAsync(string.Format("{0}/{1}",BaseURL, string.Join('/', path)),content).Result;
if (httpResponse.StatusCode == System.Net.HttpStatusCode.Created)
{
response = JSONParser.Parse(httpResponse.Content.ReadAsStringAsync().Result);
} else {
response = null;
Logging.Log(LogLevel.DEBUG, "failed URL: {0}", httpResponse.RequestMessage.RequestUri);
Logging.Log(LogLevel.DEBUG, "httpResponse; {0}", httpResponse);
}
return httpResponse.StatusCode;
}
public void Dispose() public void Dispose()
{ {
HttpClient?.Dispose(); HttpClient?.Dispose();
} }
static JsonApiClient(){
System.Net.ServicePointManager.Expect100Continue = false;
}
} }
} }

View File

@ -29,6 +29,9 @@ namespace ln.build.commands
SecretStorage secretStorage; SecretStorage secretStorage;
public SecretStorage SecretStorage { get => secretStorage ?? Parent?.SecretStorage; set => secretStorage = value; } public SecretStorage SecretStorage { get => secretStorage ?? Parent?.SecretStorage; set => secretStorage = value; }
CIJob cIJob;
public CIJob CIJob { get => cIJob ?? Parent?.CIJob ; set => cIJob = value; }
public CommandEnvironment(CommandEnvironment parent) : this(parent.Logger) { Parent = parent; WorkingDirectory = parent.WorkingDirectory; } public CommandEnvironment(CommandEnvironment parent) : this(parent.Logger) { Parent = parent; WorkingDirectory = parent.WorkingDirectory; }
public CommandEnvironment(CommandEnvironment parent, Logger logger) : this(logger) { Parent = parent; WorkingDirectory = parent.WorkingDirectory; } public CommandEnvironment(CommandEnvironment parent, Logger logger) : this(logger) { Parent = parent; WorkingDirectory = parent.WorkingDirectory; }

View File

@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<Version>0.3.0</Version> <Version>0.4.0</Version>
<Authors>Harald Wolff-Thobaben</Authors> <Authors>Harald Wolff-Thobaben</Authors>
<Company>l--n.de</Company> <Company>l--n.de</Company>
<Description>A simple build server scheduling builds triggered via web-hooks</Description> <Description>A simple build server scheduling builds triggered via web-hooks</Description>

View File

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text;
using ln.build.commands; using ln.build.commands;
using ln.json; using ln.json;
using ln.logging; using ln.logging;
@ -113,71 +108,4 @@ namespace ln.build.pipeline
} }
} }
public abstract class StageCommand
{
public string Name { get; }
public StageCommand(string name)
{
Name = name;
}
public abstract void Run(Stage stage);
static Dictionary<string,Func<string,StageCommand>> commandFactories = new Dictionary<string, Func<string, StageCommand>>();
public static StageCommand Create(string cmdline)
{
string[] tokens = cmdline.Split(new char[]{' ','\t'}, 2);
if (commandFactories.TryGetValue(tokens[0],out Func<string,StageCommand> factory))
{
return factory(tokens[1]);
}
throw new Exception(string.Format("can't find factory for command keyword '{0}'", tokens[0]));
}
static StageCommand()
{
commandFactories.Add("SH", (args) => new ShellCommand(args));
commandFactories.Add("DOTNET", (args) => new DotNetCommand(args));
commandFactories.Add("GITEA", (args) => new DotNetCommand(args));
}
}
public class ShellCommand : StageCommand
{
public event CommandExitedDelegate OnCommandExited;
public CommandRunner CommandRunner { get; }
public ShellCommand(string filename,params CommandRunner.Argument[] arguments)
:base("SH")
{
CommandRunner = new CommandRunner("/bin/bash", "-c"){ Throw = CRThrow.NEVER, };
CommandRunner.AddArguments(arguments);
}
public ShellCommand(string cmdline)
:base("SH")
{
CommandRunner = new CommandRunner("/bin/bash", "-c", string.Format("\"{0}\"",cmdline)){ Throw = CRThrow.NEVER, };
}
public override void Run(Stage stage)
{
MemoryStream logStream = new MemoryStream();
int result = CommandRunner.Run(stage.CommandEnvironment, logStream);
stage.CommandEnvironment.Logger.Log(LogLevel.INFO, "command output:\n{0}", Encoding.UTF8.GetString(logStream.ToArray()));
stage.CommandEnvironment.Logger.Log(LogLevel.INFO, "command exit code: {0}", result);
if (result != 0)
throw new Exception(String.Format("command exited with code {0} [0x{0:x}]", result));
}
}
} }

View File

@ -0,0 +1,53 @@
using System;
using System.IO;
using Jint.Native.Function;
using ln.build.repositories;
using ln.logging;
namespace ln.build.pipeline
{
public class ReleaseCommand : StageCommand
{
public string[] Arguments { get; }
public ReleaseCommand(string args) :base("RELEASE")
{
Arguments = args.Split();
}
public override void Run(Stage stage)
{
if (stage.CommandEnvironment.Get("REPO_EVENT","").Equals("release"))
{
Repository repository = stage.CommandEnvironment.CIJob?.Repository;
if (repository != null)
{
Release release = repository.GetRelease(int.Parse(stage.CommandEnvironment.Get("RELEASE_ID")));
foreach (string arg in Arguments)
{
string localPath, remoteFileName;
int ie = arg.IndexOf('=');
if (ie == -1)
{
localPath = Path.Combine(stage.CommandEnvironment.WorkingDirectory, arg);
remoteFileName = Path.GetFileName(arg);
} else {
localPath = Path.Combine(stage.CommandEnvironment.WorkingDirectory, arg.Substring(0, ie));
remoteFileName = arg.Substring(ie + 1);
}
stage.CommandEnvironment.Logger.Log(LogLevel.INFO, "Adding {0} to release as {1}", localPath, remoteFileName);
release.AddAttachement(localPath, remoteFileName);
}
} else {
stage.CommandEnvironment.Logger.Log(LogLevel.ERROR, "RELEASE: no repository interface found!");
throw new Exception("Respository needed!");
}
} else {
stage.CommandEnvironment.Logger.Log(LogLevel.INFO, "RELEASE: build is no release build. Ignoring.");
}
}
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.IO;
using System.Text;
using ln.build.commands;
using ln.logging;
namespace ln.build.pipeline
{
public class ShellCommand : StageCommand
{
public event CommandExitedDelegate OnCommandExited;
public CommandRunner CommandRunner { get; }
public ShellCommand(string filename,params CommandRunner.Argument[] arguments)
:base("SH")
{
CommandRunner = new CommandRunner("/bin/bash", "-c"){ Throw = CRThrow.NEVER, };
CommandRunner.AddArguments(arguments);
}
public ShellCommand(string cmdline)
:base("SH")
{
CommandRunner = new CommandRunner("/bin/bash", "-c", string.Format("\"{0}\"",cmdline)){ Throw = CRThrow.NEVER, };
}
public override void Run(Stage stage)
{
MemoryStream logStream = new MemoryStream();
int result = CommandRunner.Run(stage.CommandEnvironment, logStream);
stage.CommandEnvironment.Logger.Log(LogLevel.INFO, "command output:\n{0}", Encoding.UTF8.GetString(logStream.ToArray()));
stage.CommandEnvironment.Logger.Log(LogLevel.INFO, "command exit code: {0}", result);
if (result != 0)
throw new Exception(String.Format("command exited with code {0} [0x{0:x}]", result));
}
}
}

View File

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using ln.build.commands;
namespace ln.build.pipeline
{
public abstract class StageCommand
{
public string Name { get; }
public StageCommand(string name)
{
Name = name;
}
public abstract void Run(Stage stage);
static Dictionary<string,Func<string,StageCommand>> commandFactories = new Dictionary<string, Func<string, StageCommand>>();
public static StageCommand Create(string cmdline)
{
string[] tokens = cmdline.Split(new char[]{' ','\t'}, 2);
if (commandFactories.TryGetValue(tokens[0],out Func<string,StageCommand> factory))
{
return factory(tokens[1]);
}
throw new Exception(string.Format("can't find factory for command keyword '{0}'", tokens[0]));
}
static StageCommand()
{
commandFactories.Add("SH", (args) => new ShellCommand(args));
commandFactories.Add("DOTNET", (args) => new DotNetCommand(args));
commandFactories.Add("GITEA", (args) => new DotNetCommand(args));
commandFactories.Add("RELEASE", (args) => new ReleaseCommand(args));
}
}
}

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Net.Http.Headers;
namespace ln.build.repositories namespace ln.build.repositories
{ {
@ -16,7 +17,8 @@ namespace ln.build.repositories
public abstract Repository Repository { get; } public abstract Repository Repository { get; }
public abstract Attachement[] GetAttachements(); public abstract Attachement[] GetAttachements();
public abstract void AddAttachement(Attachement attachement,string localPath);
public abstract void AddAttachement(string localPath,string remoteFilename);

View File

@ -12,6 +12,7 @@ namespace ln.build.repositories
public abstract Release[] GetReleases(); public abstract Release[] GetReleases();
public abstract Release GetRelease(string tagName); public abstract Release GetRelease(string tagName);
public abstract Release GetRelease(int id);
} }

View File

@ -1,5 +1,10 @@
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using ln.json; using ln.json;
using ln.type;
namespace ln.build.repositories.gitea namespace ln.build.repositories.gitea
{ {
@ -13,7 +18,7 @@ namespace ln.build.repositories.gitea
} }
public GiteaRelease(GiteaRepository repository,JSONObject jsonRelease) : this(repository) public GiteaRelease(GiteaRepository repository,JSONObject jsonRelease) : this(repository)
{ {
Id = (int)jsonRelease["id"].ToNative(); Id = (int)(long)jsonRelease["id"].ToNative();
Name = jsonRelease["name"].ToNative().ToString(); Name = jsonRelease["name"].ToNative().ToString();
TagName = jsonRelease["tag_name"].ToNative().ToString(); TagName = jsonRelease["tag_name"].ToNative().ToString();
Body = jsonRelease["body"].ToNative().ToString(); Body = jsonRelease["body"].ToNative().ToString();
@ -21,9 +26,18 @@ namespace ln.build.repositories.gitea
IsPreRelease = (bool)jsonRelease["prerelease"].ToNative(); IsPreRelease = (bool)jsonRelease["prerelease"].ToNative();
} }
public override void AddAttachement(Attachement attachement, string localPath) public override void AddAttachement(string localPath, string remoteFileName)
{ {
throw new System.NotImplementedException(); using (FileStream fs = new FileStream(localPath,FileMode.Open))
{
StreamContent attachmentBytes = new StreamContent(fs);
MultipartFormDataContent formDataContent = new MultipartFormDataContent();
formDataContent.Add(attachmentBytes, "attachment", remoteFileName);
if (HttpStatusCode.Created != GiteaRepository.Client.PostContent(formDataContent,out JSONValue response, "repos", GiteaRepository.Owner, GiteaRepository.Name, "releases", Id.ToString(), String.Format("assets?name={0}", remoteFileName)))
{
throw new Exception(String.Format("could not create attachment to release: {0}", localPath));
}
}
} }
public override Attachement[] GetAttachements() public override Attachement[] GetAttachements()

View File

@ -29,7 +29,7 @@ namespace ln.build.repositories.gitea
Owner = owner; Owner = owner;
Name = name; Name = name;
Client = new JsonApiClient(baseURL); Client = new JsonApiClient(string.Format("{0}/api/v1",baseURL));
} }
public GiteaRepository(string baseURL, string owner, string name, string accessToken) public GiteaRepository(string baseURL, string owner, string name, string accessToken)
:this(baseURL, owner, name) :this(baseURL, owner, name)
@ -49,7 +49,7 @@ namespace ln.build.repositories.gitea
if (HttpStatusCode.Created != Client.PostJson( if (HttpStatusCode.Created != Client.PostJson(
stateObject, stateObject,
out JSONValue response, out JSONValue response,
"api/v1/repos", job.GetVariable("REPO_OWNER"), job.GetVariable("REPO_NAME"), "statuses", job.GetVariable("REPO_REF") "repos", job.GetVariable("REPO_OWNER"), job.GetVariable("REPO_NAME"), "statuses", job.GetVariable("REPO_REF")
)) ))
{ {
job.Logger.Log(LogLevel.ERROR, "{0}: UpdateBuildState(): could not post state", GetType().Name); job.Logger.Log(LogLevel.ERROR, "{0}: UpdateBuildState(): could not post state", GetType().Name);
@ -58,14 +58,9 @@ namespace ln.build.repositories.gitea
public override Release[] GetReleases() public override Release[] GetReleases()
{ {
string releasesUrl = string.Format("{0}/api/v1/repos/{1}/{2}/releases",
BaseURL,
Owner,
Name
);
if (HttpStatusCode.OK == Client.GetJson( if (HttpStatusCode.OK == Client.GetJson(
out JSONValue jsonReleases, out JSONValue jsonReleases,
"api/v1/repos", Owner, "releases" "repos", Owner, Name, "releases"
)) ))
{ {
List<Release> releases = new List<Release>(); List<Release> releases = new List<Release>();
@ -86,9 +81,17 @@ namespace ln.build.repositories.gitea
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override Release GetRelease(int releaseId)
{
if (HttpStatusCode.OK == Client.GetJson(out JSONValue jsonRelease, "repos", Owner, Name, "releases", releaseId.ToString()))
{
return new GiteaRelease(this, jsonRelease as JSONObject);
}
return null;
}
public bool ContainsFile(string filename,string _ref) => (Client.GetJson(out JSONValue response, "/api/v1/repos", Owner, Name, "contents", string.Format("{0}?ref={1}",filename, _ref)) == HttpStatusCode.OK); public bool ContainsFile(string filename,string _ref) => (Client.GetJson(out JSONValue response, "repos", Owner, Name, "contents", string.Format("{0}?ref={1}",filename, _ref)) == HttpStatusCode.OK);
@ -125,7 +128,7 @@ namespace ln.build.repositories.gitea
string giteaBaseUrl = htmlUrl.Remove(htmlUrl.Length - repoPath.Length); string giteaBaseUrl = htmlUrl.Remove(htmlUrl.Length - repoPath.Length);
SecretStorage secretStorage = new SecretStorage(Path.Combine(ciService.BaseDirectory,"secrets", String.Format("{0}.json", hookSecret))); SecretStorage secretStorage = ciService.GetSecretStorage(hookSecret);
GiteaRepository giteaRepository = new GiteaRepository(giteaBaseUrl, owner, repoName, secretStorage.GetSecret(giteaBaseUrl)){ GiteaRepository giteaRepository = new GiteaRepository(giteaBaseUrl, owner, repoName, secretStorage.GetSecret(giteaBaseUrl)){
SecretStorage = secretStorage, SecretStorage = secretStorage,
}; };
@ -156,10 +159,21 @@ namespace ln.build.repositories.gitea
.SetVariable("REPO_OWNER", owner) .SetVariable("REPO_OWNER", owner)
.SetVariable("REPO_NAME", repoName) .SetVariable("REPO_NAME", repoName)
.SetVariable("REPO_REF", _ref) .SetVariable("REPO_REF", _ref)
.SetVariable("NOTIFY", message["pusher"]["email"].ToNative().ToString()) // .SetVariable("NOTIFY", message["pusher"]["email"].ToNative().ToString())
; ;
ciJob.Environment.SecretStorage = secretStorage; ciJob.Environment.SecretStorage = secretStorage;
switch (eventType)
{
case "release":
ciJob.SetVariable("RELEASE_TAGNAME", message["release"]["tag_name"].ToNative().ToString());
ciJob.SetVariable("RELEASE_ID", message["release"]["id"].ToNative().ToString());
break;
case "push":
break;
}
ciJob.UpdateBuildState(BuildState.PENDING); ciJob.UpdateBuildState(BuildState.PENDING);
ciService.Enqueue(ciJob); ciService.Enqueue(ciJob);
} }