1+ // Copyright (c) Microsoft Corporation.
2+ // Licensed under the MIT License.
3+
4+ using System . Diagnostics . Tracing ;
5+ using System . Net . Http . Json ;
6+ using System . Text ;
7+ using System . Text . Json ;
8+ using Azure . Core ;
9+ using Azure . Core . Diagnostics ;
10+ using Azure . Identity ;
11+ using Microsoft . DevProxy . Abstractions ;
12+ using Microsoft . Extensions . Logging ;
13+
14+ namespace Microsoft . DevProxy . Plugins . RequestLogs . ApiCenter ;
15+
16+ internal class ApiCenterClientConfiguration
17+ {
18+ public string SubscriptionId { get ; set ; } = "" ;
19+ public string ResourceGroupName { get ; set ; } = "" ;
20+ public string ServiceName { get ; set ; } = "" ;
21+ public string WorkspaceName { get ; set ; } = "default" ;
22+ }
23+
24+ internal class ApiCenterClient
25+ {
26+ private readonly ApiCenterClientConfiguration _configuration ;
27+ private readonly ILogger _logger ;
28+ private readonly TokenCredential _credential = new DefaultAzureCredential ( new DefaultAzureCredentialOptions ( )
29+ {
30+ ExcludeInteractiveBrowserCredential = true ,
31+ // fails on Ubuntu
32+ ExcludeSharedTokenCacheCredential = true
33+ } ) ;
34+ private readonly HttpClient _httpClient ;
35+ private readonly AuthenticationDelegatingHandler _authenticationHandler ;
36+ private readonly string [ ] _scopes = [ "https://management.azure.com/.default" ] ;
37+
38+ internal ApiCenterClient ( ApiCenterClientConfiguration configuration , ILogger logger )
39+ {
40+ if ( configuration is null )
41+ {
42+ throw new ArgumentNullException ( nameof ( configuration ) ) ;
43+ }
44+
45+ if ( string . IsNullOrEmpty ( configuration . SubscriptionId ) )
46+ {
47+ throw new ArgumentException ( $ "Specify { nameof ( ApiCenterClientConfiguration . SubscriptionId ) } in the configuration.") ;
48+ }
49+ if ( string . IsNullOrEmpty ( configuration . ResourceGroupName ) )
50+ {
51+ throw new ArgumentException ( $ "Specify { nameof ( ApiCenterClientConfiguration . ResourceGroupName ) } in the configuration.") ;
52+ }
53+ if ( string . IsNullOrEmpty ( configuration . ServiceName ) )
54+ {
55+ throw new ArgumentException ( $ "Specify { nameof ( ApiCenterClientConfiguration . ServiceName ) } in the configuration.") ;
56+ }
57+ if ( string . IsNullOrEmpty ( configuration . WorkspaceName ) )
58+ {
59+ throw new ArgumentException ( $ "Specify { nameof ( ApiCenterClientConfiguration . WorkspaceName ) } in the configuration.") ;
60+ }
61+
62+ // load configuration from env vars
63+ if ( configuration . SubscriptionId . StartsWith ( '@' ) )
64+ {
65+ configuration . SubscriptionId = Environment . GetEnvironmentVariable ( configuration . SubscriptionId [ 1 ..] ) ?? configuration . SubscriptionId ;
66+ }
67+ if ( configuration . ResourceGroupName . StartsWith ( '@' ) )
68+ {
69+ configuration . ResourceGroupName = Environment . GetEnvironmentVariable ( configuration . ResourceGroupName [ 1 ..] ) ?? configuration . ResourceGroupName ;
70+ }
71+ if ( configuration . ServiceName . StartsWith ( '@' ) )
72+ {
73+ configuration . ServiceName = Environment . GetEnvironmentVariable ( configuration . ServiceName [ 1 ..] ) ?? configuration . ServiceName ;
74+ }
75+ if ( configuration . WorkspaceName . StartsWith ( '@' ) )
76+ {
77+ configuration . WorkspaceName = Environment . GetEnvironmentVariable ( configuration . WorkspaceName [ 1 ..] ) ?? configuration . WorkspaceName ;
78+ }
79+
80+ _configuration = configuration ;
81+ _logger = logger ;
82+
83+ _authenticationHandler = new AuthenticationDelegatingHandler ( _credential , _scopes )
84+ {
85+ InnerHandler = new HttpClientHandler ( )
86+ } ;
87+ _httpClient = new HttpClient ( _authenticationHandler ) ;
88+
89+ if ( _logger . IsEnabled ( LogLevel . Debug ) == true )
90+ {
91+ _ = AzureEventSourceListener . CreateConsoleLogger ( EventLevel . Verbose ) ;
92+ }
93+ }
94+
95+ internal Task < string ? > GetAccessToken ( CancellationToken cancellationToken )
96+ {
97+ return _authenticationHandler . GetAccessToken ( cancellationToken ) ;
98+ }
99+
100+ internal async Task < Api [ ] ? > GetApis ( )
101+ {
102+ _logger . LogInformation ( "Loading APIs from API Center..." ) ;
103+
104+ var res = await _httpClient . GetStringAsync ( $ "https://management.azure.com/subscriptions/{ _configuration . SubscriptionId } /resourceGroups/{ _configuration . ResourceGroupName } /providers/Microsoft.ApiCenter/services/{ _configuration . ServiceName } /workspaces/{ _configuration . WorkspaceName } /apis?api-version=2024-03-01") ;
105+ var collection = JsonSerializer . Deserialize < Collection < Api > > ( res , ProxyUtils . JsonSerializerOptions ) ;
106+ if ( collection is null || collection . Value is null )
107+ {
108+ return null ;
109+ }
110+ else
111+ {
112+ return collection . Value ;
113+ }
114+ }
115+
116+ internal async Task < Api ? > PutApi ( Api api , string apiName )
117+ {
118+ var content = new StringContent ( JsonSerializer . Serialize ( api , ProxyUtils . JsonSerializerOptions ) , Encoding . UTF8 , "application/json" ) ;
119+ var res = await _httpClient . PutAsync ( $ "https://management.azure.com/subscriptions/{ _configuration . SubscriptionId } /resourceGroups/{ _configuration . ResourceGroupName } /providers/Microsoft.ApiCenter/services/{ _configuration . ServiceName } /workspaces/{ _configuration . WorkspaceName } /apis/{ apiName } ?api-version=2024-03-01", content ) ;
120+
121+ var resContent = await res . Content . ReadAsStringAsync ( ) ;
122+ _logger . LogDebug ( resContent ) ;
123+
124+ if ( res . IsSuccessStatusCode )
125+ {
126+ return JsonSerializer . Deserialize < Api > ( resContent , ProxyUtils . JsonSerializerOptions ) ;
127+ }
128+ else
129+ {
130+ return null ;
131+ }
132+ }
133+
134+ internal async Task < ApiDeployment [ ] ? > GetDeployments ( string apiId )
135+ {
136+ _logger . LogDebug ( "Loading API deployments for {apiName}..." , apiId ) ;
137+
138+ var res = await _httpClient . GetStringAsync ( $ "https://management.azure.com{ apiId } /deployments?api-version=2024-03-01") ;
139+ var collection = JsonSerializer . Deserialize < Collection < ApiDeployment > > ( res , ProxyUtils . JsonSerializerOptions ) ;
140+ if ( collection is null || collection . Value is null )
141+ {
142+ return null ;
143+ }
144+ else
145+ {
146+ return collection . Value ;
147+ }
148+ }
149+
150+ internal async Task < ApiVersion [ ] ? > GetVersions ( string apiId )
151+ {
152+ _logger . LogDebug ( "Loading API versions for {apiName}..." , apiId ) ;
153+
154+ var res = await _httpClient . GetStringAsync ( $ "https://management.azure.com{ apiId } /versions?api-version=2024-03-01") ;
155+ var collection = JsonSerializer . Deserialize < Collection < ApiVersion > > ( res , ProxyUtils . JsonSerializerOptions ) ;
156+ if ( collection is null || collection . Value is null )
157+ {
158+ return null ;
159+ }
160+ else
161+ {
162+ return collection . Value ;
163+ }
164+ }
165+
166+ internal async Task < ApiVersion ? > PutVersion ( ApiVersion apiVersion , string apiId , string apiName )
167+ {
168+ var content = new StringContent ( JsonSerializer . Serialize ( apiVersion , ProxyUtils . JsonSerializerOptions ) , Encoding . UTF8 , "application/json" ) ;
169+ var res = await _httpClient . PutAsync ( $ "https://management.azure.com{ apiId } /versions/{ apiName } ?api-version=2024-03-01", content ) ;
170+
171+ var resContent = await res . Content . ReadAsStringAsync ( ) ;
172+ _logger . LogDebug ( resContent ) ;
173+
174+ if ( res . IsSuccessStatusCode )
175+ {
176+ return JsonSerializer . Deserialize < ApiVersion > ( resContent , ProxyUtils . JsonSerializerOptions ) ;
177+ }
178+ else
179+ {
180+ return null ;
181+ }
182+ }
183+
184+ internal async Task < ApiDefinition [ ] ? > GetDefinitions ( string versionId )
185+ {
186+ _logger . LogDebug ( "Loading API definitions for version {id}..." , versionId ) ;
187+
188+ var res = await _httpClient . GetStringAsync ( $ "https://management.azure.com{ versionId } /definitions?api-version=2024-03-01") ;
189+ var collection = JsonSerializer . Deserialize < Collection < ApiDefinition > > ( res , ProxyUtils . JsonSerializerOptions ) ;
190+ if ( collection is null || collection . Value is null )
191+ {
192+ return null ;
193+ }
194+ else
195+ {
196+ return collection . Value ;
197+ }
198+ }
199+
200+ internal async Task < ApiDefinition ? > GetDefinition ( string definitionId )
201+ {
202+ _logger . LogDebug ( "Loading API definition {id}..." , definitionId ) ;
203+
204+ var res = await _httpClient . GetStringAsync ( $ "https://management.azure.com{ definitionId } ?api-version=2024-03-01") ;
205+ return JsonSerializer . Deserialize < ApiDefinition > ( res , ProxyUtils . JsonSerializerOptions ) ;
206+ }
207+
208+ internal async Task < ApiDefinition ? > PutDefinition ( ApiDefinition apiDefinition , string apiVersionId , string definitionName )
209+ {
210+ var content = new StringContent ( JsonSerializer . Serialize ( apiDefinition , ProxyUtils . JsonSerializerOptions ) , Encoding . UTF8 , "application/json" ) ;
211+ var res = await _httpClient . PutAsync ( $ "https://management.azure.com{ apiVersionId } /definitions/{ definitionName } ?api-version=2024-03-01", content ) ;
212+
213+ var resContent = await res . Content . ReadAsStringAsync ( ) ;
214+ _logger . LogDebug ( resContent ) ;
215+
216+ if ( res . IsSuccessStatusCode )
217+ {
218+ return JsonSerializer . Deserialize < ApiDefinition > ( resContent , ProxyUtils . JsonSerializerOptions ) ;
219+ }
220+ else
221+ {
222+ return null ;
223+ }
224+ }
225+
226+ internal async Task < HttpResponseMessage > PostImportSpecification ( ApiSpecImport apiSpecImport , string definitionId )
227+ {
228+ var content = new StringContent ( JsonSerializer . Serialize ( apiSpecImport , ProxyUtils . JsonSerializerOptions ) , Encoding . UTF8 , "application/json" ) ;
229+ return await _httpClient . PostAsync ( $ "https://management.azure.com{ definitionId } /importSpecification?api-version=2024-03-01", content ) ;
230+ }
231+
232+ internal async Task < ApiSpecExportResult ? > PostExportSpecification ( string definitionId )
233+ {
234+ var definitionRes = await _httpClient . PostAsync ( $ "https://management.azure.com{ definitionId } /exportSpecification?api-version=2024-03-01", null ) ;
235+ return await definitionRes . Content . ReadFromJsonAsync < ApiSpecExportResult > ( ) ;
236+ }
237+ }
0 commit comments