diff --git a/src/ArmTemplates/Common/Constants/GlobalConstants.cs b/src/ArmTemplates/Common/Constants/GlobalConstants.cs index c7d9dca0..4d9fdf8f 100644 --- a/src/ArmTemplates/Common/Constants/GlobalConstants.cs +++ b/src/ArmTemplates/Common/Constants/GlobalConstants.cs @@ -44,6 +44,7 @@ public static class ParameterNames public const string SecretValues = "secretValues"; public const string IdentityProvidersSecretValues = "identityProviders"; public const string OpenIdConnectProvidersSecretValues = "openIdConnectProviders"; + public const string BackendProxy = "backendProxy"; } public static class ParameterPrefix @@ -54,5 +55,6 @@ public static class ParameterPrefix public const string LogResourceId = "LogResourceId"; public const string Backend = "Backend"; public const string ApiOauth2Scope = "ApiOauth2Scope"; + public const string BackendProxy = "BackendProxy"; } } \ No newline at end of file diff --git a/src/ArmTemplates/Common/Templates/Backend/BackendProxyParameters.cs b/src/ArmTemplates/Common/Templates/Backend/BackendProxyParameters.cs new file mode 100644 index 00000000..cc6b5de8 --- /dev/null +++ b/src/ArmTemplates/Common/Templates/Backend/BackendProxyParameters.cs @@ -0,0 +1,16 @@ +// -------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// -------------------------------------------------------------------------- + +namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.Backend +{ + public class BackendProxyParameters + { + public string Url { get; set; } + + public string Username { get; set; } + + public string Password { get; set; } + } +} diff --git a/src/ArmTemplates/Common/Templates/Backend/BackendTemplateResources.cs b/src/ArmTemplates/Common/Templates/Backend/BackendTemplateResources.cs index 3cbb6567..e6581c31 100644 --- a/src/ArmTemplates/Common/Templates/Backend/BackendTemplateResources.cs +++ b/src/ArmTemplates/Common/Templates/Backend/BackendTemplateResources.cs @@ -15,6 +15,8 @@ public class BackendTemplateResources : ITemplateResources public IDictionary BackendNameParametersCache { get; set; } = new Dictionary(); + public IDictionary BackendProxyParametersCache { get; set; } = new Dictionary(); + public TemplateResource[] BuildTemplateResources() { return this.Backends.ToArray(); diff --git a/src/ArmTemplates/Common/Templates/Builders/TemplateBuilder.Backend.cs b/src/ArmTemplates/Common/Templates/Builders/TemplateBuilder.Backend.cs index ea7a5d00..b26d6f72 100644 --- a/src/ArmTemplates/Common/Templates/Builders/TemplateBuilder.Backend.cs +++ b/src/ArmTemplates/Common/Templates/Builders/TemplateBuilder.Backend.cs @@ -21,6 +21,7 @@ public TemplateBuilder AddParameterizedBackendSettings(ExtractorParameters extra Type = "object" }; this.template.Parameters.Add(ParameterNames.BackendSettings, extractBackendParametersProperties); + this.template.Parameters.Add(ParameterNames.BackendProxy, extractBackendParametersProperties); } return this; diff --git a/src/ArmTemplates/Extractor/EntityExtractors/BackendExtractor.cs b/src/ArmTemplates/Extractor/EntityExtractors/BackendExtractor.cs index d7208e36..0b5f4384 100644 --- a/src/ArmTemplates/Extractor/EntityExtractors/BackendExtractor.cs +++ b/src/ArmTemplates/Extractor/EntityExtractors/BackendExtractor.cs @@ -134,6 +134,27 @@ void SaveBackendApiParametersToCache() { backendTemplate.TypedResources.BackendNameParametersCache.Add(backendValidName, backendApiParameters); } + + if (backendResource.Properties.Proxy != null) + { + var proxyUniqueId = $"{backendResource.Properties.Proxy.Url}_{backendResource.Properties.Proxy.Username}"; + var backendProxyParameterName = NamingHelper.GenerateValidParameterName(proxyUniqueId, ParameterPrefix.BackendProxy); + + if (!backendTemplate.TypedResources.BackendProxyParametersCache.ContainsKey(backendProxyParameterName)) + { + var backendProxyParameters = new BackendProxyParameters + { + Url = backendResource.Properties.Proxy.Url, + Username = backendResource.Properties.Proxy.Username, + Password = backendResource.Properties.Proxy.Password + }; + backendTemplate.TypedResources.BackendProxyParametersCache.Add(backendProxyParameterName, backendProxyParameters); + } + + backendResource.Properties.Proxy.Url = $"[parameters('{ParameterNames.BackendProxy}').{backendProxyParameterName}.url]"; + backendResource.Properties.Proxy.Username = $"[parameters('{ParameterNames.BackendProxy}').{backendProxyParameterName}.username]"; + backendResource.Properties.Proxy.Password = $"[parameters('{ParameterNames.BackendProxy}').{backendProxyParameterName}.password]"; + } } } diff --git a/src/ArmTemplates/Extractor/EntityExtractors/MasterTemplateExtractor.cs b/src/ArmTemplates/Extractor/EntityExtractors/MasterTemplateExtractor.cs index 28889121..70750b80 100644 --- a/src/ArmTemplates/Extractor/EntityExtractors/MasterTemplateExtractor.cs +++ b/src/ArmTemplates/Extractor/EntityExtractors/MasterTemplateExtractor.cs @@ -202,6 +202,9 @@ public Template GenerateLinkedMasterTemplate( { backendsDeployment.Properties.Parameters.Add(ParameterNames.BackendSettings, new TemplateParameterProperties() { Value = $"[parameters('{ParameterNames.BackendSettings}')]" }); + + backendsDeployment.Properties.Parameters.Add(ParameterNames.BackendProxy, + new TemplateParameterProperties() { Value = $"[parameters('{ParameterNames.BackendProxy}')]" }); } masterResources.DeploymentResources.Add(backendsDeployment); @@ -497,6 +500,10 @@ Dictionary CreateMasterTemplateParameters(E parameters.Add( ParameterNames.BackendSettings, new TemplateParameterProperties(metadataDescription: "The settings for the Backends", type: "object")); + + parameters.Add( + ParameterNames.BackendProxy, + new TemplateParameterProperties(metadataDescription: "The proxies for the Backends", type: "object")); } if (extractorParameters.ParametrizeApiOauth2Scope) diff --git a/src/ArmTemplates/Extractor/EntityExtractors/ParametersExtractor.cs b/src/ArmTemplates/Extractor/EntityExtractors/ParametersExtractor.cs index 201e281b..3a1892f9 100644 --- a/src/ArmTemplates/Extractor/EntityExtractors/ParametersExtractor.cs +++ b/src/ArmTemplates/Extractor/EntityExtractors/ParametersExtractor.cs @@ -268,6 +268,7 @@ async Task AddSecretValuesParameters() if (extractorParameters.ParameterizeBackend) { parameters.Add(ParameterNames.BackendSettings, new TemplateObjectParameterProperties() { Value = backendResources.BackendNameParametersCache }); + parameters.Add(ParameterNames.BackendProxy, new TemplateObjectParameterProperties() { Value = backendResources.BackendProxyParametersCache }); } return parametersTemplate; diff --git a/tests/ArmTemplates.Tests/Extractor/Scenarios/ApiReleaseExtractorTests.cs b/tests/ArmTemplates.Tests/Extractor/Scenarios/ApiReleaseExtractorTests.cs index 233c5988..82f70973 100644 --- a/tests/ArmTemplates.Tests/Extractor/Scenarios/ApiReleaseExtractorTests.cs +++ b/tests/ArmTemplates.Tests/Extractor/Scenarios/ApiReleaseExtractorTests.cs @@ -91,7 +91,6 @@ public async Task GenerateAllCurrentApiReleaseTemplateAsync_ProperlyCreatesTempl apiReleasesTemplate.TypedResources.ApiReleases.Any(x => x.Properties.ApiId.Contains($"/apis/echo-api;rev=1")).Should().BeTrue(); apiReleasesTemplate.TypedResources.ApiReleases.Any(x => x.Properties.ApiId.Contains($"/apis/5a7390baa5816a110435aee0;rev=1")).Should().BeTrue(); apiReleasesTemplate.TypedResources.ApiReleases.Any(x => x.Properties.ApiId.Contains($"/apis/5a73933b8f27f7cc82a2d533;rev=1")).Should().BeTrue(); - } } } diff --git a/tests/ArmTemplates.Tests/Extractor/Scenarios/BackendExtractorTests.cs b/tests/ArmTemplates.Tests/Extractor/Scenarios/BackendExtractorTests.cs index d27e8706..7e9658d5 100644 --- a/tests/ArmTemplates.Tests/Extractor/Scenarios/BackendExtractorTests.cs +++ b/tests/ArmTemplates.Tests/Extractor/Scenarios/BackendExtractorTests.cs @@ -72,6 +72,7 @@ public async Task GenerateBackendTemplates_ProperlyLaysTheInformation() backendTemplate.Parameters.Should().ContainKey(ParameterNames.ApimServiceName); backendTemplate.Parameters.Should().ContainKey(ParameterNames.BackendSettings); + backendTemplate.Parameters.Should().ContainKey(ParameterNames.BackendProxy); backendTemplate.TypedResources.Backends.Should().HaveCount(1); backendTemplate.Resources.Should().HaveCount(1); @@ -83,6 +84,61 @@ public async Task GenerateBackendTemplates_ProperlyLaysTheInformation() backendProperties.Should().NotBeNull(); backendProperties.Url.Should().Contain(ParameterNames.BackendSettings); backendProperties.Protocol.Should().Contain(ParameterNames.BackendSettings); + + backendProperties.Proxy.Password.Should().Contain(ParameterNames.BackendProxy); + backendProperties.Proxy.Username.Should().Contain(ParameterNames.BackendProxy); + backendProperties.Proxy.Url.Should().Contain(ParameterNames.BackendProxy); + } + + [Fact] + public async Task GenerateBackendTemplate_ProperlyParsesAndGeneratesTemplate() + { + // arrange + var responseFileLocation = Path.Combine(MockClientUtils.ApiClientJsonResponsesPath, "ApiManagementListBackends_success_response.json"); + var currentTestDirectory = Path.Combine(this.OutputDirectory, nameof(GenerateBackendTemplate_ProperlyParsesAndGeneratesTemplate)); + + var mockedClient = await MockBackendClient.GetMockedHttpApiClient(responseFileLocation); + + var extractorConfig = this.GetDefaultExtractorConsoleAppConfiguration( + apiName: string.Empty, + paramBackend: "true"); + var extractorParameters = new ExtractorParameters(extractorConfig); + + // mocked extractors + var backendExtractor = new BackendExtractor( + this.GetTestLogger(), + new TemplateBuilder(), + null, + mockedClient); + + var extractorExecutor = ExtractorExecutor.BuildExtractorExecutor( + this.GetTestLogger(), + backendExtractor: backendExtractor); + extractorExecutor.SetExtractorParameters(extractorParameters); + + // act + var backendTemplate = await extractorExecutor.GenerateBackendTemplateAsync(null, null, null, currentTestDirectory); + + // assert + File.Exists(Path.Combine(currentTestDirectory, extractorParameters.FileNames.Backends)).Should().BeTrue(); + + backendTemplate.Parameters.Should().ContainKey(ParameterNames.ApimServiceName); + backendTemplate.Parameters.Should().ContainKey(ParameterNames.BackendSettings); + backendTemplate.Parameters.Should().ContainKey(ParameterNames.BackendProxy); + + + backendTemplate.TypedResources.Backends.Count().Should().Be(3); + backendTemplate.TypedResources.Backends.All(x => x.Type.Equals(ResourceTypeConstants.Backend)).Should().BeTrue(); + + var proxyBackend1 = backendTemplate.TypedResources.Backends.First(x => x.Name.Contains("proxybackend1")); + proxyBackend1.Properties.Proxy.Password.Should().Contain(ParameterNames.BackendProxy); + proxyBackend1.Properties.Proxy.Username.Should().Contain(ParameterNames.BackendProxy); + proxyBackend1.Properties.Proxy.Url.Should().Contain(ParameterNames.BackendProxy); + + var proxyBackend2 = backendTemplate.TypedResources.Backends.First(x => x.Name.Contains("proxybackend2")); + proxyBackend2.Properties.Proxy.Password.Should().Contain(ParameterNames.BackendProxy); + proxyBackend2.Properties.Proxy.Username.Should().Contain(ParameterNames.BackendProxy); + proxyBackend2.Properties.Proxy.Url.Should().Contain(ParameterNames.BackendProxy); } } } diff --git a/tests/ArmTemplates.Tests/Extractor/Scenarios/MasterTemplateExtractorTests.cs b/tests/ArmTemplates.Tests/Extractor/Scenarios/MasterTemplateExtractorTests.cs index 1dec6ef2..47187b60 100644 --- a/tests/ArmTemplates.Tests/Extractor/Scenarios/MasterTemplateExtractorTests.cs +++ b/tests/ArmTemplates.Tests/Extractor/Scenarios/MasterTemplateExtractorTests.cs @@ -179,6 +179,7 @@ public async Task GenerateMasterTemplates_ProperlyLaysTheInformation() masterTemplate.Parameters.Should().ContainKey(ParameterNames.LoggerResourceId); masterTemplate.Parameters.Should().ContainKey(ParameterNames.NamedValueKeyVaultSecrets); masterTemplate.Parameters.Should().ContainKey(ParameterNames.BackendSettings); + masterTemplate.Parameters.Should().ContainKey(ParameterNames.BackendProxy); masterTemplate.Parameters.Should().ContainKey(ParameterNames.PolicyXMLBaseUrl); masterTemplate.Parameters.Should().ContainKey(ParameterNames.PolicyXMLBaseUrl); masterTemplate.Parameters.Should().ContainKey(ParameterNames.ApiOauth2ScopeSettings); diff --git a/tests/ArmTemplates.Tests/Extractor/Scenarios/ParametersExtractorTests.cs b/tests/ArmTemplates.Tests/Extractor/Scenarios/ParametersExtractorTests.cs index aec5f4e7..b50035ae 100644 --- a/tests/ArmTemplates.Tests/Extractor/Scenarios/ParametersExtractorTests.cs +++ b/tests/ArmTemplates.Tests/Extractor/Scenarios/ParametersExtractorTests.cs @@ -20,6 +20,7 @@ using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.IdentityProviders; using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Tests.Moqs.IdentityProviderClients; using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.OpenIdConnectProviders; +using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.Backend; namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Tests.Extractor.Scenarios { @@ -432,5 +433,63 @@ public void CreateResourceTemplateParameterTemplate_ProperlyGeneratesTemplate_Wi resourceTemplateParameterTemplate.Parameters["parameter1"].Should().NotBeNull(); resourceTemplateParameterTemplate.Parameters["parameter2"].Should().NotBeNull(); } + + [Fact] + public async Task CreateResourceTemplateParameterTemplate_ProperlyGeneratesTemplate_WithBackendProxySettings() + { + // arrange + var currentTestDirectory = Path.Combine(this.OutputDirectory, nameof(CreateResourceTemplateParameterTemplate_ProperlyGeneratesTemplate_WithBackendProxySettings)); + + var extractorConfig = this.GetDefaultExtractorConsoleAppConfiguration(paramBackend: "true"); + var extractorParameters = new ExtractorParameters(extractorConfig); + var extractorExecutor = this.GetExtractorInstance(extractorParameters, null); + + var backends = new BackendTemplateResources() + { + BackendNameParametersCache = new Dictionary() + { + { "key1", new BackendApiParameters() { Protocol = "protocol", ResourceId = "resourceId", Url = "url" } } + }, + BackendProxyParametersCache = new Dictionary() + { + { "key1", new BackendProxyParameters() { Username = "username", Url = "url" } } + }, + Backends = new List() + { + { + new BackendTemplateResource + { + Name = "test" + } + } + } + }; + + // act + var parametersTemplate = await extractorExecutor.GenerateParametersTemplateAsync(null, null, backends, null, new IdentityProviderResources(), new OpenIdConnectProviderResources(), currentTestDirectory); + + File.Exists(Path.Combine(currentTestDirectory, extractorParameters.FileNames.Parameters)).Should().BeTrue(); + + parametersTemplate.Parameters.Should().ContainKey(ParameterNames.BackendProxy); + parametersTemplate.Parameters.Should().ContainKey(ParameterNames.BackendSettings); + + var backendProxyParameters = (TemplateObjectParameterProperties)parametersTemplate.Parameters[ParameterNames.BackendProxy]; + var backendProxyParameterValues = (Dictionary)backendProxyParameters.Value; + + backendProxyParameterValues.Should().NotBeNull(); + backendProxyParameterValues.ContainsKey("key1").Should().BeTrue(); + backendProxyParameterValues["key1"].Username.Should().Be("username"); + backendProxyParameterValues["key1"].Url.Should().Be("url"); + + + var backendSettingsParameters = (TemplateObjectParameterProperties)parametersTemplate.Parameters[ParameterNames.BackendSettings]; + var backendSettingsParameterValues = (Dictionary)backendSettingsParameters.Value; + + backendSettingsParameterValues.Should().NotBeNull(); + backendSettingsParameterValues.ContainsKey("key1").Should().BeTrue(); + backendSettingsParameterValues["key1"].Protocol.Should().Be("protocol"); + backendSettingsParameterValues["key1"].ResourceId.Should().Be("resourceId"); + backendSettingsParameterValues["key1"].Url.Should().Be("url"); + } } } diff --git a/tests/ArmTemplates.Tests/Moqs/ApiClients/MockBackendClient.cs b/tests/ArmTemplates.Tests/Moqs/ApiClients/MockBackendClient.cs index 25f6d4b6..cb66bf20 100644 --- a/tests/ArmTemplates.Tests/Moqs/ApiClients/MockBackendClient.cs +++ b/tests/ArmTemplates.Tests/Moqs/ApiClients/MockBackendClient.cs @@ -4,7 +4,9 @@ // -------------------------------------------------------------------------- using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.API.Clients.Abstractions; +using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.API.Clients.Backend; using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.Backend; using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Models; using Moq; @@ -43,5 +45,13 @@ public static IBackendClient GetMockedApiClientWithDefaultValues() return mockBackendClient.Object; } + + public static async Task GetMockedHttpApiClient(string responseFileLocation) + { + var mockedClient = new Mock(MockBehavior.Strict, await MockClientUtils.GenerateMockedIHttpClientFactoryWithResponse(responseFileLocation)); + MockClientUtils.MockAuthOfApiClient(mockedClient); + + return mockedClient.Object; + } } } diff --git a/tests/ArmTemplates.Tests/Resources/ApiClientJsonResponses/ApiManagementListBackends_success_response.json b/tests/ArmTemplates.Tests/Resources/ApiClientJsonResponses/ApiManagementListBackends_success_response.json new file mode 100644 index 00000000..bc9cf70c --- /dev/null +++ b/tests/ArmTemplates.Tests/Resources/ApiClientJsonResponses/ApiManagementListBackends_success_response.json @@ -0,0 +1,106 @@ +{ + "value": [ + { + "id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ApiManagement/service/apimService1/backends/proxybackend1", + "type": "Microsoft.ApiManagement/service/backends", + "name": "proxybackend1", + "properties": { + "description": "description5308", + "url": "https://backendname2644/", + "protocol": "http", + "credentials": { + "query": { + "sv": [ + "xx", + "bb", + "cc" + ] + }, + "header": { + "x-my-1": [ + "val1", + "val2" + ] + }, + "authorization": { + "scheme": "Basic", + "parameter": "opensesma" + } + }, + "proxy": { + "url": "http://192.168.1.1:8080", + "username": "Contoso\\admin", + "password": "" + }, + "tls": { + "validateCertificateChain": false, + "validateCertificateName": false + } + } + }, + { + "id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ApiManagement/service/apimService1/backends/proxybackend2", + "type": "Microsoft.ApiManagement/service/backends", + "name": "proxybackend2", + "properties": { + "description": "description5308", + "url": "https://backendname2644/", + "protocol": "http", + "credentials": { + "query": { + "sv": [ + "xx", + "bb", + "cc" + ] + }, + "header": { + "x-my-1": [ + "val1", + "val2" + ] + }, + "authorization": { + "scheme": "Basic", + "parameter": "opensesma" + } + }, + "proxy": { + "url": "http://192.168.1.1:8080", + "username": "Contoso\\admin", + "password": "" + }, + "tls": { + "validateCertificateChain": false, + "validateCertificateName": false + } + } + }, + { + "id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ApiManagement/service/apimService1/backends/sfbackend", + "type": "Microsoft.ApiManagement/service/backends", + "name": "sfbackend", + "properties": { + "description": "Service Fabric Test App 1", + "url": "fabric:/mytestapp/mytestservice", + "protocol": "http", + "properties": { + "serviceFabricCluster": { + "managementEndpoints": [ + "https://somecluster.com" + ], + "clientCertificateId": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ApiManagement/service/apimService1/certificates/cert1", + "serverX509Names": [ + { + "name": "ServerCommonName1", + "issuerCertificateThumbprint": "IssuerCertificateThumbprint1" + } + ], + "maxPartitionResolutionRetries": 5 + } + } + } + } + ], + "count": 3 +} \ No newline at end of file