Skip to content

Commit 337306d

Browse files
committed
add migrations folder
1 parent e5474e6 commit 337306d

21 files changed

+1528
-0
lines changed

src/Migrations/Migrations.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Migrations
2+
3+
This directory contains common [AutoREST.PowerShell](https://github.com/Azure/autorest.powershell) configurations for Migrations v1.0 and/or beta modules.
4+
5+
## AutoRest Configuration
6+
7+
> see <https://aka.ms/autorest>
8+
9+
``` yaml
10+
require:
11+
- $(this-folder)/../readme.graph.md
12+
```
13+
14+
### Directives
15+
16+
> see https://github.com/Azure/autorest/blob/master/docs/powershell/directives.md
17+
18+
``` yaml
19+
# Directives go here!
20+
```

src/Migrations/beta/.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto

src/Migrations/beta/.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
bin
2+
obj
3+
.vs
4+
generated
5+
internal
6+
exports
7+
tools
8+
custom/*.psm1
9+
custom/autogen-model-cmdlets
10+
test/*-TestResults.xml
11+
/*.ps1
12+
/*.ps1xml
13+
/*.psm1
14+
/*.snk
15+
/*.csproj
16+
/*.nuspec
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// ------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
// ------------------------------------------------------------------------------
4+
5+
namespace Microsoft.Graph.Beta.PowerShell
6+
{
7+
using System;
8+
using System.Text.RegularExpressions;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Microsoft.Graph.Beta.PowerShell.Runtime;
12+
13+
public static class EventExtensions
14+
{
15+
/// <summary>
16+
/// Print event details to the provided stream
17+
/// </summary>
18+
/// <param name="getEventData">The event data to print</param>
19+
/// <param name="signal">The delegate for signaling events to the runtime</param>
20+
/// <param name="token">The cancellation token for the request</param>
21+
/// <param name="streamName">The name of the stream to print data to</param>
22+
/// <param name="eventName">The name of the event to be printed</param>
23+
public static async void Print(this Func<EventArgs> getEventData, Func<string, CancellationToken, Func<EventArgs>, Task> signal, CancellationToken token, string streamName, string eventName)
24+
{
25+
var eventDisplayName = EventFactory.SplitPascalCase(eventName).ToUpperInvariant();
26+
var data = EventDataConverter.ConvertFrom(getEventData()); // also, we manually use our TypeConverter to return an appropriate type
27+
if (data.Id != Events.Verbose && data.Id != Events.Warning && data.Id != Events.Debug && data.Id != Events.Information && data.Id != Events.Error)
28+
{
29+
await signal(streamName, token, () => EventFactory.CreateLogEvent($"{eventDisplayName} The contents are '{data?.Id}' and '{data?.Message}'"));
30+
if (data != null)
31+
{
32+
await signal(streamName, token, () => EventFactory.CreateLogEvent($"{eventDisplayName} Parameter: '{data.Parameter}'\n{eventDisplayName} RequestMessage '{data.RequestMessage}'\n{eventDisplayName} Response: '{data.ResponseMessage}'\n{eventDisplayName} Value: '{data.Value}'"));
33+
await signal(streamName, token, () => EventFactory.CreateLogEvent($"{eventDisplayName} ExtendedData Type: '{data.ExtendedData?.GetType()}'\n{eventDisplayName} ExtendedData '{data.ExtendedData}'"));
34+
}
35+
}
36+
}
37+
}
38+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// ------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
// ------------------------------------------------------------------------------
4+
5+
using System;
6+
using System.Text.RegularExpressions;
7+
using System.Threading.Tasks;
8+
using Microsoft.Graph.Beta.PowerShell.Runtime;
9+
10+
namespace Microsoft.Graph.Beta.PowerShell
11+
{
12+
public static class EventFactory
13+
{
14+
/// <summary>
15+
/// Create a tracing event containing a string message
16+
/// </summary>
17+
/// <param name="message">The string message to include in event data</param>
18+
/// <returns>Valid EventData containing the message</returns>
19+
public static EventData CreateLogEvent(Task<string> message)
20+
{
21+
return new EventData
22+
{
23+
Id = Guid.NewGuid().ToString(),
24+
Message = message.Result
25+
};
26+
}
27+
28+
/// <summary>
29+
/// Create a new debug message event
30+
/// </summary>
31+
/// <param name="message">The message</param>
32+
/// <returns>An event containing the debug message</returns>
33+
public static EventData CreateDebugEvent(string message)
34+
{
35+
return new EventData
36+
{
37+
Id = Events.Debug,
38+
Message = message
39+
};
40+
}
41+
42+
/// <summary>
43+
/// Create a new debug message event
44+
/// </summary>
45+
/// <param name="message">The message</param>
46+
/// <returns>An event containing the debug message</returns>
47+
public static EventData CreateWarningEvent(string message)
48+
{
49+
return new EventData
50+
{
51+
Id = Events.Warning,
52+
Message = message
53+
};
54+
}
55+
public static string SplitPascalCase(string word)
56+
{
57+
var regex = new Regex("([a-z]+)([A-Z])");
58+
var output = regex.Replace(word, "$1 $2");
59+
regex = new Regex("([A-Z])([A-Z][a-z])");
60+
return regex.Replace(output, "$1 $2");
61+
}
62+
63+
public static EventArgs CreateLogEvent(string message)
64+
{
65+
return new EventData
66+
{
67+
Id = Guid.NewGuid().ToString(),
68+
Message = message
69+
};
70+
}
71+
}
72+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// ------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
// ------------------------------------------------------------------------------
4+
namespace Microsoft.Graph.Beta.PowerShell.Cmdlets.Custom
5+
{
6+
using Microsoft.Graph.PowerShell.Authentication.Common;
7+
using System.IO;
8+
using System.Management.Automation;
9+
10+
public partial class FileUploadCmdlet : PSCmdlet
11+
{
12+
/// <summary>Backing field for <see cref="InFile" /> property.</summary>
13+
private string _inFile;
14+
15+
/// <summary>The path to the file to upload. This SHOULD include the file name and extension.</summary>
16+
[Parameter(Mandatory = true, HelpMessage = "The path to the file to upload. This should include a path and file name. If you omit the path, the current location will be used.")]
17+
[Runtime.Info(
18+
Required = true,
19+
ReadOnly = false,
20+
Description = @"The path to the file to upload. This should include a path and file name. If you omit the path, the current location will be used.",
21+
PossibleTypes = new[] { typeof(string) })]
22+
[ValidateNotNullOrEmpty()]
23+
[Category(ParameterCategory.Runtime)]
24+
public string InFile { get => this._inFile; set => this._inFile = value; }
25+
26+
/// <summary>
27+
/// Creates a file stream from the provided input file.
28+
/// </summary>
29+
/// <returns>A file stream.</returns>
30+
internal Stream GetFileAsStream()
31+
{
32+
if (MyInvocation.BoundParameters.ContainsKey(nameof(InFile)))
33+
{
34+
string resolvedFilePath = this.GetProviderPath(InFile, true);
35+
var fileProvider = ProtectedFileProvider.CreateFileProvider(resolvedFilePath, FileProtection.SharedRead, new DiskDataStore());
36+
return fileProvider.Stream;
37+
}
38+
else
39+
{
40+
return null;
41+
}
42+
}
43+
}
44+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// ------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
// ------------------------------------------------------------------------------
4+
5+
namespace Microsoft.Graph.Beta.PowerShell
6+
{
7+
using Microsoft.Graph.Beta.PowerShell.Models;
8+
using Newtonsoft.Json;
9+
using System;
10+
using System.Collections.Generic;
11+
using System.IO;
12+
using System.Linq;
13+
using System.Net.Http;
14+
using System.Net.Http.Headers;
15+
using System.Text;
16+
using System.Text.RegularExpressions;
17+
using System.Threading.Tasks;
18+
using System.Xml.Linq;
19+
20+
public static class HttpMessageLogFormatter
21+
{
22+
internal static async Task<HttpRequestMessage> CloneAsync(this HttpRequestMessage originalRequest)
23+
{
24+
var newRequest = new HttpRequestMessage(originalRequest.Method, originalRequest.RequestUri);
25+
26+
// Copy requestClone headers.
27+
foreach (var header in originalRequest.Headers)
28+
newRequest.Headers.TryAddWithoutValidation(header.Key, header.Value);
29+
30+
// Copy requestClone properties.
31+
foreach (var property in originalRequest.Properties)
32+
newRequest.Properties.Add(property);
33+
34+
// Set Content if previous requestClone had one.
35+
if (originalRequest.Content != null)
36+
{
37+
// Try cloning request content; otherwise, skip due to https://github.com/dotnet/corefx/pull/19082 in .NET 4.x.
38+
try
39+
{
40+
// HttpClient doesn't rewind streams and we have to explicitly do so.
41+
var ms = new MemoryStream();
42+
await originalRequest.Content.CopyToAsync(ms).ConfigureAwait(false);
43+
ms.Position = 0;
44+
newRequest.Content = new StreamContent(ms);
45+
// Attempt to copy request content headers with a single retry.
46+
// HttpHeaders dictionary is not thread-safe when targeting anything below .NET 7. For more information, see https://github.com/dotnet/runtime/issues/61798.
47+
int retryCount = 0;
48+
int maxRetryCount = 2;
49+
while (retryCount < maxRetryCount)
50+
{
51+
try
52+
{
53+
originalRequest.Content?.Headers?.ToList().ForEach(header => newRequest.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value));
54+
retryCount = maxRetryCount;
55+
}
56+
catch (InvalidOperationException)
57+
{
58+
retryCount++;
59+
}
60+
}
61+
}
62+
catch
63+
{
64+
}
65+
}
66+
return newRequest;
67+
}
68+
69+
public static async Task<string> GetHttpRequestLogAsync(HttpRequestMessage request)
70+
{
71+
if (request == null) return string.Empty;
72+
var requestClone = await request.CloneAsync().ConfigureAwait(false);
73+
string body = string.Empty;
74+
try
75+
{
76+
if (requestClone.Content != null)
77+
{
78+
body = FormatString(await requestClone.Content.ReadAsStringAsync());
79+
}
80+
else if (requestClone.Content == null && request.Content != null)
81+
{
82+
body = "Skipped: Content body was disposed before the logger could access it.";
83+
}
84+
}
85+
catch { }
86+
87+
StringBuilder stringBuilder = new StringBuilder();
88+
stringBuilder.AppendLine($"============================ HTTP REQUEST ============================{Environment.NewLine}");
89+
stringBuilder.AppendLine($"HTTP Method:{Environment.NewLine}{requestClone.Method}{Environment.NewLine}");
90+
stringBuilder.AppendLine($"Absolute Uri:{Environment.NewLine}{requestClone.RequestUri}{Environment.NewLine}");
91+
stringBuilder.AppendLine($"Headers:{Environment.NewLine}{HeadersToString(requestClone.Headers)}{Environment.NewLine}");
92+
stringBuilder.AppendLine($"Body:{Environment.NewLine}{SanitizeBody(body)}{Environment.NewLine}");
93+
return stringBuilder.ToString();
94+
}
95+
96+
public static async Task<string> GetHttpResponseLogAsync(HttpResponseMessage response)
97+
{
98+
if (response == null) return string.Empty;
99+
100+
string body = string.Empty;
101+
try
102+
{
103+
body = (response.Content == null) ? string.Empty : FormatString(await response.Content.ReadAsStringAsync());
104+
}
105+
catch { }
106+
107+
StringBuilder stringBuilder = new StringBuilder();
108+
stringBuilder.AppendLine($"============================ HTTP RESPONSE ============================{Environment.NewLine}");
109+
stringBuilder.AppendLine($"Status Code:{Environment.NewLine}{response.StatusCode}{Environment.NewLine}");
110+
stringBuilder.AppendLine($"Headers:{Environment.NewLine}{HeadersToString(response.Headers)}{Environment.NewLine}");
111+
stringBuilder.AppendLine($"Body:{Environment.NewLine}{SanitizeBody(body)}{Environment.NewLine}");
112+
return stringBuilder.ToString();
113+
}
114+
115+
public static async Task<string> GetErrorLogAsync(HttpResponseMessage response, IMicrosoftGraphODataErrorsMainError odataError)
116+
{
117+
if (response == null) return string.Empty;
118+
119+
StringBuilder stringBuilder = new StringBuilder();
120+
stringBuilder.AppendLine($"{odataError?.Message}{Environment.NewLine}");
121+
stringBuilder.AppendLine($"Status: {((int)response.StatusCode)} ({response.StatusCode})");
122+
stringBuilder.AppendLine($"ErrorCode: {odataError?.Code}");
123+
stringBuilder.AppendLine($"Date: {odataError?.InnerError?.Date}{Environment.NewLine}");
124+
stringBuilder.AppendLine($"Headers:{Environment.NewLine}{HeadersToString(response.Headers)}{Environment.NewLine}");
125+
return stringBuilder.ToString();
126+
}
127+
128+
internal static string HeadersToString(HttpHeaders headers)
129+
{
130+
return HeadersToString(ConvertHttpHeadersToCollection(headers));
131+
}
132+
133+
private static readonly Regex regexPattern = new Regex("(\\s*\"access_token\"\\s*:\\s*)\"[^\"]+\"", RegexOptions.Compiled);
134+
private static object SanitizeBody(string body)
135+
{
136+
IList<Regex> regexList = new List<Regex>();
137+
// Remove access_token:* instances in body.
138+
regexList.Add(regexPattern);
139+
140+
foreach (Regex matcher in regexList)
141+
{
142+
body = matcher.Replace(body, "$1\"<redacted>\"");
143+
}
144+
145+
return body;
146+
}
147+
148+
private static IDictionary<string, IEnumerable<string>> ConvertHttpHeadersToCollection(HttpHeaders headers)
149+
{
150+
headers.Remove("Authorization");
151+
return headers.ToDictionary(a => a.Key, a => a.Value);
152+
}
153+
154+
private static string HeadersToString(IDictionary<string, IEnumerable<string>> headers)
155+
{
156+
StringBuilder stringBuilder = headers.Aggregate(new StringBuilder(),
157+
(sb, kvp) => sb.AppendLine(string.Format("{0,-30}: {1}", kvp.Key, String.Join(",", kvp.Value.ToArray()))));
158+
159+
if (stringBuilder.Length > 0)
160+
stringBuilder.Remove(stringBuilder.Length - 2, 2);
161+
162+
return stringBuilder.ToString();
163+
}
164+
165+
private static string FormatString(string content)
166+
{
167+
try
168+
{
169+
content = content.Trim();
170+
if ((content.StartsWith("{") && content.EndsWith("}")) || // object
171+
(content.StartsWith("[") && content.EndsWith("]"))) // array
172+
{
173+
return JsonConvert.SerializeObject(JsonConvert.DeserializeObject(content), Formatting.Indented);
174+
}
175+
if (content.StartsWith("<"))
176+
{
177+
return XDocument.Parse(content).ToString();
178+
}
179+
}
180+
catch
181+
{
182+
return content;
183+
}
184+
185+
if (content.Length > Microsoft.Graph.PowerShell.Authentication.Constants.MaxContentLength)
186+
{
187+
return content.Substring(0, Microsoft.Graph.PowerShell.Authentication.Constants.MaxContentLength) + "\r\nDATA TRUNCATED DUE TO SIZE\r\n";
188+
}
189+
190+
return content;
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)