Родитель
6dd12ffa9b
Коммит
9cef50573a
34
LICENSE
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;
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче