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