1   /*
2    * Copyright 2017 LINE Corporation
3    *
4    * LINE Corporation licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package com.linecorp.centraldogma.server;
18  
19  import static com.google.common.base.Preconditions.checkArgument;
20  import static com.linecorp.centraldogma.server.auth.AuthConfig.DEFAULT_SESSION_CACHE_SPEC;
21  import static com.linecorp.centraldogma.server.auth.AuthConfig.DEFAULT_SESSION_TIMEOUT_MILLIS;
22  import static com.linecorp.centraldogma.server.auth.AuthConfig.DEFAULT_SESSION_VALIDATION_SCHEDULE;
23  import static com.linecorp.centraldogma.server.internal.storage.repository.RepositoryCache.validateCacheSpec;
24  import static java.util.Objects.requireNonNull;
25  
26  import java.io.File;
27  import java.net.InetSocketAddress;
28  import java.time.Duration;
29  import java.util.ArrayList;
30  import java.util.Collections;
31  import java.util.List;
32  import java.util.Set;
33  
34  import javax.annotation.Nullable;
35  
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  import com.github.benmanes.caffeine.cache.CaffeineSpec;
40  import com.google.common.collect.ImmutableList;
41  import com.google.common.collect.ImmutableSet;
42  import com.google.common.collect.ImmutableSet.Builder;
43  
44  import com.linecorp.armeria.common.Flags;
45  import com.linecorp.armeria.common.SessionProtocol;
46  import com.linecorp.armeria.server.ServerPort;
47  import com.linecorp.centraldogma.internal.Jackson;
48  import com.linecorp.centraldogma.server.auth.AuthConfig;
49  import com.linecorp.centraldogma.server.auth.AuthProvider;
50  import com.linecorp.centraldogma.server.auth.AuthProviderFactory;
51  import com.linecorp.centraldogma.server.auth.Session;
52  import com.linecorp.centraldogma.server.storage.repository.Repository;
53  
54  import io.micrometer.core.instrument.MeterRegistry;
55  
56  /**
57   * Builds a {@link CentralDogma} server.
58   *
59   * <pre>{@code
60   * CentralDogmaBuilder builder = new CentralDogmaBuilder(new File("/tmp/dogma"));
61   * builder.numRepositoryWorkers(32);
62   * builder...;
63   * CentralDogma dogma = builder.build();
64   * dogma.start();
65   * }</pre>
66   */
67  public final class CentralDogmaBuilder {
68      private static final Logger logger = LoggerFactory.getLogger(CentralDogmaBuilder.class);
69  
70      // You get 36462 if you map 'dogma' to T9 phone dialer layout.
71      private static final ServerPort DEFAULT_PORT = new ServerPort(36462, SessionProtocol.HTTP);
72  
73      static final int DEFAULT_NUM_REPOSITORY_WORKERS = 16;
74      static final int DEFAULT_NUM_MIRRORING_THREADS = 16;
75      static final int DEFAULT_MAX_NUM_FILES_PER_MIRROR = 8192;
76      static final long DEFAULT_MAX_NUM_BYTES_PER_MIRROR = 32 * 1048576; // 32 MiB
77      static final long DEFAULT_MAX_REMOVED_REPOSITORY_AGE_MILLIS = 604_800_000;  // 7 days
78  
79      static final String DEFAULT_REPOSITORY_CACHE_SPEC =
80              "maximumWeight=134217728," + // Cache up to apx. 128-megachars.
81              "expireAfterAccess=5m";      // Expire on 5 minutes of inactivity.
82  
83      // Armeria properties
84      // Note that we use nullable types here for optional properties.
85      // When a property is null, the default value will be used implicitly.
86      private final List<ServerPort> ports = new ArrayList<>(2);
87      private TlsConfig tls;
88      private List<String> trustedProxyAddresses = new ArrayList<>();
89      private List<String> clientAddressSources = new ArrayList<>();
90      private Integer numWorkers;
91      private Integer maxNumConnections;
92      private Long requestTimeoutMillis;
93      private Long idleTimeoutMillis;
94      private Integer maxFrameLength;
95  
96      // Central Dogma properties
97      private final File dataDir;
98      private int numRepositoryWorkers = DEFAULT_NUM_REPOSITORY_WORKERS;
99      private long maxRemovedRepositoryAgeMillis = DEFAULT_MAX_REMOVED_REPOSITORY_AGE_MILLIS;
100 
101     @Nullable
102     private String repositoryCacheSpec = DEFAULT_REPOSITORY_CACHE_SPEC;
103     private boolean webAppEnabled = true;
104     @Nullable
105     private String webAppTitle;
106     private boolean mirroringEnabled = true;
107     private int numMirroringThreads = DEFAULT_NUM_MIRRORING_THREADS;
108     private int maxNumFilesPerMirror = DEFAULT_MAX_NUM_FILES_PER_MIRROR;
109     private long maxNumBytesPerMirror = DEFAULT_MAX_NUM_BYTES_PER_MIRROR;
110     @Nullable
111     private GracefulShutdownTimeout gracefulShutdownTimeout;
112     private ReplicationConfig replicationConfig = ReplicationConfig.NONE;
113     private String accessLogFormat;
114 
115     // AuthConfig properties
116     @Nullable
117     private AuthProviderFactory authProviderFactory;
118     private final ImmutableSet.Builder<String> administrators = new Builder<>();
119     private boolean caseSensitiveLoginNames;
120     private String sessionCacheSpec = DEFAULT_SESSION_CACHE_SPEC;
121     private long sessionTimeoutMillis = DEFAULT_SESSION_TIMEOUT_MILLIS;
122     private String sessionValidationSchedule = DEFAULT_SESSION_VALIDATION_SCHEDULE;
123     @Nullable
124     private Object authProviderProperties;
125     private int writeQuota;
126     private int timeWindowSeconds;
127     private MeterRegistry meterRegistry = Flags.meterRegistry();
128 
129     @Nullable
130     private CorsConfig corsConfig;
131 
132     /**
133      * Creates a new builder with the specified data directory.
134      */
135     public CentralDogmaBuilder(File dataDir) {
136         this.dataDir = requireNonNull(dataDir, "dataDir");
137         if (dataDir.exists() && !dataDir.isDirectory()) {
138             throw new IllegalArgumentException("dataDir: " + dataDir + " (not a directory)");
139         }
140     }
141 
142     /**
143      * Adds a port that serves the HTTP requests. If unspecified, cleartext HTTP on port 36462 is used.
144      *
145      * @param port the TCP/IP port number
146      * @param protocol {@link SessionProtocol#HTTP} or {@link SessionProtocol#HTTPS}
147      */
148     public CentralDogmaBuilder port(int port, SessionProtocol protocol) {
149         return port(new ServerPort(port, protocol));
150     }
151 
152     /**
153      * Adds a port that serves the HTTP requests. If unspecified, cleartext HTTP on port 36462 is used.
154      *
155      * @param localAddress the TCP/IP load address to bind
156      * @param protocol {@link SessionProtocol#HTTP} or {@link SessionProtocol#HTTPS}
157      */
158     public CentralDogmaBuilder port(InetSocketAddress localAddress, SessionProtocol protocol) {
159         return port(new ServerPort(localAddress, protocol));
160     }
161 
162     /**
163      * Adds a port that serves the HTTP requests. If unspecified, cleartext HTTP on port 36462 is used.
164      */
165     public CentralDogmaBuilder port(ServerPort port) {
166         ports.add(requireNonNull(port, "port"));
167         return this;
168     }
169 
170     /**
171      * Sets a {@link TlsConfig} for supporting TLS on the server.
172      */
173     public CentralDogmaBuilder tls(TlsConfig tls) {
174         this.tls = requireNonNull(tls, "tls");
175         return this;
176     }
177 
178     /**
179      * Adds addresses or ranges of <a href="https://tools.ietf.org/html/rfc4632">
180      * Classless Inter-domain Routing (CIDR)</a> blocks of trusted proxy servers.
181      *
182      * @param exactOrCidrAddresses a list of addresses and CIDR blocks, e.g. {@code 10.0.0.1} for a single
183      *                             address or {@code 10.0.0.0/8} for a CIDR block
184      */
185     public CentralDogmaBuilder trustedProxyAddresses(String... exactOrCidrAddresses) {
186         requireNonNull(exactOrCidrAddresses, "exactOrCidrAddresses");
187         trustedProxyAddresses.addAll(ImmutableList.copyOf(exactOrCidrAddresses));
188         return this;
189     }
190 
191     /**
192      * Adds addresses or ranges of <a href="https://tools.ietf.org/html/rfc4632">
193      * Classless Inter-domain Routing (CIDR)</a> blocks of trusted proxy servers.
194      *
195      * @param exactOrCidrAddresses a list of addresses and CIDR blocks, e.g. {@code 10.0.0.1} for a single
196      *                             address or {@code 10.0.0.0/8} for a CIDR block
197      */
198     public CentralDogmaBuilder trustedProxyAddresses(Iterable<String> exactOrCidrAddresses) {
199         requireNonNull(exactOrCidrAddresses, "exactOrCidrAddresses");
200         trustedProxyAddresses.addAll(ImmutableList.copyOf(exactOrCidrAddresses));
201         return this;
202     }
203 
204     /**
205      * Adds the HTTP header names to be used for retrieving a client address.
206      *
207      * <p>Note that {@code "PROXY_PROTOCOL"} indicates the source address specified in a
208      * <a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">PROXY protocol</a> message.
209      *
210      * <p>Also note that if you configured trusted proxy addresses, {@code "forwarded"},
211      * {@code "x-forwarded-for"} and {@code "PROXY_PROTOCOL"} will be used as client address sources by default.
212      *
213      * @param clientAddressSources the HTTP header names or {@code "PROXY_PROTOCOL"} to be used for
214      *                             retrieving a client address
215      */
216     public CentralDogmaBuilder clientAddressSources(String... clientAddressSources) {
217         requireNonNull(clientAddressSources, "clientAddressSources");
218         this.clientAddressSources.addAll(ImmutableList.copyOf(clientAddressSources));
219         return this;
220     }
221 
222     /**
223      * Adds the HTTP header names to be used for retrieving a client address.
224      *
225      * <p>Note that {@code "PROXY_PROTOCOL"} indicates the source address specified in a
226      * <a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">PROXY protocol</a> message.
227      *
228      * <p>Also note that if you configured trusted proxy addresses, {@code "forwarded"},
229      * {@code "x-forwarded-for"} and {@code "PROXY_PROTOCOL"} will be used as client address sources by default.
230      *
231      * @param clientAddressSources the HTTP header names or {@code "PROXY_PROTOCOL"} to be used for
232      *                             retrieving a client address
233      */
234     public CentralDogmaBuilder clientAddressSources(Iterable<String> clientAddressSources) {
235         requireNonNull(clientAddressSources, "clientAddressSources");
236         this.clientAddressSources.addAll(ImmutableList.copyOf(clientAddressSources));
237         return this;
238     }
239 
240     /**
241      * Sets the number of I/O worker threads. <a href="https://line.github.io/armeria/">Armeria</a> default is
242      * used if unspecified.
243      */
244     public CentralDogmaBuilder numWorkers(int numWorkers) {
245         this.numWorkers = numWorkers;
246         return this;
247     }
248 
249     /**
250      * Sets the maximum allowed number of TCP/IP connections. If unspecified, no limit is enforced.
251      */
252     public CentralDogmaBuilder maxNumConnections(int maxNumConnections) {
253         this.maxNumConnections = maxNumConnections;
254         return this;
255     }
256 
257     /**
258      * Sets the timeout for handling an incoming request. If it takes more than the specified timeout to
259      * handle a request, the server may respond with '503 Service Unavailable' or fail to respond.
260      * <a href="https://line.github.io/armeria/">Armeria</a> default is used if unspecified.
261      */
262     public CentralDogmaBuilder requestTimeout(Duration requestTimeout) {
263         return requestTimeoutMillis(requireNonNull(requestTimeout, "requestTimeout").toMillis());
264     }
265 
266     /**
267      * Sets the timeout for handling an incoming request, in milliseconds. If it takes more than
268      * the specified timeout to handle a request, the server may respond with '503 Service Unavailable' or
269      * fail to respond. <a href="https://line.github.io/armeria/">Armeria</a> default is used if unspecified.
270      */
271     public CentralDogmaBuilder requestTimeoutMillis(long requestTimeoutMillis) {
272         this.requestTimeoutMillis = requestTimeoutMillis;
273         return this;
274     }
275 
276     /**
277      * Sets the timeout for keeping an idle connection. A connection is automatically closed when it stays idle
278      * without any requests in progress for more than the specified timeout.
279      * <a href="https://line.github.io/armeria/">Armeria</a> default is used if unspecified.
280      */
281     public CentralDogmaBuilder idleTimeout(Duration idleTimeout) {
282         return idleTimeoutMillis(requireNonNull(idleTimeout, "idleTimeout").toMillis());
283     }
284 
285     /**
286      * Sets the timeout for keeping an idle connection, in milliseconds. A connection is automatically closed
287      * when it stays idle without any requests in progress for more than the specified timeout.
288      */
289     public CentralDogmaBuilder idleTimeoutMillis(long idleTimeoutMillis) {
290         this.idleTimeoutMillis = idleTimeoutMillis;
291         return this;
292     }
293 
294     /**
295      * Sets the maximum allowed content length of an incoming request.
296      */
297     public CentralDogmaBuilder maxFrameLength(int maxFrameLength) {
298         this.maxFrameLength = maxFrameLength;
299         return this;
300     }
301 
302     /**
303      * Sets the number of worker threads dedicated to repository access.
304      * If unspecified, {@value #DEFAULT_NUM_REPOSITORY_WORKERS} threads are created at maximum.
305      */
306     public CentralDogmaBuilder numRepositoryWorkers(int numRepositoryWorkers) {
307         this.numRepositoryWorkers = numRepositoryWorkers;
308         return this;
309     }
310 
311     /**
312      * Sets the maximum allowed age of removed projects and repositories before they are purged.
313      * Set {@code 0} to disable automatic purge.
314      * If unspecified, the default of {@value #DEFAULT_MAX_REMOVED_REPOSITORY_AGE_MILLIS} milliseconds is used.
315      */
316     public CentralDogmaBuilder maxRemovedRepositoryAge(Duration maxRemovedRepositoryAge) {
317         maxRemovedRepositoryAgeMillis(
318                 requireNonNull(maxRemovedRepositoryAge, "maxRemovedRepositoryAge").toMillis());
319         return this;
320     }
321 
322     /**
323      * Sets the maximum allowed age, in milliseconds of removed projects and repositories
324      * before they are purged.
325      * Set {@code 0} to disable automatic purge.
326      * If unspecified, the default of {@value #DEFAULT_MAX_REMOVED_REPOSITORY_AGE_MILLIS} milliseconds is used.
327      */
328     public CentralDogmaBuilder maxRemovedRepositoryAgeMillis(long maxRemovedRepositoryAgeMillis) {
329         this.maxRemovedRepositoryAgeMillis = maxRemovedRepositoryAgeMillis;
330         return this;
331     }
332 
333     /**
334      * Sets the cache specification which determines the capacity and behavior of the cache for the return
335      * values of methods in {@link Repository} of the server. See {@link CaffeineSpec} for the syntax
336      * of the spec. If unspecified, the default cache spec of {@value #DEFAULT_REPOSITORY_CACHE_SPEC} is used.
337      *
338      * @deprecated Use {@link #repositoryCacheSpec(String)}.
339      */
340     @Deprecated
341     public CentralDogmaBuilder cacheSpec(String cacheSpec) {
342         repositoryCacheSpec = validateCacheSpec(cacheSpec);
343         return this;
344     }
345 
346     /**
347      * Sets the cache specification which determines the capacity and behavior of the cache for the return
348      * values of methods in {@link Repository} of the server. See {@link CaffeineSpec} for the syntax
349      * of the spec. If unspecified, the default cache spec of {@value #DEFAULT_REPOSITORY_CACHE_SPEC} is used.
350      */
351     public CentralDogmaBuilder repositoryCacheSpec(String repositoryCacheSpec) {
352         this.repositoryCacheSpec = validateCacheSpec(repositoryCacheSpec);
353         return this;
354     }
355 
356     /**
357      * Sets whether administrative web application is enabled or not.
358      * If unspecified, the administrative web application is enabled.
359      */
360     public CentralDogmaBuilder webAppEnabled(boolean webAppEnabled) {
361         this.webAppEnabled = webAppEnabled;
362         return this;
363     }
364 
365     /**
366      * Sets the title text which is displayed on the navigation bar of the administrative web application.
367      */
368     public CentralDogmaBuilder webAppTitle(String webAppTitle) {
369         this.webAppTitle = requireNonNull(webAppTitle, "webAppTitle");
370         return this;
371     }
372 
373     /**
374      * Sets whether {@link MirroringService} is enabled or not.
375      * If unspecified, {@link MirroringService} is enabled.
376      */
377     public CentralDogmaBuilder mirroringEnabled(boolean mirroringEnabled) {
378         this.mirroringEnabled = mirroringEnabled;
379         return this;
380     }
381 
382     /**
383      * Sets the number of worker threads dedicated to mirroring between repositories.
384      * If unspecified, {@value #DEFAULT_NUM_MIRRORING_THREADS} threads are created at maximum.
385      */
386     public CentralDogmaBuilder numMirroringThreads(int numMirroringThreads) {
387         this.numMirroringThreads = numMirroringThreads;
388         return this;
389     }
390 
391     /**
392      * Sets the maximum allowed number of files in a mirrored tree.
393      * If unspecified, {@value #DEFAULT_MAX_NUM_FILES_PER_MIRROR} files are allowed at maximum.
394      */
395     public CentralDogmaBuilder maxNumFilesPerMirror(int maxNumFilesPerMirror) {
396         this.maxNumFilesPerMirror = maxNumFilesPerMirror;
397         return this;
398     }
399 
400     /**
401      * Sets the maximum allowed number of bytes in a mirrored tree.
402      * If unspecified, {@value #DEFAULT_MAX_NUM_BYTES_PER_MIRROR} bytes are allowed at maximum.
403      */
404     public CentralDogmaBuilder maxNumBytesPerMirror(long maxNumBytesPerMirror) {
405         this.maxNumBytesPerMirror = maxNumBytesPerMirror;
406         return this;
407     }
408 
409     /**
410      * Sets the graceful shutdown timeout. If unspecified, graceful shutdown is disabled.
411      */
412     public CentralDogmaBuilder gracefulShutdownTimeout(GracefulShutdownTimeout gracefulShutdownTimeout) {
413         this.gracefulShutdownTimeout = gracefulShutdownTimeout;
414         return this;
415     }
416 
417     /**
418      * Configures the replication.
419      * If unspecified or {@link ReplicationConfig#NONE} is specified, replication is disabled.
420      */
421     public CentralDogmaBuilder replication(ReplicationConfig replicationConfig) {
422         this.replicationConfig = requireNonNull(replicationConfig, "replicationConfig");
423         return this;
424     }
425 
426     /**
427      * Configures a format of an access log. It will work only if any logging framework is configured.
428      * Read the <a href="https://line.github.io/armeria/docs/server-access-log">Writing an access log</a>
429      * document for more information.
430      */
431     public CentralDogmaBuilder accessLogFormat(String accessLogFormat) {
432         this.accessLogFormat = requireNonNull(accessLogFormat, "accessLogFormat");
433         return this;
434     }
435 
436     /**
437      * Sets an {@link AuthProviderFactory} instance which is used to create a new {@link AuthProvider}.
438      */
439     public CentralDogmaBuilder authProviderFactory(AuthProviderFactory authProviderFactory) {
440         this.authProviderFactory = requireNonNull(authProviderFactory, "authProviderFactory");
441         return this;
442     }
443 
444     /**
445      * Adds administrators to the set.
446      */
447     public CentralDogmaBuilder administrators(String... administrators) {
448         requireNonNull(administrators, "administrators");
449         for (final String administrator : administrators) {
450             this.administrators.add(administrator);
451         }
452         return this;
453     }
454 
455     /**
456      * Adds administrators to the set.
457      */
458     public CentralDogmaBuilder administrators(Iterable<String> administrators) {
459         requireNonNull(administrators, "administrators");
460         this.administrators.addAll(administrators);
461         return this;
462     }
463 
464     /**
465      * Sets whether case-sensitive matching is performed when login names are compared.
466      */
467     public CentralDogmaBuilder caseSensitiveLoginNames(boolean caseSensitiveLoginNames) {
468         this.caseSensitiveLoginNames = caseSensitiveLoginNames;
469         return this;
470     }
471 
472     /**
473      * Sets the cache specification which determines the capacity and behavior of the cache for
474      * {@link Session} of the server. See {@link CaffeineSpec} for the syntax of the spec.
475      * If unspecified, the default cache spec of {@value AuthConfig#DEFAULT_SESSION_CACHE_SPEC}
476      * is used.
477      */
478     public CentralDogmaBuilder sessionCacheSpec(String sessionCacheSpec) {
479         this.sessionCacheSpec = validateCacheSpec(sessionCacheSpec);
480         return this;
481     }
482 
483     /**
484      * Sets the session timeout for administrative web application, in milliseconds.
485      * If unspecified, {@value AuthConfig#DEFAULT_SESSION_TIMEOUT_MILLIS} is used.
486      */
487     public CentralDogmaBuilder sessionTimeoutMillis(long sessionTimeoutMillis) {
488         this.sessionTimeoutMillis = sessionTimeoutMillis;
489         return this;
490     }
491 
492     /**
493      * Sets the session timeout for administrative web application.
494      * If unspecified, {@value AuthConfig#DEFAULT_SESSION_TIMEOUT_MILLIS} is used.
495      */
496     public CentralDogmaBuilder sessionTimeout(Duration sessionTimeout) {
497         return sessionTimeoutMillis(
498                 requireNonNull(sessionTimeout, "sessionTimeout").toMillis());
499     }
500 
501     /**
502      * Sets a schedule for validating sessions.
503      * If unspecified, {@value AuthConfig#DEFAULT_SESSION_VALIDATION_SCHEDULE} is used.
504      */
505     public CentralDogmaBuilder sessionValidationSchedule(String sessionValidationSchedule) {
506         this.sessionValidationSchedule =
507                 requireNonNull(sessionValidationSchedule, "sessionValidationSchedule");
508         return this;
509     }
510 
511     /**
512      * Sets an additional properties for an {@link AuthProviderFactory}.
513      */
514     public CentralDogmaBuilder authProviderProperties(Object authProviderProperties) {
515         this.authProviderProperties = requireNonNull(authProviderProperties, "authProviderProperties");
516         return this;
517     }
518 
519     /**
520      * Sets maximum allowed write requests per {@code timeWindowSeconds} for each {@link Repository}.
521      */
522     public CentralDogmaBuilder writeQuotaPerRepository(int writeQuota, int timeWindowSeconds) {
523         checkArgument(writeQuota > 0, "writeQuota: %s (expected: > 0)", writeQuota);
524         checkArgument(timeWindowSeconds > 0, "timeWindowSeconds: %s (expected: > 0)", timeWindowSeconds);
525         this.writeQuota = writeQuota;
526         this.timeWindowSeconds = timeWindowSeconds;
527         return this;
528     }
529 
530     /**
531      * Sets the {@link MeterRegistry} used to collect metrics.
532      */
533     public CentralDogmaBuilder meterRegistry(MeterRegistry meterRegistry) {
534         this.meterRegistry = requireNonNull(meterRegistry, "meterRegistry");
535         return this;
536     }
537 
538     /**
539      * Sets CORS related configurations.
540      */
541     public CentralDogmaBuilder cors(CorsConfig corsConfig) {
542         this.corsConfig = requireNonNull(corsConfig, "corsConfig");
543         return this;
544     }
545 
546     /**
547      * Returns a newly-created {@link CentralDogma} server.
548      */
549     public CentralDogma build() {
550         return new CentralDogma(buildConfig(), meterRegistry);
551     }
552 
553     private CentralDogmaConfig buildConfig() {
554         final List<ServerPort> ports = !this.ports.isEmpty() ? this.ports
555                                                              : Collections.singletonList(DEFAULT_PORT);
556         final Set<String> adminSet = administrators.build();
557         final AuthConfig authCfg;
558         if (authProviderFactory != null) {
559             authCfg = new AuthConfig(
560                     authProviderFactory, adminSet, caseSensitiveLoginNames,
561                     sessionCacheSpec, sessionTimeoutMillis, sessionValidationSchedule,
562                     authProviderProperties != null ? Jackson.valueToTree(authProviderProperties) : null);
563         } else {
564             authCfg = null;
565             logger.info("{} is not specified, so {} will not be configured.",
566                         AuthProviderFactory.class.getSimpleName(),
567                         AuthConfig.class.getSimpleName());
568         }
569 
570         final QuotaConfig quotaConfig = writeQuota > 0 ? new QuotaConfig(writeQuota, timeWindowSeconds) : null;
571 
572         return new CentralDogmaConfig(dataDir, ports, tls, trustedProxyAddresses, clientAddressSources,
573                                       numWorkers, maxNumConnections,
574                                       requestTimeoutMillis, idleTimeoutMillis, maxFrameLength,
575                                       numRepositoryWorkers, repositoryCacheSpec,
576                                       maxRemovedRepositoryAgeMillis, gracefulShutdownTimeout,
577                                       webAppEnabled, webAppTitle, mirroringEnabled, numMirroringThreads,
578                                       maxNumFilesPerMirror, maxNumBytesPerMirror, replicationConfig,
579                                       null, accessLogFormat, authCfg, quotaConfig,
580                                       corsConfig);
581     }
582 }