Skip to content

Instantly share code, notes, and snippets.

@grenade
Created April 28, 2016 00:38
Show Gist options
  • Save grenade/943109d7366b94784cd81cfeb92b63d0 to your computer and use it in GitHub Desktop.
Save grenade/943109d7366b94784cd81cfeb92b63d0 to your computer and use it in GitHub Desktop.
a firefox build minion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices.AccountManagement;
using System.IO;
using System.Linq;
using System.Security;
using System.Security.AccessControl;
using Newtonsoft.Json;
using System.Collections;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Security.Cryptography.X509Certificates;
namespace ConsoleApplication1
{
static class Program
{
static string[] RequiredAccessPaths = new[]
{
@"C:\Users\worker\workspace",
//@"C:\ProgramData\Microsoft\Crypto",
//@"C:\Windows\SysWOW64\config\systemprofile\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python",
};
static void Main(string[] args)
{
var workingDrive = Environment.GetEnvironmentVariable("SystemDrive").ToLower();
for (char d = 'x'; d <= 'z'; d++)
{
if(Directory.Exists(string.Concat(d, ":\\")))
{
workingDrive = string.Concat(d, ':');
break;
}
}
var results = new List<CommandResult>();
string username = string.Format("task-{0}", Guid.NewGuid().ToString().Substring(0, 8));
string password = Guid.NewGuid().ToString().Substring(0, 13);
string workingDirectory = Path.Combine(string.Concat(workingDrive, '\\'), username);
var user = CreateUser(username, password, workingDirectory, "Users");
var ds = new DirectorySecurity();
foreach (var sid in new[] { GroupPrincipal.FindByIdentity(new PrincipalContext(ContextType.Machine), "Administrators").Sid, user.Sid })
{
var fsar = new FileSystemAccessRule(sid, FileSystemRights.FullControl, AccessControlType.Allow);
ds.AddAccessRule(fsar);
}
foreach(var dir in new[] { workingDirectory, Path.Combine(workingDirectory, "Temp"), Path.Combine(workingDirectory, "AppData", "Roaming"), Path.Combine(workingDirectory, "AppData", "Local") })
Directory.CreateDirectory(workingDirectory, ds);
var protectedEnvironmentVariables = new List<EnvironmentVariable>()
{
new EnvironmentVariable("USERNAME", username),
new EnvironmentVariable("HOME", workingDirectory),
new EnvironmentVariable("HOMEDRIVE", workingDirectory.Split(':').First()),
new EnvironmentVariable("HOMEPATH", workingDirectory.Split(':').Last()),
new EnvironmentVariable("USERPROFILE", workingDirectory),
new EnvironmentVariable("TEMP", Path.Combine(workingDirectory, "Temp")),
new EnvironmentVariable("TMP", Path.Combine(workingDirectory, "Temp")),
new EnvironmentVariable("APPDATA", Path.Combine(workingDirectory, "AppData", "Roaming")),
new EnvironmentVariable("LOCALAPPDATA", Path.Combine(workingDirectory, "AppData", "Local"))
};
var payload = JsonConvert.DeserializeObject<Payload>(System.IO.File.ReadAllText(@"c:\data\payload.json"));
var environmentVariables = new List<EnvironmentVariable>(payload.Environment.Select(v => new EnvironmentVariable(v.Name, ExpandEnvironmentVariablesWithSubstitution(ExpandEnvironmentVariables(v.Value, payload.Environment), workingDirectory, username, password))));
protectedEnvironmentVariables.ForEach(pev =>
{
if (environmentVariables.Any(ev => Regex.IsMatch(ev.Name, pev.Name, RegexOptions.IgnoreCase)))
environmentVariables.First(ev => Regex.IsMatch(ev.Name, pev.Name, RegexOptions.IgnoreCase)).Value = pev.Value;
else
environmentVariables.Add(pev);
});
var impossibleSubstitutions = new Dictionary<string, string>();
while (environmentVariables.Any(v => Regex.IsMatch(v.Value, "%[^%^;]+%") && !Regex.IsMatch(v.Value, string.Format("%{0}%", v.Name), RegexOptions.IgnoreCase) && !Regex.Matches(v.Value, "%([^%^;]+)%").Cast<Match>().Select(r=>r.Groups[1].Value).Any(r => impossibleSubstitutions.ContainsKey(r.ToUpperInvariant()))))
{
foreach (var v in environmentVariables.Where(v => Regex.IsMatch(v.Value, "%[^%^;]+%") && !Regex.IsMatch(v.Value, string.Format("%{0}%", v.Name), RegexOptions.IgnoreCase)))
{
foreach (Match m in Regex.Matches(v.Value, "%([^%^;]+)%"))
if (!environmentVariables.Select(x=>x.Name).Any(name=> string.Equals(name, m.Groups[1].Value)) && string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(m.Groups[1].Value)))
{
if (!impossibleSubstitutions.ContainsKey(m.Groups[1].Value.ToUpperInvariant()))
impossibleSubstitutions.Add(m.Groups[1].Value.ToUpperInvariant(), m.Groups[1].Value);
v.Value = v.Value.Replace(string.Format("%{0}%", m.Groups[1].Value), string.Empty);
}
}
environmentVariables = new List<EnvironmentVariable>(environmentVariables.Select(v => new EnvironmentVariable(v.Name, ExpandEnvironmentVariablesWithSubstitution(ExpandEnvironmentVariables(v.Value, environmentVariables), workingDirectory, username, password))));
}
// merge system environment variables
//foreach (DictionaryEntry sev in Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine))
// if (!environmentVariables.Any(v => string.Equals(v.Name, (string)sev.Key, StringComparison.OrdinalIgnoreCase)))
// environmentVariables.Add(new EnvironmentVariable((string)sev.Key, (string)sev.Value));
// replace any variables in variable values
//for (int i = 0; i < environmentVariables.Count; i++)
//{
// var any = new Regex("%([^%^;]+)%");
// if (any.IsMatch(environmentVariables[i].Value))
// {
// //todo: deal with multiple matches var matchCOunt = any.Match(environmentVariables[i].Value).Groups.Count;
// var key = any.Match(environmentVariables[i].Value).Groups[1].Value;
// if (environmentVariables.Any(v => string.Equals(v.Name, key, StringComparison.OrdinalIgnoreCase)))
// {
// var value = environmentVariables.First(v => string.Equals(v.Name, key, StringComparison.OrdinalIgnoreCase)).Value;
// environmentVariables[i].Value = Regex.Replace(environmentVariables[i].Value, string.Format("%{0}%", key), value, RegexOptions.IgnoreCase);
// }
// else
// {
// environmentVariables[i].Value = ExpandEnvironmentVariablesWithSubstitution(environmentVariables[i].Value);
// }
// }
//}
// atempt to import moz certs
//var mozCertPath = Path.Combine(@"c:\", "mozilla-build", "msys", "etc", "ca-bundle.crt");
//if (File.Exists(mozCertPath))
//{
// var mozCert = new X509Certificate2(X509Certificate.CreateFromCertFile(mozCertPath));
// X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
// store.Open(OpenFlags.ReadWrite);
// var existing = store.Certificates.Find(X509FindType.FindByThumbprint, mozCert.Thumbprint, true);
// if (existing == null)
// store.Add(mozCert);
// store.Close();
//}
try
{
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine("minion found a payload with {0} commands and {1} environment variables.", payload.Commands.Count(), payload.Environment.Count());
Console.ResetColor();
var interrupt = false;
foreach (var command in payload.Commands)
{
var environmentBefore = new List<EnvironmentVariable>(environmentVariables);
command.File = ExpandEnvironmentVariablesWithSubstitution(ExpandEnvironmentVariables(command.File, environmentVariables), workingDirectory);
command.Arguments = command.Arguments.Select(a => ExpandEnvironmentVariablesWithSubstitution(ExpandEnvironmentVariables(a, environmentVariables), workingDirectory));
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("{0} {1}", command.File, string.Join(" ", command.Arguments));
Console.ResetColor();
if (interrupt)
{
results.Add(new CommandResult()
{
ExitCode = -100000
});
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine("skipped due to previous errors.");
Console.ResetColor();
}
else
{
var errors = new List<string>();
var output = new List<string>();
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = command.File,
Arguments = command.Arguments.Any() ? string.Join(" ", command.Arguments) : null,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
WorkingDirectory = workingDirectory,
UserName = username,
Password = password.ToSecureString()
}
};
try
{
// pass environment changes to the command
environmentVariables.ForEach(e => {
if (process.StartInfo.EnvironmentVariables.ContainsKey(e.Name))
process.StartInfo.EnvironmentVariables[e.Name] = e.Value;
else
process.StartInfo.EnvironmentVariables.Add(e.Name, e.Value);
});
process.ErrorDataReceived += (s, ea) => {
errors.Add(ea.Data);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ea.Data);
Console.ResetColor();
};
process.OutputDataReceived += (s, ea) => {
output.Add(ea.Data);
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine(ea.Data);
Console.ResetColor();
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
if (process.ExitCode != 0)
{
if (!process.HasExited)
process.Kill();
interrupt = true;
}
// propagate environment changes from the command
// todo: validate that changes show up here
environmentVariables.Clear();
process.StartInfo.EnvironmentVariables.Cast<DictionaryEntry>().AsParallel().ForAll(v => environmentVariables.Add(new EnvironmentVariable((string)v.Key, (string)v.Value)));
if(process.ExitCode != 0)
{
results.Add(new CommandResult()
{
StartTime = process.StartTime,
ExitTime = process.ExitTime,
TotalProcessorTime = process.TotalProcessorTime,
ExitCode = process.ExitCode,
EnvironmentBefore = environmentBefore,
EnvironmentAfter = environmentVariables
});
foreach (var ev in environmentVariables.OrderBy(o => o.Name))
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write(ev.Name);
Console.ForegroundColor = ConsoleColor.Gray;
Console.Write(": ");
Console.ForegroundColor = ConsoleColor.Magenta;
Console.Write(ev.Value);
Console.Write('\n');
Console.ResetColor();
}
}
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine("paeon completed this task in {0} seconds.", process.TotalProcessorTime.TotalSeconds);
Console.ResetColor();
}
catch (Win32Exception win32Exception)
{
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine(win32Exception);
Console.ResetColor();
}
finally
{
process.CancelOutputRead();
process.CancelErrorRead();
if (!process.HasExited)
{
process.Kill();
}
}
}
}
}
catch (Exception exception)
{
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine(exception);
Console.ResetColor();
}
finally
{
var completed = results.Count(r => r.ExitCode == 0);
var skipped = results.Count(r => r.ExitCode == -100000);
var failed = results.Count(r => r.ExitCode != -100000 && r.ExitCode != 0);
DeleteUser(user);
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine("minion killed paeon {0} after she completed {1} tasks and failed at {2} in {3}.", username, completed, failed, new TimeSpan(results.Sum(r => r.TotalProcessorTime.Ticks)));
Console.ResetColor();
}
Console.ReadKey();
}
static SecureString ToSecureString(this string s)
{
var ss = new SecureString();
if (!string.IsNullOrEmpty(s))
foreach (var c in s.ToCharArray())
ss.AppendChar(c);
return ss;
}
private static UserPrincipal CreateUser(string username, string password, string homeDirectory, params string[] groups)
{
UserPrincipal up;
using (var mc = new PrincipalContext(ContextType.Machine))
{
up = new UserPrincipal(mc, username, password, true)
{
PasswordNeverExpires = true,
HomeDirectory = homeDirectory
};
up.Save();
groups.Select(group => GroupPrincipal.FindByIdentity(mc, group))
.AsParallel()
.Where(gp => gp != null)
.ForAll(gp =>
{
gp.Members.Add(up);
gp.Save();
});
}
var folders = new List<string>();
var files = new List<string>();
foreach (var path in RequiredAccessPaths)
{
GetSubfoldersAndFiles(path, ref folders, ref files);
foreach (var folder in folders)
{
AddPathSecurity(folder, username, FileSystemRights.Read | FileSystemRights.Traverse, AccessControlType.Allow);
}
foreach (var file in files)
{
AddPathSecurity(file, username, FileSystemRights.Read, AccessControlType.Allow);
}
}
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine("minion spawned a paeon {0} and commanded {1} to take her into their fold.", username, string.Join(", ", groups));
Console.ResetColor();
return up;
}
private static void DeleteUser(UserPrincipal user)
{
var username = user.SamAccountName;
foreach (var path in RequiredAccessPaths)
{
var folders = new List<string>();
var files = new List<string>();
GetSubfoldersAndFiles(path, ref folders, ref files);
foreach (var folder in folders)
{
RemovePathSecurity(folder, username, FileSystemRights.Read | FileSystemRights.Traverse, AccessControlType.Allow);
}
foreach (var file in files)
{
RemovePathSecurity(file, username, FileSystemRights.Read, AccessControlType.Allow);
}
}
user.Delete();
}
private static void AddPathSecurity(string path, string username, FileSystemRights fsr, AccessControlType act)
{
var di = new DirectoryInfo(path);
var ds = di.GetAccessControl();
ds.AddAccessRule(new FileSystemAccessRule(username, fsr, act));
try
{
di.SetAccessControl(ds);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("worker access granted: {0}.", path);
Console.ResetColor();
}
catch
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("worker access not granted: {0}.", path);
Console.ResetColor();
}
}
private static void RemovePathSecurity(string path, string username, FileSystemRights fsr, AccessControlType act)
{
var di = new DirectoryInfo(path);
var ds = di.GetAccessControl();
ds.RemoveAccessRule(new FileSystemAccessRule(username, fsr, act));
try
{
di.SetAccessControl(ds);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("worker access removed: {0}.", path);
Console.ResetColor();
}
catch
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("worker access not removed: {0}.", path);
Console.ResetColor();
}
}
private static void GetSubfoldersAndFiles(string path, ref List<string> folders, ref List<string> files)
{
if (!folders.Any())
folders.Add(path);
foreach (string folder in Directory.GetDirectories(path))
{
folders.Add(folder);
foreach (string file in Directory.GetFiles(folder))
{
files.Add(file);
}
GetSubfoldersAndFiles(folder, ref folders, ref files);
}
}
private static string ExpandEnvironmentVariables(string value, IEnumerable<EnvironmentVariable> environmentVariables)
{
foreach (var ev in environmentVariables)
if (value != ev.Value)
value = Regex.Replace(value, string.Format("%{0}%", ev.Name), ev.Value, RegexOptions.IgnoreCase);
return value;
}
private static string ExpandEnvironmentVariablesWithSubstitution(string value, string workingDirectory = null, string username = null, string password = null)
{
string result = string.Empty;
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd",
Arguments = string.Concat("/c echo ", value),
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
WorkingDirectory = workingDirectory,
UserName = username,
Password = password.ToSecureString()
}
};
process.OutputDataReceived += (s, e) => result = string.IsNullOrWhiteSpace(e.Data) ? result: e.Data;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelOutputRead();
if (!process.HasExited)
process.Kill();
return result;
}
}
public class Payload
{
public IEnumerable<Command> Commands { get; set; }
public IEnumerable<EnvironmentVariable> Environment { get; set; }
}
public class Command
{
public string File { get; set; }
public IEnumerable<string> Arguments { get; set; }
}
public class EnvironmentVariable
{
public EnvironmentVariable() { }
public EnvironmentVariable(string name, string value)
{
Name = name;
Value = value;
}
public string Name { get; set; }
public string Value { get; set; }
}
public class CommandResult
{
public IEnumerable<EnvironmentVariable> EnvironmentBefore { get; set; }
public IEnumerable<EnvironmentVariable> EnvironmentAfter { get; set; }
public int ExitCode { get; set; }
public DateTime StartTime { get; set; }
public DateTime ExitTime { get; set; }
public TimeSpan TotalProcessorTime { get; set; }
}
}
{
"environment":
[
{
"name": "GECKO_HEAD_REV",
"value": "a14283391eab25c83976735e057728aeb6fb8457"
},
{
"name": "WORKSPACE",
"value": "%cd%"
},
{
"name": "NO_CACHE",
"value": "1"
},
{
"name": "MOZHARNESS_ACTIONS",
"value": "get-secrets build check-test generate-build-stats update"
},
{
"name": "MACHTYPE",
"value": "i686-pc-msys"
},
{
"name": "MAKE_MODE",
"value": "unix"
},
{
"name": "MSYSTEM",
"value": "MINGW32"
},
{
"name": "TOOLTOOL_CACHE",
"value": "c:\\home\\worker\\tooltool-cache"
},
{
"name": "UPLOAD_HOST",
"value": "localhost"
},
{
"name": "UPLOAD_PATH",
"value": "c:\\home\\worker\\public\\build"
},
{
"name": "PATH",
"value": "%PATH:C:=c:%"
},
{
"name": "DISTUTILS_USE_SDK",
"value": "1"
},
{
"name": "MSSdk",
"value": "1"
},
{
"name": "MOZ_CRASHREPORTER_NO_REPORT",
"value": "1"
},
{
"name": "MOZ_OBJDIR",
"value": "obj-firefox"
},
{
"name": "TINDERBOX_OUTPUT",
"value": "1"
},
{
"name": "LIBPATH",
"value": "c:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319;c:\\Program Files (x86)\\Windows Kits\\8.1\\References\\CommonConfiguration\\Neutral"
},
{
"name": "LIBRARY_PATH",
"value": "%LIBRARY_PATH%;%WORKSPACE%\\src\\obj-firefox;%WORKSPACE%\\src\\gcc\\lib64"
},
{
"name": "SRCSRV_ROOT",
"value": "https://hg.mozilla.org/try"
},
{
"name": "VCINSTALLDIR",
"value": "C:\\Users\\worker\\Visual C++ for Python\\9.0\\VC"
}
],
"commands": [
{
"file": "cmd",
"arguments":
[
"/c",
"if",
"exist",
"%WORKSPACE%\\build",
"rmdir",
"%WORKSPACE%\\build",
"/s",
"/q"
]
},
{
"file": "cmd",
"arguments":
[
"/c",
"mkdir",
"%WORKSPACE%\\build\\tools"
]
},
{
"file": "hg",
"arguments": [
"clone",
"-U",
"c:\\builds\\hg-shared\\build\\tools",
"%WORKSPACE%\\build\\tools"
]
},
{
"file": "hg",
"arguments":
[
"pull",
"-u",
"-R",
"%WORKSPACE%\\build\\tools",
"--rev",
"default",
"http://hg.mozilla.org/build/tools"
]
},
{
"file": "hg",
"arguments":
[
"update",
"-R",
"%WORKSPACE%\\build\\tools",
"--rev",
"default"
]
},
{
"file": "cmd",
"arguments":
[
"/c",
"mkdir",
"%WORKSPACE%\\build\\src"
]
},
{
"file": "hg",
"arguments":
[
"clone",
"-U",
"c:\\builds\\hg-shared\\mozilla-central",
"%WORKSPACE%\\build\\src"
]
},
{
"file": "hg",
"arguments":
[
"pull",
"-u",
"-R",
"%WORKSPACE%\\build\\src",
"--rev",
"%GECKO_HEAD_REV%",
"http://hg.mozilla.org/try"
]
},
{
"file": "hg",
"arguments":
[
"update",
"-R",
"%WORKSPACE%\\build\\src",
"--rev",
"%GECKO_HEAD_REV%"
]
},
{
"file": "cp",
"arguments":
[
"C:\\Users\\worker\\workspace\\buildprops.json"
".\\buildprops.json",
]
},
{
"file": "\"%VCINSTALLDIR%\\..\\vcvarsall.bat\"",
"arguments": [ ]
},
{
"file": "python",
"arguments":
[
"%WORKSPACE%\\build\\src\\testing\\mozharness\\scripts\\fx_desktop_build.py",
"--config",
"builds\\releng_base_windows_64_builds.py",
"--disable-mock",
"--no-setup-mock",
"--no-checkout-sources",
"--no-clone-tools",
"--no-clobber",
"--no-update",
"--no-upload-files",
"--no-sendchange",
"--log-level=debug",
"--work-dir=%WORKSPACE%\\build",
"--no-action=generate-build-stats",
"--branch=try",
"--build-pool=staging"
]
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment