PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/Kudu.SiteManagement/SiteManager.cs

https://github.com/moacap/kudu
C# | 457 lines | 381 code | 56 blank | 20 comment | 16 complexity | f095d04918e5410eb91972f361ecdfa5 MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net.NetworkInformation;
  7. using System.Threading;
  8. using Kudu.Core.Infrastructure;
  9. using IIS = Microsoft.Web.Administration;
  10. namespace Kudu.SiteManagement
  11. {
  12. public class SiteManager : ISiteManager
  13. {
  14. private static Random portNumberGenRnd = new Random((int)DateTime.Now.Ticks);
  15. private readonly IPathResolver _pathResolver;
  16. private readonly bool _traceFailedRequests;
  17. private readonly string _logPath;
  18. public SiteManager(IPathResolver pathResolver)
  19. : this(pathResolver, traceFailedRequests: false, logPath: null)
  20. {
  21. }
  22. public SiteManager(IPathResolver pathResolver, bool traceFailedRequests, string logPath)
  23. {
  24. _logPath = logPath;
  25. _pathResolver = pathResolver;
  26. _traceFailedRequests = traceFailedRequests;
  27. }
  28. public IEnumerable<string> GetSites()
  29. {
  30. var iis = new IIS.ServerManager();
  31. // The app pool is the app name
  32. return iis.Sites.Where(x => x.Name.StartsWith("kudu_"))
  33. .Select(x => x.Applications[0].ApplicationPoolName)
  34. .Distinct();
  35. }
  36. public Site GetSite(string applicationName)
  37. {
  38. var iis = new IIS.ServerManager();
  39. var mainSiteName = GetLiveSite(applicationName);
  40. var serviceSiteName = GetServiceSite(applicationName);
  41. var devSiteName = GetDevSite(applicationName);
  42. IIS.Site mainSite = iis.Sites[mainSiteName];
  43. if (mainSite == null)
  44. {
  45. return null;
  46. }
  47. IIS.Site serviceSite = iis.Sites[serviceSiteName];
  48. IIS.Site devSite = iis.Sites[devSiteName];
  49. var site = new Site();
  50. site.SiteUrl = GetSiteUrl(mainSite);
  51. site.ServiceUrl = GetSiteUrl(serviceSite);
  52. site.DevSiteUrl = GetSiteUrl(devSite);
  53. return site;
  54. }
  55. private string GetSiteUrl(IIS.Site site)
  56. {
  57. if (site == null)
  58. {
  59. return null;
  60. }
  61. IIS.Binding binding = site.Bindings.Last();
  62. var builder = new UriBuilder
  63. {
  64. Host = String.IsNullOrEmpty(binding.Host) ? "localhost" : binding.Host,
  65. Scheme = binding.Protocol,
  66. Port = binding.EndPoint.Port
  67. };
  68. if (builder.Port == 80)
  69. {
  70. builder.Port = -1;
  71. }
  72. return builder.ToString();
  73. }
  74. public Site CreateSite(string applicationName)
  75. {
  76. var iis = new IIS.ServerManager();
  77. try
  78. {
  79. // Create the service site for this site
  80. string serviceSiteName = GetServiceSite(applicationName);
  81. int serviceSitePort = CreateSite(iis, applicationName, serviceSiteName, _pathResolver.ServiceSitePath);
  82. // Create the main site
  83. string siteName = GetLiveSite(applicationName);
  84. string siteRoot = _pathResolver.GetLiveSitePath(applicationName);
  85. string webRoot = Path.Combine(siteRoot, Constants.WebRoot);
  86. FileSystemHelpers.EnsureDirectory(webRoot);
  87. File.WriteAllText(Path.Combine(webRoot, "index.html"), @"<html>
  88. <head>
  89. <title>The web site is under construction</title>
  90. <style type=""text/css"">
  91. BODY { color: #444444; background-color: #E5F2FF; font-family: verdana; margin: 0px; text-align: center; margin-top: 100px; }
  92. H1 { font-size: 16pt; margin-bottom: 4px; }
  93. </style>
  94. </head>
  95. <body>
  96. <h1>The web site is under construction</h1><br/>
  97. </body>
  98. </html>");
  99. int sitePort = CreateSite(iis, applicationName, siteName, webRoot);
  100. // Map a path called app to the site root under the service site
  101. MapServiceSitePath(iis, applicationName, Constants.MappedLiveSite, siteRoot);
  102. // Commit the changes to iis
  103. iis.CommitChanges();
  104. // Give IIS some time to create the site and map the path
  105. // REVIEW: Should we poll the site's state?
  106. Thread.Sleep(1000);
  107. return new Site
  108. {
  109. ServiceUrl = String.Format("http://localhost:{0}/", serviceSitePort),
  110. SiteUrl = String.Format("http://localhost:{0}/", sitePort),
  111. };
  112. }
  113. catch
  114. {
  115. DeleteSite(applicationName);
  116. throw;
  117. }
  118. }
  119. public bool TryCreateDeveloperSite(string applicationName, out string siteUrl)
  120. {
  121. var iis = new IIS.ServerManager();
  122. string devSiteName = GetDevSite(applicationName);
  123. IIS.Site site = iis.Sites[devSiteName];
  124. if (site == null)
  125. {
  126. // Get the path to the dev site
  127. string siteRoot = _pathResolver.GetDeveloperApplicationPath(applicationName);
  128. string webRoot = Path.Combine(siteRoot, Constants.WebRoot);
  129. int sitePort = CreateSite(iis, applicationName, devSiteName, webRoot);
  130. // Ensure the directory is created
  131. FileSystemHelpers.EnsureDirectory(webRoot);
  132. // Map a path called app to the site root under the service site
  133. MapServiceSitePath(iis, applicationName, Constants.MappedDevSite, siteRoot);
  134. iis.CommitChanges();
  135. siteUrl = String.Format("http://localhost:{0}/", sitePort);
  136. return true;
  137. }
  138. siteUrl = String.Format("http://localhost:{0}/", site.Bindings[0].EndPoint.Port);
  139. return false;
  140. }
  141. public void DeleteSite(string applicationName)
  142. {
  143. var iis = new IIS.ServerManager();
  144. // Get the app pool for this application
  145. string appPoolName = GetAppPool(applicationName);
  146. IIS.ApplicationPool kuduPool = iis.ApplicationPools[appPoolName];
  147. // Make sure the acls are gone
  148. RemoveAcls(applicationName, appPoolName);
  149. if (kuduPool == null)
  150. {
  151. // If there's no app pool then do nothing
  152. return;
  153. }
  154. DeleteSite(iis, GetLiveSite(applicationName));
  155. DeleteSite(iis, GetDevSite(applicationName));
  156. // Don't delete the physical files for the service site
  157. DeleteSite(iis, GetServiceSite(applicationName), deletePhysicalFiles: false);
  158. iis.CommitChanges();
  159. string appPath = _pathResolver.GetApplicationPath(applicationName);
  160. var sitePath = _pathResolver.GetLiveSitePath(applicationName);
  161. var devPath = _pathResolver.GetDeveloperApplicationPath(applicationName);
  162. try
  163. {
  164. kuduPool.StopAndWait();
  165. DeleteSafe(sitePath);
  166. DeleteSafe(devPath);
  167. DeleteSafe(appPath);
  168. }
  169. catch (Exception ex)
  170. {
  171. Debug.WriteLine(ex.ToString());
  172. }
  173. finally
  174. {
  175. // Remove the app pool and commit changes
  176. iis.ApplicationPools.Remove(iis.ApplicationPools[appPoolName]);
  177. iis.CommitChanges();
  178. }
  179. }
  180. public void SetDeveloperSiteWebRoot(string applicationName, string siteRoot)
  181. {
  182. var iis = new IIS.ServerManager();
  183. string siteName = GetDevSite(applicationName);
  184. IIS.Site site = iis.Sites[siteName];
  185. if (site != null)
  186. {
  187. string devSitePath = _pathResolver.GetDeveloperApplicationPath(applicationName);
  188. string webRoot = Path.Combine(devSitePath, Constants.WebRoot, siteRoot);
  189. // Change the web root
  190. site.Applications[0].VirtualDirectories[0].PhysicalPath = webRoot;
  191. iis.CommitChanges();
  192. Thread.Sleep(1000);
  193. }
  194. }
  195. private static void MapServiceSitePath(IIS.ServerManager iis, string applicationName, string path, string siteRoot)
  196. {
  197. string serviceSiteName = GetServiceSite(applicationName);
  198. // Get the service site
  199. IIS.Site site = iis.Sites[serviceSiteName];
  200. if (site == null)
  201. {
  202. throw new InvalidOperationException("Could not retrieve service site");
  203. }
  204. // Map the path to the live site in the service site
  205. site.Applications.Add(path, siteRoot);
  206. }
  207. private IIS.ApplicationPool EnsureAppPool(IIS.ServerManager iis, string appName)
  208. {
  209. string appPoolName = GetAppPool(appName);
  210. var kuduAppPool = iis.ApplicationPools[appPoolName];
  211. if (kuduAppPool == null)
  212. {
  213. iis.ApplicationPools.Add(appPoolName);
  214. iis.CommitChanges();
  215. kuduAppPool = iis.ApplicationPools[appPoolName];
  216. kuduAppPool.Enable32BitAppOnWin64 = true;
  217. kuduAppPool.ManagedPipelineMode = IIS.ManagedPipelineMode.Integrated;
  218. kuduAppPool.ManagedRuntimeVersion = "v4.0";
  219. kuduAppPool.AutoStart = true;
  220. kuduAppPool.ProcessModel.LoadUserProfile = true;
  221. kuduAppPool.WaitForState(IIS.ObjectState.Started);
  222. SetupAcls(appName, appPoolName);
  223. }
  224. return kuduAppPool;
  225. }
  226. private void RemoveAcls(string appName, string appPoolName)
  227. {
  228. // Setup Acls for this user
  229. var icacls = new Executable(@"C:\Windows\System32\icacls.exe", Directory.GetCurrentDirectory());
  230. string applicationPath = _pathResolver.GetApplicationPath(appName);
  231. try
  232. {
  233. // Give full control to the app folder (we can make it minimal later)
  234. icacls.Execute(@"""{0}\"" /remove ""IIS AppPool\{1}""", applicationPath, appPoolName);
  235. }
  236. catch (Exception ex)
  237. {
  238. Debug.WriteLine(ex.Message);
  239. }
  240. try
  241. {
  242. icacls.Execute(@"""{0}"" /remove ""IIS AppPool\{1}""", _pathResolver.ServiceSitePath, appPoolName);
  243. }
  244. catch (Exception ex)
  245. {
  246. Debug.WriteLine(ex.Message);
  247. }
  248. try
  249. {
  250. // Give full control to the temp folder
  251. string windowsTemp = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Temp");
  252. icacls.Execute(@"""{0}"" /remove ""IIS AppPool\{1}""", windowsTemp, appPoolName);
  253. }
  254. catch (Exception ex)
  255. {
  256. Debug.WriteLine(ex.Message);
  257. }
  258. }
  259. private void SetupAcls(string appName, string appPoolName)
  260. {
  261. // Setup Acls for this user
  262. var icacls = new Executable(@"C:\Windows\System32\icacls.exe", Directory.GetCurrentDirectory());
  263. // Make sure the application path exists
  264. string applicationPath = _pathResolver.GetApplicationPath(appName);
  265. Directory.CreateDirectory(applicationPath);
  266. try
  267. {
  268. // Give full control to the app folder (we can make it minimal later)
  269. icacls.Execute(@"""{0}"" /grant:r ""IIS AppPool\{1}:(OI)(CI)(F)"" /C /Q /T", applicationPath, appPoolName);
  270. }
  271. catch (Exception ex)
  272. {
  273. Debug.WriteLine(ex.Message);
  274. }
  275. try
  276. {
  277. icacls.Execute(@"""{0}"" /grant:r ""IIS AppPool\{1}:(OI)(CI)(RX)"" /C /Q /T", _pathResolver.ServiceSitePath, appPoolName);
  278. }
  279. catch (Exception ex)
  280. {
  281. Debug.WriteLine(ex.Message);
  282. }
  283. try
  284. {
  285. // Give full control to the temp folder
  286. string windowsTemp = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Temp");
  287. icacls.Execute(@"""{0}"" /grant:r ""IIS AppPool\{1}:(OI)(CI)(F)"" /C /Q /T", windowsTemp, appPoolName);
  288. }
  289. catch (Exception ex)
  290. {
  291. Debug.WriteLine(ex.Message);
  292. }
  293. }
  294. private int GetRandomPort(IIS.ServerManager iis)
  295. {
  296. int randomPort = portNumberGenRnd.Next(1025, 65535);
  297. while (!IsAvailable(randomPort, iis))
  298. {
  299. randomPort = portNumberGenRnd.Next(1025, 65535);
  300. }
  301. return randomPort;
  302. }
  303. private bool IsAvailable(int port, IIS.ServerManager iis)
  304. {
  305. var tcpConnections = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections();
  306. foreach (var connectionInfo in tcpConnections)
  307. {
  308. if (connectionInfo.LocalEndPoint.Port == port)
  309. {
  310. return false;
  311. }
  312. }
  313. foreach (var iisSite in iis.Sites)
  314. {
  315. foreach (var binding in iisSite.Bindings)
  316. {
  317. if (binding.EndPoint != null && binding.EndPoint.Port == port)
  318. {
  319. return false;
  320. }
  321. }
  322. }
  323. return true;
  324. }
  325. private int CreateSite(IIS.ServerManager iis, string applicationName, string siteName, string siteRoot)
  326. {
  327. var pool = EnsureAppPool(iis, applicationName);
  328. int sitePort = GetRandomPort(iis);
  329. var site = iis.Sites.Add(siteName, siteRoot, sitePort);
  330. site.ApplicationDefaults.ApplicationPoolName = pool.Name;
  331. if (_traceFailedRequests)
  332. {
  333. site.TraceFailedRequestsLogging.Enabled = true;
  334. string path = Path.Combine(_logPath, applicationName, "Logs");
  335. Directory.CreateDirectory(path);
  336. site.TraceFailedRequestsLogging.Directory = path;
  337. }
  338. return sitePort;
  339. }
  340. private void DeleteSite(IIS.ServerManager iis, string siteName, bool deletePhysicalFiles = true)
  341. {
  342. var site = iis.Sites[siteName];
  343. if (site != null)
  344. {
  345. site.StopAndWait();
  346. if (deletePhysicalFiles)
  347. {
  348. string physicalPath = site.Applications[0].VirtualDirectories[0].PhysicalPath;
  349. DeleteSafe(physicalPath);
  350. }
  351. iis.Sites.Remove(site);
  352. }
  353. }
  354. private static string GetDevSite(string applicationName)
  355. {
  356. return "kudu_dev_" + applicationName;
  357. }
  358. private static string GetLiveSite(string applicationName)
  359. {
  360. return "kudu_" + applicationName;
  361. }
  362. private static string GetServiceSite(string applicationName)
  363. {
  364. return "kudu_service_" + applicationName;
  365. }
  366. private static string GetAppPool(string applicationName)
  367. {
  368. return applicationName;
  369. }
  370. private static void DeleteSafe(string physicalPath)
  371. {
  372. if (!Directory.Exists(physicalPath))
  373. {
  374. return;
  375. }
  376. FileSystemHelpers.DeleteDirectorySafe(physicalPath);
  377. }
  378. }
  379. }