зеркало из https://github.com/Azure/Sia-Root.git
Merge remote-tracking branch 'origin/master' into pdimit/DomainInRoot
This commit is contained in:
Коммит
e12c519604
|
@ -7,6 +7,7 @@ namespace Sia.Domain.ApiModels.Playbooks
|
|||
{
|
||||
public class CreateAction
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public CreateActionTemplate NewActionTemplate { get; set; }
|
||||
public long? ExistingActionTemplateId { get; set; }
|
||||
public ICollection<CreateConditionSet> NewConditionSets { get; set; }
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace Sia.Domain.Playbook
|
|||
public class Action : IEntity
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public ActionTemplate ActionTemplate { get; set; }
|
||||
public ICollection<ConditionSet> ConditionSets { get; set; }
|
||||
= new List<ConditionSet>();
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace Sia.Domain.Playbook
|
|||
{
|
||||
AnyOf,
|
||||
AllOf,
|
||||
NoneOf
|
||||
NoneOf,
|
||||
NotAllOf
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Sia.Shared.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Sia.Shared.Tests.Data
|
||||
{
|
||||
[TestClass]
|
||||
public class PartialJsonResolverTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ResolveJsonToString_Method_Resolve_WhenSourceValid_SerializesSourceArgumentObjectToString()
|
||||
{
|
||||
var input = new TestHasJsonDataObject
|
||||
{
|
||||
Data = new JsonSerializationTestObject()
|
||||
};
|
||||
var expectedResultDataValue = JsonSerializationTestObject.ExpectedSerialization();
|
||||
var objectUnderTest = new ResolveJsonToString<TestHasJsonDataObject, TestHasJsonDataString>();
|
||||
|
||||
|
||||
var result = objectUnderTest.Resolve(input, null, null, null);
|
||||
|
||||
|
||||
Assert.AreEqual(expectedResultDataValue, result, false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResolveStringToJson_Method_Resolve_WhenSourceValid_SerializesSourceArgumentStringToObject()
|
||||
{
|
||||
var input = new TestHasJsonDataString()
|
||||
{
|
||||
Data = JsonSerializationTestObject.ExpectedSerialization()
|
||||
};
|
||||
var expectedResult = new JsonSerializationTestObject();
|
||||
var objectUnderTest = new ResolveStringToJson<TestHasJsonDataString, TestHasJsonDataObject>();
|
||||
|
||||
|
||||
var result = objectUnderTest.Resolve(input, null, null, null);
|
||||
|
||||
|
||||
Assert.AreEqual(expectedResult.a, ExtractPropertyFromResult(result, "a"));
|
||||
Assert.AreEqual(expectedResult.b, ExtractPropertyFromResult(result, "b"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResolveJsonToString_Method_Resolve_WhenSourceDataNull_ReturnsNull()
|
||||
{
|
||||
var input = new TestHasJsonDataObject
|
||||
{
|
||||
Data = null
|
||||
};
|
||||
var expectedResultDataValue = JsonSerializationTestObject.ExpectedSerialization();
|
||||
var objectUnderTest = new ResolveJsonToString<TestHasJsonDataObject, TestHasJsonDataString>();
|
||||
|
||||
|
||||
var result = objectUnderTest.Resolve(input, null, null, null);
|
||||
|
||||
|
||||
Assert.AreEqual(null, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResolveStringToJson_Method_Resolve_WhenSourceDataNull_ReturnsNull()
|
||||
{
|
||||
var input = new TestHasJsonDataString()
|
||||
{
|
||||
Data = null
|
||||
};
|
||||
var expectedResult = new JsonSerializationTestObject();
|
||||
var objectUnderTest = new ResolveStringToJson<TestHasJsonDataString, TestHasJsonDataObject>();
|
||||
|
||||
|
||||
var result = objectUnderTest.Resolve(input, null, null, null);
|
||||
|
||||
|
||||
Assert.AreEqual(null, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResolveJsonToString_Method_Resolve_WhenSourceNull_ReturnsNull()
|
||||
{
|
||||
TestHasJsonDataObject input = null;
|
||||
var expectedResultDataValue = JsonSerializationTestObject.ExpectedSerialization();
|
||||
var objectUnderTest = new ResolveJsonToString<TestHasJsonDataObject, TestHasJsonDataString>();
|
||||
|
||||
|
||||
var result = objectUnderTest.Resolve(input, null, null, null);
|
||||
|
||||
|
||||
Assert.AreEqual(null, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResolveStringToJson_Method_Resolve_WhenSourceNull_ReturnsNull()
|
||||
{
|
||||
TestHasJsonDataString input = null;
|
||||
var expectedResult = new JsonSerializationTestObject();
|
||||
var objectUnderTest = new ResolveStringToJson<TestHasJsonDataString, TestHasJsonDataObject>();
|
||||
|
||||
|
||||
var result = objectUnderTest.Resolve(input, null, null, null);
|
||||
|
||||
|
||||
Assert.AreEqual(null, result);
|
||||
}
|
||||
|
||||
private static JToken ExtractPropertyFromResult(object result, string propName) => ((JObject)result).Property(propName).Value;
|
||||
}
|
||||
|
||||
internal class JsonSerializationTestObject :IEquatable<JsonSerializationTestObject>
|
||||
{
|
||||
public static string ExpectedSerialization()
|
||||
=> "{\"a\":\"ValueOfA\",\"b\":1}";
|
||||
public bool Equals(JsonSerializationTestObject other)
|
||||
=> a == other.a && b == other.b;
|
||||
|
||||
public string a { get; set; } = "ValueOfA";
|
||||
public int b { get; set; } = 1;
|
||||
}
|
||||
|
||||
internal class TestHasJsonDataString : IJsonDataString
|
||||
{
|
||||
public string Data { get; set; }
|
||||
}
|
||||
|
||||
internal class TestHasJsonDataObject : IJsonDataObject
|
||||
{
|
||||
public object Data { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20171012-09" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Sia.Shared\Sia.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,65 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sia.Shared.Validation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sia.Shared.Tests.Validation
|
||||
{
|
||||
[TestClass]
|
||||
public class ThrowIfTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Null_StaticMethod_WhenObjectIsNotNull_ReturnsObject()
|
||||
{
|
||||
var input = new Object();
|
||||
|
||||
var result = ThrowIf.Null(input, nameof(input));
|
||||
|
||||
Assert.AreSame(input, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void Null_StaticMethod_WhenObjectIsNull_ThrowsArgumentNullException()
|
||||
{
|
||||
object input = null;
|
||||
|
||||
var result = ThrowIf.Null(input, nameof(input));
|
||||
|
||||
//expect exception
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void NullOrWhiteSpace_StaticMethod_WhenInputIsNull_ThrowsArgumentException()
|
||||
{
|
||||
string input = null;
|
||||
|
||||
var result = ThrowIf.NullOrWhiteSpace(input, nameof(input));
|
||||
|
||||
//Expect exception
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void NullOrWhiteSpace_StaticMethod_WhenInputIsOnlyWhitespace_ThrowsArgumentException()
|
||||
{
|
||||
string input = " ";
|
||||
|
||||
var result = ThrowIf.NullOrWhiteSpace(input, nameof(input));
|
||||
|
||||
//Expect exception
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NullOrWhiteSpace_StaticMethod_WhenInputStringWithAnyNonWhitespace_ReturnsString()
|
||||
{
|
||||
string input = " . ";
|
||||
|
||||
var result = ThrowIf.NullOrWhiteSpace(input, nameof(input));
|
||||
|
||||
Assert.AreSame(input, result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
|
||||
namespace Sia.Shared.Authentication
|
||||
{
|
||||
public class AzureActiveDirectoryAuthenticationInfo
|
||||
{
|
||||
public AzureActiveDirectoryAuthenticationInfo(string resource, string clientId, string clientSecret, string tenant)
|
||||
{
|
||||
Resource = resource;
|
||||
ClientId = clientId;
|
||||
ClientSecret = clientSecret;
|
||||
Tenant = tenant;
|
||||
}
|
||||
/// <summary>
|
||||
/// For the public AAD endpoint, this will be https://login.microsoftonline.com/{0}, but it may be different in China and potentially other sovereign clouds
|
||||
/// </summary>
|
||||
public string AadInstance => "https://login.microsoftonline.com/{0}";
|
||||
/// <summary>
|
||||
/// The client ID or resource URI of the application you're authenticating TO
|
||||
/// </summary>
|
||||
public string Resource { get; }
|
||||
/// <summary>
|
||||
/// The application ID of the application you're authenticating FROM
|
||||
/// </summary>
|
||||
public string ClientId { get; }
|
||||
/// <summary>
|
||||
/// A valid secret for the application you're authenticating FROM
|
||||
/// </summary>
|
||||
public string ClientSecret { get; }
|
||||
/// <summary>
|
||||
/// Tenant name. For Microsoft internal, microsoft.onmicrosoft.com can be used
|
||||
/// </summary>
|
||||
public string Tenant { get; }
|
||||
/// <summary>
|
||||
/// Authentication scheme to use. Must match scheme expected by remote resource.
|
||||
/// </summary>
|
||||
public string Authority => String.Format(AadInstance, Tenant);
|
||||
/// <summary>
|
||||
/// Authentication scheme to use. Must match scheme expected by remote resource.
|
||||
/// </summary>
|
||||
public string Scheme => "Bearer";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
|
||||
namespace Sia.Shared.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// From https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect-aspnetcore/blob/master/WebApp-WebAPI-OpenIdConnect-DotNet/Utils/NaiveSessionCache.cs
|
||||
/// </summary>
|
||||
public class NaiveSessionCache : TokenCache
|
||||
{
|
||||
private static readonly object FileLock = new object();
|
||||
string UserObjectId = string.Empty;
|
||||
string CacheId = string.Empty;
|
||||
ISession Session = null;
|
||||
|
||||
public NaiveSessionCache(string userId, ISession session)
|
||||
{
|
||||
UserObjectId = userId;
|
||||
CacheId = UserObjectId + "_TokenCache";
|
||||
Session = session;
|
||||
this.AfterAccess = AfterAccessNotification;
|
||||
this.BeforeAccess = BeforeAccessNotification;
|
||||
Load();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
lock (FileLock)
|
||||
{
|
||||
this.Deserialize(Session.Get(CacheId));
|
||||
}
|
||||
}
|
||||
|
||||
public void Persist()
|
||||
{
|
||||
lock (FileLock)
|
||||
{
|
||||
// reflect changes in the persistent store
|
||||
Session.Set(CacheId, this.Serialize());
|
||||
// once the write operation took place, restore the HasStateChanged bit to false
|
||||
this.HasStateChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Empties the persistent store.
|
||||
public override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
Session.Remove(CacheId);
|
||||
}
|
||||
|
||||
public override void DeleteItem(TokenCacheItem item)
|
||||
{
|
||||
base.DeleteItem(item);
|
||||
Persist();
|
||||
}
|
||||
|
||||
// Triggered right before ADAL needs to access the cache.
|
||||
// Reload the cache from the persistent store in case it changed since the last access.
|
||||
void BeforeAccessNotification(TokenCacheNotificationArgs args)
|
||||
{
|
||||
Load();
|
||||
}
|
||||
|
||||
// Triggered right after ADAL accessed the cache.
|
||||
void AfterAccessNotification(TokenCacheNotificationArgs args)
|
||||
{
|
||||
// if the access operation resulted in a cache update
|
||||
if (this.HasStateChanged)
|
||||
{
|
||||
Persist();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
using Sia.Shared.Authentication;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Net.Http
|
||||
{
|
||||
public static class HttpClientExtensions
|
||||
{
|
||||
public static async Task<HttpResponseMessage> GetAsync(this HttpClient client, string requestUri, AuthenticatedUserContext authenticationInfo)
|
||||
{
|
||||
return await client.SendAsync(requestUri, authenticationInfo, HttpMethod.Get);
|
||||
}
|
||||
|
||||
public static async Task<HttpResponseMessage> PostAsync(this HttpClient client, string requestUri, HttpContent postContent, AuthenticatedUserContext authenticationInfo)
|
||||
{
|
||||
return await client.SendAsync(requestUri, postContent, authenticationInfo, HttpMethod.Post);
|
||||
}
|
||||
|
||||
public static async Task<HttpResponseMessage> PutAsync(this HttpClient client, string requestUri, HttpContent postContent, AuthenticatedUserContext authenticationInfo)
|
||||
{
|
||||
return await client.SendAsync(requestUri, postContent, authenticationInfo, HttpMethod.Put);
|
||||
}
|
||||
|
||||
private static async Task<HttpResponseMessage> SendAsync(this HttpClient client, string requestUri, AuthenticatedUserContext authenticationInfo, HttpMethod method)
|
||||
{
|
||||
HttpRequestMessage request = await GenerateRequest(requestUri, authenticationInfo, method);
|
||||
|
||||
return await client.SendAsync(request);
|
||||
}
|
||||
|
||||
private static async Task<HttpResponseMessage> SendAsync(this HttpClient client, string requestUri, HttpContent postContent, AuthenticatedUserContext authenticationInfo, HttpMethod method)
|
||||
{
|
||||
HttpRequestMessage request = await GenerateRequest(requestUri, authenticationInfo, method);
|
||||
request.Content = postContent;
|
||||
|
||||
return await client.SendAsync(request);
|
||||
}
|
||||
|
||||
private static async Task<HttpRequestMessage> GenerateRequest(string requestUri, AuthenticatedUserContext authenticationInfo, HttpMethod method)
|
||||
{
|
||||
var tokenResult = await AcquireTokenAsync(authenticationInfo);
|
||||
|
||||
var request = new HttpRequestMessage(method, requestUri);
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue(authenticationInfo.AuthConfig.Scheme, tokenResult);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static async Task<string> AcquireTokenAsync(AuthenticatedUserContext authenticationInfo)
|
||||
{
|
||||
//Todo: per-user auth based on delegated identity
|
||||
//string userObjectID = (authenticationInfo.User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
|
||||
string userObjectID = "Test";
|
||||
|
||||
var authContext = new AuthenticationContext(authenticationInfo.AuthConfig.Authority, new NaiveSessionCache(userObjectID, authenticationInfo.Session));
|
||||
var credential = new ClientCredential(authenticationInfo.AuthConfig.ClientId, authenticationInfo.AuthConfig.ClientSecret);
|
||||
try
|
||||
{
|
||||
var result = await authContext.AcquireTokenSilentAsync(authenticationInfo.AuthConfig.Resource, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
|
||||
return result.AccessToken;
|
||||
}
|
||||
catch (AdalSilentTokenAcquisitionException)
|
||||
{
|
||||
var result = await authContext.AcquireTokenAsync(authenticationInfo.AuthConfig.Resource, credential);
|
||||
return result.AccessToken;
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpClient CreateHttpClient(string baseUrl)
|
||||
{
|
||||
var client = new HttpClient()
|
||||
{
|
||||
BaseAddress = new Uri(baseUrl)
|
||||
};
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(JsonMediaType));
|
||||
return client;
|
||||
}
|
||||
|
||||
public const string JsonMediaType = "application/json";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using Sia.Shared.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
|
||||
namespace Sia.Shared.Authentication.Http
|
||||
{
|
||||
public class HttpClientLookup
|
||||
{
|
||||
private IDictionary<string, string> _endpointToBaseUrl;
|
||||
private IDictionary<string, HttpClient> _endpointToHttpClient { get; }
|
||||
|
||||
public HttpClientLookup()
|
||||
{
|
||||
_endpointToBaseUrl = new Dictionary<string, string>();
|
||||
_endpointToHttpClient = new Dictionary<string, HttpClient>();
|
||||
}
|
||||
|
||||
public void RegisterEndpoint(string endpointName, string baseUrl)
|
||||
=> _endpointToBaseUrl.Add(endpointName, baseUrl);
|
||||
|
||||
public HttpClient GetClientForEndpoint(string endpointName)
|
||||
{
|
||||
if (_endpointToHttpClient.TryGetValue(endpointName, out HttpClient storedHttpClient)) return storedHttpClient;
|
||||
if (!_endpointToBaseUrl.TryGetValue(endpointName, out string baseUrl)) throw new EndpointNotConfiguredException(endpointName);
|
||||
|
||||
var client = HttpClientExtensions.CreateHttpClient(baseUrl);
|
||||
|
||||
_endpointToHttpClient.Add(endpointName, client);
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Sia.Shared.Authentication
|
||||
{
|
||||
public class AuthenticatedUserContext
|
||||
{
|
||||
public AuthenticatedUserContext(ClaimsPrincipal user, ISession session, AzureActiveDirectoryAuthenticationInfo authConfig)
|
||||
{
|
||||
User = user;
|
||||
Session = session;
|
||||
AuthConfig = authConfig;
|
||||
}
|
||||
|
||||
public ClaimsPrincipal User { get; private set; }
|
||||
public ISession Session { get; private set; }
|
||||
public AzureActiveDirectoryAuthenticationInfo AuthConfig { get; private set; }
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ namespace Sia.Shared.Data
|
|||
where TDestination: IJsonDataString
|
||||
{
|
||||
public string Resolve(TSource source, TDestination destination, string destMember, ResolutionContext context)
|
||||
=> source.Data is null ? null : JsonConvert.SerializeObject(source.Data);
|
||||
=> source?.Data is null ? null : JsonConvert.SerializeObject(source.Data);
|
||||
}
|
||||
|
||||
public class ResolveStringToJson<TSource, TDestination>
|
||||
|
@ -34,6 +34,6 @@ namespace Sia.Shared.Data
|
|||
where TDestination : IJsonDataObject
|
||||
{
|
||||
public object Resolve(TSource source, TDestination destination, object destMember, ResolutionContext context)
|
||||
=> source.Data is null ? null : JsonConvert.DeserializeObject(source.Data);
|
||||
=> source?.Data is null ? null : JsonConvert.DeserializeObject(source.Data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
using Sia.Shared.Transactions;
|
||||
using Sia.Shared.Protocol;
|
||||
using System;
|
||||
|
||||
namespace Sia.Shared.Exceptions
|
||||
{
|
||||
public static class ConvertStatusToException
|
||||
{
|
||||
public static void ThrowExceptionOnUnsuccessfulStatus<T>(this IResponse<T> response)
|
||||
public static void ThrowExceptionOnUnsuccessfulStatus(this IResponse response)
|
||||
{
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sia.Shared.Exceptions
|
||||
{
|
||||
public class EndpointNotConfiguredException : Exception
|
||||
{
|
||||
public EndpointNotConfiguredException(string endpointName)
|
||||
: base($"No base URI was configured for ${endpointName}")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using AutoMapper.EquivalencyExpression;
|
||||
using Sia.Shared.Data;
|
||||
|
||||
namespace AutoMapper
|
||||
{
|
||||
public static class AutoMapperInitializationExtensions
|
||||
{
|
||||
public static IMappingExpression<TSource, TDestination> UseResolveJsonToString<TSource, TDestination>(this IMappingExpression<TSource, TDestination> mapping)
|
||||
where TSource : IJsonDataObject
|
||||
where TDestination : IJsonDataString
|
||||
=> mapping.ForMember((ev) => ev.Data, (config) => config.ResolveUsing<ResolveJsonToString<TSource, TDestination>>());
|
||||
|
||||
|
||||
public static IMappingExpression<TSource, TDestination> UseResolveStringToJson<TSource, TDestination>(this IMappingExpression<TSource, TDestination> mapping)
|
||||
where TSource : IJsonDataString
|
||||
where TDestination : IJsonDataObject
|
||||
=> mapping.ForMember((ev) => ev.Data, (config) => config.ResolveUsing<ResolveStringToJson<TSource, TDestination>>());
|
||||
|
||||
|
||||
public static IMappingExpression<T1, T2> EqualityInsertOnly<T1, T2>(this IMappingExpression<T1, T2> mappingExpression)
|
||||
where T1 : class
|
||||
where T2 : class
|
||||
=> mappingExpression.EqualityComparison((one, two) => false);
|
||||
|
||||
public static IMappingExpression<T1, T2> EqualityById<T1, T2>(this IMappingExpression<T1, T2> mappingExpression)
|
||||
where T1 : class, IEntity
|
||||
where T2 : class, IEntity
|
||||
=> mappingExpression.EqualityComparison((one, two) => one.Id == two.Id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using MediatR;
|
||||
using Sia.Shared.Authentication;
|
||||
|
||||
namespace Sia.Shared.Requests
|
||||
{
|
||||
public abstract class AuthenticatedRequest<T> : AuthenticatedRequestBase, IRequest<T>
|
||||
{
|
||||
protected AuthenticatedRequest(AuthenticatedUserContext userContext) : base(userContext)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AuthenticatedRequest : AuthenticatedRequestBase, IRequest
|
||||
{
|
||||
protected AuthenticatedRequest(AuthenticatedUserContext userContext) : base(userContext)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AuthenticatedRequestBase
|
||||
{
|
||||
protected AuthenticatedRequestBase(AuthenticatedUserContext userContext)
|
||||
{
|
||||
UserContext = userContext;
|
||||
}
|
||||
|
||||
public AuthenticatedUserContext UserContext { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Shared.Requests
|
||||
{
|
||||
public abstract class DatabaseOperationHandler<TContext, TRequest, TResult> : IAsyncRequestHandler<TRequest, TResult>
|
||||
where TRequest : IRequest<TResult>
|
||||
where TContext : DbContext
|
||||
{
|
||||
protected readonly TContext _context;
|
||||
|
||||
protected DatabaseOperationHandler(TContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public abstract Task<TResult> Handle(TRequest message);
|
||||
}
|
||||
|
||||
public abstract class DatabaseOperationHandler<TContext, TRequest> : IAsyncRequestHandler<TRequest>
|
||||
where TRequest : IRequest
|
||||
where TContext : DbContext
|
||||
{
|
||||
protected readonly TContext _context;
|
||||
|
||||
protected DatabaseOperationHandler(TContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public abstract Task Handle(TRequest message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using MediatR;
|
||||
using Sia.Shared.Authentication.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using Sia.Shared.Protocol;
|
||||
using Sia.Shared.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
using Sia.Shared.Requests;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace Sia.Shared.Extensions.Mediatr
|
||||
{
|
||||
public abstract class ProxyHandler<TRequest, TResult> : ProxyHandlerBase<TRequest>, IAsyncRequestHandler<TRequest, TResult>
|
||||
where TRequest : AuthenticatedRequest<TResult>
|
||||
{
|
||||
protected ProxyHandler(HttpClientLookup clientFactory, string endpointName)
|
||||
: base(clientFactory, endpointName)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual async Task<TResult> Handle(TRequest request)
|
||||
{
|
||||
var httpResponse = await SendRequest(request);
|
||||
var logicalResponse = await Response<TResult>.Create(httpResponse);
|
||||
logicalResponse.ThrowExceptionOnUnsuccessfulStatus();
|
||||
return logicalResponse.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ProxyHandler<TRequest> : ProxyHandlerBase<TRequest>, IAsyncRequestHandler<TRequest>
|
||||
where TRequest : AuthenticatedRequest
|
||||
{
|
||||
protected ProxyHandler(HttpClientLookup clientFactory, string endpointName)
|
||||
: base(clientFactory, endpointName)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual async Task Handle(TRequest request)
|
||||
{
|
||||
var httpResponse = await SendRequest(request);
|
||||
var logicalResponse = await Response.Create(httpResponse);
|
||||
logicalResponse.ThrowExceptionOnUnsuccessfulStatus();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ProxyHandlerBase<TRequest>
|
||||
where TRequest: AuthenticatedRequestBase
|
||||
{
|
||||
protected readonly HttpClient _client;
|
||||
protected abstract HttpMethod Method();
|
||||
protected abstract string RelativeUri(TRequest request);
|
||||
protected abstract object MessageContent(TRequest request);
|
||||
|
||||
protected virtual async Task<HttpResponseMessage> SendRequest(TRequest request)
|
||||
{
|
||||
var message = new HttpRequestMessage(Method(), RelativeUri(request));
|
||||
AddContentToMessage(request, message);
|
||||
await AddAuthorizationToMessage(request, message);
|
||||
return await _client.SendAsync(message);
|
||||
}
|
||||
protected virtual void AddContentToMessage(TRequest request, HttpRequestMessage message)
|
||||
{
|
||||
var content = MessageContent(request);
|
||||
if (content is null) return;
|
||||
var encodedContent = JsonConvert.SerializeObject(content);
|
||||
var httpContent = new StringContent(encodedContent, Encoding.UTF8, HttpClientExtensions.JsonMediaType);
|
||||
message.Content = httpContent;
|
||||
}
|
||||
protected virtual async Task AddAuthorizationToMessage(TRequest request, HttpRequestMessage message)
|
||||
{
|
||||
var tokenResult = await HttpClientExtensions.AcquireTokenAsync(request.UserContext);
|
||||
message.Headers.Authorization = new AuthenticationHeaderValue(request.UserContext.AuthConfig.Scheme, tokenResult);
|
||||
}
|
||||
|
||||
protected ProxyHandlerBase(HttpClientLookup clientFactory, string endpointName)
|
||||
{
|
||||
_client = clientFactory.GetClientForEndpoint(endpointName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Sia.Shared.Authentication;
|
||||
using Sia.Shared.Validation.Filters;
|
||||
|
||||
namespace Sia.Shared.Controllers
|
||||
{
|
||||
[Return400BadRequestWhenModelStateInvalid]
|
||||
[Authorize()]
|
||||
public abstract class BaseController : Controller
|
||||
{
|
||||
protected readonly IMediator _mediator;
|
||||
protected readonly AzureActiveDirectoryAuthenticationInfo _authConfig;
|
||||
protected readonly IUrlHelper _urlHelper;
|
||||
|
||||
protected AuthenticatedUserContext _authContext => new AuthenticatedUserContext(User, HttpContext.Session, _authConfig);
|
||||
|
||||
protected BaseController(IMediator mediator,
|
||||
AzureActiveDirectoryAuthenticationInfo authConfig,
|
||||
IUrlHelper urlHelper)
|
||||
{
|
||||
_mediator = mediator;
|
||||
_authConfig = authConfig;
|
||||
_urlHelper = urlHelper;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Sia.Shared.Exceptions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Shared.Middleware
|
||||
{
|
||||
public class ExceptionHandler
|
||||
{
|
||||
private RequestDelegate _next;
|
||||
|
||||
public ExceptionHandler(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
catch (BaseException ex)
|
||||
{
|
||||
await HandleExceptionAsync(context, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleExceptionAsync(HttpContext context, BaseException ex)
|
||||
{
|
||||
var result = JsonConvert.SerializeObject(new { error = ex.Message });
|
||||
context.Response.ContentType = "application/json";
|
||||
context.Response.StatusCode = ex.StatusCode;
|
||||
await context.Response.WriteAsync(result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,17 @@
|
|||
using System.Net;
|
||||
|
||||
namespace Sia.Shared.Transactions
|
||||
namespace Sia.Shared.Protocol
|
||||
{
|
||||
public interface IResponse<T>
|
||||
public interface IResponse<T> : IResponse
|
||||
{
|
||||
T Value { get; }
|
||||
|
||||
}
|
||||
|
||||
public interface IResponse
|
||||
{
|
||||
HttpStatusCode StatusCode { get; }
|
||||
bool IsSuccessStatusCode { get; }
|
||||
string Content { get; }
|
||||
T Value { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Sia.Shared.Protocol
|
||||
{
|
||||
public class LinksHeader
|
||||
{
|
||||
private PaginationMetadata _metadata;
|
||||
private IUrlHelper _urlHelper;
|
||||
private string _routeName;
|
||||
|
||||
public LinksHeader(PaginationMetadata metadata, IUrlHelper urlHelper, string routeName)
|
||||
{
|
||||
_metadata = metadata;
|
||||
_urlHelper = urlHelper;
|
||||
_routeName = routeName;
|
||||
}
|
||||
|
||||
public const string HeaderName = "links";
|
||||
public StringValues HeaderValues => JsonConvert.SerializeObject(new
|
||||
{
|
||||
PageNumber = _metadata.PageNumber,
|
||||
PageSize = _metadata.PageSize,
|
||||
TotalRecords = _metadata.TotalRecords,
|
||||
TotalPages = _metadata.TotalPages,
|
||||
NextPageLink = _metadata.NextPageExists ? _urlHelper.Action(_routeName, _metadata.NextPageLinkInfo) : null,
|
||||
PrevPageLink = _metadata.PreviousPageExists ? _urlHelper.Action(_routeName, _metadata.PreviousPageLinkInfo) : null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Sia.Shared.Protocol;
|
||||
|
||||
public static class PaginationExtensions
|
||||
{
|
||||
public static void AddPagination(this IHeaderDictionary headers, LinksHeader header)
|
||||
{
|
||||
headers.Add("Access-Control-Expose-Headers", LinksHeader.HeaderName);
|
||||
headers.Add(LinksHeader.HeaderName, header.HeaderValues);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using Sia.Shared.Protocol;
|
||||
|
||||
namespace Sia.Shared.Protocol
|
||||
{
|
||||
public class PaginationMetadata
|
||||
{
|
||||
public int PageNumber { get; set; } = 1;
|
||||
public int PageSize { get; set; } = 50;
|
||||
public long TotalRecords { get; set; }
|
||||
public long TotalPages => (TotalRecords / PageSize) + (TotalRecords % PageSize > 0 ? 1 : 0);
|
||||
public object PreviousPageLinkInfo => PreviousPageExists ? new
|
||||
{
|
||||
PageNumber = PageNumber - 1,
|
||||
PageSize = PageSize
|
||||
} : null;
|
||||
|
||||
public object NextPageLinkInfo => NextPageExists ? new
|
||||
{
|
||||
PageNumber = PageNumber + 1,
|
||||
PageSize = PageSize
|
||||
} : null;
|
||||
|
||||
public bool PreviousPageExists => PageNumber > 1;
|
||||
public bool NextPageExists => PageNumber < TotalPages;
|
||||
}
|
||||
}
|
||||
|
||||
namespace System.Linq
|
||||
{
|
||||
public static class PaginationExtensions
|
||||
{
|
||||
public static IQueryable<T> WithPagination<T>(this IQueryable<T> source, PaginationMetadata pagination)
|
||||
{
|
||||
pagination.TotalRecords = source.LongCount();
|
||||
return source.Skip((pagination.PageNumber - 1) * pagination.PageSize).Take(pagination.PageSize);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using System.Buffers;
|
||||
using System.Text;
|
||||
using Sia.Shared.Data;
|
||||
|
||||
namespace Sia.Shared.Protocol
|
||||
{
|
||||
public class PartialSerializedJsonOutputFormatter : JsonOutputFormatter
|
||||
{
|
||||
public PartialSerializedJsonOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool<char> charPool)
|
||||
: base(serializerSettings, charPool)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||
{
|
||||
var dataStream = (IEnumerable<IJsonDataObject>)context.Object;
|
||||
|
||||
foreach (var objectToWrite in dataStream)
|
||||
{
|
||||
var jsonData = objectToWrite.Data;
|
||||
if (jsonData is string) objectToWrite.Data = Deserialize((string)jsonData);
|
||||
}
|
||||
|
||||
return base.WriteResponseBodyAsync(context, selectedEncoding);
|
||||
}
|
||||
|
||||
private const int NumberOfCharactersInGenericTypeNotUsedByGetInterfaceMethod = 3;
|
||||
|
||||
protected override bool CanWriteType(Type type)
|
||||
{
|
||||
if (type is null
|
||||
|| !type.IsGenericType
|
||||
|| type.GetGenericArguments().Count() != 1) return false;
|
||||
|
||||
var enumIntName = typeof(IEnumerable<>).ToString();
|
||||
var enumerableInterface = type.GetInterface(enumIntName
|
||||
.Substring(0, enumIntName.Length - NumberOfCharactersInGenericTypeNotUsedByGetInterfaceMethod));
|
||||
if (enumerableInterface is null) return false;
|
||||
|
||||
return !(type.GetGenericArguments()[0].GetInterface(nameof(IJsonDataObject)) is null);
|
||||
}
|
||||
|
||||
private object Deserialize(string serializedData) => JsonConvert.DeserializeObject(serializedData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using Newtonsoft.Json;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Shared.Protocol
|
||||
{
|
||||
public class Response<T> : Response, IResponse<T>
|
||||
{
|
||||
public T Value { get; private set; }
|
||||
|
||||
public static new async Task<Response<T>> Create(HttpResponseMessage message)
|
||||
{
|
||||
var response = new Response<T>();
|
||||
response.IsSuccessStatusCode = message.IsSuccessStatusCode;
|
||||
response.StatusCode = message.StatusCode;
|
||||
var content = await message.Content.ReadAsStringAsync();
|
||||
if (message.IsSuccessStatusCode)
|
||||
{
|
||||
response.Value = JsonConvert.DeserializeObject<T>(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.Content = content;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
public class Response : IResponse
|
||||
{
|
||||
public HttpStatusCode StatusCode { get; protected set; }
|
||||
public bool IsSuccessStatusCode { get; protected set; }
|
||||
public string Content { get; protected set; }
|
||||
public static async Task<Response> Create(HttpResponseMessage message)
|
||||
=> new Response()
|
||||
{
|
||||
IsSuccessStatusCode = message.IsSuccessStatusCode,
|
||||
StatusCode = message.StatusCode,
|
||||
Content = await message.Content.ReadAsStringAsync()
|
||||
};
|
||||
}
|
||||
}
|
|
@ -15,6 +15,9 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="6.1.1" />
|
||||
<PackageReference Include="AutoMapper.Collection" Version="3.1.2" />
|
||||
<PackageReference Include="MediatR" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Azure.KeyVault" Version="2.3.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" />
|
||||
|
@ -22,4 +25,10 @@
|
|||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Mvc.Formatters.Json">
|
||||
<HintPath>..\..\..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.mvc.formatters.json\2.0.0\lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Formatters.Json.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,31 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Shared.Transactions
|
||||
{
|
||||
public class Response<T> : IResponse<T>
|
||||
{
|
||||
public HttpStatusCode StatusCode { get; private set; }
|
||||
public bool IsSuccessStatusCode { get; private set; }
|
||||
public string Content { get; private set; }
|
||||
public T Value { get; private set; }
|
||||
public static async Task<Response<T>> Create(HttpResponseMessage message)
|
||||
{
|
||||
var response = new Response<T>();
|
||||
response.IsSuccessStatusCode = message.IsSuccessStatusCode;
|
||||
response.StatusCode = message.StatusCode;
|
||||
var content = await message.Content.ReadAsStringAsync();
|
||||
if (message.IsSuccessStatusCode)
|
||||
{
|
||||
response.Value = JsonConvert.DeserializeObject<T>(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.Content = content;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sia.Shared.Validation.Filters
|
||||
{
|
||||
public class Return400BadRequestWhenModelStateInvalid : Attribute, IAsyncActionFilter
|
||||
{
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
if (!context.ModelState.IsValid)
|
||||
{
|
||||
context.Result = new ValidationFailedResult(context.ModelState);
|
||||
}
|
||||
else
|
||||
{
|
||||
await next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using System;
|
||||
|
||||
namespace Sia.Shared.Validation
|
||||
{
|
||||
public class ValidationFailedResult : ObjectResult
|
||||
{
|
||||
const int BadRequestCode = 400;
|
||||
public ValidationFailedResult(ModelStateDictionary modelState)
|
||||
: base(new SerializableError(modelState))
|
||||
{
|
||||
if (modelState == null) throw new ArgumentNullException(nameof(modelState));
|
||||
StatusCode = BadRequestCode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ Issues labeled beginner are good candidates to pick up if you are in the code fo
|
|||
# Installing Prerequisites
|
||||
## You'll need…
|
||||
### For Development
|
||||
* [NodeJs](https://nodejs.org/en/download/)
|
||||
* [Node.js](https://nodejs.org/en/download/)
|
||||
* [Visual Studio 2017 or VSCode](https://www.visualstudio.com/downloads/)
|
||||
* [.Net Core SDK](https://www.microsoft.com/net/download/core)
|
||||
* [.Net Core Runtime](https://www.microsoft.com/net/download/core#/runtime) (if version of the runtime shipped with the SDK is not working)
|
||||
|
@ -23,8 +23,8 @@ Issues labeled beginner are good candidates to pick up if you are in the code fo
|
|||
## Configurations
|
||||
### For Development
|
||||
* Follow instructions in the README.md files in each of the child repositories.
|
||||
* [Sia-Gateway](https://github.com/Azure/Sia-Gateway/blob/master/src/Sia.Gateway/README.md)
|
||||
* [Sia-EventUi](https://github.com/Azure/Sia-EventUI/blob/master/README.md)
|
||||
* [Sia-Gateway](https://github.com/Azure/Sia-Gateway/blob/master/src/Sia.Gateway/README.md) (located in <code>Sia-Gateway/src/Sia.Gateway/README.md</code>)
|
||||
* [Sia-EventUi](https://github.com/Azure/Sia-EventUI/blob/master/README.md) (located in <code>Sia-EventUI/README.md</code>)
|
||||
|
||||
### For A Functional, Authenticated Environment
|
||||
//Todo: Instructions for configuring Azure resources
|
||||
|
|
11
README.md
11
README.md
|
@ -1,3 +1,4 @@
|
|||
|
||||
# SIA - SRE Incident Assistant
|
||||
|
||||
SIA is a new incident management tool that reads from event sources and recommends courses of action that help mitigate incidents quickly. SIA can read from nearly any event stream or ticketing system, and works with many live site response models.
|
||||
|
@ -7,7 +8,6 @@ Software systems are only as effective as they are reliable. As online services
|
|||
* [Grey failures](https://www.microsoft.com/en-us/research/wp-content/uploads/2017/06/paper-1.pdf), capacity tipping points, and other cases where multiple systems interact in unanticipated ways to produce problems without a known path to mitigation, especially when changes to code or configuration may result in more impact to users than the problem itself.
|
||||
* Situations where multiple teams are simultaneously investigating outages, some (but not all) of which share a root cause.
|
||||
* Long-running issues that require coordination between multiple teams and handoff between shifts within each team over the course of several days or weeks.
|
||||
* Long-running issues that require coordination between multiple teams and handoff between shifts within each team over the course of several days or weeks.
|
||||
* Major security issues such as [Heartbleed](https://en.wikipedia.org/wiki/Heartbleed) and [WannaCry](https://en.wikipedia.org/wiki/WannaCry_ransomware_attack) that require immediate updates across significant portions of an organization's infrastructure.
|
||||
* Disasters that cause extended or permanent loss of significant physical capacity, including undersea fiber cuts and loss of data center buildings.
|
||||
|
||||
|
@ -16,12 +16,17 @@ The SRE Incident Assistant (SIA) is designed to facilitate coordination, communi
|
|||
|
||||
# Quick Start
|
||||
* [Install prerequisites](https://github.com/Azure/Sia-Root/blob/master/HOWTOCONTRIBUTE.md#installing-prerequisites)
|
||||
* For Windows Users, the PowerShell script (installEventUI.ps1) can help installing the prerequisites, as well as cloning the UI repos.
|
||||
* Clone the repos:
|
||||
* Gateway: git clone [https://github.com/Azure/Sia-Gateway](https://github.com/Azure/Sia-Gateway)
|
||||
* UI: git clone [https://github.com/Azure/Sia-EventUi](https://github.com/Azure/Sia-EventUi)
|
||||
* Init the submodule from the gateway root directory
|
||||
* git submodule init
|
||||
* git submodule update --remote
|
||||
* Update the [configurations](https://github.com/Azure/Sia-Root/blob/master/HOWTOCONTRIBUTE.md#development-workflow)
|
||||
* Launch Gateway form VS2017 or VS Code
|
||||
* Start UI with npm: npm start
|
||||
* Launch Gateway form Visual Studio 2017 or Visual Studio Code
|
||||
* Start UI with npm
|
||||
* npm start
|
||||
* Open [http://localhost:3000](http://localhost:3000) in your browser, and voilà
|
||||
|
||||
## Contributing
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
# installing prerequisites and cloning source codes for SIA-EventUI on Windows PC
|
||||
|
||||
# checking registry key to see if any prerequisites is already installed
|
||||
# all bits are x64 Windows PC versions unless specified
|
||||
$gitHash = @{
|
||||
name = "Git"
|
||||
url = "https://github.com/git-for-windows/git/releases/download/v2.14.2.windows.1/Git-2.14.2-64-bit.exe"
|
||||
regKeyCheck = "Get-ItemProperty 'HKLM:\SOFTWARE\GitForWindows' -ErrorAction SilentlyContinue"
|
||||
}
|
||||
$nodeHash = @{
|
||||
name = "Node.js"
|
||||
url = "https://nodejs.org/dist/v6.11.3/node-v6.11.3-x64.msi"
|
||||
regKeyCheck = "Get-ItemProperty 'HKLM:\SOFTWARE\Node.js' -ErrorAction SilentlyContinue"
|
||||
}
|
||||
$dotnetCoreSDKHash = @{
|
||||
name = "dotnetCoreSDK"
|
||||
url = "https://download.microsoft.com/download/0/F/D/0FD852A4-7EA1-4E2A-983A-0484AC19B92C/dotnet-sdk-2.0.0-win-x64.exe"
|
||||
regKeyCheck = "Get-ItemProperty 'HKLM:\SOFTWARE\dotnet\Setup\InstalledVersions\x64\sharedhost' -ErrorAction SilentlyContinue"
|
||||
}
|
||||
$dotnetCoreRuntimeHash = @{
|
||||
name = "dotnetCoreRuntime"
|
||||
url = "https://download.microsoft.com/download/5/6/B/56BFEF92-9045-4414-970C-AB31E0FC07EC/dotnet-runtime-2.0.0-win-x64.exe"
|
||||
regKeyCheck = "Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\ASP.NET Core\Runtime Package Store\v2.0\RTM' -ErrorAction SilentlyContinue"
|
||||
}
|
||||
|
||||
# puttig all hash into a single array for looping actions later
|
||||
$prereqArr = $gitHash, $nodeHash, $dotnetCoreSDKHash, $dotnetCoreRuntimeHash
|
||||
|
||||
function CheckIfElevated()
|
||||
{
|
||||
Write-Host "Checking if PowerShell is running with elevated permissions..."
|
||||
$wid=[System.Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$prp=new-object System.Security.Principal.WindowsPrincipal($wid)
|
||||
$adm=[System.Security.Principal.WindowsBuiltInRole]::Administrator
|
||||
$IsAdmin=$prp.IsInRole($adm)
|
||||
if ($IsAdmin)
|
||||
{
|
||||
Write-Host "Verified - PowerShell is running in Admin mode"
|
||||
return $true
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "You are not running PowerShell with elevated permissions. Please re-launch Powershell in Administrator mode and run the script again." -ForegroundColor Yellow
|
||||
Write-Host "Press any key to exit..."
|
||||
$In = Read-Host
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function CheckIfUnrestricted()
|
||||
{
|
||||
Write-Host "Checking if PowerShell is running with Unrestricted execution policy..."
|
||||
$executionPolicy = Get-ExecutionPolicy
|
||||
if($executionPolicy -eq "Unrestricted") {
|
||||
Write-Host "Verified - PowerShell Execution Policy is set to Unrestricted"
|
||||
return $true
|
||||
}
|
||||
else {
|
||||
Write-Host "You are not running PowerShell with elevated permissions. Please re-launch Powershell after executing the command: `nset-executionpolicy unrestricted " -ForegroundColor Yellow
|
||||
Write-Host "Press any key to exit..."
|
||||
$In = Read-Host
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function invoke-download(){
|
||||
param(
|
||||
[string]$url,
|
||||
[string]$serviceName
|
||||
)
|
||||
|
||||
$fileExtension = $url.Substring($url.Length-4)
|
||||
$currentDirectory = (Get-Location).Path
|
||||
$outputFileName = "$currentDirectory\$serviceName" + $fileExtension
|
||||
$webClient = New-Object System.Net.WebClient
|
||||
$webClient.DownloadFile($url,$outputFileName)
|
||||
|
||||
if($fileExtension -eq ".exe")
|
||||
{
|
||||
$install = (Start-Process -FilePath $outputFileName -ArgumentList /passive -PassThru -Wait)
|
||||
}
|
||||
else
|
||||
{
|
||||
$argumentlist = "/i [application] /qb"
|
||||
$argumentlist = $argumentlist.Replace("[application]",$outputFileName)
|
||||
$install = (Start-Process -FilePath "C:\Windows\System32\msiexec.exe" -ArgumentList $argumentlist -PassThru -Wait)
|
||||
}
|
||||
|
||||
if($install.ExitCode -eq 0)
|
||||
{
|
||||
Write-Output "$serviceName successfully installed"
|
||||
Remove-Item $outputFileName
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Output "$serviceName installation failed with Exit Code: $install.ExitCode"
|
||||
}
|
||||
}
|
||||
|
||||
if(!(CheckIfElevated) -or !(CheckIfUnrestricted))
|
||||
{
|
||||
exit
|
||||
}
|
||||
|
||||
foreach ($prereq in $prereqArr) {
|
||||
Write-Host "Checking if $($prereq.name) is installed..."
|
||||
if(Invoke-Expression $prereq.regKeyCheck) {
|
||||
Write-Output "$($prereq.name) is already installed. Skipping installation..."
|
||||
|
||||
}
|
||||
else {
|
||||
Write-Output "Downloading and installing $($prereq.name)"
|
||||
invoke-download($prereq.url) ($prereq.name)
|
||||
}
|
||||
}
|
||||
|
||||
# refreshing the PowerShell prompt after Node.js installation to avoid relaunching the prompt
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
||||
|
||||
# checking if current working directory is under Sia-Root or Sia-EventUI
|
||||
# moving up one level above to go back to repos directory if needed
|
||||
if ((Get-Location).Path -match "Sia-Root" -or (Get-Location).Path -match "Sia-EventUI") {
|
||||
$reposDir = Split-Path (Get-Location).Path
|
||||
Write-Output "Moving up one directory level to $reposDir"
|
||||
Push-Location $reposDir
|
||||
}
|
||||
|
||||
if (Test-Path Sia-EventUI) {
|
||||
$removeMsg = "Sia-EventUI folder already existed. `nEnter [Y] Yes to remove and then re-clone the folder. Or enter any other key to skip this step."
|
||||
$removeResponse = Read-Host -Prompt $removeMsg
|
||||
if (($removeResponse -eq "y") -or ($removeResponse -eq "yes")) {
|
||||
Write-Output "Deleting existing Sia-EventUI folder..."
|
||||
Remove-Item Sia-EventUI -Recurse -Force -ErrorAction Ignore
|
||||
Write-Output "Cloning Sia-EventUI source code from GitHub again..."
|
||||
git clone https://github.com/Azure/Sia-EventUI.git
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Output "Sia-EventUI is not there. Cloning the source code from GitHub..."
|
||||
git clone https://github.com/Azure/Sia-EventUI.git
|
||||
}
|
||||
|
||||
Push-Location Sia-EventUI
|
||||
|
||||
#npm install @aspnet/signalr-client
|
||||
Write-Output "Executing npm install for dependencies..."
|
||||
npm install
|
||||
|
||||
# creating localhost.const from constExample.js as part of the requirements
|
||||
Write-Output "Copying the required config file: localhost.const.js..."
|
||||
Copy-Item cfg\exampleConstants.js cfg\localhost.const.js -Recurse -Force
|
||||
|
||||
Write-Output "`nSIA-EventUI is now installed successfully with the prerequisites and source files."
|
||||
Write-Output "You may now start the UI with 'npm start', and then open http://localhost:3000 in your browser.`n"
|
Загрузка…
Ссылка в новой задаче