6
6
using System . Collections . ObjectModel ;
7
7
using System . IO ;
8
8
using System . Linq ;
9
+ using System . Net . Http ;
10
+ using System . Text ;
9
11
using System . Threading . Tasks ;
10
12
using Microsoft . AspNetCore . Http ;
11
- using Microsoft . Azure . WebJobs . Script ;
13
+ using Microsoft . Azure . WebJobs . Script . Description ;
14
+ using Microsoft . Azure . WebJobs . Script . Extensions ;
12
15
using Microsoft . Azure . WebJobs . Script . Management . Models ;
13
- using Microsoft . Azure . WebJobs . Script . WebHost ;
14
16
using Microsoft . Azure . WebJobs . Script . WebHost . Extensions ;
15
17
using Microsoft . Azure . WebJobs . Script . WebHost . Helpers ;
16
18
using Microsoft . Extensions . Logging ;
17
19
using Newtonsoft . Json ;
20
+ using Newtonsoft . Json . Linq ;
18
21
19
22
namespace Microsoft . Azure . WebJobs . Script . WebHost . Management
20
23
{
@@ -37,9 +40,7 @@ public WebFunctionsManager(WebHostSettings webSettings, ILoggerFactory loggerFac
37
40
/// <returns>collection of FunctionMetadataResponse</returns>
38
41
public async Task < IEnumerable < FunctionMetadataResponse > > GetFunctionsMetadata ( HttpRequest request )
39
42
{
40
- return await ScriptHost . ReadFunctionsMetadata ( FileUtility . EnumerateDirectories ( _config . RootScriptPath ) , _logger , new Dictionary < string , Collection < string > > ( ) )
41
- . Select ( fm => fm . ToFunctionMetadataResponse ( request , _config ) )
42
- . WhenAll ( ) ;
43
+ return await GetFunctionsMetadata ( ) . Select ( fm => fm . ToFunctionMetadataResponse ( request , _config ) ) . WhenAll ( ) ;
43
44
}
44
45
45
46
/// <summary>
@@ -123,7 +124,7 @@ await functionMetadata
123
124
/// <returns>(success, FunctionMetadataResponse)</returns>
124
125
public async Task < ( bool , FunctionMetadataResponse ) > TryGetFunction ( string name , HttpRequest request )
125
126
{
126
- var functionMetadata = ScriptHost . ReadFunctionMetadata ( Path . Combine ( _config . RootScriptPath , name ) , _logger , new Dictionary < string , Collection < string > > ( ) ) ;
127
+ var functionMetadata = ScriptHost . ReadFunctionMetadata ( Path . Combine ( _config . RootScriptPath , name ) , new Dictionary < string , Collection < string > > ( ) ) ;
127
128
if ( functionMetadata != null )
128
129
{
129
130
return ( true , await functionMetadata . ToFunctionMetadataResponse ( request , _config ) ) ;
@@ -158,6 +159,88 @@ await functionMetadata
158
159
}
159
160
}
160
161
162
+ /// <summary>
163
+ /// Try to perform sync triggers to the scale controller
164
+ /// </summary>
165
+ /// <returns>(success, error)</returns>
166
+ public async Task < ( bool success , string error ) > TrySyncTriggers ( )
167
+ {
168
+ var durableTaskHubName = await GetDurableTaskHubName ( ) ;
169
+ var functionsTriggers = ( await GetFunctionsMetadata ( )
170
+ . Select ( f => f . ToFunctionTrigger ( _config ) )
171
+ . WhenAll ( ) )
172
+ . Where ( t => t != null )
173
+ . Select ( t =>
174
+ {
175
+ // if we have a durableTask hub name and the function trigger is either orchestrationTrigger OR activityTrigger,
176
+ // add a property "taskHubName" with durable task hub name.
177
+ if ( durableTaskHubName != null
178
+ && ( t [ "type" ] ? . ToString ( ) . Equals ( "orchestrationTrigger" , StringComparison . OrdinalIgnoreCase ) == true
179
+ || t [ "type" ] ? . ToString ( ) . Equals ( "activityTrigger" , StringComparison . OrdinalIgnoreCase ) == true ) )
180
+ {
181
+ t [ "taskHubName" ] = durableTaskHubName ;
182
+ }
183
+ return t ;
184
+ } ) ;
185
+
186
+ if ( FileUtility . FileExists ( Path . Combine ( _config . RootScriptPath , ScriptConstants . ProxyMetadataFileName ) ) )
187
+ {
188
+ // This is because we still need to scale function apps that are proxies only
189
+ functionsTriggers = functionsTriggers . Append ( new JObject ( new { type = "routingTrigger" } ) ) ;
190
+ }
191
+
192
+ return await InternalSyncTriggers ( functionsTriggers ) ;
193
+ }
194
+
195
+ // This function will call POST https://{app}.azurewebsites.net/operation/settriggers with the content
196
+ // of triggers. It'll verify app owner ship using a SWT token valid for 5 minutes. It should be plenty.
197
+ private async Task < ( bool , string ) > InternalSyncTriggers ( IEnumerable < JObject > triggers )
198
+ {
199
+ var content = JsonConvert . SerializeObject ( triggers ) ;
200
+ var token = SimpleWebTokenHelper . CreateToken ( DateTime . UtcNow . AddMinutes ( 5 ) ) ;
201
+
202
+ // This will be a problem for national clouds. However, Antares isn't injecting the
203
+ // WEBSITE_HOSTNAME yet for linux apps. So until then will use this.
204
+ var url = $ "https://{ Environment . GetEnvironmentVariable ( "WEBSITE_SITE_NAME" ) } .azurewebsites.net/operations/settriggers";
205
+
206
+ using ( var request = new HttpRequestMessage ( HttpMethod . Post , url ) )
207
+ {
208
+ // This has to start with Mozilla because the frontEnd checks for it.
209
+ request . Headers . Add ( "User-Agent" , "Mozilla/5.0" ) ;
210
+ request . Headers . Add ( "x-ms-site-restricted-token" , token ) ;
211
+ request . Content = new StringContent ( content , Encoding . UTF8 , "application/json" ) ;
212
+
213
+ var response = await HttpClientUtility . Instance . SendAsync ( request ) ;
214
+ return response . IsSuccessStatusCode
215
+ ? ( true , string . Empty )
216
+ : ( false , $ "Sync triggers failed with: { response . StatusCode } ") ;
217
+ }
218
+ }
219
+
220
+ private IEnumerable < FunctionMetadata > GetFunctionsMetadata ( )
221
+ {
222
+ return ScriptHost
223
+ . ReadFunctionsMetadata ( FileUtility . EnumerateDirectories ( _config . RootScriptPath ) , _logger , new Dictionary < string , Collection < string > > ( ) ) ;
224
+ }
225
+
226
+ private async Task < string > GetDurableTaskHubName ( )
227
+ {
228
+ string hostJsonPath = Path . Combine ( _config . RootScriptPath , ScriptConstants . HostMetadataFileName ) ;
229
+ if ( FileUtility . FileExists ( hostJsonPath ) )
230
+ {
231
+ // We're looking for {VALUE}
232
+ // {
233
+ // "durableTask": {
234
+ // "HubName": "{VALUE}"
235
+ // }
236
+ // }
237
+ var hostJson = JsonConvert . DeserializeObject < HostJsonModel > ( await FileUtility . ReadAsync ( hostJsonPath ) ) ;
238
+ return hostJson ? . DurableTask ? . HubName ;
239
+ }
240
+
241
+ return null ;
242
+ }
243
+
161
244
private void DeleteFunctionArtifacts ( FunctionMetadataResponse function )
162
245
{
163
246
// TODO: clear secrets
@@ -169,5 +252,15 @@ private void DeleteFunctionArtifacts(FunctionMetadataResponse function)
169
252
FileUtility . DeleteFileSafe ( testDataPath ) ;
170
253
}
171
254
}
255
+
256
+ private class HostJsonModel
257
+ {
258
+ public DurableTaskHostModel DurableTask { get ; set ; }
259
+ }
260
+
261
+ private class DurableTaskHostModel
262
+ {
263
+ public string HubName { get ; set ; }
264
+ }
172
265
}
173
266
}
0 commit comments