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+ }
0 commit comments