From 0b07aadf57f6ba8fd0dfd17c94999ccfe59315c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B5=AC=EA=B2=BD=EC=9D=BC?= <62415185+flex-gyeongil@users.noreply.github.com> Date: Mon, 23 Sep 2024 18:15:24 +0900 Subject: [PATCH] Spring Boot Centraldogma Loader --- build.gradle | 63 ++--- cloud-native/boot-loader/build.gradle | 36 +++ cloud-native/boot-loader/gradle.properties | 3 + .../armeria/server/saml/SamlEndpoint.java | 156 ++++++++++++ .../armeria/server/saml/package-info.java | 25 ++ .../centraldogma/config/AuthConfig.kt | 135 ++++++++++ .../config/GracefulShutdownTimeout.kt | 42 ++++ .../config/MirroringServicePluginConfig.kt | 58 +++++ .../centraldogma/config/QuotaConfig.kt | 70 ++++++ .../centraldogma/config/ServerPort.kt | 27 ++ .../linecorp/centraldogma/config/TlsConfig.kt | 32 +++ .../CustomConfigBindHandlerConfiguration.kt | 95 +++++++ .../server/auth/saml/AuthConfig.kt | 233 ++++++++++++++++++ .../auth/saml/SamlAuthProviderFactory.kt | 81 ++++++ .../spring/loader/CentralDogmaProperties.kt | 190 ++++++++++++++ .../spring/loader/CentralDogmaSpringLoader.kt | 108 ++++++++ .../src/main/resources/application.yaml | 28 +++ .../loader/ConfigurationPropertiesTest.kt | 133 ++++++++++ .../src/test/resources/full-config.yaml | 104 ++++++++ common/build.gradle | 1 + .../centraldogma/internal/Jackson.java | 4 +- dependencies.toml | 19 ++ .../it/ReplicationWriteQuotaTest.java | 13 +- .../it/TestAllReplicasPlugin.java | 8 +- .../centraldogma/it/WriteQuotaTestBase.java | 9 +- .../it/zoneleader/ZoneLeaderPluginTest.java | 10 +- .../auth/saml/SamlAuthProviderFactory.java | 8 +- .../server/auth/saml/SamlAuthSsoHandler.java | 6 +- .../server/auth/saml/SamlSession.java | 45 ++++ .../saml/SessionGroupTokenAuthorizer.java | 93 +++++++ .../AuthenticationDeserializationTest.java | 7 +- .../server/auth/shiro/ShiroAuthProvider.java | 6 +- .../auth/shiro/ShiroAuthProviderFactory.java | 10 +- .../server/AbstractCentralDogmaConfig.java | 110 +++++++++ .../centraldogma/server/CentralDogma.java | 113 +++++++-- .../server/CentralDogmaBuilder.java | 20 +- .../server/CentralDogmaConfig.java | 196 ++++----------- .../server/CentralDogmaConfigSpec.java | 210 ++++++++++++++++ .../centraldogma/server/CorsConfig.java | 17 +- .../centraldogma/server/CorsConfigSpec.java | 41 +++ .../server/GracefulShutdownTimeout.java | 12 +- .../server/GracefulShutdownTimeoutSpec.java | 32 +++ .../centraldogma/server/ManagementConfig.java | 20 +- .../server/ManagementConfigSpec.java | 50 ++++ .../centraldogma/server/PluginGroup.java | 35 +-- .../centraldogma/server/QuotaConfig.java | 19 +- .../centraldogma/server/QuotaConfigSpec.java | 39 +++ .../centraldogma/server/TlsConfig.java | 16 +- .../centraldogma/server/TlsConfigSpec.java | 46 ++++ .../centraldogma/server/ZoneConfig.java | 10 +- .../centraldogma/server/ZoneConfigSpec.java | 34 +++ .../server/ZooKeeperReplicationConfig.java | 129 ++-------- .../ZooKeeperReplicationConfigSpec.java | 162 ++++++++++++ .../server/ZooKeeperServerConfig.java | 46 ++-- .../server/ZooKeeperServerConfigSpec.java | 68 +++++ .../centraldogma/server/auth/AuthConfig.java | 74 +----- .../server/auth/AuthConfigSpec.java | 109 ++++++++ .../server/auth/AuthProviderParameters.java | 14 +- .../centraldogma/server/auth/Session.java | 16 +- .../server/command/CommandExecutor.java | 8 +- .../command/ForwardingCommandExecutor.java | 6 +- .../command/StandaloneCommandExecutor.java | 8 +- .../admin/auth/FileBasedSessionManager.java | 6 +- .../internal/api/MetadataApiService.java | 5 +- .../internal/api/MirroringServiceV1.java | 12 +- .../mirror/DefaultMirroringServicePlugin.java | 20 +- .../server/internal/mirror/MirrorRunner.java | 4 +- .../mirror/MirrorSchedulingService.java | 6 +- .../replication/ZooKeeperCommandExecutor.java | 39 +-- .../storage/PurgeSchedulingServicePlugin.java | 17 +- .../repository/DefaultMetaRepository.java | 6 +- .../mirror/MirroringServicePluginConfig.java | 19 +- .../MirroringServicePluginConfigSpec.java | 43 ++++ .../server/plugin/AllReplicasPlugin.java | 6 +- .../server/plugin/NoopPluginConfig.java | 32 +++ .../centraldogma/server/plugin/Plugin.java | 12 +- .../server/plugin/PluginContext.java | 9 +- .../server/plugin/PluginInitContext.java | 6 +- .../storage/repository/MetaRepository.java | 4 +- .../server/CentralDogmaConfigTest.java | 18 +- .../server/ConfigDeserializationTest.java | 6 +- .../centraldogma/server/PluginGroupTest.java | 25 +- .../ZooKeeperReplicationConfigTest.java | 8 +- .../auth/ReplicatedLoginAndLogoutTest.java | 5 +- .../CommandExecutorStatusManagerTest.java | 4 +- .../MirrorSchedulingServicePluginTest.java | 6 +- .../internal/replication/ClusterBuilder.java | 5 +- .../server/internal/replication/Replica.java | 12 +- .../StartStopWithoutInitialQuorumTest.java | 5 +- .../ZooKeeperCommandExecutorTest.java | 8 +- .../server/plugin/AbstractNoopPlugin.java | 7 +- .../plugin/NoopPluginForAllReplicas.java | 12 +- .../server/plugin/NoopPluginForLeader.java | 12 +- settings.gradle | 5 +- site/src/sphinx/auth.rst | 2 +- .../CentralDogmaReplicationExtension.java | 12 +- .../internal/CentralDogmaRuleDelegate.java | 9 +- .../xds/internal/ControlPlanePlugin.java | 5 +- .../XdsKubernetesEndpointFetchingPlugin.java | 10 +- .../ControlPlanePluginConfigTest.java | 2 +- .../internal/CreatingInternalGroupPlugin.java | 6 +- 101 files changed, 3223 insertions(+), 725 deletions(-) create mode 100644 cloud-native/boot-loader/build.gradle create mode 100644 cloud-native/boot-loader/gradle.properties create mode 100644 cloud-native/boot-loader/src/main/java/com/linecorp/armeria/server/saml/SamlEndpoint.java create mode 100644 cloud-native/boot-loader/src/main/java/com/linecorp/armeria/server/saml/package-info.java create mode 100644 cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/AuthConfig.kt create mode 100644 cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/GracefulShutdownTimeout.kt create mode 100644 cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/MirroringServicePluginConfig.kt create mode 100644 cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/QuotaConfig.kt create mode 100644 cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/ServerPort.kt create mode 100644 cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/TlsConfig.kt create mode 100644 cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/server/CustomConfigBindHandlerConfiguration.kt create mode 100644 cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/server/auth/saml/AuthConfig.kt create mode 100644 cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/server/auth/saml/SamlAuthProviderFactory.kt create mode 100644 cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/spring/loader/CentralDogmaProperties.kt create mode 100644 cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/spring/loader/CentralDogmaSpringLoader.kt create mode 100644 cloud-native/boot-loader/src/main/resources/application.yaml create mode 100644 cloud-native/boot-loader/src/test/kotlin/com/linecorp/centraldogma/spring/loader/ConfigurationPropertiesTest.kt create mode 100644 cloud-native/boot-loader/src/test/resources/full-config.yaml create mode 100644 server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SamlSession.java create mode 100644 server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SessionGroupTokenAuthorizer.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/AbstractCentralDogmaConfig.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/CentralDogmaConfigSpec.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/CorsConfigSpec.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/GracefulShutdownTimeoutSpec.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/ManagementConfigSpec.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/QuotaConfigSpec.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/TlsConfigSpec.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/ZoneConfigSpec.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfigSpec.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperServerConfigSpec.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/auth/AuthConfigSpec.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/mirror/MirroringServicePluginConfigSpec.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/plugin/NoopPluginConfig.java diff --git a/build.gradle b/build.gradle index e8e111470..7ee1f6c56 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,9 @@ buildscript { plugins { alias libs.plugins.nexus.publish alias libs.plugins.osdetector apply false + alias libs.plugins.kotlin apply false + alias libs.plugins.ktlint apply false + alias libs.plugins.jib apply false } allprojects { @@ -78,28 +81,11 @@ configure(projectsWithFlags('java')) { compileOnly libs.jetty.alpn.api // Logging - if (project.name.startsWith("java-spring-boot3")) { - implementation libs.slf4j2.api - testImplementation libs.slf4j2.jul.to.slf4j - testImplementation libs.slf4j2.jcl.over.slf4j - testImplementation libs.slf4j2.log4j.over.slf4j - testRuntimeOnly libs.logback15 - configurations.configureEach { - resolutionStrategy { - force libs.slf4j2.api.get() - force libs.slf4j2.jul.to.slf4j.get() - force libs.slf4j2.jcl.over.slf4j.get() - force libs.slf4j2.log4j.over.slf4j.get() - force libs.logback15.get() - } - } - } else { - implementation libs.slf4j1.api - testImplementation libs.slf4j1.jul.to.slf4j - testImplementation libs.slf4j1.jcl.over.slf4j - testImplementation libs.slf4j1.log4j.over.slf4j - testRuntimeOnly libs.logback12 - } + implementation libs.slf4j1.api + testImplementation libs.slf4j1.jul.to.slf4j + testImplementation libs.slf4j1.jcl.over.slf4j + testImplementation libs.slf4j1.log4j.over.slf4j + testRuntimeOnly libs.logback12 // Test-time dependencies testImplementation libs.json.unit.fluent @@ -115,12 +101,9 @@ configure(projectsWithFlags('java')) { testRuntimeOnly libs.junit5.vintage.engine } - // Target Java 8 except for spring-boot3 - if (!project.name.startsWith("java-spring-boot3")) { - tasks.withType(JavaCompile) { - if (JavaVersion.current() >= JavaVersion.VERSION_1_9) { - options.release = 8 - } + tasks.withType(JavaCompile) { + if (JavaVersion.current() >= JavaVersion.VERSION_1_9) { + options.release = 8 } } @@ -218,6 +201,30 @@ class TestsReportTask extends DefaultTask { } } +configure(projectsWithFlags('spring-boot3')) { + + tasks.withType(JavaCompile) { + options.release = 17 + } + + dependencies { + implementation libs.slf4j2.api + testImplementation libs.slf4j2.jul.to.slf4j + testImplementation libs.slf4j2.jcl.over.slf4j + testImplementation libs.slf4j2.log4j.over.slf4j + testRuntimeOnly libs.logback15 + configurations.configureEach { + resolutionStrategy { + force libs.slf4j2.api.get() + force libs.slf4j2.jul.to.slf4j.get() + force libs.slf4j2.jcl.over.slf4j.get() + force libs.slf4j2.log4j.over.slf4j.get() + force libs.logback15.get() + } + } + } +} + // Configure the Javadoc tasks of all projects. allprojects { tasks.withType(Javadoc) { diff --git a/cloud-native/boot-loader/build.gradle b/cloud-native/boot-loader/build.gradle new file mode 100644 index 000000000..f080a0a89 --- /dev/null +++ b/cloud-native/boot-loader/build.gradle @@ -0,0 +1,36 @@ +plugins { + alias libs.plugins.spring.boot + alias libs.plugins.kotlin.spring + alias libs.plugins.jib +} + +dependencies { + implementation libs.spring.boot3.starter.web + implementation libs.spring.boot3.starter.actuator + implementation libs.jackson.kotlin + implementation project(":server") + implementation project(":server-auth:saml") + implementation project(':server-mirror-git') + + implementation(libs.armeria.saml) { + exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on' + } + + implementation 'org.opensaml:opensaml-security-impl:3.4.6' + implementation 'org.opensaml:opensaml-saml-impl:3.4.6' + + testImplementation libs.spring.boot3.starter.test + + testImplementation("com.willowtreeapps.assertk:assertk:0.28.1") +} + +jib { + from { + image = 'eclipse-temurin:21-jre-jammy' + } + + container { + creationTime = 'USE_CURRENT_TIMESTAMP' + ports = ['8080', '36462'] + } +} diff --git a/cloud-native/boot-loader/gradle.properties b/cloud-native/boot-loader/gradle.properties new file mode 100644 index 000000000..cae212e42 --- /dev/null +++ b/cloud-native/boot-loader/gradle.properties @@ -0,0 +1,3 @@ +javaSourceCompatibility=17 +javaTargetCompatibility=17 +minimumJavaVersion=17 diff --git a/cloud-native/boot-loader/src/main/java/com/linecorp/armeria/server/saml/SamlEndpoint.java b/cloud-native/boot-loader/src/main/java/com/linecorp/armeria/server/saml/SamlEndpoint.java new file mode 100644 index 000000000..b8992a21e --- /dev/null +++ b/cloud-native/boot-loader/src/main/java/com/linecorp/armeria/server/saml/SamlEndpoint.java @@ -0,0 +1,156 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.linecorp.armeria.server.saml; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.linecorp.armeria.server.saml.SamlPortConfig.validatePort; +import static java.util.Objects.requireNonNull; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.springframework.web.util.UriComponentsBuilder; + +import com.google.common.base.MoreObjects; + +import com.linecorp.armeria.common.annotation.Nullable; +import com.linecorp.armeria.common.util.Exceptions; + +/** + * A SAML service URL and its binding protocol. + */ +public final class SamlEndpoint { + /** + * Creates a {@link SamlEndpoint} of the specified {@code uri} and the HTTP Redirect binding protocol. + */ + public static SamlEndpoint ofHttpRedirect(String uri) { + requireNonNull(uri, "uri"); + try { + return ofHttpRedirect(new URI(uri)); + } catch (URISyntaxException e) { + return Exceptions.throwUnsafely(e); + } + } + + /** + * Creates a {@link SamlEndpoint} of the specified {@code uri} and the HTTP Redirect binding protocol. + */ + public static SamlEndpoint ofHttpRedirect(URI uri) { + requireNonNull(uri, "uri"); + return new SamlEndpoint(uri, SamlBindingProtocol.HTTP_REDIRECT); + } + + /** + * Creates a {@link SamlEndpoint} of the specified {@code uri} and the HTTP POST binding protocol. + */ + public static SamlEndpoint ofHttpPost(String uri) { + requireNonNull(uri, "uri"); + try { + return ofHttpPost(new URI(uri)); + } catch (URISyntaxException e) { + return Exceptions.throwUnsafely(e); + } + } + + /** + * Creates a {@link SamlEndpoint} of the specified {@code uri} and the HTTP POST binding protocol. + */ + public static SamlEndpoint ofHttpPost(URI uri) { + requireNonNull(uri, "uri"); + return new SamlEndpoint(uri, SamlBindingProtocol.HTTP_POST); + } + + private final URI uri; + private final SamlBindingProtocol bindingProtocol; + private final String uriAsString; + + private SamlEndpoint(URI uri, SamlBindingProtocol bindingProtocol) { + this.uri = uri; + this.bindingProtocol = bindingProtocol; + uriAsString = uri.toString(); + } + + /** + * Returns a {@link URI} of this endpoint. + */ + public URI uri() { + return uri; + } + + /** + * Returns a {@link URI} of this endpoint as a string. + */ + public String toUriString() { + return uriAsString; + } + + /** + * Returns a {@link URI} of this endpoint as a string. The omitted values in the {@link URI} will be + * replaced with the specified default values, such as {@code defaultScheme}, {@code defaultHostname} + * and {@code defaultPort}. + */ + String toUriString(String defaultScheme, String defaultHostname, int defaultPort) { + requireNonNull(defaultScheme, "defaultScheme"); + requireNonNull(defaultHostname, "defaultHostname"); + validatePort(defaultPort); + + final String scheme = firstNonNull(uri.getScheme(), defaultScheme); + final UriComponentsBuilder builder = UriComponentsBuilder.newInstance() + .scheme(scheme) + .host(firstNonNull(uri.getHost(), defaultHostname)); + + // FIXME Should be applied to armeria upstream!! + final int port = uri.getPort() > 0 ? uri.getPort() : defaultPort; + if (!(port == 80 && "http".equals(scheme) || port == 443 && "https".equals(scheme))) { + builder.port(uri.getPort() > 0 ? uri.getPort() : defaultPort); + } + return builder + .path(uri.getPath()) + .toUriString(); + } + + /** + * Returns a {@link SamlBindingProtocol} of this endpoint. + */ + public SamlBindingProtocol bindingProtocol() { + return bindingProtocol; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SamlEndpoint)) { + return false; + } + final SamlEndpoint that = (SamlEndpoint) o; + return uri().equals(that.uri()) && bindingProtocol() == that.bindingProtocol(); + } + + @Override + public int hashCode() { + return uri().hashCode() * 31 + bindingProtocol().hashCode(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("uri", uri) + .add("bindingProtocol", bindingProtocol) + .toString(); + } +} diff --git a/cloud-native/boot-loader/src/main/java/com/linecorp/armeria/server/saml/package-info.java b/cloud-native/boot-loader/src/main/java/com/linecorp/armeria/server/saml/package-info.java new file mode 100644 index 000000000..251d88008 --- /dev/null +++ b/cloud-native/boot-loader/src/main/java/com/linecorp/armeria/server/saml/package-info.java @@ -0,0 +1,25 @@ + +/* + * Copyright 2018 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * SAML-based single sign-on + * service. + */ +@NonNullByDefault +package com.linecorp.armeria.server.saml; + +import com.linecorp.armeria.common.annotation.NonNullByDefault; diff --git a/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/AuthConfig.kt b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/AuthConfig.kt new file mode 100644 index 000000000..272769660 --- /dev/null +++ b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/AuthConfig.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.config + +import com.google.common.base.Ascii +import com.linecorp.centraldogma.internal.Jackson +import com.linecorp.centraldogma.server.ReplicationConfig +import com.linecorp.centraldogma.server.ReplicationMethod +import com.linecorp.centraldogma.server.ZooKeeperReplicationConfigSpec +import com.linecorp.centraldogma.server.ZooKeeperServerConfigSpec +import com.linecorp.centraldogma.server.auth.AuthConfigSpec +import com.linecorp.centraldogma.server.auth.AuthProviderFactory +import org.springframework.validation.annotation.Validated +import java.util.function.Function + +open class ReplicationConfigImpl( + private val method: ReplicationMethod, +) : ReplicationConfig { + override fun method(): ReplicationMethod = method +} + +data class NoneReplicationConfig(private val method: ReplicationMethod) : ReplicationConfigImpl(method) + +@Validated +data class ZooKeeperServerConfig( + private val host: String, + private val quorumPort: Int, + private val electionPort: Int, + private val clientPort: Int, + private val groupId: Int?, + private val weight: Int = 1, +) : ZooKeeperServerConfigSpec { + init { + check(weight >= 0) { "weight: $weight (expected: >= 0)" } + } + + override fun host(): String = host + + override fun quorumPort(): Int = ZooKeeperServerConfigSpec.validatePort(quorumPort, "quorumPort") + + override fun electionPort(): Int = ZooKeeperServerConfigSpec.validatePort(electionPort, "electionPort") + + override fun clientPort(): Int = clientPort + + override fun groupId(): Int? = groupId + + override fun weight(): Int = weight +} + +data class ZooKeeperReplicationConfig( + private val method: ReplicationMethod, + private val servers: Map, + private val secret: String, + private val additionalProperties: Map = emptyMap(), + private val timeoutMillis: Int = ZooKeeperReplicationConfigSpec.DEFAULT_TIMEOUT_MILLIS, + private val numWorkers: Int = ZooKeeperReplicationConfigSpec.DEFAULT_NUM_WORKERS, + private val maxLogCount: Int = ZooKeeperReplicationConfigSpec.DEFAULT_MAX_LOG_COUNT, + private val minLogAgeMillis: Long = ZooKeeperReplicationConfigSpec.DEFAULT_MIN_LOG_AGE_MILLIS, +) : ReplicationConfigImpl(method), ZooKeeperReplicationConfigSpec { + private val serverId: Int = ZooKeeperReplicationConfigSpec.findServerId(servers) + + override fun serverId(): Int = serverId + + override fun serverConfig(): ZooKeeperServerConfigSpec = servers[serverId]!! + + override fun servers(): Map = servers + + override fun secret(): String = secret + + override fun additionalProperties(): Map = additionalProperties + + override fun timeoutMillis(): Int = timeoutMillis + + override fun numWorkers(): Int = numWorkers + + override fun maxLogCount(): Int = maxLogCount + + override fun minLogAgeMillis(): Long = minLogAgeMillis +} + +data class AuthConfig( + private val factoryClassName: String, + private val administrators: Set = emptySet(), + private val caseSensitiveLoginNames: Boolean = true, + private val sessionCacheSpec: String = AuthConfigSpec.DEFAULT_SESSION_CACHE_SPEC, + private val sessionTimeoutMillis: Long = AuthConfigSpec.DEFAULT_SESSION_TIMEOUT_MILLIS, + private val sessionValidationSchedule: String = AuthConfigSpec.DEFAULT_SESSION_VALIDATION_SCHEDULE, + private val properties: Map?, +) : AuthConfigSpec { + private val factory = + AuthConfigSpec::class.java.classLoader + .loadClass(factoryClassName) + .getDeclaredConstructor() + .newInstance() as AuthProviderFactory + + override fun factory(): AuthProviderFactory = factory + + override fun factoryClassName(): String = factoryClassName + + override fun systemAdministrators(): Set = administrators + + override fun caseSensitiveLoginNames(): Boolean = caseSensitiveLoginNames + + override fun sessionCacheSpec(): String = sessionCacheSpec + + override fun sessionTimeoutMillis(): Long = sessionTimeoutMillis + + override fun sessionValidationSchedule(): String = sessionValidationSchedule + + override fun properties(clazz: Class): T? = + properties?.let { + Jackson.convertValue(it, clazz) + } + + override fun loginNameNormalizer(): Function = + if (caseSensitiveLoginNames) { + Function.identity() + } else { + Function(Ascii::toLowerCase) + } +} diff --git a/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/GracefulShutdownTimeout.kt b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/GracefulShutdownTimeout.kt new file mode 100644 index 000000000..76272594c --- /dev/null +++ b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/GracefulShutdownTimeout.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.config + +import com.google.common.base.Preconditions.checkArgument +import com.linecorp.centraldogma.server.GracefulShutdownTimeoutSpec + +data class GracefulShutdownTimeout( + private val quietPeriodMillis: Long, + private val timeoutMillis: Long, +) : GracefulShutdownTimeoutSpec { + init { + checkArgument( + quietPeriodMillis >= 0, + "quietPeriodMillis: %s (expected: >= 0)", + quietPeriodMillis, + ) + checkArgument( + timeoutMillis >= 0, + "timeoutMillis: %s (expected: >= 0)", + timeoutMillis, + ) + } + + override fun quietPeriodMillis(): Long = quietPeriodMillis + + override fun timeoutMillis(): Long = timeoutMillis +} diff --git a/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/MirroringServicePluginConfig.kt b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/MirroringServicePluginConfig.kt new file mode 100644 index 000000000..d69e8bdfd --- /dev/null +++ b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/MirroringServicePluginConfig.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.config + +import com.google.common.base.Preconditions.checkArgument +import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfig +import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfigSpec +import com.linecorp.centraldogma.server.plugin.AbstractPluginConfig + +open class PluginConfigBase( + enabled: Boolean, + val type: String, +) : AbstractPluginConfig(enabled) + +data class MirroringServicePluginConfig( + private val enabled: Boolean, + private val numMirroringThreads: Int = MirroringServicePluginConfigSpec.DEFAULT_NUM_MIRRORING_THREADS, + private val maxNumFilesPerMirror: Int = MirroringServicePluginConfigSpec.DEFAULT_MAX_NUM_FILES_PER_MIRROR, + private val maxNumBytesPerMirror: Long = MirroringServicePluginConfigSpec.DEFAULT_MAX_NUM_BYTES_PER_MIRROR, +) : PluginConfigBase(enabled, MirroringServicePluginConfig::class.java.name), MirroringServicePluginConfigSpec { + init { + checkArgument( + numMirroringThreads > 0, + "numMirroringThreads: %s (expected: > 0)", + numMirroringThreads, + ) + checkArgument( + maxNumFilesPerMirror > 0, + "maxNumFilesPerMirror: %s (expected: > 0)", + maxNumFilesPerMirror, + ) + checkArgument( + maxNumBytesPerMirror > 0, + "maxNumBytesPerMirror: %s (expected: > 0)", + maxNumBytesPerMirror, + ) + } + + override fun numMirroringThreads(): Int = numMirroringThreads + + override fun maxNumFilesPerMirror(): Int = maxNumFilesPerMirror + + override fun maxNumBytesPerMirror(): Long = maxNumBytesPerMirror +} diff --git a/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/QuotaConfig.kt b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/QuotaConfig.kt new file mode 100644 index 000000000..21d465828 --- /dev/null +++ b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/QuotaConfig.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.config + +import com.google.common.base.Preconditions.checkArgument +import com.linecorp.armeria.common.SessionProtocol +import com.linecorp.centraldogma.server.CorsConfigSpec +import com.linecorp.centraldogma.server.ManagementConfigSpec +import com.linecorp.centraldogma.server.QuotaConfigSpec + +data class QuotaConfig( + private val requestQuota: Int, + private val timeWindowSeconds: Int, +) : QuotaConfigSpec { + override fun timeWindowSeconds(): Int = timeWindowSeconds + + override fun requestQuota(): Int = requestQuota +} + +data class CorsConfig( + private val allowedOrigins: List, + private val maxAgeSecond: Int, +) : CorsConfigSpec { + override fun allowedOrigins(): List = allowedOrigins + + override fun maxAgeSeconds(): Int = maxAgeSecond +} + +data class ManagementConfig( + private val protocol: String = ManagementConfigSpec.DEFAULT_PROTOCOL, + private val address: String?, + private val port: Int, + private val path: String = ManagementConfigSpec.DEFAULT_PATH, +) : ManagementConfigSpec { + private val sessionProtocol = + SessionProtocol.of(protocol).also { + checkArgument( + it != SessionProtocol.PROXY, + "protocol: %s (expected: one of %s)", + protocol, + SessionProtocol.httpAndHttpsValues(), + ) + } + + init { + checkArgument(port in 0..65535, "management.port: %s (expected: 0-65535)", port) + } + + override fun protocol(): SessionProtocol = sessionProtocol + + override fun address(): String? = address + + override fun port(): Int = port + + override fun path(): String = path +} diff --git a/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/ServerPort.kt b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/ServerPort.kt new file mode 100644 index 000000000..8d34632e7 --- /dev/null +++ b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/ServerPort.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.config + +data class InetSocketAddress( + val host: String = "*", + val port: Int, +) + +data class ServerPort( + val localAddress: InetSocketAddress, + val protocols: List, +) diff --git a/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/TlsConfig.kt b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/TlsConfig.kt new file mode 100644 index 000000000..8e66a97b4 --- /dev/null +++ b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/config/TlsConfig.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.config + +import com.linecorp.centraldogma.server.TlsConfigSpec +import java.io.InputStream + +data class TlsConfig( + private val keyCertChain: String, + private val key: String, + private val password: String?, +) : TlsConfigSpec { + override fun keyCertChainInputStream(): InputStream = keyCertChain.byteInputStream() + + override fun keyInputStream(): InputStream = key.byteInputStream() + + override fun keyPassword(): String? = password +} diff --git a/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/server/CustomConfigBindHandlerConfiguration.kt b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/server/CustomConfigBindHandlerConfiguration.kt new file mode 100644 index 000000000..80ebeef21 --- /dev/null +++ b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/server/CustomConfigBindHandlerConfiguration.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server + +import com.linecorp.centraldogma.config.MirroringServicePluginConfig +import com.linecorp.centraldogma.config.NoneReplicationConfig +import com.linecorp.centraldogma.config.PluginConfigBase +import com.linecorp.centraldogma.config.ReplicationConfigImpl +import com.linecorp.centraldogma.config.ZooKeeperReplicationConfig +import org.springframework.boot.context.properties.ConfigurationPropertiesBindHandlerAdvisor +import org.springframework.boot.context.properties.bind.AbstractBindHandler +import org.springframework.boot.context.properties.bind.BindContext +import org.springframework.boot.context.properties.bind.BindHandler +import org.springframework.boot.context.properties.bind.Bindable +import org.springframework.boot.context.properties.source.ConfigurationPropertyName +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Import +import kotlin.reflect.KClass +import kotlin.reflect.full.isSuperclassOf +import kotlin.reflect.jvm.jvmName + +abstract class PolymorphicBindHandler( + parent: BindHandler, + private val kClass: KClass, +) : AbstractBindHandler(parent) { + override fun onSuccess( + name: ConfigurationPropertyName?, + target: Bindable<*>?, + context: BindContext, + result: Any?, + ): Any { + if (result == null || !kClass.isSuperclassOf(result::class)) { + return super.onSuccess(name, target, context, result) + } + + @Suppress("UNCHECKED_CAST") + val bindType = decideBindType(result as BASE_TYPE) + return context.binder.bind(name, Bindable.of(bindType.java)).get() + } + + abstract fun decideBindType(result: BASE_TYPE): KClass +} + +class ReplicationMethodBindHandlerAdvisor : ConfigurationPropertiesBindHandlerAdvisor { + override fun apply(bindHandler: BindHandler): BindHandler { + return object : PolymorphicBindHandler( + bindHandler, + ReplicationConfigImpl::class, + ) { + override fun decideBindType(result: ReplicationConfigImpl): KClass = + when (result.method()) { + ReplicationMethod.NONE -> NoneReplicationConfig::class + ReplicationMethod.ZOOKEEPER -> ZooKeeperReplicationConfig::class + } + } + } +} + +class PluginConfigBindHandlerAdvisor : ConfigurationPropertiesBindHandlerAdvisor { + override fun apply(bindHandler: BindHandler): BindHandler { + return object : PolymorphicBindHandler( + bindHandler, + PluginConfigBase::class, + ) { + private val knownPluginClasses = + listOf(MirroringServicePluginConfig::class) + .associateBy { it.jvmName } + + override fun decideBindType(result: PluginConfigBase): KClass { + return knownPluginClasses[result.type] ?: error("unknown plugin type ${result.type}") + } + } + } +} + +@Import( + ReplicationMethodBindHandlerAdvisor::class, + PluginConfigBindHandlerAdvisor::class, +) +@Configuration +class CustomConfigBindHandlerConfiguration diff --git a/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/server/auth/saml/AuthConfig.kt b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/server/auth/saml/AuthConfig.kt new file mode 100644 index 000000000..1bb47ea0a --- /dev/null +++ b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/server/auth/saml/AuthConfig.kt @@ -0,0 +1,233 @@ +package com.linecorp.centraldogma.server.auth.saml + +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import com.google.common.base.Strings +import com.linecorp.armeria.common.AggregatedHttpRequest +import com.linecorp.armeria.common.HttpRequest +import com.linecorp.armeria.common.HttpResponse +import com.linecorp.armeria.common.HttpStatus +import com.linecorp.armeria.common.MediaType +import com.linecorp.armeria.server.ServiceRequestContext +import com.linecorp.armeria.server.saml.InvalidSamlRequestException +import com.linecorp.armeria.server.saml.SamlBindingProtocol +import com.linecorp.armeria.server.saml.SamlEndpoint +import com.linecorp.armeria.server.saml.SamlIdentityProviderConfig +import com.linecorp.armeria.server.saml.SamlNameIdFormat +import com.linecorp.armeria.server.saml.SamlSingleSignOnHandler +import com.linecorp.centraldogma.server.auth.Session +import com.linecorp.centraldogma.server.internal.api.HttpApiUtil +import io.netty.handler.codec.http.QueryStringDecoder +import org.opensaml.core.xml.schema.XSString +import org.opensaml.messaging.context.MessageContext +import org.opensaml.saml.common.messaging.context.SAMLBindingContext +import org.opensaml.saml.saml2.core.Attribute +import org.opensaml.saml.saml2.core.AuthnRequest +import org.opensaml.saml.saml2.core.Response +import org.opensaml.xmlsec.signature.support.SignatureConstants +import java.io.UnsupportedEncodingException +import java.net.URLEncoder +import java.time.Duration +import java.util.Objects.requireNonNull +import java.util.Optional +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionStage + +data class Idp( + val entityId: String, + val uri: String, + val binding: SamlBindingProtocol = SamlBindingProtocol.HTTP_POST, + val signingKey: String, + val encryptionKey: String, + val subjectLoginNameIdFormat: String?, + val attributeLoginName: String?, + val attributeGroupName: String?, +) { + @JsonCreator + constructor( + @JsonProperty("entityId") entityId: String, + @JsonProperty("uri") uri: String, + @JsonProperty("binding") binding: String?, + @JsonProperty("signingKey") signingKey: String?, + @JsonProperty("encryptionKey") encryptionKey: String?, + @JsonProperty("subjectLoginNameIdFormat") subjectLoginNameIdFormat: String?, + @JsonProperty("attributeLoginName") attributeLoginName: String?, + @JsonProperty("attributeGroupName") attributeGroupName: String?, + ) : this( + entityId = entityId, + uri = uri, + binding = binding?.let(SamlBindingProtocol::valueOf) ?: SamlBindingProtocol.HTTP_POST, + signingKey = listOfNotNull(signingKey, entityId).first(), + encryptionKey = listOfNotNull(encryptionKey, entityId).first(), + subjectLoginNameIdFormat = + if (subjectLoginNameIdFormat == null && attributeLoginName == null) { + subjectLoginNameIdFormat + } else { + SamlNameIdFormat.EMAIL.urn() + }, + attributeLoginName = + if (subjectLoginNameIdFormat == null && attributeLoginName == null) { + null + } else { + attributeLoginName + }, + attributeGroupName, + ) + + val endpoint = + when (binding) { + SamlBindingProtocol.HTTP_POST -> SamlEndpoint.ofHttpPost(uri) + SamlBindingProtocol.HTTP_REDIRECT -> SamlEndpoint.ofHttpRedirect(uri) + else -> throw IllegalStateException("Failed to get an endpoint of the Idp: $entityId") + } +} + +data class SamlAuthProperties( + val entityId: String, + val hostname: String, + val signingKey: String = "signing", + val encryptionKey: String = "encryption", + val samlMetadata: String, + val signatureAlgorithm: String = SignatureConstants.ALGO_ID_SIGNATURE_RSA, + val idp: Idp, +) + +class SamlAuthSsoHandler( + private val sessionIdGenerator: () -> String, + private val loginSessionPropagator: (Session) -> CompletableFuture, + private val sessionValidDuration: Duration, + private val loginNameNormalizer: (String) -> String, + private val subjectLoginNameIdFormat: String?, + private val attributeLoginName: String?, + private val attributeGroupName: String?, +) : SamlSingleSignOnHandler { + override fun beforeInitiatingSso( + ctx: ServiceRequestContext, + req: HttpRequest, + message: MessageContext, + idpConfig: SamlIdentityProviderConfig, + ): CompletionStage { + val decoder = QueryStringDecoder(req.path(), true) + val ref = decoder.parameters()["ref"] + if (ref.isNullOrEmpty()) return CompletableFuture.completedFuture(null) + + val relayState = ref[0] + if (idpConfig.ssoEndpoint().bindingProtocol() == SamlBindingProtocol.HTTP_REDIRECT && + relayState.length > 80 + ) { + return CompletableFuture.completedFuture(null) + } + + message.getSubcontext(SAMLBindingContext::class.java, true).apply { + checkNotNull(this) { SAMLBindingContext::class.java.name } + this.relayState = relayState + } + + return CompletableFuture.completedFuture(null) + } + + override fun loginSucceeded( + ctx: ServiceRequestContext, + req: AggregatedHttpRequest, + message: MessageContext, + sessionIndex: String?, + relayState: String?, + ): HttpResponse { + val response = + requireNonNull(message, "message").message!! + val username = + Optional.ofNullable(findLoginNameFromSubjects(response)) + .orElseGet { findLoginNameFromAttributes(response) } + if (Strings.isNullOrEmpty(username)) { + return loginFailed( + ctx, + req, + message, + IllegalStateException("Cannot get a username from the response"), + ) + } + + val sessionId: String = sessionIdGenerator() + val samlSession = SamlSession(findGroupsFromAttributes(response)) + val session = + Session(sessionId, loginNameNormalizer(username), sessionValidDuration, samlSession) + val redirectionScript = + if (!Strings.isNullOrEmpty(relayState)) { + try { + "window.location.href='/#${URLEncoder.encode(relayState, "UTF-8")}'" + } catch (e: UnsupportedEncodingException) { + // Should never reach here. + throw Error() + } + } else { + "window.location.href='/'" + } + return HttpResponse.of( + loginSessionPropagator(session).thenApply { _ -> + HttpResponse.of( + HttpStatus.OK, + MediaType.HTML_UTF_8, + HtmlUtil.getHtmlWithOnload( + "localStorage.setItem('sessionId','$sessionId')", + redirectionScript, + ), + ) + }, + ) + } + + private fun findLoginNameFromSubjects(response: Response): String? { + if (subjectLoginNameIdFormat.isNullOrEmpty()) return null + return response.assertions + .map { it.subject.nameID } + .firstOrNull { it.format == subjectLoginNameIdFormat } + ?.value + } + + private fun findLoginNameFromAttributes(response: Response): String? { + if (attributeLoginName.isNullOrEmpty()) return null + return response.assertions + .asSequence() + .flatMap { it.attributeStatements.asSequence() } + .flatMap { it.attributes.asSequence() } + .filter { it.name == attributeLoginName } + .firstOrNull() + ?.let { attr: Attribute -> + val v = attr.attributeValues[0] + if (v is XSString) { + v.value + } else { + null + } + } + } + + private fun findGroupsFromAttributes(response: Response): List { + if (attributeGroupName.isNullOrEmpty()) return emptyList() + + return response.assertions + .asSequence() + .flatMap { it.attributeStatements } + .flatMap { it.attributes } + .filter { it.name == attributeGroupName } + .flatMap { it.attributeValues } + .filterIsInstance() + .mapNotNull { it.value } + .toList() + } + + override fun loginFailed( + ctx: ServiceRequestContext, + req: AggregatedHttpRequest, + message: MessageContext?, + cause: Throwable, + ): HttpResponse { + val status = + if (cause is InvalidSamlRequestException) { + HttpStatus.BAD_REQUEST + } else { + HttpStatus.INTERNAL_SERVER_ERROR + } + return HttpApiUtil.newResponse(ctx, status, cause) + } +} diff --git a/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/server/auth/saml/SamlAuthProviderFactory.kt b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/server/auth/saml/SamlAuthProviderFactory.kt new file mode 100644 index 000000000..d27f56e5c --- /dev/null +++ b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/server/auth/saml/SamlAuthProviderFactory.kt @@ -0,0 +1,81 @@ +package com.linecorp.centraldogma.server.auth.saml + +import com.google.common.annotations.VisibleForTesting +import com.linecorp.armeria.common.SessionProtocol +import com.linecorp.armeria.server.ServerPort +import com.linecorp.armeria.server.saml.SamlServiceProvider +import com.linecorp.centraldogma.server.auth.AuthProvider +import com.linecorp.centraldogma.server.auth.AuthProviderFactory +import com.linecorp.centraldogma.server.auth.AuthProviderParameters +import org.opensaml.security.credential.CredentialResolver +import org.opensaml.security.credential.UsageType +import org.opensaml.security.credential.impl.StaticCredentialResolver +import org.opensaml.security.x509.BasicX509Credential +import java.security.KeyPairGenerator +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import java.time.Duration + +class SamlAuthProviderFactory : AuthProviderFactory { + override fun create(parameters: AuthProviderParameters): AuthProvider { + val properties = + checkNotNull( + parameters.authConfig().properties(SamlAuthProperties::class.java), + ) { "authentication properties are not specified" } + + val idp = properties.idp + + val samlServiceProvider = + SamlServiceProvider.builder() + .entityId(properties.entityId) + .hostname(properties.hostname) + .schemeAndPort(ServerPort(443, SessionProtocol.HTTPS)) + .authorizer(parameters.authorizer()) + .ssoHandler( + SamlAuthSsoHandler( + sessionIdGenerator = { parameters.sessionIdGenerator().get() }, + loginSessionPropagator = { parameters.loginSessionPropagator().apply(it) }, + sessionValidDuration = + Duration.ofMillis( + parameters.authConfig().sessionTimeoutMillis(), + ), + loginNameNormalizer = { parameters.authConfig().loginNameNormalizer().apply(it) }, + subjectLoginNameIdFormat = idp.subjectLoginNameIdFormat, + attributeLoginName = idp.attributeLoginName, + attributeGroupName = idp.attributeGroupName, + ), + ) + .credentialResolver(metadataResolver(properties)) + .signatureAlgorithm(properties.signatureAlgorithm) + .apply { + idp() + .entityId(idp.entityId) + .ssoEndpoint(idp.endpoint) + .signingKey(idp.signingKey) + .encryptionKey(idp.encryptionKey) + } + .build() + + return SamlAuthProvider(samlServiceProvider) + } + + @VisibleForTesting + internal fun metadataResolver(properties: SamlAuthProperties): CredentialResolver { + val factory = CertificateFactory.getInstance("X.509") + + val certificate = + factory.generateCertificate(properties.samlMetadata.byteInputStream()) as X509Certificate + + val keyPairGenerator = KeyPairGenerator.getInstance(certificate.publicKey.algorithm) + + val generateKeyPair = keyPairGenerator.generateKeyPair() + + val credential = + BasicX509Credential(certificate).apply { + setUsageType(UsageType.SIGNING) + setPrivateKey(generateKeyPair.private) + } + + return StaticCredentialResolver(credential) + } +} diff --git a/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/spring/loader/CentralDogmaProperties.kt b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/spring/loader/CentralDogmaProperties.kt new file mode 100644 index 000000000..5bb6a74ca --- /dev/null +++ b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/spring/loader/CentralDogmaProperties.kt @@ -0,0 +1,190 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.spring.loader + +import com.google.common.base.Preconditions.checkArgument +import com.linecorp.armeria.common.SessionProtocol +import com.linecorp.armeria.server.ClientAddressSource +import com.linecorp.centraldogma.config.AuthConfig +import com.linecorp.centraldogma.config.CorsConfig +import com.linecorp.centraldogma.config.GracefulShutdownTimeout +import com.linecorp.centraldogma.config.ManagementConfig +import com.linecorp.centraldogma.config.PluginConfigBase +import com.linecorp.centraldogma.config.QuotaConfig +import com.linecorp.centraldogma.config.ReplicationConfigImpl +import com.linecorp.centraldogma.config.ServerPort +import com.linecorp.centraldogma.config.TlsConfig +import com.linecorp.centraldogma.server.AbstractCentralDogmaConfig +import com.linecorp.centraldogma.server.CorsConfigSpec +import com.linecorp.centraldogma.server.GracefulShutdownTimeoutSpec +import com.linecorp.centraldogma.server.ManagementConfigSpec +import com.linecorp.centraldogma.server.QuotaConfigSpec +import com.linecorp.centraldogma.server.ReplicationConfig +import com.linecorp.centraldogma.server.TlsConfigSpec +import com.linecorp.centraldogma.server.ZoneConfigSpec +import com.linecorp.centraldogma.server.auth.AuthConfigSpec +import com.linecorp.centraldogma.server.internal.storage.repository.RepositoryCache.validateCacheSpec +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.NestedConfigurationProperty +import java.io.File +import java.net.InetAddress +import java.net.InetSocketAddress +import java.util.Optional +import java.util.function.Predicate + +@ConfigurationProperties("central-dogma") +data class CentralDogmaProperties( + private val dataDir: File, + @NestedConfigurationProperty + private val ports: List, + @NestedConfigurationProperty + private val tls: TlsConfig? = null, + private val trustedProxyAddresses: List? = null, + private val clientAddressSources: List? = null, + private val numWorkers: Int? = null, + private val maxNumConnections: Int? = null, + private val requestTimeoutMillis: Long? = null, + private val idleTimeoutMillis: Long? = null, + private val maxFrameLength: Int? = null, + /** + * default value from [com.linecorp.centraldogma.server.CentralDogmaBuilder.DEFAULT_NUM_REPOSITORY_WORKERS] + */ + private val numRepositoryWorkers: Int = 16, + private val repositoryCacheSpec: String = "maximumWeight=134217728,expireAfterAccess=5m", + private val maxRemovedRepositoryAgeMillis: Long = 604_800_000, + @NestedConfigurationProperty + private val gracefulShutdownTimeout: GracefulShutdownTimeout? = null, + private val webAppEnabled: Boolean = true, + private val webAppTitle: String? = null, + @NestedConfigurationProperty + private val replication: ReplicationConfigImpl, + private val csrfTokenRequiredForThrift: Boolean = true, + private val accessLogFormat: String? = null, + @NestedConfigurationProperty + private val authentication: AuthConfig? = null, + @NestedConfigurationProperty + private val writeQuotaPerRepository: QuotaConfig? = null, + @NestedConfigurationProperty + private val cors: CorsConfig? = null, + private val pluginConfigs: List? = null, + @NestedConfigurationProperty + private val management: ManagementConfig? = null, +) : AbstractCentralDogmaConfig(pluginConfigs) { + private val trustedProxyAddressPredicate: Predicate + + private val clientAddressSourceList: List + + private val armeriaServerPorts: List = + ports.map { serverPort -> + val localAddress = + if (serverPort.localAddress.host == "*") { + InetSocketAddress(serverPort.localAddress.port) + } else { + InetSocketAddress(serverPort.localAddress.host, serverPort.localAddress.port) + } + + val protocols = + serverPort.protocols.map { + SessionProtocol.of(it) + }.toSet() + + com.linecorp.armeria.server.ServerPort(localAddress, protocols) + } + + init { + checkArgument( + this.numRepositoryWorkers > 0, + "numRepositoryWorkers: %s (expected: > 0)", + this.numRepositoryWorkers, + ) + checkArgument( + this.maxRemovedRepositoryAgeMillis >= 0, + "maxRemovedRepositoryAgeMillis: %s (expected: >= 0)", + this.maxRemovedRepositoryAgeMillis, + ) + + validateCacheSpec(repositoryCacheSpec) + + val hasTrustedProxyAddrCfg = !trustedProxyAddresses.isNullOrEmpty() + + trustedProxyAddressPredicate = + if (hasTrustedProxyAddrCfg) { + toTrustedProxyAddressPredicate(trustedProxyAddresses!!) + } else { + Predicate { false } + } + + clientAddressSourceList = + toClientAddressSourceList( + clientAddressSources, hasTrustedProxyAddrCfg, + armeriaServerPorts.any(com.linecorp.armeria.server.ServerPort::hasProxyProtocol), + ) + } + + override fun dataDir(): File = dataDir + + override fun ports(): List = armeriaServerPorts + + override fun tls(): TlsConfigSpec? = tls + + override fun trustedProxyAddresses(): List? = trustedProxyAddresses + + override fun clientAddressSources(): List? = clientAddressSources + + override fun numWorkers(): Optional = Optional.ofNullable(numWorkers) + + override fun maxNumConnections(): Optional = Optional.ofNullable(maxNumConnections) + + override fun requestTimeoutMillis(): Optional = Optional.ofNullable(requestTimeoutMillis) + + override fun idleTimeoutMillis(): Optional = Optional.ofNullable(idleTimeoutMillis) + + override fun maxFrameLength(): Optional = Optional.ofNullable(maxFrameLength) + + override fun numRepositoryWorkers(): Int = numRepositoryWorkers + + override fun maxRemovedRepositoryAgeMillis(): Long = maxRemovedRepositoryAgeMillis + + override fun repositoryCacheSpec(): String = repositoryCacheSpec + + override fun gracefulShutdownTimeout(): Optional = Optional.ofNullable(gracefulShutdownTimeout) + + override fun isWebAppEnabled(): Boolean = webAppEnabled + + override fun webAppTitle(): String? = webAppTitle + + override fun replicationConfig(): ReplicationConfig = replication + + override fun isCsrfTokenRequiredForThrift(): Boolean = this.csrfTokenRequiredForThrift + + override fun accessLogFormat(): String? = accessLogFormat + + override fun authConfig(): AuthConfigSpec? = authentication + + override fun writeQuotaPerRepository(): QuotaConfigSpec? = writeQuotaPerRepository + + override fun corsConfig(): CorsConfigSpec? = cors + + override fun managementConfig(): ManagementConfigSpec? = management + + // TODO 이따 구현 + override fun zone(): ZoneConfigSpec? = null + + override fun trustedProxyAddressPredicate(): Predicate = trustedProxyAddressPredicate + + override fun clientAddressSourceList(): List = clientAddressSourceList +} diff --git a/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/spring/loader/CentralDogmaSpringLoader.kt b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/spring/loader/CentralDogmaSpringLoader.kt new file mode 100644 index 000000000..317f88f5c --- /dev/null +++ b/cloud-native/boot-loader/src/main/kotlin/com/linecorp/centraldogma/spring/loader/CentralDogmaSpringLoader.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.spring.loader + +import com.linecorp.centraldogma.server.CentralDogma +import com.linecorp.centraldogma.server.CentralDogma.TokenAuthorizerCreator +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec +import com.linecorp.centraldogma.server.CustomConfigBindHandlerConfiguration +import com.linecorp.centraldogma.server.auth.AuthProviderParameters +import com.linecorp.centraldogma.server.auth.saml.SessionGroupTokenAuthorizer +import com.linecorp.centraldogma.server.command.Command +import com.linecorp.centraldogma.server.internal.api.auth.ApplicationTokenAuthorizer +import io.micrometer.core.instrument.MeterRegistry +import org.springframework.beans.factory.DisposableBean +import org.springframework.beans.factory.InitializingBean +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.boot.runApplication +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Import + +class CentralDogmaManager(val centralDogma: CentralDogma) : DisposableBean, InitializingBean { + override fun afterPropertiesSet() { + centralDogma.start().join() + } + + override fun destroy() { + centralDogma.stop().join() + } +} + +@ConfigurationProperties("central-dogma-admin") +data class CentralDogmaAdminProperties( + val adminGroupNames: Set = emptySet(), +) + +@Import(CustomConfigBindHandlerConfiguration::class) +@EnableConfigurationProperties( + CentralDogmaProperties::class, + CentralDogmaAdminProperties::class, +) +@SpringBootApplication +class CentralDogmaSpringLoader { + @Bean + fun centralDogma( + properties: CentralDogmaProperties, + adminProperties: CentralDogmaAdminProperties, + meterRegistry: MeterRegistry, + ): CentralDogmaManager { + val authConfig = properties.authConfig() + + Thread.sleep(10000) + val tokenAuthorizerCreator = + TokenAuthorizerCreator { mds, sessionManager -> + ApplicationTokenAuthorizer(mds::findTokenBySecret) + .orElse(SessionGroupTokenAuthorizer(sessionManager, adminProperties.adminGroupNames)) + } + val authProviderCreator = + if (authConfig == null) { + null + } else { + CentralDogma.AuthProviderCreator { commandExecutor, sessionManager, mds -> + checkNotNull(sessionManager) { "SessionManager is null" } + + val parameters = + AuthProviderParameters( + tokenAuthorizerCreator.create(mds, sessionManager), + properties, + sessionManager::generateSessionId, + { commandExecutor.execute(Command.createSession(it)) }, + { commandExecutor.execute(Command.removeSession(it)) }, + ) + + authConfig.factory().create(parameters) + } + } + + val centralDogmaConfig = + object : CentralDogmaConfigSpec by properties { + override fun toString(): String = "Contains sensitive values. Please use actuator instead." + } + + return CentralDogmaManager( + CentralDogma.forConfig(centralDogmaConfig, meterRegistry) + .authProviderCreator(authProviderCreator) + .tokenAuthorizerCreator(tokenAuthorizerCreator), + ) + } +} + +fun main(args: Array) { + runApplication(*args) +} diff --git a/cloud-native/boot-loader/src/main/resources/application.yaml b/cloud-native/boot-loader/src/main/resources/application.yaml new file mode 100644 index 000000000..a2c9a388b --- /dev/null +++ b/cloud-native/boot-loader/src/main/resources/application.yaml @@ -0,0 +1,28 @@ +--- +central-dogma: + dataDir: "./data" + ports: + - local-address: + host: '*' + port: 36462 + protocols: + - http + num-repository-workers: 16 + repository-cache-spec: "maximumWeight=134217728,expireAfterAccess=5m" + graceful-shutdown-timeout: + quiet-period-millis: 1000 + timeout-millis: 10000 + web-app-enabled: true + replication: + method: NONE + access-log-format: common + +management: + endpoints: + web: + exposure: + include: '*' + endpoint: + health: + probes: + enabled: true diff --git a/cloud-native/boot-loader/src/test/kotlin/com/linecorp/centraldogma/spring/loader/ConfigurationPropertiesTest.kt b/cloud-native/boot-loader/src/test/kotlin/com/linecorp/centraldogma/spring/loader/ConfigurationPropertiesTest.kt new file mode 100644 index 000000000..40af6f43c --- /dev/null +++ b/cloud-native/boot-loader/src/test/kotlin/com/linecorp/centraldogma/spring/loader/ConfigurationPropertiesTest.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.spring.loader + +import assertk.all +import assertk.assertThat +import assertk.assertions.containsOnly +import assertk.assertions.hasSize +import assertk.assertions.isEqualTo +import assertk.assertions.isInstanceOf +import assertk.assertions.isNotNull +import assertk.assertions.isNull +import assertk.assertions.prop +import com.linecorp.armeria.common.SessionProtocol +import com.linecorp.armeria.server.ServerPort +import com.linecorp.centraldogma.config.GracefulShutdownTimeout +import com.linecorp.centraldogma.config.MirroringServicePluginConfig +import com.linecorp.centraldogma.config.ZooKeeperReplicationConfig +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec +import com.linecorp.centraldogma.server.CustomConfigBindHandlerConfiguration +import com.linecorp.centraldogma.server.ReplicationMethod +import com.linecorp.centraldogma.server.ZooKeeperReplicationConfigSpec +import com.linecorp.centraldogma.server.ZooKeeperServerConfigSpec +import com.linecorp.centraldogma.server.auth.AuthConfigSpec +import com.linecorp.centraldogma.server.auth.saml.Idp +import com.linecorp.centraldogma.server.auth.saml.SamlAuthProperties +import com.linecorp.centraldogma.server.auth.saml.SamlAuthProviderFactory +import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfigSpec +import org.junit.jupiter.api.Test +import org.opensaml.xmlsec.signature.support.SignatureConstants +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Import +import org.springframework.test.context.TestConstructor +import org.springframework.test.context.TestPropertySource +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig +import java.io.File +import java.util.Optional + +@SpringJUnitConfig( + TestConfiguration::class, + initializers = [ConfigDataApplicationContextInitializer::class], +) +@TestPropertySource(properties = ["spring.config.location=classpath:full-config.yaml"]) +@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL) +class ConfigurationPropertiesTest( + private val centralDogmaConfig: CentralDogmaConfigSpec, +) { + @Test + fun test() { + println(centralDogmaConfig) + + assertThat(centralDogmaConfig).all { + prop(CentralDogmaConfigSpec::dataDir).isEqualTo(File("./data")) + prop(CentralDogmaConfigSpec::ports).isEqualTo(listOf(ServerPort(36462, SessionProtocol.HTTP))) + prop(CentralDogmaConfigSpec::numRepositoryWorkers).isEqualTo(16) + prop(CentralDogmaConfigSpec::repositoryCacheSpec).isEqualTo("maximumWeight=134217728,expireAfterAccess=5m") + prop(CentralDogmaConfigSpec::gracefulShutdownTimeout) + .isEqualTo(Optional.of(GracefulShutdownTimeout(1000L, 10000L))) + + prop("isWebAppEnabled") { it.isWebAppEnabled }.isEqualTo(true) + + prop(CentralDogmaConfigSpec::replicationConfig) + .isInstanceOf() + .all { + prop(ZooKeeperReplicationConfigSpec::method).isEqualTo(ReplicationMethod.ZOOKEEPER) + prop(ZooKeeperReplicationConfigSpec::secret).isEqualTo("secretKey") + prop(ZooKeeperReplicationConfigSpec::servers).all { + hasSize(1) + prop("1") { it[1] }.isNotNull().all { + prop(ZooKeeperServerConfigSpec::host).isEqualTo("127.0.0.1") + prop(ZooKeeperServerConfigSpec::quorumPort).isEqualTo(2030) + prop(ZooKeeperServerConfigSpec::electionPort).isEqualTo(2345) + prop(ZooKeeperServerConfigSpec::clientPort).isEqualTo(2345) + prop(ZooKeeperServerConfigSpec::groupId).isNull() + prop(ZooKeeperServerConfigSpec::weight).isEqualTo(1) + } + } + } + prop(CentralDogmaConfigSpec::accessLogFormat).isEqualTo("common") + prop(CentralDogmaConfigSpec::pluginConfigs).all { + hasSize(1) + prop("0") { it.first() }.isInstanceOf().all { + prop(MirroringServicePluginConfigSpec::numMirroringThreads).isEqualTo(10) + prop(MirroringServicePluginConfigSpec::maxNumFilesPerMirror).isEqualTo(10) + prop(MirroringServicePluginConfigSpec::maxNumBytesPerMirror) + .isEqualTo(MirroringServicePluginConfigSpec.DEFAULT_MAX_NUM_BYTES_PER_MIRROR) + } + } + prop(CentralDogmaConfigSpec::authConfig).isNotNull().all { + prop(AuthConfigSpec::factoryClassName).isEqualTo(SamlAuthProviderFactory::class.qualifiedName) + prop(AuthConfigSpec::factory).isInstanceOf() + prop(AuthConfigSpec::systemAdministrators).all { + hasSize(1) + containsOnly("test@linecorp.com") + } + prop("properties") { it.properties(SamlAuthProperties::class.java) }.isNotNull().all { + prop(SamlAuthProperties::entityId).isEqualTo("https://dogma.com") + prop(SamlAuthProperties::hostname).isEqualTo("dogma.com") + prop(SamlAuthProperties::samlMetadata).isNotNull() + prop(SamlAuthProperties::signatureAlgorithm).isEqualTo(SignatureConstants.ALGO_ID_SIGNATURE_RSA) + prop(SamlAuthProperties::idp).all { + prop(Idp::entityId).isEqualTo("dogma-saml") + prop(Idp::uri).isEqualTo("https://dogma.com/signon") + } + } + } + } + + SamlAuthProviderFactory() + .metadataResolver(centralDogmaConfig.authConfig()!!.properties(SamlAuthProperties::class.java)!!) + } +} + +@Import(CustomConfigBindHandlerConfiguration::class) +@EnableConfigurationProperties(CentralDogmaProperties::class) +@Configuration(proxyBeanMethods = false) +class TestConfiguration diff --git a/cloud-native/boot-loader/src/test/resources/full-config.yaml b/cloud-native/boot-loader/src/test/resources/full-config.yaml new file mode 100644 index 000000000..3885cdd63 --- /dev/null +++ b/cloud-native/boot-loader/src/test/resources/full-config.yaml @@ -0,0 +1,104 @@ +--- +central-dogma: + dataDir: "./data" + ports: + - local-address: + host: '*' + port: 36462 + protocols: + - http + num-repository-workers: 16 + repository-cache-spec: "maximumWeight=134217728,expireAfterAccess=5m" + graceful-shutdown-timeout: + quiet-period-millis: 1000 + timeout-millis: 10000 + web-app-enabled: true + replication: + method: ZOOKEEPER + secret: secretKey + servers: + '1': + host: 127.0.0.1 + quorumPort: 2030 + electionPort: 2345 + clientPort: 2345 + groupId: null + weight: 1 + access-log-format: common + pluginConfigs: + - enabled: true + type: com.linecorp.centraldogma.config.MirroringServicePluginConfig + num-mirroring-threads: 10 + max-num-files-per-mirror: 10 + authentication: + administrators: [ "test@linecorp.com" ] + factoryClassName: com.linecorp.centraldogma.server.auth.saml.SamlAuthProviderFactory + properties: + entityId: https://dogma.com + hostname: dogma.com + signatureAlgorithm: http://www.w3.org/2000/09/xmldsig#rsa-sha1 + samlMetadata: |- + Bag Attributes + friendlyName: encryption + localKeyID: 54 69 6D 65 20 31 37 32 37 32 33 31 35 36 33 35 34 34 + subject=C=Unknown, ST=Unknown, L=Unknown, O=Unknown, OU=Unknown, CN=Unknown + issuer=C=Unknown, ST=Unknown, L=Unknown, O=Unknown, OU=Unknown, CN=Unknown + -----BEGIN CERTIFICATE----- + MIIDdzCCAl+gAwIBAgIEbiT6NTANBgkqhkiG9w0BAQUFADBsMRAwDgYDVQQGEwdV + bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD + VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du + MB4XDTE4MTAwNTA2MjcyNVoXDTE5MDEwMzA2MjcyNVowbDEQMA4GA1UEBhMHVW5r + bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE + ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC + ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK9V1vYacLGyBk3owicRYamd + ttk/VJ0Y79BhjJobpMCeP8oyqiLNoeQXh3q5MI3YRSrSewtJKweun1E2rNHWveuk + 6+wIEtJ4HChAorNLkI4R0OG99Qe/kltRsr3Q1GDycuAFi9jWTN221S40Zj9kIadn + 3dtwXUrg2gSIdFW7+mhTuODKRzBaZAO/5OZN7600ZL9j7IjtldpIZCum3FIIXTQD + 6z88Ymfbgs8eMQzNmXvC04ULWjgD5KzbJX5W2df1Yg3a4QJBcTIyD2NBJKL1410R + t4/tHd7bhcJ+MXU23qShWkxThP8n4akFZ38uslNQeI86F2wWT5uxyrsFmVXtAqMC + AwEAAaMhMB8wHQYDVR0OBBYEFLKyum/cqOwnnMFJdc9SRa9j5H8oMA0GCSqGSIb3 + DQEBBQUAA4IBAQBYLxmsaljw9qLFxWBjaQBKv1xSzQIwL01zaXntkS7zhffhdElS + fn9ril18N32M6K/l4wkjY57tUXNyDr6WsYouP+Xv8FilwVM3GKW6Jj3ED5rtkRzr + jWU1t4Si8rLSVClIRw/4UTy7BLonhE47DI/3jGIC60jxrE8fomtHeusioXn1NYfK + Qvrfjd0ZldhBz1ZEU7Dlx6+vQ4YfonFUWDByUJUobF6NIWP+kmdRVV6fX4fP6+Yq + ciPzN59IELyxvnvCSBvuE8ihIzT6zg5bundOU+ATOvTIe+94tXmFwU3+0ifHdmas + fAzZ+eEXN6iz7yZLHLQg5FuRUjvJRWQmSkfe + -----END CERTIFICATE----- + Bag Attributes + friendlyName: signing + localKeyID: 54 69 6D 65 20 31 37 32 37 32 33 31 35 36 33 36 33 33 + subject=C=Unknown, ST=Unknown, L=Unknown, O=Unknown, OU=Unknown, CN=Unknown + issuer=C=Unknown, ST=Unknown, L=Unknown, O=Unknown, OU=Unknown, CN=Unknown + -----BEGIN CERTIFICATE----- + MIIDdzCCAl+gAwIBAgIEIXeHCDANBgkqhkiG9w0BAQUFADBsMRAwDgYDVQQGEwdV + bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD + VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du + MB4XDTE4MTAwNTA2Mjc0M1oXDTE5MDEwMzA2Mjc0M1owbDEQMA4GA1UEBhMHVW5r + bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE + ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC + ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKubyc0G4ekrNBd131G1+WCS + QIMLYhunLfUjz+46PngDWWt7nS21RbHvV8eDirpc3wEuEfTYP4FpwF6etTHB92u6 + X+1oSboiMfx3EiEDA+3ziBinzhBP9lL6Xd8x0v7hPCTtlWrwBY5RMSkvBT5N4khX + APQwCgYmwK2UBoTEPcr3O/s2ysbnpu4fa5SAX1mxQhUAeZMRgG7j05jP9Np8KoIH + W0zPsF4W+n+09pbG5JYOgVFkM1gVc56PmTUwFa+4D6Wqq/LyRowkXvjyWPoAE6JI + jbFhu2UTMKpL6kA9j/48b/ixULOblfV6ahB+kA9Pw9kjMAgcXUTm6hRepmu+gTcC + AwEAAaMhMB8wHQYDVR0OBBYEFDXaz1aatlHvzZi355hb+PHMtlK4MA0GCSqGSIb3 + DQEBBQUAA4IBAQCXcCcwTpj2bWMzrxJOv1YScPT/KzRNlj3Bu6bKh4UW3//wwfO4 + 1uUhiYL9ont+EFCX6qtxbrWz16fahklaHKIc4AlKvwplwAqw2FUlNKhztmDDC0UM + as9o/TUs9rIthOofrfho8FeLULsiKQbZjwSp2o81xooIElBxilkXin+5GjyTQIlL + COMJngk/mFdOP5oCvAXVI3YsISgUsDFmR1A1vO0oiUKZY2evruMb7O+Pk2JieyJZ + AYI29mmfoDtaBH9pb1OpqEs1dpkTT1EkM51qOQv0wDGV7cksPOHqBNnJHzc9oyuW + tCaFCVVxUeL48EUfHU3efqCMw9ba++1LlYAp + -----END CERTIFICATE----- + idp: + entityId: dogma-saml + uri: https://dogma.com/signon +management: + endpoints: + web: + exposure: + include: '*' + endpoint: + health: + probes: + enabled: true diff --git a/common/build.gradle b/common/build.gradle index 41505e6d5..ad4f5b197 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -7,6 +7,7 @@ dependencies { implementation libs.jackson.core implementation libs.jackson.databind implementation libs.jackson.datatype.jsr310 + implementation libs.jackson.kotlin // Guava implementation libs.guava diff --git a/common/src/main/java/com/linecorp/centraldogma/internal/Jackson.java b/common/src/main/java/com/linecorp/centraldogma/internal/Jackson.java index 5d76f8b05..7a19501be 100644 --- a/common/src/main/java/com/linecorp/centraldogma/internal/Jackson.java +++ b/common/src/main/java/com/linecorp/centraldogma/internal/Jackson.java @@ -51,6 +51,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer; +import com.fasterxml.jackson.module.kotlin.KotlinModule; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.jayway.jsonpath.Configuration; @@ -79,7 +80,8 @@ public final class Jackson { prettyMapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS); registerModules(new SimpleModule().addSerializer(Instant.class, InstantSerializer.INSTANCE) - .addDeserializer(Instant.class, InstantDeserializer.INSTANT)); + .addDeserializer(Instant.class, InstantDeserializer.INSTANT), + new KotlinModule.Builder().build()); } private static final JsonFactory compactFactory = new JsonFactory(compactMapper); diff --git a/dependencies.toml b/dependencies.toml index 14e42f696..83fd15ed8 100644 --- a/dependencies.toml +++ b/dependencies.toml @@ -53,6 +53,9 @@ jsoup = "1.18.1" # JSoup is only used for Gradle script. jmh-core = "1.37" jmh-gradle-plugin = "0.7.2" jxr = "0.2.1" +ktlint-gradle-plugin = "12.1.1" +kotlin = "1.9.23" +kotlin-coroutine = "1.8.1" kubernetes-client = "6.13.4" logback12 = { strictly = "1.2.13" } logback15 = { strictly = "1.5.7" } @@ -222,6 +225,8 @@ module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" # Only used for testing. See JacksonRequestConverterFunctionTest for more information. [libraries.jackson-module-scala] module = "com.fasterxml.jackson.module:jackson-module-scala_3" +[libraries.jackson-kotlin] +module = "com.fasterxml.jackson.module:jackson-module-kotlin" [libraries.javassist] module = "org.javassist:javassist" @@ -454,6 +459,14 @@ version.ref = "spring-boot3" module = "org.springframework.boot:spring-boot-starter" version.ref = "spring-boot3" javadocs = "https://docs.spring.io/spring/docs/current/javadoc-api/" +[libraries.spring-boot3-starter-actuator] +module = "org.springframework.boot:spring-boot-starter-actuator" +version.ref = "spring-boot3" +javadocs = "https://docs.spring.io/spring/docs/current/javadoc-api/" +[libraries.spring-boot3-starter-web] +module = "org.springframework.boot:spring-boot-starter-web" +version.ref = "spring-boot3" +javadocs = "https://docs.spring.io/spring/docs/current/javadoc-api/" [libraries.spring-boot3-starter-test] module = "org.springframework.boot:spring-boot-starter-test" version.ref = "spring-boot3" @@ -485,9 +498,15 @@ exclusions = [ [plugins] docker = { id = "com.bmuschko.docker-remote-api", version.ref = "docker" } download = { id = "de.undercouch.download", version.ref = "download" } +jib = { id = "com.google.cloud.tools.jib", version = "3.2.1" } jmh = { id = "me.champeau.jmh", version.ref = "jmh-gradle-plugin" } jxr = { id = "net.davidecavestro.gradle.jxr", version.ref = "jxr" } nexus-publish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus-publish-plugin" } node-gradle = { id = "com.github.node-gradle.node", version.ref = "node-gradle-plugin" } osdetector = { id = "com.google.osdetector", version.ref = "osdetector" } sphinx = { id = "kr.motd.sphinx", version.ref = "sphinx" } +kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint-gradle-plugin" } +spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot3" } +kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" } + diff --git a/it/server/src/test/java/com/linecorp/centraldogma/it/ReplicationWriteQuotaTest.java b/it/server/src/test/java/com/linecorp/centraldogma/it/ReplicationWriteQuotaTest.java index bacf598aa..67d54efbd 100644 --- a/it/server/src/test/java/com/linecorp/centraldogma/it/ReplicationWriteQuotaTest.java +++ b/it/server/src/test/java/com/linecorp/centraldogma/it/ReplicationWriteQuotaTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -46,6 +46,7 @@ import com.linecorp.centraldogma.server.GracefulShutdownTimeout; import com.linecorp.centraldogma.server.ZooKeeperReplicationConfig; import com.linecorp.centraldogma.server.ZooKeeperServerConfig; +import com.linecorp.centraldogma.server.ZooKeeperServerConfigSpec; import com.linecorp.centraldogma.server.auth.AuthProviderFactory; import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfig; import com.linecorp.centraldogma.testing.internal.FlakyTest; @@ -80,7 +81,7 @@ void setUp() throws IOException { final int port2 = InstanceSpec.getRandomPort(); final int port3 = InstanceSpec.getRandomPort(); - final Map servers = randomServerConfigs(3); + final Map servers = randomServerConfigs(3); final CompletableFuture r1 = startNewReplicaWithRetries(port1, 1, servers); final CompletableFuture r2 = startNewReplicaWithRetries(port2, 2, servers); @@ -101,8 +102,8 @@ void setUp() throws IOException { .build(); } - private static Map randomServerConfigs(int numReplicas) { - final ImmutableMap.Builder builder = + private static Map randomServerConfigs(int numReplicas) { + final ImmutableMap.Builder builder = ImmutableMap.builderWithExpectedSize(numReplicas); for (int i = 0; i < numReplicas; i++) { final int zkQuorumPort = InstanceSpec.getRandomPort(); @@ -116,7 +117,7 @@ private static Map randomServerConfigs(int numRe } private static CompletableFuture startNewReplicaWithRetries( - int port, int serverId, Map servers) throws IOException { + int port, int serverId, Map servers) throws IOException { final AtomicReference> futureRef = new AtomicReference<>(); await().pollInSameThread().pollInterval(Duration.ofSeconds(1)).untilAsserted(() -> { assertThatCode(() -> { @@ -127,7 +128,7 @@ private static CompletableFuture startNewReplicaWithRetries( } private static CompletableFuture startNewReplica( - int port, int serverId, Map servers) throws IOException { + int port, int serverId, Map servers) throws IOException { return new CentralDogmaBuilder(tempDir.newFolder().toFile()) .port(port, SessionProtocol.HTTP) .systemAdministrators(TestAuthMessageUtil.USERNAME) diff --git a/it/server/src/test/java/com/linecorp/centraldogma/it/TestAllReplicasPlugin.java b/it/server/src/test/java/com/linecorp/centraldogma/it/TestAllReplicasPlugin.java index 280f2858e..2ea2a869c 100644 --- a/it/server/src/test/java/com/linecorp/centraldogma/it/TestAllReplicasPlugin.java +++ b/it/server/src/test/java/com/linecorp/centraldogma/it/TestAllReplicasPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -21,6 +21,8 @@ import com.linecorp.armeria.common.HttpResponse; import com.linecorp.armeria.common.util.UnmodifiableFuture; import com.linecorp.centraldogma.server.plugin.AllReplicasPlugin; +import com.linecorp.centraldogma.server.plugin.NoopPluginConfig; +import com.linecorp.centraldogma.server.plugin.PluginConfig; import com.linecorp.centraldogma.server.plugin.PluginContext; import com.linecorp.centraldogma.server.plugin.PluginInitContext; @@ -43,8 +45,8 @@ public CompletionStage stop(PluginContext context) { } @Override - public Class configType() { + public Class configType() { // Return the plugin class itself because it does not have a configuration. - return TestAllReplicasPlugin.class; + return NoopPluginConfig.class; } } diff --git a/it/server/src/test/java/com/linecorp/centraldogma/it/WriteQuotaTestBase.java b/it/server/src/test/java/com/linecorp/centraldogma/it/WriteQuotaTestBase.java index a99a685ec..3a8bbb1ea 100644 --- a/it/server/src/test/java/com/linecorp/centraldogma/it/WriteQuotaTestBase.java +++ b/it/server/src/test/java/com/linecorp/centraldogma/it/WriteQuotaTestBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -42,6 +42,7 @@ import com.linecorp.centraldogma.common.PushResult; import com.linecorp.centraldogma.internal.Jackson; import com.linecorp.centraldogma.server.QuotaConfig; +import com.linecorp.centraldogma.server.QuotaConfigSpec; import com.linecorp.centraldogma.server.metadata.ProjectMetadata; abstract class WriteQuotaTestBase { @@ -65,7 +66,7 @@ void updateWriteQuota() throws Exception { /// update write quota to 2qps QuotaConfig writeQuota = new QuotaConfig(2, 1); - QuotaConfig updated = updateWriteQuota(webClient(), repositoryName, writeQuota); + QuotaConfigSpec updated = updateWriteQuota(webClient(), repositoryName, writeQuota); assertThat(updated).isEqualTo(writeQuota); // Wait for releasing previously acquired locks @@ -94,8 +95,8 @@ void updateWriteQuota() throws Exception { assertThat(CompletableFutures.allAsList(futures4).join()).hasSize(8); } - private static QuotaConfig updateWriteQuota( - WebClient systemAdminClient, String repoName, QuotaConfig writeQuota) + private static QuotaConfigSpec updateWriteQuota(WebClient systemAdminClient, String repoName, + QuotaConfig writeQuota) throws JsonProcessingException { final String updatePath = "/api/v1/metadata/test_prj/repos/" + repoName + "/quota/write"; final String content = mapper.writeValueAsString(writeQuota); diff --git a/it/zone-leader-plugin/src/test/java/com/linecorp/centraldogma/it/zoneleader/ZoneLeaderPluginTest.java b/it/zone-leader-plugin/src/test/java/com/linecorp/centraldogma/it/zoneleader/ZoneLeaderPluginTest.java index 7489c0ca8..d8bf17e9b 100644 --- a/it/zone-leader-plugin/src/test/java/com/linecorp/centraldogma/it/zoneleader/ZoneLeaderPluginTest.java +++ b/it/zone-leader-plugin/src/test/java/com/linecorp/centraldogma/it/zoneleader/ZoneLeaderPluginTest.java @@ -31,9 +31,11 @@ import com.linecorp.armeria.common.util.UnmodifiableFuture; import com.linecorp.centraldogma.server.CentralDogmaBuilder; -import com.linecorp.centraldogma.server.CentralDogmaConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; import com.linecorp.centraldogma.server.ZoneConfig; +import com.linecorp.centraldogma.server.plugin.NoopPluginConfig; import com.linecorp.centraldogma.server.plugin.Plugin; +import com.linecorp.centraldogma.server.plugin.PluginConfig; import com.linecorp.centraldogma.server.plugin.PluginContext; import com.linecorp.centraldogma.server.plugin.PluginTarget; import com.linecorp.centraldogma.testing.internal.CentralDogmaReplicationExtension; @@ -119,7 +121,7 @@ private ZoneLeaderTestPlugin(int serverId) { } @Override - public PluginTarget target(CentralDogmaConfig config) { + public PluginTarget target(CentralDogmaConfigSpec config) { return PluginTarget.ZONE_LEADER_ONLY; } @@ -140,8 +142,8 @@ public CompletionStage stop(PluginContext context) { } @Override - public Class configType() { - return getClass(); + public Class configType() { + return NoopPluginConfig.class; } } } diff --git a/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SamlAuthProviderFactory.java b/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SamlAuthProviderFactory.java index 0e2fecd21..5f2e5f938 100644 --- a/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SamlAuthProviderFactory.java +++ b/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SamlAuthProviderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -29,7 +29,7 @@ import com.linecorp.armeria.server.saml.KeyStoreCredentialResolverBuilder; import com.linecorp.armeria.server.saml.SamlServiceProvider; import com.linecorp.armeria.server.saml.SamlServiceProviderBuilder; -import com.linecorp.centraldogma.server.auth.AuthConfig; +import com.linecorp.centraldogma.server.auth.AuthConfigSpec; import com.linecorp.centraldogma.server.auth.AuthProvider; import com.linecorp.centraldogma.server.auth.AuthProviderFactory; import com.linecorp.centraldogma.server.auth.AuthProviderParameters; @@ -73,14 +73,14 @@ public AuthProvider create(AuthProviderParameters parameters) { } } - private static SamlAuthProperties getProperties(AuthConfig authConfig) { + private static SamlAuthProperties getProperties(AuthConfigSpec authConfig) { try { final SamlAuthProperties p = authConfig.properties(SamlAuthProperties.class); checkState(p != null, "authentication properties are not specified"); return p; } catch (JsonProcessingException e) { throw new IllegalArgumentException("Failed to get properties from " + - AuthConfig.class.getSimpleName(), e); + authConfig.getClass().getSimpleName(), e); } } diff --git a/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SamlAuthSsoHandler.java b/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SamlAuthSsoHandler.java index 34cda7480..90bc9955f 100644 --- a/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SamlAuthSsoHandler.java +++ b/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SamlAuthSsoHandler.java @@ -123,7 +123,11 @@ public HttpResponse loginSucceeded(ServiceRequestContext ctx, AggregatedHttpRequ final String sessionId = sessionIdGenerator.get(); final Session session = - new Session(sessionId, loginNameNormalizer.apply(username), sessionValidDuration); + new Session( + sessionId, + loginNameNormalizer.apply(username), + sessionValidDuration + ); final String redirectionScript; if (!Strings.isNullOrEmpty(relayState)) { diff --git a/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SamlSession.java b/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SamlSession.java new file mode 100644 index 000000000..c4e7dc0bd --- /dev/null +++ b/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SamlSession.java @@ -0,0 +1,45 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server.auth.saml; + +import java.io.Serializable; +import java.util.List; + +/** + * A class representing a SAML session. + */ +public class SamlSession implements Serializable { + private final List groups; + + /** + * Creates a new instance of the `SamlSession` class with the specified list of groups. + * + * @param groups The list of groups for this session. + */ + public SamlSession(List groups) { + this.groups = groups; + } + + /** + * Returns the list of groups for this session. + * + * @return The list of groups for this session. + */ + public List groups() { + return groups; + } +} diff --git a/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SessionGroupTokenAuthorizer.java b/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SessionGroupTokenAuthorizer.java new file mode 100644 index 000000000..b7455d121 --- /dev/null +++ b/server-auth/saml/src/main/java/com/linecorp/centraldogma/server/auth/saml/SessionGroupTokenAuthorizer.java @@ -0,0 +1,93 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server.auth.saml; + +import static com.linecorp.centraldogma.server.metadata.User.LEVEL_SYSTEM_ADMIN; +import static com.linecorp.centraldogma.server.metadata.User.LEVEL_USER; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.CompletableFuture.completedFuture; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletionStage; + +import com.linecorp.armeria.common.HttpRequest; +import com.linecorp.armeria.common.auth.OAuth2Token; +import com.linecorp.armeria.server.ServiceRequestContext; +import com.linecorp.armeria.server.auth.AuthTokenExtractors; +import com.linecorp.armeria.server.auth.Authorizer; +import com.linecorp.centraldogma.server.auth.SessionManager; +import com.linecorp.centraldogma.server.internal.admin.auth.AuthUtil; +import com.linecorp.centraldogma.server.internal.admin.auth.SessionTokenAuthorizer; +import com.linecorp.centraldogma.server.metadata.User; + +/** + * A decorator to grant level from saml authentication result. It is one another implementation of + * {@link SessionTokenAuthorizer}. + */ +public class SessionGroupTokenAuthorizer implements Authorizer { + + private final SessionManager sessionManager; + private final Set adminUserGroups; + + /** + * Constructs a SessionGroupTokenAuthorizer with the specified session manager and admin user groups + * from saml login result. + * + * @param sessionManager the session manager used for managing sessions + * @param adminUserGroups a set of admin user group identifiers + * @throws NullPointerException if sessionManager or adminUserGroups is null + */ + public SessionGroupTokenAuthorizer(SessionManager sessionManager, Set adminUserGroups) { + this.sessionManager = requireNonNull(sessionManager, "sessionManager"); + this.adminUserGroups = requireNonNull(adminUserGroups, "adminUserGroups"); + } + + @Override + public CompletionStage authorize(ServiceRequestContext ctx, HttpRequest data) { + final OAuth2Token token = AuthTokenExtractors.oAuth2().apply(data.headers()); + if (token == null) { + return completedFuture(false); + } + + return sessionManager.get(token.accessToken()) + .thenApply(session -> { + if (session == null) { + return false; + } + + final SamlSession samlSession; + try { + samlSession = session.castRawSession(); + } catch (Exception e) { + return false; + } + + final List roles = + samlSession.groups() + .stream().anyMatch(adminUserGroups::contains) ? + LEVEL_SYSTEM_ADMIN : LEVEL_USER; + + final String username = session.username(); + + final User user = new User(username, roles); + ctx.logBuilder().authenticatedUser("user/" + username); + AuthUtil.setCurrentUser(ctx, user); + return true; + }); + } +} diff --git a/server-auth/saml/src/test/java/com/linecorp/centraldogma/server/auth/saml/AuthenticationDeserializationTest.java b/server-auth/saml/src/test/java/com/linecorp/centraldogma/server/auth/saml/AuthenticationDeserializationTest.java index e508b8f0c..4eb65bcf3 100644 --- a/server-auth/saml/src/test/java/com/linecorp/centraldogma/server/auth/saml/AuthenticationDeserializationTest.java +++ b/server-auth/saml/src/test/java/com/linecorp/centraldogma/server/auth/saml/AuthenticationDeserializationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -29,6 +29,7 @@ import com.linecorp.centraldogma.internal.Jackson; import com.linecorp.centraldogma.server.ConfigValueConverter; import com.linecorp.centraldogma.server.auth.AuthConfig; +import com.linecorp.centraldogma.server.auth.AuthConfigSpec; class AuthenticationDeserializationTest { @@ -51,7 +52,7 @@ void authConfigValueConverter() throws Exception { " \"entityId\": \"dogma-saml\", " + " \"uri\": \"https://dogma.com/signon\"" + "}}}}", SamlAuthProviderFactory.class.getName()); - final AuthConfig authConfig = Jackson.readValue(jsonConfig, ParentConfig.class).authConfig; + final AuthConfigSpec authConfig = Jackson.readValue(jsonConfig, ParentConfig.class).authConfig; final SamlAuthProperties properties = authConfig.properties(SamlAuthProperties.class); assertThat(properties.keyStore().password()).isEqualTo("foo1"); assertThat(properties.keyStore().keyPasswords()).containsOnly(Maps.immutableEntry("dogma", "bar1")); @@ -77,7 +78,7 @@ public String convert(String prefix, String value) { } static class ParentConfig { - private final AuthConfig authConfig; + private final AuthConfigSpec authConfig; @JsonCreator ParentConfig(@JsonProperty("authentication") AuthConfig authConfig) { diff --git a/server-auth/shiro/src/main/java/com/linecorp/centraldogma/server/auth/shiro/ShiroAuthProvider.java b/server-auth/shiro/src/main/java/com/linecorp/centraldogma/server/auth/shiro/ShiroAuthProvider.java index 5baadd449..29f5f8fa1 100644 --- a/server-auth/shiro/src/main/java/com/linecorp/centraldogma/server/auth/shiro/ShiroAuthProvider.java +++ b/server-auth/shiro/src/main/java/com/linecorp/centraldogma/server/auth/shiro/ShiroAuthProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -30,7 +30,7 @@ import org.apache.shiro.util.Factory; import com.linecorp.armeria.server.HttpService; -import com.linecorp.centraldogma.server.auth.AuthConfig; +import com.linecorp.centraldogma.server.auth.AuthConfigSpec; import com.linecorp.centraldogma.server.auth.AuthProvider; import com.linecorp.centraldogma.server.auth.Session; @@ -42,7 +42,7 @@ public final class ShiroAuthProvider implements AuthProvider { private final HttpService loginApiService; private final HttpService logoutApiService; - ShiroAuthProvider(AuthConfig authConfig, + ShiroAuthProvider(AuthConfigSpec authConfig, Ini config, Supplier sessionIdGenerator, Function> loginSessionPropagator, diff --git a/server-auth/shiro/src/main/java/com/linecorp/centraldogma/server/auth/shiro/ShiroAuthProviderFactory.java b/server-auth/shiro/src/main/java/com/linecorp/centraldogma/server/auth/shiro/ShiroAuthProviderFactory.java index e813fbde3..2c81c1361 100644 --- a/server-auth/shiro/src/main/java/com/linecorp/centraldogma/server/auth/shiro/ShiroAuthProviderFactory.java +++ b/server-auth/shiro/src/main/java/com/linecorp/centraldogma/server/auth/shiro/ShiroAuthProviderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -21,7 +21,7 @@ import org.apache.shiro.config.Ini; -import com.linecorp.centraldogma.server.auth.AuthConfig; +import com.linecorp.centraldogma.server.auth.AuthConfigSpec; import com.linecorp.centraldogma.server.auth.AuthProvider; import com.linecorp.centraldogma.server.auth.AuthProviderFactory; import com.linecorp.centraldogma.server.auth.AuthProviderParameters; @@ -31,7 +31,7 @@ */ public final class ShiroAuthProviderFactory implements AuthProviderFactory { - private final Function iniConfigResolver; + private final Function iniConfigResolver; /** * Creates a new instance with the default {@link Ini} config resolver. @@ -43,7 +43,7 @@ public ShiroAuthProviderFactory() { /** * Creates a new instance with the specified {@code iniConfigResolver}. */ - public ShiroAuthProviderFactory(Function iniConfigResolver) { + public ShiroAuthProviderFactory(Function iniConfigResolver) { this.iniConfigResolver = requireNonNull(iniConfigResolver, "iniConfigResolver"); } @@ -57,7 +57,7 @@ public AuthProvider create(AuthProviderParameters parameters) { parameters.logoutSessionPropagator()); } - private static Ini fromConfig(AuthConfig cfg) { + private static Ini fromConfig(AuthConfigSpec cfg) { try { final String iniPath = cfg.properties(String.class); return Ini.fromResourcePath(iniPath); diff --git a/server/src/main/java/com/linecorp/centraldogma/server/AbstractCentralDogmaConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/AbstractCentralDogmaConfig.java new file mode 100644 index 000000000..056eccafa --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/AbstractCentralDogmaConfig.java @@ -0,0 +1,110 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.linecorp.armeria.common.util.InetAddressPredicates.ofCidr; +import static com.linecorp.armeria.common.util.InetAddressPredicates.ofExact; +import static com.linecorp.armeria.server.ClientAddressSource.ofHeader; +import static com.linecorp.armeria.server.ClientAddressSource.ofProxyProtocol; + +import java.net.InetAddress; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.function.Predicate; + +import javax.annotation.Nullable; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; + +import com.linecorp.armeria.common.HttpHeaderNames; +import com.linecorp.armeria.server.ClientAddressSource; +import com.linecorp.centraldogma.server.plugin.PluginConfig; + +/** + * abstract {@link CentralDogma} server configuration. + */ +public abstract class AbstractCentralDogmaConfig implements CentralDogmaConfigSpec { + private final List pluginConfigs; + private final Map, PluginConfig> pluginConfigMap; + + protected AbstractCentralDogmaConfig(@Nullable final List pluginConfigs) { + this.pluginConfigs = firstNonNull(pluginConfigs, ImmutableList.of()); + pluginConfigMap = this.pluginConfigs.stream().collect( + toImmutableMap(PluginConfig::getClass, Function.identity())); + } + + @Override + public List pluginConfigs() { + return pluginConfigs; + } + + @Override + public Map, PluginConfig> pluginConfigMap() { + return pluginConfigMap; + } + + @SuppressWarnings("unchecked") + @Nullable + @Override + public T pluginConfig(Class pluginClass) { + return (T) pluginConfigMap.entrySet() + .stream() + .filter(entry -> pluginClass.isAssignableFrom(entry.getKey())) + .map(Entry::getValue) + .findFirst() + .orElse(null); + } + + protected static Predicate toTrustedProxyAddressPredicate(List trustedProxyAddresses) { + final String first = trustedProxyAddresses.get(0); + Predicate predicate = first.indexOf('/') < 0 ? ofExact(first) : ofCidr(first); + for (int i = 1; i < trustedProxyAddresses.size(); i++) { + final String next = trustedProxyAddresses.get(i); + predicate = predicate.or(next.indexOf('/') < 0 ? ofExact(next) : ofCidr(next)); + } + return predicate; + } + + protected static List toClientAddressSourceList( + @Nullable List clientAddressSources, + boolean useDefaultSources, boolean specifiedProxyProtocol) { + if (clientAddressSources != null && !clientAddressSources.isEmpty()) { + return clientAddressSources.stream() + .map(name -> "PROXY_PROTOCOL".equals(name) ? ofProxyProtocol() + : ofHeader(name)) + .collect(toImmutableList()); + } + + if (useDefaultSources) { + final Builder builder = new Builder<>(); + builder.add(ofHeader(HttpHeaderNames.FORWARDED)); + builder.add(ofHeader(HttpHeaderNames.X_FORWARDED_FOR)); + if (specifiedProxyProtocol) { + builder.add(ofProxyProtocol()); + } + return builder.build(); + } + + return ImmutableList.of(); + } +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/CentralDogma.java b/server/src/main/java/com/linecorp/centraldogma/server/CentralDogma.java index 8d0b05ad3..94a2de8ac 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/CentralDogma.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/CentralDogma.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -117,7 +117,7 @@ import com.linecorp.centraldogma.internal.CsrfToken; import com.linecorp.centraldogma.internal.Jackson; import com.linecorp.centraldogma.internal.thrift.CentralDogmaService; -import com.linecorp.centraldogma.server.auth.AuthConfig; +import com.linecorp.centraldogma.server.auth.AuthConfigSpec; import com.linecorp.centraldogma.server.auth.AuthProvider; import com.linecorp.centraldogma.server.auth.AuthProviderParameters; import com.linecorp.centraldogma.server.auth.SessionManager; @@ -221,6 +221,14 @@ public static CentralDogma forConfig(File configFile) throws IOException { return new CentralDogma(CentralDogmaConfig.load(configFile), Flags.meterRegistry(), ImmutableList.of()); } + /** + * Creates a new instance from the given configuration instance. + */ + public static CentralDogma forConfig(CentralDogmaConfigSpec config, MeterRegistry meterRegistry) { + requireNonNull(config, "config"); + return new CentralDogma(config, meterRegistry, ImmutableList.of()); + } + private final SettableHealthChecker serverHealth = new SettableHealthChecker(false); private final CentralDogmaStartStop startStop; @@ -234,7 +242,7 @@ public static CentralDogma forConfig(File configFile) throws IOException { @Nullable private final PluginGroup pluginsForZoneLeaderOnly; - private final CentralDogmaConfig cfg; + private final CentralDogmaConfigSpec cfg; @Nullable private volatile ProjectManager pm; @Nullable @@ -257,7 +265,46 @@ public static CentralDogma forConfig(File configFile) throws IOException { @Nullable private volatile MirrorRunner mirrorRunner; - CentralDogma(CentralDogmaConfig cfg, MeterRegistry meterRegistry, List plugins) { + /** + * A functional interface that creates an instance of {@link AuthProvider}. + */ + @FunctionalInterface + public interface AuthProviderCreator { + + /** + * Creates an instance of AuthProvider. + * + * @param commandExecutor the command executor + * @param sessionManager the session manager, may be null + * @param mds the metadata service + * @return an instance of AuthProvider, may be null + */ + @Nullable + AuthProvider create(CommandExecutor commandExecutor, + @Nullable SessionManager sessionManager, + MetadataService mds); + } + + /** + * A functional interface for creating an {@link Authorizer} for HTTP requests. + */ + @FunctionalInterface + public interface TokenAuthorizerCreator { + /** + * Creates an {@link Authorizer} instance based on the provided + * {@link MetadataService} and {@link SessionManager}. + * + * @param mds the metadata service used for authorizing requests + * @param sessionManager the session manager for managing user sessions + * @return an {@link Authorizer} for HTTP requests + */ + Authorizer create(MetadataService mds, SessionManager sessionManager); + } + + private AuthProviderCreator authProviderCreator; + private TokenAuthorizerCreator tokenAuthorizerCreator; + + CentralDogma(CentralDogmaConfigSpec cfg, MeterRegistry meterRegistry, List plugins) { this.cfg = requireNonNull(cfg, "cfg"); pluginGroups = PluginGroup.loadPlugins(CentralDogma.class.getClassLoader(), cfg, plugins); pluginsForAllReplicas = pluginGroups.get(PluginTarget.ALL_REPLICAS); @@ -269,6 +316,37 @@ public static CentralDogma forConfig(File configFile) throws IOException { } startStop = new CentralDogmaStartStop(pluginsForAllReplicas); this.meterRegistry = meterRegistry; + + authProviderCreator = this::createAuthProvider; + tokenAuthorizerCreator = (mds, sessionManager) -> new ApplicationTokenAuthorizer(mds::findTokenBySecret) + .orElse(new SessionTokenAuthorizer(sessionManager, + cfg.authConfig().systemAdministrators())); + } + + /** + * Sets the authentication provider creator. + * + * @param authProviderCreator the authentication provider creator, or null to use the default creator + * @return the current instance of CentralDogma + */ + public CentralDogma authProviderCreator(@Nullable AuthProviderCreator authProviderCreator) { + if (authProviderCreator == null) { + this.authProviderCreator = this::createAuthProvider; + } else { + this.authProviderCreator = authProviderCreator; + } + return this; + } + + /** + * Sets the TokenAuthorizerCreator for this CentralDogma instance. + * + * @param tokenAuthorizerCreator the TokenAuthorizerCreator to be set + * @return the current CentralDogma instance for method chaining + */ + public CentralDogma tokenAuthorizerCreator(TokenAuthorizerCreator tokenAuthorizerCreator) { + this.tokenAuthorizerCreator = tokenAuthorizerCreator; + return this; } /** @@ -276,7 +354,7 @@ public static CentralDogma forConfig(File configFile) throws IOException { * * @return the {@link CentralDogmaConfig} instance which is used for configuring this {@link CentralDogma}. */ - public CentralDogmaConfig config() { + public CentralDogmaConfigSpec config() { return cfg; } @@ -364,7 +442,7 @@ public CompletableFuture start() { public CompletableFuture stop() { serverHealth.setHealthy(false); - final Optional gracefulTimeoutOpt = cfg.gracefulShutdownTimeout(); + final Optional gracefulTimeoutOpt = cfg.gracefulShutdownTimeout(); if (gracefulTimeoutOpt.isPresent()) { try { // Sleep 1 second so that clients have some time to redirect traffic according @@ -580,7 +658,7 @@ private CommandExecutor startCommandExecutor( @Nullable private SessionManager initializeSessionManager() throws Exception { - final AuthConfig authCfg = cfg.authConfig(); + final AuthConfigSpec authCfg = cfg.authConfig(); if (authCfg == null) { return null; } @@ -620,7 +698,7 @@ private Server startServer(ProjectManager pm, CommandExecutor executor, if (needsTls) { try { - final TlsConfig tlsConfig = cfg.tls(); + final TlsConfigSpec tlsConfig = cfg.tls(); if (tlsConfig != null) { try (InputStream keyCertChainInputStream = tlsConfig.keyCertChainInputStream(); InputStream keyInputStream = tlsConfig.keyInputStream()) { @@ -650,7 +728,7 @@ private Server startServer(ProjectManager pm, CommandExecutor executor, final MetadataService mds = new MetadataService(pm, executor); final WatchService watchService = new WatchService(meterRegistry); - final AuthProvider authProvider = createAuthProvider(executor, sessionManager, mds); + final AuthProvider authProvider = authProviderCreator.create(executor, sessionManager, mds); final ProjectApiManager projectApiManager = new ProjectApiManager(pm, executor, mds); configureThriftService(sb, projectApiManager, executor, watchService, mds); @@ -732,7 +810,7 @@ static HttpFile webAppTitleFile(@Nullable String webAppTitle, String hostname) { @Nullable private AuthProvider createAuthProvider( CommandExecutor commandExecutor, @Nullable SessionManager sessionManager, MetadataService mds) { - final AuthConfig authCfg = cfg.authConfig(); + final AuthConfigSpec authCfg = cfg.authConfig(); if (authCfg == null) { return null; } @@ -759,7 +837,7 @@ private CommandExecutor newZooKeeperCommandExecutor( @Nullable Consumer onReleaseLeadership, @Nullable Consumer onTakeZoneLeadership, @Nullable Consumer onReleaseZoneLeadership) { - final ZooKeeperReplicationConfig zkCfg = (ZooKeeperReplicationConfig) cfg.replicationConfig(); + final ZooKeeperReplicationConfigSpec zkCfg = (ZooKeeperReplicationConfigSpec) cfg.replicationConfig(); // Delete the old UUID replica ID which is not used anymore. final File dataDir = cfg.dataDir(); @@ -810,13 +888,10 @@ private Function authService( if (authProvider == null) { return AuthService.newDecorator(new CsrfTokenAuthorizer()); } - final AuthConfig authCfg = cfg.authConfig(); + final AuthConfigSpec authCfg = cfg.authConfig(); assert authCfg != null : "authCfg"; assert sessionManager != null : "sessionManager"; - final Authorizer tokenAuthorizer = - new ApplicationTokenAuthorizer(mds::findTokenBySecret) - .orElse(new SessionTokenAuthorizer(sessionManager, - authCfg.systemAdministrators())); + final Authorizer tokenAuthorizer = tokenAuthorizerCreator.create(mds, sessionManager); return AuthService.builder() .add(tokenAuthorizer) .onFailure(new CentralDogmaAuthFailureHandler()) @@ -895,7 +970,7 @@ protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) { } }); - final AuthConfig authCfg = cfg.authConfig(); + final AuthConfigSpec authCfg = cfg.authConfig(); assert authCfg != null : "authCfg"; apiV1ServiceBuilder .annotatedService(new MetadataApiService(executor, mds, authCfg.loginNameNormalizer())) @@ -955,7 +1030,7 @@ protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) { sb.errorHandler(new HttpApiExceptionHandler()); } - private static void configCors(ServerBuilder sb, @Nullable CorsConfig corsConfig) { + private static void configCors(ServerBuilder sb, @Nullable CorsConfigSpec corsConfig) { if (corsConfig == null) { return; } @@ -968,7 +1043,7 @@ private static void configCors(ServerBuilder sb, @Nullable CorsConfig corsConfig .newDecorator()); } - private static void configManagement(ServerBuilder sb, @Nullable ManagementConfig managementConfig) { + private static void configManagement(ServerBuilder sb, @Nullable ManagementConfigSpec managementConfig) { if (managementConfig == null) { return; } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/CentralDogmaBuilder.java b/server/src/main/java/com/linecorp/centraldogma/server/CentralDogmaBuilder.java index 1e35ced3c..08ceab165 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/CentralDogmaBuilder.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/CentralDogmaBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -17,9 +17,6 @@ package com.linecorp.centraldogma.server; import static com.google.common.base.Preconditions.checkArgument; -import static com.linecorp.centraldogma.server.auth.AuthConfig.DEFAULT_SESSION_CACHE_SPEC; -import static com.linecorp.centraldogma.server.auth.AuthConfig.DEFAULT_SESSION_TIMEOUT_MILLIS; -import static com.linecorp.centraldogma.server.auth.AuthConfig.DEFAULT_SESSION_VALIDATION_SCHEDULE; import static com.linecorp.centraldogma.server.internal.storage.repository.RepositoryCache.validateCacheSpec; import static java.util.Objects.requireNonNull; @@ -46,6 +43,7 @@ import com.linecorp.armeria.server.ServerPort; import com.linecorp.centraldogma.internal.Jackson; import com.linecorp.centraldogma.server.auth.AuthConfig; +import com.linecorp.centraldogma.server.auth.AuthConfigSpec; import com.linecorp.centraldogma.server.auth.AuthProvider; import com.linecorp.centraldogma.server.auth.AuthProviderFactory; import com.linecorp.centraldogma.server.auth.Session; @@ -120,9 +118,9 @@ public final class CentralDogmaBuilder { private AuthProviderFactory authProviderFactory; private final ImmutableSet.Builder systemAdministrators = new Builder<>(); private boolean caseSensitiveLoginNames; - private String sessionCacheSpec = DEFAULT_SESSION_CACHE_SPEC; - private long sessionTimeoutMillis = DEFAULT_SESSION_TIMEOUT_MILLIS; - private String sessionValidationSchedule = DEFAULT_SESSION_VALIDATION_SCHEDULE; + private String sessionCacheSpec = AuthConfigSpec.DEFAULT_SESSION_CACHE_SPEC; + private long sessionTimeoutMillis = AuthConfigSpec.DEFAULT_SESSION_TIMEOUT_MILLIS; + private String sessionValidationSchedule = AuthConfigSpec.DEFAULT_SESSION_VALIDATION_SCHEDULE; @Nullable private Object authProviderProperties; private int writeQuota; @@ -446,7 +444,7 @@ public CentralDogmaBuilder caseSensitiveLoginNames(boolean caseSensitiveLoginNam /** * Sets the cache specification which determines the capacity and behavior of the cache for * {@link Session} of the server. See {@link CaffeineSpec} for the syntax of the spec. - * If unspecified, the default cache spec of {@value AuthConfig#DEFAULT_SESSION_CACHE_SPEC} + * If unspecified, the default cache spec of {@value AuthConfigSpec#DEFAULT_SESSION_CACHE_SPEC} * is used. */ public CentralDogmaBuilder sessionCacheSpec(String sessionCacheSpec) { @@ -456,7 +454,7 @@ public CentralDogmaBuilder sessionCacheSpec(String sessionCacheSpec) { /** * Sets the session timeout for administrative web application, in milliseconds. - * If unspecified, {@value AuthConfig#DEFAULT_SESSION_TIMEOUT_MILLIS} is used. + * If unspecified, {@value AuthConfigSpec#DEFAULT_SESSION_TIMEOUT_MILLIS} is used. */ public CentralDogmaBuilder sessionTimeoutMillis(long sessionTimeoutMillis) { this.sessionTimeoutMillis = sessionTimeoutMillis; @@ -465,7 +463,7 @@ public CentralDogmaBuilder sessionTimeoutMillis(long sessionTimeoutMillis) { /** * Sets the session timeout for administrative web application. - * If unspecified, {@value AuthConfig#DEFAULT_SESSION_TIMEOUT_MILLIS} is used. + * If unspecified, {@value AuthConfigSpec#DEFAULT_SESSION_TIMEOUT_MILLIS} is used. */ public CentralDogmaBuilder sessionTimeout(Duration sessionTimeout) { return sessionTimeoutMillis( @@ -474,7 +472,7 @@ public CentralDogmaBuilder sessionTimeout(Duration sessionTimeout) { /** * Sets a schedule for validating sessions. - * If unspecified, {@value AuthConfig#DEFAULT_SESSION_VALIDATION_SCHEDULE} is used. + * If unspecified, {@value AuthConfigSpec#DEFAULT_SESSION_VALIDATION_SCHEDULE} is used. */ public CentralDogmaBuilder sessionValidationSchedule(String sessionValidationSchedule) { this.sessionValidationSchedule = diff --git a/server/src/main/java/com/linecorp/centraldogma/server/CentralDogmaConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/CentralDogmaConfig.java index 0b3894d45..367ddc97b 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/CentralDogmaConfig.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/CentralDogmaConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -18,12 +18,6 @@ import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.linecorp.armeria.common.util.InetAddressPredicates.ofCidr; -import static com.linecorp.armeria.common.util.InetAddressPredicates.ofExact; -import static com.linecorp.armeria.server.ClientAddressSource.ofHeader; -import static com.linecorp.armeria.server.ClientAddressSource.ofProxyProtocol; import static com.linecorp.centraldogma.server.CentralDogmaBuilder.DEFAULT_MAX_REMOVED_REPOSITORY_AGE_MILLIS; import static com.linecorp.centraldogma.server.CentralDogmaBuilder.DEFAULT_NUM_REPOSITORY_WORKERS; import static com.linecorp.centraldogma.server.CentralDogmaBuilder.DEFAULT_REPOSITORY_CACHE_SPEC; @@ -41,7 +35,6 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.ServiceLoader; -import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -69,28 +62,25 @@ import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.util.StdConverter; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Streams; -import com.linecorp.armeria.common.HttpHeaderNames; import com.linecorp.armeria.common.SessionProtocol; import com.linecorp.armeria.server.ClientAddressSource; import com.linecorp.armeria.server.ServerPort; import com.linecorp.centraldogma.internal.Jackson; import com.linecorp.centraldogma.server.auth.AuthConfig; +import com.linecorp.centraldogma.server.auth.AuthConfigSpec; import com.linecorp.centraldogma.server.plugin.PluginConfig; import com.linecorp.centraldogma.server.plugin.PluginConfigDeserializer; -import com.linecorp.centraldogma.server.plugin.PluginTarget; -import com.linecorp.centraldogma.server.storage.repository.Repository; import io.netty.util.NetUtil; /** * {@link CentralDogma} server configuration. */ -public final class CentralDogmaConfig { +public final class CentralDogmaConfig extends AbstractCentralDogmaConfig { private static final Logger logger = LoggerFactory.getLogger(CentralDogmaConfig.class); @@ -265,7 +255,6 @@ public static CentralDogmaConfig load(String json) throws JsonMappingException, private final CorsConfig corsConfig; private final List pluginConfigs; - private final Map, PluginConfig> pluginConfigMap; @Nullable private final ManagementConfig managementConfig; @@ -302,6 +291,8 @@ public static CentralDogmaConfig load(String json) throws JsonMappingException, @JsonProperty("management") @Nullable ManagementConfig managementConfig, @JsonProperty("zone") @Nullable ZoneConfig zoneConfig) { + super(pluginConfigs); + this.dataDir = requireNonNull(dataDir, "dataDir"); this.ports = ImmutableList.copyOf(requireNonNull(ports, "ports")); checkArgument(!ports.isEmpty(), "ports must have at least one port."); @@ -346,130 +337,94 @@ public static CentralDogmaConfig load(String json) throws JsonMappingException, this.writeQuotaPerRepository = writeQuotaPerRepository; this.corsConfig = corsConfig; this.pluginConfigs = firstNonNull(pluginConfigs, ImmutableList.of()); - pluginConfigMap = this.pluginConfigs.stream().collect( - toImmutableMap(PluginConfig::getClass, Function.identity())); this.managementConfig = managementConfig; this.zoneConfig = zoneConfig; } - /** - * Returns the data directory. - */ @JsonProperty + @Override public File dataDir() { return dataDir; } - /** - * Returns the {@link ServerPort}s. - */ @JsonProperty @JsonSerialize(contentUsing = ServerPortSerializer.class) + @Override public List ports() { return ports; } - /** - * Returns the TLS configuration. - */ @Nullable @JsonProperty - public TlsConfig tls() { + @Override + public TlsConfigSpec tls() { return tls; } - /** - * Returns the IP addresses of the trusted proxy servers. If trusted, the sources specified in - * {@link #clientAddressSources()} will be used to determine the actual IP address of clients. - */ @Nullable @JsonProperty + @Override public List trustedProxyAddresses() { return trustedProxyAddresses; } - /** - * Returns the sources that determines a client address. For example: - *
    - *
  • {@code "forwarded"}
  • - *
  • {@code "x-forwarded-for"}
  • - *
  • {@code "PROXY_PROTOCOL"}
  • - *
- * - */ @Nullable @JsonProperty + @Override public List clientAddressSources() { return clientAddressSources; } - /** - * Returns the number of event loop threads. - */ @JsonProperty @JsonSerialize(converter = OptionalConverter.class) + @Override public Optional numWorkers() { return Optional.ofNullable(numWorkers); } - /** - * Returns the maximum number of established connections. - */ @JsonProperty @JsonSerialize(converter = OptionalConverter.class) + @Override public Optional maxNumConnections() { return Optional.ofNullable(maxNumConnections); } - /** - * Returns the request timeout in milliseconds. - */ @JsonProperty @JsonSerialize(converter = OptionalConverter.class) + @Override public Optional requestTimeoutMillis() { return Optional.ofNullable(requestTimeoutMillis); } - /** - * Returns the timeout of an idle connection in milliseconds. - */ @JsonProperty @JsonSerialize(converter = OptionalConverter.class) + @Override public Optional idleTimeoutMillis() { return Optional.ofNullable(idleTimeoutMillis); } - /** - * Returns the maximum length of request content in bytes. - */ @JsonProperty @JsonSerialize(converter = OptionalConverter.class) + @Override public Optional maxFrameLength() { return Optional.ofNullable(maxFrameLength); } - /** - * Returns the number of repository worker threads. - */ @JsonProperty - int numRepositoryWorkers() { + @Override + public int numRepositoryWorkers() { return numRepositoryWorkers; } - /** - * Returns the maximum age of a removed repository in milliseconds. A removed repository is first marked - * as removed, and then is purged permanently once the amount of time returned by this property passes - * since marked. - */ @JsonProperty + @Override public long maxRemovedRepositoryAgeMillis() { return maxRemovedRepositoryAgeMillis; } /** - * Returns the {@code repositoryCacheSpec}. - * - * @deprecated Use {@link #repositoryCacheSpec()}. + * Replaced to {@link CentralDogmaConfig#repositoryCacheSpec() }. + * @deprecated This cache spec is replaced to {@link CentralDogmaConfig#repositoryCacheSpec() }. */ @JsonProperty @Deprecated @@ -477,123 +432,88 @@ public String cacheSpec() { return repositoryCacheSpec; } - /** - * Returns the cache spec of the repository cache. - */ @JsonProperty + @Override public String repositoryCacheSpec() { return repositoryCacheSpec; } - /** - * Returns the graceful shutdown timeout. - */ @JsonProperty @JsonSerialize(converter = OptionalConverter.class) - public Optional gracefulShutdownTimeout() { + @Override + public Optional gracefulShutdownTimeout() { return Optional.ofNullable(gracefulShutdownTimeout); } - /** - * Returns whether web app is enabled. - */ @JsonProperty + @Override public boolean isWebAppEnabled() { return webAppEnabled; } - /** - * Returns the title of the web app. - */ @Nullable @JsonProperty("webAppTitle") + @Override public String webAppTitle() { return webAppTitle; } - /** - * Returns the {@link ReplicationConfig}. - */ @JsonProperty("replication") + @Override public ReplicationConfig replicationConfig() { return replicationConfig; } - /** - * Returns whether a CSRF token is required for Thrift clients. Note that it's not safe to enable this - * feature. It only exists for a legacy Thrift client that does not send a CSRF token. - */ @JsonProperty + @Override public boolean isCsrfTokenRequiredForThrift() { return csrfTokenRequiredForThrift; } - /** - * Returns the access log format. - */ @JsonProperty @Nullable + @Override public String accessLogFormat() { return accessLogFormat; } - /** - * Returns the {@link AuthConfig}. - */ @Nullable @JsonProperty("authentication") - public AuthConfig authConfig() { + @Override + public AuthConfigSpec authConfig() { return authConfig; } - /** - * Returns the maximum allowed write quota per {@link Repository}. - */ @Nullable @JsonProperty("writeQuotaPerRepository") - public QuotaConfig writeQuotaPerRepository() { + @Override + public QuotaConfigSpec writeQuotaPerRepository() { return writeQuotaPerRepository; } - /** - * Returns the {@link CorsConfig}. - */ @Nullable @JsonProperty("cors") - public CorsConfig corsConfig() { + @Override + public CorsConfigSpec corsConfig() { return corsConfig; } - /** - * Returns the list of {@link PluginConfig}s. - */ @JsonProperty("pluginConfigs") + @Override public List pluginConfigs() { return pluginConfigs; } - /** - * Returns the map of {@link PluginConfig}s. - */ - public Map, PluginConfig> pluginConfigMap() { - return pluginConfigMap; - } - - /** - * Returns the {@link ManagementConfig}. - */ @Nullable @JsonProperty("management") - public ManagementConfig managementConfig() { + @Override + public ManagementConfigSpec managementConfig() { return managementConfig; } - /** - * Returns the zone information of the server. - * Note that the zone must be specified to use the {@link PluginTarget#ZONE_LEADER_ONLY} target. - */ @Nullable @JsonProperty("zone") + @Override public ZoneConfig zone() { return zoneConfig; } @@ -607,46 +527,16 @@ public String toString() { } } - Predicate trustedProxyAddressPredicate() { + @Override + public Predicate trustedProxyAddressPredicate() { return trustedProxyAddressPredicate; } - List clientAddressSourceList() { + @Override + public List clientAddressSourceList() { return clientAddressSourceList; } - private static Predicate toTrustedProxyAddressPredicate(List trustedProxyAddresses) { - final String first = trustedProxyAddresses.get(0); - Predicate predicate = first.indexOf('/') < 0 ? ofExact(first) : ofCidr(first); - for (int i = 1; i < trustedProxyAddresses.size(); i++) { - final String next = trustedProxyAddresses.get(i); - predicate = predicate.or(next.indexOf('/') < 0 ? ofExact(next) : ofCidr(next)); - } - return predicate; - } - - private static List toClientAddressSourceList( - @Nullable List clientAddressSources, - boolean useDefaultSources, boolean specifiedProxyProtocol) { - if (clientAddressSources != null && !clientAddressSources.isEmpty()) { - return clientAddressSources.stream().map( - name -> "PROXY_PROTOCOL".equals(name) ? ofProxyProtocol() : ofHeader(name)) - .collect(toImmutableList()); - } - - if (useDefaultSources) { - final Builder builder = new Builder<>(); - builder.add(ofHeader(HttpHeaderNames.FORWARDED)); - builder.add(ofHeader(HttpHeaderNames.X_FORWARDED_FOR)); - if (specifiedProxyProtocol) { - builder.add(ofProxyProtocol()); - } - return builder.build(); - } - - return ImmutableList.of(); - } - static final class ServerPortSerializer extends JsonSerializer { @Override public void serialize(ServerPort value, diff --git a/server/src/main/java/com/linecorp/centraldogma/server/CentralDogmaConfigSpec.java b/server/src/main/java/com/linecorp/centraldogma/server/CentralDogmaConfigSpec.java new file mode 100644 index 000000000..b5acf2681 --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/CentralDogmaConfigSpec.java @@ -0,0 +1,210 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server; + +import java.io.File; +import java.net.InetAddress; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; + +import javax.annotation.Nullable; + +import com.linecorp.armeria.server.ClientAddressSource; +import com.linecorp.armeria.server.ServerPort; +import com.linecorp.centraldogma.server.auth.AuthConfigSpec; +import com.linecorp.centraldogma.server.plugin.PluginConfig; +import com.linecorp.centraldogma.server.plugin.PluginTarget; +import com.linecorp.centraldogma.server.storage.repository.Repository; + +/** + * {@link CentralDogma} server configuration spec. + */ +public interface CentralDogmaConfigSpec { + /** + * Returns the data directory. + */ + File dataDir(); + + /** + * Returns the {@link ServerPort}s. + */ + List ports(); + + /** + * Returns the TLS configuration. + */ + @Nullable + TlsConfigSpec tls(); + + /** + * Returns the IP addresses of the trusted proxy servers. If trusted, the sources specified in + * {@link #clientAddressSources()} will be used to determine the actual IP address of clients. + */ + @Nullable + List trustedProxyAddresses(); + + /** + * Returns the sources that determines a client address. For example: + *
    + *
  • {@code "forwarded"}
  • + *
  • {@code "x-forwarded-for"}
  • + *
  • {@code "PROXY_PROTOCOL"}
  • + *
+ * + */ + @Nullable + List clientAddressSources(); + + /** + * Returns the number of event loop threads. + */ + Optional numWorkers(); + + /** + * Returns the maximum number of established connections. + */ + Optional maxNumConnections(); + + /** + * Returns the request timeout in milliseconds. + */ + Optional requestTimeoutMillis(); + + /** + * Returns the timeout of an idle connection in milliseconds. + */ + Optional idleTimeoutMillis(); + + /** + * Returns the maximum length of request content in bytes. + */ + Optional maxFrameLength(); + + /** + * Returns the number of repository worker threads. + */ + int numRepositoryWorkers(); + + /** + * Returns the maximum age of a removed repository in milliseconds. A removed repository is first marked + * as removed, and then is purged permanently once the amount of time returned by this property passes + * since marked. + */ + long maxRemovedRepositoryAgeMillis(); + + /** + * Returns the cache spec of the repository cache. + */ + String repositoryCacheSpec(); + + /** + * Returns the graceful shutdown timeout. + */ + Optional gracefulShutdownTimeout(); + + /** + * Returns whether web app is enabled. + */ + + boolean isWebAppEnabled(); + + /** + * Returns the title of the web app. + */ + @Nullable + String webAppTitle(); + + /** + * Returns the {@link ReplicationConfig}. + */ + + ReplicationConfig replicationConfig(); + + /** + * Returns whether a CSRF token is required for Thrift clients. Note that it's not safe to enable this + * feature. It only exists for a legacy Thrift client that does not send a CSRF token. + */ + + boolean isCsrfTokenRequiredForThrift(); + + /** + * Returns the access log format. + */ + @Nullable + String accessLogFormat(); + + /** + * Returns the {@link AuthConfigSpec}. + */ + @Nullable + AuthConfigSpec authConfig(); + + /** + * Returns the maximum allowed write quota per {@link Repository}. + */ + @Nullable + QuotaConfigSpec writeQuotaPerRepository(); + + /** + * Returns the {@link CorsConfigSpec}. + */ + @Nullable + CorsConfigSpec corsConfig(); + + /** + * Returns the list of {@link PluginConfig}s. + */ + List pluginConfigs(); + + /** + * Returns the map of {@link PluginConfig}s. + * @deprecated This will be removed soon. + */ + @Deprecated + Map, PluginConfig> pluginConfigMap(); + + /** + * Returns the {@link PluginConfig} with casting. + */ + @Nullable + T pluginConfig(Class pluginClass); + + /** + * Returns the {@link ManagementConfig}. + */ + @Nullable + ManagementConfigSpec managementConfig(); + + /** + * Returns the zone information of the server. + * Note that the zone must be specified to use the {@link PluginTarget#ZONE_LEADER_ONLY} target. + */ + @Nullable + ZoneConfigSpec zone(); + + /** + * Returns the predicate of trusted proxy address. + */ + Predicate trustedProxyAddressPredicate(); + + /** + * Returns the client address source list. + */ + List clientAddressSourceList(); +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/CorsConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/CorsConfig.java index aec3d4a8a..f793aa2d8 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/CorsConfig.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/CorsConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -28,9 +28,7 @@ /** * CORS configuration. */ -public final class CorsConfig { - - private static final int DEFAULT_MAX_AGE = 7200; +public final class CorsConfig implements CorsConfigSpec { private final List allowedOrigins; private final int maxAgeSeconds; @@ -60,7 +58,7 @@ public CorsConfig(@JsonProperty("allowedOrigins") Object allowedOrigins, } if (maxAgeSeconds == null) { - maxAgeSeconds = DEFAULT_MAX_AGE; + maxAgeSeconds = CorsConfigSpec.DEFAULT_MAX_AGE; } if (maxAgeSeconds <= 0) { throw new IllegalArgumentException( @@ -70,19 +68,14 @@ public CorsConfig(@JsonProperty("allowedOrigins") Object allowedOrigins, this.maxAgeSeconds = maxAgeSeconds; } - /** - * Returns the list of origins which are allowed a CORS policy. - */ @JsonProperty + @Override public List allowedOrigins() { return allowedOrigins; } - /** - * Returns how long in seconds the results of a preflight request can be cached. - * If unspecified, the default of {@code 7200} seconds is returned. - */ @JsonProperty + @Override public int maxAgeSeconds() { return maxAgeSeconds; } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/CorsConfigSpec.java b/server/src/main/java/com/linecorp/centraldogma/server/CorsConfigSpec.java new file mode 100644 index 000000000..5623afc15 --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/CorsConfigSpec.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * CORS configuration spec. + */ +public interface CorsConfigSpec { + int DEFAULT_MAX_AGE = 7200; + + /** + * Returns the list of origins which are allowed a CORS policy. + */ + @JsonProperty + List allowedOrigins(); + + /** + * Returns how long in seconds the results of a preflight request can be cached. + * If unspecified, the default of {@code 7200} seconds is returned. + */ + @JsonProperty + int maxAgeSeconds(); +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/GracefulShutdownTimeout.java b/server/src/main/java/com/linecorp/centraldogma/server/GracefulShutdownTimeout.java index 8bafd8626..470ca8af4 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/GracefulShutdownTimeout.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/GracefulShutdownTimeout.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -23,7 +23,7 @@ /** * Graceful shutdown timeout. */ -public final class GracefulShutdownTimeout { +public final class GracefulShutdownTimeout implements GracefulShutdownTimeoutSpec { private final long quietPeriodMillis; private final long timeoutMillis; @@ -47,18 +47,14 @@ public GracefulShutdownTimeout( this.timeoutMillis = timeoutMillis; } - /** - * Returns the quiet period of graceful shutdown process, in milliseconds. - */ @JsonProperty("quietPeriodMillis") + @Override public long quietPeriodMillis() { return quietPeriodMillis; } - /** - * Returns the timeout of graceful shutdown process, in milliseconds. - */ @JsonProperty("timeoutMillis") + @Override public long timeoutMillis() { return timeoutMillis; } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/GracefulShutdownTimeoutSpec.java b/server/src/main/java/com/linecorp/centraldogma/server/GracefulShutdownTimeoutSpec.java new file mode 100644 index 000000000..ce3421e42 --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/GracefulShutdownTimeoutSpec.java @@ -0,0 +1,32 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server; + +/** + * Graceful shutdown timeout spec. + */ +public interface GracefulShutdownTimeoutSpec { + /** + * Returns the quiet period of graceful shutdown process, in milliseconds. + */ + long quietPeriodMillis(); + + /** + * Returns the timeout of graceful shutdown process, in milliseconds. + */ + long timeoutMillis(); +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/ManagementConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/ManagementConfig.java index 96c7da8b9..2e34ede61 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/ManagementConfig.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/ManagementConfig.java @@ -32,9 +32,7 @@ /** * A configuration for the {@link ManagementService}. */ -public final class ManagementConfig { - private static final String DEFAULT_PROTOCOL = "http"; - private static final String DEFAULT_PATH = "/internal/management"; +public final class ManagementConfig implements ManagementConfigSpec { private final SessionProtocol protocol; private final @Nullable String address; @@ -70,34 +68,26 @@ public ManagementConfig(@Nullable SessionProtocol protocol, this.path = firstNonNull(path, DEFAULT_PATH); } - /** - * Returns the protocol of the management service. - */ @JsonProperty("protocol") + @Override public SessionProtocol protocol() { return protocol; } - /** - * Returns the address of the management service. - */ @JsonProperty("address") + @Override public @Nullable String address() { return address; } - /** - * Returns the port of the management service. - */ @JsonProperty("port") + @Override public int port() { return port; } - /** - * Returns the path of the management service. - */ @JsonProperty("path") + @Override public String path() { return path; } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/ManagementConfigSpec.java b/server/src/main/java/com/linecorp/centraldogma/server/ManagementConfigSpec.java new file mode 100644 index 000000000..a92d3e849 --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/ManagementConfigSpec.java @@ -0,0 +1,50 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server; + +import com.linecorp.armeria.common.SessionProtocol; +import com.linecorp.armeria.common.annotation.Nullable; +import com.linecorp.armeria.server.management.ManagementService; + +/** + * A configuration spec for the {@link ManagementService}. + */ +public interface ManagementConfigSpec { + String DEFAULT_PROTOCOL = "http"; + String DEFAULT_PATH = "/internal/management"; + + /** + * Returns the protocol of the management service. + */ + SessionProtocol protocol(); + + /** + * Returns the address of the management service. + */ + @Nullable + String address(); + + /** + * Returns the port of the management service. + */ + int port(); + + /** + * Returns the path of the management service. + */ + String path(); +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/PluginGroup.java b/server/src/main/java/com/linecorp/centraldogma/server/PluginGroup.java index a68c12d0d..9b5f8dff4 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/PluginGroup.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/PluginGroup.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -30,6 +30,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import javax.annotation.Nullable; @@ -69,7 +70,7 @@ final class PluginGroup { */ @VisibleForTesting @Nullable - static PluginGroup loadPlugins(PluginTarget target, CentralDogmaConfig config) { + static PluginGroup loadPlugins(PluginTarget target, CentralDogmaConfigSpec config) { return loadPlugins(PluginGroup.class.getClassLoader(), config, ImmutableList.of()).get(target); } @@ -80,27 +81,33 @@ static PluginGroup loadPlugins(PluginTarget target, CentralDogmaConfig config) { * * @param classLoader which is used to load the {@link Plugin}s */ - static Map loadPlugins(ClassLoader classLoader, CentralDogmaConfig config, + static Map loadPlugins(ClassLoader classLoader, CentralDogmaConfigSpec config, List plugins) { requireNonNull(classLoader, "classLoader"); requireNonNull(config, "config"); final ServiceLoader loader = ServiceLoader.load(Plugin.class, classLoader); - final ImmutableMap.Builder, Plugin> allPlugins = new ImmutableMap.Builder<>(); - for (Plugin plugin : Iterables.concat(plugins, loader)) { - if (plugin.isEnabled(config)) { - allPlugins.put(plugin.configType(), plugin); - } + + final List allPlugins = StreamSupport.stream(Iterables.concat(plugins, loader).spliterator(), + false) + .filter(plugin -> plugin.isEnabled(config)) + .collect(toImmutableList()); + + final long uniquePluginCounts = allPlugins.stream() + .map(Plugin::getClass) + .distinct() + .count(); + + if (allPlugins.size() != uniquePluginCounts) { + throw new IllegalArgumentException("Found duplicated plugins"); } - // IllegalArgumentException is thrown if there are duplicate keys. - final Map, Plugin> pluginMap = allPlugins.build(); - if (pluginMap.isEmpty()) { + if (allPlugins.isEmpty()) { return ImmutableMap.of(); } final Map pluginGroups = - pluginMap.values() + allPlugins .stream() .collect(Collectors.groupingBy(plugin -> plugin.target(config))) .entrySet() @@ -150,7 +157,7 @@ T findFirstPlugin(Class clazz) { /** * Starts the {@link Plugin}s managed by this {@link PluginGroup}. */ - CompletableFuture start(CentralDogmaConfig config, ProjectManager projectManager, + CompletableFuture start(CentralDogmaConfigSpec config, ProjectManager projectManager, CommandExecutor commandExecutor, MeterRegistry meterRegistry, ScheduledExecutorService purgeWorker, InternalProjectInitializer internalProjectInitializer) { @@ -162,7 +169,7 @@ CompletableFuture start(CentralDogmaConfig config, ProjectManager projectM /** * Stops the {@link Plugin}s managed by this {@link PluginGroup}. */ - CompletableFuture stop(CentralDogmaConfig config, ProjectManager projectManager, + CompletableFuture stop(CentralDogmaConfigSpec config, ProjectManager projectManager, CommandExecutor commandExecutor, MeterRegistry meterRegistry, ScheduledExecutorService purgeWorker, InternalProjectInitializer internalProjectInitializer) { diff --git a/server/src/main/java/com/linecorp/centraldogma/server/QuotaConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/QuotaConfig.java index 5f9cb8c13..2da7d130a 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/QuotaConfig.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/QuotaConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -24,7 +24,7 @@ /** * {@link CentralDogma} API quota configuration. */ -public final class QuotaConfig { +public final class QuotaConfig implements QuotaConfigSpec { private final int requestQuota; private final int timeWindowSeconds; @@ -42,29 +42,18 @@ public QuotaConfig(@JsonProperty("requestQuota") int requestQuota, this.timeWindowSeconds = timeWindowSeconds; } - /** - * Returns a time windows in seconds which is created with the given {@code timeWindowSeconds}. - */ @JsonProperty + @Override public int timeWindowSeconds() { return timeWindowSeconds; } - /** - * Returns a maximum number of acceptable requests which is created with the given {@code requestQuota}. - */ @JsonProperty + @Override public int requestQuota() { return requestQuota; } - /** - * Returns a maximum number of acceptable requests per second. - */ - public double permitsPerSecond() { - return requestQuota() * 1.0 / timeWindowSeconds(); - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/server/src/main/java/com/linecorp/centraldogma/server/QuotaConfigSpec.java b/server/src/main/java/com/linecorp/centraldogma/server/QuotaConfigSpec.java new file mode 100644 index 000000000..16b66d4f3 --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/QuotaConfigSpec.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server; + +/** + * {@link CentralDogma} API quota configuration spec. + */ +public interface QuotaConfigSpec { + /** + * Returns a time windows in seconds which is created with the given {@code timeWindowSeconds}. + */ + int timeWindowSeconds(); + + /** + * Returns a maximum number of acceptable requests which is created with the given {@code requestQuota}. + */ + int requestQuota(); + + /** + * Returns a maximum number of acceptable requests per second. + */ + default double permitsPerSecond() { + return requestQuota() * 1.0 / timeWindowSeconds(); + } +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/TlsConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/TlsConfig.java index a4ea2af80..241820dcf 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/TlsConfig.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/TlsConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -35,7 +35,7 @@ /** * TLS configuration. */ -public final class TlsConfig { +public final class TlsConfig implements TlsConfigSpec { @Nullable private final File keyCertChainFile; @@ -103,18 +103,14 @@ public File keyFile() { return keyFile; } - /** - * Returns an {@link InputStream} of the certificate chain. - */ @MustBeClosed + @Override public InputStream keyCertChainInputStream() { return inputStream(keyCertChainFile, keyCertChain, "keyCertChain"); } - /** - * Returns an {@link InputStream} of the private key. - */ @MustBeClosed + @Override public InputStream keyInputStream() { return inputStream(keyFile, key, "key"); } @@ -138,11 +134,9 @@ private static InputStream inputStream(@Nullable File file, return new ByteArrayInputStream(converted.getBytes()); } - /** - * Returns a password for the private key file. Return {@code null} if no password is set. - */ @JsonProperty @Nullable + @Override public String keyPassword() { return convertValue(keyPassword, "tls.keyPassword"); } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/TlsConfigSpec.java b/server/src/main/java/com/linecorp/centraldogma/server/TlsConfigSpec.java new file mode 100644 index 000000000..4bcbcc199 --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/TlsConfigSpec.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server; + +import java.io.InputStream; + +import javax.annotation.Nullable; + +import com.google.errorprone.annotations.MustBeClosed; + +/** + * TLS configuration spec. + */ +public interface TlsConfigSpec { + /** + * Returns an {@link InputStream} of the certificate chain. + */ + @MustBeClosed + InputStream keyCertChainInputStream(); + + /** + * Returns an {@link InputStream} of the private key. + */ + @MustBeClosed + InputStream keyInputStream(); + + /** + * Returns a password for the private key file. Return {@code null} if no password is set. + */ + @Nullable + String keyPassword(); +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/ZoneConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/ZoneConfig.java index 96bde8aca..00d9b015d 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/ZoneConfig.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/ZoneConfig.java @@ -31,7 +31,7 @@ /** * A configuration class for the zone. */ -public final class ZoneConfig { +public final class ZoneConfig implements ZoneConfigSpec { private final String currentZone; private final List allZones; @@ -50,18 +50,14 @@ public ZoneConfig(@JsonProperty("currentZone") String currentZone, currentZone, allZones); } - /** - * Returns the current zone. - */ @JsonProperty("currentZone") + @Override public String currentZone() { return currentZone; } - /** - * Returns all zones. - */ @JsonProperty("allZones") + @Override public List allZones() { return allZones; } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/ZoneConfigSpec.java b/server/src/main/java/com/linecorp/centraldogma/server/ZoneConfigSpec.java new file mode 100644 index 000000000..9facccf48 --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/ZoneConfigSpec.java @@ -0,0 +1,34 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server; + +import java.util.List; + +/** + * A configuration spec for the zone. + */ +public interface ZoneConfigSpec { + /** + * Returns the current zone. + */ + String currentZone(); + + /** + * Returns all zones. + */ + List allZones(); +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfig.java index 067077963..1600a631a 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfig.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -21,14 +21,7 @@ import static com.linecorp.centraldogma.server.CentralDogmaConfig.convertValue; import static java.util.Objects.requireNonNull; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.Enumeration; import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -39,21 +32,13 @@ import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; -import io.netty.util.NetUtil; - /** * ZooKeeper-based replication configuration. */ -public final class ZooKeeperReplicationConfig implements ReplicationConfig { - - private static final int DEFAULT_TIMEOUT_MILLIS = 10000; - private static final int DEFAULT_NUM_WORKERS = 16; - private static final int DEFAULT_MAX_LOG_COUNT = 1024; - private static final long DEFAULT_MIN_LOG_AGE_MILLIS = TimeUnit.DAYS.toMillis(1); - private static final String DEFAULT_SECRET = "ch4n63m3"; +public final class ZooKeeperReplicationConfig implements ZooKeeperReplicationConfigSpec { private final int serverId; - private final Map servers; + private final Map servers; @Nullable private final String secret; private final Map additionalProperties; @@ -68,13 +53,13 @@ public final class ZooKeeperReplicationConfig implements ReplicationConfig { * @param serverId the ID of this ZooKeeper server in {@code servers} * @param servers the ZooKeeper server addresses, keyed by their ZooKeeper server IDs */ - public ZooKeeperReplicationConfig(int serverId, Map servers) { + public ZooKeeperReplicationConfig(int serverId, Map servers) { this(serverId, servers, null, null, null, null, null, null); } @VisibleForTesting ZooKeeperReplicationConfig( - int serverId, Map servers, String secret, + int serverId, Map servers, String secret, Map additionalProperties, int timeoutMillis, int numWorkers, int maxLogCount, long minLogAgeMillis) { this(Integer.valueOf(serverId), servers, secret, additionalProperties, Integer.valueOf(timeoutMillis), @@ -85,7 +70,7 @@ public ZooKeeperReplicationConfig(int serverId, Map servers, + Map servers, @JsonProperty("secret") @Nullable String secret, @JsonProperty("additionalProperties") @JsonDeserialize(keyAs = String.class, contentAs = String.class) @@ -96,7 +81,7 @@ public ZooKeeperReplicationConfig(int serverId, Map 0, "serverId: %s (expected: > 0)", serverId); this.secret = secret; checkArgument(!secret().isEmpty(), "secret is empty."); @@ -125,137 +110,59 @@ public ZooKeeperReplicationConfig(int serverId, Map servers) { - int serverId = -1; - try { - for (final Enumeration e = NetworkInterface.getNetworkInterfaces(); - e.hasMoreElements();) { - serverId = findServerId(servers, serverId, e.nextElement()); - } - } catch (SocketException e) { - throw new IllegalStateException("failed to retrieve the network interface list", e); - } - - if (serverId < 0) { - throw new IllegalStateException( - "failed to auto-detect server ID because there is no matching IP address."); - } - - return serverId; - } - - private static int findServerId(Map servers, int currentServerId, - NetworkInterface iface) { - for (final Enumeration ea = iface.getInetAddresses(); ea.hasMoreElements();) { - currentServerId = findServerId(servers, currentServerId, ea.nextElement()); - } - return currentServerId; - } - - private static int findServerId(Map servers, int currentServerId, - InetAddress addr) { - final String ip = NetUtil.toAddressString(addr, true); - for (Entry entry : servers.entrySet()) { - final String zkAddr; - try { - zkAddr = NetUtil.toAddressString(InetAddress.getByName(entry.getValue().host()), true); - } catch (UnknownHostException uhe) { - throw new IllegalStateException( - "failed to resolve the IP address of the server name: " + entry.getValue().host()); - } - - if (zkAddr.equals(ip)) { - final int serverId = entry.getKey().intValue(); - if (currentServerId < 0) { - currentServerId = serverId; - } else if (currentServerId != serverId) { - throw new IllegalStateException( - "cannot auto-detect server ID because there are more than one IP address match. " + - "Both server ID " + currentServerId + " and " + serverId + - " have a matching IP address. Consider specifying server ID explicitly."); - } - } - } - return currentServerId; - } - @Override public ReplicationMethod method() { return ReplicationMethod.ZOOKEEPER; } - /** - * Returns the ID of this ZooKeeper server in {@link #servers()}. - */ @JsonProperty + @Override public int serverId() { return serverId; } - /** - * Returns the configuration of this ZooKeeper server in {@link #servers()}. - */ - public ZooKeeperServerConfig serverConfig() { + @Override + public ZooKeeperServerConfigSpec serverConfig() { return servers.get(serverId); } - /** - * Returns the configuration of all ZooKeeper servers, keyed by their server IDs. - */ @JsonProperty - public Map servers() { + @Override + public Map servers() { return servers; } - /** - * Returns the secret string used for authenticating the ZooKeeper peers. - */ + @Override public String secret() { return firstNonNull(convertValue(secret, "replication.secret"), DEFAULT_SECRET); } - /** - * Returns the additional ZooKeeper properties. - * If unspecified, an empty {@link Map} is returned. - */ @JsonProperty + @Override public Map additionalProperties() { return additionalProperties; } - /** - * Returns the ZooKeeper timeout, in milliseconds. - * If unspecified, the default of {@value #DEFAULT_TIMEOUT_MILLIS} is returned. - */ @JsonProperty + @Override public int timeoutMillis() { return timeoutMillis; } - /** - * Returns the number of worker threads dedicated for replication. - * If unspecified, the default of {@value #DEFAULT_NUM_WORKERS} is returned. - */ @JsonProperty + @Override public int numWorkers() { return numWorkers; } - /** - * Returns the maximum number of log items to keep in ZooKeeper. Note that the log items will still not be - * removed if they are younger than {@link #minLogAgeMillis()}. - * If unspecified, the default of {@value #DEFAULT_MAX_LOG_COUNT} is returned. - */ @JsonProperty + @Override public int maxLogCount() { return maxLogCount; } - /** - * Returns the minimum allowed age of log items before they are removed from ZooKeeper. - * If unspecified, the default of 1 hour is returned. - */ @JsonProperty + @Override public long minLogAgeMillis() { return minLogAgeMillis; } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfigSpec.java b/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfigSpec.java new file mode 100644 index 000000000..4e27fe8ea --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfigSpec.java @@ -0,0 +1,162 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import io.netty.util.NetUtil; + +/** + * ZooKeeper-based replication configuration spec. + */ +public interface ZooKeeperReplicationConfigSpec extends ReplicationConfig { + int DEFAULT_TIMEOUT_MILLIS = 10000; + int DEFAULT_NUM_WORKERS = 16; + int DEFAULT_MAX_LOG_COUNT = 1024; + long DEFAULT_MIN_LOG_AGE_MILLIS = TimeUnit.DAYS.toMillis(1); + String DEFAULT_SECRET = "ch4n63m3"; + + /** + * Find a server id from given zooKeeper server configs. + * @param servers zooKeeper server configs. + * @return serverId + */ + static int findServerId(Map servers) { + int serverId = -1; + try { + for (final Enumeration e = NetworkInterface.getNetworkInterfaces(); + e.hasMoreElements();) { + serverId = findServerId(servers, serverId, e.nextElement()); + } + } catch (SocketException e) { + throw new IllegalStateException("failed to retrieve the network interface list", e); + } + + if (serverId < 0) { + throw new IllegalStateException( + "failed to auto-detect server ID because there is no matching IP address."); + } + + return serverId; + } + + /** + * Find a server id from given zooKeeper server configs. + * @param servers ZooKeeper server configs. + * @param currentServerId Current server id. + * @param iface The network interface for current server instance. + * @return serverId + */ + static int findServerId(Map servers, int currentServerId, + NetworkInterface iface) { + for (final Enumeration ea = iface.getInetAddresses(); ea.hasMoreElements();) { + currentServerId = findServerId(servers, currentServerId, ea.nextElement()); + } + return currentServerId; + } + + /** + * Find a server id from given zooKeeper server configs. + * @param servers ZooKeeper server configs. + * @param currentServerId Current server id. + * @param addr The inet address which same as current server instance. + * @return serverId + */ + static int findServerId(Map servers, int currentServerId, + InetAddress addr) { + final String ip = NetUtil.toAddressString(addr, true); + for (Map.Entry entry : servers.entrySet()) { + final String zkAddr; + try { + zkAddr = NetUtil.toAddressString(InetAddress.getByName(entry.getValue().host()), true); + } catch (UnknownHostException uhe) { + throw new IllegalStateException( + "failed to resolve the IP address of the server name: " + entry.getValue().host()); + } + + if (zkAddr.equals(ip)) { + final int serverId = entry.getKey(); + if (currentServerId < 0) { + currentServerId = serverId; + } else if (currentServerId != serverId) { + throw new IllegalStateException( + "cannot auto-detect server ID because there are more than one IP address match. " + + "Both server ID " + currentServerId + " and " + serverId + + " have a matching IP address. Consider specifying server ID explicitly."); + } + } + } + return currentServerId; + } + + /** + * Returns the ID of this ZooKeeper server in {@link #servers()}. + */ + int serverId(); + + /** + * Returns the configuration of this ZooKeeper server in {@link #servers()}. + */ + ZooKeeperServerConfigSpec serverConfig(); + + /** + * Returns the configuration of all ZooKeeper servers, keyed by their server IDs. + */ + Map servers(); + + /** + * Returns the secret string used for authenticating the ZooKeeper peers. + */ + String secret(); + + /** + * Returns the additional ZooKeeper properties. + * If unspecified, an empty {@link java.util.Map} is returned. + */ + Map additionalProperties(); + + /** + * Returns the ZooKeeper timeout, in milliseconds. + * If unspecified, the default of {@value #DEFAULT_TIMEOUT_MILLIS} is returned. + */ + int timeoutMillis(); + + /** + * Returns the number of worker threads dedicated for replication. + * If unspecified, the default of {@value #DEFAULT_NUM_WORKERS} is returned. + */ + int numWorkers(); + + /** + * Returns the maximum number of log items to keep in ZooKeeper. Note that the log items will still not be + * removed if they are younger than {@link #minLogAgeMillis()}. + * If unspecified, the default of {@value #DEFAULT_MAX_LOG_COUNT} is returned. + */ + int maxLogCount(); + + /** + * Returns the minimum allowed age of log items before they are removed from ZooKeeper. + * If unspecified, the default of 1 hour is returned. + */ + long minLogAgeMillis(); +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperServerConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperServerConfig.java index a610d0c8c..d38559222 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperServerConfig.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperServerConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -16,6 +16,7 @@ package com.linecorp.centraldogma.server; import static com.google.common.base.Preconditions.checkArgument; +import static com.linecorp.centraldogma.server.ZooKeeperServerConfigSpec.validatePort; import static java.util.Objects.requireNonNull; import java.util.Objects; @@ -29,7 +30,7 @@ /** * Represents the address and port numbers of a ZooKeeper node. */ -public final class ZooKeeperServerConfig { +public final class ZooKeeperServerConfig implements ZooKeeperServerConfigSpec { private final String host; private final int quorumPort; @@ -66,7 +67,7 @@ public ZooKeeperServerConfig( clientPort = 0; } checkArgument(clientPort >= 0 && clientPort <= 65535, - "clientPort: %s (expected: 0-65535)", clientPort); + "clientPort: %s (expected: 0-65535)", clientPort); this.clientPort = clientPort; this.groupId = groupId; if (weight == null) { @@ -76,56 +77,39 @@ public ZooKeeperServerConfig( this.weight = weight; } - private static int validatePort(int port, String name) { - checkArgument(port > 0 && port <= 65535, "%s: %s (expected: 1-65535)", name, port); - return port; - } - - /** - * Returns the IP address or host name of the ZooKeeper server. - */ @JsonProperty + @Override public String host() { return host; } - /** - * Returns the quorum port number. - */ @JsonProperty + @Override public int quorumPort() { return quorumPort; } - /** - * Returns the election port number. - */ @JsonProperty + @Override public int electionPort() { return electionPort; } - /** - * Returns the client port number. - */ @JsonProperty + @Override public int clientPort() { return clientPort; } - /** - * Returns the group ID to use hierarchical quorums. - */ @Nullable @JsonProperty + @Override public Integer groupId() { return groupId; } - /** - * Returns the weight of the ZooKeeper server. - */ @JsonProperty + @Override public int weight() { return weight; } @@ -147,11 +131,11 @@ public boolean equals(Object o) { final ZooKeeperServerConfig that = (ZooKeeperServerConfig) o; return host.equals(that.host) && - quorumPort == that.quorumPort && - electionPort == that.electionPort && - clientPort == that.clientPort && - Objects.equals(groupId, that.groupId) && - weight == that.weight; + quorumPort == that.quorumPort && + electionPort == that.electionPort && + clientPort == that.clientPort && + Objects.equals(groupId, that.groupId) && + weight == that.weight; } @Override diff --git a/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperServerConfigSpec.java b/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperServerConfigSpec.java new file mode 100644 index 000000000..cbde0a8b4 --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperServerConfigSpec.java @@ -0,0 +1,68 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server; + +import static com.google.common.base.Preconditions.checkArgument; + +import javax.annotation.Nullable; + +/** + * Represents the address and port numbers of a ZooKeeper node. + */ +public interface ZooKeeperServerConfigSpec { + /** + * Validate port. + * @param port Port. + * @param name Port name to make error message. + * @return Validated port + */ + static int validatePort(int port, String name) { + checkArgument(port > 0 && port <= 65535, "%s: %s (expected: 1-65535)", name, port); + return port; + } + + /** + * Returns the IP address or host name of the ZooKeeper server. + */ + String host(); + + /** + * Returns the quorum port number. + */ + int quorumPort(); + + /** + * Returns the election port number. + */ + int electionPort(); + + /** + * Returns the client port number. + */ + int clientPort(); + + /** + * Returns the group ID to use hierarchical quorums. + */ + @Nullable + Integer groupId(); + + /** + * Returns the weight of the ZooKeeper server. + */ + int weight(); +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/auth/AuthConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/auth/AuthConfig.java index 6748cad77..a01eeb559 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/auth/AuthConfig.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/auth/AuthConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -20,14 +20,11 @@ import static com.linecorp.centraldogma.server.internal.storage.repository.RepositoryCache.validateCacheSpec; import static java.util.Objects.requireNonNull; -import java.text.ParseException; import java.util.Set; import java.util.function.Function; import javax.annotation.Nullable; -import org.quartz.CronExpression; - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; @@ -40,23 +37,7 @@ /** * An authentication configuration for the Central Dogma server. */ -public final class AuthConfig { - /** - * A default session timeout in milliseconds. - */ - public static final long DEFAULT_SESSION_TIMEOUT_MILLIS = 604800000; // 7 days - - /** - * A default specification for a session cache. - */ - public static final String DEFAULT_SESSION_CACHE_SPEC = - // Expire after the duration of session timeout. - "maximumSize=8192,expireAfterWrite=" + (DEFAULT_SESSION_TIMEOUT_MILLIS / 1000) + 's'; - - /** - * A default schedule for validating sessions at 0:30, 4:30, 8:30, 12:30, 16:30 and 20:30 for every day. - */ - public static final String DEFAULT_SESSION_VALIDATION_SCHEDULE = "0 30 */4 ? * *"; +public final class AuthConfig implements AuthConfigSpec { private final AuthProviderFactory factory; @@ -131,7 +112,7 @@ public AuthConfig(AuthProviderFactory factory, checkArgument(sessionTimeoutMillis > 0, "sessionTimeoutMillis: %s (expected: > 0)", sessionTimeoutMillis); this.sessionTimeoutMillis = sessionTimeoutMillis; - this.sessionValidationSchedule = validateSchedule( + this.sessionValidationSchedule = AuthConfigSpec.validateSchedule( requireNonNull(sessionValidationSchedule, "sessionValidationSchedule")); this.properties = properties; } @@ -139,14 +120,13 @@ public AuthConfig(AuthProviderFactory factory, /** * Returns the {@link AuthProviderFactory}. */ + @Override public AuthProviderFactory factory() { return factory; } - /** - * Returns the class name of the {@link AuthProviderFactory}. - */ @JsonProperty + @Override public String factoryClassName() { return factory.getClass().getName(); } @@ -155,63 +135,42 @@ public String factoryClassName() { * Returns the usernames of the users with system administrator rights. */ @JsonProperty + @Override public Set systemAdministrators() { return systemAdministrators; } - /** - * Returns whether login names are case-sensitive. - */ @JsonProperty + @Override public boolean caseSensitiveLoginNames() { return caseSensitiveLoginNames; } - /** - * Returns the spec of the session cache. - */ @JsonProperty + @Override public String sessionCacheSpec() { return sessionCacheSpec; } - /** - * Returns the timeout of an inactive session in milliseconds. - */ @JsonProperty + @Override public long sessionTimeoutMillis() { return sessionTimeoutMillis; } - /** - * Returns the cron expression that describes how often session validation task should run. - */ @JsonProperty + @Override public String sessionValidationSchedule() { return sessionValidationSchedule; } - /** - * Returns the additional properties given to the {@link AuthProviderFactory}. - */ - @Nullable - @JsonProperty - public JsonNode properties() { - return properties; - } - - /** - * Returns the additional properties, converted to {@code T}. - */ @Nullable + @Override public T properties(Class clazz) throws JsonProcessingException { return properties != null ? Jackson.treeToValue(properties, clazz) : null; } - /** - * Returns a {@link Function} which normalizes a login name based on the - * {@link AuthConfig#caseSensitiveLoginNames()} property. - */ + @Override public Function loginNameNormalizer() { return caseSensitiveLoginNames() ? Function.identity() : Ascii::toLowerCase; } @@ -224,13 +183,4 @@ public String toString() { throw new IllegalStateException(e); } } - - private static String validateSchedule(String sessionValidationSchedule) { - try { - CronExpression.validateExpression(sessionValidationSchedule); - return sessionValidationSchedule; - } catch (ParseException e) { - throw new IllegalArgumentException("Invalid session validation schedule", e); - } - } } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/auth/AuthConfigSpec.java b/server/src/main/java/com/linecorp/centraldogma/server/auth/AuthConfigSpec.java new file mode 100644 index 000000000..71c794073 --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/auth/AuthConfigSpec.java @@ -0,0 +1,109 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server.auth; + +import java.text.ParseException; +import java.util.Set; +import java.util.function.Function; + +import javax.annotation.Nullable; + +import org.quartz.CronExpression; + +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * An authentication configuration spec for the Central Dogma server. + */ +public interface AuthConfigSpec { + + /** + * A default session timeout in milliseconds. + */ + long DEFAULT_SESSION_TIMEOUT_MILLIS = 604800000; // 7 days + /** + * A default specification for a session cache. + */ + String DEFAULT_SESSION_CACHE_SPEC = + // Expire after the duration of session timeout. + "maximumSize=8192,expireAfterWrite=" + (DEFAULT_SESSION_TIMEOUT_MILLIS / 1000) + 's'; + /** + * A default schedule for validating sessions at 0:30, 4:30, 8:30, 12:30, 16:30 and 20:30 for every day. + */ + String DEFAULT_SESSION_VALIDATION_SCHEDULE = "0 30 */4 ? * *"; + + /** + * To validate session schedule. + * @param sessionValidationSchedule Target session schedule. + * @return Validated session schedule. + */ + static String validateSchedule(String sessionValidationSchedule) { + try { + CronExpression.validateExpression(sessionValidationSchedule); + return sessionValidationSchedule; + } catch (ParseException e) { + throw new IllegalArgumentException("Invalid session validation schedule", e); + } + } + + /** + * Returns the {@link AuthProviderFactory}. + */ + AuthProviderFactory factory(); + + /** + * Returns the class name of the {@link AuthProviderFactory}. + */ + String factoryClassName(); + + /** + * Returns the usernames of the users with administrator rights. + */ + Set systemAdministrators(); + + /** + * Returns whether login names are case-sensitive. + */ + boolean caseSensitiveLoginNames(); + + /** + * Returns the spec of the session cache. + */ + String sessionCacheSpec(); + + /** + * Returns the timeout of an inactive session in milliseconds. + */ + long sessionTimeoutMillis(); + + /** + * Returns the cron expression that describes how often session validation task should run. + */ + String sessionValidationSchedule(); + + /** + * Returns the additional properties, converted to {@code T}. + */ + @Nullable + T properties(Class clazz) throws JsonProcessingException; + + /** + * Returns a {@link Function} which normalizes a login name based on the + * {@link AuthConfigSpec#caseSensitiveLoginNames()} property. + */ + Function loginNameNormalizer(); +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/auth/AuthProviderParameters.java b/server/src/main/java/com/linecorp/centraldogma/server/auth/AuthProviderParameters.java index b606e0ba6..665bda1ad 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/auth/AuthProviderParameters.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/auth/AuthProviderParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -23,7 +23,7 @@ import com.linecorp.armeria.common.HttpRequest; import com.linecorp.armeria.server.auth.Authorizer; -import com.linecorp.centraldogma.server.CentralDogmaConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; /** * Parameters which are used to create a new {@link AuthProvider} instance. @@ -31,8 +31,8 @@ public final class AuthProviderParameters { private final Authorizer authorizer; - private final CentralDogmaConfig config; - private final AuthConfig authConfig; + private final CentralDogmaConfigSpec config; + private final AuthConfigSpec authConfig; private final Supplier sessionIdGenerator; private final Function> loginSessionPropagator; private final Function> logoutSessionPropagator; @@ -50,7 +50,7 @@ public final class AuthProviderParameters { */ public AuthProviderParameters( Authorizer authorizer, - CentralDogmaConfig config, + CentralDogmaConfigSpec config, Supplier sessionIdGenerator, Function> loginSessionPropagator, Function> logoutSessionPropagator) { @@ -72,14 +72,14 @@ public Authorizer authorizer() { /** * Returns the configuration for the Central Dogma server. */ - public CentralDogmaConfig config() { + public CentralDogmaConfigSpec config() { return config; } /** * Returns the authentication configuration. */ - public AuthConfig authConfig() { + public AuthConfigSpec authConfig() { return authConfig; } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/auth/Session.java b/server/src/main/java/com/linecorp/centraldogma/server/auth/Session.java index ecc784e0c..c39ef0570 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/auth/Session.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/auth/Session.java @@ -53,11 +53,23 @@ public final class Session { * @param sessionValidDuration the {@link Duration} that this session is valid */ public Session(String id, String username, Duration sessionValidDuration) { + this(id, username, sessionValidDuration, null); + } + + /** + * Creates a new {@link Session} instance. + * + * @param id the session ID + * @param username the name of the user which belongs to this session + * @param sessionValidDuration the {@link Duration} that this session is valid + * @param rawSession the serializable session object which is specific to authentication provider + */ + public Session(String id, String username, Duration sessionValidDuration, Serializable rawSession) { this.id = requireNonNull(id, "id"); this.username = requireNonNull(username, "username"); creationTime = Instant.now(); expirationTime = creationTime.plus(requireNonNull(sessionValidDuration, "sessionValidDuration")); - rawSession = null; + this.rawSession = rawSession; } /** @@ -132,7 +144,7 @@ public Serializable rawSession() { * @throws NullPointerException if the {@code rawSession} is {@code null} * @throws ClassCastException if the {@code rawSession} cannot be casted to {@code T} */ - T castRawSession() { + public T castRawSession() { return Util.unsafeCast(requireNonNull(rawSession, "rawSession")); } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/command/CommandExecutor.java b/server/src/main/java/com/linecorp/centraldogma/server/command/CommandExecutor.java index 8c9307c03..39f9724ae 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/command/CommandExecutor.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/command/CommandExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -20,7 +20,7 @@ import javax.annotation.Nullable; -import com.linecorp.centraldogma.server.QuotaConfig; +import com.linecorp.centraldogma.server.QuotaConfigSpec; /** * An executor interface which executes {@link Command}s. @@ -61,10 +61,10 @@ public interface CommandExecutor { void setWritable(boolean writable); /** - * Sets the specified {@linkplain QuotaConfig write quota} to the specified {@code repoName} in the + * Sets the specified {@linkplain QuotaConfigSpec write quota} to the specified {@code repoName} in the * specified {@code projectName}. */ - void setWriteQuota(String projectName, String repoName, @Nullable QuotaConfig writeQuota); + void setWriteQuota(String projectName, String repoName, @Nullable QuotaConfigSpec writeQuota); /** * Executes the specified {@link Command}. diff --git a/server/src/main/java/com/linecorp/centraldogma/server/command/ForwardingCommandExecutor.java b/server/src/main/java/com/linecorp/centraldogma/server/command/ForwardingCommandExecutor.java index 7330bb614..5cca94267 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/command/ForwardingCommandExecutor.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/command/ForwardingCommandExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -21,7 +21,7 @@ import java.util.concurrent.CompletableFuture; import com.linecorp.centraldogma.internal.Util; -import com.linecorp.centraldogma.server.QuotaConfig; +import com.linecorp.centraldogma.server.QuotaConfigSpec; /** * A {@link CommandExecutor} which forwards all its method calls to another {@link CommandExecutor}. @@ -69,7 +69,7 @@ public void setWritable(boolean writable) { } @Override - public void setWriteQuota(String projectName, String repoName, QuotaConfig writeQuota) { + public void setWriteQuota(String projectName, String repoName, QuotaConfigSpec writeQuota) { delegate().setWriteQuota(projectName, repoName, writeQuota); } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java b/server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java index b1cd02f3e..943788288 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -34,7 +34,7 @@ import com.spotify.futures.CompletableFutures; import com.linecorp.centraldogma.common.TooManyRequestsException; -import com.linecorp.centraldogma.server.QuotaConfig; +import com.linecorp.centraldogma.server.QuotaConfigSpec; import com.linecorp.centraldogma.server.auth.Session; import com.linecorp.centraldogma.server.auth.SessionManager; import com.linecorp.centraldogma.server.management.ServerStatusManager; @@ -81,7 +81,7 @@ public StandaloneCommandExecutor(ProjectManager projectManager, Executor repositoryWorker, ServerStatusManager serverStatusManager, @Nullable SessionManager sessionManager, - @Nullable QuotaConfig writeQuota, + @Nullable QuotaConfigSpec writeQuota, @Nullable Consumer onTakeLeadership, @Nullable Consumer onReleaseLeadership, @Nullable Consumer onTakeZoneLeadership, @@ -341,7 +341,7 @@ private CompletableFuture getRateLimiter(String projectName, String } @Override - public final void setWriteQuota(String projectName, String repoName, @Nullable QuotaConfig writeQuota) { + public final void setWriteQuota(String projectName, String repoName, @Nullable QuotaConfigSpec writeQuota) { if (!writeQuotaEnabled()) { // This method should be called only when a write quota is enabled. return; diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/FileBasedSessionManager.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/FileBasedSessionManager.java index aefaefffa..23844379c 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/FileBasedSessionManager.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/FileBasedSessionManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -53,7 +53,7 @@ import com.google.common.collect.ImmutableMap; import com.linecorp.centraldogma.internal.Jackson; -import com.linecorp.centraldogma.server.auth.AuthConfig; +import com.linecorp.centraldogma.server.auth.AuthConfigSpec; import com.linecorp.centraldogma.server.auth.AuthException; import com.linecorp.centraldogma.server.auth.Session; import com.linecorp.centraldogma.server.auth.SessionManager; @@ -61,7 +61,7 @@ /** * A {@link SessionManager} based on the file system. The sessions stored in the file system would be * deleted when the {@link #delete(String)} method is called or when the {@link ExpiredSessionDeletingJob} - * finds the expired session. The {@link AuthConfig#sessionValidationSchedule()} can configure + * finds the expired session. The {@link AuthConfigSpec#sessionValidationSchedule()} can configure * the schedule for deleting expired sessions. */ public final class FileBasedSessionManager implements SessionManager { diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/MetadataApiService.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/MetadataApiService.java index 74003c8d6..0771858e1 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/MetadataApiService.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/MetadataApiService.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -45,6 +45,7 @@ import com.linecorp.centraldogma.internal.jsonpatch.JsonPatchOperation; import com.linecorp.centraldogma.internal.jsonpatch.ReplaceOperation; import com.linecorp.centraldogma.server.QuotaConfig; +import com.linecorp.centraldogma.server.QuotaConfigSpec; import com.linecorp.centraldogma.server.command.CommandExecutor; import com.linecorp.centraldogma.server.internal.api.auth.RequiresProjectRole; import com.linecorp.centraldogma.server.internal.api.auth.RequiresSystemAdministrator; @@ -250,7 +251,7 @@ public CompletableFuture removeTokenRepositoryRole(@Param String proje /** * PATCH /metadata/{projectName}/repos/{repoName}/quota/write * - *

Updates the {@linkplain QuotaConfig write quota} for the specified {@code repoName} + *

Updates the {@linkplain QuotaConfigSpec write quota} for the specified {@code repoName} * in the specified {@code projectName}. */ @Patch("/metadata/{projectName}/repos/{repoName}/quota/write") diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/MirroringServiceV1.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/MirroringServiceV1.java index e8029de3e..e92695433 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/MirroringServiceV1.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/MirroringServiceV1.java @@ -48,8 +48,8 @@ import com.linecorp.centraldogma.common.Revision; import com.linecorp.centraldogma.internal.api.v1.MirrorDto; import com.linecorp.centraldogma.internal.api.v1.PushResultDto; -import com.linecorp.centraldogma.server.CentralDogmaConfig; -import com.linecorp.centraldogma.server.ZoneConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; +import com.linecorp.centraldogma.server.ZoneConfigSpec; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.command.CommandExecutor; import com.linecorp.centraldogma.server.command.CommitResult; @@ -77,10 +77,10 @@ public class MirroringServiceV1 extends AbstractService { private final MirrorRunner mirrorRunner; private final Map mirrorZoneConfig; @Nullable - private final ZoneConfig zoneConfig; + private final ZoneConfigSpec zoneConfig; public MirroringServiceV1(ProjectApiManager projectApiManager, CommandExecutor executor, - MirrorRunner mirrorRunner, CentralDogmaConfig config) { + MirrorRunner mirrorRunner, CentralDogmaConfigSpec config) { super(executor); this.projectApiManager = projectApiManager; this.mirrorRunner = mirrorRunner; @@ -88,12 +88,12 @@ public MirroringServiceV1(ProjectApiManager projectApiManager, CommandExecutor e mirrorZoneConfig = mirrorZoneConfig(config); } - private static Map mirrorZoneConfig(CentralDogmaConfig config) { + private static Map mirrorZoneConfig(CentralDogmaConfigSpec config) { final MirroringServicePluginConfig mirrorConfig = mirrorConfig(config); final ImmutableMap.Builder builder = ImmutableMap.builderWithExpectedSize(2); final boolean zonePinned = mirrorConfig != null && mirrorConfig.zonePinned(); builder.put("zonePinned", zonePinned); - final ZoneConfig zone = config.zone(); + final ZoneConfigSpec zone = config.zone(); if (zone != null) { builder.put("zone", zone); } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringServicePlugin.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringServicePlugin.java index b3fbbe7b6..738e1e3c9 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringServicePlugin.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringServicePlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -25,17 +25,19 @@ import com.google.common.base.MoreObjects; -import com.linecorp.centraldogma.server.CentralDogmaConfig; -import com.linecorp.centraldogma.server.ZoneConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; +import com.linecorp.centraldogma.server.ZoneConfigSpec; import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfig; +import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfigSpec; import com.linecorp.centraldogma.server.plugin.Plugin; +import com.linecorp.centraldogma.server.plugin.PluginConfig; import com.linecorp.centraldogma.server.plugin.PluginContext; import com.linecorp.centraldogma.server.plugin.PluginTarget; public final class DefaultMirroringServicePlugin implements Plugin { @Nullable - public static MirroringServicePluginConfig mirrorConfig(CentralDogmaConfig config) { + public static MirroringServicePluginConfig mirrorConfig(CentralDogmaConfigSpec config) { return (MirroringServicePluginConfig) config.pluginConfigMap().get(MirroringServicePluginConfig.class); } @@ -46,7 +48,7 @@ public static MirroringServicePluginConfig mirrorConfig(CentralDogmaConfig confi private PluginTarget pluginTarget; @Override - public PluginTarget target(CentralDogmaConfig config) { + public PluginTarget target(CentralDogmaConfigSpec config) { requireNonNull(config, "config"); if (pluginTarget != null) { return pluginTarget; @@ -67,12 +69,12 @@ public synchronized CompletionStage start(PluginContext context) { MirrorSchedulingService mirroringService = this.mirroringService; if (mirroringService == null) { - final CentralDogmaConfig cfg = context.config(); + final CentralDogmaConfigSpec cfg = context.config(); final MirroringServicePluginConfig mirroringServicePluginConfig = mirrorConfig(cfg); final int numThreads; final int maxNumFilesPerMirror; final long maxNumBytesPerMirror; - final ZoneConfig zoneConfig; + final ZoneConfigSpec zoneConfig; if (mirroringServicePluginConfig != null) { numThreads = mirroringServicePluginConfig.numMirroringThreads(); @@ -112,8 +114,8 @@ public synchronized CompletionStage stop(PluginContext context) { } @Override - public Class configType() { - return MirroringServicePluginConfig.class; + public Class configType() { + return MirroringServicePluginConfigSpec.class; } @Nullable diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/MirrorRunner.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/MirrorRunner.java index 8e0079cee..42b8188ca 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/MirrorRunner.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/MirrorRunner.java @@ -35,7 +35,7 @@ import com.linecorp.armeria.common.util.SafeCloseable; import com.linecorp.centraldogma.common.MirrorException; -import com.linecorp.centraldogma.server.CentralDogmaConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; import com.linecorp.centraldogma.server.command.CommandExecutor; import com.linecorp.centraldogma.server.internal.storage.project.ProjectApiManager; import com.linecorp.centraldogma.server.metadata.User; @@ -62,7 +62,7 @@ public final class MirrorRunner implements SafeCloseable { private final String currentZone; public MirrorRunner(ProjectApiManager projectApiManager, CommandExecutor commandExecutor, - CentralDogmaConfig cfg, MeterRegistry meterRegistry) { + CentralDogmaConfigSpec cfg, MeterRegistry meterRegistry) { this.projectApiManager = projectApiManager; this.commandExecutor = commandExecutor; // TODO(ikhoon): Periodically clean up stale repositories. diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/MirrorSchedulingService.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/MirrorSchedulingService.java index 4dc69f743..72628e230 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/MirrorSchedulingService.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/MirrorSchedulingService.java @@ -52,7 +52,7 @@ import com.linecorp.centraldogma.common.MirrorException; import com.linecorp.centraldogma.server.MirroringService; -import com.linecorp.centraldogma.server.ZoneConfig; +import com.linecorp.centraldogma.server.ZoneConfigSpec; import com.linecorp.centraldogma.server.command.CommandExecutor; import com.linecorp.centraldogma.server.metadata.User; import com.linecorp.centraldogma.server.mirror.Mirror; @@ -93,7 +93,7 @@ public final class MirrorSchedulingService implements MirroringService { private final int maxNumFilesPerMirror; private final long maxNumBytesPerMirror; @Nullable - private final ZoneConfig zoneConfig; + private final ZoneConfigSpec zoneConfig; @Nullable private final String currentZone; @@ -107,7 +107,7 @@ public final class MirrorSchedulingService implements MirroringService { @VisibleForTesting public MirrorSchedulingService(File workDir, ProjectManager projectManager, MeterRegistry meterRegistry, int numThreads, int maxNumFilesPerMirror, long maxNumBytesPerMirror, - @Nullable ZoneConfig zoneConfig) { + @Nullable ZoneConfigSpec zoneConfig) { this.workDir = requireNonNull(workDir, "workDir"); this.projectManager = requireNonNull(projectManager, "projectManager"); diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/replication/ZooKeeperCommandExecutor.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/replication/ZooKeeperCommandExecutor.java index b3e674221..c65eafd33 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/replication/ZooKeeperCommandExecutor.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/replication/ZooKeeperCommandExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -97,8 +97,9 @@ import com.linecorp.centraldogma.common.TooManyRequestsException; import com.linecorp.centraldogma.internal.Jackson; import com.linecorp.centraldogma.server.QuotaConfig; -import com.linecorp.centraldogma.server.ZooKeeperReplicationConfig; -import com.linecorp.centraldogma.server.ZooKeeperServerConfig; +import com.linecorp.centraldogma.server.QuotaConfigSpec; +import com.linecorp.centraldogma.server.ZooKeeperReplicationConfigSpec; +import com.linecorp.centraldogma.server.ZooKeeperServerConfigSpec; import com.linecorp.centraldogma.server.command.AbstractCommandExecutor; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.command.CommandExecutor; @@ -159,9 +160,9 @@ public final class ZooKeeperCommandExecutor new ConcurrentHashMap<>(); @VisibleForTesting - final Cache writeQuotaCache = Caffeine.newBuilder().maximumSize(2000).build(); + final Cache writeQuotaCache = Caffeine.newBuilder().maximumSize(2000).build(); - private final ZooKeeperReplicationConfig cfg; + private final ZooKeeperReplicationConfigSpec cfg; private final File revisionFile; private final File zkConfFile; private final File zkDataDir; @@ -170,7 +171,7 @@ public final class ZooKeeperCommandExecutor private final MeterRegistry meterRegistry; @Nullable - private final QuotaConfig writeQuota; + private final QuotaConfigSpec writeQuota; @Nullable private final String zone; @@ -405,11 +406,11 @@ private static final class ListenerInfo { private volatile ListenerInfo listenerInfo; - public ZooKeeperCommandExecutor(ZooKeeperReplicationConfig cfg, + public ZooKeeperCommandExecutor(ZooKeeperReplicationConfigSpec cfg, File dataDir, CommandExecutor delegate, MeterRegistry meterRegistry, ProjectManager projectManager, - @Nullable QuotaConfig writeQuota, + @Nullable QuotaConfigSpec writeQuota, @Nullable String zone, @Nullable Consumer onTakeLeadership, @Nullable Consumer onReleaseLeadership, @@ -626,11 +627,11 @@ private EmbeddedZooKeeper startZooKeeper() throws Exception { // Set the client port, which is unused. zkProps.setProperty("clientPort", String.valueOf(cfg.serverConfig().clientPort())); - final Map servers = cfg.servers(); + final Map servers = cfg.servers(); // Add replicas. boolean hasGroupId = false; - for (Entry entry : servers.entrySet()) { - final ZooKeeperServerConfig serverConfig = entry.getValue(); + for (Entry entry : servers.entrySet()) { + final ZooKeeperServerConfigSpec serverConfig = entry.getValue(); zkProps.setProperty( "server." + entry.getKey(), serverConfig.host() + ':' + serverConfig.quorumPort() + ':' + @@ -645,11 +646,11 @@ private EmbeddedZooKeeper startZooKeeper() throws Exception { if (hasGroupId) { final ImmutableMultimap.Builder groupBuilder = ImmutableMultimap.builder(); boolean isHierarchical = true; - for (Entry entry : servers.entrySet()) { + for (Entry entry : servers.entrySet()) { final Integer groupId = entry.getValue().groupId(); if (groupId == null) { isHierarchical = false; - final List noGroupIds = + final List noGroupIds = servers.values().stream() .filter(serverConfig -> serverConfig.groupId() == null) .collect(toImmutableList()); @@ -1059,7 +1060,7 @@ private WriteLock acquireWriteLock(NormalizingPushCommand command) throws Except return null; } - QuotaConfig writeQuota = writeQuotaCache.getIfPresent(command.executionPath()); + QuotaConfigSpec writeQuota = writeQuotaCache.getIfPresent(command.executionPath()); if (writeQuota == UNLIMITED_QUOTA) { return null; } @@ -1097,8 +1098,8 @@ private WriteLock acquireWriteLock(NormalizingPushCommand command) throws Except } @Override - public void setWriteQuota(String projectName, String repoName, @Nullable QuotaConfig writeQuota) { - final QuotaConfig quota = firstNonNull(writeQuota, UNLIMITED_QUOTA); + public void setWriteQuota(String projectName, String repoName, @Nullable QuotaConfigSpec writeQuota) { + final QuotaConfigSpec quota = firstNonNull(writeQuota, UNLIMITED_QUOTA); writeQuotaCache.put(rateLimiterKey(projectName, repoName), quota); semaphoreMap.compute(rateLimiterKey(projectName, repoName), (k, v) -> { @@ -1127,7 +1128,7 @@ private static String rateLimiterKey(String projectName, String repoName) { private void scheduleWriteLockRelease(WriteLock writeLock, InterProcessMutex mtx, String executionPath) { final Lease lease = writeLock.lease; - final QuotaConfig writeQuota = writeLock.writeQuota; + final QuotaConfigSpec writeQuota = writeLock.writeQuota; if (lease == null) { safeRelease(mtx); throw new TooManyRequestsException("commits", executionPath, writeQuota.permitsPerSecond()); @@ -1406,11 +1407,11 @@ private static final class WriteLock { private final InterProcessSemaphoreV2 semaphore; @Nullable private final Lease lease; - private final QuotaConfig writeQuota; + private final QuotaConfigSpec writeQuota; private WriteLock(InterProcessSemaphoreV2 semaphore, @Nullable Lease lease, - QuotaConfig writeQuota) { + QuotaConfigSpec writeQuota) { this.semaphore = semaphore; this.lease = lease; this.writeQuota = writeQuota; diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/PurgeSchedulingServicePlugin.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/PurgeSchedulingServicePlugin.java index 4f3843022..c71087ce2 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/PurgeSchedulingServicePlugin.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/PurgeSchedulingServicePlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -24,9 +24,11 @@ import com.google.common.base.MoreObjects; -import com.linecorp.centraldogma.server.CentralDogmaConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; import com.linecorp.centraldogma.server.metadata.MetadataService; +import com.linecorp.centraldogma.server.plugin.NoopPluginConfig; import com.linecorp.centraldogma.server.plugin.Plugin; +import com.linecorp.centraldogma.server.plugin.PluginConfig; import com.linecorp.centraldogma.server.plugin.PluginContext; import com.linecorp.centraldogma.server.plugin.PluginTarget; @@ -36,7 +38,7 @@ public final class PurgeSchedulingServicePlugin implements Plugin { private volatile PurgeSchedulingService purgeSchedulingService; @Override - public PluginTarget target(CentralDogmaConfig config) { + public PluginTarget target(CentralDogmaConfigSpec config) { return PluginTarget.LEADER_ONLY; } @@ -46,7 +48,7 @@ public synchronized CompletionStage start(PluginContext context) { PurgeSchedulingService purgeSchedulingService = this.purgeSchedulingService; if (purgeSchedulingService == null) { - final CentralDogmaConfig cfg = context.config(); + final CentralDogmaConfigSpec cfg = context.config(); purgeSchedulingService = new PurgeSchedulingService(context.projectManager(), context.purgeWorker(), cfg.maxRemovedRepositoryAgeMillis()); @@ -68,14 +70,13 @@ public synchronized CompletionStage stop(PluginContext context) { } @Override - public boolean isEnabled(CentralDogmaConfig config) { + public boolean isEnabled(CentralDogmaConfigSpec config) { return requireNonNull(config, "config").maxRemovedRepositoryAgeMillis() > 0; } @Override - public Class configType() { - // Return the plugin class itself because it does not have a configuration. - return getClass(); + public Class configType() { + return NoopPluginConfig.class; } @Nullable diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/DefaultMetaRepository.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/DefaultMetaRepository.java index a1c838e2b..4505b632c 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/DefaultMetaRepository.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/DefaultMetaRepository.java @@ -46,7 +46,7 @@ import com.linecorp.centraldogma.common.Revision; import com.linecorp.centraldogma.internal.Jackson; import com.linecorp.centraldogma.internal.api.v1.MirrorDto; -import com.linecorp.centraldogma.server.ZoneConfig; +import com.linecorp.centraldogma.server.ZoneConfigSpec; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.command.CommitResult; import com.linecorp.centraldogma.server.credential.Credential; @@ -240,7 +240,7 @@ private CompletableFuture>> find(String filePattern) { @Override public CompletableFuture> createPushCommand(MirrorDto mirrorDto, Author author, - @Nullable ZoneConfig zoneConfig, + @Nullable ZoneConfigSpec zoneConfig, boolean update) { validateMirror(mirrorDto, zoneConfig); if (update) { @@ -294,7 +294,7 @@ private Command newCommand(Credential credential, Author author, S change); } - private static void validateMirror(MirrorDto mirror, @Nullable ZoneConfig zoneConfig) { + private static void validateMirror(MirrorDto mirror, @Nullable ZoneConfigSpec zoneConfig) { checkArgument(!Strings.isNullOrEmpty(mirror.id()), "Mirror ID is empty"); final String scheduleString = mirror.schedule(); if (scheduleString != null) { diff --git a/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirroringServicePluginConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirroringServicePluginConfig.java index 3aa4afde0..29a676678 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirroringServicePluginConfig.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirroringServicePluginConfig.java @@ -29,15 +29,12 @@ /** * A mirroring service plugin configuration. */ -public final class MirroringServicePluginConfig extends AbstractPluginConfig { +public final class MirroringServicePluginConfig extends AbstractPluginConfig + implements MirroringServicePluginConfigSpec { public static final MirroringServicePluginConfig INSTANCE = new MirroringServicePluginConfig(true, null, null, null, false); - static final int DEFAULT_NUM_MIRRORING_THREADS = 16; - static final int DEFAULT_MAX_NUM_FILES_PER_MIRROR = 8192; - static final long DEFAULT_MAX_NUM_BYTES_PER_MIRROR = 32 * 1048576; // 32 MiB - private final int numMirroringThreads; private final int maxNumFilesPerMirror; private final long maxNumBytesPerMirror; @@ -73,26 +70,20 @@ public MirroringServicePluginConfig( this.zonePinned = zonePinned; } - /** - * Returns the number of mirroring threads. - */ @JsonProperty + @Override public int numMirroringThreads() { return numMirroringThreads; } - /** - * Returns the maximum allowed number of files per mirror. - */ @JsonProperty + @Override public int maxNumFilesPerMirror() { return maxNumFilesPerMirror; } - /** - * Returns the maximum allowed number of bytes per mirror. - */ @JsonProperty + @Override public long maxNumBytesPerMirror() { return maxNumBytesPerMirror; } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirroringServicePluginConfigSpec.java b/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirroringServicePluginConfigSpec.java new file mode 100644 index 000000000..25d637edf --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirroringServicePluginConfigSpec.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server.mirror; + +import com.linecorp.centraldogma.server.plugin.PluginConfig; + +/** + * A mirroring service plugin configuration spec. + */ +public interface MirroringServicePluginConfigSpec extends PluginConfig { + int DEFAULT_NUM_MIRRORING_THREADS = 16; + int DEFAULT_MAX_NUM_FILES_PER_MIRROR = 8192; + long DEFAULT_MAX_NUM_BYTES_PER_MIRROR = 32 * 1048576; // 32 MiB + + /** + * Returns the number of mirroring threads. + */ + int numMirroringThreads(); + + /** + * Returns the maximum allowed number of files per mirror. + */ + int maxNumFilesPerMirror(); + + /** + * Returns the maximum allowed number of bytes per mirror. + */ + long maxNumBytesPerMirror(); +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/plugin/AllReplicasPlugin.java b/server/src/main/java/com/linecorp/centraldogma/server/plugin/AllReplicasPlugin.java index 2f0605ef4..5450e4cf9 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/plugin/AllReplicasPlugin.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/plugin/AllReplicasPlugin.java @@ -15,10 +15,10 @@ */ package com.linecorp.centraldogma.server.plugin; -import com.linecorp.centraldogma.server.CentralDogmaConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; /** - * A Base class for {@link Plugin} whose {@link #target(CentralDogmaConfig)} is + * A Base class for {@link Plugin} whose {@link #target(CentralDogmaConfigSpec)} is * {@link PluginTarget#ALL_REPLICAS}. */ public abstract class AllReplicasPlugin implements Plugin { @@ -29,7 +29,7 @@ public abstract class AllReplicasPlugin implements Plugin { public void init(PluginInitContext pluginInitContext) {} @Override - public final PluginTarget target(CentralDogmaConfig config) { + public final PluginTarget target(CentralDogmaConfigSpec config) { return PluginTarget.ALL_REPLICAS; } } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/plugin/NoopPluginConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/plugin/NoopPluginConfig.java new file mode 100644 index 000000000..5c3fdcb31 --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/plugin/NoopPluginConfig.java @@ -0,0 +1,32 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.centraldogma.server.plugin; + +import javax.annotation.Nullable; + +/** + * No op {@link PluginConfig} implementation. + */ +public class NoopPluginConfig extends AbstractPluginConfig { + /** + * Creates a new instance. + * @param enabled Plugin enabled. + */ + protected NoopPluginConfig(@Nullable Boolean enabled) { + super(enabled); + } +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/plugin/Plugin.java b/server/src/main/java/com/linecorp/centraldogma/server/plugin/Plugin.java index f8bb613be..e7e606ef0 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/plugin/Plugin.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/plugin/Plugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -17,7 +17,7 @@ import java.util.concurrent.CompletionStage; -import com.linecorp.centraldogma.server.CentralDogmaConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; /** * An interface which defines callbacks for a plug-in. If you want to initialize a {@link Plugin} by configuring @@ -27,7 +27,7 @@ public interface Plugin { /** * Returns the {@link PluginTarget} which specifies the replicas that this {@link Plugin} is applied to. */ - PluginTarget target(CentralDogmaConfig config); + PluginTarget target(CentralDogmaConfigSpec config); /** * Invoked when this {@link Plugin} is supposed to be started. @@ -46,8 +46,8 @@ public interface Plugin { /** * Returns {@code true} if this {@link Plugin} is enabled. */ - default boolean isEnabled(CentralDogmaConfig config) { - final PluginConfig pluginConfig = config.pluginConfigMap().get(configType()); + default boolean isEnabled(CentralDogmaConfigSpec config) { + final PluginConfig pluginConfig = config.pluginConfig(configType()); if (pluginConfig == null) { // Enabled if not found. return true; @@ -58,5 +58,5 @@ default boolean isEnabled(CentralDogmaConfig config) { /** * Returns the type of the configuration for this {@link Plugin}. */ - Class configType(); + Class configType(); } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/plugin/PluginContext.java b/server/src/main/java/com/linecorp/centraldogma/server/plugin/PluginContext.java index 87eca18c4..64ac5bd61 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/plugin/PluginContext.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/plugin/PluginContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -20,6 +20,7 @@ import java.util.concurrent.ScheduledExecutorService; import com.linecorp.centraldogma.server.CentralDogmaConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.command.CommandExecutor; import com.linecorp.centraldogma.server.storage.project.InternalProjectInitializer; @@ -33,7 +34,7 @@ */ public class PluginContext { - private final CentralDogmaConfig config; + private final CentralDogmaConfigSpec config; private final ProjectManager projectManager; private final CommandExecutor commandExecutor; private final MeterRegistry meterRegistry; @@ -49,7 +50,7 @@ public class PluginContext { * @param meterRegistry the {@link MeterRegistry} of the Central Dogma server * @param purgeWorker the {@link ScheduledExecutorService} for the purging service */ - public PluginContext(CentralDogmaConfig config, + public PluginContext(CentralDogmaConfigSpec config, ProjectManager projectManager, CommandExecutor commandExecutor, MeterRegistry meterRegistry, @@ -67,7 +68,7 @@ public PluginContext(CentralDogmaConfig config, /** * Returns the {@link CentralDogmaConfig}. */ - public CentralDogmaConfig config() { + public CentralDogmaConfigSpec config() { return config; } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/plugin/PluginInitContext.java b/server/src/main/java/com/linecorp/centraldogma/server/plugin/PluginInitContext.java index ad8ea34bd..b82d6ff1d 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/plugin/PluginInitContext.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/plugin/PluginInitContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -24,7 +24,7 @@ import com.linecorp.armeria.server.HttpService; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.auth.AuthService; -import com.linecorp.centraldogma.server.CentralDogmaConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; import com.linecorp.centraldogma.server.command.CommandExecutor; import com.linecorp.centraldogma.server.storage.project.InternalProjectInitializer; import com.linecorp.centraldogma.server.storage.project.ProjectManager; @@ -42,7 +42,7 @@ public final class PluginInitContext extends PluginContext { /** * Creates a new instance. */ - public PluginInitContext(CentralDogmaConfig config, + public PluginInitContext(CentralDogmaConfigSpec config, ProjectManager projectManager, CommandExecutor commandExecutor, MeterRegistry meterRegistry, diff --git a/server/src/main/java/com/linecorp/centraldogma/server/storage/repository/MetaRepository.java b/server/src/main/java/com/linecorp/centraldogma/server/storage/repository/MetaRepository.java index f82864a8d..7d7fd4f72 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/storage/repository/MetaRepository.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/storage/repository/MetaRepository.java @@ -23,7 +23,7 @@ import com.linecorp.centraldogma.common.Author; import com.linecorp.centraldogma.internal.api.v1.MirrorDto; -import com.linecorp.centraldogma.server.ZoneConfig; +import com.linecorp.centraldogma.server.ZoneConfigSpec; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.command.CommitResult; import com.linecorp.centraldogma.server.credential.Credential; @@ -66,7 +66,7 @@ default CompletableFuture> mirrors() { * Create a push {@link Command} for the {@link MirrorDto}. */ CompletableFuture> createPushCommand(MirrorDto mirrorDto, Author author, - @Nullable ZoneConfig zoneConfig, + @Nullable ZoneConfigSpec zoneConfig, boolean update); /** diff --git a/server/src/test/java/com/linecorp/centraldogma/server/CentralDogmaConfigTest.java b/server/src/test/java/com/linecorp/centraldogma/server/CentralDogmaConfigTest.java index b5655a9d6..570d122b2 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/CentralDogmaConfigTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/CentralDogmaConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -38,7 +38,7 @@ class CentralDogmaConfigTest { @Test void trustedProxyAddress_withCustomClientAddressSources() throws Exception { - final CentralDogmaConfig cfg = + final CentralDogmaConfigSpec cfg = Jackson.readValue("{\n" + " \"dataDir\": \"./data\",\n" + " \"ports\": [\n" + @@ -84,7 +84,7 @@ void trustedProxyAddress_withCustomClientAddressSources() throws Exception { @Test void trustedProxyAddress_withDefaultClientAddressSources() throws Exception { - final CentralDogmaConfig cfg = + final CentralDogmaConfigSpec cfg = Jackson.readValue("{\n" + " \"dataDir\": \"./data\",\n" + " \"ports\": [\n" + @@ -121,7 +121,7 @@ void trustedProxyAddress_withDefaultClientAddressSources() throws Exception { @Test void trustedProxyAddress_withDefaultClientAddressSources_withoutProxyProtocol() throws Exception { - final CentralDogmaConfig cfg = + final CentralDogmaConfigSpec cfg = Jackson.readValue("{\n" + " \"dataDir\": \"./data\",\n" + " \"ports\": [\n" + @@ -153,7 +153,7 @@ void trustedProxyAddress_withDefaultClientAddressSources_withoutProxyProtocol() @Test void noTrustedProxyAddress() throws Exception { - final CentralDogmaConfig cfg = + final CentralDogmaConfigSpec cfg = Jackson.readValue("{\n" + " \"dataDir\": \"./data\",\n" + " \"ports\": [\n" + @@ -178,7 +178,7 @@ void noTrustedProxyAddress() throws Exception { @Test void maxRemovedRepositoryAgeMillis() throws Exception { - final CentralDogmaConfig cfg = + final CentralDogmaConfigSpec cfg = Jackson.readValue("{\n" + " \"dataDir\": \"./data\",\n" + " \"ports\": [\n" + @@ -202,7 +202,7 @@ void maxRemovedRepositoryAgeMillis() throws Exception { @Test void maxRemovedRepositoryAgeMillis_withDefault() throws Exception { - final CentralDogmaConfig cfg = + final CentralDogmaConfigSpec cfg = Jackson.readValue("{\n" + " \"dataDir\": \"./data\",\n" + " \"ports\": [\n" + @@ -249,7 +249,7 @@ void maxRemovedRepositoryAgeMillis_withNegativeValue() { @Test void corsConfig_withSingleAllowOriginA_withDefaultMaxAge() throws Exception { - final CentralDogmaConfig cfg = + final CentralDogmaConfigSpec cfg = Jackson.readValue("{\n" + " \"dataDir\": \"./data\",\n" + " \"ports\": [\n" + @@ -282,7 +282,7 @@ void corsConfig_withSingleAllowOriginA_withDefaultMaxAge() throws Exception { @Test void corsConfig_withListAllowOrigins_withSpecifiedMaxAge() throws Exception { - final CentralDogmaConfig cfg = + final CentralDogmaConfigSpec cfg = Jackson.readValue("{\n" + " \"dataDir\": \"./data\",\n" + " \"ports\": [\n" + diff --git a/server/src/test/java/com/linecorp/centraldogma/server/ConfigDeserializationTest.java b/server/src/test/java/com/linecorp/centraldogma/server/ConfigDeserializationTest.java index 87f39a16f..f267c7de1 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/ConfigDeserializationTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/ConfigDeserializationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -84,7 +84,7 @@ void tlsConfigConfigValueConverter() throws Exception { } private static void checkContent(String jsonConfig) throws IOException { - final TlsConfig tlsConfig = Jackson.readValue(jsonConfig, ParentConfig.class).tlsConfig; + final TlsConfigSpec tlsConfig = Jackson.readValue(jsonConfig, ParentConfig.class).tlsConfig; readStream(tlsConfig.keyCertChainInputStream(), "foo"); readStream(tlsConfig.keyInputStream(), "bar"); @@ -121,7 +121,7 @@ public String convert(String prefix, String value) { } static class ParentConfig { - private final TlsConfig tlsConfig; + private final TlsConfigSpec tlsConfig; @JsonCreator ParentConfig(@JsonProperty("tls") TlsConfig tlsConfig) { diff --git a/server/src/test/java/com/linecorp/centraldogma/server/PluginGroupTest.java b/server/src/test/java/com/linecorp/centraldogma/server/PluginGroupTest.java index b63ed4ce6..f4c3c10bc 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/PluginGroupTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/PluginGroupTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -16,6 +16,7 @@ package com.linecorp.centraldogma.server; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -23,22 +24,22 @@ import org.junit.jupiter.api.Test; -import com.google.common.collect.ImmutableMap; - import com.linecorp.centraldogma.server.internal.mirror.DefaultMirroringServicePlugin; import com.linecorp.centraldogma.server.internal.storage.PurgeSchedulingServicePlugin; import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfig; +import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfigSpec; import com.linecorp.centraldogma.server.plugin.AbstractNoopPlugin; import com.linecorp.centraldogma.server.plugin.NoopPluginForAllReplicas; import com.linecorp.centraldogma.server.plugin.NoopPluginForLeader; import com.linecorp.centraldogma.server.plugin.PluginContext; import com.linecorp.centraldogma.server.plugin.PluginTarget; +@SuppressWarnings("DataFlowIssue") class PluginGroupTest { @Test void confirmPluginsForAllReplicasLoaded() { - final CentralDogmaConfig cfg = mock(CentralDogmaConfig.class); + final CentralDogmaConfigSpec cfg = mock(CentralDogmaConfigSpec.class); final PluginGroup group = PluginGroup.loadPlugins(PluginTarget.ALL_REPLICAS, cfg); assertThat(group).isNotNull(); confirmPluginStartStop(group.findFirstPlugin(NoopPluginForAllReplicas.class)); @@ -46,7 +47,7 @@ void confirmPluginsForAllReplicasLoaded() { @Test void confirmPluginsForLeaderLoaded() { - final CentralDogmaConfig cfg = mock(CentralDogmaConfig.class); + final CentralDogmaConfigSpec cfg = mock(CentralDogmaConfigSpec.class); final PluginGroup group = PluginGroup.loadPlugins(PluginTarget.LEADER_ONLY, cfg); assertThat(group).isNotNull(); confirmPluginStartStop(group.findFirstPlugin(NoopPluginForLeader.class)); @@ -54,20 +55,20 @@ void confirmPluginsForLeaderLoaded() { @Test void confirmDefaultMirroringServiceLoadedDependingOnConfig() { - final CentralDogmaConfig cfg = mock(CentralDogmaConfig.class); - when(cfg.pluginConfigMap()).thenReturn(ImmutableMap.of()); + final CentralDogmaConfigSpec cfg = mock(CentralDogmaConfigSpec.class); final PluginGroup group1 = PluginGroup.loadPlugins(PluginTarget.LEADER_ONLY, cfg); assertThat(group1).isNotNull(); assertThat(group1.findFirstPlugin(DefaultMirroringServicePlugin.class)).isNotNull(); - when(cfg.pluginConfigMap()).thenReturn(ImmutableMap.of( - MirroringServicePluginConfig.class, new MirroringServicePluginConfig(true))); + when(cfg.pluginConfig(any())).thenReturn(null); + when(cfg.pluginConfig(MirroringServicePluginConfigSpec.class)) + .thenReturn(new MirroringServicePluginConfig(true)); final PluginGroup group2 = PluginGroup.loadPlugins(PluginTarget.LEADER_ONLY, cfg); assertThat(group2).isNotNull(); assertThat(group2.findFirstPlugin(DefaultMirroringServicePlugin.class)).isNotNull(); - when(cfg.pluginConfigMap()).thenReturn(ImmutableMap.of( - MirroringServicePluginConfig.class, new MirroringServicePluginConfig(false))); + when(cfg.pluginConfig(MirroringServicePluginConfigSpec.class)) + .thenReturn(new MirroringServicePluginConfig(false)); final PluginGroup group3 = PluginGroup.loadPlugins(PluginTarget.LEADER_ONLY, cfg); assertThat(group3).isNotNull(); assertThat(group3.findFirstPlugin(DefaultMirroringServicePlugin.class)).isNull(); @@ -79,7 +80,7 @@ void confirmDefaultMirroringServiceLoadedDependingOnConfig() { */ @Test void confirmScheduledPurgingServiceLoadedDependingOnConfig() { - final CentralDogmaConfig cfg = mock(CentralDogmaConfig.class); + final CentralDogmaConfigSpec cfg = mock(CentralDogmaConfigSpec.class); when(cfg.maxRemovedRepositoryAgeMillis()).thenReturn(1L); final PluginGroup group1 = PluginGroup.loadPlugins(PluginTarget.LEADER_ONLY, cfg); assertThat(group1).isNotNull(); diff --git a/server/src/test/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfigTest.java b/server/src/test/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfigTest.java index 84ed85f63..63f3b89c2 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfigTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -30,10 +30,10 @@ class ZooKeeperReplicationConfigTest { @Test void testJsonConversion() { - final ImmutableMap servers = ImmutableMap.of( + final ImmutableMap servers = ImmutableMap.of( 1, new ZooKeeperServerConfig("2", 3, 4, 5, /* groupId */ null, /* weight */ 1), 6, new ZooKeeperServerConfig("7", 8, 9, 10, /* groupId */ null, /* weight */ 1)); - final ZooKeeperReplicationConfig cfg = new ZooKeeperReplicationConfig( + final ZooKeeperReplicationConfigSpec cfg = new ZooKeeperReplicationConfig( 1, servers, "11", ImmutableMap.of("12", "13", "14", "15", "quorumListenOnAllIPs", "true"), 16, 17, 18, 19); assertJsonConversion(cfg, ReplicationConfig.class, @@ -101,7 +101,7 @@ void testJsonConversionWithoutOptionalProperties() throws Exception { @Test void autoDetection() throws Exception { - final ZooKeeperReplicationConfig cfg = Jackson.readValue( + final ZooKeeperReplicationConfigSpec cfg = Jackson.readValue( '{' + " \"method\": \"ZOOKEEPER\"," + " \"servers\": {" + diff --git a/server/src/test/java/com/linecorp/centraldogma/server/auth/ReplicatedLoginAndLogoutTest.java b/server/src/test/java/com/linecorp/centraldogma/server/auth/ReplicatedLoginAndLogoutTest.java index 5c00d5f84..a2c9a0628 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/auth/ReplicatedLoginAndLogoutTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/auth/ReplicatedLoginAndLogoutTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -54,6 +54,7 @@ import com.linecorp.centraldogma.server.GracefulShutdownTimeout; import com.linecorp.centraldogma.server.ZooKeeperReplicationConfig; import com.linecorp.centraldogma.server.ZooKeeperServerConfig; +import com.linecorp.centraldogma.server.ZooKeeperServerConfigSpec; import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfig; import com.linecorp.centraldogma.testing.internal.FlakyTest; import com.linecorp.centraldogma.testing.internal.TemporaryFolderExtension; @@ -85,7 +86,7 @@ void setUp() throws Exception { final int zkElectionPort2 = InstanceSpec.getRandomPort(); final int zkClientPort2 = InstanceSpec.getRandomPort(); - final Map servers = ImmutableMap.of( + final Map servers = ImmutableMap.of( 1, new ZooKeeperServerConfig("127.0.0.1", zkQuorumPort1, zkElectionPort1, zkClientPort1, /* groupId */ null, /* weight */ 1), 2, new ZooKeeperServerConfig("127.0.0.1", zkQuorumPort2, zkElectionPort2, diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/command/CommandExecutorStatusManagerTest.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/command/CommandExecutorStatusManagerTest.java index ab8a4bfd0..35fa05733 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/command/CommandExecutorStatusManagerTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/command/CommandExecutorStatusManagerTest.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test; import com.linecorp.armeria.common.util.UnmodifiableFuture; -import com.linecorp.centraldogma.server.QuotaConfig; +import com.linecorp.centraldogma.server.QuotaConfigSpec; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.command.CommandExecutor; import com.linecorp.centraldogma.server.command.CommandExecutorStatusManager; @@ -111,7 +111,7 @@ public void setWritable(boolean writable) { } @Override - public void setWriteQuota(String projectName, String repoName, @Nullable QuotaConfig writeQuota) { + public void setWriteQuota(String projectName, String repoName, @Nullable QuotaConfigSpec writeQuota) { } @Override diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirrorSchedulingServicePluginTest.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirrorSchedulingServicePluginTest.java index 59ea58c38..2a0644fd4 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirrorSchedulingServicePluginTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirrorSchedulingServicePluginTest.java @@ -21,6 +21,7 @@ import com.linecorp.centraldogma.server.CentralDogmaConfig; import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfig; +import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfigSpec; class MirrorSchedulingServicePluginTest { @@ -50,9 +51,8 @@ void pluginConfig() throws Exception { " }" + " ]\n" + '}'); - final MirroringServicePluginConfig mirroringServicePluginConfig = - (MirroringServicePluginConfig) centralDogmaConfig.pluginConfigMap() - .get(MirroringServicePluginConfig.class); + final MirroringServicePluginConfigSpec mirroringServicePluginConfig = + centralDogmaConfig.pluginConfig(MirroringServicePluginConfigSpec.class); assertThat(mirroringServicePluginConfig).isNotNull(); assertThat(mirroringServicePluginConfig.enabled()).isTrue(); assertThat(mirroringServicePluginConfig.numMirroringThreads()).isOne(); diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/ClusterBuilder.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/ClusterBuilder.java index 36153aa4a..1b2145b3f 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/ClusterBuilder.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/ClusterBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -36,6 +36,7 @@ import com.linecorp.centraldogma.server.QuotaConfig; import com.linecorp.centraldogma.server.ZooKeeperServerConfig; +import com.linecorp.centraldogma.server.ZooKeeperServerConfigSpec; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.testing.internal.TemporaryFolder; @@ -89,7 +90,7 @@ Cluster build(Supplier, CompletableFuture>> commandExecut final TemporaryFolder tempFolder = new TemporaryFolder(); tempFolder.create(); final List specs = new ArrayList<>(); - final Map servers = new HashMap<>(); + final Map servers = new HashMap<>(); final int groupSize = numReplicas / numGroups; for (int i = 0; i < numReplicas; i++) { final int serverId = i + 1; diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/Replica.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/Replica.java index e5c2db9aa..a511920df 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/Replica.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/Replica.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -38,8 +38,10 @@ import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.centraldogma.common.Author; import com.linecorp.centraldogma.server.QuotaConfig; +import com.linecorp.centraldogma.server.QuotaConfigSpec; import com.linecorp.centraldogma.server.ZooKeeperReplicationConfig; -import com.linecorp.centraldogma.server.ZooKeeperServerConfig; +import com.linecorp.centraldogma.server.ZooKeeperReplicationConfigSpec; +import com.linecorp.centraldogma.server.ZooKeeperServerConfigSpec; import com.linecorp.centraldogma.server.command.AbstractCommandExecutor; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.metadata.MetadataService; @@ -56,7 +58,7 @@ final class Replica { private final MeterRegistry meterRegistry; private final CompletableFuture startFuture; - Replica(InstanceSpec spec, Map servers, + Replica(InstanceSpec spec, Map servers, Function, CompletableFuture> delegate, @Nullable QuotaConfig writeQuota, boolean start) { this.delegate = delegate; @@ -65,7 +67,7 @@ final class Replica { meterRegistry = PrometheusMeterRegistries.newRegistry(); final int id = spec.getServerId(); - final ZooKeeperReplicationConfig zkCfg = new ZooKeeperReplicationConfig(id, servers); + final ZooKeeperReplicationConfigSpec zkCfg = new ZooKeeperReplicationConfig(id, servers); commandExecutor = new ZooKeeperCommandExecutor( zkCfg, dataDir, new AbstractCommandExecutor(null, null, null, null) { @@ -75,7 +77,7 @@ public int replicaId() { } @Override - public void setWriteQuota(String projectName, String repoName, QuotaConfig writeQuota) {} + public void setWriteQuota(String projectName, String repoName, QuotaConfigSpec writeQuota) {} @Override protected void doStart( diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/StartStopWithoutInitialQuorumTest.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/StartStopWithoutInitialQuorumTest.java index 91ff1845c..2a0b1e91f 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/StartStopWithoutInitialQuorumTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/StartStopWithoutInitialQuorumTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -28,6 +28,7 @@ import com.linecorp.centraldogma.server.CentralDogmaBuilder; import com.linecorp.centraldogma.server.ZooKeeperReplicationConfig; import com.linecorp.centraldogma.server.ZooKeeperServerConfig; +import com.linecorp.centraldogma.server.ZooKeeperServerConfigSpec; import com.linecorp.centraldogma.testing.internal.FlakyTest; import com.linecorp.centraldogma.testing.junit.CentralDogmaExtension; @@ -51,7 +52,7 @@ protected void configure(CentralDogmaBuilder builder) { final int electionPort = InstanceSpec.getRandomPort(); final int clientPort = InstanceSpec.getRandomPort(); - final Map servers = + final Map servers = ImmutableMap.of(1, new ZooKeeperServerConfig("127.0.0.1", quorumPort, electionPort, clientPort, /* groupId */ null, /* weight */ 1), diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/ZooKeeperCommandExecutorTest.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/ZooKeeperCommandExecutorTest.java index 75f6da968..000dfaa80 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/ZooKeeperCommandExecutorTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/replication/ZooKeeperCommandExecutorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -47,6 +47,7 @@ import java.util.function.Supplier; import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.function.ThrowingConsumer; @@ -70,6 +71,7 @@ import com.linecorp.centraldogma.common.ReadOnlyException; import com.linecorp.centraldogma.common.Revision; import com.linecorp.centraldogma.server.QuotaConfig; +import com.linecorp.centraldogma.server.QuotaConfigSpec; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.command.CommandType; import com.linecorp.centraldogma.server.command.CommitResult; @@ -550,12 +552,12 @@ void setWriteQuota() throws Exception { final String key = project + '/' + repo; final Replica replica = cluster.get(0); final ZooKeeperCommandExecutor executor = replica.commandExecutor(); - final Cache quotaCache = executor.writeQuotaCache; + final Cache quotaCache = executor.writeQuotaCache; final ConcurrentMap> semaphoreMap = executor.semaphoreMap; // Initial state - QuotaConfig writeQuota = quotaCache.getIfPresent(key); + @Nullable QuotaConfigSpec writeQuota = quotaCache.getIfPresent(key); Entry entry = semaphoreMap.get(key); assertThat(writeQuota).isNull(); assertThat(entry).isNull(); diff --git a/server/src/test/java/com/linecorp/centraldogma/server/plugin/AbstractNoopPlugin.java b/server/src/test/java/com/linecorp/centraldogma/server/plugin/AbstractNoopPlugin.java index d1ddb3995..26f1687e3 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/plugin/AbstractNoopPlugin.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/plugin/AbstractNoopPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -39,4 +39,9 @@ public CompletionStage stop(PluginContext context) { public boolean isStarted() { return isStarted; } + + @Override + public Class configType() { + return NoopPluginConfig.class; + } } diff --git a/server/src/test/java/com/linecorp/centraldogma/server/plugin/NoopPluginForAllReplicas.java b/server/src/test/java/com/linecorp/centraldogma/server/plugin/NoopPluginForAllReplicas.java index d4809bacf..f42e827ae 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/plugin/NoopPluginForAllReplicas.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/plugin/NoopPluginForAllReplicas.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -15,17 +15,11 @@ */ package com.linecorp.centraldogma.server.plugin; -import com.linecorp.centraldogma.server.CentralDogmaConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; public class NoopPluginForAllReplicas extends AbstractNoopPlugin { @Override - public PluginTarget target(CentralDogmaConfig config) { + public PluginTarget target(CentralDogmaConfigSpec config) { return PluginTarget.ALL_REPLICAS; } - - @Override - public Class configType() { - // Return the plugin class itself because it does not have a configuration. - return getClass(); - } } diff --git a/server/src/test/java/com/linecorp/centraldogma/server/plugin/NoopPluginForLeader.java b/server/src/test/java/com/linecorp/centraldogma/server/plugin/NoopPluginForLeader.java index f1055a236..ffd2aadf8 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/plugin/NoopPluginForLeader.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/plugin/NoopPluginForLeader.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -15,17 +15,11 @@ */ package com.linecorp.centraldogma.server.plugin; -import com.linecorp.centraldogma.server.CentralDogmaConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; public class NoopPluginForLeader extends AbstractNoopPlugin { @Override - public PluginTarget target(CentralDogmaConfig config) { + public PluginTarget target(CentralDogmaConfigSpec config) { return PluginTarget.LEADER_ONLY; } - - @Override - public Class configType() { - // Return the plugin class itself because it does not have a configuration. - return getClass(); - } } diff --git a/settings.gradle b/settings.gradle index 594bfbd01..93ccfdaa1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,8 +12,9 @@ includeWithFlags ':client:java-armeria-legacy', 'java', 'publish', ' includeWithFlags ':client:java-armeria-xds', 'java', 'publish', 'relocate' includeWithFlags ':client:java-spring-boot2-autoconfigure', 'java', 'publish', 'relocate', 'no_aggregation' includeWithFlags ':client:java-spring-boot2-starter', 'java', 'publish', 'relocate', 'no_aggregation' -includeWithFlags ':client:java-spring-boot3-autoconfigure', 'java17', 'publish', 'relocate' -includeWithFlags ':client:java-spring-boot3-starter', 'java17', 'publish', 'relocate' +includeWithFlags ':client:java-spring-boot3-autoconfigure', 'java17', 'spring-boot3', 'publish', 'relocate' +includeWithFlags ':client:java-spring-boot3-starter', 'java17', 'spring-boot3', 'publish', 'relocate' +includeWithFlags ':cloud-native:boot-loader', 'kotlin', 'spring-boot3' includeWithFlags ':common', 'java', 'publish', 'shade', 'trim' includeWithFlags ':common-legacy', 'java', 'publish', 'relocate' includeWithFlags ':server', 'java', 'publish', 'relocate' diff --git a/site/src/sphinx/auth.rst b/site/src/sphinx/auth.rst index a66fc3113..4477eff16 100644 --- a/site/src/sphinx/auth.rst +++ b/site/src/sphinx/auth.rst @@ -43,7 +43,7 @@ The authentication configuration consists of the following properties: - ``factoryClassName`` (string) - the fully-qualified class name of the :api:`AuthProviderFactory` implementation. Can be one of - ``com.linecorp.centraldogma.server.auth.saml.SamlAuthProviderFactory`` or + ``com.linecorp.armeria.auth.saml.SamlAuthProviderFactory`` or ``com.linecorp.centraldogma.server.auth.shiro.ShiroAuthProviderFactory``. - ``systemAdministrators`` (string array) diff --git a/testing-internal/src/main/java/com/linecorp/centraldogma/testing/internal/CentralDogmaReplicationExtension.java b/testing-internal/src/main/java/com/linecorp/centraldogma/testing/internal/CentralDogmaReplicationExtension.java index 8894792f1..ad09ed8ec 100644 --- a/testing-internal/src/main/java/com/linecorp/centraldogma/testing/internal/CentralDogmaReplicationExtension.java +++ b/testing-internal/src/main/java/com/linecorp/centraldogma/testing/internal/CentralDogmaReplicationExtension.java @@ -51,7 +51,9 @@ import com.linecorp.centraldogma.server.CentralDogmaBuilder; import com.linecorp.centraldogma.server.GracefulShutdownTimeout; import com.linecorp.centraldogma.server.ZooKeeperReplicationConfig; +import com.linecorp.centraldogma.server.ZooKeeperReplicationConfigSpec; import com.linecorp.centraldogma.server.ZooKeeperServerConfig; +import com.linecorp.centraldogma.server.ZooKeeperServerConfigSpec; import com.linecorp.centraldogma.server.auth.AuthProviderFactory; import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfig; import com.linecorp.centraldogma.testing.internal.auth.TestAuthMessageUtil; @@ -92,7 +94,7 @@ public List servers() { } private List newDogmaCluster(int numReplicas) throws IOException { - final ImmutableMap.Builder builder = + final ImmutableMap.Builder builder = ImmutableMap.builderWithExpectedSize(numReplicas); final int[] unusedPorts = TestUtil.unusedTcpPorts(numReplicas * 4); for (int i = 0; i < numReplicas; i++) { @@ -105,7 +107,7 @@ private List newDogmaCluster(int numReplicas) throws I zkClientPort, /* groupId */ null, /* weight */ 1)); } - final Map zooKeeperServers = builder.build(); + final Map zooKeeperServers = builder.build(); return zooKeeperServers.keySet().stream().map(serverId -> { final int dogmaPort = unusedPorts[(serverId - 1) * 4 + 3]; @@ -215,9 +217,9 @@ public void start() throws InterruptedException { } final List ports = new ArrayList<>(12); - final ZooKeeperReplicationConfig zkConfig = - (ZooKeeperReplicationConfig) dogmaCluster.get(0).dogma().config().replicationConfig(); - for (ZooKeeperServerConfig config : zkConfig.servers().values()) { + final ZooKeeperReplicationConfigSpec zkConfig = + (ZooKeeperReplicationConfigSpec) dogmaCluster.get(0).dogma().config().replicationConfig(); + for (ZooKeeperServerConfigSpec config : zkConfig.servers().values()) { ports.add(config.clientPort()); ports.add(config.electionPort()); ports.add(config.quorumPort()); diff --git a/testing/common/src/main/java/com/linecorp/centraldogma/testing/internal/CentralDogmaRuleDelegate.java b/testing/common/src/main/java/com/linecorp/centraldogma/testing/internal/CentralDogmaRuleDelegate.java index 081ada182..6a733824c 100644 --- a/testing/common/src/main/java/com/linecorp/centraldogma/testing/internal/CentralDogmaRuleDelegate.java +++ b/testing/common/src/main/java/com/linecorp/centraldogma/testing/internal/CentralDogmaRuleDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -46,6 +46,7 @@ import com.linecorp.centraldogma.server.MirroringService; import com.linecorp.centraldogma.server.TlsConfig; import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfig; +import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfigSpec; import com.linecorp.centraldogma.server.plugin.PluginConfig; import com.linecorp.centraldogma.server.storage.project.ProjectManager; @@ -116,8 +117,8 @@ public final CompletableFuture startAsync(File dataDir) { configure(builder); final com.linecorp.centraldogma.server.CentralDogma dogma = builder.build(); - final PluginConfig mirroringConfig = dogma.config().pluginConfigMap().get( - MirroringServicePluginConfig.class); + final PluginConfig mirroringConfig = dogma.config().pluginConfig( + MirroringServicePluginConfigSpec.class); final com.linecorp.centraldogma.server.CentralDogma dogma0; if (mirroringConfig == null) { // Disable mirror plugin if not configured. @@ -160,7 +161,7 @@ public final CompletableFuture startAsync(File dataDir) { } final String protocol = serverPort.hasHttp() ? "h2c" : "h2"; - final String uri = protocol + "://127.0.0.1:" + serverPort.localAddress().getPort(); + final String uri = protocol + "://127.0.0.1:" + serverPort.localAddress().getPort(); final WebClientBuilder webClientBuilder = WebClient.builder(uri); if (accessToken != null) { webClientBuilder.auth(AuthToken.ofOAuth2(accessToken)); diff --git a/xds/src/main/java/com/linecorp/centraldogma/xds/internal/ControlPlanePlugin.java b/xds/src/main/java/com/linecorp/centraldogma/xds/internal/ControlPlanePlugin.java index 2cda5ac42..98a1d6bb7 100644 --- a/xds/src/main/java/com/linecorp/centraldogma/xds/internal/ControlPlanePlugin.java +++ b/xds/src/main/java/com/linecorp/centraldogma/xds/internal/ControlPlanePlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 LINE Corporation + * Copyright 2024 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -23,6 +23,7 @@ import com.linecorp.armeria.common.annotation.Nullable; import com.linecorp.armeria.common.util.UnmodifiableFuture; import com.linecorp.centraldogma.server.plugin.AllReplicasPlugin; +import com.linecorp.centraldogma.server.plugin.PluginConfig; import com.linecorp.centraldogma.server.plugin.PluginContext; import com.linecorp.centraldogma.server.plugin.PluginInitContext; import com.linecorp.centraldogma.server.plugin.PluginTarget; @@ -61,7 +62,7 @@ public CompletionStage stop(PluginContext context) { } @Override - public Class configType() { + public Class configType() { return ControlPlanePluginConfig.class; } diff --git a/xds/src/main/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesEndpointFetchingPlugin.java b/xds/src/main/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesEndpointFetchingPlugin.java index b3a3f5d26..e701b0b44 100644 --- a/xds/src/main/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesEndpointFetchingPlugin.java +++ b/xds/src/main/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesEndpointFetchingPlugin.java @@ -26,8 +26,10 @@ import com.google.common.base.MoreObjects; import com.linecorp.armeria.common.util.UnmodifiableFuture; -import com.linecorp.centraldogma.server.CentralDogmaConfig; +import com.linecorp.centraldogma.server.CentralDogmaConfigSpec; +import com.linecorp.centraldogma.server.plugin.NoopPluginConfig; import com.linecorp.centraldogma.server.plugin.Plugin; +import com.linecorp.centraldogma.server.plugin.PluginConfig; import com.linecorp.centraldogma.server.plugin.PluginContext; import com.linecorp.centraldogma.server.plugin.PluginTarget; @@ -44,7 +46,7 @@ public final class XdsKubernetesEndpointFetchingPlugin implements Plugin { private XdsKubernetesEndpointFetchingService fetchingService; @Override - public PluginTarget target(CentralDogmaConfig config) { + public PluginTarget target(CentralDogmaConfigSpec config) { return PluginTarget.LEADER_ONLY; } @@ -74,8 +76,8 @@ public synchronized CompletionStage stop(PluginContext context) { } @Override - public Class configType() { - return getClass(); + public Class configType() { + return NoopPluginConfig.class; } @Override diff --git a/xds/src/test/java/com/linecorp/centraldogma/xds/internal/ControlPlanePluginConfigTest.java b/xds/src/test/java/com/linecorp/centraldogma/xds/internal/ControlPlanePluginConfigTest.java index 0635b1438..2d2d871ee 100644 --- a/xds/src/test/java/com/linecorp/centraldogma/xds/internal/ControlPlanePluginConfigTest.java +++ b/xds/src/test/java/com/linecorp/centraldogma/xds/internal/ControlPlanePluginConfigTest.java @@ -50,7 +50,7 @@ void duplicatePluginConfigs() throws Exception { " }" + " ]\n" + '}'); - final PluginConfig pluginConfig = config.pluginConfigMap().get(ControlPlanePluginConfig.class); + final PluginConfig pluginConfig = config.pluginConfig(ControlPlanePluginConfig.class); assertThat(pluginConfig).isNotNull(); assertThat(pluginConfig.enabled()).isFalse(); } diff --git a/xds/src/test/java/com/linecorp/centraldogma/xds/internal/CreatingInternalGroupPlugin.java b/xds/src/test/java/com/linecorp/centraldogma/xds/internal/CreatingInternalGroupPlugin.java index 7f4f54fac..80e3e8836 100644 --- a/xds/src/test/java/com/linecorp/centraldogma/xds/internal/CreatingInternalGroupPlugin.java +++ b/xds/src/test/java/com/linecorp/centraldogma/xds/internal/CreatingInternalGroupPlugin.java @@ -23,6 +23,8 @@ import com.linecorp.centraldogma.common.Author; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.plugin.AllReplicasPlugin; +import com.linecorp.centraldogma.server.plugin.NoopPluginConfig; +import com.linecorp.centraldogma.server.plugin.PluginConfig; import com.linecorp.centraldogma.server.plugin.PluginContext; import com.linecorp.centraldogma.server.plugin.PluginInitContext; @@ -53,7 +55,7 @@ public CompletionStage stop(PluginContext context) { } @Override - public Class configType() { - return getClass(); + public Class configType() { + return NoopPluginConfig.class; } }