7
7
import { v1 } from "@authzed/authzed-node" ;
8
8
import { log } from "@gitpod/gitpod-protocol/lib/util/logging" ;
9
9
import * as grpc from "@grpc/grpc-js" ;
10
+ import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server" ;
11
+ import { TrustedValue } from "@gitpod/gitpod-protocol/lib/util/scrubbing" ;
10
12
11
13
export interface SpiceDBClientConfig {
12
14
address : string ;
@@ -15,6 +17,34 @@ export interface SpiceDBClientConfig {
15
17
16
18
export type SpiceDBClient = v1 . ZedPromiseClientInterface ;
17
19
type Client = v1 . ZedClientInterface & grpc . Client ;
20
+ const DEFAULT_FEATURE_FLAG_VAULE = "undefined" ;
21
+ const DefaultClientOptions : grpc . ClientOptions = {
22
+ // we ping frequently to check if the connection is still alive
23
+ "grpc.keepalive_time_ms" : 1000 ,
24
+ "grpc.keepalive_timeout_ms" : 1000 ,
25
+
26
+ "grpc.max_reconnect_backoff_ms" : 5000 ,
27
+ "grpc.initial_reconnect_backoff_ms" : 500 ,
28
+ "grpc.service_config" : JSON . stringify ( {
29
+ methodConfig : [
30
+ {
31
+ name : [ { } ] ,
32
+ retryPolicy : {
33
+ maxAttempts : 10 ,
34
+ initialBackoff : "0.1s" ,
35
+ maxBackoff : "5s" ,
36
+ backoffMultiplier : 2.0 ,
37
+ retryableStatusCodes : [ "UNAVAILABLE" , "DEADLINE_EXCEEDED" ] ,
38
+ } ,
39
+ } ,
40
+ ] ,
41
+ } ) ,
42
+ "grpc.enable_retries" : 1 , //TODO enabled by default
43
+
44
+ // Governs how log DNS resolution results are cached (at minimum!)
45
+ // default is 30s, which is too long for us during rollouts (where service DNS entries are updated)
46
+ "grpc.dns_min_time_between_resolutions_ms" : 2000 ,
47
+ } ;
18
48
19
49
export function spiceDBConfigFromEnv ( ) : SpiceDBClientConfig | undefined {
20
50
const token = process . env [ "SPICEDB_PRESHARED_KEY" ] ;
@@ -36,48 +66,80 @@ export function spiceDBConfigFromEnv(): SpiceDBClientConfig | undefined {
36
66
37
67
export class SpiceDBClientProvider {
38
68
private client : Client | undefined ;
69
+ private previousClientOptionsString : string = DEFAULT_FEATURE_FLAG_VAULE ;
70
+ private clientOptions : grpc . ClientOptions ;
39
71
40
72
constructor (
41
73
private readonly clientConfig : SpiceDBClientConfig ,
42
74
private readonly interceptors : grpc . Interceptor [ ] = [ ] ,
43
- ) { }
75
+ ) {
76
+ this . clientOptions = DefaultClientOptions ;
77
+ this . reconcileClientOptions ( )
78
+ . then ( ( ) => { } )
79
+ . catch ( ( e ) => {
80
+ log . error ( "[spicedb] Failed to reconcile client options" , e ) ;
81
+ } ) ;
82
+ }
44
83
45
- getClient ( ) : SpiceDBClient {
46
- if ( ! this . client ) {
47
- this . client = v1 . NewClient (
48
- this . clientConfig . token ,
49
- this . clientConfig . address ,
50
- v1 . ClientSecurity . INSECURE_PLAINTEXT_CREDENTIALS ,
51
- undefined , //
52
- {
53
- // we ping frequently to check if the connection is still alive
54
- "grpc.keepalive_time_ms" : 1000 ,
55
- "grpc.keepalive_timeout_ms" : 1000 ,
84
+ private async reconcileClientOptions ( ) {
85
+ const doReconcileClientOptions = async ( ) => {
86
+ const customClientOptions = await getExperimentsClientForBackend ( ) . getValueAsync (
87
+ "spicedb_client_options" ,
88
+ DEFAULT_FEATURE_FLAG_VAULE ,
89
+ { } ,
90
+ ) ;
91
+ if ( customClientOptions === this . previousClientOptionsString ) {
92
+ return ;
93
+ }
94
+ try {
95
+ let clientOptions = DefaultClientOptions ;
96
+ if ( customClientOptions && customClientOptions != DEFAULT_FEATURE_FLAG_VAULE ) {
97
+ clientOptions = JSON . parse ( customClientOptions ) ;
98
+ }
99
+ if ( this . client != null ) {
100
+ const newClient = this . createClient ( clientOptions ) ;
101
+ const oldClient = this . client ;
102
+ this . client = newClient ;
56
103
57
- "grpc.max_reconnect_backoff_ms" : 5000 ,
58
- "grpc.initial_reconnect_backoff_ms" : 500 ,
59
- "grpc.service_config" : JSON . stringify ( {
60
- methodConfig : [
61
- {
62
- name : [ { } ] ,
63
- retryPolicy : {
64
- maxAttempts : 10 ,
65
- initialBackoff : "0.1s" ,
66
- maxBackoff : "5s" ,
67
- backoffMultiplier : 2.0 ,
68
- retryableStatusCodes : [ "UNAVAILABLE" , "DEADLINE_EXCEEDED" ] ,
69
- } ,
70
- } ,
71
- ] ,
72
- } ) ,
73
- "grpc.enable_retries" : 1 , //TODO enabled by default
104
+ log . info ( "[spicedb] client options changes" , {
105
+ clientOptions : new TrustedValue ( clientOptions ) ,
106
+ } ) ;
74
107
75
- // Governs how log DNS resolution results are cached (at minimum!)
76
- // default is 30s, which is too long for us during rollouts (where service DNS entries are updated)
77
- "grpc.dns_min_time_between_resolutions_ms" : 2000 ,
78
- interceptors : this . interceptors ,
79
- } ,
80
- ) as Client ;
108
+ setTimeout ( ( ) => {
109
+ oldClient . close ( ) ;
110
+ } , 10 * 1000 ) ;
111
+ }
112
+ this . clientOptions = clientOptions ;
113
+ this . previousClientOptionsString = customClientOptions ;
114
+ } catch ( e ) {
115
+ log . error ( "[spicedb] Failed to parse custom client options" , e ) ;
116
+ }
117
+ } ;
118
+ while ( true ) {
119
+ await doReconcileClientOptions ( ) ;
120
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 60 * 1000 ) ) ;
121
+ }
122
+ }
123
+
124
+ private createClient ( clientOptions : grpc . ClientOptions ) : Client {
125
+ log . debug ( "[spicedb] creating client" , {
126
+ clientOptions : new TrustedValue ( clientOptions ) ,
127
+ } ) ;
128
+ return v1 . NewClient (
129
+ this . clientConfig . token ,
130
+ this . clientConfig . address ,
131
+ v1 . ClientSecurity . INSECURE_PLAINTEXT_CREDENTIALS ,
132
+ undefined , //
133
+ {
134
+ ...clientOptions ,
135
+ interceptors : this . interceptors ,
136
+ } ,
137
+ ) as Client ;
138
+ }
139
+
140
+ getClient ( ) : SpiceDBClient {
141
+ if ( ! this . client ) {
142
+ this . client = this . createClient ( this . clientOptions ) ;
81
143
}
82
144
return this . client . promises ;
83
145
}
0 commit comments