1313using System . Threading ;
1414using System . Threading . Tasks ;
1515using Microsoft . Azure . WebJobs . Script . IO ;
16+ using Microsoft . Extensions . Logging ;
1617using Microsoft . Net . Http . Headers ;
1718using Newtonsoft . Json ;
1819using Newtonsoft . Json . Linq ;
@@ -27,18 +28,20 @@ public class SimpleKubernetesClient : IKubernetesClient, IDisposable
2728 private const string KubernetesSecretsDir = "/run/secrets/functions-keys" ;
2829 private readonly HttpClient _httpClient ;
2930 private readonly IEnvironment _environment ;
31+ private readonly ILogger _logger ;
3032 private Action _watchCallback ;
3133 private bool _disposed ;
3234 private AutoRecoveringFileSystemWatcher _fileWatcher ;
3335
34- public SimpleKubernetesClient ( IEnvironment environment ) : this ( environment , CreateHttpClient ( ) )
36+ public SimpleKubernetesClient ( IEnvironment environment , ILogger logger ) : this ( environment , CreateHttpClient ( ) , logger )
3537 { }
3638
3739 // for testing
38- internal SimpleKubernetesClient ( IEnvironment environment , HttpClient client )
40+ internal SimpleKubernetesClient ( IEnvironment environment , HttpClient client , ILogger logger )
3941 {
4042 _httpClient = client ;
4143 _environment = environment ;
44+ _logger = logger ;
4245 Task . Run ( ( ) => RunWatcher ( ) ) ;
4346 }
4447
@@ -58,7 +61,9 @@ public async Task<IDictionary<string, string>> GetSecrets()
5861 }
5962 else
6063 {
61- throw new InvalidOperationException ( $ "{ nameof ( KubernetesSecretsRepository ) } requires setting { EnvironmentSettingNames . AzureWebJobsKubernetesSecretName } or mounting secrets to { KubernetesSecretsDir } ") ;
64+ var exception = new InvalidOperationException ( $ "{ nameof ( KubernetesSecretsRepository ) } requires setting { EnvironmentSettingNames . AzureWebJobsKubernetesSecretName } or mounting secrets to { KubernetesSecretsDir } ") ;
65+ _logger . LogError ( exception , "Error geting secrets" ) ;
66+ throw exception ;
6267 }
6368 }
6469
@@ -74,6 +79,12 @@ public async Task UpdateSecrets(IDictionary<string, string> data)
7479 using ( var request = await GetRequest ( HttpMethod . Patch , url , new [ ] { new { op = "replace" , path = "/data" , value = data } } , "application/json-patch+json" ) )
7580 {
7681 var response = await _httpClient . SendAsync ( request ) ;
82+ if ( ! response . IsSuccessStatusCode )
83+ {
84+ _logger . LogError ( "Error updating Kubernetes secrets. {StatusCode}: {Content}" ,
85+ response . StatusCode ,
86+ await response . Content . ReadAsStringAsync ( ) ) ;
87+ }
7788 response . EnsureSuccessStatusCode ( ) ;
7889 }
7990 }
@@ -84,6 +95,15 @@ public void OnSecretChange(Action callback)
8495 }
8596
8697 private async Task RunWatcher ( )
98+ {
99+ while ( ! _disposed )
100+ {
101+ // watch API requests terminate after 4 minutes
102+ await RunWatcherInternal ( ) ;
103+ }
104+ }
105+
106+ private async Task RunWatcherInternal ( )
87107 {
88108 if ( string . IsNullOrEmpty ( KubernetesObjectName ) && FileUtility . DirectoryExists ( KubernetesSecretsDir ) )
89109 {
@@ -124,13 +144,15 @@ private async Task<IDictionary<string, string>> GetFromApiServer(string objectNa
124144 if ( response . IsSuccessStatusCode )
125145 {
126146 var obj = await response . Content . ReadAsAsync < JObject > ( ) ;
127- return obj [ "data" ]
128- ? . ToObject < IDictionary < string , string > > ( )
129- ? . ToDictionary (
147+ return obj . ContainsKey ( "data" )
148+ ? obj [ "data" ]
149+ . ToObject < IDictionary < string , string > > ( )
150+ . ToDictionary (
130151 k => k . Key ,
131152 v => decode
132153 ? Encoding . UTF8 . GetString ( Convert . FromBase64String ( v . Value ) )
133- : v . Value ) ;
154+ : v . Value )
155+ : new Dictionary < string , string > ( ) ;
134156 }
135157 else if ( response . StatusCode == HttpStatusCode . NotFound )
136158 {
@@ -139,7 +161,9 @@ private async Task<IDictionary<string, string>> GetFromApiServer(string objectNa
139161 }
140162 else
141163 {
142- throw new HttpRequestException ( $ "Error calling GET { url } , Status: { response . StatusCode } , Content: { await response . Content . ReadAsStringAsync ( ) } ") ;
164+ var exception = new HttpRequestException ( $ "Error calling GET { url } , Status: { response . StatusCode } , Content: { await response . Content . ReadAsStringAsync ( ) } ") ;
165+ _logger . LogError ( exception , "Error reading secrets from Kubernetes" ) ;
166+ throw exception ;
143167 }
144168 }
145169 }
@@ -165,6 +189,12 @@ private async Task CreateIfDoesntExist(string url, bool isSecret)
165189 using ( var createRequest = await GetRequest ( HttpMethod . Post , url , payload ) )
166190 {
167191 var createResponse = await _httpClient . SendAsync ( createRequest ) ;
192+ if ( ! createResponse . IsSuccessStatusCode )
193+ {
194+ _logger . LogError ( "Error creating Kubernetes secrets. {StatusCode}: {Content}" ,
195+ createResponse . StatusCode ,
196+ await createResponse . Content . ReadAsStringAsync ( ) ) ;
197+ }
168198 createResponse . EnsureSuccessStatusCode ( ) ;
169199 }
170200 }
0 commit comments