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 { public class GiteaRepository : GitRepository { public string BaseURL { get; } public string Owner { get; } public string Name { get; } public SecretStorage SecretStorage { get; set; } 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)) { Logging.Log(LogLevel.DEBUG, "new GiteaRepository({0},{1},{2})", baseURL, owner, name); BaseURL = baseURL; Owner = owner; Name = name; Client = new JsonApiClient(string.Format("{0}/api/v1",baseURL)); } 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)); } public override void UpdateBuildState(CIJob job) { JSONObject stateObject = new JSONObject(); stateObject.Add("context", string.Format("ln.build - {0}", job.CIService.HostName)); stateObject.Add("description", "build job pending"); stateObject.Add("state", job.BuildState.ToString().ToLower()); stateObject.Add("target_url", job.CIService.GetJobURL(job)); if (HttpStatusCode.Created != Client.PostJson( stateObject, out JSONValue response, "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); } } public override Release[] GetReleases() { if (HttpStatusCode.OK == Client.GetJson( out JSONValue jsonReleases, "repos", Owner, Name, "releases" )) { List releases = new List(); foreach (JSONObject jsonRelease in jsonReleases.Children) { GiteaRelease giteaRelease = new GiteaRelease(this, jsonRelease); releases.Add(giteaRelease); } return releases.ToArray(); } return new Release[0]; } public override Release GetRelease(string tagName) { 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, "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) { HttpResponse response = new HttpResponse(request); if (!request.Method.Equals("POST")) { response.StatusCode = 405; } else if (!request.GetRequestHeader("content-type").Equals("application/json")) { response.StatusCode = 415; response.ContentWriter.WriteLine("Unsupported Media Type, should be application/json"); } else { JSONValue jsonRequest = JSONParser.Parse(request.ContentReader.ReadToEnd()); if (jsonRequest is JSONObject message) { try { string hookSecret = message["secret"].ToNative().ToString(); string owner = message["repository"]["owner"]["username"].ToNative().ToString(); string repoName = message["repository"]["name"].ToNative().ToString(); string htmlUrl = message["repository"]["html_url"].ToNative().ToString(); string repoPath = String.Format("/{0}/{1}", owner, repoName); if (!htmlUrl.EndsWith(repoPath)) { Logging.Log(LogLevel.WARNING, "unable to detect gitea base url from [{0}] for ({1}/{2})", htmlUrl, owner, repoName); response.StatusCode = 500; return response; } string giteaBaseUrl = htmlUrl.Remove(htmlUrl.Length - repoPath.Length); SecretStorage secretStorage = ciService.GetSecretStorage(hookSecret); GiteaRepository giteaRepository = new GiteaRepository(giteaBaseUrl, owner, repoName, secretStorage.GetSecret(giteaBaseUrl)){ SecretStorage = secretStorage, }; string eventType = request.GetRequestHeader("X-GITEA-EVENT"); List buildRefs = new List(); JSONObject jsonCommit = null; switch (eventType) { case "release": if (!message["action"].ToNative().ToString().Equals("published")) return response; buildRefs.Add(message["release"]["tag_name"].ToNative().ToString()); break; case "push": 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); return response; } foreach (string _ref in buildRefs) { if (giteaRepository.ContainsFile("build.ln", _ref)) { CIJob ciJob = new CIJob(ciService, giteaRepository) .SetVariable("REPO_EVENT", eventType) .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; 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": 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; } ciJob.UpdateBuildState(BuildState.PENDING); ciService.Enqueue(ciJob); } } } catch (Exception e) { response.StatusCode = 500; response.StatusMessage = "An exception occured"; response.ContentWriter.WriteLine("{0}", e.ToString()); Logging.Log(e); } } else { response.StatusCode = 400; } } return response; } } }