前两篇博文中提到Development,QA,Staging,Production四个环境,也说明了源代码的分支和四个环境的对应关系,本篇博文聊一下,怎么把源码自动化发布到对应的环境中。
市面上主流的DevOpt工具都支持这些功能,github,gitlab,都有CICD功能,当然,如果源码服务器是自己搭建的,也可以利用像Jenkins这类软件来实现CICD,关于这些大众工具,网上有很多教程序,这里就不主要来分享了,本例是用.net core实现一个极简的自动发布工具——《MyCICD》。
说一下实现思路吧!
- clone 或 pull分支代码
- publish
- run
是不是很简单,上代码吧
using System; using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; namespace MyCICD { class Program { static void Main(string[] args) { var processIDs = new int[0]; while (true) { if (Clone()) { if (Publish(processIDs)) { processIDs = Run(); } } Thread.Sleep(30000); } } /// <summary> /// git 克隆 /// </summary> /// <returns></returns> static bool Clone() { var gitLib = ConfigurationManager.AppSettings["GitLib"]; var sourcePath = ConfigurationManager.AppSettings["SourcePath"]; var sourceDir = $"{sourcePath.TrimEnd('/', '\\') }/{ Path.GetFileNameWithoutExtension(gitLib)} "; //存在就拉取代码,不存在就克隆 if (Directory.Exists(sourceDir)) { return Pull(sourceDir); } else { return Clone(gitLib, sourceDir); } } /// <summary> /// 克隆项目代码 /// </summary> /// <param name="gitLib">git库</param> /// <param name="sourceDir">本地保存路径</param> /// <returns></returns> static bool Clone(string gitLib, string sourceDir) { Console.WriteLine("开始Clone"); var processStartInfo = new ProcessStartInfo("git", $"clone {gitLib} {sourceDir}") { RedirectStandardOutput = true }; var process = Process.Start(processStartInfo); if (process == null) { Console.WriteLine("请确认是否安装git"); return false; } else { using (var output = process.StandardOutput) { while (!output.EndOfStream) { Console.WriteLine(output.ReadLine()); } if (!process.HasExited) { process.Kill(); } } Console.WriteLine($"执行时间 :{(process.ExitTime - process.StartTime).TotalMilliseconds} ms"); Console.WriteLine("结束Clone"); return process.ExitCode == 0; } } /// <summary> /// 拉取项目代码 /// </summary> /// <param name="sourceDir">源码路径</param> /// <returns></returns> static bool Pull(string sourceDir) { Console.WriteLine("开始Fetch"); var processStartInfo = new ProcessStartInfo("git", $"pull origin") { RedirectStandardOutput = true, WorkingDirectory = sourceDir, }; var process = Process.Start(processStartInfo); using (var output = process.StandardOutput) { var resultBuilder = new StringBuilder(); while (!output.EndOfStream) { resultBuilder.AppendLine(output.ReadLine()); } Console.WriteLine(resultBuilder); if (!process.HasExited) { process.Kill(); } if (resultBuilder.ToString() != "Already up to date.\r\n") { Console.WriteLine("结束Fetch"); return true; } else { Console.WriteLine("结束Fetch,远程仓库没有更新"); return false; } } } #region 发布项目 /// <summary> /// 发布项目 /// </summary> /// <returns></returns> static bool Publish(int[] processIDs) { Console.WriteLine("开始Publish"); var sourcePath = ConfigurationManager.AppSettings["SourcePath"]; var publishProject = ConfigurationManager.AppSettings["PublishProject"]; //找出要发布的项目 var projectPathLists = publishProject.Split(","); var projects = GetProjectsPath(sourcePath, projectPathLists); var publishDir = $"{sourcePath}/publish"; var result = true;//如果有一个项目失败,发布就会失败 //为了发布,关闭之前运行中的进程 foreach (var processid in processIDs) { Process.GetProcessById(processid).Kill(); } //发布项目 foreach (var project in projects) { var processStartInfo = new ProcessStartInfo("dotnet", $"publish {project} -o {publishDir}/{Path.GetFileNameWithoutExtension(project)}") { RedirectStandardOutput = true }; var process = Process.Start(processStartInfo); if (process == null) { Console.WriteLine("请确认是否安装dotnet sdk"); return false; } else { using (var output = process.StandardOutput) { while (!output.EndOfStream) { Console.WriteLine(output.ReadLine()); } if (!process.HasExited) { process.Kill(); } } Console.WriteLine($"执行时间 :{(process.ExitTime - process.StartTime).TotalMilliseconds} ms"); if (process.ExitCode != 0) { Console.WriteLine($"{Path.GetFileNameWithoutExtension(project)}发布失败"); } result = result || process.ExitCode == 0; } } Console.WriteLine("结束Publish"); return result; } /// <summary> /// 查找项目 /// </summary> /// <param name="sourcePath">源码路径</param> /// <param name="projects">项目集</param> /// <returns></returns> static string[] GetProjectsPath(string sourcePath, string[] projects) { var paths = new List<string>(); foreach (var file in Directory.GetFiles(sourcePath)) { if (projects.Contains(Path.GetFileName(file))) { paths.Add(file); } } foreach (var dir in Directory.GetDirectories(sourcePath)) { paths.AddRange(GetProjectsPath(dir, projects)); } return paths.ToArray(); } #endregion #region 运行项目 /// <summary> /// 运行项目 /// </summary> /// <returns></returns> static int[] Run() { Console.WriteLine("开始运行"); var sourcePath = ConfigurationManager.AppSettings["SourcePath"]; var publishDir = $"{sourcePath}/publish"; var proceddIDs = new List<int>(); foreach (var projectPath in Directory.GetDirectories(publishDir)) { var processStartInfo = new ProcessStartInfo("dotnet", $"{Path.GetFileNameWithoutExtension(projectPath)}.dll") { RedirectStandardOutput = true, WorkingDirectory = projectPath, }; var process = Process.Start(processStartInfo); proceddIDs.Add(process.Id); } Console.WriteLine("结束运行"); return proceddIDs.ToArray(); } #endregion } }
App.config配置文件
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <!--git库相关:git库路径,clone后本地何存路径--> <add key="GitLib" value="https://github.com/axzxs2001/Asp.NetCoreExperiment.git"/> <add key="SourcePath" value="e:/test/"/> <!--dotnet发布相关:要发布的项目--> <add key="PublishProject" value="AspNetCoreEnvironment.csproj,WebError.csproj"/> </appSettings> </configuration>
这个例子很简单,只支持在windows下运行,同时run起来的应用和MyCICD是在一个进程中,一但进程关闭,run的服务也就掉了,还有很多需要改进,如果有兴趣,可以完善,比如可以跑大多个平台上(linux,docker,mac)下,也可以把MyCICD和运行的服务分离,进程间互不影响。
想要更快更方便的了解相关知识,可以关注微信公众号