├── gitlab-ci-runner ├── runner │ ├── State.cs │ ├── Runner.cs │ └── Build.cs ├── packages.config ├── Properties │ └── AssemblyInfo.cs ├── Program.cs ├── helper │ ├── IniFile.cs │ ├── SSHKey.cs │ ├── TextFile.cs │ └── Network.cs ├── setup │ └── Setup.cs ├── api │ └── API.cs ├── app.manifest ├── gitlab-ci-runner.csproj └── conf │ └── Config.cs ├── runner.Build.success.test ├── Properties │ └── AssemblyInfo.cs ├── BuildTest.cs └── runner.Build.success.test.csproj ├── gitlab-ci-runner.sln ├── .gitignore └── README.md /gitlab-ci-runner/runner/State.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace gitlab_ci_runner.runner 7 | { 8 | enum State 9 | { 10 | RUNNING, 11 | FAILED, 12 | SUCCESS, 13 | WAITING 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gitlab-ci-runner/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /runner.Build.success.test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("runner.Build.success.test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("runner.Build.success.test")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("74a91e53-3ebc-4154-b4cd-19357166d87a")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /gitlab-ci-runner.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gitlab-ci-runner", "gitlab-ci-runner\gitlab-ci-runner.csproj", "{CA24BB4A-79A3-424E-9EDC-8B1871E2D601}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "runner.Build.success.test", "runner.Build.success.test\runner.Build.success.test.csproj", "{AD9B6A82-AEBD-445B-B37E-C2C425BDDC76}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A1F260B-3C0C-4A98-8DA7-476C7AA7E303}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {CA24BB4A-79A3-424E-9EDC-8B1871E2D601}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {CA24BB4A-79A3-424E-9EDC-8B1871E2D601}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {CA24BB4A-79A3-424E-9EDC-8B1871E2D601}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {CA24BB4A-79A3-424E-9EDC-8B1871E2D601}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {AD9B6A82-AEBD-445B-B37E-C2C425BDDC76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {AD9B6A82-AEBD-445B-B37E-C2C425BDDC76}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {AD9B6A82-AEBD-445B-B37E-C2C425BDDC76}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {AD9B6A82-AEBD-445B-B37E-C2C425BDDC76}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /gitlab-ci-runner/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Allgemeine Informationen über eine Assembly werden über die folgenden 6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 7 | // die mit einer Assembly verknüpft sind. 8 | [assembly: AssemblyTitle("gitlab-ci-runner")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("gitlab-ci-runner")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 18 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 19 | // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. 20 | [assembly: ComVisible(false)] 21 | 22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 23 | [assembly: Guid("e7e13988-cb89-4ffd-8dda-d1aa99c3b5a7")] 24 | 25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 26 | // 27 | // Hauptversion 28 | // Nebenversion 29 | // Buildnummer 30 | // Revision 31 | // 32 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 33 | // übernehmen, indem Sie "*" eingeben: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | 38 | [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("runner.Build.success.test")] -------------------------------------------------------------------------------- /gitlab-ci-runner/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | using System.IO; 7 | using System.Reflection; 8 | using gitlab_ci_runner.conf; 9 | using gitlab_ci_runner.runner; 10 | using gitlab_ci_runner.setup; 11 | 12 | namespace gitlab_ci_runner 13 | { 14 | class Program 15 | { 16 | static void Main(string[] args) 17 | { 18 | Console.InputEncoding = Encoding.Default; 19 | Console.OutputEncoding = Encoding.Default; 20 | ServicePointManager.DefaultConnectionLimit = 999; 21 | 22 | if (args.Contains ("-sslbypass")) 23 | { 24 | Program.RegisterSecureSocketsLayerBypass (); 25 | } 26 | 27 | if (Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location).Substring(0, 1) == @"\") { 28 | Console.WriteLine("Can't run on UNC Path"); 29 | } else { 30 | Console.WriteLine("Starting Gitlab CI Runner for Windows"); 31 | Config.loadConfig(); 32 | if (Config.isConfigured()) { 33 | // Load the runner 34 | Runner.run(); 35 | } else { 36 | // Load the setup 37 | Setup.run(); 38 | } 39 | } 40 | Console.WriteLine(); 41 | Console.WriteLine("Runner quit. Press any key to exit!"); 42 | Console.ReadKey(); 43 | } 44 | 45 | static void RegisterSecureSocketsLayerBypass() 46 | { 47 | System.Net.ServicePointManager.ServerCertificateValidationCallback += 48 | delegate (object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, 49 | System.Security.Cryptography.X509Certificates.X509Chain chain, 50 | System.Net.Security.SslPolicyErrors sslPolicyErrors) 51 | { 52 | return true; // **** Always accept 53 | }; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /gitlab-ci-runner/helper/IniFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | 7 | namespace gitlab_ci_runner.helper 8 | { 9 | /// 10 | /// .ini File Parser 11 | /// (c) http://www.codeproject.com/Articles/1966/An-INI-file-handling-class-using-C 12 | /// 13 | class IniFile 14 | { 15 | public string path; 16 | 17 | [DllImport("kernel32")] 18 | private static extern long WritePrivateProfileString(string section, 19 | string key, string val, string filePath); 20 | [DllImport("kernel32")] 21 | private static extern int GetPrivateProfileString(string section, 22 | string key, string def, StringBuilder retVal, 23 | int size, string filePath); 24 | 25 | /// 26 | /// INIFile Constructor. 27 | /// 28 | /// Path to the .ini File 29 | public IniFile(string INIPath) 30 | { 31 | path = INIPath; 32 | } 33 | /// 34 | /// Write Data to the INI File 35 | /// 36 | /// Section name 37 | /// Key Name 38 | /// Value Name 39 | public void IniWriteValue(string Section, string Key, string Value) 40 | { 41 | WritePrivateProfileString(Section, Key, Value, this.path); 42 | } 43 | 44 | /// 45 | /// Read Data Value From the Ini File 46 | /// 47 | /// Section name 48 | /// Key Name 49 | /// Value 50 | public string IniReadValue(string Section, string Key) 51 | { 52 | StringBuilder temp = new StringBuilder(255); 53 | int i = GetPrivateProfileString(Section, Key, "", temp, 54 | 255, this.path); 55 | return temp.ToString(); 56 | 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /gitlab-ci-runner/setup/Setup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using gitlab_ci_runner.conf; 6 | using gitlab_ci_runner.helper; 7 | 8 | namespace gitlab_ci_runner.setup 9 | { 10 | class Setup 11 | { 12 | /// 13 | /// Start the Setup 14 | /// 15 | public static void run() 16 | { 17 | Console.WriteLine("This seems to be the first run,"); 18 | Console.WriteLine("please provide the following info to proceed:"); 19 | Console.WriteLine(); 20 | 21 | // Read coordinator URL 22 | String sCoordUrl = ""; 23 | while (sCoordUrl == "") 24 | { 25 | Console.WriteLine("Please enter the gitlab-ci coordinator URL (e.g. http(s)://gitlab-ci.org:3000/ )"); 26 | sCoordUrl = Console.ReadLine(); 27 | } 28 | Config.url = sCoordUrl; 29 | Console.WriteLine(); 30 | 31 | // Generate SSH Keys 32 | SSHKey.generateKeypair(); 33 | 34 | // Register Runner 35 | registerRunner(); 36 | } 37 | 38 | /// 39 | /// Register the runner with the coordinator 40 | /// 41 | private static void registerRunner() 42 | { 43 | // Read Token 44 | string sToken = ""; 45 | while (sToken == "") 46 | { 47 | Console.WriteLine("Please enter the gitlab-ci token for this runner:"); 48 | sToken = Console.ReadLine(); 49 | } 50 | 51 | // Register Runner 52 | string sTok = Network.registerRunner(SSHKey.getPublicKey(), sToken); 53 | if (sTok != null) 54 | { 55 | // Save Config 56 | Config.token = sTok; 57 | Config.saveConfig(); 58 | 59 | Console.WriteLine(); 60 | Console.WriteLine("Runner registered successfully. Feel free to start it!"); 61 | } 62 | else 63 | { 64 | Console.WriteLine(); 65 | Console.WriteLine("Failed to register this runner. Perhaps your SSH key is invalid or you are having network problems"); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gitlab-ci-runner/api/API.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using ServiceStack; 6 | 7 | 8 | /// 9 | /// V1 API 10 | /// 11 | namespace gitlab_ci_runner.api 12 | { 13 | [Route ("/runners/register.json","POST")] 14 | public class RegisterRunner : IReturn 15 | { 16 | public string token 17 | { 18 | get; 19 | set; 20 | } 21 | 22 | public string public_key 23 | { 24 | get; 25 | set; 26 | } 27 | } 28 | 29 | [Route ("/builds/register.json", "POST")] 30 | public class CheckForBuild : IReturn 31 | { 32 | public string token 33 | { 34 | get; 35 | set; 36 | } 37 | } 38 | 39 | [Route ("/builds/{id}", "PUT")] 40 | public class PushBuild : IReturn 41 | { 42 | public string id 43 | { 44 | get; 45 | set; 46 | } 47 | 48 | public string token 49 | { 50 | get; 51 | set; 52 | } 53 | 54 | public string state 55 | { 56 | get; 57 | set; 58 | } 59 | 60 | public string trace 61 | { 62 | get; 63 | set; 64 | } 65 | } 66 | 67 | public class RunnerInfo 68 | { 69 | public int id 70 | { 71 | get; 72 | set; 73 | } 74 | public string token 75 | { 76 | get; 77 | set; 78 | } 79 | } 80 | 81 | public class BuildInfo 82 | { 83 | public int id 84 | { 85 | get; 86 | set; 87 | } 88 | 89 | public int project_id 90 | { 91 | get; 92 | set; 93 | } 94 | 95 | public string project_name 96 | { 97 | get; 98 | set; 99 | } 100 | 101 | public string commands 102 | { 103 | get; 104 | set; 105 | } 106 | 107 | public string repo_url 108 | { 109 | get; 110 | set; 111 | } 112 | 113 | public string sha 114 | { 115 | get; 116 | set; 117 | } 118 | 119 | public string before_sha 120 | { 121 | get; 122 | set; 123 | } 124 | 125 | public string @ref 126 | { 127 | get; 128 | set; 129 | } 130 | public int timeout 131 | { 132 | get; 133 | set; 134 | } 135 | 136 | public bool allow_git_fetch 137 | { 138 | get; 139 | set; 140 | } 141 | 142 | public string[] GetCommands() 143 | { 144 | return System.Text.RegularExpressions.Regex.Replace (this.commands, "(\r|\n)+", "\n").Split ('\n'); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /gitlab-ci-runner/helper/SSHKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | 9 | namespace gitlab_ci_runner.helper 10 | { 11 | class SSHKey 12 | { 13 | /// 14 | /// Generate a keypair 15 | /// 16 | public static void generateKeypair() 17 | { 18 | // We need both, the Public and Private Key 19 | // Public Key to send to Gitlab 20 | // Private Key to connect to Gitlab later 21 | if (File.Exists(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\.ssh\id_rsa.pub") && 22 | File.Exists(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\.ssh\id_rsa")) 23 | { 24 | return; 25 | } 26 | 27 | try { 28 | Directory.CreateDirectory(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\.ssh"); 29 | } catch (Exception) {} 30 | Process p = new Process(); 31 | p.StartInfo.FileName = "ssh-keygen"; 32 | p.StartInfo.Arguments = "-t rsa -f \"" + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\.ssh\\id_rsa\" -N \"\""; 33 | p.Start(); 34 | Console.WriteLine(); 35 | Console.WriteLine("Waiting for SSH Key to be generated ..."); 36 | p.WaitForExit(); 37 | while (!File.Exists(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\.ssh\id_rsa.pub") && 38 | !File.Exists(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\.ssh\id_rsa")) 39 | { 40 | Thread.Sleep(1000); 41 | } 42 | Console.WriteLine("SSH Key generated successfully!"); 43 | } 44 | 45 | /// 46 | /// Get the public key 47 | /// 48 | /// 49 | public static string getPublicKey() 50 | { 51 | if (File.Exists(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\.ssh\id_rsa.pub")) 52 | { 53 | return TextFile.ReadFile(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\.ssh\id_rsa.pub"); 54 | } 55 | else 56 | { 57 | return null; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /gitlab-ci-runner/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /runner.Build.success.test/BuildTest.cs: -------------------------------------------------------------------------------- 1 | using gitlab_ci_runner.runner; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using gitlab_ci_runner.api; 5 | 6 | namespace runner.Build.success.test 7 | { 8 | 9 | 10 | /// 11 | ///This is a test class for BuildTest and is intended 12 | ///to contain all BuildTest Unit Tests 13 | /// 14 | [TestClass()] 15 | public class BuildTest 16 | { 17 | 18 | 19 | private TestContext testContextInstance; 20 | 21 | /// 22 | ///Gets or sets the test context which provides 23 | ///information about and functionality for the current test run. 24 | /// 25 | public TestContext TestContext 26 | { 27 | get 28 | { 29 | return testContextInstance; 30 | } 31 | set 32 | { 33 | testContextInstance = value; 34 | } 35 | } 36 | 37 | #region Additional test attributes 38 | // 39 | //You can use the following additional attributes as you write your tests: 40 | // 41 | //Use ClassInitialize to run code before running the first test in the class 42 | //[ClassInitialize()] 43 | //public static void MyClassInitialize(TestContext testContext) 44 | //{ 45 | //} 46 | // 47 | //Use ClassCleanup to run code after all tests in a class have run 48 | //[ClassCleanup()] 49 | //public static void MyClassCleanup() 50 | //{ 51 | //} 52 | // 53 | //Use TestInitialize to run code before running each test 54 | //[TestInitialize()] 55 | //public void MyTestInitialize() 56 | //{ 57 | //} 58 | // 59 | //Use TestCleanup to run code after each test has run 60 | //[TestCleanup()] 61 | //public void MyTestCleanup() 62 | //{ 63 | //} 64 | // 65 | #endregion 66 | 67 | 68 | /// 69 | ///A test for run 70 | /// 71 | [TestMethod()] 72 | public void runTest() 73 | { 74 | // copied from official gitlab ci runner spec 75 | BuildInfo buildInfo = new BuildInfo(); 76 | buildInfo.commands = "dir"; 77 | buildInfo.allow_git_fetch = false; 78 | buildInfo.project_id = 0; 79 | buildInfo.id = 9312; 80 | buildInfo.repo_url = "https://github.com/randx/six.git"; 81 | buildInfo.sha = "2e008a711430a16092cd6a20c225807cb3f51db7"; 82 | buildInfo.timeout = 1800; 83 | buildInfo.@ref = "master"; 84 | 85 | gitlab_ci_runner.runner.Build target = new gitlab_ci_runner.runner.Build(buildInfo); 86 | target.run(); 87 | Console.WriteLine(target.output); 88 | Assert.AreEqual(target.state, State.SUCCESS); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # VS files 10 | *.vsmdi 11 | *.testsettings 12 | 13 | # Build results 14 | 15 | [Dd]ebug/ 16 | [Rr]elease/ 17 | x64/ 18 | build/ 19 | [Bb]in/ 20 | [Oo]bj/ 21 | 22 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 23 | !packages/*/build/ 24 | 25 | # MSTest test Results 26 | [Tt]est[Rr]esult*/ 27 | [Bb]uild[Ll]og.* 28 | 29 | *_i.c 30 | *_p.c 31 | *.ilk 32 | *.meta 33 | *.obj 34 | *.pch 35 | *.pdb 36 | *.pgc 37 | *.pgd 38 | *.rsp 39 | *.sbr 40 | *.tlb 41 | *.tli 42 | *.tlh 43 | *.tmp 44 | *.tmp_proj 45 | *.log 46 | *.vspscc 47 | *.vssscc 48 | .builds 49 | *.pidb 50 | *.log 51 | *.scc 52 | 53 | # Visual C++ cache files 54 | ipch/ 55 | *.aps 56 | *.ncb 57 | *.opensdf 58 | *.sdf 59 | *.cachefile 60 | 61 | # Visual Studio profiler 62 | *.psess 63 | *.vsp 64 | *.vspx 65 | 66 | # Guidance Automation Toolkit 67 | *.gpState 68 | 69 | # ReSharper is a .NET coding add-in 70 | _ReSharper*/ 71 | *.[Rr]e[Ss]harper 72 | 73 | # TeamCity is a build add-in 74 | _TeamCity* 75 | 76 | # DotCover is a Code Coverage Tool 77 | *.dotCover 78 | 79 | # NCrunch 80 | *.ncrunch* 81 | .*crunch*.local.xml 82 | 83 | # Installshield output folder 84 | [Ee]xpress/ 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish/ 98 | 99 | # Publish Web Output 100 | *.Publish.xml 101 | *.pubxml 102 | 103 | # NuGet Packages Directory 104 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 105 | packages/ 106 | 107 | # Windows Azure Build Output 108 | csx 109 | *.build.csdef 110 | 111 | # Windows Store app package directory 112 | AppPackages/ 113 | 114 | # Others 115 | sql/ 116 | *.Cache 117 | ClientBin/ 118 | [Ss]tyle[Cc]op.* 119 | ~$* 120 | *~ 121 | *.dbmdl 122 | *.[Pp]ublish.xml 123 | *.pfx 124 | *.publishsettings 125 | 126 | # RIA/Silverlight projects 127 | Generated_Code/ 128 | 129 | # Backup & report files from converting an old project file to a newer 130 | # Visual Studio version. Backup files are not needed, because we have git ;-) 131 | _UpgradeReport_Files/ 132 | Backup*/ 133 | UpgradeLog*.XML 134 | UpgradeLog*.htm 135 | 136 | # SQL Server files 137 | App_Data/*.mdf 138 | App_Data/*.ldf 139 | 140 | # ========================= 141 | # Windows detritus 142 | # ========================= 143 | 144 | # Windows image file caches 145 | Thumbs.db 146 | ehthumbs.db 147 | 148 | # Folder config file 149 | Desktop.ini 150 | 151 | # Recycle Bin used on file shares 152 | $RECYCLE.BIN/ 153 | 154 | # Mac crap 155 | .DS_Store 156 | -------------------------------------------------------------------------------- /runner.Build.success.test/runner.Build.success.test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 7 | 8 | 2.0 9 | {AD9B6A82-AEBD-445B-B37E-C2C425BDDC76} 10 | Library 11 | Properties 12 | runner.Build.success.test 13 | runner.Build.success.test 14 | v4.0 15 | 512 16 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | ..\packages\ServiceStack.Text.3.9.63\lib\net35\ServiceStack.Text.dll 40 | 41 | 42 | 43 | 3.5 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | False 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {CA24BB4A-79A3-424E-9EDC-8B1871E2D601} 62 | gitlab-ci-runner 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /gitlab-ci-runner/runner/Runner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using gitlab_ci_runner.api; 7 | using gitlab_ci_runner.helper; 8 | 9 | namespace gitlab_ci_runner.runner 10 | { 11 | class Runner 12 | { 13 | /// 14 | /// Build process 15 | /// 16 | private static Build build = null; 17 | 18 | /// 19 | /// Start the configured runner 20 | /// 21 | public static void run() 22 | { 23 | Console.WriteLine("* Gitlab CI Runner started"); 24 | Console.WriteLine("* Waiting for builds"); 25 | waitForBuild(); 26 | } 27 | 28 | /// 29 | /// Build completed? 30 | /// 31 | public static bool completed 32 | { 33 | get 34 | { 35 | return running && build.completed; 36 | } 37 | } 38 | 39 | /// 40 | /// Build running? 41 | /// 42 | public static bool running 43 | { 44 | get 45 | { 46 | return build != null; 47 | } 48 | } 49 | 50 | /// 51 | /// Wait for an incoming build or update current Build 52 | /// 53 | private static void waitForBuild() 54 | { 55 | while (true) 56 | { 57 | if (completed || running) 58 | { 59 | // Build is running or completed 60 | // Update build 61 | updateBuild(); 62 | } 63 | else 64 | { 65 | // Get new build 66 | getBuild(); 67 | } 68 | Thread.Sleep(5000); 69 | } 70 | } 71 | 72 | /// 73 | /// Update the current running build progress 74 | /// 75 | private static void updateBuild() 76 | { 77 | if (build.completed) 78 | { 79 | // Build finished 80 | if (pushBuild()) 81 | { 82 | Console.WriteLine("[" + DateTime.Now.ToString() + "] Completed build " + build.buildInfo.id); 83 | build = null; 84 | } 85 | } 86 | else 87 | { 88 | // Build is currently running 89 | pushBuild(); 90 | } 91 | } 92 | 93 | /// 94 | /// PUSH Build Status to Gitlab CI 95 | /// 96 | /// true on success, false on fail 97 | private static bool pushBuild() 98 | { 99 | return Network.pushBuild(build.buildInfo.id, build.state, build.output); 100 | } 101 | 102 | /// 103 | /// Get a new build job 104 | /// 105 | private static void getBuild() 106 | { 107 | BuildInfo binfo = Network.getBuild(); 108 | if (binfo != null) 109 | { 110 | // Create Build Job 111 | build = new Build(binfo); 112 | Console.WriteLine("[" + DateTime.Now.ToString() + "] Build " + binfo.id + " started..."); 113 | Thread t = new Thread(build.run); 114 | t.Start(); 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /gitlab-ci-runner/gitlab-ci-runner.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {CA24BB4A-79A3-424E-9EDC-8B1871E2D601} 8 | Exe 9 | Properties 10 | gitlab_ci_runner 11 | gitlab-ci-runner 12 | v4.0 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | none 28 | true 29 | bin\Release\ 30 | 31 | 32 | prompt 33 | 4 34 | false 35 | Off 36 | 37 | 38 | app.manifest 39 | 40 | 41 | 42 | ..\packages\ini-parser.2.1.1\lib\INIFileParser.dll 43 | 44 | 45 | ..\packages\Microsoft.Experimental.IO.1.0.0.0\lib\NETFramework40\Microsoft.Experimental.IO.dll 46 | 47 | 48 | ..\packages\ServiceStack.Client.4.0.20\lib\net40\ServiceStack.Client.dll 49 | 50 | 51 | ..\packages\ServiceStack.Interfaces.4.0.20\lib\net40\ServiceStack.Interfaces.dll 52 | 53 | 54 | False 55 | ..\packages\ServiceStack.Text.4.0.20\lib\net40\ServiceStack.Text.dll 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 92 | -------------------------------------------------------------------------------- /gitlab-ci-runner/helper/TextFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace gitlab_ci_runner.helper 8 | { 9 | class TextFile 10 | { 11 | /// 12 | /// Get the content of the file 13 | /// 14 | ///File path and name 15 | public static string ReadFile(String sFilename) 16 | { 17 | string sContent = ""; 18 | if (File.Exists(sFilename)) 19 | { 20 | StreamReader myFile = new StreamReader(sFilename, System.Text.Encoding.Default); 21 | sContent = myFile.ReadToEnd(); 22 | myFile.Close(); 23 | } 24 | return sContent; 25 | } 26 | 27 | /// 28 | /// Writes a file 29 | /// 30 | ///File path and name 31 | ///Content 32 | public static void WriteFile(String sFilename, String sLines) 33 | { 34 | StreamWriter myFile = new StreamWriter(sFilename); 35 | myFile.Write(sLines); 36 | myFile.Close(); 37 | } 38 | 39 | /// 40 | /// Appends the file 41 | /// 42 | ///File path and name 43 | ///Content 44 | public static void Append(string sFilename, string sLines) 45 | { 46 | StreamWriter myFile = new StreamWriter(sFilename, true); 47 | myFile.Write(sLines); 48 | myFile.Close(); 49 | } 50 | 51 | /// 52 | /// Get the content of a defined line 53 | /// 54 | ///File path and name 55 | ///Line number 56 | public static string ReadLine(String sFilename, int iLine) 57 | { 58 | string sContent = ""; 59 | float fRow = 0; 60 | if (File.Exists(sFilename)) 61 | { 62 | StreamReader myFile = new StreamReader(sFilename, System.Text.Encoding.Default); 63 | while (!myFile.EndOfStream && fRow < iLine) 64 | { 65 | fRow++; 66 | sContent = myFile.ReadLine(); 67 | } 68 | myFile.Close(); 69 | if (fRow < iLine) 70 | sContent = ""; 71 | } 72 | return sContent; 73 | } 74 | 75 | ///  76 | /// Writes into a defined line 77 | /// 78 | ///File path and name 79 | ///Line number 80 | ///Content of the line 81 | ///Replace the lines content (true) or append (false) 82 | public static void WriteLine(String sFilename, int iLine, string sLines, bool bReplace) 83 | { 84 | string sContent = ""; 85 | string[] delimiterstring = { "\r\n" }; 86 | if (File.Exists(sFilename)) 87 | { 88 | StreamReader myFile = new StreamReader(sFilename, System.Text.Encoding.Default); 89 | sContent = myFile.ReadToEnd(); 90 | myFile.Close(); 91 | } 92 | string[] sCols = sContent.Split(delimiterstring, StringSplitOptions.None); 93 | if (sCols.Length >= iLine) 94 | { 95 | if (!bReplace) 96 | sCols[iLine - 1] = sLines + "\r\n" + sCols[iLine - 1]; 97 | else 98 | sCols[iLine - 1] = sLines; 99 | sContent = ""; 100 | for (int x = 0; x < sCols.Length - 1; x++) 101 | { 102 | sContent += sCols[x] + "\r\n"; 103 | } 104 | sContent += sCols[sCols.Length - 1]; 105 | } 106 | else 107 | { 108 | for (int x = 0; x < iLine - sCols.Length; x++) 109 | sContent += "\r\n"; 110 | sContent += sLines; 111 | } 112 | StreamWriter mySaveFile = new StreamWriter(sFilename); 113 | mySaveFile.Write(sContent); 114 | mySaveFile.Close(); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /gitlab-ci-runner/helper/Network.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Web; 9 | using gitlab_ci_runner.api; 10 | using gitlab_ci_runner.conf; 11 | using gitlab_ci_runner.runner; 12 | using ServiceStack; 13 | using ServiceStack.Text; 14 | 15 | namespace gitlab_ci_runner.helper 16 | { 17 | class Network 18 | { 19 | /// 20 | /// Gitlab CI API URL 21 | /// 22 | private static string apiurl 23 | { 24 | get 25 | { 26 | return Config.url + "/api/v1/"; 27 | } 28 | } 29 | 30 | /// 31 | /// Register the runner with the coordinator 32 | /// 33 | /// SSH Public Key 34 | /// Token 35 | /// Token 36 | public static string registerRunner(String sPubKey, String sToken) 37 | { 38 | var client = new JsonServiceClient (apiurl); 39 | try 40 | { 41 | var authToken = client.Post (new RegisterRunner 42 | { 43 | token = Uri.EscapeDataString (sToken), 44 | public_key = Uri.EscapeDataString (sPubKey) 45 | }); 46 | 47 | if (!authToken.token.IsNullOrEmpty ()) 48 | { 49 | Console.WriteLine ("Runner registered with id {0}", authToken.id); 50 | return authToken.token; 51 | } 52 | else 53 | { 54 | return null; 55 | } 56 | } 57 | catch(WebException ex) 58 | { 59 | Console.WriteLine ("Error while registering runner :", ex.Message); 60 | return null; 61 | } 62 | } 63 | 64 | /// 65 | /// Get a new build 66 | /// 67 | /// BuildInfo object or null on error/no build 68 | public static BuildInfo getBuild() 69 | { 70 | Console.WriteLine("* Checking for builds..."); 71 | var client = new JsonServiceClient (apiurl); 72 | try 73 | { 74 | var buildInfo = client.Post (new CheckForBuild 75 | { 76 | token = Uri.EscapeDataString (Config.token) 77 | }); 78 | 79 | if (buildInfo != null) 80 | { 81 | return buildInfo; 82 | } 83 | } 84 | catch (WebServiceException ex) 85 | { 86 | if(ex.StatusCode == 404) 87 | { 88 | Console.WriteLine ("* Nothing"); 89 | } 90 | else 91 | { 92 | Console.WriteLine ("* Failed"); 93 | } 94 | } 95 | 96 | return null; 97 | } 98 | 99 | /// 100 | /// PUSH the Build to the Gitlab CI Coordinator 101 | /// 102 | /// Build ID 103 | /// State 104 | /// Command output 105 | /// 106 | public static bool pushBuild(int iId, State state, string sTrace) 107 | { 108 | Console.WriteLine("[" + DateTime.Now + "] Submitting build " + iId + " to coordinator ..."); 109 | 110 | var stateValue = ""; 111 | if (state == State.RUNNING) 112 | { 113 | stateValue = "running"; 114 | } 115 | else if (state == State.SUCCESS) 116 | { 117 | stateValue = "success"; 118 | } 119 | else if (state == State.FAILED) 120 | { 121 | stateValue = "failed"; 122 | } 123 | else 124 | { 125 | stateValue = "waiting"; 126 | } 127 | 128 | var trace = new StringBuilder(); 129 | foreach (string t in sTrace.Split('\n')) 130 | trace.Append(t).Append("\n"); 131 | 132 | int iTry = 0; 133 | while (iTry <= 5) 134 | { 135 | try 136 | { 137 | var client = new JsonServiceClient(apiurl); 138 | var resp = client.Put (new PushBuild { 139 | id = iId + ".json", 140 | token = Uri.EscapeDataString(Config.token), 141 | state = stateValue, 142 | trace = trace.ToString () }); 143 | 144 | if (resp != null) 145 | { 146 | return true; 147 | } 148 | } 149 | catch 150 | { } 151 | 152 | iTry++; 153 | Thread.Sleep(1000); 154 | } 155 | 156 | return false; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gitlab CI Runner for Windows 2 | ============================ 3 | 4 | This is an unofficial runner for [Gitlab CI](https://github.com/gitlabhq/gitlab-ci) under Windows. 5 | 6 | Requirements 7 | ------------ 8 | 9 | This program only runs under Windows operating systems. 10 | 11 | For Linux operating systems, take a look at the [official runner for linux](https://github.com/gitlabhq/gitlab-ci-runner). 12 | 13 | The following programs are needed: 14 | 15 | [msysgit](http://msysgit.github.io/) (Install with openssh style ssh key support, not with putty keys) 16 | 17 | Don't forget to add the `git/bin` directory (usually `C:\Program Files\Git\bin`) to your `PATH` variable! 18 | 19 | Installation 20 | ------------ 21 | 22 | Just checkout this repository, open the project in Visual Studio 2013 and compile it yourself. 23 | 24 | The configuration initialization begins on the first run. 25 | 26 | After the first run the config is written and you can start the runner in the background. 27 | 28 | To register the runner as service try the [NSSM](http://nssm.cc/). But remember to run the service as the user you created the config with! 29 | 30 | Options 31 | ------- 32 | 33 | If your CI server is using a self-signed certificate you can add -sslbypass option in argument 34 | 35 | How it works 36 | ------------ 37 | 38 | The whole thing works in the following way: 39 | 40 | 1. The runner should be endlessly up and listen to the commands from CI (this is why you should use NSSM or its equivalent). 41 | 2. As soon as CI sends a command to the runner it performs the preparation actions (clones or fetches the project from the server to some folder) 42 | 3. When the project is on the runner's machine, it executes the script you write in the CI control panel. 43 | 44 | You may need an additional control how the runner updates the project. For example, you may want to use a particular project folder. Or avoid using `git reset --hard` before each update. Or use `git clone --recursive` instead of simple `git clone`. 45 | 46 | To do it, you can additionally configure runner as explained in the next section. 47 | 48 | Additional configuration 49 | ------------------------ 50 | 51 | After the first launch, the runner creates a `runner.cfg` file which holds the URL to the Gitlab CI server and a runner token you should insert when registering the runner there: 52 | 53 | ``` 54 | [main] 55 | url=http://ci.gitlab.example.com/ 56 | token= 57 | ``` 58 | 59 | You may add extra sections (see below how you name them) which may hold the following keys: 60 | 61 | - `ProjectDir` - a directory where the project should be created. It may be relative to the runner folder or absolute. 62 | - `NewRepoInitCommand` - a command which should prepare a new repo 63 | - `ExistingRepoInitCommand` - a command which should update a repo if it already exists 64 | - `PostPrepareCommand` - a command which is called after `NewRepoInitCommand` or `ExistingRepoInitCommand` is executed. 65 | 66 | ### About commands 67 | 68 | Commands are executed in the `ProjectDir`, so there is no need to `cd` there. 69 | 70 | If you need multiple commands, you can either put concatenate them with `&&` (`git clone {repo_url} && git checkout {commit}`) or put 71 | them to a `.bat` file. 72 | 73 | If you don't specify commands, by default it will be: 74 | 75 | ``` 76 | NewRepoInitCommand=git clone {repo_url} {project_dir} && cd {project_dir} && git checkout {commit} 77 | ExistingRepoInitCommand=git reset --hard && git clean -f && git fetch && git checkout {commit} 78 | ``` 79 | 80 | ### Placeholders 81 | 82 | Each of these keys may contain placeholders which will be replaced by a data sent from CI: 83 | 84 | - `{build_id}` - build ID provided by Gitlab CI server 85 | - `{project_id}` - project ID provided by Gitlab CI (note, not by Gitlab itself!) 86 | - `{project_name}` - a project name including namespace, without whitespaces (e.g. Group1/myproject) 87 | - `{project_dir}` - won't be applied for the ProjectDir key. 88 | - `{commit}` - uuid which identifies a current commit 89 | - `{previous_commit}` - uuid of the previous commit 90 | - `{repo_url}` - repo URL 91 | - `{ref_name}` - ref name 92 | 93 | ### Defining different settings for different projects 94 | 95 | The same runner may be reused by multiple projects. You may want to have different project with different settings. 96 | 97 | To achieve this, you should refer the project in the section name. You can do it either using ID or name: 98 | 99 | `[id=123]` 100 | 101 | or 102 | 103 | `[name=group1/myprojectname]` 104 | 105 | You can use the same commands for several projects by separating them with a `|` sign: 106 | 107 | `[id=123|id=234|name=group1/someproject]` 108 | 109 | At last, you may specify the universal rules using `*` sign: 110 | 111 | `[*]` 112 | 113 | Note, you should put `[*]` in the end of config, otherwise it will ignore settings for your specific project. You may think about it as `default` in the `switch...case` block. 114 | 115 | ### Putting things together 116 | 117 | It is a time for an example: 118 | 119 | ``` ini 120 | [main] 121 | url=http://ci.gitlab.example.com/ 122 | token= 123 | [id=123] 124 | ProjectDir=C:\CI\MyTest 125 | NewRepoInitCommand=git clone --recursive {repo_url} 126 | [name=somegroup/someproject|id=124] 127 | ProjectDir=C:\CI\{project_name} 128 | NewRepoInitCommand=myclonecommands.bat {project_id} {repo_url} 129 | ExistingRepoInitCommand=myfetchcommands.bat {project_id} {repo_url} 130 | [*] 131 | ProjectDir=C:\defaultprojectdir\project_{project_id} 132 | PostPrepareDir=echo "ProjDir: {project_dir}, ProjID: {project_id}, ProjName={project_name}, BuildID: {build_id}, RepoUrl: {repo_url}, RefName: {ref_name}, SHA: {sha}, BeforeSHA: {before_sha}" 133 | ``` 134 | -------------------------------------------------------------------------------- /gitlab-ci-runner/conf/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Runtime.Serialization.Formatters.Binary; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | using gitlab_ci_runner.helper; 10 | using gitlab_ci_runner.api; 11 | 12 | namespace gitlab_ci_runner.conf 13 | { 14 | 15 | public class Config 16 | { 17 | 18 | public class PrebuildConfig 19 | { 20 | private static string GetValue(IniParser.Model.KeyDataCollection keys, string name) 21 | { 22 | return keys.ContainsKey(name) ? keys[name] : ""; 23 | } 24 | 25 | internal PrebuildConfig(BuildInfo b, IniParser.Model.SectionData data) 26 | { 27 | /* 28 | _clone = new CmdConfig(b, data, "Clone"); 29 | _checkout = new CmdConfig(b, data, "Checkout"); 30 | _fetch = new CmdConfig(b, data, "Fetch"); 31 | */ 32 | 33 | _projectDir = GetValue(data.Keys, "ProjectDir"); 34 | _newRepoInit = GetValue(data.Keys, "NewRepoInitCommand"); 35 | _existingRepoInit = GetValue(data.Keys, "ExistingRepoInitCommand"); 36 | _postPrepare = GetValue(data.Keys, "PostPrepareCommand"); 37 | var r = new[] { 38 | new { key = "{project_dir}", val = "" }, 39 | new { key = "{build_id}", val = b.id.ToString() }, 40 | new { key = "{project_id}", val = b.project_id.ToString() }, 41 | new { key = "{project_name}", val = Regex.Replace(b.project_name, @"\s+", "") }, 42 | new { key = "{commit}", val = b.sha }, 43 | new { key = "{previous_commit}", val = b.before_sha }, 44 | new { key = "{repo_url}", val = b.repo_url }, 45 | new { key = "{ref_name}", val = b.ref_name } }; 46 | 47 | foreach(var rule in r) 48 | { 49 | _projectDir = _projectDir.Replace(rule.key, rule.val); 50 | } 51 | 52 | r[0] = new { key = "{project_dir}", val =_projectDir}; 53 | foreach (var rule in r) 54 | { 55 | _newRepoInit = _newRepoInit.Replace(rule.key, rule.val); 56 | _existingRepoInit = _existingRepoInit.Replace(rule.key, rule.val); 57 | _postPrepare = _postPrepare.Replace(rule.key, rule.val); 58 | } 59 | 60 | } 61 | 62 | private string _projectDir; 63 | private string _postPrepare; 64 | private string _newRepoInit; 65 | private string _existingRepoInit; 66 | 67 | public string ProjectDir { get { return _projectDir; } } 68 | public string NewRepoInit { get { return _newRepoInit; } } 69 | public string ExistingRepoInit { get { return _existingRepoInit; } } 70 | public string PostPrepare { get { return _postPrepare; } } 71 | } 72 | 73 | /// 74 | /// URL to the Gitlab CI coordinator 75 | /// 76 | public static string url; 77 | 78 | /// 79 | /// Gitlab CI runner auth token 80 | /// 81 | public static string token; 82 | 83 | /// 84 | /// Configuration Path 85 | /// 86 | private static string confPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\runner.cfg"; 87 | 88 | /// 89 | /// Load the configuration 90 | /// 91 | public static void loadConfig() 92 | { 93 | if (File.Exists(confPath)) 94 | { 95 | IniFile ini = new IniFile(confPath); 96 | url = ini.IniReadValue("main", "url"); 97 | token = ini.IniReadValue("main", "token"); 98 | } 99 | } 100 | 101 | /// 102 | /// Save the configuration 103 | /// 104 | public static void saveConfig() 105 | { 106 | if (File.Exists(confPath)) 107 | { 108 | File.Delete(confPath); 109 | } 110 | 111 | IniFile ini = new IniFile(confPath); 112 | ini.IniWriteValue("main", "url", url); 113 | ini.IniWriteValue("main", "token", token); 114 | } 115 | 116 | public static PrebuildConfig getDataForBuild(gitlab_ci_runner.api.BuildInfo b) 117 | { 118 | if (!isConfigured()) 119 | { 120 | throw new NotImplementedException("Situation when no configuration file is provided is not supported yet. Make sure that runner.cfg exists at the file location."); 121 | } 122 | IniParser.FileIniDataParser ini = new IniParser.FileIniDataParser(); 123 | IniParser.Model.IniData data = ini.ReadFile(confPath); 124 | 125 | IniParser.Model.SectionData section = null; 126 | foreach (var sect in data.Sections) 127 | { 128 | string[] projectIdentifiers = sect.SectionName.Split(new char[] {'|'} ); 129 | foreach(var p in projectIdentifiers) 130 | { 131 | string[] components = p.Split( new char[] {'='} ); 132 | if (components.Length == 2) 133 | { 134 | switch (components[0]) 135 | { 136 | case "id": 137 | if (Convert.ToInt32(components[1]) == b.project_id) 138 | { 139 | section = sect; 140 | } 141 | break; 142 | case "name": 143 | if (components[1] == b.project_name || components[1] == Regex.Replace(b.project_name, @"\s+", "")) 144 | { 145 | section = sect; 146 | } 147 | break; 148 | default: 149 | throw new Exception(String.Format("Cannot parse the {0} due to unknown project filter {1}", confPath, p)); 150 | } 151 | } 152 | else 153 | { 154 | if (p == "*") 155 | { 156 | section = sect; 157 | } 158 | else 159 | { 160 | if (p!="main") 161 | throw new Exception(String.Format("Cannot parse the {0} while searching for the project prebuild steps commands. Section {1} cannot be recognized.", confPath, p)); 162 | } 163 | } 164 | if (section != null) 165 | { 166 | break; 167 | } 168 | } 169 | if (section != null) 170 | { 171 | break; 172 | } 173 | } 174 | return new PrebuildConfig(b, section); 175 | } 176 | 177 | /// 178 | /// Is the runner already configured? 179 | /// 180 | /// true if configured, false if not 181 | public static bool isConfigured() 182 | { 183 | if (url != null && url != "" && token != null && token != "") 184 | { 185 | return true; 186 | } 187 | else 188 | { 189 | return false; 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /gitlab-ci-runner/runner/Build.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using gitlab_ci_runner.api; 10 | using Microsoft.Experimental.IO; 11 | using gitlab_ci_runner.conf; 12 | 13 | namespace gitlab_ci_runner.runner 14 | { 15 | class Build 16 | { 17 | /// 18 | /// Build completed? 19 | /// 20 | public bool completed { get; private set; } 21 | 22 | /// 23 | /// Command output 24 | /// Build internal! 25 | /// 26 | private ConcurrentQueue outputList; 27 | 28 | /// 29 | /// Command output 30 | /// 31 | public string output 32 | { 33 | get 34 | { 35 | string t; 36 | while (outputList.TryPeek(out t) && string.IsNullOrEmpty(t)) 37 | { 38 | outputList.TryDequeue(out t); 39 | } 40 | return String.Join("\n", outputList.ToArray()) + "\n"; 41 | } 42 | } 43 | 44 | /// 45 | /// Project Directory 46 | /// 47 | private string sProjectDir; 48 | 49 | /// 50 | /// Build Infos 51 | /// 52 | public BuildInfo buildInfo; 53 | 54 | /// 55 | /// Command list 56 | /// 57 | private LinkedList commands; 58 | 59 | /// 60 | /// Execution State 61 | /// 62 | public State state = State.WAITING; 63 | 64 | /// 65 | /// Command Timeout 66 | /// 67 | public int iTimeout 68 | { 69 | get 70 | { 71 | return this.buildInfo.timeout; 72 | } 73 | } 74 | 75 | /// 76 | /// Constructor 77 | /// 78 | /// Build Info 79 | public Build(BuildInfo buildInfo) 80 | { 81 | this.buildInfo = buildInfo; 82 | Config.PrebuildConfig cfg = Config.getDataForBuild(buildInfo); 83 | 84 | if (cfg.ProjectDir != "") 85 | { 86 | if (Path.IsPathRooted(cfg.ProjectDir)) 87 | { 88 | sProjectDir = cfg.ProjectDir; 89 | } 90 | else 91 | { 92 | sProjectDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\" + cfg.ProjectDir; 93 | } 94 | } 95 | sProjectDir = sProjectDir.EndsWith(@"\") ? sProjectDir : sProjectDir + @"\"; 96 | 97 | commands = new LinkedList(); 98 | outputList = new ConcurrentQueue(); 99 | completed = false; 100 | 101 | commands.AddFirst("echo \"Project directory is set to: " + cfg.ProjectDir + "\""); 102 | } 103 | 104 | /// 105 | /// Run the Build Job 106 | /// 107 | public void run() 108 | { 109 | state = State.RUNNING; 110 | 111 | try { 112 | 113 | // Initialize project dir 114 | initProjectDir(); 115 | 116 | // Add build commands 117 | foreach (string sCommand in buildInfo.GetCommands ()) 118 | { 119 | commands.AddLast(sCommand); 120 | } 121 | 122 | // Execute 123 | foreach (string sCommand in commands) 124 | { 125 | if (!exec(sCommand)) 126 | { 127 | state = State.FAILED; 128 | break; 129 | } 130 | } 131 | 132 | if (state == State.RUNNING) 133 | { 134 | state = State.SUCCESS; 135 | } 136 | 137 | } catch (Exception rex) { 138 | outputList.Enqueue(""); 139 | outputList.Enqueue("A runner exception occoured: " + rex.Message); 140 | outputList.Enqueue(""); 141 | state = State.FAILED; 142 | } 143 | 144 | 145 | completed = true; 146 | } 147 | 148 | /// 149 | /// Initialize project dir and checkout repo 150 | /// 151 | private void initProjectDir() 152 | { 153 | 154 | string sProjectsDir = System.IO.Directory.GetParent(sProjectDir).FullName; 155 | // Check if projects directory exists 156 | if (!Directory.Exists(sProjectsDir)) 157 | { 158 | // Create projects directory 159 | Directory.CreateDirectory(sProjectsDir); 160 | } 161 | 162 | // Check if already a git repo 163 | if (Directory.Exists(sProjectDir + @".git") && buildInfo.allow_git_fetch) 164 | { 165 | //string status = String.Format("Git repo exists ({0}) and fetch command is allowed (allow_git_fetch={1})", sProjectDir + @".git", Convert.ToString(buildInfo.allow_git_fetch)); 166 | //commands.AddLast("echo \"" + status + "\""); 167 | 168 | // Already a git repo, pull changes 169 | commands.AddLast(fetchCmd()); 170 | } 171 | else 172 | { 173 | // No git repo, checkout 174 | if (Directory.Exists(sProjectDir)) 175 | { 176 | DeleteDirectory(sProjectDir); 177 | } 178 | 179 | commands.AddLast(cloneCmd()); 180 | } 181 | 182 | Config.PrebuildConfig cfg = Config.getDataForBuild(buildInfo); 183 | if (cfg.PostPrepare != "") 184 | { 185 | commands.AddLast(cfg.PostPrepare); 186 | } 187 | } 188 | 189 | /// 190 | /// Execute a command 191 | /// 192 | /// Command to execute 193 | private bool exec(string sCommand) 194 | { 195 | try 196 | { 197 | // Remove Whitespaces 198 | sCommand = sCommand.Trim(); 199 | 200 | // Output command 201 | outputList.Enqueue(""); 202 | outputList.Enqueue(sCommand); 203 | outputList.Enqueue(""); 204 | 205 | // Build process 206 | Process p = new Process(); 207 | p.StartInfo.UseShellExecute = false; 208 | if (Directory.Exists(sProjectDir)) 209 | { 210 | p.StartInfo.WorkingDirectory = sProjectDir; // Set Current Working Directory to project directory 211 | } 212 | p.StartInfo.FileName = "cmd.exe"; // use cmd.exe so we dont have to split our command in file name and arguments 213 | p.StartInfo.Arguments = "/C \"" + sCommand + "\""; // pass full command as arguments 214 | 215 | // Environment variables 216 | p.StartInfo.EnvironmentVariables["HOME"] = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); // Fix for missing SSH Key 217 | 218 | p.StartInfo.EnvironmentVariables["BUNDLE_GEMFILE"] = sProjectDir + @"\Gemfile"; 219 | p.StartInfo.EnvironmentVariables["BUNDLE_BIN_PATH"] = ""; 220 | p.StartInfo.EnvironmentVariables["RUBYOPT"] = ""; 221 | 222 | p.StartInfo.EnvironmentVariables["CI_SERVER"] = "yes"; 223 | p.StartInfo.EnvironmentVariables["CI_SERVER_NAME"] = "GitLab CI"; 224 | p.StartInfo.EnvironmentVariables["CI_SERVER_VERSION"] = null; // GitlabCI Version 225 | p.StartInfo.EnvironmentVariables["CI_SERVER_REVISION"] = null; // GitlabCI Revision 226 | 227 | p.StartInfo.EnvironmentVariables["CI_BUILD_REF"] = buildInfo.sha; 228 | p.StartInfo.EnvironmentVariables["CI_BUILD_REF_NAME"] = buildInfo.@ref; 229 | p.StartInfo.EnvironmentVariables["CI_BUILD_ID"] = buildInfo.id.ToString(); 230 | 231 | // Redirect Standard Output and Standard Error 232 | p.StartInfo.RedirectStandardOutput = true; 233 | p.StartInfo.RedirectStandardError = true; 234 | p.OutputDataReceived += new DataReceivedEventHandler(outputHandler); 235 | p.ErrorDataReceived += new DataReceivedEventHandler(outputHandler); 236 | 237 | try 238 | { 239 | // Run the command 240 | p.Start(); 241 | p.BeginOutputReadLine(); 242 | p.BeginErrorReadLine(); 243 | 244 | if (!p.WaitForExit(iTimeout * 1000)) 245 | { 246 | p.Kill(); 247 | } 248 | return p.ExitCode == 0; 249 | } 250 | finally 251 | { 252 | p.OutputDataReceived -= new DataReceivedEventHandler(outputHandler); 253 | p.ErrorDataReceived -= new DataReceivedEventHandler(outputHandler); 254 | } 255 | } 256 | catch (Exception) 257 | { 258 | return false; 259 | } 260 | } 261 | 262 | /// 263 | /// STDOUT/STDERR Handler 264 | /// 265 | /// Source process 266 | /// Output Line 267 | private void outputHandler(object sendingProcess, DataReceivedEventArgs outLine) 268 | { 269 | if (!String.IsNullOrEmpty(outLine.Data)) 270 | { 271 | outputList.Enqueue(outLine.Data); 272 | } 273 | } 274 | 275 | /// 276 | /// Get the Clone CMD 277 | /// 278 | /// Clone CMD 279 | private string cloneCmd() 280 | { 281 | Config.PrebuildConfig cfg = Config.getDataForBuild(buildInfo); 282 | String sCmd = ""; 283 | 284 | // Change to drive 285 | sCmd = sProjectDir.Substring(0, 1) + ":"; 286 | // Change to directory 287 | sCmd += " && cd " + System.IO.Directory.GetParent(sProjectDir.TrimEnd('\\')).FullName; 288 | if (cfg.NewRepoInit == "") 289 | { 290 | // Git Clone 291 | sCmd += " && git clone " + buildInfo.repo_url + " " + Path.GetFileName(sProjectDir.TrimEnd('\\')); 292 | // Change to directory 293 | sCmd += " && cd " + sProjectDir; 294 | // Git Checkout 295 | sCmd += " && git checkout " + buildInfo.sha; 296 | } 297 | else 298 | { 299 | sCmd += " && " + cfg.NewRepoInit; 300 | } 301 | 302 | return sCmd; 303 | } 304 | 305 | /// 306 | /// Get the Fetch CMD 307 | /// 308 | /// Fetch CMD 309 | private string fetchCmd() 310 | { 311 | String sCmd = ""; 312 | 313 | // Change to drive 314 | sCmd = sProjectDir.Substring(0, 1) + ":"; 315 | // Change to directory 316 | sCmd += " && cd " + sProjectDir; 317 | 318 | Config.PrebuildConfig cfg = Config.getDataForBuild(buildInfo); 319 | if (cfg.ExistingRepoInit == "") 320 | { 321 | // Git Reset 322 | sCmd += " && git reset --hard"; 323 | // Git Clean 324 | sCmd += " && git clean -f"; 325 | // Git fetch 326 | sCmd += " && git fetch"; 327 | // Git Checkout 328 | sCmd += " && git checkout " + buildInfo.sha; 329 | } 330 | else 331 | { 332 | sCmd += " && " + cfg.ExistingRepoInit; 333 | } 334 | 335 | 336 | return sCmd; 337 | } 338 | 339 | /// 340 | /// Delete non empty directory tree 341 | /// 342 | private void DeleteDirectory(string target_dir) 343 | { 344 | string[] files = Directory.GetFiles(target_dir); 345 | string[] dirs = Directory.GetDirectories(target_dir); 346 | 347 | foreach (string file in files) 348 | { 349 | try 350 | { 351 | File.SetAttributes(file, FileAttributes.Normal); 352 | File.Delete(file); 353 | } 354 | catch (PathTooLongException) 355 | { 356 | LongPathFile.Delete(file); 357 | } 358 | } 359 | 360 | foreach (string dir in dirs) 361 | { 362 | // Only recurse into "normal" directories 363 | if ((File.GetAttributes(dir) & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint) 364 | try 365 | { 366 | Directory.Delete(dir, false); 367 | } 368 | catch (PathTooLongException) 369 | { 370 | LongPathDirectory.Delete(dir); 371 | } 372 | else 373 | DeleteDirectory(dir); 374 | } 375 | 376 | try 377 | { 378 | Directory.Delete(target_dir, false); 379 | } 380 | catch (PathTooLongException) 381 | { 382 | LongPathDirectory.Delete(target_dir); 383 | } 384 | } 385 | } 386 | } 387 | --------------------------------------------------------------------------------