前两篇博文中提到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和运行的服务分离,进程间互不影响。