зеркало из https://github.com/Azure/Sia-Gateway.git
Merged from master to resolve conflicts
This commit is contained in:
Коммит
567f4343cd
51
README.md
51
README.md
|
@ -12,3 +12,54 @@ provided by the bot. You will only need to do this once across all repos using o
|
|||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
//Todo: figure out what else should be in this file.
|
||||
#Cloning this repository
|
||||
|
||||
This repository has the https://github.com/Azure/Sia-Root git repository embedded as a submodule.
|
||||
Once you've cloned this repository, use your git client to navigate to "domain" subdirectory, and run
|
||||
|
||||
1. git submodule init
|
||||
2. git submodule update
|
||||
|
||||
This will clone the submodule repository. For more information on submodules, see [the Git Tools Documentation for Submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules)
|
||||
|
||||
|
||||
#Setting Up Your Development Environment
|
||||
|
||||
This Gateway is intended to be extended by Sia microservices in other code repositories.
|
||||
|
||||
In order to test functionality of the gateway on your local machine:
|
||||
1. For each 'Service' in appsettings.Development.json:
|
||||
1. Clone the associated repository (Example: Incident is associated with the SiaManagement repository)
|
||||
2. Change the default startup from the project name to IIS Express and start
|
||||
3. You will need to have a separate visual studio instance open for each service the gateway will interact with.
|
||||
(If you find a good way to do this without having visual studio open, please let the maintainers of this project know)
|
||||
4. Validate that the localhost port the service is running on matches its configuration in appsettings.Development.json.
|
||||
You can find IIS Express information by right clicking the IIS Express icon in your system tray.
|
||||
2. Get appropriate Secrets for authentication
|
||||
1. Right click on the Sia.Gateway project and select "Manage user secrets"
|
||||
2. Open usersecrets.template.json to use as a template for your usersecrets json file
|
||||
3. You will need these configuration values:
|
||||
1. Your AAD instance (the default is correct for the vast majority of scenarios)
|
||||
2. Your AAD tenant
|
||||
3. Your Key Vault vault name
|
||||
4. Your ApplicationInsights instrumentation key name
|
||||
5. The secret that is used to authenticate to azure key vault for the gateway (ClientSecret)
|
||||
* This secret can be found in the azure portal in the AAD App Registration for your Gateway instance
|
||||
3. Start the gateway
|
||||
|
||||
|
||||
#Ticketing System Connectors
|
||||
|
||||
Sia can be configured to to work with a separate ticketing system,
|
||||
either by leveraging a proxy API or by direct access with a custom connector.
|
||||
|
||||
Data from connected ticketing systems can be used to generate events and/or present
|
||||
additional information and context on the UI.
|
||||
|
||||
Sia will still function without a connected ticketing system, and will persist limited
|
||||
ticket data in the Incident database (see Sia.Data.Incident\Models\Ticket.cs)
|
||||
|
||||
For additional information on use, configuration, and creation of ticketing system
|
||||
connectors, see Sia.Connectors.Tickets\README.md
|
|
@ -27,6 +27,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sia.Shared.Tests", "domain\
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sia.Data.Incident.Tests", "Sia.Data.Incident.Tests\Sia.Data.Incident.Tests.csproj", "{C3D38E8D-54BC-4D71-9B3C-FD6D641D9763}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{9845797A-7F60-4400-B4C4-D748417E1F64}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitignore = .gitignore
|
||||
.gitmodules = .gitmodules
|
||||
LICENSE = LICENSE
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
2
domain
2
domain
|
@ -1 +1 @@
|
|||
Subproject commit ac81ef875645a6f645a409eb736b437a156f052d
|
||||
Subproject commit 881099688463da7f969024d58ff041cf8806db6a
|
|
@ -0,0 +1,40 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Runtime.Loader;
|
||||
|
||||
namespace Sia.Gateway.Initialization
|
||||
{
|
||||
public static partial class Initialization
|
||||
{
|
||||
public static void LoadConnectorFromAssembly(
|
||||
this IServiceCollection services,
|
||||
IHostingEnvironment env,
|
||||
IConfigurationRoot config,
|
||||
string ticketConnectorAssemblyPath,
|
||||
string assemblyLoaderType = "Sia.Gateway.Initialization.LoadAssembly"
|
||||
)
|
||||
{
|
||||
var connectorAssembly = AssemblyLoadContext
|
||||
.Default
|
||||
.LoadFromAssemblyPath(ticketConnectorAssemblyPath);
|
||||
var connectorInitializerType = connectorAssembly
|
||||
.GetType(assemblyLoaderType);
|
||||
|
||||
var connectorConfigureServices = connectorInitializerType
|
||||
.GetMethod(
|
||||
"AddConnector",
|
||||
new Type[] {
|
||||
typeof(IServiceCollection),
|
||||
typeof(IConfigurationRoot),
|
||||
typeof(IHostingEnvironment)
|
||||
}
|
||||
);
|
||||
connectorConfigureServices.Invoke(
|
||||
null,
|
||||
new object[] { services, config, env }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Connectors.Tickets
|
||||
{
|
||||
public abstract class Client<TTicket>
|
||||
{
|
||||
public abstract Task<TTicket> GetAsync(string originId);
|
||||
}
|
||||
}
|
|
@ -1,18 +1,45 @@
|
|||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sia.Domain;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Connectors.Tickets
|
||||
{
|
||||
public class Connector<TTicket>
|
||||
public class Connector
|
||||
{
|
||||
public Client<TTicket> Client { get; }
|
||||
public Converter<TTicket> Converter { get; }
|
||||
|
||||
public Connector(Client<TTicket> client, Converter<TTicket> converter)
|
||||
public Connector(TicketingClient client, ILoggerFactory loggerFactory)
|
||||
{
|
||||
Client = client;
|
||||
Converter = converter;
|
||||
Logger = loggerFactory.CreateLogger<Connector>();
|
||||
}
|
||||
protected TicketingClient Client { get; }
|
||||
protected ILogger Logger { get; }
|
||||
public virtual async Task AppendDataAsync(Ticket persistedTicket)
|
||||
{
|
||||
try
|
||||
{
|
||||
persistedTicket.Data = await Client.GetAsync(persistedTicket.OriginId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(
|
||||
ex,
|
||||
"Exception during GetAsync for ticket with Id: {0}",
|
||||
new object[] { persistedTicket.Id }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AppendData(ICollection<Ticket> persistedTickets)
|
||||
=> Task.WaitAll(
|
||||
persistedTickets
|
||||
.Select(tic => AppendDataAsync(tic))
|
||||
.ToArray()
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
using AutoMapper;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sia.Connectors.Tickets
|
||||
{
|
||||
public abstract class Converter<TTicket>
|
||||
{
|
||||
public abstract ICollection<Data.Incidents.Models.Event> ExtractEvents(TTicket ticket);
|
||||
|
||||
public virtual Domain.Incident AssembleIncident(Data.Incidents.Models.Incident databaseRecord, TTicket ticket)
|
||||
{
|
||||
if(ticket != null)
|
||||
{
|
||||
foreach (var ev in ExtractEvents(ticket))
|
||||
{
|
||||
databaseRecord.Events.Add(ev);
|
||||
}
|
||||
}
|
||||
return Mapper.Map<Domain.Incident>(databaseRecord);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Sia.Shared.Validation;
|
||||
|
||||
namespace Sia.Gateway.Initialization
|
||||
{
|
||||
public static partial class Initialization
|
||||
{
|
||||
public static void AddTicketingConnector(
|
||||
this IServiceCollection services,
|
||||
IHostingEnvironment env,
|
||||
IConfigurationRoot config)
|
||||
{
|
||||
if (TryGetConfigValue(
|
||||
config,
|
||||
"Connector:Ticket:Path",
|
||||
out var ticketConnectorAssemblyPath))
|
||||
{
|
||||
services
|
||||
.LoadConnectorFromAssembly(
|
||||
env,
|
||||
config,
|
||||
ticketConnectorAssemblyPath
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryGetConfigValue(
|
||||
config,
|
||||
"Connector:Ticket:ProxyEndpoint",
|
||||
out var proxyEndpoint))
|
||||
{
|
||||
services.AddProxyConnector(config, proxyEndpoint);
|
||||
return;
|
||||
}
|
||||
|
||||
services.AddNoTicketingSystem();
|
||||
}
|
||||
|
||||
private static bool TryGetConfigValue(
|
||||
this IConfigurationRoot config,
|
||||
string configName,
|
||||
out string configValue)
|
||||
{
|
||||
ThrowIf.NullOrWhiteSpace(configName, nameof(configName));
|
||||
configValue = config[configName];
|
||||
return !string.IsNullOrEmpty(configValue);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
namespace Sia.Connectors.Tickets.None
|
||||
{
|
||||
public class EmptyTicket
|
||||
{
|
||||
}
|
||||
}
|
|
@ -6,12 +6,10 @@ namespace Sia.Gateway.Initialization
|
|||
{
|
||||
public static partial class Initialization
|
||||
{
|
||||
public static IServiceCollection AddNoTicketingSystem(this IServiceCollection services)
|
||||
{
|
||||
return services
|
||||
.AddSingleton<Converter<EmptyTicket>, NoConverter>()
|
||||
.AddSingleton<Client<EmptyTicket>, NoClient>()
|
||||
.AddSingleton<Connector<EmptyTicket>, NoConnector>();
|
||||
}
|
||||
public static IServiceCollection AddNoTicketingSystem(
|
||||
this IServiceCollection services
|
||||
) => services
|
||||
.AddSingleton<NoClient>()
|
||||
.AddSingleton<Connector, NoConnector>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,9 @@
|
|||
|
||||
namespace Sia.Connectors.Tickets.None
|
||||
{
|
||||
public class NoClient : Client<EmptyTicket>
|
||||
public class NoClient : TicketingClient
|
||||
{
|
||||
public override Task<EmptyTicket> GetAsync(string originId)
|
||||
{
|
||||
return Task.FromResult(new EmptyTicket());
|
||||
}
|
||||
public override Task<object> GetAsync(string originId)
|
||||
=> Task.FromResult<object>(null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
namespace Sia.Connectors.Tickets.None
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Sia.Connectors.Tickets.None
|
||||
{
|
||||
public class NoConnector : Connector<EmptyTicket>
|
||||
public class NoConnector : Connector
|
||||
{
|
||||
public NoConnector(Client<EmptyTicket> client, Converter<EmptyTicket> converter)
|
||||
: base(client, converter)
|
||||
public NoConnector(NoClient client, ILoggerFactory loggerFactory)
|
||||
: base(client, loggerFactory)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
using Sia.Data.Incidents.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sia.Connectors.Tickets.None
|
||||
{
|
||||
public class NoConverter : Converter<EmptyTicket>
|
||||
{
|
||||
public override ICollection<Event> ExtractEvents(EmptyTicket ticket)
|
||||
{
|
||||
return new List<Event>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
Sia can be configured to to work with a separate ticketing system,
|
||||
either by leveraging a proxy API or by direct access using a custom connector.
|
||||
|
||||
Data from connected ticketing systems can be used to generate events and/or
|
||||
Data from connected ticketing systems can be used to
|
||||
present additional information and context on the UI.
|
||||
|
||||
Sia will still function without a connected ticketing system, and will
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<PackageId>Microsoft.Sia.Connectors.Tickets</PackageId>
|
||||
<Version>1.0.6-alpha</Version>
|
||||
<Authors>pdimit, magpint, jache, chtownes</Authors>
|
||||
<Version>1.1.8-alpha</Version>
|
||||
<Authors>Microsoft</Authors>
|
||||
<Company>Microsoft</Company>
|
||||
<Product>SRE Incident Assistant</Product>
|
||||
<Description>Ticketing system connector primitives for SIA</Description>
|
||||
|
@ -15,6 +15,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="6.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sia.Connectors.Tickets;
|
||||
using Sia.Connectors.Tickets.TicketProxy;
|
||||
using Sia.Shared.Authentication;
|
||||
|
@ -8,23 +9,63 @@ namespace Sia.Gateway.Initialization
|
|||
{
|
||||
public static partial class Initialization
|
||||
{
|
||||
public static IServiceCollection AddProxyWithoutAuth(this IServiceCollection services, string endpoint)
|
||||
=> services.AddProxy(new ProxyConnectionInfo(endpoint));
|
||||
public static IServiceCollection AddProxyWithoutAuth(
|
||||
this IServiceCollection services,
|
||||
string endpoint
|
||||
) => services.AddProxy(new ProxyConnectionInfo(endpoint));
|
||||
|
||||
|
||||
public static IServiceCollection AddProxyWithCert(this IServiceCollection services, string endpoint, string certThumbprint)
|
||||
=> services.AddProxy(new ProxyConnectionInfo(endpoint, certThumbprint));
|
||||
public static IServiceCollection AddProxyWithCert(
|
||||
this IServiceCollection services,
|
||||
string endpoint,
|
||||
string certThumbprint
|
||||
) => services.AddProxy(new ProxyConnectionInfo(endpoint, certThumbprint));
|
||||
|
||||
|
||||
public static IServiceCollection AddProxyWithCertFromKeyVault(this IServiceCollection services, string endpoint, KeyVaultConfiguration config, string certName)
|
||||
=> services.AddProxy(new ProxyConnectionInfo(endpoint, config, certName));
|
||||
public static IServiceCollection AddProxyWithCertFromKeyVault(
|
||||
this IServiceCollection services,
|
||||
string endpoint,
|
||||
KeyVaultConfiguration config,
|
||||
string certName
|
||||
) => services.AddProxy(new ProxyConnectionInfo(endpoint, config, certName));
|
||||
|
||||
private static IServiceCollection AddProxy(this IServiceCollection services, ProxyConnectionInfo proxyConnection)
|
||||
private static IServiceCollection AddProxy(
|
||||
this IServiceCollection services,
|
||||
ProxyConnectionInfo proxyConnection
|
||||
) => services
|
||||
.AddScoped(serv => proxyConnection)
|
||||
.AddScoped<TicketingClient, ProxyClient>()
|
||||
.AddScoped<Connector, ProxyConnector>();
|
||||
|
||||
public static void AddProxyConnector(
|
||||
this IServiceCollection services,
|
||||
IConfigurationRoot config,
|
||||
string proxyEndpoint)
|
||||
{
|
||||
return services
|
||||
.AddScoped<Converter<ProxyTicket>, ProxyConverter>()
|
||||
.AddScoped<Client<ProxyTicket>>(serv => proxyConnection.GetClient())
|
||||
.AddScoped<Connector<ProxyTicket>, ProxyConnector>();
|
||||
var proxyAuthType = config["Connector:Ticket:ProxyAuthType"];
|
||||
switch (proxyAuthType)
|
||||
{
|
||||
case "Certificate":
|
||||
services.AddProxyWithCert(
|
||||
proxyEndpoint,
|
||||
config["Connector:Ticket:ProxyCertThumbprint"]
|
||||
);
|
||||
return;
|
||||
case "VaultCertificate":
|
||||
services.AddProxyWithCertFromKeyVault(
|
||||
proxyEndpoint,
|
||||
new KeyVaultConfiguration(
|
||||
config["ClientId"],
|
||||
config["ClientSecret"],
|
||||
config["Connector:Ticket:VaultName"]
|
||||
),
|
||||
config["Connector:Ticket:CertName"]
|
||||
);
|
||||
return;
|
||||
default:
|
||||
services.AddProxyWithoutAuth(proxyEndpoint);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,40 @@
|
|||
using Newtonsoft.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Sia.Shared.Authentication;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Connectors.Tickets.TicketProxy
|
||||
{
|
||||
public class ProxyClient : Client<ProxyTicket>
|
||||
public class ProxyClient : TicketingClient
|
||||
{
|
||||
private readonly string _endpoint;
|
||||
private readonly HttpClient _client;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
public ProxyClient(HttpClient singletonClient, string endpoint)
|
||||
private ProxyConnectionInfo _connectionInfo { get; }
|
||||
private HttpClient _client { get; set; }
|
||||
|
||||
public ProxyClient(
|
||||
ProxyConnectionInfo connectionInfo,
|
||||
ILoggerFactory loggerFactory
|
||||
)
|
||||
{
|
||||
_endpoint = endpoint;
|
||||
_client = singletonClient;
|
||||
_loggerFactory = loggerFactory;
|
||||
_connectionInfo = connectionInfo;
|
||||
}
|
||||
|
||||
public override async Task<ProxyTicket> GetAsync(string originId)
|
||||
public override async Task<object> GetAsync(string originId)
|
||||
{
|
||||
string incidentUrl = $"{_endpoint}/{originId}";
|
||||
if(_client is null)
|
||||
{
|
||||
_client = await _connectionInfo.GetClientAsync(_loggerFactory);
|
||||
}
|
||||
string incidentUrl = $"{_connectionInfo.Endpoint}/{originId}";
|
||||
var response = await _client.GetAsync(incidentUrl);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<ProxyTicket>(content);
|
||||
return JsonConvert.DeserializeObject<ProxyData>(content);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sia.Shared.Authentication;
|
||||
using Sia.Shared.Validation;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Connectors.Tickets.TicketProxy
|
||||
{
|
||||
public class ProxyConnectionInfo
|
||||
{
|
||||
private AzureSecretVault _keyVault;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates ProxyConnectionInfo with no Authentication
|
||||
/// </summary>
|
||||
/// <param name="endpoint">Proxy Endpoint</param>
|
||||
public ProxyConnectionInfo(string endpoint)
|
||||
: this(endpoint, AuthenticationType.None)
|
||||
public ProxyConnectionInfo(
|
||||
string endpoint
|
||||
) : this(
|
||||
endpoint,
|
||||
AuthenticationType.None)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
|
@ -22,8 +28,13 @@ namespace Sia.Connectors.Tickets.TicketProxy
|
|||
/// </summary>
|
||||
/// <param name="endpoint">Proxy Endpoint</param>
|
||||
/// <param name="certThumbprint">Thumbprint for searching local certificate store</param>
|
||||
public ProxyConnectionInfo(string endpoint, string certThumbprint)
|
||||
: this(endpoint, AuthenticationType.Certificate)
|
||||
public ProxyConnectionInfo(
|
||||
string endpoint,
|
||||
string certThumbprint
|
||||
) : this(
|
||||
endpoint,
|
||||
AuthenticationType.Certificate
|
||||
)
|
||||
{
|
||||
CertIdentifier = ThrowIf.NullOrWhiteSpace(certThumbprint, nameof(certThumbprint));
|
||||
}
|
||||
|
@ -34,39 +45,55 @@ namespace Sia.Connectors.Tickets.TicketProxy
|
|||
/// <param name="endpoint">Proxy Endpoint</param>
|
||||
/// <param name="config">Configuration root for initialization</param>
|
||||
/// <param name="vaultName">Key vault name</param>
|
||||
public ProxyConnectionInfo(string endpoint, KeyVaultConfiguration config, string vaultName)
|
||||
: this(endpoint, AuthenticationType.CertificateFromKeyVault)
|
||||
public ProxyConnectionInfo(
|
||||
string endpoint,
|
||||
KeyVaultConfiguration config,
|
||||
string vaultName
|
||||
) : this(
|
||||
endpoint,
|
||||
AuthenticationType.CertificateFromKeyVault
|
||||
)
|
||||
{
|
||||
_keyVault = new AzureSecretVault(config);
|
||||
Vault = new AzureSecretVault(config);
|
||||
CertIdentifier = ThrowIf.NullOrWhiteSpace(vaultName, nameof(vaultName));
|
||||
}
|
||||
|
||||
protected ProxyConnectionInfo(string endpoint, AuthenticationType authType)
|
||||
protected ProxyConnectionInfo(
|
||||
string endpoint,
|
||||
AuthenticationType authType
|
||||
)
|
||||
{
|
||||
Endpoint = ThrowIf.NullOrWhiteSpace(endpoint, nameof(endpoint));
|
||||
AuthenticationType = authType;
|
||||
}
|
||||
|
||||
public ProxyClient GetClient() => new ProxyClient(ClientFactory.GetClient(), Endpoint);
|
||||
public AuthenticationType AuthenticationType { get; protected set; }
|
||||
public string Endpoint { get; protected set; }
|
||||
public string CertIdentifier { get; protected set; }
|
||||
|
||||
protected IHttpClientFactory ClientFactory
|
||||
public async Task<HttpClient> GetClientAsync (ILoggerFactory loggerFactory)
|
||||
{
|
||||
get
|
||||
if(_client is null)
|
||||
{
|
||||
switch (AuthenticationType)
|
||||
{
|
||||
case AuthenticationType.Certificate:
|
||||
return new LocalCertificateRetriever(CertIdentifier);
|
||||
case AuthenticationType.CertificateFromKeyVault:
|
||||
return new KeyVaultCertificateRetriever(_keyVault, CertIdentifier);
|
||||
case AuthenticationType.None:
|
||||
return new UnauthenticatedClientFactory();
|
||||
default:
|
||||
throw new NotImplementedException($"Unrecognized authentication type {AuthenticationType.GetName(typeof(AuthenticationType), AuthenticationType)}");
|
||||
}
|
||||
_client = await ClientFactory(loggerFactory).GetClientAsync();
|
||||
}
|
||||
return _client;
|
||||
}
|
||||
|
||||
private HttpClient _client;
|
||||
private readonly AzureSecretVault Vault;
|
||||
public AuthenticationType AuthenticationType { get; protected set; }
|
||||
public readonly string Endpoint;
|
||||
private readonly string CertIdentifier;
|
||||
|
||||
protected IHttpClientFactory ClientFactory(ILoggerFactory loggerFactory)
|
||||
{
|
||||
switch (AuthenticationType)
|
||||
{
|
||||
case AuthenticationType.Certificate:
|
||||
return new LocalCertificateRetriever(CertIdentifier, loggerFactory);
|
||||
case AuthenticationType.CertificateFromKeyVault:
|
||||
return new KeyVaultCertificateRetriever(Vault, CertIdentifier, loggerFactory);
|
||||
case AuthenticationType.None:
|
||||
return new UnauthenticatedClientFactory();
|
||||
default:
|
||||
throw new NotImplementedException($"Unrecognized authentication type {Enum.GetName(typeof(AuthenticationType), AuthenticationType)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
namespace Sia.Connectors.Tickets.TicketProxy
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Sia.Connectors.Tickets.TicketProxy
|
||||
{
|
||||
public class ProxyConnector : Connector<ProxyTicket>
|
||||
public class ProxyConnector : Connector
|
||||
{
|
||||
public ProxyConnector(Client<ProxyTicket> client, Converter<ProxyTicket> converter)
|
||||
: base(client, converter)
|
||||
public ProxyConnector(ProxyClient client, ILoggerFactory logger)
|
||||
: base(client, logger)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
using Sia.Data.Incidents.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sia.Connectors.Tickets.TicketProxy
|
||||
{
|
||||
public class ProxyConverter : Converter<ProxyTicket>
|
||||
{
|
||||
public override ICollection<Event> ExtractEvents(ProxyTicket ticket)
|
||||
{
|
||||
return new List<Event>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,7 @@
|
|||
namespace Sia.Connectors.Tickets.TicketProxy
|
||||
{
|
||||
public class ProxyTicket
|
||||
public class ProxyData
|
||||
{
|
||||
public string OriginId { get; set; }
|
||||
public long IncidentSystemId { get; set; }
|
||||
public string OriginUri { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Owner { get; set; }
|
||||
public string Severity { get; set; }
|
|
@ -0,0 +1,9 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Connectors.Tickets
|
||||
{
|
||||
public abstract class TicketingClient
|
||||
{
|
||||
public abstract Task<object> GetAsync(string originId);
|
||||
}
|
||||
}
|
|
@ -28,7 +28,10 @@ namespace Sia.Gateway.Initialization
|
|||
public static class ServicesStartup
|
||||
{
|
||||
|
||||
public static void AddFirstPartyServices(this IServiceCollection services, IHostingEnvironment env, IConfigurationRoot config)
|
||||
public static void AddFirstPartyServices(
|
||||
this IServiceCollection services,
|
||||
IHostingEnvironment env,
|
||||
IConfigurationRoot config)
|
||||
{
|
||||
ConfigureAuth(services, config);
|
||||
|
||||
|
@ -50,24 +53,6 @@ namespace Sia.Gateway.Initialization
|
|||
services.AddSingleton(httpClients);
|
||||
}
|
||||
|
||||
private static void AddTicketingConnector(this IServiceCollection services, IHostingEnvironment env, IConfigurationRoot config)
|
||||
{
|
||||
if (TryGetConfigValue(config, "Connector:Ticket:Path", out var ticketConnectorAssemblyPath))
|
||||
{
|
||||
LoadConnectorFromAssembly(services, env, config, ticketConnectorAssemblyPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryGetConfigValue(config, "Connector:Ticket:ProxyEndpoint", out var proxyEndpoint))
|
||||
{
|
||||
AddProxyConnector(services, config, proxyEndpoint);
|
||||
return;
|
||||
}
|
||||
|
||||
services.AddIncidentClient(typeof(EmptyTicket));
|
||||
services.AddNoTicketingSystem();
|
||||
}
|
||||
|
||||
private static bool TryGetConfigValue(this IConfigurationRoot config, string configName, out string configValue)
|
||||
{
|
||||
ThrowIf.NullOrWhiteSpace(configName, nameof(configName));
|
||||
|
@ -75,57 +60,12 @@ namespace Sia.Gateway.Initialization
|
|||
return !string.IsNullOrEmpty(configValue);
|
||||
}
|
||||
|
||||
private static void AddProxyConnector(IServiceCollection services, IConfigurationRoot config, string proxyEndpoint)
|
||||
{
|
||||
services.AddIncidentClient(typeof(ProxyTicket));
|
||||
var proxyAuthType = config["Connector:Ticket:ProxyAuthType"];
|
||||
switch(proxyAuthType)
|
||||
{
|
||||
case "Certificate":
|
||||
services.AddProxyWithCert(proxyEndpoint, config["Connector:Ticket:ProxyCertThumbprint"]);
|
||||
return;
|
||||
case "VaultCertificate":
|
||||
services.AddProxyWithCertFromKeyVault(
|
||||
proxyEndpoint,
|
||||
new KeyVaultConfiguration(
|
||||
config["ClientId"],
|
||||
config["ClientSecret"],
|
||||
config["Connector:Ticket:VaultName"]
|
||||
),
|
||||
config["Connector:Ticket:CertName"]
|
||||
);
|
||||
return;
|
||||
default:
|
||||
services.AddProxyWithoutAuth(proxyEndpoint);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConfigureAuth(IServiceCollection services, IConfigurationRoot config)
|
||||
{
|
||||
var incidentAuthConfig = new AzureActiveDirectoryAuthenticationInfo(config["Playbook:ClientId"], config["ClientId"], config["ClientSecret"], config["AzureAd:Tenant"]);
|
||||
services.AddSingleton<AzureActiveDirectoryAuthenticationInfo>(i => incidentAuthConfig);
|
||||
}
|
||||
|
||||
private static void LoadConnectorFromAssembly(IServiceCollection services, IHostingEnvironment env, IConfigurationRoot config, string ticketConnectorAssemblyPath)
|
||||
{
|
||||
var connectorAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(ticketConnectorAssemblyPath);
|
||||
var connectorInitializerType = connectorAssembly.GetType("Sia.Gateway.Initialization.Initialization");
|
||||
|
||||
var ticketType = (Type)connectorInitializerType.GetMethod("TicketType").Invoke(null, null);
|
||||
services.AddIncidentClient(ticketType);
|
||||
|
||||
var connectorConfigureServices = connectorInitializerType.GetMethod("AddConnector", new Type[] { typeof(IServiceCollection), typeof(IConfigurationRoot), typeof(IHostingEnvironment) });
|
||||
connectorConfigureServices.Invoke(null, new object[] { services, config, env });
|
||||
}
|
||||
|
||||
private static void AddIncidentClient(this IServiceCollection services, Type ticketType)
|
||||
{
|
||||
var handlerType = typeof(GetIncidentHandler<>).MakeGenericType(new Type[] { ticketType });
|
||||
services.AddScoped(typeof(IGetIncidentHandler), handlerType);
|
||||
services.AddScoped<IAsyncRequestHandler<GetIncidentRequest, Incident>, GetIncidentHandlerWrapper>();
|
||||
}
|
||||
|
||||
public static void AddThirdPartyServices(this IServiceCollection services, IConfigurationRoot config)
|
||||
{
|
||||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
//Todo: figure out what else should be in this file.
|
||||
|
||||
#Setting Up Your Development Environment
|
||||
|
||||
This Gateway is intended to be extended by Sia microservices in other code repositories.
|
||||
|
||||
In order to test functionality of the gateway on your local machine:
|
||||
1. For each 'Service' in appsettings.Development.json:
|
||||
1. Clone the associated repository (Example: Incident is associated with the SiaManagement repository)
|
||||
2. Change the default startup from the project name to IIS Express and start
|
||||
3. You will need to have a separate visual studio instance open for each service the gateway will interact with.
|
||||
(If you find a good way to do this without having visual studio open, please let the maintainers of this project know)
|
||||
4. Validate that the localhost port the service is running on matches its configuration in appsettings.Development.json.
|
||||
You can find IIS Express information by right clicking the IIS Express icon in your system tray.
|
||||
2. Get appropriate Secrets for authentication
|
||||
1. Right click on the Sia.Gateway project and select "Manage user secrets"
|
||||
2. Open usersecrets.template.json to use as a template for your usersecrets json file
|
||||
3. You will need these configuration values:
|
||||
1. Your AAD instance (the default is correct for the vast majority of scenarios)
|
||||
2. Your AAD tenant
|
||||
3. Your Key Vault vault name
|
||||
4. Your ApplicationInsights instrumentation key name
|
||||
5. The secret that is used to authenticate to azure key vault for the gateway (ClientSecret)
|
||||
* This secret can be found in the azure portal in the AAD App Registration for your Gateway instance
|
||||
3. Start the gateway
|
||||
|
||||
|
||||
#Ticketing System Connectors
|
||||
|
||||
Sia can be configured to to work with a separate ticketing system,
|
||||
either by leveraging a proxy API or by direct access with a custom connector.
|
||||
|
||||
Data from connected ticketing systems can be used to generate events and/or present
|
||||
additional information and context on the UI.
|
||||
|
||||
Sia will still function without a connected ticketing system, and will persist limited
|
||||
ticket data in the Incident database (see Sia.Data.Incident\Models\Ticket.cs)
|
||||
|
||||
For additional information on use, configuration, and creation of ticketing system
|
||||
connectors, see Sia.Connectors.Tickets\README.md
|
|
@ -1,4 +1,5 @@
|
|||
using MediatR;
|
||||
using AutoMapper;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Connectors.Tickets;
|
||||
using Sia.Data.Incidents;
|
||||
|
@ -21,52 +22,26 @@ namespace Sia.Gateway.Requests
|
|||
public long Id { get; }
|
||||
}
|
||||
|
||||
public class GetIncidentHandler<TTicket> : IGetIncidentHandler
|
||||
public class GetIncidentHandler
|
||||
: IncidentConnectorHandler<GetIncidentRequest, Incident>
|
||||
{
|
||||
private readonly IncidentContext _context;
|
||||
private readonly Connector<TTicket> _connector;
|
||||
public GetIncidentHandler(IncidentContext context, Connector<TTicket> connector)
|
||||
|
||||
public GetIncidentHandler(IncidentContext context, Connector connector)
|
||||
:base(context, connector){}
|
||||
|
||||
public override async Task<Incident> Handle(GetIncidentRequest getIncident)
|
||||
{
|
||||
_context = context;
|
||||
_connector = connector;
|
||||
}
|
||||
public async Task<Incident> Handle(GetIncidentRequest getIncident)
|
||||
{
|
||||
var incidentRecord = await _context.Incidents
|
||||
.WithEagerLoading()
|
||||
.FirstOrDefaultAsync(cr => cr.Id == getIncident.Id);
|
||||
var incidentRecord = await _context
|
||||
.Incidents
|
||||
.WithEagerLoading()
|
||||
.SingleOrDefaultAsync(cr => cr.Id == getIncident.Id);
|
||||
if (incidentRecord == null) throw new KeyNotFoundException();
|
||||
|
||||
var remoteId = incidentRecord
|
||||
.Tickets
|
||||
.FirstOrDefault(t => t.IsPrimary)
|
||||
.OriginId;
|
||||
var incident = Mapper.Map<Incident>(incidentRecord);
|
||||
|
||||
var ticket = await _connector.Client.GetAsync(remoteId);
|
||||
AttachTickets(incident);
|
||||
|
||||
return _connector
|
||||
.Converter
|
||||
.AssembleIncident(incidentRecord, ticket);
|
||||
return incident;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IGetIncidentHandler
|
||||
{
|
||||
Task<Incident> Handle(GetIncidentRequest getIncident);
|
||||
}
|
||||
|
||||
//Why does this exist?
|
||||
//Purely because I haven't been able to get Mediatr to work with generics
|
||||
public class GetIncidentHandlerWrapper : IAsyncRequestHandler<GetIncidentRequest, Incident>
|
||||
{
|
||||
private readonly IGetIncidentHandler _actualHandler;
|
||||
|
||||
public GetIncidentHandlerWrapper(IGetIncidentHandler actualHandler)
|
||||
{
|
||||
_actualHandler = actualHandler;
|
||||
}
|
||||
|
||||
public Task<Incident> Handle(GetIncidentRequest message)
|
||||
=> _actualHandler.Handle(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using AutoMapper.QueryableExtensions;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Connectors.Tickets;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Shared.Authentication;
|
||||
|
@ -19,19 +20,19 @@ namespace Sia.Gateway.Requests
|
|||
}
|
||||
|
||||
public class GetIncidentsHandler
|
||||
: IncidentContextHandler<GetIncidentsRequest, IEnumerable<Incident>>
|
||||
: IncidentConnectorHandler<GetIncidentsRequest, IEnumerable<Incident>>
|
||||
{
|
||||
public GetIncidentsHandler(IncidentContext context)
|
||||
:base(context)
|
||||
{
|
||||
|
||||
}
|
||||
public GetIncidentsHandler(
|
||||
IncidentContext context,
|
||||
Connector connector
|
||||
) : base(context, connector) {}
|
||||
public override async Task<IEnumerable<Incident>> Handle(GetIncidentsRequest request)
|
||||
{
|
||||
var incidentRecords = await _context.Incidents
|
||||
.WithEagerLoading()
|
||||
.ProjectTo<Incident>()
|
||||
.ToListAsync();
|
||||
AttachTickets(incidentRecords);
|
||||
return incidentRecords;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using AutoMapper.QueryableExtensions;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sia.Connectors.Tickets;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Domain;
|
||||
using Sia.Domain.ApiModels;
|
||||
|
@ -13,10 +14,13 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Sia.Gateway.Requests
|
||||
{
|
||||
public class GetIncidentsByTicketCreateIfNeededRequest : AuthenticatedRequest<IEnumerable<Incident>>
|
||||
public class GetIncidentsByTicketCreateIfNeededRequest
|
||||
: AuthenticatedRequest<IEnumerable<Incident>>
|
||||
{
|
||||
public GetIncidentsByTicketCreateIfNeededRequest(string ticketId, AuthenticatedUserContext userContext)
|
||||
: base(userContext)
|
||||
public GetIncidentsByTicketCreateIfNeededRequest(
|
||||
string ticketId,
|
||||
AuthenticatedUserContext userContext
|
||||
) : base(userContext)
|
||||
{
|
||||
TicketId = ticketId;
|
||||
}
|
||||
|
@ -25,23 +29,32 @@ namespace Sia.Gateway.Requests
|
|||
|
||||
}
|
||||
|
||||
public class GetIncidentsByTicketCreateIfNeededRequestHandler : IncidentContextHandler<GetIncidentsByTicketCreateIfNeededRequest, IEnumerable<Incident>>
|
||||
public class GetIncidentsByTicketCreateIfNeededRequestHandler
|
||||
: IncidentConnectorHandler<
|
||||
GetIncidentsByTicketCreateIfNeededRequest,
|
||||
IEnumerable<Incident>
|
||||
>
|
||||
{
|
||||
public GetIncidentsByTicketCreateIfNeededRequestHandler(IncidentContext context)
|
||||
:base(context)
|
||||
{
|
||||
|
||||
}
|
||||
public GetIncidentsByTicketCreateIfNeededRequestHandler(
|
||||
IncidentContext context,
|
||||
Connector connector
|
||||
) :base(context, connector){}
|
||||
|
||||
public override async Task<IEnumerable<Incident>> Handle(GetIncidentsByTicketCreateIfNeededRequest message)
|
||||
public override async Task<IEnumerable<Incident>> Handle(
|
||||
GetIncidentsByTicketCreateIfNeededRequest message
|
||||
)
|
||||
{
|
||||
var incidents = await _context.Incidents
|
||||
.WithEagerLoading()
|
||||
.Where(incident => incident.Tickets.Any(inc => inc.OriginId == message.TicketId))
|
||||
.Where(incident => incident
|
||||
.Tickets
|
||||
.Any(inc => inc.OriginId == message.TicketId))
|
||||
.ProjectTo<Incident>().ToListAsync();
|
||||
|
||||
if (incidents.Any())
|
||||
{
|
||||
AttachTickets(incidents);
|
||||
return incidents;
|
||||
}
|
||||
|
||||
|
@ -59,7 +72,10 @@ namespace Sia.Gateway.Requests
|
|||
var result = _context.Incidents.Add(dataIncident);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return new List<Incident> { Mapper.Map<Incident>(result.Entity) };
|
||||
var incidentDto = Mapper.Map<Incident>(result.Entity);
|
||||
AttachTickets(incidentDto);
|
||||
|
||||
return new List<Incident> { incidentDto };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Sia.Data.Incidents;
|
||||
using Sia.Connectors.Tickets;
|
||||
using MediatR;
|
||||
using Sia.Domain;
|
||||
|
||||
namespace Sia.Gateway.Requests
|
||||
{
|
||||
public abstract class IncidentConnectorHandler<TRequest, TResult>
|
||||
: IncidentContextHandler<TRequest, TResult>
|
||||
where TRequest : IRequest<TResult>
|
||||
{
|
||||
protected readonly Connector _connector;
|
||||
protected IncidentConnectorHandler(
|
||||
IncidentContext context,
|
||||
Connector connector
|
||||
) : base(context)
|
||||
{
|
||||
_connector = connector;
|
||||
}
|
||||
|
||||
protected void AttachTickets(Incident incident)
|
||||
=> _connector.AppendData(incident.Tickets);
|
||||
|
||||
protected void AttachTickets(List<Incident> incidents)
|
||||
=> incidents.ForEach(inc => AttachTickets(inc));
|
||||
|
||||
}
|
||||
}
|
|
@ -24,8 +24,12 @@ namespace Sia.Gateway.Tests.Requests
|
|||
Id = expectedIncidentId,
|
||||
Title = expectedIncidentTitle
|
||||
};
|
||||
var serviceUnderTest = new GetIncidentHandler<EmptyTicket>(await MockFactory.IncidentContext("Get"), new NoConnector(new NoClient(), new NoConverter()));
|
||||
var request = new GetIncidentRequest(expectedIncidentId, new DummyAuthenticatedUserContext());
|
||||
var serviceUnderTest = new GetIncidentHandler(
|
||||
await MockFactory.IncidentContext("Get"),
|
||||
new NoConnector(new NoClient(), new StubLoggerFactory()));
|
||||
var request = new GetIncidentRequest(
|
||||
expectedIncidentId,
|
||||
new DummyAuthenticatedUserContext());
|
||||
|
||||
|
||||
var result = await serviceUnderTest.Handle(request);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sia.Connectors.Tickets.None;
|
||||
using Sia.Domain;
|
||||
using Sia.Gateway.Initialization;
|
||||
using Sia.Gateway.Requests;
|
||||
|
@ -21,7 +22,10 @@ namespace Sia.Gateway.Tests.Requests
|
|||
[TestMethod]
|
||||
public async Task Handle_WhenIncidentNotExist_ReturnNewIncident()
|
||||
{
|
||||
var serviceUnderTest = new GetIncidentsByTicketCreateIfNeededRequestHandler(await MockFactory.IncidentContext("Get"));
|
||||
var serviceUnderTest = new GetIncidentsByTicketCreateIfNeededRequestHandler(
|
||||
await MockFactory.IncidentContext("Get"),
|
||||
new NoConnector(new NoClient(), new StubLoggerFactory())
|
||||
);
|
||||
var request = new GetIncidentsByTicketCreateIfNeededRequest("100", new DummyAuthenticatedUserContext());
|
||||
|
||||
|
||||
|
@ -36,7 +40,10 @@ namespace Sia.Gateway.Tests.Requests
|
|||
public async Task Handle_WhenIncidentExists_ReturnCorrectIncidents()
|
||||
{
|
||||
|
||||
var serviceUnderTest = new GetIncidentsByTicketCreateIfNeededRequestHandler(await MockFactory.IncidentContext("Get"));
|
||||
var serviceUnderTest = new GetIncidentsByTicketCreateIfNeededRequestHandler(
|
||||
await MockFactory.IncidentContext("Get"),
|
||||
new NoConnector(new NoClient(), new StubLoggerFactory())
|
||||
);
|
||||
var request = new GetIncidentsByTicketCreateIfNeededRequest("44444444", new DummyAuthenticatedUserContext());
|
||||
|
||||
var result = (await serviceUnderTest.Handle(request)).ToList();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sia.Connectors.Tickets.None;
|
||||
using Sia.Domain;
|
||||
using Sia.Gateway.Initialization;
|
||||
using Sia.Gateway.Requests;
|
||||
|
@ -34,7 +35,10 @@ namespace Sia.Gateway.Tests.Requests
|
|||
Title = expectedIncidentTitles[i]
|
||||
};
|
||||
}
|
||||
var serviceUnderTest = new GetIncidentsHandler(await MockFactory.IncidentContext("Get"));
|
||||
var serviceUnderTest = new GetIncidentsHandler(
|
||||
await MockFactory.IncidentContext("Get"),
|
||||
new NoConnector(new NoClient(), new StubLoggerFactory())
|
||||
);
|
||||
var request = new GetIncidentsRequest(new DummyAuthenticatedUserContext());
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sia.Gateway.Tests.TestDoubles
|
||||
{
|
||||
public class StubLoggerFactory
|
||||
: ILoggerFactory
|
||||
{
|
||||
public void AddProvider(ILoggerProvider provider) => throw new NotImplementedException();
|
||||
public ILogger CreateLogger(string categoryName) => null;
|
||||
public void Dispose() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче