Compare commits

...

18 Commits

Author SHA1 Message Date
Harald Wolff 1e49ba6247 Version 0.4.4
ln.build - build0.l--n.de build job pending Details
2021-07-10 02:13:00 +02:00
Harald Wolff e2a6124707 Fix version detection, use <PackageVersion> if exists, then <Version>
ln.build - build0.l--n.de build job pending Details
2021-07-10 02:12:04 +02:00
Harald Wolff 833890438f Ignore .csproj when no version set
ln.build - build0.waldrennach.l--n.de build job pending Details
2020-12-09 17:16:22 +01:00
Harald Wolff 353397e523 Sort stages by priority 2020-12-09 17:15:58 +01:00
Harald Wolff 65d64c2484 Fix WorkingDirectory in -m build mode 2020-12-09 17:15:46 +01:00
Harald Wolff 440c6af85d Fix Nullreference in CSProjHelper.GetVersion()
ln.build - build0.waldrennach.l--n.de build job pending Details
2020-12-09 13:00:43 +01:00
Harald Wolff 1b922b8096 SemVersion: fix operators >/< 2020-12-09 13:00:22 +01:00
Harald Wolff 0e17216cc9 CI Versioning 2020-12-09 12:59:49 +01:00
Harald Wolff 3845bbd4c4 Release 0.4.3
ln.build - build0.waldrennach.l--n.de build job pending Details
2020-12-09 12:38:17 +01:00
Harald Wolff b770183fef Fix release stage command
ln.build - build0.waldrennach.l--n.de build job pending Details
2020-12-09 12:36:14 +01:00
Harald Wolff 73994ac366 Release 0.4.3
ln.build - build0.waldrennach.l--n.de build job pending Details
2020-12-09 12:07:18 +01:00
Harald Wolff 5f265a41d9 implemented release command 2020-12-09 12:06:51 +01:00
Harald Wolff b6cde40c20 Fixed incorrect relative paths in DotnetCommands
ln.build - build0.waldrennach.l--n.de build job pending Details
2020-12-09 11:03:03 +01:00
Harald Wolff 654ae4156e Fixed double call to CIService.Initialize()
ln.build - build0.waldrennach.l--n.de build job pending Details
2020-12-09 10:33:13 +01:00
Harald Wolff 1b9ed4e7ad 0.4.3-ci
ln.build - build0.waldrennach.l--n.de build job pending Details
2020-12-09 10:05:52 +01:00
Harald Wolff 18e7a3a229 change version
ln.build - build0.waldrennach.l--n.de build job pending Details
2020-12-04 11:27:35 +01:00
Harald Wolff ff4068c718 Fix Attachment handling for RELEASE stage command
ln.build - build0.waldrennach.l--n.de build job pending Details
2020-12-04 10:42:22 +01:00
Harald Wolff 2e48a0ac99 JsonApiClient: +Delete(..)
ln.build - build0.waldrennach.l--n.de build job pending Details
2020-12-04 10:41:24 +01:00
31 changed files with 1315 additions and 330 deletions

View File

@ -1,52 +1,17 @@
{
"templates": [
"dotnet"
],
"env": {
"NUGET_SOURCE": "https://nexus.niclas-thobaben.de/repository/l--n.de/",
"CONFIGURATION": "Release"
},
"stages": [
{
"name": "setup",
"env": {
"SOME_ENV_VAR": "Some text",
},
"commands": [
"SH echo Setting up build environment",
"SH set",
"SH rm -Rf .build"
]
},
{
"name": "prepare",
"commands": [
"SH dotnet restore",
"SH dotnet clean"
"dotnet prepare */*.csproj"
]
},
{
"name": "build",
"commands": [
"SH dotnet build -c $CONFIGURATION"
]
},
{
"name": "pack_and_publish",
"commands": [
"SH dotnet pack ln.build -o .build -c $CONFIGURATION",
"SH dotnet pack ln.build.server -o .build -c $CONFIGURATION",
"SH dotnet publish ln.build.server -p:PublishTrimmed=true -p:PublishSingleFile=true -p:PublishReadyToRun=true --self-contained -r linux-x64 -c $CONFIGURATION -o .build/linux-x64",
"SH dotnet publish ln.build.server -p:PublishTrimmed=true -p:PublishSingleFile=true -p:PublishReadyToRun=false --self-contained -r win-x64 -c $CONFIGURATION -o .build/windows-x64"
]
},
{
"name": "push",
"commands": [
"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/windows-x64/ln.build.server.exe=ln.build.server-windows-x64.exe"
],
"secrets": {
"NUGET_APIKEY": "https://nexus.niclas-thobaben.de"
}
}
]
}

View File

@ -12,18 +12,41 @@ using Microsoft.VisualBasic;
using ln.application;
using System.IO;
using ln.build.repositories.gitea;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using ln.build.semver;
using ln.build.pipeline;
namespace ln.build.server
{
public enum RunMode {
serv,
versioning,
build
}
class Program
{
static CIService CIService;
[StaticArgument( Option = 'm')]
static RunMode RunMode { get; set; } = RunMode.serv;
[StaticArgument( LongOption = "build")]
public static string BuildPath { get; set; }
static string BuildPath { get; set; }
[StaticArgument( LongOption = "build-secret")]
public static string BuildSecret { get; set; }
static string BuildSecret { get; set; }
[StaticArgument( LongOption = "versioning-provider")]
static string VersioningProviderName { get; set; }
[StaticArgument( LongOption = "versioning-level")]
static SemVerLevels VersioningLevel { get; set;} = SemVerLevels.PATCH;
[StaticArgument( LongOption = "versioning-source")]
static string VersioningSource { get; set;}
static void Main(string[] args)
{
@ -35,20 +58,42 @@ namespace ln.build.server
CIService.Initialize();
if (BuildPath != null)
switch (RunMode)
{
CIJob job = new CIJob(CIService,null, (BuildSecret != null) ? CIService.GetSecretStorage(BuildSecret) : null);
job.WorkingDirectory = BuildPath;
job.RunJob();
} else {
CIService.Initialize();
CIService.AddWebHookHandler("gitea", GiteaRepository.WebHookHandler);
CIService.Start();
case RunMode.serv:
CIService.AddWebHookHandler("gitea", GiteaRepository.WebHookHandler);
CIService.Start();
break;
case RunMode.build:
CIJob job = new CIJob(CIService, null, (BuildSecret != null) ? CIService.GetSecretStorage(BuildSecret) : null);
job.WorkingDirectory = BuildPath;
job.Environment.WorkingDirectory = BuildPath;
job.RunJob();
break;
case RunMode.versioning:
Versioning versioning = new Versioning(VersioningProviderName);
versioning.Sources = new string[]{ VersioningSource };
SemVersion version = versioning.GetCurrentVersion(null);
Logging.Log("INFO: found version {0}", version);
version.Increment(VersioningLevel);
Logging.Log("INFO: write back version {0}", version);
versioning.SetVersion(null, version);
break;
}
}
/* public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSystemd()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<ServiceWorker>();
});
*/
}
}

View File

@ -0,0 +1,16 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
namespace ln.build
{
public class ServiceWorker : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
throw new System.NotImplementedException();
}
}
}

View File

@ -1,29 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<Version>0.4.1</Version>
<Authors>Harald Wolff-Thobaben</Authors>
<Company>l--n.de</Company>
<Description>A simple build server scheduling builds triggered via web-hooks</Description>
<Copyright>(c) 2020 Harald Wolff-Thobaben</Copyright>
<PackageTags>build build-server</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ln.application" Version="0.1.1" />
<PackageReference Include="ln.http" Version="0.1.2" />
<PackageReference Include="ln.json" Version="1.0.0" />
<PackageReference Include="ln.logging" Version="1.0.1" />
<PackageReference Include="ln.threading" Version="0.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../ln.build/ln.build.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PackageVersion>0.4.4</PackageVersion>
</PropertyGroup>
<PropertyGroup>
<Version>0.4.4-ci</Version>
<Authors>Harald Wolff-Thobaben</Authors>
<Company>l--n.de</Company>
<Description>A simple build server scheduling builds triggered via web-hooks</Description>
<Copyright>(c) 2020 Harald Wolff-Thobaben</Copyright>
<PackageTags>build build-server</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ln.application" Version="0.1.*" />
<PackageReference Include="ln.http" Version="0.1.*" />
<PackageReference Include="ln.json" Version="1.0.*" />
<PackageReference Include="ln.logging" Version="1.0.*" />
<PackageReference Include="ln.threading" Version="0.1.*" />
<PackageReference Include="ln.type" Version="0.1.*" />
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../ln.build/ln.build.csproj" />
</ItemGroup>
</Project>

View File

@ -1,20 +1,22 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
using ln.build.commands;
using ln.build.pipeline;
using ln.build.repositories;
using ln.build.secrets;
using ln.build.semver;
using ln.json;
using ln.logging;
using ln.threading;
namespace ln.build
{
public delegate void CIJobCompleted(CIJob cIJob);
public class CIJob : PoolJob
{
public event CIJobCompleted OnJobCompleted;
public string JobID { get; } = Guid.NewGuid().ToString("N");
public CIService CIService { get; }
public Repository Repository { get; set; }
@ -53,7 +55,7 @@ namespace ln.build
Logger = new Logger(new FileLogger(Path.Combine(ciService.ReportsDirectory, JobID, "build.log")));
Logger.Backends.Add(Logger.ConsoleLogger);
Environment = new CommandEnvironment(){ CIJob = this };
Environment = new CommandEnvironment(){ CIJob = this, WorkingDirectory = WorkingDirectory };
}
public Stream GetLogStream(string name)
@ -115,6 +117,8 @@ namespace ln.build
Logger.Log(LogLevel.ERROR, "CIJob failed at CloneRepository()");
}
OnJobCompleted?.Invoke(this);
Notify();
Cleanup();
}
@ -158,5 +162,30 @@ namespace ln.build
Directory.Delete(WorkingDirectory, true);
}
public void PublishRelease(SemVerLevels releaseLevel)
{
if (BuildState == BuildState.SUCCESS)
{
if (PipeLine.Versioning != null)
{
Versioning versioning = PipeLine.Versioning;
SemVersion version = versioning.GetCurrentVersion(this);
if (version.IsPreRelease)
{
SemVersion releaseVersion = new SemVersion(version);
releaseVersion.PreRelease = null;
versioning.SetVersion(this, releaseVersion);
} else if (version.IsRelease)
{
}
}
}
}
}
}

View File

@ -5,8 +5,11 @@ using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using ln.application;
using ln.build.commands;
using ln.build.pipeline;
using ln.build.repositories;
using ln.build.secrets;
using ln.build.semver;
using ln.http;
using ln.http.router;
using ln.json;
@ -43,9 +46,7 @@ namespace ln.build
TemplateRouter templateRouter;
HTTPServer httpServer;
HashSet<PipeLine> pipelines = new HashSet<PipeLine>();
public IEnumerable<PipeLine> PipeLines => pipelines;
public StageCommandContainer StageCommands { get; } = new StageCommandContainer(null,null);
public CIService()
{
@ -72,6 +73,8 @@ namespace ln.build
Directory.CreateDirectory(ReportsDirectory);
Directory.CreateDirectory(Path.Combine(BaseDirectory, "secrets"));
InitializeStageCommands();
InitializeHttpServer();
}
@ -114,6 +117,21 @@ namespace ln.build
}
}
public void InitializeStageCommands()
{
StageCommands.AddCommand(CoreCommands.ShellCommand, "sh");
StageCommands.AddCommand(DotNetCommand.Prepare, "dotnet", "prepare" );
StageCommands.AddCommand(DotNetCommand.Build, "dotnet", "build" );
StageCommands.AddCommand(DotNetCommand.Test, "dotnet", "test" );
StageCommands.AddCommand(DotNetCommand.Pack, "dotnet", "pack" );
StageCommands.AddCommand(DotNetCommand.Push, "dotnet", "push" );
StageCommands.AddCommand(DotNetCommand.Publish, "dotnet", "publish" );
StageCommands.AddCommand(DeployCommand.Deploy, "deploy" );
StageCommands.AddCommand(DeployCommand.Release, "release" );
}
public string GetJobURL(CIJob job) => string.Format("{0}/builds/{1}", BaseURL, job.JobID);
public void Start()
@ -122,11 +140,6 @@ namespace ln.build
httpServer.Start();
}
public void AddPipeline(PipeLine pipeLine)
{
pipelines.Add(pipeLine);
}
public void AddWebHookHandler(string name, Func<CIService,HttpRequest,HttpResponse> webHookHandler)
{
hookRouter.AddSimpleRoute(String.Format("/{0}", name), (HttpRoutingContext context,HttpRequest request) => webHookHandler(this, request));

View File

@ -62,6 +62,7 @@ namespace ln.build
return httpResponse.StatusCode;
}
public HttpStatusCode Delete(params string[] path) => HttpClient.DeleteAsync(string.Format("{0}/{1}",BaseURL, string.Join('/', path))).Result.StatusCode;
public void Dispose()
{

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.IO;
using ln.http.exceptions;
namespace ln.build
{
public static class PathHelper
{
public static IEnumerable<string> ResolvePattern(string pattern) => ResolvePattern(pattern, Environment.CurrentDirectory);
public static IEnumerable<string> ResolvePattern(string pattern, string start)
{
string[] patternTokens = pattern.Split('/', StringSplitOptions.RemoveEmptyEntries);
List<string> matches = new List<string>();
collect(patternTokens, 0, start, matches);
return matches;
}
static void collect(string[] tokens,int depth,string currentPath, List<string> matches)
{
if (depth < tokens.Length-1)
{
foreach (string dirname in Directory.GetDirectories(currentPath, tokens[depth]))
{
collect(tokens, depth + 1, dirname, matches);
}
} else if (depth == (tokens.Length - 1))
{
foreach (string filename in Directory.GetFiles(currentPath, tokens[depth]))
{
matches.Add(filename);
}
} else
throw new Exception("serious bug");
}
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Jint.Parser.Ast;
using ln.logging;
namespace ln.build.commands
@ -33,9 +34,9 @@ namespace ln.build.commands
TestExitCode = testExitCode;
}
public void AddArgument(Argument argument) => arguments.Add(argument);
public void AddArguments(params Argument[] args) => arguments.AddRange(args);
public CommandRunner AddArgument(Argument argument){ arguments.Add(argument); return this; }
public CommandRunner AddArguments(params Argument[] args) { arguments.AddRange(args); return this; }
public CommandRunner AddArguments(params string[] args) { arguments.AddRange(args.Select((string arg)=>(Argument)arg)); return this; }
public string FindFileInPath(CommandEnvironment environment, string filename)
{
Logger.Log(LogLevel.DEBUG, "Looking up {0} in paths {1}", filename, environment.Get("PATH",""));
@ -116,7 +117,7 @@ namespace ln.build.commands
public class Argument
{
string value;
protected string value;
public bool MaskValue { get; set; }
protected Argument(){ }
@ -142,8 +143,10 @@ namespace ln.build.commands
public Option(string optionArgument, string optionValue) : this(optionArgument, optionValue, false){ }
public Option(string optionArgument, string optionValue, bool maskValue)
{
value = optionValue;
OptionArgument = optionArgument;
GetValue = (e) => optionValue;
GetValue = (e) => value;
MaskValue = maskValue;
}
public Option(string optionArgument, Func<CommandEnvironment,string> getOptionValue) : this(optionArgument, getOptionValue, false) { }

View File

@ -1,30 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<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>
<Copyright>(c) 2020 Harald Wolff-Thobaben</Copyright>
<PackageTags>build build-server</PackageTags>
</PropertyGroup>
<ItemGroup>
<None Update="html/**" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ln.logging" Version="1.0.1" />
<PackageReference Include="ln.threading" Version="0.1.0" />
<PackageReference Include="ln.json" Version="1.0.0" />
<PackageReference Include="ln.http" Version="0.1.2" />
<PackageReference Include="ln.templates" Version="0.1.1" />
<PackageReference Include="ln.templates.http" Version="0.0.1-test" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PackageVersion>0.4.4</PackageVersion>
</PropertyGroup>
<PropertyGroup>
<Version>0.4.4-ci</Version>
<Authors>Harald Wolff-Thobaben</Authors>
<Company>l--n.de</Company>
<Description>A simple build server scheduling builds triggered via web-hooks</Description>
<Copyright>(c) 2020 Harald Wolff-Thobaben</Copyright>
<PackageTags>build build-server</PackageTags>
</PropertyGroup>
<ItemGroup>
<None Update="html/**" CopyToOutputDirectory="PreserveNewest" />
<None Update="scripts/**" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ln.logging" Version="1.0.*" />
<PackageReference Include="ln.threading" Version="0.1.*" />
<PackageReference Include="ln.json" Version="1.0.*" />
<PackageReference Include="ln.http" Version="0.1.*" />
<PackageReference Include="ln.templates" Version="0.1.*" />
<PackageReference Include="ln.templates.http" Version="0.0.*" />
<PackageReference Include="ln.type" Version="0.1.*" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,28 @@
using System;
using System.IO;
using System.Text;
using ln.build.commands;
using ln.logging;
namespace ln.build.pipeline
{
public static class CoreCommands
{
public static void ShellCommand(Stage stage,params string[] arguments)
{
CommandRunner commandRunner = new CommandRunner("/bin/bash", "-c", string.Format("\"{0}\"", string.Join(' ', arguments))) { Throw = CRThrow.NEVER, };
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));
return;
}
}
}

View File

@ -1,5 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using ln.build.commands;
using ln.build.semver;
using ln.json;
using ln.logging;
@ -14,6 +19,11 @@ namespace ln.build.pipeline
List<Stage> stages = new List<Stage>();
public IEnumerable<Stage> Stages => stages;
public Versioning Versioning { get; set; }
List<string> loadedTemplates = new List<string>();
public IEnumerable<string> LoadedTemplates => loadedTemplates;
public DefaultPipeLine(CIService ciService)
{
CIService = ciService;
@ -27,6 +37,29 @@ namespace ln.build.pipeline
public void LoadJson(JSONObject jsonPipeLine)
{
if (jsonPipeLine.ContainsKey("templates"))
{
foreach (JSONString jsonTemplateName in jsonPipeLine["templates"].Children)
{
if (!loadedTemplates.Contains(jsonTemplateName.Value))
{
loadedTemplates.Add(jsonTemplateName.Value);
string repoTemplate = Path.Combine(CommandEnvironment.WorkingDirectory, String.Format("{0}.ln", jsonTemplateName.Value));
string systemTemplate = Path.Combine(CIService.ContextDirectory, "scripts", "pipeline", String.Format("{0}.ln", jsonTemplateName.Value));
if (File.Exists(repoTemplate))
{
LoadJson(JSONParser.ParseFile(repoTemplate) as JSONObject);
} else if (File.Exists(systemTemplate))
{
LoadJson(JSONParser.ParseFile(systemTemplate) as JSONObject);
} else
throw new FileNotFoundException(String.Format("{0}.ln", jsonTemplateName.Value));
}
}
}
if (jsonPipeLine.ContainsKey("env"))
CommandEnvironment.Apply(jsonPipeLine["env"] as JSONObject);
@ -35,16 +68,19 @@ namespace ln.build.pipeline
JSONArray jsonStages = jsonPipeLine["stages"] as JSONArray;
foreach (JSONObject jsonStage in jsonStages.Children)
{
Stage stage = new Stage(this);
Stage stage = GetStage(jsonStage["name"].ToNative().ToString());
stage.LoadJson(jsonStage);
stages.Add(stage);
}
}
if (jsonPipeLine.ContainsKey("versioning"))
Versioning = new Versioning(jsonPipeLine["versioning"] as JSONObject);
}
public void Run()
{
stages.Sort((a,b)=>a.Priority-b.Priority);
foreach (Stage stage in stages)
{
CommandEnvironment.Logger.Log(LogLevel.INFO,"-------------------------------------------------------------------------------------");
@ -54,6 +90,19 @@ namespace ln.build.pipeline
}
}
public Stage GetStage(string stageName)
{
foreach (Stage stage in stages)
{
if (stageName.Equals(stage.Name))
return stage;
}
Stage _stage = new Stage(this);
_stage.Name = stageName;
stages.Add(_stage);
return _stage;
}
}
@ -61,11 +110,11 @@ namespace ln.build.pipeline
{
public DefaultPipeLine PipeLine { get; }
public string Name { get; set; }
public int Priority { get; set; }
public CommandEnvironment CommandEnvironment { get; }
List<StageCommand> commands = new List<StageCommand>();
public IEnumerable<StageCommand> Commands => commands;
public List<string> commands = new List<string>();
public Stage(DefaultPipeLine pipeLine)
{
@ -76,6 +125,8 @@ namespace ln.build.pipeline
public void LoadJson(JSONObject jsonStage)
{
Name = jsonStage["name"].ToNative().ToString();
if (jsonStage.ContainsKey("priority"))
Priority = (int)(long)jsonStage["priority"].ToNative();
if (jsonStage.ContainsKey("env"))
{
@ -86,7 +137,8 @@ namespace ln.build.pipeline
JSONArray jsonCommands = jsonStage["commands"] as JSONArray;
foreach (JSONValue jsonValue in jsonCommands.Children)
{
commands.Add(StageCommand.Create(jsonValue.ToNative().ToString()));
Logging.Log(LogLevel.DEBUG, "stage {0} command: {1}", Name, jsonValue.ToNative().ToString());
commands.Add(jsonValue.ToNative().ToString());
}
}
if (jsonStage.ContainsKey("secrets"))
@ -96,15 +148,14 @@ namespace ln.build.pipeline
{
CommandEnvironment.Set(key, CommandEnvironment.SecretStorage?.GetSecret(jsonSecrets[key]?.ToNative()?.ToString()));
}
}
}
public void Run()
{
foreach (StageCommand command in commands)
command.Run(this);
foreach (string command in commands)
PipeLine.CIService.StageCommands.Run(this, command.Split());
}
}

View File

@ -0,0 +1,94 @@
using System;
using System.IO;
using ln.build.repositories;
using ln.build.semver;
using ln.logging;
namespace ln.build.pipeline
{
public static class DeployCommand
{
public static void Deploy(Stage stage,params string[] arguments)
{
stage.CommandEnvironment.Logger.Log(LogLevel.WARNING, "stage command: deploy not yet implemented");
}
public static void Release(Stage stage,params string[] arguments)
{
if (stage.CommandEnvironment.CIJob.Repository is Repository repository)
{
SemVersion releaseVersion = (SemVersion)stage.CommandEnvironment.Get("RELEASE_VERSION");
if (releaseVersion?.IsRelease ?? false)
{
Release release = repository.GetRelease(releaseVersion.ToString());
if (release != null)
{
stage.CommandEnvironment.Logger.Log(LogLevel.WARNING, "source repository already has release {0}", releaseVersion);
} else
{
string releaseBody = String.Format("automatic release by ln.build.server");
string release_ref = stage.CommandEnvironment.Get("REPO_REF");
if (release_ref == null)
{
stage.CommandEnvironment.Logger.Log(LogLevel.WARNING,"release: no source repository reference found. can't create release!");
} else
{
release = repository.CreateRelease(releaseVersion, String.Format("CI Release {0}", releaseVersion.ToString()), releaseBody, release_ref);
foreach (string artefact in stage.CommandEnvironment.Get("RELEASE_ARTEFACTS","").Split(':'))
{
release.CreateOrReplaceAttachment(artefact,Path.GetFileName(artefact));
}
}
}
}
} else
{
stage.CommandEnvironment.Logger.Log(LogLevel.WARNING, "release: no repository interface attached to CIJob");
}
}
/*
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);
Attachment attachment = release.CreateOrReplaceAttachment( localPath, remoteFileName );
// Cleanup attachments
foreach (Attachment b in release.GetAttachments())
{
if (!b.Equals(attachment) && b.Name.Equals(attachment.Name))
b.Delete();
}
}
} 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

@ -1,26 +0,0 @@
using ln.build.pipeline;
namespace ln.build.commands
{
public class DotNetCommand : StageCommand
{
public DotNetCommand(string arguments) :base("DOTNET"){
}
public void Analyze(Stage stage)
{
}
public override void Run(Stage stage)
{
}
}
}

View File

@ -0,0 +1,225 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ln.build.pipeline;
using ln.build.semver;
using ln.build.support.dotnet;
using ln.logging;
using ln.type;
namespace ln.build.commands
{
public static class DotNetCommand
{
public static void Prepare(Stage stage,params string[] arguments)
{
if (Directory.Exists(Path.Combine(stage.CommandEnvironment.WorkingDirectory, ".build")))
Directory.Delete(Path.Combine(stage.CommandEnvironment.WorkingDirectory, ".build"), true);
List<string> projectFiles = new List<string>();
List<string> slnFiles = new List<string>();
foreach (string argument in arguments)
{
foreach (string filename in PathHelper.ResolvePattern(argument, stage.CommandEnvironment.WorkingDirectory))
{
stage.CommandEnvironment.Logger.Log(LogLevel.INFO, "dotnet prepare: found {0}", filename);
if (filename.EndsWith(".csproj",StringComparison.InvariantCultureIgnoreCase))
projectFiles.Add(filename);
else
slnFiles.Add(filename);
}
}
// ToDo: Parse .sln files for referenced projects
SemVersion highestVersion = new SemVersion(0,0,0);
foreach (string projectFileName in projectFiles)
{
CSProjHelper csp = new CSProjHelper(projectFileName);
string projectName = csp.GetName();
SemVersion projectVersion= csp.GetVersion();
string ot = csp.GetOutputType();
stage.CommandEnvironment.Logger.Log(LogLevel.INFO, "dotnet prepare: project {0} version={1} type={2}", projectName, projectVersion, ot);
if (projectVersion == null)
continue;
if (projectVersion > highestVersion)
highestVersion = projectVersion;
stage.PipeLine.CommandEnvironment.Extend("DOTNET_PROJECTS", projectFileName);
}
stage.PipeLine.CommandEnvironment.Set("RELEASE_VERSION", highestVersion.ToString());
}
public static void Build(Stage stage,params string[] arguments)
{
foreach (string projectFileName in stage.CommandEnvironment.Get("DOTNET_PROJECTS","").Split(':'))
{
new CommandRunner("dotnet","build", projectFileName, new CommandRunner.Option("-c", stage.CommandEnvironment.Get("DOTNET_CONFIGURATION")))
.Run(stage.CommandEnvironment);
}
}
public static void Pack(Stage stage,params string[] arguments)
{
foreach (string projectFileName in stage.CommandEnvironment.Get("DOTNET_PROJECTS","").Split(':'))
{
CSProjHelper csProject = new CSProjHelper(projectFileName);
if (!csProject.GetOutputType().Equals("Library"))
{
stage.CommandEnvironment.Logger.Log(LogLevel.WARNING, "dotnet pack: not packing {0} [{1}]",projectFileName, csProject.GetOutputType());
continue;
}
stage.CommandEnvironment.Logger.Log(LogLevel.WARNING, "dotnet pack: packing now {0} [{1}]",projectFileName, csProject.GetOutputType());
new CommandRunner("dotnet","pack", projectFileName)
.AddArgument(new CommandRunner.Option("-c", stage.CommandEnvironment.Get("DOTNET_CONFIGURATION")))
.AddArguments("-o", Path.Combine(stage.CommandEnvironment.WorkingDirectory, ".build/"))
.Run(stage.CommandEnvironment);
string artefact = String.Format(Path.Combine(stage.CommandEnvironment.WorkingDirectory, ".build","{0}.{1}.nupkg"),csProject.GetName(), csProject.GetVersion());
if (!File.Exists(artefact))
throw new FileNotFoundException(artefact);
stage.PipeLine.CommandEnvironment.Extend("DOTNET_ARTEFACTS", artefact);
}
}
public static void Publish(Stage stage,params string[] arguments)
{
List<string> projectFileNames = new List<string>();
List<string> artefacts = new List<string>();
if (arguments.Length == 0)
projectFileNames.AddRange(stage.CommandEnvironment.Get("DOTNET_PROJECTS","").Split(':'));
else
projectFileNames.AddRange(arguments);
foreach (string projectFileName in projectFileNames)
{
CSProjHelper csProject = new CSProjHelper(projectFileName);
if (!csProject.GetOutputType().Equals("Exe"))
{
stage.CommandEnvironment.Logger.Log(LogLevel.WARNING, "dotnet publish: not publishing {0} [{1}]",projectFileName, csProject.GetOutputType());
continue;
}
stage.CommandEnvironment.Logger.Log(LogLevel.WARNING, "dotnet publish: publishing now {0} [{1}]",projectFileName, csProject.GetOutputType());
CommandRunner cr = new CommandRunner("dotnet","publish", projectFileName)
.AddArgument(new CommandRunner.Option("-c", stage.CommandEnvironment.Get("DOTNET_CONFIGURATION")))
.AddArguments("-p:PublishTrimmed=true","-p:PublishSingleFile=true","-p:PublishReadyToRun=false");
CommandRunner.Option outputOption = new CommandRunner.Option("-o", Path.Combine(stage.CommandEnvironment.WorkingDirectory, ".build"));
cr.AddArgument(outputOption);
string[] rids = stage.CommandEnvironment.Get("DOTNET_RIDS","").Split(':');
stage.CommandEnvironment.Logger.Log(LogLevel.INFO,"dotnet publish: using rids: {0}", string.Join(' ', rids));
if (rids.Length == 0)
{
cr.Run(stage.CommandEnvironment);
} else
{
CommandRunner.Option ridOption = new CommandRunner.Option("-r", "");
cr.AddArgument(ridOption);
cr.AddArgument("--self-contained");
foreach (string rid in rids)
{
ridOption.SetValue(rid);
outputOption.SetValue(Path.Combine(stage.CommandEnvironment.WorkingDirectory, ".build",rid));
cr.Run(stage.CommandEnvironment);
string artefact = Directory.GetFiles(Path.Combine(stage.CommandEnvironment.WorkingDirectory, ".build",rid)).Where((fn)=>!fn.EndsWith(".pdb")).FirstOrDefault();
if (artefact != null)
{
string ext = Path.GetExtension(artefact);
if (!ext.Equals(".exe"))
ext = "";
string finalName = Path.Combine(stage.CommandEnvironment.WorkingDirectory, ".build", string.Format("{0}-{3}-{1}{2}",csProject.GetName(), rid, ext, csProject.GetVersion()));
File.Move(artefact, finalName);
artefacts.Add(finalName);
stage.PipeLine.CommandEnvironment.Extend("DOTNET_ARTEFACTS", finalName);
Directory.Delete(Path.Combine(stage.CommandEnvironment.WorkingDirectory, ".build",rid), true);
}
}
}
}
stage.CommandEnvironment.Logger.Log(LogLevel.INFO,"dotnet publish: created the following artefacts: {0}", string.Join(' ', artefacts));
}
public static void Test(Stage stage,params string[] arguments)
{
foreach (string projectFileName in stage.CommandEnvironment.Get("DOTNET_PROJECTS","").Split(':'))
{
new CommandRunner("dotnet","test", projectFileName, new CommandRunner.Option("-c", stage.CommandEnvironment.Get("DOTNET_CONFIGURATION")))
.Run(stage.CommandEnvironment);
}
}
public static void Push(Stage stage,params string[] arguments)
{
if (stage.CommandEnvironment.SecretStorage == null)
{
stage.CommandEnvironment.Logger.Log(LogLevel.WARNING,"dotnet push: no SecretStorage available, push operations are going to fail without authorization!");
} else {
stage.CommandEnvironment.Logger.Log(LogLevel.INFO,"dotnet push: using secrets from {0}", stage.CommandEnvironment.SecretStorage.FileName);
}
string[] nupkgList = stage.CommandEnvironment.Get("DOTNET_ARTEFACTS","").Split(':').Where((fn)=>fn.EndsWith(".nupkg")).ToArray();
string[] binaryList = stage.CommandEnvironment.Get("DOTNET_ARTEFACTS","").Split(':').Where((fn)=>!fn.EndsWith(".nupkg")).ToArray();
foreach (string binaryArtefact in binaryList)
{
stage.PipeLine.CommandEnvironment.Extend("RELEASE_ARTEFACTS", binaryArtefact);
}
if (nupkgList.Length > 0)
{
string nugetSource = stage.CommandEnvironment.Get("NUGET_SOURCE");
string nugetApiKey = null;
if (!(stage.CommandEnvironment.SecretStorage?.TryGetSecret(nugetSource, out nugetApiKey) ?? false))
{
URI uriNuget = new URI(nugetSource);
stage.CommandEnvironment.SecretStorage?.TryGetSecret(new URI(uriNuget.Scheme, uriNuget.Authority, "").ToString(), out nugetApiKey);
}
CommandRunner cr = new CommandRunner("dotnet", "nuget", "push");
CommandRunner.Argument argNupkg = new CommandRunner.Argument("");
cr.AddArgument(argNupkg);
CommandRunner.Option optSource = new CommandRunner.Option("-s",nugetSource);
cr.AddArgument(optSource);
CommandRunner.Option optApiKey = new CommandRunner.Option("-k", nugetApiKey);
cr.AddArgument(optApiKey);
foreach (string nupkg in nupkgList)
{
argNupkg.SetValue(nupkg);
cr.Run(stage.CommandEnvironment);
stage.PipeLine.CommandEnvironment.Extend("RELEASE_ARTEFACTS", nupkg);
}
}
}
}
}

View File

@ -1,53 +0,0 @@
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

@ -1,40 +0,0 @@
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

@ -1,48 +0,0 @@
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

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using ln.build.commands;
using ln.type;
namespace ln.build.pipeline
{
public class StageCommandContainer
{
public StageCommandContainer Parent { get; }
public string Name { get; }
public string FullName => Parent?.FullName != null ? string.Format("{0} {1}", Parent.FullName, Name) : Name;
Dictionary<string,Action<Stage,string[]>> commands = new Dictionary<string, Action<Stage,string[]>>();
Dictionary<string,StageCommandContainer> children = new Dictionary<string, StageCommandContainer>();
public StageCommandContainer(StageCommandContainer parent, string name)
{
Parent = parent;
Name = name;
}
public void AddCommand(Action<Stage,string[]> commandAction, string commandPath) => AddCommand(commandAction, commandPath.Split());
public void AddCommand(Action<Stage,string[]> commandAction, params string[] commandPath)
{
if (commandPath.Length == 0)
throw new ArgumentException(nameof(commandPath));
if (commandPath.Length > 1)
{
if (!children.TryGetValue(commandPath[0], out StageCommandContainer childContainer))
{
childContainer = new StageCommandContainer(this, commandPath[0]);
children.Add(commandPath[0], childContainer);
}
childContainer.AddCommand(commandAction, commandPath.Slice(1));
} else {
commands.Add(commandPath[0], commandAction);
}
}
public void Run(Stage stage, params string[] arguments)
{
if (commands.TryGetValue(arguments[0], out Action<Stage,string[]> commandAction))
commandAction(stage, arguments.Slice(1));
else if (children.TryGetValue(arguments[0], out StageCommandContainer childContainer))
childContainer.Run(stage, arguments.Slice(1));
else
throw new ArgumentException(String.Format("command not found: {0}", arguments[0]));
}
}
}

View File

@ -0,0 +1,54 @@
using System;
using System.IO;
using System.Linq;
using ln.build.semver;
using ln.build.semver.provider;
using ln.json;
namespace ln.build.pipeline
{
public class Versioning
{
public string ProviderName { get; set; }
public string[] Sources { get; set; }
public Provider Provider { get; }
public Versioning()
{}
public Versioning(JSONObject jsonVersioning)
{
ProviderName = jsonVersioning["provider"].ToNative().ToString();
if (jsonVersioning.ContainsKey("sources"))
Sources = jsonVersioning["sources"].Children.Select((s) => s.ToNative().ToString()).ToArray();
Provider = Provider.CreateProvider(ProviderName);
}
public Versioning(string providerName)
{
ProviderName = providerName;
Provider = Provider.CreateProvider(ProviderName);
}
public SemVersion GetCurrentVersion(CIJob job) => GetVersion(job, Sources[0]);
public SemVersion GetVersion(CIJob job, string source)
{
string sourceFileName = Path.Combine(job?.WorkingDirectory ?? "", source);
return Provider.GetVersion(sourceFileName);
}
public void SetVersion(CIJob job, SemVersion version)
{
foreach (string source in Sources)
SetVersion(job, source, version);
}
public void SetVersion(CIJob job, string source, SemVersion version)
{
string sourceFileName = Path.Combine(job?.WorkingDirectory ?? "", source);
Provider.SetVersion(sourceFileName, version);
}
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Net.Http.Headers;
using System.Runtime;
namespace ln.build.repositories
{
@ -12,31 +13,45 @@ namespace ln.build.repositories
public Boolean IsDraft {get; set; }
public Boolean IsPreRelease {get; set; }
public string TargetCommit { get; set; }
public string Body { get; set; }
public abstract Repository Repository { get; }
public abstract Attachement[] GetAttachements();
public abstract Attachment[] GetAttachments();
public abstract void AddAttachement(string localPath,string remoteFilename);
public abstract Attachment CreateOrReplaceAttachment(string localPath, string remoteFileName);
public class Attachement
public virtual Attachment FindAttachmentByName(string name)
{
public Release Release { get; set; }
public int Id { get; set; } = -1;
public string Name { get; set; }
public Guid UUID { get; set; }
public string DownloadURL { get; set; }
public Attachement(Release release)
{
Release = release;
}
foreach (Attachment attachment in GetAttachments())
if (attachment.Name.Equals(name))
return attachment;
return null;
}
}
public abstract class Attachment
{
public abstract Release Release { get; }
public abstract int Id { get; set; }
public abstract string Name { get; set; }
public abstract string DownloadURL { get; set; }
public Attachment()
{
}
public abstract void Create(string localPath);
public abstract void Delete();
public override bool Equals(object obj) => (obj is Attachment attachment) && Id.Equals(attachment.Id);
public override int GetHashCode() => Id.GetHashCode();
}
}

View File

@ -1,5 +1,6 @@
using System;
using ln.build.semver;
using ln.http;
namespace ln.build.repositories
@ -13,6 +14,12 @@ namespace ln.build.repositories
public abstract Release[] GetReleases();
public abstract Release GetRelease(string tagName);
public abstract Release GetRelease(int id);
public abstract Release CreateRelease(SemVersion releaseVersion, string name, string body, string target_reference);
public abstract void CommitAndPush(string message, string[] addedPaths, string[] modifiedPaths, string[] removedPaths);
}

View File

@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Reflection.Emit;
using ln.json;
using ln.json.attributes;
using ln.type;
namespace ln.build.repositories.gitea
@ -12,11 +15,17 @@ namespace ln.build.repositories.gitea
{
public override Repository Repository => GiteaRepository;
public GiteaRepository GiteaRepository { get; }
public GiteaRelease(GiteaRepository repository)
{
GiteaRepository = repository;
}
public GiteaRelease(GiteaRepository repository,JSONObject jsonRelease) : this(repository)
{
UpdateFromJson(jsonRelease);
}
public void UpdateFromJson(JSONObject jsonRelease)
{
Id = (int)(long)jsonRelease["id"].ToNative();
Name = jsonRelease["name"].ToNative().ToString();
@ -24,25 +33,102 @@ namespace ln.build.repositories.gitea
Body = jsonRelease["body"].ToNative().ToString();
IsDraft = (bool)jsonRelease["draft"].ToNative();
IsPreRelease = (bool)jsonRelease["prerelease"].ToNative();
TargetCommit = jsonRelease["target_commitish"].ToNative().ToString();
}
public override void AddAttachement(string localPath, string remoteFileName)
public JSONObject ToJson()
{
JSONObject jsonRelease = new JSONObject();
jsonRelease.Add("name", Name);
jsonRelease.Add("tag_name", TagName);
jsonRelease.Add("target_comittish", TargetCommit);
jsonRelease.Add("prerelease",IsPreRelease);
jsonRelease.Add("draft",IsDraft);
jsonRelease.Add("body",Body);
return jsonRelease;
}
public override Attachment CreateOrReplaceAttachment(string localPath, string remoteFileName)
{
Attachment attachment = FindAttachmentByName(remoteFileName) ?? new GiteaAttachment(this){ Name = remoteFileName, };
attachment.Create(localPath);
return attachment;
}
public override Attachment[] GetAttachments()
{
if (HttpStatusCode.OK == GiteaRepository.Client.GetJson(out JSONValue jsonAssets, "repos", GiteaRepository.Owner, GiteaRepository.Name, "releases", Id.ToString(), "assets" ))
{
List<Attachment> attachments = new List<Attachment>();
foreach (JSONObject jsonAsset in (jsonAssets as JSONArray).Children)
attachments.Add(new GiteaAttachment(this, jsonAsset));
return attachments.ToArray();
}
return null;
}
}
public class GiteaAttachment : Attachment
{
public override Release Release => GiteaRelease;
public GiteaRelease GiteaRelease { get; }
int id = -1;
string downloadURL;
public override int Id { get => id; set => throw new NotImplementedException(); }
public override string Name { get; set; }
public override string DownloadURL { get => downloadURL; set => throw new NotImplementedException(); }
public GiteaAttachment(GiteaRelease giteaRelease)
{
GiteaRelease = giteaRelease;
}
public GiteaAttachment(GiteaRelease giteaRelease,JSONObject jsonAttachment) :this(giteaRelease)
{
LoadJson(jsonAttachment);
}
private void LoadJson(JSONObject jsonAttachment)
{
id = (int)(long)jsonAttachment["id"].ToNative();
Name = jsonAttachment["name"].ToNative().ToString();
downloadURL = jsonAttachment["browser_download_url"].ToNative().ToString();
}
public override void Delete()
{
if (id != -1)
{
if (HttpStatusCode.NoContent != GiteaRelease.GiteaRepository.Client.Delete("repos", GiteaRelease.GiteaRepository.Owner, GiteaRelease.GiteaRepository.Name, "releases", GiteaRelease.Id.ToString(), "assets", id.ToString()))
throw new Exception(String.Format("could not delete attachment {0}/{1}", GiteaRelease.Id, id));
id = -1;
}
}
public override void Create(string localPath)
{
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)))
formDataContent.Add(attachmentBytes, "attachment", Name);
if (HttpStatusCode.Created != GiteaRelease.GiteaRepository.Client.PostContent(formDataContent,out JSONValue response, "repos", GiteaRelease.GiteaRepository.Owner, GiteaRelease.GiteaRepository.Name, "releases", GiteaRelease.Id.ToString(), string.Format("assets?name={0}", Name)))
{
throw new Exception(String.Format("could not create attachment to release: {0}", localPath));
}
if (id != -1)
Delete();
LoadJson(response as JSONObject);
}
}
public override Attachement[] GetAttachements()
{
throw new System.NotImplementedException();
}
}
}

View File

@ -1,11 +1,17 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using ln.build.commands;
using ln.build.pipeline;
using ln.build.secrets;
using ln.build.semver;
using ln.http;
using ln.json;
using ln.logging;
using ln.threading;
using ln.type;
namespace ln.build.repositories.gitea
{
@ -20,6 +26,8 @@ namespace ln.build.repositories.gitea
public JsonApiClient Client { get; }
string AccessToken { get; set; }
public GiteaRepository(string baseURL, string owner, string name)
:base(string.Format("{0}/{1}/{2}.git",baseURL, owner, name))
{
@ -34,6 +42,7 @@ namespace ln.build.repositories.gitea
public GiteaRepository(string baseURL, string owner, string name, string accessToken)
:this(baseURL, owner, name)
{
AccessToken = accessToken;
if (accessToken != null)
Client.HttpClient.DefaultRequestHeaders.Add("Authorization",String.Format("token {0}", accessToken));
}
@ -79,7 +88,11 @@ namespace ln.build.repositories.gitea
public override Release GetRelease(string tagName)
{
throw new NotImplementedException();
if (HttpStatusCode.OK == Client.GetJson(out JSONValue jsonRelease, "repos", Owner, Name, "releases", "tags", tagName))
{
return new GiteaRelease(this, jsonRelease as JSONObject);
}
return null;
}
public override Release GetRelease(int releaseId)
{
@ -90,9 +103,40 @@ namespace ln.build.repositories.gitea
return null;
}
public override Release CreateRelease(SemVersion releaseVersion, string name, string body, string target_reference)
{
GiteaRelease giteaRelease = new GiteaRelease(this){ TagName = releaseVersion.ToString(), Name = name, Body = body, TargetCommit = target_reference };
if (HttpStatusCode.Created != Client.PostJson(giteaRelease.ToJson(), out JSONValue jsonRelease, "repos", Owner, Name, "releases"))
throw new Exception(string.Format("release could not be created => {0}", giteaRelease.ToJson()));
giteaRelease.UpdateFromJson(jsonRelease as JSONObject);
return giteaRelease;
}
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);
public override void CommitAndPush(string message, string[] addedPaths, string[] modifiedPaths, string[] removedPaths)
{
CommandRunner cr = new CommandRunner("git","add");
cr.AddArguments(addedPaths);
cr.AddArguments(modifiedPaths);
cr.Run();
cr = new CommandRunner("git","rm");
cr.AddArguments(removedPaths);
cr.Run();
cr = new CommandRunner("git","commit", "-m", message);
cr.Run();
URI baseUri = new URI(BaseURL);
if (!AccessToken?.Equals(String.Empty) ?? false)
baseUri = baseUri.WithUserInfo(AccessToken);
cr = new CommandRunner("git","push", baseUri.ToString(true));
cr.Run();
}
public static HttpResponse WebHookHandler(CIService ciService, HttpRequest request)
@ -135,6 +179,7 @@ namespace ln.build.repositories.gitea
string eventType = request.GetRequestHeader("X-GITEA-EVENT");
List<string> buildRefs = new List<string>();
JSONObject jsonCommit = null;
switch (eventType)
{
@ -145,8 +190,16 @@ namespace ln.build.repositories.gitea
buildRefs.Add(message["release"]["tag_name"].ToNative().ToString());
break;
case "push":
foreach (JSONObject jsonCommit in (message["commits"] as JSONArray).Children)
buildRefs.Add(jsonCommit["id"].ToNative().ToString());
string currentCommitId = message["after"].ToNative().ToString();
foreach (JSONObject _jsonCommit in (message["commits"] as JSONArray).Children)
{
if (currentCommitId.Equals(_jsonCommit["id"].ToNative().ToString()))
{
buildRefs.Add(_jsonCommit["id"].ToNative().ToString());
jsonCommit = _jsonCommit;
break;
}
}
break;
default:
Logging.Log(LogLevel.WARNING, "received webhook with unsupported event type [{0}]", eventType);
@ -162,6 +215,7 @@ namespace ln.build.repositories.gitea
.SetVariable("REPO_OWNER", owner)
.SetVariable("REPO_NAME", repoName)
.SetVariable("REPO_REF", _ref)
.SetVariable("REPO_BASE", giteaRepository.BaseURL)
// .SetVariable("NOTIFY", message["pusher"]["email"].ToNative().ToString())
;
ciJob.Environment.SecretStorage = secretStorage;
@ -173,6 +227,16 @@ namespace ln.build.repositories.gitea
ciJob.SetVariable("RELEASE_ID", message["release"]["id"].ToNative().ToString());
break;
case "push":
if (jsonCommit != null)
{
string commitMessage = jsonCommit["message"].ToNative().ToString();
if (commitMessage.Contains("#ReleaseMajor"))
ciJob.OnJobCompleted += (job) => job.PublishRelease(SemVerLevels.MAJOR);
else if (commitMessage.Contains("#ReleaseMinor"))
ciJob.OnJobCompleted += (job) => job.PublishRelease(SemVerLevels.MINOR);
else if (commitMessage.Contains("#ReleasePatch"))
ciJob.OnJobCompleted += (job) => job.PublishRelease(SemVerLevels.PATCH);
}
break;
}

View File

@ -0,0 +1,46 @@
{
"env": {
"CONFIGURATION": "Release",
"DOTNET_RIDS": "linux-x64:win-x64:osx-x64"
},
"stages": [
{
"name": "prepare",
"priority": 100,
"commands": [
"dotnet prepare *.sln *.csproj"
]
},
{
"name": "build",
"priority": 300,
"commands": [
"dotnet build"
]
},
{
"name": "test",
"priority": 500,
"commands": [
"dotnet test"
]
},
{
"name": "pack",
"priority": 700,
"commands": [
"dotnet pack",
"dotnet publish"
]
},
{
"name": "push",
"priority": 1000,
"commands": [
"dotnet push",
"release",
"deploy"
]
}
]
}

View File

@ -28,6 +28,7 @@ namespace ln.build.secrets
JSONObject secretsObject = JSONParser.ParseFile(FileName) as JSONObject;
foreach (string key in secretsObject.Keys)
{
//Logging.Log(LogLevel.INFO, "loading secret {0}", key);
secrets.Add(key, secretsObject[key].ToNative().ToString());
}
}
@ -36,9 +37,15 @@ namespace ln.build.secrets
public string GetSecret(string key)
{
//Logging.Log(LogLevel.INFO, "trying to fetch secret for: [{0}]", key);
TryGetSecret(key, out string secret);
return secret;
}
public bool TryGetSecret(string key, out string secret) => secrets.TryGetValue(key, out secret);
public bool TryGetSecret(string key, out string secret){
//Logging.Log(LogLevel.INFO, "trying to fetch secret for: [{0}]", key);
bool success = secrets.TryGetValue(key, out secret);
//Logging.Log(LogLevel.INFO, "{1} to fetch secret for: [{0}]", key, success ? "succeded" : "failed");
return success;
}
}
}

View File

@ -0,0 +1,11 @@
namespace ln.build.semver
{
public enum SemVerLevels {
MAJOR,
MINOR,
PATCH,
TAG
}
}

View File

@ -0,0 +1,149 @@
using System;
using System.IO;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Text.RegularExpressions;
using Jint.Parser.Ast;
using ln.build.semver;
using Microsoft.VisualBasic;
namespace ln.build.semver
{
public class SemVersion
{
public int Major { get; set; }
public int Minor { get; set; }
public int Patch { get; set; }
public string PreRelease { get; set; } = String.Empty;
public bool IsPreRelease => !PreRelease?.Equals(string.Empty) ?? false;
public bool IsRelease => PreRelease?.Equals(string.Empty) ?? true;
public SemVersion(int major,int minor,int patch) : this(major, minor, patch, ""){ }
public SemVersion(int major,int minor,int patch,string prerelease)
{
Major = major;
Minor = minor;
Patch = patch;
PreRelease = prerelease;
}
private SemVersion(){}
public SemVersion(SemVersion source)
:this(source.Major, source.Minor, source.Patch, source.PreRelease)
{ }
public void Increment(SemVerLevels level){
switch (level)
{
case SemVerLevels.MAJOR:
Major++;
Minor = 0;
Patch = 0;
break;
case SemVerLevels.MINOR:
Minor++;
Patch = 0;
break;
case SemVerLevels.PATCH:
Patch++;
break;
}
}
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendFormat("{0}.{1}.{2}", Major, Minor, Patch);
if (!PreRelease?.Equals(string.Empty) ?? false)
stringBuilder.AppendFormat("-{0}", PreRelease);
return stringBuilder.ToString();
}
public static Regex rexVersion = new Regex("^(?<major>\\d+).(?<minor>\\d+).(?<patch>\\d+)(-(?<prerelease>.+))?$");
public static SemVersion Parse(string versionString)
{
Match match = rexVersion.Match(versionString);
if (!match.Success)
throw new FormatException(String.Format("{0} is no valid SemVer", versionString));
SemVersion version = new SemVersion();
version.Major = int.Parse(match.Groups["major"].Value);
version.Minor = int.Parse(match.Groups["minor"].Value);
version.Patch = int.Parse(match.Groups["patch"].Value);
if (match.Groups["prerelease"].Success)
version.PreRelease = match.Groups["prerelease"].Value;
return version;
}
public static bool operator <(SemVersion a,SemVersion b)
{
if (a is null || b is null)
return false;
if (a.Major < b.Major)
return true;
if (a.Major > b.Major)
return false;
if (a.Minor < b.Minor)
return true;
if (a.Minor > b.Minor)
return false;
if (a.Patch < b.Patch)
return true;
if (a.Patch > b.Patch)
return false;
if (a.IsPreRelease && b.IsRelease)
return true;
if (a.IsRelease && b.IsPreRelease)
return false;
if (a.IsRelease && b.IsRelease)
return false;
return a.PreRelease.CompareTo(b.PreRelease) > 0;
}
public static bool operator >(SemVersion b,SemVersion a)
{
if (a is null || b is null)
return false;
if (a.Major < b.Major)
return true;
if (a.Major > b.Major)
return false;
if (a.Minor < b.Minor)
return true;
if (a.Minor > b.Minor)
return false;
if (a.Patch < b.Patch)
return true;
if (a.Patch > b.Patch)
return false;
if (a.IsPreRelease && b.IsRelease)
return true;
if (a.IsRelease && b.IsPreRelease)
return false;
if (a.IsRelease && b.IsRelease)
return false;
return a.PreRelease.CompareTo(b.PreRelease) > 0;
}
public static explicit operator SemVersion(string versionString) => versionString == null ? null : SemVersion.Parse(versionString);
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Xml;
using ln.build.semver;
namespace ln.build.semver.provider
{
public class DotNetProvider : Provider
{
public DotNetProvider(): base("dotnet")
{}
public override SemVersion GetVersion(string source)
{
XmlDocument projectFile = new XmlDocument();
projectFile.Load(source);
XmlNode nodeVersion = projectFile.SelectSingleNode("Project/PropertyGroup/Version");
return SemVersion.Parse(nodeVersion.InnerText);
}
public override void SetVersion(string source, SemVersion version)
{
XmlDocument projectFile = new XmlDocument();
projectFile.Load(source);
XmlNode nodeVersion = projectFile.SelectSingleNode("Project/PropertyGroup/Version");
nodeVersion.InnerText = version.ToString();
projectFile.Save(source);
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Diagnostics.Contracts;
using System.IO;
namespace ln.build.semver.provider
{
public abstract class Provider
{
public String Name { get; }
public Provider(string providerName)
{
Name = providerName;
}
public abstract SemVersion GetVersion(string source);
public abstract void SetVersion(string source, SemVersion version);
public static Provider CreateProvider(string providerName)
{
switch (providerName)
{
case "dotnet":
return new DotNetProvider();
default:
throw new FileNotFoundException();
}
}
}
}

View File

@ -0,0 +1,66 @@
using System.IO;
using System.Xml;
using ln.build.semver;
namespace ln.build.support.dotnet
{
public class CSProjHelper
{
public string FileName { get; set; }
public CSProjHelper(string filename)
{
FileName = filename;
}
public string GetName()
{
XmlDocument projectFile = new XmlDocument();
projectFile.Load(FileName);
XmlNode nodeVersion = projectFile.SelectSingleNode("Project/PropertyGroup/AssemblyName");
return nodeVersion?.InnerText ?? Path.GetFileNameWithoutExtension(FileName);
}
public SemVersion GetVersion()
{
XmlDocument projectFile = new XmlDocument();
projectFile.Load(FileName);
XmlNode nodeVersion = projectFile.SelectSingleNode("Project/PropertyGroup/PackageVersion") ?? projectFile.SelectSingleNode("Project/PropertyGroup/Version");
return (nodeVersion == null) ? null : SemVersion.Parse(nodeVersion.InnerText);
}
public void SetVersion(SemVersion version)
{
XmlDocument projectFile = new XmlDocument();
projectFile.Load(FileName);
XmlNode nodeVersion = projectFile.SelectSingleNode("Project/PropertyGroup/Version");
nodeVersion.InnerText = version.ToString();
projectFile.Save(FileName);
}
public bool IsPackable()
{
XmlDocument projectFile = new XmlDocument();
projectFile.Load(FileName);
XmlNode nodePackable = projectFile.SelectSingleNode("Project/PropertyGroup/IsPackable");
return bool.Parse(nodePackable?.InnerText ?? "true");
}
public string GetOutputType()
{
XmlDocument projectFile = new XmlDocument();
projectFile.Load(FileName);
XmlNode nodeVersion = projectFile.SelectSingleNode("Project/PropertyGroup/OutputType");
return nodeVersion?.InnerText ?? "Library";
}
}
}