diff --git a/.gitignore b/.gitignore index c2e1708217..414ddb4426 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ project.lock.json *DS_Store *.ncrunchsolution *.*sdf -*.ipch \ No newline at end of file +*.ipch +*.vs/ \ No newline at end of file diff --git a/src/Microsoft.AspNet.Tools.PublishIIS/Program.cs b/src/Microsoft.AspNet.Tools.PublishIIS/Program.cs new file mode 100644 index 0000000000..d3ceadffa9 --- /dev/null +++ b/src/Microsoft.AspNet.Tools.PublishIIS/Program.cs @@ -0,0 +1,73 @@ +using System; +using System.IO; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.AspNet.Tools.PublishIIS +{ + public class Program + { + public static int Main(string[] args) + { + var app = new CommandLineApplication + { + // TODO: This needs to be updated once we know how this is going to be called + Name = "dotnet(????) publish-iis", + FullName = "Asp.Net IIS Publisher", + Description = "IIS Publisher for the Asp.Net web applications", + }; + app.HelpOption("-h|--help"); + + var publishFolderOption = app.Option("--publish-folder", "The path to the publish output folder", CommandOptionType.SingleValue); + var appNameOption = app.Option("--application-name", "The name of the application", CommandOptionType.SingleValue); + + app.OnExecute(() => + { + var publishFolder = publishFolderOption.Value(); + var appName = appNameOption.Value(); + + if (publishFolder == null || appName == null) + { + app.ShowHelp(); + return 2; + } + + XDocument webConfigXml = null; + var webConfigPath = Path.Combine(publishFolder, "wwwroot", "web.config"); + if (File.Exists(webConfigPath)) + { + try + { + webConfigXml = XDocument.Load(webConfigPath); + } + catch (XmlException) { } + } + + var transformedConfig = WebConfigTransform.Transform(webConfigXml, appName); + + using (var f = new FileStream(webConfigPath, FileMode.Create)) + { + transformedConfig.Save(f); + } + + return 0; + }); + + try + { + return app.Execute(args); + } + catch (Exception e) + { +#if DEBUG + Console.Error.WriteLine(e); +#else + Console.Error.WriteLine(ex.Message); +#endif + } + + return 1; + } + } +} diff --git a/src/Microsoft.AspNet.Tools.PublishIIS/WebConfigTransform.cs b/src/Microsoft.AspNet.Tools.PublishIIS/WebConfigTransform.cs new file mode 100644 index 0000000000..288de27ed7 --- /dev/null +++ b/src/Microsoft.AspNet.Tools.PublishIIS/WebConfigTransform.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +namespace Microsoft.AspNet.Tools.PublishIIS +{ + public static class WebConfigTransform + { + public static XDocument Transform(XDocument webConfig, string appName) + { + const string HandlersElementName = "handlers"; + const string httpPlatformElementName = "httpPlatform"; + + webConfig = webConfig == null || webConfig.Root.Name.LocalName != "configuration" + ? XDocument.Parse("") + : webConfig; + + var webServerSection = GetOrCreateChild(webConfig.Root, "system.webServer"); + + TransformHandlers(GetOrCreateChild(webServerSection, HandlersElementName)); + TransformHttpPlatform(GetOrCreateChild(webServerSection, httpPlatformElementName), appName); + + // make sure that the httpPlatform element is after handlers element + var httpPlatformElement = webServerSection.Element(HandlersElementName) + .ElementsBeforeSelf(httpPlatformElementName).SingleOrDefault(); + if (httpPlatformElement != null) + { + httpPlatformElement.Remove(); + webServerSection.Element(HandlersElementName).AddAfterSelf(httpPlatformElement); + } + + return webConfig; + } + + private static void TransformHandlers(XElement handlersElement) + { + var platformHandlerElement = + handlersElement.Elements("add") + .FirstOrDefault(e => string.Equals((string)e.Attribute("name"), "httpplatformhandler", StringComparison.OrdinalIgnoreCase)); + + if (platformHandlerElement == null) + { + platformHandlerElement = new XElement("add"); + handlersElement.Add(platformHandlerElement); + } + + platformHandlerElement.SetAttributeValue("name", "httpPlatformHandler"); + SetAttributeValueIfEmpty(platformHandlerElement, "path", "*"); + SetAttributeValueIfEmpty(platformHandlerElement, "verb", "*"); + SetAttributeValueIfEmpty(platformHandlerElement, "modules", "httpPlatformHandler"); + SetAttributeValueIfEmpty(platformHandlerElement, "resourceType", "Unspecified"); + } + + private static void TransformHttpPlatform(XElement httpPlatformElement, string appName) + { + httpPlatformElement.SetAttributeValue("processPath", $@"..\wwwroot\{appName}"); + SetAttributeValueIfEmpty(httpPlatformElement, "stdoutLogEnabled", "false"); + SetAttributeValueIfEmpty(httpPlatformElement, "startupTimeLimit", "3600"); + } + + private static XElement GetOrCreateChild(XElement parent, string childName) + { + var childElement = parent.Element(childName); + if (childElement == null) + { + childElement = new XElement(childName); + parent.Add(childElement); + } + return childElement; + } + + private static void SetAttributeValueIfEmpty(XElement element, string attributeName, string value) + { + element.SetAttributeValue(attributeName, (string)element.Attribute(attributeName) ?? value); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Tools.PublishIIS/project.json b/src/Microsoft.AspNet.Tools.PublishIIS/project.json new file mode 100644 index 0000000000..cd6d70d6b3 --- /dev/null +++ b/src/Microsoft.AspNet.Tools.PublishIIS/project.json @@ -0,0 +1,19 @@ +{ + "name": "publish-iis", + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + + "dependencies": { + "NETStandard.Library": "1.0.0-*", + "System.Xml.XDocument": "4.0.11-beta-*", + "Microsoft.Extensions.CommandLineUtils": "1.0.0-*" + }, + + "frameworks": { + "dnxcore50": { + "Microsoft.NETCore.Platforms": "1.0.0-*" + } + } +} diff --git a/test/Microsoft.AspNet.Tools.PublishIIS.Tests/WebConfigTransformFacts.cs b/test/Microsoft.AspNet.Tools.PublishIIS.Tests/WebConfigTransformFacts.cs new file mode 100644 index 0000000000..74dc5e518d --- /dev/null +++ b/test/Microsoft.AspNet.Tools.PublishIIS.Tests/WebConfigTransformFacts.cs @@ -0,0 +1,111 @@ +using Xunit; +using System.Linq; +using System.Xml.Linq; +using Microsoft.AspNet.Tools.PublishIIS; + +namespace Microsoft.AspNet.Tools.PublishIIS.Tests +{ + public class WebConfigTransformFacts + { + private XDocument WebConfigTemplate => XDocument.Parse( +@" + + + + + + +"); + + [Fact] + public void WebConfigTransform_creates_new_config_if_one_does_not_exist() + { + Assert.True(XNode.DeepEquals(WebConfigTemplate, WebConfigTransform.Transform(null, "test.exe"))); + } + + [Fact] + public void WebConfigTransform_creates_new_config_if_one_has_unexpected_format() + { + Assert.True(XNode.DeepEquals(WebConfigTemplate, WebConfigTransform.Transform(XDocument.Parse(""), "test.exe"))); + } + + [Theory] + [InlineData(new object[] { new[] {"system.webServer"}})] + [InlineData(new object[] { new[] {"add"}})] + [InlineData(new object[] { new[] {"handlers"}})] + [InlineData(new object[] { new[] {"httpPlatform"}})] + [InlineData(new object[] { new[] {"handlers", "httpPlatform"}})] + public void WebConfigTransform_adds_missing_elements(string[] elementNames) + { + var input = new XDocument(WebConfigTemplate); + foreach (var elementName in elementNames) + { + input.Descendants(elementName).Remove(); + } + + Assert.True(XNode.DeepEquals(WebConfigTemplate, WebConfigTransform.Transform(input, "test.exe"))); + } + + [Theory] + [InlineData("add", "path", "test")] + [InlineData("add", "verb","test")] + [InlineData("add", "modules", "mods")] + [InlineData("add", "resourceType", "Either")] + [InlineData("httpPlatform", "stdoutLogEnabled", "true")] + [InlineData("httpPlatform", "startupTimeLimit", "1200")] + [InlineData("httpPlatform", "arguments", "arg1")] + [InlineData("httpPlatform", "stdoutLogFile", "logfile.log")] + public void WebConfigTransform_wont_override_custom_values(string elementName, string attributeName, string attributeValue) + { + var input = new XDocument(WebConfigTemplate); + input.Descendants(elementName).Single().SetAttributeValue(attributeName, attributeValue); + + var output = WebConfigTransform.Transform(input, "test.exe"); + Assert.Equal(attributeValue, (string)output.Descendants(elementName).Single().Attribute(attributeName)); + } + + [Fact] + public void WebConfigTransform_overwrites_processPath() + { + var newProcessPath = + (string)WebConfigTransform.Transform(WebConfigTemplate, "app.exe") + .Descendants("httpPlatform").Single().Attribute("processPath"); + + Assert.Equal(@"..\wwwroot\app.exe", newProcessPath); + } + + [Fact] + public void WebConfigTransform_fixes_httpPlatformHandler_casing() + { + var input = new XDocument(WebConfigTemplate); + input.Descendants("add").Single().SetAttributeValue("name", "httpplatformhandler"); + + Assert.True(XNode.DeepEquals(WebConfigTemplate, WebConfigTransform.Transform(input, "test.exe"))); + } + + [Fact] + public void WebConfigTransform_does_not_remove_children_of_httpPlatform_element() + { + var envVarsElement = + new XElement("environmentVariables", + new XElement("environmentVariable", new XAttribute("name", "ENVVAR"), new XAttribute("value", "123"))); + + var input = new XDocument(WebConfigTemplate); + input.Descendants("httpPlatform").Single().Add(envVarsElement); + + Assert.True(XNode.DeepEquals(envVarsElement, + WebConfigTransform.Transform(input, "app.exe").Descendants("httpPlatform").Single().Elements().Single())); + } + + private bool VerifyMissingElementCreated(params string[] elementNames) + { + var input = new XDocument(WebConfigTemplate); + foreach (var elementName in elementNames) + { + input.Descendants(elementName).Remove(); + } + + return XNode.DeepEquals(WebConfigTemplate, WebConfigTransform.Transform(input, "test.exe")); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Tools.PublishIIS.Tests/project.json b/test/Microsoft.AspNet.Tools.PublishIIS.Tests/project.json new file mode 100644 index 0000000000..d5060c0ace --- /dev/null +++ b/test/Microsoft.AspNet.Tools.PublishIIS.Tests/project.json @@ -0,0 +1,19 @@ +{ + "dependencies": { + "xunit": "2.1.0", + "Microsoft.AspNet.Tools.PublishIIS" : "" + }, + + "frameworks": { + "dnxcore50": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.0-*", + "xunit.runner.aspnet": "2.0.0-aspnet-*" + } + } + }, + + "commands": { + "test": "xunit.runner.aspnet" + } +} \ No newline at end of file