PageRenderTime 56ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/Plugins/BuildServerIntegration/JenkinsIntegration/JenkinsAdapter.cs

https://gitlab.com/Rockyspade/gitextensions
C# | 365 lines | 307 code | 53 blank | 5 comment | 43 complexity | 5912dad88e15524e07a8e543a82b98fa MD5 | raw file
Possible License(s): GPL-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.Composition;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Net.Http;
  9. using System.Net.Http.Headers;
  10. using System.Reactive.Concurrency;
  11. using System.Reactive.Linq;
  12. using System.Text;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. using GitCommands.Settings;
  16. using GitCommands.Utils;
  17. using GitUIPluginInterfaces;
  18. using GitUIPluginInterfaces.BuildServerIntegration;
  19. using Newtonsoft.Json.Linq;
  20. namespace JenkinsIntegration
  21. {
  22. [MetadataAttribute]
  23. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
  24. public class JenkinsIntegrationMetadata : BuildServerAdapterMetadataAttribute
  25. {
  26. public JenkinsIntegrationMetadata(string buildServerType)
  27. : base(buildServerType) { }
  28. public override string CanBeLoaded
  29. {
  30. get
  31. {
  32. if (EnvUtils.IsNet4FullOrHigher())
  33. return null;
  34. return ".Net 4 full framework required";
  35. }
  36. }
  37. }
  38. [Export(typeof(IBuildServerAdapter))]
  39. [JenkinsIntegrationMetadata("Jenkins")]
  40. [PartCreationPolicy(CreationPolicy.NonShared)]
  41. internal class JenkinsAdapter : IBuildServerAdapter
  42. {
  43. private IBuildServerWatcher _buildServerWatcher;
  44. private HttpClient _httpClient;
  45. private IList<Task<IEnumerable<string>>> _getBuildUrls;
  46. public void Initialize(IBuildServerWatcher buildServerWatcher, ISettingsSource config)
  47. {
  48. if (_buildServerWatcher != null)
  49. throw new InvalidOperationException("Already initialized");
  50. _buildServerWatcher = buildServerWatcher;
  51. var projectName = config.GetString("ProjectName", null);
  52. var hostName = config.GetString("BuildServerUrl", null);
  53. if (!string.IsNullOrEmpty(hostName) && !string.IsNullOrEmpty(projectName))
  54. {
  55. var baseAdress = hostName.Contains("://")
  56. ? new Uri(hostName, UriKind.Absolute)
  57. : new Uri(string.Format("{0}://{1}:8080", Uri.UriSchemeHttp, hostName), UriKind.Absolute);
  58. _httpClient = new HttpClient
  59. {
  60. Timeout = TimeSpan.FromMinutes(2),
  61. BaseAddress = baseAdress
  62. };
  63. var buildServerCredentials = buildServerWatcher.GetBuildServerCredentials(this, true);
  64. UpdateHttpClientOptions(buildServerCredentials);
  65. _getBuildUrls = new List<Task<IEnumerable<string>>>();
  66. string[] projectUrls = projectName.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
  67. foreach (var projectUrl in projectUrls.Select(s => baseAdress + "job/" + s.Trim() + "/"))
  68. {
  69. AddGetBuildUrl(projectUrl);
  70. }
  71. }
  72. }
  73. private void AddGetBuildUrl(string projectUrl)
  74. {
  75. _getBuildUrls.Add(GetResponseAsync(FormatToGetJson(projectUrl), CancellationToken.None)
  76. .ContinueWith(
  77. task =>
  78. {
  79. JObject jobDescription = JObject.Parse(task.Result);
  80. return jobDescription["builds"].Select(b => b["url"].ToObject<string>());
  81. },
  82. TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.AttachedToParent));
  83. }
  84. /// <summary>
  85. /// Gets a unique key which identifies this build server.
  86. /// </summary>
  87. public string UniqueKey
  88. {
  89. get { return _httpClient.BaseAddress.Host; }
  90. }
  91. public IObservable<BuildInfo> GetFinishedBuildsSince(IScheduler scheduler, DateTime? sinceDate = null)
  92. {
  93. return GetBuilds(scheduler, sinceDate, false);
  94. }
  95. public IObservable<BuildInfo> GetRunningBuilds(IScheduler scheduler)
  96. {
  97. return GetBuilds(scheduler, null, true);
  98. }
  99. public IObservable<BuildInfo> GetBuilds(IScheduler scheduler, DateTime? sinceDate = null, bool? running = null)
  100. {
  101. if (_getBuildUrls == null || _getBuildUrls.Count() == 0)
  102. {
  103. return Observable.Empty<BuildInfo>(scheduler);
  104. }
  105. return Observable.Create<BuildInfo>((observer, cancellationToken) =>
  106. Task<IDisposable>.Factory.StartNew(
  107. () => scheduler.Schedule(() => ObserveBuilds(sinceDate, running, observer, cancellationToken))));
  108. }
  109. private void ObserveBuilds(DateTime? sinceDate, bool? running, IObserver<BuildInfo> observer, CancellationToken cancellationToken)
  110. {
  111. try
  112. {
  113. if (_getBuildUrls.All(t => t.IsCanceled))
  114. {
  115. observer.OnCompleted();
  116. return;
  117. }
  118. foreach (var currentGetBuildUrls in _getBuildUrls)
  119. {
  120. if (currentGetBuildUrls.IsFaulted)
  121. {
  122. Debug.Assert(currentGetBuildUrls.Exception != null);
  123. observer.OnError(currentGetBuildUrls.Exception);
  124. continue;
  125. }
  126. var buildContents = currentGetBuildUrls.Result
  127. .Select(buildUrl => GetResponseAsync(FormatToGetJson(buildUrl), cancellationToken).Result)
  128. .Where(s => !string.IsNullOrEmpty(s)).ToArray();
  129. foreach (var buildDetails in buildContents)
  130. {
  131. JObject buildDescription = JObject.Parse(buildDetails);
  132. var startDate = TimestampToDateTime(buildDescription["timestamp"].ToObject<long>());
  133. var isRunning = buildDescription["building"].ToObject<bool>();
  134. if (sinceDate.HasValue && sinceDate.Value > startDate)
  135. continue;
  136. if (running.HasValue && running.Value != isRunning)
  137. continue;
  138. var buildInfo = CreateBuildInfo(buildDescription);
  139. if (buildInfo.CommitHashList.Any())
  140. {
  141. observer.OnNext(buildInfo);
  142. }
  143. }
  144. }
  145. }
  146. catch (OperationCanceledException)
  147. {
  148. // Do nothing, the observer is already stopped
  149. }
  150. catch (Exception ex)
  151. {
  152. observer.OnError(ex);
  153. }
  154. }
  155. private static BuildInfo CreateBuildInfo(JObject buildDescription)
  156. {
  157. var idValue = buildDescription["number"].ToObject<string>();
  158. var statusValue = buildDescription["result"].ToObject<string>();
  159. var startDateTicks = buildDescription["timestamp"].ToObject<long>();
  160. var displayName = buildDescription["fullDisplayName"].ToObject<string>();
  161. var webUrl = buildDescription["url"].ToObject<string>();
  162. var action = buildDescription["actions"];
  163. var commitHashList = new List<string>();
  164. int nbTests = 0;
  165. int nbFailedTests = 0;
  166. int nbSkippedTests = 0;
  167. foreach (var element in action)
  168. {
  169. if (element["lastBuiltRevision"] != null)
  170. commitHashList.Add(element["lastBuiltRevision"]["SHA1"].ToObject<string>());
  171. if (element["totalCount"] != null)
  172. {
  173. nbTests = element["totalCount"].ToObject<int>();
  174. nbFailedTests = element["failCount"].ToObject<int>();
  175. nbSkippedTests = element["skipCount"].ToObject<int>();
  176. }
  177. }
  178. string testResults = string.Empty;
  179. if (nbTests != 0)
  180. {
  181. testResults = String.Format(" : {0} tests ( {1} failed, {2} skipped )", nbTests, nbFailedTests, nbSkippedTests);
  182. }
  183. var isRunning = buildDescription["building"].ToObject<bool>();
  184. var status = ParseBuildStatus(statusValue);
  185. var statusText = isRunning ? string.Empty : status.ToString("G");
  186. var buildInfo = new BuildInfo
  187. {
  188. Id = idValue,
  189. StartDate = TimestampToDateTime(startDateTicks),
  190. Status = isRunning ? BuildInfo.BuildStatus.InProgress : status,
  191. Description = displayName + " " + statusText + testResults,
  192. CommitHashList = commitHashList.ToArray(),
  193. Url = webUrl
  194. };
  195. return buildInfo;
  196. }
  197. public static DateTime TimestampToDateTime(long timestamp)
  198. {
  199. return new DateTime(1970, 1, 1, 0, 0, 0, DateTime.Now.Kind).AddMilliseconds(timestamp);
  200. }
  201. private static AuthenticationHeaderValue CreateBasicHeader(string username, string password)
  202. {
  203. byte[] byteArray = Encoding.UTF8.GetBytes(string.Format("{0}:{1}", username, password));
  204. return new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
  205. }
  206. private static BuildInfo.BuildStatus ParseBuildStatus(string statusValue)
  207. {
  208. switch (statusValue)
  209. {
  210. case "SUCCESS":
  211. return BuildInfo.BuildStatus.Success;
  212. case "FAILURE":
  213. return BuildInfo.BuildStatus.Failure;
  214. case "UNSTABLE":
  215. return BuildInfo.BuildStatus.Unstable;
  216. case "ABORTED":
  217. return BuildInfo.BuildStatus.Stopped;
  218. default:
  219. return BuildInfo.BuildStatus.Unknown;
  220. }
  221. }
  222. private Task<Stream> GetStreamAsync(string restServicePath, CancellationToken cancellationToken)
  223. {
  224. cancellationToken.ThrowIfCancellationRequested();
  225. return _httpClient.GetAsync(restServicePath, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
  226. .ContinueWith(
  227. task => GetStreamFromHttpResponseAsync(task, restServicePath, cancellationToken),
  228. cancellationToken,
  229. TaskContinuationOptions.AttachedToParent,
  230. TaskScheduler.Current)
  231. .Unwrap();
  232. }
  233. private Task<Stream> GetStreamFromHttpResponseAsync(Task<HttpResponseMessage> task, string restServicePath, CancellationToken cancellationToken)
  234. {
  235. #if !__MonoCS__
  236. bool retry = task.IsCanceled && !cancellationToken.IsCancellationRequested;
  237. bool unauthorized = task.Status == TaskStatus.RanToCompletion &&
  238. task.Result.StatusCode == HttpStatusCode.Unauthorized;
  239. if (!retry)
  240. {
  241. if (task.Result.IsSuccessStatusCode)
  242. {
  243. var httpContent = task.Result.Content;
  244. if (httpContent.Headers.ContentType.MediaType == "text/html")
  245. {
  246. // Jenkins responds with an HTML login page when guest access is denied.
  247. unauthorized = true;
  248. }
  249. else
  250. {
  251. return httpContent.ReadAsStreamAsync();
  252. }
  253. }
  254. }
  255. if (retry)
  256. {
  257. return GetStreamAsync(restServicePath, cancellationToken);
  258. }
  259. if (unauthorized)
  260. {
  261. var buildServerCredentials = _buildServerWatcher.GetBuildServerCredentials(this, false);
  262. if (buildServerCredentials != null)
  263. {
  264. UpdateHttpClientOptions(buildServerCredentials);
  265. return GetStreamAsync(restServicePath, cancellationToken);
  266. }
  267. throw new OperationCanceledException(task.Result.ReasonPhrase);
  268. }
  269. throw new HttpRequestException(task.Result.ReasonPhrase);
  270. #else
  271. return null;
  272. #endif
  273. }
  274. private void UpdateHttpClientOptions(IBuildServerCredentials buildServerCredentials)
  275. {
  276. var useGuestAccess = buildServerCredentials == null || buildServerCredentials.UseGuestAccess;
  277. _httpClient.DefaultRequestHeaders.Authorization = useGuestAccess
  278. ? null : CreateBasicHeader(buildServerCredentials.Username, buildServerCredentials.Password);
  279. }
  280. private Task<string> GetResponseAsync(string relativePath, CancellationToken cancellationToken)
  281. {
  282. var getStreamTask = GetStreamAsync(relativePath, cancellationToken);
  283. return getStreamTask.ContinueWith(
  284. task =>
  285. {
  286. if (task.Status != TaskStatus.RanToCompletion)
  287. return string.Empty;
  288. using (var responseStream = task.Result)
  289. {
  290. return new StreamReader(responseStream).ReadToEnd();
  291. }
  292. },
  293. cancellationToken,
  294. TaskContinuationOptions.AttachedToParent,
  295. TaskScheduler.Current);
  296. }
  297. private string FormatToGetJson(string restServicePath)
  298. {
  299. if (!restServicePath.EndsWith("/"))
  300. restServicePath += "/";
  301. return restServicePath + "api/json";
  302. }
  303. public void Dispose()
  304. {
  305. GC.SuppressFinalize(this);
  306. if (_httpClient != null)
  307. {
  308. _httpClient.Dispose();
  309. }
  310. }
  311. }
  312. }