diff --git a/Configuration.sln b/Configuration.sln index c6956aa..5b878af 100644 --- a/Configuration.sln +++ b/Configuration.sln @@ -18,6 +18,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Spring.Extensions.Configuration.Server.IntegrationTest", "test\Spring.Extensions.Configuration.Server.IntegrationTest\Spring.Extensions.Configuration.Server.IntegrationTest.xproj", "{8D43B1B0-0C20-4AA5-B510-332E1190D0C3}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Spring.Extensions.Configuration.Cloudfoundry", "src\Spring.Extensions.Configuration.Cloudfoundry\Spring.Extensions.Configuration.Cloudfoundry.xproj", "{4A9D10C9-D365-41DD-9870-9D846A3D9EB1}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Spring.Extensions.Configuration.Cloudfoundry.Test", "test\Spring.Extensions.Configuration.Cloudfoundry.Test\Spring.Extensions.Configuration.Cloudfoundry.Test.xproj", "{9F29BED6-ECE3-42A2-8C57-C1A6831B9CF1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,6 +40,14 @@ Global {8D43B1B0-0C20-4AA5-B510-332E1190D0C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D43B1B0-0C20-4AA5-B510-332E1190D0C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D43B1B0-0C20-4AA5-B510-332E1190D0C3}.Release|Any CPU.Build.0 = Release|Any CPU + {4A9D10C9-D365-41DD-9870-9D846A3D9EB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A9D10C9-D365-41DD-9870-9D846A3D9EB1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A9D10C9-D365-41DD-9870-9D846A3D9EB1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A9D10C9-D365-41DD-9870-9D846A3D9EB1}.Release|Any CPU.Build.0 = Release|Any CPU + {9F29BED6-ECE3-42A2-8C57-C1A6831B9CF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F29BED6-ECE3-42A2-8C57-C1A6831B9CF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F29BED6-ECE3-42A2-8C57-C1A6831B9CF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F29BED6-ECE3-42A2-8C57-C1A6831B9CF1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -44,5 +56,7 @@ Global {D06283B3-995E-4AC2-B45F-DF6A6C377644} = {68054802-1B15-4550-8ED5-1899E5EA3DD9} {ECCB52BE-A124-4213-95A3-0C4842AA55A1} = {51AE05BE-C7D3-48A4-9B30-4D7B90283CD8} {8D43B1B0-0C20-4AA5-B510-332E1190D0C3} = {51AE05BE-C7D3-48A4-9B30-4D7B90283CD8} + {4A9D10C9-D365-41DD-9870-9D846A3D9EB1} = {68054802-1B15-4550-8ED5-1899E5EA3DD9} + {9F29BED6-ECE3-42A2-8C57-C1A6831B9CF1} = {51AE05BE-C7D3-48A4-9B30-4D7B90283CD8} EndGlobalSection EndGlobal diff --git a/src/Spring.Extensions.Configuration.Cloudfoundry/CloudFoundryConfigurationProvider.cs b/src/Spring.Extensions.Configuration.Cloudfoundry/CloudFoundryConfigurationProvider.cs new file mode 100644 index 0000000..690e54b --- /dev/null +++ b/src/Spring.Extensions.Configuration.Cloudfoundry/CloudFoundryConfigurationProvider.cs @@ -0,0 +1,104 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration.EnvironmentVariables; +using System.Collections; +using Microsoft.Extensions.Configuration.Json; +using System.IO; + +namespace Spring.Extensions.Configuration.Cloudfoundry +{ + public class CloudfoundryConfigurationProvider : ConfigurationProvider + { + private const string VCAP_PREFIX = "VCAP_"; + private const string APPLICATION = "APPLICATION"; + private const string SERVICES = "SERVICES"; + + public CloudfoundryConfigurationProvider() + { + } + public override void Load() + { + var builder = new ConfigurationBuilder(); + builder.Add(new EnvironmentVariablesConfigurationProvider(VCAP_PREFIX)); + var vcap = builder.Build(); + Process(vcap); + } + + private void Process(IConfigurationRoot vcap) + { + string appJson = vcap[APPLICATION]; + if (!string.IsNullOrEmpty(appJson)) + { + // TODO: Hack in order to use asp.net json config provider + // Need to write parser + var path = CreateTempFile(appJson); + ConfigurationBuilder builder = new ConfigurationBuilder(); + builder.AddJsonFile(path); + var applicationData = builder.Build(); + + if (applicationData != null) + { + LoadData("vcap:application", applicationData.GetChildren()); + } + } + + string appServicesJson = vcap[SERVICES]; + if (!string.IsNullOrEmpty(appServicesJson)) + { + // TODO: Hack in order to use asp.net json config provider + // Need to write parser + var path = CreateTempFile(appServicesJson); + ConfigurationBuilder builder = new ConfigurationBuilder(); + builder.AddJsonFile(path); + var servicesData = builder.Build(); + + if (servicesData != null) + { + LoadData("vcap:services", servicesData.GetChildren()); + } + } + + } + internal IDictionary Properties + { + get + { + return Data; + } + } + + private void LoadData(string prefix, IEnumerable sections) + { + if (sections == null || sections.Count() == 0) + { + return; + } + foreach(IConfigurationSection section in sections) + { + LoadSection(prefix, section); + LoadData(prefix, section.GetChildren()); + } + } + + private void LoadSection(string prefix, IConfigurationSection section) + { + if (section == null) + return; + if (string.IsNullOrEmpty(section.Value)) + return; + Data[prefix + Constants.KeyDelimiter + section.Path] = section.Value; + } + + // TODO: Remove + private static string CreateTempFile(string contents) + { + var tempFile = Path.GetTempFileName(); + File.WriteAllText(tempFile, contents); + return tempFile; + + } + } +} diff --git a/src/Spring.Extensions.Configuration.Cloudfoundry/CloudfoundryConfigurationExtensions.cs b/src/Spring.Extensions.Configuration.Cloudfoundry/CloudfoundryConfigurationExtensions.cs new file mode 100644 index 0000000..ea86855 --- /dev/null +++ b/src/Spring.Extensions.Configuration.Cloudfoundry/CloudfoundryConfigurationExtensions.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Spring.Extensions.Configuration.Cloudfoundry +{ + public static class CloudfoundryConfigurationExtensions + { + + public static IConfigurationBuilder AddCloudfoundry(this IConfigurationBuilder configurationBuilder) + { + if (configurationBuilder == null) + { + throw new ArgumentNullException(nameof(configurationBuilder)); + } + + configurationBuilder.Add(new CloudfoundryConfigurationProvider()); + + return configurationBuilder; + + } + } +} diff --git a/src/Spring.Extensions.Configuration.Cloudfoundry/Properties/AssemblyInfo.cs b/src/Spring.Extensions.Configuration.Cloudfoundry/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c96af2c --- /dev/null +++ b/src/Spring.Extensions.Configuration.Cloudfoundry/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Spring.Extensions.Configuration.Cloudfoundry")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Spring.Extensions.Configuration.Cloudfoundry")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4a9d10c9-d365-41dd-9870-9d846a3d9eb1")] + +[assembly: InternalsVisibleTo("Spring.Extensions.Configuration.Cloudfoundry.Test")] diff --git a/src/Spring.Extensions.Configuration.Cloudfoundry/Spring.Extensions.Configuration.Cloudfoundry.xproj b/src/Spring.Extensions.Configuration.Cloudfoundry/Spring.Extensions.Configuration.Cloudfoundry.xproj new file mode 100644 index 0000000..3ba5d98 --- /dev/null +++ b/src/Spring.Extensions.Configuration.Cloudfoundry/Spring.Extensions.Configuration.Cloudfoundry.xproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 4a9d10c9-d365-41dd-9870-9d846a3d9eb1 + Spring.Extensions.Configuration.Cloudfoundry + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + diff --git a/src/Spring.Extensions.Configuration.Cloudfoundry/project.json b/src/Spring.Extensions.Configuration.Cloudfoundry/project.json new file mode 100644 index 0000000..491e6e7 --- /dev/null +++ b/src/Spring.Extensions.Configuration.Cloudfoundry/project.json @@ -0,0 +1,26 @@ +{ + "version": "1.0.0-*", + "description": "Cloudfoundry configuration provider", + "authors": [ "dtillman" ], + "tags": [ "" ], + "projectUrl": "", + "licenseUrl": "", + "frameworks": { + "net451": { + "dependencies": { + } + }, + "dnxcore50": { + "dependencies": { + } + } + }, + "dependencies": { + "Microsoft.Extensions.Configuration": "1.0.0-*", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-*", + "Microsoft.Extensions.Configuration.Json": "1.0.0-*", + "Microsoft.Extensions.Logging.Abstractions": "1.0.0-*", + "Newtonsoft.Json": "7.0.1" + + } +} diff --git a/test/Spring.Extensions.Configuration.Cloudfoundry.Test/CloudfoundryConfigurationExtensionsTest.cs b/test/Spring.Extensions.Configuration.Cloudfoundry.Test/CloudfoundryConfigurationExtensionsTest.cs new file mode 100644 index 0000000..c1a4a2f --- /dev/null +++ b/test/Spring.Extensions.Configuration.Cloudfoundry.Test/CloudfoundryConfigurationExtensionsTest.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Spring.Extensions.Configuration.Cloudfoundry.Test +{ + public class CloudfoundryConfigurationExtensionsTest + { + [Fact] + public void AddConfigService_ThrowsIfConfigBuilderNull() + { + // Arrange + IConfigurationBuilder configurationBuilder = null; + + // Act and Assert + var ex = Assert.Throws(() => CloudfoundryConfigurationExtensions.AddCloudfoundry(configurationBuilder)); + Assert.Contains(nameof(configurationBuilder), ex.Message); + + } + + [Fact] + public void AddConfigService_AddsConfigServerProviderToProvidersList() + { + // Arrange + var configurationBuilder = new ConfigurationBuilder(); + + // Act and Assert + configurationBuilder.AddCloudfoundry(); + + CloudfoundryConfigurationProvider cloudProvider = null; + foreach (IConfigurationProvider provider in configurationBuilder.Providers) + { + cloudProvider = provider as CloudfoundryConfigurationProvider; + if (cloudProvider != null) + break; + } + Assert.NotNull(cloudProvider); + + } + } +} diff --git a/test/Spring.Extensions.Configuration.Cloudfoundry.Test/CloudfoundryConfigurationProviderTest.cs b/test/Spring.Extensions.Configuration.Cloudfoundry.Test/CloudfoundryConfigurationProviderTest.cs new file mode 100644 index 0000000..138dbf4 --- /dev/null +++ b/test/Spring.Extensions.Configuration.Cloudfoundry.Test/CloudfoundryConfigurationProviderTest.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Spring.Extensions.Configuration.Cloudfoundry; + +namespace Spring.Extensions.Configuration.Cloudfoundry.Test +{ + public class CloudfoundryConfigurationProviderTest + { + public CloudfoundryConfigurationProviderTest() + { + } + + [Fact] + public void LoadVCAP_APPLICATION_ChangesDataDictionary() + { + // Arrange + var environment = @" +{ + + 'application_id': 'fa05c1a9-0fc1-4fbd-bae1-139850dec7a3', + 'application_name': 'my-app', + 'application_uris': [ + 'my-app.10.244.0.34.xip.io' + ], + 'application_version': 'fb8fbcc6-8d58-479e-bcc7-3b4ce5a7f0ca', + 'limits': { + 'disk': 1024, + 'fds': 16384, + 'mem': 256 + }, + 'name': 'my-app', + 'space_id': '06450c72-4669-4dc6-8096-45f9777db68a', + 'space_name': 'my-space', + 'uris': [ + 'my-app.10.244.0.34.xip.io', + 'my-app2.10.244.0.34.xip.io' + ], + 'users': null, + 'version': 'fb8fbcc6-8d58-479e-bcc7-3b4ce5a7f0ca' + }"; + + Environment.SetEnvironmentVariable("VCAP_APPLICATION", environment); + var provider = new CloudfoundryConfigurationProvider(); + + // Act and Assert + provider.Load(); + IDictionary dict = provider.Properties; + Assert.Equal("fa05c1a9-0fc1-4fbd-bae1-139850dec7a3", dict["vcap:application:application_id"]); + Assert.Equal("1024", dict["vcap:application:limits:disk"]); + Assert.Equal("my-app.10.244.0.34.xip.io", dict["vcap:application:uris:0"]); + Assert.Equal("my-app2.10.244.0.34.xip.io", dict["vcap:application:uris:1"]); + } + + [Fact] + public void LoadVCAP_SERVICES_ChangesDataDictionary() + { + var environment = @" +{ + 'elephantsql': [ + { + 'name': 'elephantsql-c6c60', + 'label': 'elephantsql', + 'tags': [ + 'postgres', + 'postgresql', + 'relational' + ], + 'plan': 'turtle', + 'credentials': { + 'uri': 'postgres://seilbmbd:ABcdEF@babar.elephantsql.com:5432/seilbmbd' + } + } + ], + 'sendgrid': [ + { + 'name': 'mysendgrid', + 'label': 'sendgrid', + 'tags': [ + 'smtp' + ], + 'plan': 'free', + 'credentials': { + 'hostname': 'smtp.sendgrid.net', + 'username': 'QvsXMbJ3rK', + 'password': 'HCHMOYluTv' + } + } + ] +}"; + Environment.SetEnvironmentVariable("VCAP_SERVICES", environment); + var provider = new CloudfoundryConfigurationProvider(); + + // Act and Assert + provider.Load(); + IDictionary dict = provider.Properties; + Assert.Equal("elephantsql-c6c60", dict["vcap:services:elephantsql:0:name"]); + //Assert.Equal("1024", dict["vcap:application:limits:disk"]); + //Assert.Equal("my-app.10.244.0.34.xip.io", dict["vcap:application:uris:0"]); + //Assert.Equal("my-app2.10.244.0.34.xip.io", dict["vcap:application:uris:1"]); + } + } +} diff --git a/test/Spring.Extensions.Configuration.Cloudfoundry.Test/Properties/AssemblyInfo.cs b/test/Spring.Extensions.Configuration.Cloudfoundry.Test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b1ebb22 --- /dev/null +++ b/test/Spring.Extensions.Configuration.Cloudfoundry.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Spring.Extensions.Configuration.Cloudfoundry.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Spring.Extensions.Configuration.Cloudfoundry.Test")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9f29bed6-ece3-42a2-8c57-c1a6831b9cf1")] diff --git a/test/Spring.Extensions.Configuration.Cloudfoundry.Test/Spring.Extensions.Configuration.Cloudfoundry.Test.xproj b/test/Spring.Extensions.Configuration.Cloudfoundry.Test/Spring.Extensions.Configuration.Cloudfoundry.Test.xproj new file mode 100644 index 0000000..143d5bf --- /dev/null +++ b/test/Spring.Extensions.Configuration.Cloudfoundry.Test/Spring.Extensions.Configuration.Cloudfoundry.Test.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 9f29bed6-ece3-42a2-8c57-c1a6831b9cf1 + Spring.Extensions.Configuration.Cloudfoundry.Test + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Spring.Extensions.Configuration.Cloudfoundry.Test/project.json b/test/Spring.Extensions.Configuration.Cloudfoundry.Test/project.json new file mode 100644 index 0000000..0c0d6f1 --- /dev/null +++ b/test/Spring.Extensions.Configuration.Cloudfoundry.Test/project.json @@ -0,0 +1,31 @@ +{ + "version": "1.0.0-*", + "description": "Unit test project for Spring.Extensions.Configuration.Cloudfoundry", + "authors": [ "dtillman" ], + "tags": [ "" ], + "projectUrl": "", + "licenseUrl": "", + "frameworks": { + "dnx451": { }, + "dnxcore50": { + "dependencies": { + } + } + }, + + "dependencies": { + "Microsoft.AspNet.Hosting": "1.0.0-*", + "Microsoft.AspNet.Http.Abstractions": "1.0.0-*", + "Microsoft.AspNet.TestHost": "1.0.0-*", + "Microsoft.Extensions.Configuration": "1.0.0-*", + "Spring.Extensions.Configuration.Cloudfoundry": "1.0.0-*", + "xunit": "2.1.0", + "xunit.runner.dnx": "2.1.0-rc1-build204" + }, + + "commands": { + "test": "xunit.runner.dnx" + } + +} +