13
13
using System . Threading ;
14
14
using System . Threading . Tasks ;
15
15
using Microsoft . Azure . WebJobs . Script . IO ;
16
+ using Microsoft . Extensions . Logging ;
16
17
using Microsoft . Net . Http . Headers ;
17
18
using Newtonsoft . Json ;
18
19
using Newtonsoft . Json . Linq ;
@@ -27,18 +28,20 @@ public class SimpleKubernetesClient : IKubernetesClient, IDisposable
27
28
private const string KubernetesSecretsDir = "/run/secrets/functions-keys" ;
28
29
private readonly HttpClient _httpClient ;
29
30
private readonly IEnvironment _environment ;
31
+ private readonly ILogger _logger ;
30
32
private Action _watchCallback ;
31
33
private bool _disposed ;
32
34
private AutoRecoveringFileSystemWatcher _fileWatcher ;
33
35
34
- public SimpleKubernetesClient ( IEnvironment environment ) : this ( environment , CreateHttpClient ( ) )
36
+ public SimpleKubernetesClient ( IEnvironment environment , ILogger logger ) : this ( environment , CreateHttpClient ( ) , logger )
35
37
{ }
36
38
37
39
// for testing
38
- internal SimpleKubernetesClient ( IEnvironment environment , HttpClient client )
40
+ internal SimpleKubernetesClient ( IEnvironment environment , HttpClient client , ILogger logger )
39
41
{
40
42
_httpClient = client ;
41
43
_environment = environment ;
44
+ _logger = logger ;
42
45
Task . Run ( ( ) => RunWatcher ( ) ) ;
43
46
}
44
47
@@ -58,7 +61,9 @@ public async Task<IDictionary<string, string>> GetSecrets()
58
61
}
59
62
else
60
63
{
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 ;
62
67
}
63
68
}
64
69
@@ -74,6 +79,12 @@ public async Task UpdateSecrets(IDictionary<string, string> data)
74
79
using ( var request = await GetRequest ( HttpMethod . Patch , url , new [ ] { new { op = "replace" , path = "/data" , value = data } } , "application/json-patch+json" ) )
75
80
{
76
81
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
+ }
77
88
response . EnsureSuccessStatusCode ( ) ;
78
89
}
79
90
}
@@ -84,6 +95,15 @@ public void OnSecretChange(Action callback)
84
95
}
85
96
86
97
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 ( )
87
107
{
88
108
if ( string . IsNullOrEmpty ( KubernetesObjectName ) && FileUtility . DirectoryExists ( KubernetesSecretsDir ) )
89
109
{
@@ -124,13 +144,15 @@ private async Task<IDictionary<string, string>> GetFromApiServer(string objectNa
124
144
if ( response . IsSuccessStatusCode )
125
145
{
126
146
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 (
130
151
k => k . Key ,
131
152
v => decode
132
153
? Encoding . UTF8 . GetString ( Convert . FromBase64String ( v . Value ) )
133
- : v . Value ) ;
154
+ : v . Value )
155
+ : new Dictionary < string , string > ( ) ;
134
156
}
135
157
else if ( response . StatusCode == HttpStatusCode . NotFound )
136
158
{
@@ -139,7 +161,9 @@ private async Task<IDictionary<string, string>> GetFromApiServer(string objectNa
139
161
}
140
162
else
141
163
{
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 ;
143
167
}
144
168
}
145
169
}
@@ -165,6 +189,12 @@ private async Task CreateIfDoesntExist(string url, bool isSecret)
165
189
using ( var createRequest = await GetRequest ( HttpMethod . Post , url , payload ) )
166
190
{
167
191
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
+ }
168
198
createResponse . EnsureSuccessStatusCode ( ) ;
169
199
}
170
200
}
0 commit comments