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",
"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"
],
"secrets": {

View File

@ -22,6 +22,9 @@ namespace ln.build.server
[StaticArgument( LongOption = "build")]
public static string BuildPath { get; set; }
[StaticArgument( LongOption = "build-secret")]
public static string BuildSecret { get; set; }
static void Main(string[] args)
{
ArgumentContainer ac = new ArgumentContainer(typeof(Program));
@ -34,7 +37,7 @@ namespace ln.build.server
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.RunJob();
} else {

View File

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

View File

@ -40,6 +40,7 @@ namespace ln.build
:this(ciService, repository)
{
SecretStorage = secretStorage;
Environment.SecretStorage = secretStorage;
}
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.Backends.Add(Logger.ConsoleLogger);
Environment = new CommandEnvironment();
Environment = new CommandEnvironment(){ CIJob = this };
}
public Stream GetLogStream(string name)

View File

@ -6,6 +6,7 @@ using System.Net;
using System.Net.NetworkInformation;
using ln.application;
using ln.build.repositories;
using ln.build.secrets;
using ln.http;
using ln.http.router;
using ln.json;
@ -131,6 +132,8 @@ namespace ln.build
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)
{

View File

@ -47,12 +47,30 @@ namespace ln.build
}
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()
{
HttpClient?.Dispose();
}
static JsonApiClient(){
System.Net.ServicePointManager.Expect100Continue = false;
}
}
}

View File

@ -29,6 +29,9 @@ namespace ln.build.commands
SecretStorage secretStorage;
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, Logger logger) : this(logger) { Parent = parent; WorkingDirectory = parent.WorkingDirectory; }

View File

@ -5,7 +5,7 @@
</PropertyGroup>
<PropertyGroup>
<Version>0.3.0</Version>
<Version>0.4.0</Version>
<Authors>Harald Wolff-Thobaben</Authors>
<Company>l--n.de</Company>
<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.IO;
using System.Text;
using ln.build.commands;
using ln.json;
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.Net.Http.Headers;
namespace ln.build.repositories
{
@ -16,7 +17,8 @@ namespace ln.build.repositories
public abstract Repository Repository { get; }
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 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.type;
namespace ln.build.repositories.gitea
{
@ -13,7 +18,7 @@ namespace ln.build.repositories.gitea
}
public GiteaRelease(GiteaRepository repository,JSONObject jsonRelease) : this(repository)
{
Id = (int)jsonRelease["id"].ToNative();
Id = (int)(long)jsonRelease["id"].ToNative();
Name = jsonRelease["name"].ToNative().ToString();
TagName = jsonRelease["tag_name"].ToNative().ToString();
Body = jsonRelease["body"].ToNative().ToString();
@ -21,9 +26,18 @@ namespace ln.build.repositories.gitea
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()

View File

@ -29,7 +29,7 @@ namespace ln.build.repositories.gitea
Owner = owner;
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)
:this(baseURL, owner, name)
@ -49,7 +49,7 @@ namespace ln.build.repositories.gitea
if (HttpStatusCode.Created != Client.PostJson(
stateObject,
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);
@ -58,14 +58,9 @@ namespace ln.build.repositories.gitea
public override Release[] GetReleases()
{
string releasesUrl = string.Format("{0}/api/v1/repos/{1}/{2}/releases",
BaseURL,
Owner,
Name
);
if (HttpStatusCode.OK == Client.GetJson(
out JSONValue jsonReleases,
"api/v1/repos", Owner, "releases"
"repos", Owner, Name, "releases"
))
{
List<Release> releases = new List<Release>();
@ -86,9 +81,17 @@ namespace ln.build.repositories.gitea
{
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);
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)){
SecretStorage = secretStorage,
};
@ -156,10 +159,21 @@ namespace ln.build.repositories.gitea
.SetVariable("REPO_OWNER", owner)
.SetVariable("REPO_NAME", repoName)
.SetVariable("REPO_REF", _ref)
.SetVariable("NOTIFY", message["pusher"]["email"].ToNative().ToString())
// .SetVariable("NOTIFY", message["pusher"]["email"].ToNative().ToString())
;
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);
ciService.Enqueue(ciJob);
}