Map resource Id from input data #6 #7 (#8)

This commit is contained in:
Bernie White 2019-12-14 00:19:34 +10:00 коммит произвёл GitHub
Родитель 6dd12ffa9b
Коммит 9cef50573a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 134 добавлений и 48 удалений

34
LICENSE
Просмотреть файл

@ -1,21 +1,21 @@
MIT License
MIT License
Copyright (c) Microsoft Corporation.
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Просмотреть файл

@ -13,6 +13,8 @@ This project is to be considered a **proof-of-concept** and **not a supported pr
If you have any problems please check our GitHub [issues](https://github.com/Microsoft/PSRule.Monitor/issues) page.
If you do not see your problem captured, please file a new issue and follow the provided template.
If you have any problems with the [PSRule][engine] engine, please check the project GitHub [issues](https://github.com/Microsoft/PSRule/issues) page instead.
## Getting started
### Upload results

Просмотреть файл

@ -15,7 +15,7 @@ Send analysis results from PSRule to Azure Monitor.
```text
Send-PSRuleMonitorRecord [-WorkspaceId] <String> [-SharedKey] <SecureString> [[-InputObject] <PSObject>]
[<CommonParameters>]
[-LogName <String>] [<CommonParameters>]
```
## DESCRIPTION
@ -86,6 +86,29 @@ Accept pipeline input: False
Accept wildcard characters: False
```
### -LogName
Optionally specifies an alternative data source to store log data into.
By default PSRule is used.
If the log does not already exist, it is created.
It can create up to 15 minutes to create the log initially.
The specified log name will have the _CL suffix appended to it.
For example, when the log name is PSRule the use PSRule_CL in log queries.
```yaml
Type: String
Parameter Sets: (All)
Aliases:
Required: False
Position: Named
Default value: PSRule
Accept pipeline input: False
Accept wildcard characters: False
```
### -InputObject
The analysis record to send to Azure Monitor.

Просмотреть файл

@ -241,9 +241,18 @@ task BuildHelp BuildModule, PlatyPS, {
$Null = New-ExternalHelp -OutputPath out/docs/PSRule.Monitor -Path '.\docs\commands\PSRule.Monitor\en-US' -Force;
# Copy generated help into module out path
$Null = Copy-Item -Path out/docs/PSRule.Monitor/ -Destination out/modules/PSRule.Monitor/en-US/ -Recurse;
$Null = Copy-Item -Path out/docs/PSRule.Monitor/ -Destination out/modules/PSRule.Monitor/en-AU/ -Recurse;
$Null = Copy-Item -Path out/docs/PSRule.Monitor/ -Destination out/modules/PSRule.Monitor/en-GB/ -Recurse;
if (!(Test-Path -Path out/modules/PSRule.Monitor/en-US/)) {
$Null = New-Item -Path out/modules/PSRule.Monitor/en-US -Force -ItemType Directory;
}
if (!(Test-Path -Path out/modules/PSRule.Monitor/en-AU/)) {
$Null = New-Item -Path out/modules/PSRule.Monitor/en-AU -Force -ItemType Directory;
}
if (!(Test-Path -Path out/modules/PSRule.Monitor/en-GB/)) {
$Null = New-Item -Path out/modules/PSRule.Monitor/en-GB -Force -ItemType Directory;
}
$Null = Copy-Item -Path out/docs/PSRule.Monitor/* -Destination out/modules/PSRule.Monitor/en-US/ -Recurse;
$Null = Copy-Item -Path out/docs/PSRule.Monitor/* -Destination out/modules/PSRule.Monitor/en-AU/ -Recurse;
$Null = Copy-Item -Path out/docs/PSRule.Monitor/* -Destination out/modules/PSRule.Monitor/en-GB/ -Recurse;
}
task ScaffoldHelp Build, {

Просмотреть файл

@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Newtonsoft.Json;
namespace PSRule.Monitor.Data
{
/// <summary>
@ -16,6 +18,7 @@ namespace PSRule.Monitor.Data
public string Outcome { get; set; }
[JsonIgnore]
public string ResourceId { get; set; }
}
}

Просмотреть файл

@ -31,7 +31,11 @@ function Send-PSRuleMonitorRecord {
[SecureString]$SharedKey,
[Parameter(Mandatory = $False, ValueFromPipeline = $True)]
[PSObject]$InputObject
[PSObject]$InputObject,
[Parameter(Mandatory = $False)]
[PSDefaultValue(Value = 'PSRule')]
[String]$LogName
)
begin {
Write-Verbose -Message '[Send-PSRuleMonitorRecord] BEGIN::';
@ -41,6 +45,10 @@ function Send-PSRuleMonitorRecord {
$builder.WorkspaceId($WorkspaceId);
$builder.SharedKey($SharedKey);
if ($PSBoundParameters.ContainsKey('LogName')) {
$builder.LogName($LogName);
}
$builder.UseCommandRuntime($PSCmdlet.CommandRuntime);
$builder.UseExecutionContext($ExecutionContext);
try {

Просмотреть файл

@ -40,13 +40,13 @@ namespace PSRule.Monitor.Pipeline
if (_Queue.Count < minSize || _Queue.Count == 0)
return false;
string resourceId = _Queue.TryPeek(out LogRecord record) ? record.ResourceId : null;
var batchSize = _Queue.Count > maxSize ? maxSize : _Queue.Count;
var batch = new List<LogRecord>(batchSize);
for (var i = 0; i < maxSize && _Queue.Count > 0; i++)
{
if (_Queue.TryDequeue(out LogRecord record))
for (var i = 0; i < maxSize && _Queue.Count > 0 && _Queue.TryPeek(out record) && record.ResourceId == resourceId; i++)
if (_Queue.TryDequeue(out record))
batch.Add(record);
}
records = batch.ToArray();
return true;
}

Просмотреть файл

@ -18,13 +18,17 @@ namespace PSRule.Monitor.Pipeline
void WorkspaceId(string workspaceId);
void SharedKey(SecureString sharedKey);
void LogName(string logName);
}
internal sealed class InjestPipelineBuilder : PipelineBuilderBase, IInjestPipelineBuilder
{
private const string DEFAULT_LOGNAME = "PSRule";
private string _WorkspaceId;
private SecureString _SharedKey;
private string _LogName = "PSRule";
private string _LogName = DEFAULT_LOGNAME;
public void WorkspaceId(string workspaceId)
{
@ -36,6 +40,11 @@ namespace PSRule.Monitor.Pipeline
_SharedKey = sharedKey;
}
public void LogName(string logName)
{
_LogName = logName;
}
public override IPipeline Build()
{
return new InjestPipeline(PrepareContext(), PrepareReader(), _WorkspaceId, _SharedKey, _LogName);
@ -44,9 +53,9 @@ namespace PSRule.Monitor.Pipeline
internal sealed class InjestPipeline : PipelineBase
{
private const string ContentType = "application/json";
private const string TimeStampField = "";
private const string ResourceIdField = "resourceId";
private const string CONTENTTYPE = "application/json";
private const string TIMESTAMPFIELD = "";
private const string APIVERSION = "2016-04-01";
private const string HEADER_ACCEPT = "Accept";
private const string HEADER_AUTHORIZATION = "Authorization";
@ -67,7 +76,7 @@ namespace PSRule.Monitor.Pipeline
: base(context, reader)
{
_LogName = logName;
_EndpointUri = new Uri(string.Concat("https://", workspaceId, ".ods.opinsights.azure.com/api/logs?api-version=2016-04-01"));
_EndpointUri = new Uri(string.Concat("https://", workspaceId, ".ods.opinsights.azure.com/api/logs?api-version=", APIVERSION));
_Hash = new CollectionHash(workspaceId, sharedKey);
_SubmissionQueue = new BatchQueue();
_HttpClient = GetClient();
@ -97,26 +106,57 @@ namespace PSRule.Monitor.Pipeline
if (sourceObject == null)
return null;
var ruleName = GetProperty<string>(sourceObject, "ruleName");
var targetName = GetProperty<string>(sourceObject, "targetName");
var targetType = GetProperty<string>(sourceObject, "targetType");
var outcome = GetProperty<string>(sourceObject, "outcome");
//var resourceId = GetProperty<Hashtable>(sourceObject, "properties")?.ContainsKey("resourceId") ?? string.Empty;
var ruleName = GetPropertyValue(sourceObject, "ruleName");
var targetName = GetPropertyValue(sourceObject, "targetName");
var targetType = GetPropertyValue(sourceObject, "targetType");
var outcome = GetPropertyValue(sourceObject, "outcome");
var field = GetProperty<object>(sourceObject, "field");
var resourceId = GetField(field, "resourceId");
var record = new LogRecord
{
RuleName = ruleName,
TargetName = targetName,
TargetType = targetType,
Outcome = outcome,
ResourceId = string.Empty
ResourceId = resourceId
};
return record;
}
private static string GetPropertyValue(PSObject obj, string propertyName)
{
return obj.Properties[propertyName] == null || obj.Properties[propertyName].Value == null ? null : obj.Properties[propertyName].Value.ToString();
}
private static T GetProperty<T>(PSObject obj, string propertyName)
{
return null == obj.Properties[propertyName] ? default(T) : (T)obj.Properties[propertyName].Value;
return obj.Properties[propertyName] == null ? default(T) : (T)obj.Properties[propertyName].Value;
}
private static string GetField(object obj, string propertyName)
{
if (obj is IDictionary dictionary && TryDictionary(dictionary, propertyName, out object value) && value != null)
return value.ToString();
if (obj is PSObject pso)
return GetPropertyValue(pso, propertyName);
return null;
}
private static bool TryDictionary(IDictionary dictionary, string key, out object value)
{
value = null;
var comparer = StringComparer.OrdinalIgnoreCase;
foreach (var k in dictionary.Keys)
{
if (comparer.Equals(key, k))
{
value = dictionary[k];
return true;
}
}
return false;
}
/// <summary>
@ -125,20 +165,21 @@ namespace PSRule.Monitor.Pipeline
private void SubmitBatch(LogRecord[] records)
{
var json = JsonConvert.SerializeObject(records);
var resourceId = records[0].ResourceId;
// Create a hash for the API signature
var date = DateTime.UtcNow;
var data = Encoding.UTF8.GetBytes(json);
var signature = _Hash.ComputeSignature(data.Length, date, ContentType);
PostData(signature, date, json);
var signature = _Hash.ComputeSignature(data.Length, date, CONTENTTYPE);
PostData(signature, date, resourceId, json);
}
/// <summary>
/// Post log data to Azure Monitor endpoint.
/// </summary>
private void PostData(string signature, DateTime date, string json)
private void PostData(string signature, DateTime date, string resourceId, string json)
{
using (var request = PrepareRequest(signature, date, json))
using (var request = PrepareRequest(signature, date, resourceId, json))
{
var response = _HttpClient.SendAsync(request);
response.Wait();
@ -149,19 +190,19 @@ namespace PSRule.Monitor.Pipeline
private HttpClient GetClient()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add(HEADER_ACCEPT, ContentType);
client.DefaultRequestHeaders.Add(HEADER_ACCEPT, CONTENTTYPE);
client.DefaultRequestHeaders.Add(HEADER_LOGTYPE, _LogName);
return client;
}
private HttpRequestMessage PrepareRequest(string signature, DateTime date, string json)
private HttpRequestMessage PrepareRequest(string signature, DateTime date, string resourceId, string json)
{
var request = new HttpRequestMessage(HttpMethod.Post, _EndpointUri);
request.Headers.Add(HEADER_AUTHORIZATION, signature);
request.Headers.Add(HEADER_DATE, date.ToString("r", FormatCulture));
request.Headers.Add(HEADER_TIMEGENERATED, TimeStampField);
request.Headers.Add(HEADER_RESOURCEID, ResourceIdField);
request.Content = new StringContent(json, Encoding.UTF8, ContentType);
request.Headers.Add(HEADER_TIMEGENERATED, TIMESTAMPFIELD);
request.Headers.Add(HEADER_RESOURCEID, resourceId);
request.Content = new StringContent(json, Encoding.UTF8, CONTENTTYPE);
return request;
}