Skip to content

Commit 1a87746

Browse files
authored
Merge pull request #5972 from sergey-shandar/sergey-strategies-update2
Move generic code to strategies library.
2 parents 450e76e + b459f26 commit 1a87746

File tree

16 files changed

+518
-182
lines changed

16 files changed

+518
-182
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Microsoft.Rest;
5+
using Xunit;
6+
using Microsoft.WindowsAzure.Commands.ScenarioTest;
7+
using System.Threading;
8+
9+
namespace Microsoft.Azure.Commands.Common.Strategies.UnitTest
10+
{
11+
public class AsyncCmdletExtensionsTest
12+
{
13+
class Client : IClient
14+
{
15+
public T GetClient<T>() where T : ServiceClient<T>
16+
=> null;
17+
}
18+
19+
class RG { }
20+
21+
class Parameters : IParameters<RG>
22+
{
23+
public string DefaultLocation => "eastus";
24+
25+
public string Location
26+
{
27+
get
28+
{
29+
return "somelocation";
30+
}
31+
32+
set
33+
{
34+
}
35+
}
36+
37+
public async Task<ResourceConfig<RG>> CreateConfigAsync()
38+
=> new ResourceConfig<RG>(
39+
new ResourceStrategy<RG>(
40+
null,
41+
async (c, p) => new RG(),
42+
null,
43+
null,
44+
null,
45+
false),
46+
null,
47+
null,
48+
null,
49+
null);
50+
}
51+
52+
class AsyncCmdlet : IAsyncCmdlet
53+
{
54+
public CancellationToken CancellationToken { get; }
55+
= new CancellationToken();
56+
57+
public IEnumerable<KeyValuePair<string, object>> Parameters
58+
{
59+
get
60+
{
61+
yield return new KeyValuePair<string, object>("Str", "str");
62+
}
63+
}
64+
65+
public string VerbsNew
66+
{
67+
get
68+
{
69+
throw new NotImplementedException();
70+
}
71+
}
72+
73+
public void ReportTaskProgress(ITaskProgress taskProgress)
74+
{
75+
throw new NotImplementedException();
76+
}
77+
78+
public Task<bool> ShouldProcessAsync(string target, string action)
79+
{
80+
throw new NotImplementedException();
81+
}
82+
83+
public void WriteObject(object value)
84+
{
85+
throw new NotImplementedException();
86+
}
87+
88+
public void WriteVerbose(string message)
89+
{
90+
Assert.Equal("Str = \"str\"", message);
91+
}
92+
}
93+
94+
[Fact]
95+
[Trait(Category.AcceptanceType, Category.CheckIn)]
96+
public async Task TestVerboseStream()
97+
{
98+
var client = new Client();
99+
var parameters = new Parameters();
100+
var asyncCmdlet = new AsyncCmdlet();
101+
102+
await client.RunAsync("subscriptionId", parameters, asyncCmdlet);
103+
}
104+
}
105+
}

src/ResourceManager/Common/Commands.Common.Strategies.UnitTest/Commands.Common.Strategies.UnitTest.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
</Reference>
6565
</ItemGroup>
6666
<ItemGroup>
67+
<Compile Include="AsyncCmdletExtensionsTest.cs" />
6768
<Compile Include="Compute\ImageVersionTest.cs" />
6869
<Compile Include="Properties\AssemblyInfo.cs" />
6970
<Compile Include="TargetStateTest.cs" />
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using System;
16+
using System.Collections;
17+
using System.Collections.Generic;
18+
using System.Linq;
19+
using System.Threading;
20+
using System.Threading.Tasks;
21+
22+
namespace Microsoft.Azure.Commands.Common.Strategies
23+
{
24+
public static class AsyncCmdletExtensions
25+
{
26+
/// <summary>
27+
/// WriteVerbose function with formatting.
28+
/// </summary>
29+
/// <param name="cmdlet">Cmdlet</param>
30+
/// <param name="message">message with formatting</param>
31+
/// <param name="p">message parameters</param>
32+
public static void WriteVerbose(this IAsyncCmdlet cmdlet, string message, params object[] p)
33+
=> cmdlet.WriteVerbose(string.Format(message, p));
34+
35+
/// <summary>
36+
/// The function read current Azure state and update it according to the `parameters`.
37+
/// </summary>
38+
/// <typeparam name="TModel">A resource model type.</typeparam>
39+
/// <param name="client">Azure SDK client.</param>
40+
/// <param name="subscriptionId">Subbscription Id.</param>
41+
/// <param name="parameters">Cmdlet parameters.</param>
42+
/// <param name="asyncCmdlet">Asynchronous cmdlet interface.</param>
43+
/// <param name="cancellationToken">Cancellation token.</param>
44+
/// <returns></returns>
45+
public static async Task<TModel> RunAsync<TModel>(
46+
this IClient client,
47+
string subscriptionId,
48+
IParameters<TModel> parameters,
49+
IAsyncCmdlet asyncCmdlet)
50+
where TModel : class
51+
{
52+
// create a DAG of configs.
53+
var config = await parameters.CreateConfigAsync();
54+
// read current Azure state.
55+
var current = await config.GetStateAsync(client, asyncCmdlet.CancellationToken);
56+
// update location.
57+
parameters.Location =
58+
parameters.Location ?? current.GetLocation(config) ?? parameters.DefaultLocation;
59+
// update a DAG of configs.
60+
config = await parameters.CreateConfigAsync();
61+
// create a target.
62+
var target = config.GetTargetState(
63+
current, new SdkEngine(subscriptionId), parameters.Location);
64+
// print paramaters to a verbose stream.
65+
foreach (var p in asyncCmdlet.Parameters)
66+
{
67+
asyncCmdlet.WriteVerbose(p.Key + " = " + ToPowerShellString(p.Value));
68+
}
69+
70+
// apply the target state
71+
var newState = await config.UpdateStateAsync(
72+
client,
73+
target,
74+
asyncCmdlet.CancellationToken,
75+
new ShouldProcess(asyncCmdlet),
76+
asyncCmdlet.ReportTaskProgress);
77+
// return a resource model
78+
return newState.Get(config) ?? current.Get(config);
79+
}
80+
81+
static string ToPowerShellString(object value)
82+
{
83+
if (value == null)
84+
{
85+
return "$null";
86+
}
87+
88+
var s = value as string;
89+
if (s != null)
90+
{
91+
return "\"" + s + "\"";
92+
}
93+
94+
var e = value as IEnumerable;
95+
if (e != null)
96+
{
97+
return string.Join(",", e.Cast<object>().Select(ToPowerShellString));
98+
}
99+
100+
return value.ToString();
101+
}
102+
103+
sealed class ShouldProcess : IShouldProcess
104+
{
105+
readonly IAsyncCmdlet _Cmdlet;
106+
107+
public ShouldProcess(IAsyncCmdlet cmdlet)
108+
{
109+
_Cmdlet = cmdlet;
110+
}
111+
112+
public Task<bool> ShouldCreate<TModel>(ResourceConfig<TModel> config, TModel model)
113+
where TModel : class
114+
=> _Cmdlet.ShouldProcessAsync(config.GetFullName(), _Cmdlet.VerbsNew);
115+
}
116+
117+
/// <summary>
118+
/// Note: the function must be called in the main PowerShell thread.
119+
/// </summary>
120+
/// <param name="cmdlet"></param>
121+
/// <param name="createAndStartTask"></param>
122+
public static void CmdletStartAndWait(
123+
this ICmdlet cmdlet, Func<IAsyncCmdlet, Task> createAndStartTask)
124+
{
125+
var asyncCmdlet = new AsyncCmdlet(cmdlet);
126+
string previousX = null;
127+
string previousOperation = null;
128+
asyncCmdlet.Scheduler.Wait(
129+
createAndStartTask(asyncCmdlet),
130+
() =>
131+
{
132+
if (asyncCmdlet.TaskProgressList.Any())
133+
{
134+
var progress = 0.0;
135+
var activeTasks = new List<string>();
136+
foreach (var taskProgress in asyncCmdlet.TaskProgressList)
137+
{
138+
if (!taskProgress.IsDone)
139+
{
140+
var config = taskProgress.Config;
141+
activeTasks.Add(config.GetFullName());
142+
}
143+
144+
progress += taskProgress.GetProgress();
145+
}
146+
147+
var percent = (int)(progress * 100.0);
148+
var r = new[] { "|", "/", "-", "\\" };
149+
var x = r[DateTime.Now.Second % 4];
150+
var operation = activeTasks.Count > 0
151+
? "Creating " + string.Join(", ", activeTasks) + "."
152+
: null;
153+
154+
// write progress only if it's changed.
155+
if (x != previousX || operation != previousOperation)
156+
{
157+
asyncCmdlet.Cmdlet.WriteProgress(
158+
activity: "Creating Azure resources",
159+
statusDescription: percent + "% " + x,
160+
currentOperation: operation,
161+
percentComplete: percent);
162+
previousX = x;
163+
previousOperation = operation;
164+
}
165+
}
166+
});
167+
}
168+
169+
sealed class AsyncCmdlet : IAsyncCmdlet
170+
{
171+
public SyncTaskScheduler Scheduler { get; } = new SyncTaskScheduler();
172+
173+
public ICmdlet Cmdlet { get; }
174+
175+
public List<ITaskProgress> TaskProgressList { get; }
176+
= new List<ITaskProgress>();
177+
178+
public string VerbsNew => Cmdlet.VerbsNew;
179+
180+
public IEnumerable<KeyValuePair<string, object>> Parameters
181+
=> Cmdlet.Parameters;
182+
183+
public CancellationToken CancellationToken { get; }
184+
= new CancellationToken();
185+
186+
public AsyncCmdlet(ICmdlet cmdlet)
187+
{
188+
Cmdlet = cmdlet;
189+
}
190+
191+
public void WriteVerbose(string message)
192+
=> Scheduler.BeginInvoke(() => Cmdlet.WriteVerbose(message));
193+
194+
public Task<bool> ShouldProcessAsync(string target, string action)
195+
=> Scheduler.Invoke(() => Cmdlet.ShouldProcess(target, action));
196+
197+
public void WriteObject(object value)
198+
=> Scheduler.BeginInvoke(() => Cmdlet.WriteObject(value));
199+
200+
public void ReportTaskProgress(ITaskProgress taskProgress)
201+
=> Scheduler.BeginInvoke(() => TaskProgressList.Add(taskProgress));
202+
203+
204+
}
205+
}
206+
}

src/ResourceManager/Common/Commands.Common.Strategies/Commands.Common.Strategies.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,15 @@
5555
<Reference Include="System.Xml" />
5656
</ItemGroup>
5757
<ItemGroup>
58+
<Compile Include="AsyncCmdletExtensions.cs" />
5859
<Compile Include="Compute\ImageVersion.cs" />
5960
<Compile Include="DependencyEngine.cs" />
61+
<Compile Include="IAsyncCmdlet.cs" />
62+
<Compile Include="ICmdlet.cs" />
6063
<Compile Include="INestedResourceConfig.cs" />
6164
<Compile Include="INestedResourceConfigVisitor.cs" />
6265
<Compile Include="INestedResourceStrategy.cs" />
66+
<Compile Include="IParameters.cs" />
6367
<Compile Include="IResourceId.cs" />
6468
<Compile Include="Property.cs" />
6569
<Compile Include="ResourceId.cs" />

0 commit comments

Comments
 (0)