From 58e3a225694372dd51ff98e0266311266b7ce8e7 Mon Sep 17 00:00:00 2001 From: minwoox Date: Mon, 15 Jul 2024 23:41:06 +0900 Subject: [PATCH 1/6] Add xDS application service. Motivation: A user might want to manage her own xDS resources with a group of people who have the permission to create and update these resources. By creating an xDS application (a Central Dogma repository), she can easily manage these xDS resources and grant write permissions to specific people, allowing them to manage the resources under Central Dogma's authorization system. Modifications: - Added `XdsApplicationService` to create an application, which is a Central Dogma repository. - With this application, a user can easily manage privileged people under the Central Dogma authorization system. Result: - You can now create xDS applications to manage your xDS resources under the Central Dogma authorization system. --- dependencies.toml | 11 ++ .../internal/api/RepositoryServiceUtil.java | 43 +++++++ .../internal/api/RepositoryServiceV1.java | 18 ++- .../application/v1/XdsApplicationService.java | 118 ++++++++++++++++++ .../xds/application/v1/package-info.java | 22 ++++ .../xds/internal/ControlPlanePlugin.java | 6 +- .../xds/k8s/v1/xds_application.proto | 62 +++++++++ .../v1/XdsApplicationServiceTest.java | 102 +++++++++++++++ 8 files changed, 375 insertions(+), 7 deletions(-) create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceUtil.java create mode 100644 xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java create mode 100644 xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/package-info.java create mode 100644 xds/src/main/proto/centraldogma/xds/k8s/v1/xds_application.proto create mode 100644 xds/src/test/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationServiceTest.java diff --git a/dependencies.toml b/dependencies.toml index 40c5a9c4e3..e2c9b1a74e 100644 --- a/dependencies.toml +++ b/dependencies.toml @@ -20,6 +20,7 @@ dropwizard-metrics = "4.2.21" eddsa = "0.3.0" findbugs = "3.0.2" futures-completable = "0.3.6" +grpc-java = "1.64.0" guava = "33.2.1-jre" guava-failureaccess = "1.0.1" hamcrest-library = "2.2" @@ -53,6 +54,8 @@ nexus-publish-plugin = "2.0.0" node-gradle-plugin = "7.0.2" osdetector = "1.7.3" proguard = "7.4.2" +protobuf = "3.25.1" +protobuf-gradle-plugin = "0.8.19" quartz = "2.3.2" shadow-gradle-plugin = "7.1.2" shiro = "1.3.2" @@ -72,6 +75,7 @@ zookeeper = "3.7.2" [boms] armeria = { module = "com.linecorp.armeria:armeria-bom", version.ref = "armeria" } jackson = { module = "com.fasterxml.jackson:jackson-bom", version.ref = "jackson" } +grpc-java = { module = "io.grpc:grpc-bom", version.ref = "grpc-java" } junit5 = { module = "org.junit:junit-bom", version.ref = "junit5" } [libraries.armeria] @@ -328,6 +332,13 @@ version.ref = "mockito" module = "com.guardsquare:proguard-gradle" version.ref = "proguard" +[libraries.protobuf-protoc] +module = "com.google.protobuf:protoc" +version.ref = "protobuf" +[libraries.protobuf-gradle-plugin] +module = "com.google.protobuf:protobuf-gradle-plugin" +version.ref = "protobuf-gradle-plugin" + [libraries.quartz] module = "org.quartz-scheduler:quartz" version.ref = "quartz" diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceUtil.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceUtil.java new file mode 100644 index 0000000000..62fd90fb49 --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceUtil.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.internal.api; + +import java.util.concurrent.CompletableFuture; + +import com.linecorp.centraldogma.common.Author; +import com.linecorp.centraldogma.common.Revision; +import com.linecorp.centraldogma.server.command.Command; +import com.linecorp.centraldogma.server.command.CommandExecutor; +import com.linecorp.centraldogma.server.metadata.MetadataService; + +public final class RepositoryServiceUtil { + + public static CompletableFuture createRepository( + CommandExecutor commandExecutor, MetadataService mds, + Author author, String projectName, String repoName) { + return commandExecutor.execute(Command.createRepository(author, projectName, repoName)) + .thenCompose(unused -> mds.addRepo(author, projectName, repoName)); + } + + public static CompletableFuture removeRepository( + CommandExecutor commandExecutor, MetadataService mds, Author author, + String projectName, String repoName) { + return commandExecutor.execute(Command.removeRepository(author, projectName, repoName)) + .thenCompose(unused -> mds.removeRepo(author, projectName, repoName)); + } + + private RepositoryServiceUtil() {} +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceV1.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceV1.java index 48d1535dc3..21099bc5da 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceV1.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceV1.java @@ -121,13 +121,15 @@ public CompletableFuture> listRepositories(ServiceRequestCon public CompletableFuture createRepository(ServiceRequestContext ctx, Project project, CreateRepositoryRequest request, Author author) { - if (Project.isReservedRepoName(request.name())) { + final String repoName = request.name(); + if (Project.isReservedRepoName(repoName)) { return HttpApiUtil.throwResponse(ctx, HttpStatus.FORBIDDEN, "A reserved repository cannot be created."); } - return execute(Command.createRepository(author, project.name(), request.name())) - .thenCompose(unused -> mds.addRepo(author, project.name(), request.name())) - .handle(returnOrThrow(() -> DtoConverter.convert(project.repos().get(request.name())))); + final CommandExecutor commandExecutor = executor(); + final CompletableFuture future = + RepositoryServiceUtil.createRepository(commandExecutor, mds, author, project.name(), repoName); + return future.handle(returnOrThrow(() -> DtoConverter.convert(project.repos().get(repoName)))); } /** @@ -145,8 +147,12 @@ public CompletableFuture removeRepository(ServiceRequestContext ctx, return HttpApiUtil.throwResponse(ctx, HttpStatus.FORBIDDEN, "A reserved repository cannot be removed."); } - return execute(Command.removeRepository(author, repository.parent().name(), repository.name())) - .thenCompose(unused -> mds.removeRepo(author, repository.parent().name(), repository.name())) + final String projectName = repository.parent().name(); + final MetadataService mds = this.mds; + final CommandExecutor commandExecutor = executor(); + final CompletableFuture future = + RepositoryServiceUtil.removeRepository(commandExecutor, mds, author, projectName, repoName); + return future .handle(HttpApiUtil::throwUnsafelyIfNonNull); } diff --git a/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java b/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java new file mode 100644 index 0000000000..618a17e134 --- /dev/null +++ b/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java @@ -0,0 +1,118 @@ +/* + * 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.xds.application.v1; + +import static com.linecorp.centraldogma.server.internal.api.RepositoryServiceUtil.createRepository; +import static com.linecorp.centraldogma.server.internal.api.RepositoryServiceUtil.removeRepository; +import static com.linecorp.centraldogma.xds.internal.ControlPlanePlugin.XDS_CENTRAL_DOGMA_PROJECT; + +import com.google.protobuf.Empty; + +import com.linecorp.centraldogma.common.Author; +import com.linecorp.centraldogma.common.RepositoryExistsException; +import com.linecorp.centraldogma.server.command.CommandExecutor; +import com.linecorp.centraldogma.server.metadata.MetadataService; +import com.linecorp.centraldogma.server.storage.project.Project; +import com.linecorp.centraldogma.server.storage.project.ProjectManager; +import com.linecorp.centraldogma.xds.application.v1.XdsApplicationServiceGrpc.XdsApplicationServiceImplBase; + +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.stub.StreamObserver; + +/** + * An {@link XdsApplicationServiceImplBase} implementation that provides methods to manage XDS applications. + */ +public final class XdsApplicationService extends XdsApplicationServiceImplBase { + + private final ProjectManager projectManager; + private final CommandExecutor commandExecutor; + private final MetadataService mds; + + /** + * Creates a new instance. + */ + public XdsApplicationService(ProjectManager projectManager, CommandExecutor commandExecutor) { + this.projectManager = projectManager; + this.commandExecutor = commandExecutor; + mds = new MetadataService(projectManager, commandExecutor); + } + + @Override + public void createApplication(CreateApplicationRequest request, + StreamObserver responseObserver) { + final String applicationName = request.getApplication().getName(); + final String name = removePrefix("applications/", applicationName); + if (projectManager.get(XDS_CENTRAL_DOGMA_PROJECT).repos().exists(name)) { + throwAlreadyExists(name); + } + // Use the real author after https://github.com/line/centraldogma/pull/969 is merged + createRepository(commandExecutor, mds, Author.SYSTEM, XDS_CENTRAL_DOGMA_PROJECT, name) + .handle((unused, cause) -> { + if (cause != null) { + if (cause instanceof RepositoryExistsException) { + throwAlreadyExists(name); + } + responseObserver.onError( + new StatusRuntimeException(Status.INTERNAL.withCause(cause))); + return null; + } + responseObserver.onNext(Application.newBuilder().setName(applicationName).build()); + responseObserver.onCompleted(); + return null; + }); + } + + private static String removePrefix(String prefix, String name) { + if (!name.startsWith(prefix)) { + throw new StatusRuntimeException( + Status.INVALID_ARGUMENT.withDescription(name + " does not start with prefix: " + prefix)); + } + return name.substring(prefix.length()); + } + + private static void throwAlreadyExists(String applicationName) { + throw new StatusRuntimeException( + Status.ALREADY_EXISTS.withDescription("Application already exists: " + applicationName)); + } + + @Override + public void deleteApplication(DeleteApplicationRequest request, StreamObserver responseObserver) { + final String applicationName = request.getName(); + final String name = removePrefix("applications/", applicationName); + if (!projectManager.get(XDS_CENTRAL_DOGMA_PROJECT).repos().exists(name)) { + throw new StatusRuntimeException( + Status.NOT_FOUND.withDescription("Application does not exist: " + applicationName)); + } + if (Project.isReservedRepoName(name)) { + throw new StatusRuntimeException( + Status.PERMISSION_DENIED.withDescription("Now allowed to delete " + applicationName)); + } + + // Check the permission and return PERMISSION_DENIED if the user does not have the permission. + removeRepository(commandExecutor, mds, Author.SYSTEM, XDS_CENTRAL_DOGMA_PROJECT, name) + .handle((unused, cause) -> { + if (cause != null) { + responseObserver.onError(new StatusRuntimeException( + Status.INTERNAL.withDescription("Failed to delete " + applicationName) + .withCause(cause))); + } + responseObserver.onNext(Empty.getDefaultInstance()); + responseObserver.onCompleted(); + return null; + }); + } +} diff --git a/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/package-info.java b/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/package-info.java new file mode 100644 index 0000000000..b0e3ac932b --- /dev/null +++ b/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ +/** + * xDS application service. + */ +@NonNullByDefault +package com.linecorp.centraldogma.xds.application.v1; + +import com.linecorp.centraldogma.common.util.NonNullByDefault; 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 241f6875f9..318bba352b 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 @@ -50,6 +50,7 @@ import com.linecorp.centraldogma.server.storage.repository.DiffResultType; import com.linecorp.centraldogma.server.storage.repository.Repository; import com.linecorp.centraldogma.server.storage.repository.RepositoryManager; +import com.linecorp.centraldogma.xds.application.v1.XdsApplicationService; import io.envoyproxy.controlplane.cache.v3.SimpleCache; import io.envoyproxy.controlplane.server.DiscoveryServerCallbacks; @@ -68,7 +69,7 @@ public final class ControlPlanePlugin extends AllReplicasPlugin { private static final Logger logger = LoggerFactory.getLogger(ControlPlanePlugin.class); - static final String XDS_CENTRAL_DOGMA_PROJECT = "@xds"; + public static final String XDS_CENTRAL_DOGMA_PROJECT = "@xds"; static final String CLUSTERS_DIRECTORY = "/clusters/"; static final String ENDPOINTS_DIRECTORY = "/endpoints/"; @@ -145,6 +146,9 @@ private void init0(PluginInitContext pluginInitContext) { .addService(server.getListenerDiscoveryServiceImpl()) .addService(server.getRouteDiscoveryServiceImpl()) .addService(server.getAggregatedDiscoveryServiceImpl()) + .addService(new XdsApplicationService( + projectManager, pluginInitContext.commandExecutor())) + .enableHttpJsonTranscoding(true) .useBlockingTaskExecutor(true) .build(); sb.route().build(grpcService); diff --git a/xds/src/main/proto/centraldogma/xds/k8s/v1/xds_application.proto b/xds/src/main/proto/centraldogma/xds/k8s/v1/xds_application.proto new file mode 100644 index 0000000000..90278ac6e1 --- /dev/null +++ b/xds/src/main/proto/centraldogma/xds/k8s/v1/xds_application.proto @@ -0,0 +1,62 @@ +// 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. +syntax = "proto3"; + +package centraldogma.xds.application.v1; + +option java_multiple_files = true; +option java_outer_classname = "XdsApplicationProto"; +option java_package = "com.linecorp.centraldogma.xds.application.v1"; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/empty.proto"; + +// An XdsApplicationService provides methods to manage applications. +service XdsApplicationService { + + // Creates a new application. + rpc CreateApplication(CreateApplicationRequest) returns (Application) { + option (google.api.http) = { + post: "/api/v1/xds/applications" + body: "application" + }; + } + + // Deletes an application. + rpc DeleteApplication(DeleteApplicationRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/api/v1/xds/{name=applications/*}" + }; + } +} + +message CreateApplicationRequest { + Application application = 1 [(google.api.field_behavior) = REQUIRED]; +} + +message DeleteApplicationRequest { + // Format: applications/{application} + string name = 1 [(google.api.field_behavior) = REQUIRED]; + + // If set to true, any xds Resources from this application will also be deleted. + // (Otherwise, the request will only work if the application has no xDS resources.) + // bool force = 2; +} + +message Application { + // Format: applications/{application} + string name = 1 [(google.api.field_behavior) = IDENTIFIER]; +} diff --git a/xds/src/test/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationServiceTest.java b/xds/src/test/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationServiceTest.java new file mode 100644 index 0000000000..9053ff3c03 --- /dev/null +++ b/xds/src/test/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationServiceTest.java @@ -0,0 +1,102 @@ +/* + * 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.xds.application.v1; + +import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.google.protobuf.Empty; + +import com.linecorp.armeria.client.grpc.GrpcClients; +import com.linecorp.armeria.common.AggregatedHttpResponse; +import com.linecorp.armeria.common.HttpMethod; +import com.linecorp.armeria.common.HttpStatus; +import com.linecorp.armeria.common.MediaType; +import com.linecorp.armeria.common.RequestHeaders; +import com.linecorp.centraldogma.testing.junit.CentralDogmaExtension; +import com.linecorp.centraldogma.xds.application.v1.XdsApplicationServiceGrpc.XdsApplicationServiceBlockingStub; + +import io.grpc.Status; + +class XdsApplicationServiceTest { + + @RegisterExtension + static final CentralDogmaExtension dogma = new CentralDogmaExtension(); + + @Test + void createApplicationViaHttp() { + // Invalid name. + AggregatedHttpResponse response = createApplication("invalid/foo"); + assertThat(response.status()).isSameAs(HttpStatus.BAD_REQUEST); + + response = createApplication("applications/foo"); + assertThat(response.status()).isSameAs(HttpStatus.OK); + assertThat(response.headers().get("grpc-status")).isEqualTo("0"); + assertThatJson(response.contentUtf8()).isEqualTo("{\"name\":\"applications/foo\"}"); + + // Cannot create with the same name. + response = createApplication("applications/foo"); + assertThat(response.status()).isSameAs(HttpStatus.CONFLICT); + assertThat(response.headers().get("grpc-status")) + .isEqualTo(Integer.toString(Status.ALREADY_EXISTS.getCode().value())); + } + + private static AggregatedHttpResponse createApplication(String applicationName) { + final RequestHeaders headers = RequestHeaders.builder(HttpMethod.POST, "/api/v1/xds/applications") + .contentType(MediaType.JSON_UTF_8).build(); + return dogma.httpClient().execute(headers, "{\"application\": {\"name\":\"" + applicationName + "\"}}") + .aggregate().join(); + } + + @Test + void deleteApplicationViaHttp() { + AggregatedHttpResponse response = deleteApplication("applications/bar"); + assertThat(response.status()).isSameAs(HttpStatus.NOT_FOUND); + + response = createApplication("applications/bar"); + assertThat(response.status()).isSameAs(HttpStatus.OK); + + // Add permission test. + + response = deleteApplication("applications/bar"); + assertThat(response.status()).isSameAs(HttpStatus.OK); + assertThat(response.headers().get("grpc-status")).isEqualTo("0"); + assertThat(response.contentUtf8()).isEqualTo("{}"); + } + + private static AggregatedHttpResponse deleteApplication(String applicationName) { + return dogma.httpClient().delete("/api/v1/xds/" + applicationName).aggregate().join(); + } + + @Test + void createAndDeleteApplicationViaStub() { + final XdsApplicationServiceBlockingStub client = GrpcClients.newClient( + dogma.httpClient().uri(), XdsApplicationServiceBlockingStub.class); + final Application application = client.createApplication( + CreateApplicationRequest.newBuilder() + .setApplication(Application.newBuilder() + .setName("applications/baz")) + .build()); + assertThat(application.getName()).isEqualTo("applications/baz"); + // No exception is thrown. + final Empty ignored = client.deleteApplication(DeleteApplicationRequest.newBuilder() + .setName("applications/baz") + .build()); + } +} From 99f4ecdbc7a29ee79dbd3cbd32c910da412b6a27 Mon Sep 17 00:00:00 2001 From: minwoox Date: Tue, 16 Jul 2024 15:38:04 +0900 Subject: [PATCH 2/6] Update --- .../centraldogma/server/CentralDogma.java | 93 ++++++++++--------- .../server/internal/admin/auth/AuthUtil.java | 8 +- .../api/auth/RequiresPermissionDecorator.java | 30 ++++-- .../api/auth/RequiresRoleDecorator.java | 14 ++- .../metadata/MetadataServiceInjector.java | 71 -------------- .../server/plugin/PluginInitContext.java | 13 +++ .../internal/api/auth/PermissionTest.java | 18 ++-- .../application/v1/XdsApplicationService.java | 42 ++++----- .../xds/internal/ControlPlanePlugin.java | 9 +- .../v1/XdsApplicationServiceTest.java | 14 ++- 10 files changed, 147 insertions(+), 165 deletions(-) delete mode 100644 server/src/main/java/com/linecorp/centraldogma/server/metadata/MetadataServiceInjector.java 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 0c0e23a385..9349c438c4 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/CentralDogma.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/CentralDogma.java @@ -143,6 +143,9 @@ import com.linecorp.centraldogma.server.internal.api.TokenService; import com.linecorp.centraldogma.server.internal.api.WatchService; import com.linecorp.centraldogma.server.internal.api.auth.ApplicationTokenAuthorizer; +import com.linecorp.centraldogma.server.internal.api.auth.RequiresPermissionDecorator.RequiresReadPermissionDecoratorFactory; +import com.linecorp.centraldogma.server.internal.api.auth.RequiresPermissionDecorator.RequiresWritePermissionDecoratorFactory; +import com.linecorp.centraldogma.server.internal.api.auth.RequiresRoleDecorator.RequiresRoleDecoratorFactory; import com.linecorp.centraldogma.server.internal.api.converter.HttpApiRequestConverter; import com.linecorp.centraldogma.server.internal.mirror.DefaultMirroringServicePlugin; import com.linecorp.centraldogma.server.internal.replication.ZooKeeperCommandExecutor; @@ -156,7 +159,6 @@ import com.linecorp.centraldogma.server.management.ServerStatus; import com.linecorp.centraldogma.server.management.ServerStatusManager; import com.linecorp.centraldogma.server.metadata.MetadataService; -import com.linecorp.centraldogma.server.metadata.MetadataServiceInjector; import com.linecorp.centraldogma.server.mirror.MirrorProvider; import com.linecorp.centraldogma.server.plugin.AllReplicasPlugin; import com.linecorp.centraldogma.server.plugin.Plugin; @@ -618,8 +620,9 @@ private Server startServer(ProjectManager pm, CommandExecutor executor, HttpHeaders.of(HttpHeaderNames.AUTHORIZATION, "Bearer " + CsrfToken.ANONYMOUS)) .build()); - - configureHttpApi(sb, projectApiManager, executor, watchService, mds, authProvider, sessionManager, + final Function authService = + authService(mds, authProvider, sessionManager); + configureHttpApi(sb, projectApiManager, executor, watchService, mds, authProvider, authService, meterRegistry); configureMetrics(sb, meterRegistry); @@ -642,7 +645,7 @@ private Server startServer(ProjectManager pm, CommandExecutor executor, if (pluginsForAllReplicas != null) { final PluginInitContext pluginInitContext = new PluginInitContext(config(), pm, executor, meterRegistry, purgeWorker, sb, - projectInitializer); + authService, projectInitializer); pluginsForAllReplicas.plugins() .forEach(p -> { if (!(p instanceof AllReplicasPlugin)) { @@ -745,45 +748,38 @@ private void configureThriftService(ServerBuilder sb, ProjectApiManager projectA sb.service("/cd/thrift/v1", thriftService); } + private Function authService( + MetadataService mds, @Nullable AuthProvider authProvider, @Nullable SessionManager sessionManager) { + if (authProvider == null) { + return AuthService.newDecorator(new CsrfTokenAuthorizer()); + } + final AuthConfig authCfg = cfg.authConfig(); + assert authCfg != null : "authCfg"; + assert sessionManager != null : "sessionManager"; + final Authorizer tokenAuthorizer = + new ApplicationTokenAuthorizer(mds::findTokenBySecret) + .orElse(new SessionTokenAuthorizer(sessionManager, + authCfg.administrators())); + return AuthService.builder() + .add(tokenAuthorizer) + .onFailure(new CentralDogmaAuthFailureHandler()) + .newDecorator(); + } + private void configureHttpApi(ServerBuilder sb, ProjectApiManager projectApiManager, CommandExecutor executor, WatchService watchService, MetadataService mds, @Nullable AuthProvider authProvider, - @Nullable SessionManager sessionManager, MeterRegistry meterRegistry) { - Function decorator; - - if (authProvider != null) { - sb.service("/security_enabled", new AbstractHttpService() { - @Override - protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) { - return HttpResponse.of(HttpStatus.OK); - } - }); - - final AuthConfig authCfg = cfg.authConfig(); - assert authCfg != null : "authCfg"; - assert sessionManager != null : "sessionManager"; - final Authorizer tokenAuthorizer = - new ApplicationTokenAuthorizer(mds::findTokenBySecret) - .orElse(new SessionTokenAuthorizer(sessionManager, - authCfg.administrators())); - decorator = MetadataServiceInjector - .newDecorator(mds) - .andThen(AuthService.builder() - .add(tokenAuthorizer) - .onFailure(new CentralDogmaAuthFailureHandler()) - .newDecorator()); - } else { - decorator = MetadataServiceInjector - .newDecorator(mds) - .andThen(AuthService.newDecorator(new CsrfTokenAuthorizer())); - } - + Function authService, + MeterRegistry meterRegistry) { final DependencyInjector dependencyInjector = DependencyInjector.ofSingletons( // Use the default ObjectMapper without any configuration. // See JacksonRequestConverterFunctionTest new JacksonRequestConverterFunction(new ObjectMapper()), - new HttpApiRequestConverter(projectApiManager) + new HttpApiRequestConverter(projectApiManager), + new RequiresReadPermissionDecoratorFactory(mds), + new RequiresWritePermissionDecoratorFactory(mds), + new RequiresRoleDecoratorFactory(mds) ); sb.dependencyInjector(dependencyInjector, false) // TODO(ikhoon): Consider exposing ReflectiveDependencyInjector as a public API via @@ -791,7 +787,8 @@ protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) { .dependencyInjector(new ReflectiveDependencyInjector(), false); // Enable content compression for API responses. - decorator = decorator.andThen(contentEncodingDecorator()); + final Function decorator = + authService.andThen(contentEncodingDecorator()); for (String path : ImmutableList.of(API_V0_PATH_PREFIX, API_V1_PATH_PREFIX)) { final DecoratingServiceBindingBuilder decoratorBuilder = sb.routeDecorator().pathPrefix(path); @@ -805,7 +802,6 @@ protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) { } assert statusManager != null; - final ContextPathServicesBuilder apiV0ServiceBuilder = sb.contextPath(API_V0_PATH_PREFIX); final ContextPathServicesBuilder apiV1ServiceBuilder = sb.contextPath(API_V1_PATH_PREFIX); apiV1ServiceBuilder .annotatedService(new AdministrativeService(executor, statusManager)) @@ -833,12 +829,14 @@ public String serviceName(ServiceRequestContext ctx) { }) .build(new ContentServiceV1(executor, watchService, meterRegistry)); - sb.annotatedService() - .decorator(decorator) - .decorator(DecodingService.newDecorator()) - .build(new GitHttpService(projectApiManager)); - if (authProvider != null) { + sb.service("/security_enabled", new AbstractHttpService() { + @Override + protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) { + return HttpResponse.of(HttpStatus.OK); + } + }); + final AuthConfig authCfg = cfg.authConfig(); assert authCfg != null : "authCfg"; apiV1ServiceBuilder @@ -860,10 +858,15 @@ public String serviceName(ServiceRequestContext ctx) { authProvider.moreServices().forEach(sb::service); } + sb.annotatedService() + .decorator(decorator) + .decorator(DecodingService.newDecorator()) + .build(new GitHttpService(projectApiManager)); + if (cfg.isWebAppEnabled()) { - apiV0ServiceBuilder - .annotatedService(new UserService(executor)) - .annotatedService(new RepositoryService(projectApiManager, executor)); + sb.contextPath(API_V0_PATH_PREFIX) + .annotatedService(new UserService(executor)) + .annotatedService(new RepositoryService(projectApiManager, executor)); if (authProvider != null) { // Will redirect to /web/auth/login by default. diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/AuthUtil.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/AuthUtil.java index 6a1f3fcd5c..206a5fdb43 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/AuthUtil.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/AuthUtil.java @@ -41,14 +41,18 @@ public final class AuthUtil { public static Author currentAuthor(ServiceRequestContext ctx) { final User user = ctx.attr(CURRENT_USER); assert user != null; - return user == User.DEFAULT ? Author.DEFAULT - : new Author(user.name(), user.email()); + return getAuthor(user); } public static Author currentAuthor() { return currentAuthor(RequestContext.current()); } + public static Author getAuthor(User user) { + return user == User.DEFAULT ? Author.DEFAULT + : new Author(user.name(), user.email()); + } + public static User currentUser(ServiceRequestContext ctx) { return ctx.attr(CURRENT_USER); } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresPermissionDecorator.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresPermissionDecorator.java index c91b14efb2..b9fc5cf5a1 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresPermissionDecorator.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresPermissionDecorator.java @@ -42,7 +42,6 @@ import com.linecorp.centraldogma.server.internal.api.GitHttpService; import com.linecorp.centraldogma.server.internal.api.HttpApiUtil; import com.linecorp.centraldogma.server.metadata.MetadataService; -import com.linecorp.centraldogma.server.metadata.MetadataServiceInjector; import com.linecorp.centraldogma.server.metadata.Permission; import com.linecorp.centraldogma.server.metadata.User; import com.linecorp.centraldogma.server.storage.project.Project; @@ -52,16 +51,19 @@ */ public final class RequiresPermissionDecorator extends SimpleDecoratingHttpService { + private final MetadataService mds; + private final Permission requiredPermission; @Nullable private final String projectName; @Nullable private final String repoName; - RequiresPermissionDecorator(HttpService delegate, Permission requiredPermission, + RequiresPermissionDecorator(HttpService delegate, MetadataService mds, Permission requiredPermission, @Nullable String projectName, @Nullable String repoName) { super(delegate); + this.mds = mds; this.requiredPermission = requireNonNull(requiredPermission, "requiredPermission"); this.projectName = projectName; this.repoName = repoName; @@ -69,7 +71,6 @@ public final class RequiresPermissionDecorator extends SimpleDecoratingHttpServi @Override public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception { - final MetadataService mds = MetadataServiceInjector.getMetadataService(ctx); final User user = AuthUtil.currentUser(ctx); String projectName = this.projectName; @@ -89,7 +90,7 @@ public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exc } return unwrap().serve(ctx, req); } - return serveUserRepo(ctx, req, mds, user, projectName, maybeRemoveGitSuffix(repoName)); + return serveUserRepo(ctx, req, user, projectName, maybeRemoveGitSuffix(repoName)); } private static HttpResponse throwForbiddenResponse(ServiceRequestContext ctx, String projectName, @@ -111,8 +112,7 @@ private static String maybeRemoveGitSuffix(String repoName) { } private HttpResponse serveUserRepo(ServiceRequestContext ctx, HttpRequest req, - MetadataService mds, User user, - String projectName, String repoName) throws Exception { + User user, String projectName, String repoName) throws Exception { final CompletionStage> f; try { f = mds.findPermissions(projectName, repoName, user); @@ -144,10 +144,17 @@ private HttpResponse serveUserRepo(ServiceRequestContext ctx, HttpRequest req, */ public static final class RequiresReadPermissionDecoratorFactory implements DecoratorFactoryFunction { + + private final MetadataService mds; + + public RequiresReadPermissionDecoratorFactory(MetadataService mds) { + this.mds = requireNonNull(mds, "mds"); + } + @Override public Function newDecorator(RequiresReadPermission parameter) { - return delegate -> new RequiresPermissionDecorator(delegate, Permission.READ, + return delegate -> new RequiresPermissionDecorator(delegate, mds, Permission.READ, Strings.emptyToNull(parameter.project()), Strings.emptyToNull(parameter.repository())); } @@ -159,10 +166,17 @@ public static final class RequiresReadPermissionDecoratorFactory */ public static final class RequiresWritePermissionDecoratorFactory implements DecoratorFactoryFunction { + + private final MetadataService mds; + + public RequiresWritePermissionDecoratorFactory(MetadataService mds) { + this.mds = requireNonNull(mds, "mds"); + } + @Override public Function newDecorator(RequiresWritePermission parameter) { - return delegate -> new RequiresPermissionDecorator(delegate, Permission.WRITE, + return delegate -> new RequiresPermissionDecorator(delegate, mds, Permission.WRITE, Strings.emptyToNull(parameter.project()), Strings.emptyToNull(parameter.repository())); } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresRoleDecorator.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresRoleDecorator.java index 4754ffbe63..13f114f5fc 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresRoleDecorator.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresRoleDecorator.java @@ -39,7 +39,6 @@ import com.linecorp.centraldogma.server.internal.admin.auth.AuthUtil; import com.linecorp.centraldogma.server.internal.api.HttpApiUtil; import com.linecorp.centraldogma.server.metadata.MetadataService; -import com.linecorp.centraldogma.server.metadata.MetadataServiceInjector; import com.linecorp.centraldogma.server.metadata.ProjectRole; import com.linecorp.centraldogma.server.metadata.User; @@ -48,11 +47,13 @@ */ public final class RequiresRoleDecorator extends SimpleDecoratingHttpService { + private final MetadataService mds; private final Set accessibleRoles; private final String roleNames; - RequiresRoleDecorator(HttpService delegate, Set accessibleRoles) { + RequiresRoleDecorator(HttpService delegate, MetadataService mds, Set accessibleRoles) { super(delegate); + this.mds = requireNonNull(mds, "mds"); this.accessibleRoles = ImmutableSet.copyOf(requireNonNull(accessibleRoles, "accessibleRoles")); roleNames = String.join(",", accessibleRoles.stream().map(ProjectRole::name).collect(toImmutableList())); @@ -60,7 +61,6 @@ public final class RequiresRoleDecorator extends SimpleDecoratingHttpService { @Override public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception { - final MetadataService mds = MetadataServiceInjector.getMetadataService(ctx); final User user = AuthUtil.currentUser(ctx); final String projectName = ctx.pathParam("projectName"); @@ -101,9 +101,15 @@ static HttpResponse handleException(ServiceRequestContext ctx, Throwable cause) public static final class RequiresRoleDecoratorFactory implements DecoratorFactoryFunction { + private final MetadataService mds; + + public RequiresRoleDecoratorFactory(MetadataService mds) { + this.mds = mds; + } + @Override public Function newDecorator(RequiresRole parameter) { - return delegate -> new RequiresRoleDecorator(delegate, ImmutableSet.copyOf(parameter.roles())); + return delegate -> new RequiresRoleDecorator(delegate, mds, ImmutableSet.copyOf(parameter.roles())); } } } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/metadata/MetadataServiceInjector.java b/server/src/main/java/com/linecorp/centraldogma/server/metadata/MetadataServiceInjector.java deleted file mode 100644 index bedbd99a32..0000000000 --- a/server/src/main/java/com/linecorp/centraldogma/server/metadata/MetadataServiceInjector.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2019 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.metadata; - -import static java.util.Objects.requireNonNull; - -import java.util.function.Function; - -import com.linecorp.armeria.common.HttpRequest; -import com.linecorp.armeria.common.HttpResponse; -import com.linecorp.armeria.server.HttpService; -import com.linecorp.armeria.server.Service; -import com.linecorp.armeria.server.ServiceRequestContext; -import com.linecorp.armeria.server.SimpleDecoratingHttpService; - -import io.netty.util.AttributeKey; - -/** - * Injects the {@link MetadataService} instance into the attribute of the {@link ServiceRequestContext}. - */ -public final class MetadataServiceInjector extends SimpleDecoratingHttpService { - - /** - * Returns a newly created {@link Service} decorator from the specified {@link MetadataService}. - */ - public static Function newDecorator(MetadataService mds) { - requireNonNull(mds, "mds"); - return service -> new MetadataServiceInjector(service, mds); - } - - private static final AttributeKey METADATA_SERVICE_ATTRIBUTE_KEY = - AttributeKey.valueOf(MetadataServiceInjector.class, "METADATA"); - - private final MetadataService mds; - - private MetadataServiceInjector(HttpService delegate, MetadataService mds) { - super(delegate); - this.mds = requireNonNull(mds, "mds"); - } - - @Override - public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception { - ctx.setAttr(METADATA_SERVICE_ATTRIBUTE_KEY, mds); - return unwrap().serve(ctx, req); - } - - /** - * Returns the {@link MetadataService} instance from the specified {@link ServiceRequestContext}. - */ - public static MetadataService getMetadataService(ServiceRequestContext ctx) { - final MetadataService mds = ctx.attr(METADATA_SERVICE_ATTRIBUTE_KEY); - if (mds != null) { - return mds; - } - throw new IllegalStateException("No metadata service instance exists."); - } -} 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 faf230ce65..ad8ea34bd9 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 @@ -19,8 +19,11 @@ import static java.util.Objects.requireNonNull; import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Function; +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.command.CommandExecutor; import com.linecorp.centraldogma.server.storage.project.InternalProjectInitializer; @@ -34,6 +37,7 @@ public final class PluginInitContext extends PluginContext { private final ServerBuilder serverBuilder; + private final Function authService; /** * Creates a new instance. @@ -43,9 +47,11 @@ public PluginInitContext(CentralDogmaConfig config, CommandExecutor commandExecutor, MeterRegistry meterRegistry, ScheduledExecutorService purgeWorker, ServerBuilder serverBuilder, + Function authService, InternalProjectInitializer projectInitializer) { super(config, projectManager, commandExecutor, meterRegistry, purgeWorker, projectInitializer); this.serverBuilder = requireNonNull(serverBuilder, "serverBuilder"); + this.authService = requireNonNull(authService, "authService"); } /** @@ -54,4 +60,11 @@ public PluginInitContext(CentralDogmaConfig config, public ServerBuilder serverBuilder() { return serverBuilder; } + + /** + * Returns the {@link AuthService} of the Central Dogma server. + */ + public Function authService() { + return authService; + } } diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/api/auth/PermissionTest.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/api/auth/PermissionTest.java index a8b2c4fb3e..cb99fcbb88 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/api/auth/PermissionTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/api/auth/PermissionTest.java @@ -24,7 +24,6 @@ import java.io.File; import java.util.Set; import java.util.concurrent.ForkJoinPool; -import java.util.function.Function; import java.util.stream.Stream; import org.junit.jupiter.api.Order; @@ -39,13 +38,13 @@ import com.linecorp.armeria.client.BlockingWebClient; import com.linecorp.armeria.client.WebClient; import com.linecorp.armeria.common.AggregatedHttpResponse; +import com.linecorp.armeria.common.DependencyInjector; import com.linecorp.armeria.common.HttpData; import com.linecorp.armeria.common.HttpHeaderNames; import com.linecorp.armeria.common.HttpResponse; import com.linecorp.armeria.common.HttpStatus; import com.linecorp.armeria.common.annotation.Nullable; import com.linecorp.armeria.common.metric.NoopMeterRegistry; -import com.linecorp.armeria.server.HttpService; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.server.annotation.Get; @@ -59,10 +58,12 @@ import com.linecorp.centraldogma.server.command.CommandExecutor; import com.linecorp.centraldogma.server.command.StandaloneCommandExecutor; import com.linecorp.centraldogma.server.internal.api.HttpApiExceptionHandler; +import com.linecorp.centraldogma.server.internal.api.auth.RequiresPermissionDecorator.RequiresReadPermissionDecoratorFactory; +import com.linecorp.centraldogma.server.internal.api.auth.RequiresPermissionDecorator.RequiresWritePermissionDecoratorFactory; +import com.linecorp.centraldogma.server.internal.api.auth.RequiresRoleDecorator.RequiresRoleDecoratorFactory; import com.linecorp.centraldogma.server.internal.storage.project.DefaultProjectManager; import com.linecorp.centraldogma.server.management.ServerStatusManager; import com.linecorp.centraldogma.server.metadata.MetadataService; -import com.linecorp.centraldogma.server.metadata.MetadataServiceInjector; import com.linecorp.centraldogma.server.metadata.PerRolePermissions; import com.linecorp.centraldogma.server.metadata.Permission; import com.linecorp.centraldogma.server.metadata.ProjectRole; @@ -124,10 +125,11 @@ protected void configure(ServerBuilder sb) throws Exception { // app-2 is a member and it has read-only permission. mds.addToken(AUTHOR, "project1", APP_ID_2, ProjectRole.MEMBER) .toCompletableFuture().join(); - - final Function decorator = - MetadataServiceInjector.newDecorator(mds).andThen(AuthService.newDecorator( - new ApplicationTokenAuthorizer(mds::findTokenBySecret))); + sb.dependencyInjector( + DependencyInjector.ofSingletons(new RequiresReadPermissionDecoratorFactory(mds), + new RequiresWritePermissionDecoratorFactory(mds), + new RequiresRoleDecoratorFactory(mds)), + false); sb.annotatedService(new Object() { @Get("/projects/{projectName}") @RequiresRole(roles = { ProjectRole.OWNER, ProjectRole.MEMBER }) @@ -148,7 +150,7 @@ public HttpResponse read(@Param String projectName, @Param String repoName) { return HttpResponse.of(HttpStatus.OK); } - }, decorator); + }, AuthService.newDecorator(new ApplicationTokenAuthorizer(mds::findTokenBySecret))); sb.errorHandler(new HttpApiExceptionHandler()); } diff --git a/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java b/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java index 618a17e134..ca1043d0a7 100644 --- a/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java +++ b/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java @@ -15,13 +15,13 @@ */ package com.linecorp.centraldogma.xds.application.v1; +import static com.linecorp.centraldogma.server.internal.admin.auth.AuthUtil.currentAuthor; import static com.linecorp.centraldogma.server.internal.api.RepositoryServiceUtil.createRepository; import static com.linecorp.centraldogma.server.internal.api.RepositoryServiceUtil.removeRepository; import static com.linecorp.centraldogma.xds.internal.ControlPlanePlugin.XDS_CENTRAL_DOGMA_PROJECT; import com.google.protobuf.Empty; -import com.linecorp.centraldogma.common.Author; import com.linecorp.centraldogma.common.RepositoryExistsException; import com.linecorp.centraldogma.server.command.CommandExecutor; import com.linecorp.centraldogma.server.metadata.MetadataService; @@ -60,14 +60,14 @@ public void createApplication(CreateApplicationRequest request, throwAlreadyExists(name); } // Use the real author after https://github.com/line/centraldogma/pull/969 is merged - createRepository(commandExecutor, mds, Author.SYSTEM, XDS_CENTRAL_DOGMA_PROJECT, name) + createRepository(commandExecutor, mds, currentAuthor(), XDS_CENTRAL_DOGMA_PROJECT, name) .handle((unused, cause) -> { if (cause != null) { if (cause instanceof RepositoryExistsException) { throwAlreadyExists(name); } responseObserver.onError( - new StatusRuntimeException(Status.INTERNAL.withCause(cause))); + Status.INTERNAL.withCause(cause).asRuntimeException()); return null; } responseObserver.onNext(Application.newBuilder().setName(applicationName).build()); @@ -85,8 +85,8 @@ private static String removePrefix(String prefix, String name) { } private static void throwAlreadyExists(String applicationName) { - throw new StatusRuntimeException( - Status.ALREADY_EXISTS.withDescription("Application already exists: " + applicationName)); + throw Status.ALREADY_EXISTS.withDescription("Application already exists: " + applicationName) + .asRuntimeException(); } @Override @@ -94,25 +94,25 @@ public void deleteApplication(DeleteApplicationRequest request, StreamObserver { - if (cause != null) { - responseObserver.onError(new StatusRuntimeException( - Status.INTERNAL.withDescription("Failed to delete " + applicationName) - .withCause(cause))); - } - responseObserver.onNext(Empty.getDefaultInstance()); - responseObserver.onCompleted(); - return null; - }); + // TODO(minwoox): Check the permission. + removeRepository(commandExecutor, mds, currentAuthor(), XDS_CENTRAL_DOGMA_PROJECT, name) + .handle((unused, cause1) -> { + if (cause1 != null) { + responseObserver.onError( + Status.INTERNAL.withDescription("Failed to delete " + applicationName) + .withCause(cause1).asRuntimeException()); + } + responseObserver.onNext(Empty.getDefaultInstance()); + responseObserver.onCompleted(); + return null; + }); } } 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 318bba352b..dd349fd12a 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 @@ -146,12 +146,15 @@ private void init0(PluginInitContext pluginInitContext) { .addService(server.getListenerDiscoveryServiceImpl()) .addService(server.getRouteDiscoveryServiceImpl()) .addService(server.getAggregatedDiscoveryServiceImpl()) - .addService(new XdsApplicationService( - projectManager, pluginInitContext.commandExecutor())) - .enableHttpJsonTranscoding(true) .useBlockingTaskExecutor(true) .build(); sb.route().build(grpcService); + final GrpcService xdsApplicationService = + GrpcService.builder() + .addService(new XdsApplicationService( + projectManager, pluginInitContext.commandExecutor())) + .enableHttpJsonTranscoding(true).build(); + sb.service(xdsApplicationService, pluginInitContext.authService()); } private void setXdsResources(String path, String contentAsText, String repoName) diff --git a/xds/src/test/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationServiceTest.java b/xds/src/test/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationServiceTest.java index 9053ff3c03..88b0390d7b 100644 --- a/xds/src/test/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationServiceTest.java +++ b/xds/src/test/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationServiceTest.java @@ -25,6 +25,7 @@ import com.linecorp.armeria.client.grpc.GrpcClients; import com.linecorp.armeria.common.AggregatedHttpResponse; +import com.linecorp.armeria.common.HttpHeaderNames; import com.linecorp.armeria.common.HttpMethod; import com.linecorp.armeria.common.HttpStatus; import com.linecorp.armeria.common.MediaType; @@ -59,6 +60,7 @@ void createApplicationViaHttp() { private static AggregatedHttpResponse createApplication(String applicationName) { final RequestHeaders headers = RequestHeaders.builder(HttpMethod.POST, "/api/v1/xds/applications") + .set(HttpHeaderNames.AUTHORIZATION, "Bearer anonymous") .contentType(MediaType.JSON_UTF_8).build(); return dogma.httpClient().execute(headers, "{\"application\": {\"name\":\"" + applicationName + "\"}}") .aggregate().join(); @@ -81,13 +83,19 @@ void deleteApplicationViaHttp() { } private static AggregatedHttpResponse deleteApplication(String applicationName) { - return dogma.httpClient().delete("/api/v1/xds/" + applicationName).aggregate().join(); + final RequestHeaders headers = + RequestHeaders.builder(HttpMethod.DELETE, "/api/v1/xds/" + applicationName) + .set(HttpHeaderNames.AUTHORIZATION, "Bearer anonymous") + .build(); + return dogma.httpClient().execute(headers).aggregate().join(); } @Test void createAndDeleteApplicationViaStub() { - final XdsApplicationServiceBlockingStub client = GrpcClients.newClient( - dogma.httpClient().uri(), XdsApplicationServiceBlockingStub.class); + final XdsApplicationServiceBlockingStub client = + GrpcClients.builder(dogma.httpClient().uri()) + .setHeader(HttpHeaderNames.AUTHORIZATION, "Bearer anonymous") + .build(XdsApplicationServiceBlockingStub.class); final Application application = client.createApplication( CreateApplicationRequest.newBuilder() .setApplication(Application.newBuilder() From 4a5b661973078fca1630c7f233b0b41796ed57e1 Mon Sep 17 00:00:00 2001 From: minwoox Date: Tue, 16 Jul 2024 15:46:46 +0900 Subject: [PATCH 3/6] Update --- .../server/internal/api/RepositoryServiceV1.java | 10 +++------- .../internal/api/auth/RequiresPermissionDecorator.java | 2 +- .../xds/application/v1/XdsApplicationService.java | 1 - 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceV1.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceV1.java index 679dce4f06..2c2052d4da 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceV1.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceV1.java @@ -145,13 +145,9 @@ public CompletableFuture removeRepository(ServiceRequestContext ctx, return HttpApiUtil.throwResponse(ctx, HttpStatus.FORBIDDEN, "A reserved repository cannot be removed."); } - final String projectName = repository.parent().name(); - final MetadataService mds = this.mds; - final CommandExecutor commandExecutor = executor(); - final CompletableFuture future = - RepositoryServiceUtil.removeRepository(commandExecutor, mds, author, projectName, repoName); - return future - .handle(HttpApiUtil::throwUnsafelyIfNonNull); + return RepositoryServiceUtil.removeRepository(executor(), mds, author, + repository.parent().name(), repoName) + .handle(HttpApiUtil::throwUnsafelyIfNonNull); } /** diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresPermissionDecorator.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresPermissionDecorator.java index b9fc5cf5a1..3c663ac88c 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresPermissionDecorator.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresPermissionDecorator.java @@ -63,7 +63,7 @@ public final class RequiresPermissionDecorator extends SimpleDecoratingHttpServi @Nullable String projectName, @Nullable String repoName) { super(delegate); - this.mds = mds; + this.mds = requireNonNull(mds, "mds"); this.requiredPermission = requireNonNull(requiredPermission, "requiredPermission"); this.projectName = projectName; this.repoName = repoName; diff --git a/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java b/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java index ca1043d0a7..2407e3bd51 100644 --- a/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java +++ b/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java @@ -59,7 +59,6 @@ public void createApplication(CreateApplicationRequest request, if (projectManager.get(XDS_CENTRAL_DOGMA_PROJECT).repos().exists(name)) { throwAlreadyExists(name); } - // Use the real author after https://github.com/line/centraldogma/pull/969 is merged createRepository(commandExecutor, mds, currentAuthor(), XDS_CENTRAL_DOGMA_PROJECT, name) .handle((unused, cause) -> { if (cause != null) { From 71c970684bf9127e50c41ca7e9d39f163f9638fe Mon Sep 17 00:00:00 2001 From: minwoox Date: Tue, 16 Jul 2024 16:00:25 +0900 Subject: [PATCH 4/6] rename package --- .../xds/{k8s => application}/v1/xds_application.proto | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename xds/src/main/proto/centraldogma/xds/{k8s => application}/v1/xds_application.proto (100%) diff --git a/xds/src/main/proto/centraldogma/xds/k8s/v1/xds_application.proto b/xds/src/main/proto/centraldogma/xds/application/v1/xds_application.proto similarity index 100% rename from xds/src/main/proto/centraldogma/xds/k8s/v1/xds_application.proto rename to xds/src/main/proto/centraldogma/xds/application/v1/xds_application.proto From b4f2dbb078dc5f825dc779e2e431f6f6ae615f01 Mon Sep 17 00:00:00 2001 From: minwoox Date: Tue, 16 Jul 2024 21:17:25 +0900 Subject: [PATCH 5/6] Address comments from @ikhoon --- .../application/v1/XdsApplicationService.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java b/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java index 2407e3bd51..6bb9503d74 100644 --- a/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java +++ b/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java @@ -57,16 +57,17 @@ public void createApplication(CreateApplicationRequest request, final String applicationName = request.getApplication().getName(); final String name = removePrefix("applications/", applicationName); if (projectManager.get(XDS_CENTRAL_DOGMA_PROJECT).repos().exists(name)) { - throwAlreadyExists(name); + throw alreadyExistsException(name); } createRepository(commandExecutor, mds, currentAuthor(), XDS_CENTRAL_DOGMA_PROJECT, name) .handle((unused, cause) -> { if (cause != null) { if (cause instanceof RepositoryExistsException) { - throwAlreadyExists(name); + responseObserver.onError(alreadyExistsException(name)); + } else { + responseObserver.onError( + Status.INTERNAL.withCause(cause).asRuntimeException()); } - responseObserver.onError( - Status.INTERNAL.withCause(cause).asRuntimeException()); return null; } responseObserver.onNext(Application.newBuilder().setName(applicationName).build()); @@ -83,9 +84,9 @@ private static String removePrefix(String prefix, String name) { return name.substring(prefix.length()); } - private static void throwAlreadyExists(String applicationName) { - throw Status.ALREADY_EXISTS.withDescription("Application already exists: " + applicationName) - .asRuntimeException(); + private static RuntimeException alreadyExistsException(String applicationName) { + return Status.ALREADY_EXISTS.withDescription("Application already exists: " + applicationName) + .asRuntimeException(); } @Override @@ -108,6 +109,7 @@ public void deleteApplication(DeleteApplicationRequest request, StreamObserver Date: Wed, 17 Jul 2024 12:22:06 +0900 Subject: [PATCH 6/6] rename to group --- .../v1/XdsGroupService.java} | 36 +++++------ .../v1/package-info.java | 4 +- .../xds/internal/ControlPlanePlugin.java | 4 +- .../v1/xds_group.proto} | 40 ++++++------- .../v1/XdsGroupServiceTest.java} | 60 +++++++++---------- 5 files changed, 72 insertions(+), 72 deletions(-) rename xds/src/main/java/com/linecorp/centraldogma/xds/{application/v1/XdsApplicationService.java => group/v1/XdsGroupService.java} (75%) rename xds/src/main/java/com/linecorp/centraldogma/xds/{application => group}/v1/package-info.java (89%) rename xds/src/main/proto/centraldogma/xds/{application/v1/xds_application.proto => group/v1/xds_group.proto} (51%) rename xds/src/test/java/com/linecorp/centraldogma/xds/{application/v1/XdsApplicationServiceTest.java => group/v1/XdsGroupServiceTest.java} (64%) diff --git a/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java b/xds/src/main/java/com/linecorp/centraldogma/xds/group/v1/XdsGroupService.java similarity index 75% rename from xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java rename to xds/src/main/java/com/linecorp/centraldogma/xds/group/v1/XdsGroupService.java index 6bb9503d74..ed9d4dd200 100644 --- a/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationService.java +++ b/xds/src/main/java/com/linecorp/centraldogma/xds/group/v1/XdsGroupService.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package com.linecorp.centraldogma.xds.application.v1; +package com.linecorp.centraldogma.xds.group.v1; import static com.linecorp.centraldogma.server.internal.admin.auth.AuthUtil.currentAuthor; import static com.linecorp.centraldogma.server.internal.api.RepositoryServiceUtil.createRepository; @@ -27,16 +27,16 @@ import com.linecorp.centraldogma.server.metadata.MetadataService; import com.linecorp.centraldogma.server.storage.project.Project; import com.linecorp.centraldogma.server.storage.project.ProjectManager; -import com.linecorp.centraldogma.xds.application.v1.XdsApplicationServiceGrpc.XdsApplicationServiceImplBase; +import com.linecorp.centraldogma.xds.group.v1.XdsGroupServiceGrpc.XdsGroupServiceImplBase; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; /** - * An {@link XdsApplicationServiceImplBase} implementation that provides methods to manage XDS applications. + * An {@link XdsGroupServiceImplBase} implementation that provides methods to manage XDS groups. */ -public final class XdsApplicationService extends XdsApplicationServiceImplBase { +public final class XdsGroupService extends XdsGroupServiceImplBase { private final ProjectManager projectManager; private final CommandExecutor commandExecutor; @@ -45,17 +45,17 @@ public final class XdsApplicationService extends XdsApplicationServiceImplBase { /** * Creates a new instance. */ - public XdsApplicationService(ProjectManager projectManager, CommandExecutor commandExecutor) { + public XdsGroupService(ProjectManager projectManager, CommandExecutor commandExecutor) { this.projectManager = projectManager; this.commandExecutor = commandExecutor; mds = new MetadataService(projectManager, commandExecutor); } @Override - public void createApplication(CreateApplicationRequest request, - StreamObserver responseObserver) { - final String applicationName = request.getApplication().getName(); - final String name = removePrefix("applications/", applicationName); + public void createGroup(CreateGroupRequest request, + StreamObserver responseObserver) { + final String groupName = request.getGroup().getName(); + final String name = removePrefix("groups/", groupName); if (projectManager.get(XDS_CENTRAL_DOGMA_PROJECT).repos().exists(name)) { throw alreadyExistsException(name); } @@ -70,7 +70,7 @@ public void createApplication(CreateApplicationRequest request, } return null; } - responseObserver.onNext(Application.newBuilder().setName(applicationName).build()); + responseObserver.onNext(Group.newBuilder().setName(groupName).build()); responseObserver.onCompleted(); return null; }); @@ -84,21 +84,21 @@ private static String removePrefix(String prefix, String name) { return name.substring(prefix.length()); } - private static RuntimeException alreadyExistsException(String applicationName) { - return Status.ALREADY_EXISTS.withDescription("Application already exists: " + applicationName) + private static RuntimeException alreadyExistsException(String groupName) { + return Status.ALREADY_EXISTS.withDescription("Group already exists: " + groupName) .asRuntimeException(); } @Override - public void deleteApplication(DeleteApplicationRequest request, StreamObserver responseObserver) { - final String applicationName = request.getName(); - final String name = removePrefix("applications/", applicationName); + public void deleteGroup(DeleteGroupRequest request, StreamObserver responseObserver) { + final String groupName = request.getName(); + final String name = removePrefix("groups/", groupName); if (!projectManager.get(XDS_CENTRAL_DOGMA_PROJECT).repos().exists(name)) { - throw Status.NOT_FOUND.withDescription("Application does not exist: " + applicationName) + throw Status.NOT_FOUND.withDescription("Group does not exist: " + groupName) .asRuntimeException(); } if (Project.isReservedRepoName(name)) { - throw Status.PERMISSION_DENIED.withDescription("Now allowed to delete " + applicationName) + throw Status.PERMISSION_DENIED.withDescription("Now allowed to delete " + groupName) .asRuntimeException(); } @@ -107,7 +107,7 @@ public void deleteApplication(DeleteApplicationRequest request, StreamObserver { if (cause1 != null) { responseObserver.onError( - Status.INTERNAL.withDescription("Failed to delete " + applicationName) + Status.INTERNAL.withDescription("Failed to delete " + groupName) .withCause(cause1).asRuntimeException()); return null; } diff --git a/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/package-info.java b/xds/src/main/java/com/linecorp/centraldogma/xds/group/v1/package-info.java similarity index 89% rename from xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/package-info.java rename to xds/src/main/java/com/linecorp/centraldogma/xds/group/v1/package-info.java index b0e3ac932b..cb2e809f4a 100644 --- a/xds/src/main/java/com/linecorp/centraldogma/xds/application/v1/package-info.java +++ b/xds/src/main/java/com/linecorp/centraldogma/xds/group/v1/package-info.java @@ -14,9 +14,9 @@ * under the License. */ /** - * xDS application service. + * xDS group service. */ @NonNullByDefault -package com.linecorp.centraldogma.xds.application.v1; +package com.linecorp.centraldogma.xds.group.v1; import com.linecorp.centraldogma.common.util.NonNullByDefault; 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 dd349fd12a..a74721f48b 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 @@ -50,7 +50,7 @@ import com.linecorp.centraldogma.server.storage.repository.DiffResultType; import com.linecorp.centraldogma.server.storage.repository.Repository; import com.linecorp.centraldogma.server.storage.repository.RepositoryManager; -import com.linecorp.centraldogma.xds.application.v1.XdsApplicationService; +import com.linecorp.centraldogma.xds.group.v1.XdsGroupService; import io.envoyproxy.controlplane.cache.v3.SimpleCache; import io.envoyproxy.controlplane.server.DiscoveryServerCallbacks; @@ -151,7 +151,7 @@ private void init0(PluginInitContext pluginInitContext) { sb.route().build(grpcService); final GrpcService xdsApplicationService = GrpcService.builder() - .addService(new XdsApplicationService( + .addService(new XdsGroupService( projectManager, pluginInitContext.commandExecutor())) .enableHttpJsonTranscoding(true).build(); sb.service(xdsApplicationService, pluginInitContext.authService()); diff --git a/xds/src/main/proto/centraldogma/xds/application/v1/xds_application.proto b/xds/src/main/proto/centraldogma/xds/group/v1/xds_group.proto similarity index 51% rename from xds/src/main/proto/centraldogma/xds/application/v1/xds_application.proto rename to xds/src/main/proto/centraldogma/xds/group/v1/xds_group.proto index 90278ac6e1..8e8df0ac58 100644 --- a/xds/src/main/proto/centraldogma/xds/application/v1/xds_application.proto +++ b/xds/src/main/proto/centraldogma/xds/group/v1/xds_group.proto @@ -13,50 +13,50 @@ // under the License. syntax = "proto3"; -package centraldogma.xds.application.v1; +package centraldogma.xds.group.v1; option java_multiple_files = true; -option java_outer_classname = "XdsApplicationProto"; -option java_package = "com.linecorp.centraldogma.xds.application.v1"; +option java_outer_classname = "XdsGroupProto"; +option java_package = "com.linecorp.centraldogma.xds.group.v1"; import "google/api/annotations.proto"; import "google/api/client.proto"; import "google/api/field_behavior.proto"; import "google/protobuf/empty.proto"; -// An XdsApplicationService provides methods to manage applications. -service XdsApplicationService { +// An XdsGroupService provides methods to manage groups. +service XdsGroupService { - // Creates a new application. - rpc CreateApplication(CreateApplicationRequest) returns (Application) { + // Creates a new group. + rpc CreateGroup(CreateGroupRequest) returns (Group) { option (google.api.http) = { - post: "/api/v1/xds/applications" - body: "application" + post: "/api/v1/xds/groups" + body: "group" }; } - // Deletes an application. - rpc DeleteApplication(DeleteApplicationRequest) returns (google.protobuf.Empty) { + // Deletes an group. + rpc DeleteGroup(DeleteGroupRequest) returns (google.protobuf.Empty) { option (google.api.http) = { - delete: "/api/v1/xds/{name=applications/*}" + delete: "/api/v1/xds/{name=groups/*}" }; } } -message CreateApplicationRequest { - Application application = 1 [(google.api.field_behavior) = REQUIRED]; +message CreateGroupRequest { + Group group = 1 [(google.api.field_behavior) = REQUIRED]; } -message DeleteApplicationRequest { - // Format: applications/{application} +message DeleteGroupRequest { + // Format: groups/{group} string name = 1 [(google.api.field_behavior) = REQUIRED]; - // If set to true, any xds Resources from this application will also be deleted. - // (Otherwise, the request will only work if the application has no xDS resources.) + // If set to true, any xds Resources from this group will also be deleted. + // (Otherwise, the request will only work if the group has no xDS resources.) // bool force = 2; } -message Application { - // Format: applications/{application} +message Group { + // Format: groups/{group} string name = 1 [(google.api.field_behavior) = IDENTIFIER]; } diff --git a/xds/src/test/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationServiceTest.java b/xds/src/test/java/com/linecorp/centraldogma/xds/group/v1/XdsGroupServiceTest.java similarity index 64% rename from xds/src/test/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationServiceTest.java rename to xds/src/test/java/com/linecorp/centraldogma/xds/group/v1/XdsGroupServiceTest.java index 88b0390d7b..d2fab15124 100644 --- a/xds/src/test/java/com/linecorp/centraldogma/xds/application/v1/XdsApplicationServiceTest.java +++ b/xds/src/test/java/com/linecorp/centraldogma/xds/group/v1/XdsGroupServiceTest.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package com.linecorp.centraldogma.xds.application.v1; +package com.linecorp.centraldogma.xds.group.v1; import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; @@ -31,80 +31,80 @@ import com.linecorp.armeria.common.MediaType; import com.linecorp.armeria.common.RequestHeaders; import com.linecorp.centraldogma.testing.junit.CentralDogmaExtension; -import com.linecorp.centraldogma.xds.application.v1.XdsApplicationServiceGrpc.XdsApplicationServiceBlockingStub; +import com.linecorp.centraldogma.xds.group.v1.XdsGroupServiceGrpc.XdsGroupServiceBlockingStub; import io.grpc.Status; -class XdsApplicationServiceTest { +class XdsGroupServiceTest { @RegisterExtension static final CentralDogmaExtension dogma = new CentralDogmaExtension(); @Test - void createApplicationViaHttp() { + void createGroupViaHttp() { // Invalid name. - AggregatedHttpResponse response = createApplication("invalid/foo"); + AggregatedHttpResponse response = createGroup("invalid/foo"); assertThat(response.status()).isSameAs(HttpStatus.BAD_REQUEST); - response = createApplication("applications/foo"); + response = createGroup("groups/foo"); assertThat(response.status()).isSameAs(HttpStatus.OK); assertThat(response.headers().get("grpc-status")).isEqualTo("0"); - assertThatJson(response.contentUtf8()).isEqualTo("{\"name\":\"applications/foo\"}"); + assertThatJson(response.contentUtf8()).isEqualTo("{\"name\":\"groups/foo\"}"); // Cannot create with the same name. - response = createApplication("applications/foo"); + response = createGroup("groups/foo"); assertThat(response.status()).isSameAs(HttpStatus.CONFLICT); assertThat(response.headers().get("grpc-status")) .isEqualTo(Integer.toString(Status.ALREADY_EXISTS.getCode().value())); } - private static AggregatedHttpResponse createApplication(String applicationName) { - final RequestHeaders headers = RequestHeaders.builder(HttpMethod.POST, "/api/v1/xds/applications") + private static AggregatedHttpResponse createGroup(String groupName) { + final RequestHeaders headers = RequestHeaders.builder(HttpMethod.POST, "/api/v1/xds/groups") .set(HttpHeaderNames.AUTHORIZATION, "Bearer anonymous") .contentType(MediaType.JSON_UTF_8).build(); - return dogma.httpClient().execute(headers, "{\"application\": {\"name\":\"" + applicationName + "\"}}") - .aggregate().join(); + return dogma.httpClient().execute(headers, "{\"group\": {\"name\":\"" + groupName + "\"}}") + .aggregate().join(); } @Test - void deleteApplicationViaHttp() { - AggregatedHttpResponse response = deleteApplication("applications/bar"); + void deleteGroupViaHttp() { + AggregatedHttpResponse response = deleteGroup("groups/bar"); assertThat(response.status()).isSameAs(HttpStatus.NOT_FOUND); - response = createApplication("applications/bar"); + response = createGroup("groups/bar"); assertThat(response.status()).isSameAs(HttpStatus.OK); // Add permission test. - response = deleteApplication("applications/bar"); + response = deleteGroup("groups/bar"); assertThat(response.status()).isSameAs(HttpStatus.OK); assertThat(response.headers().get("grpc-status")).isEqualTo("0"); assertThat(response.contentUtf8()).isEqualTo("{}"); } - private static AggregatedHttpResponse deleteApplication(String applicationName) { + private static AggregatedHttpResponse deleteGroup(String groupName) { final RequestHeaders headers = - RequestHeaders.builder(HttpMethod.DELETE, "/api/v1/xds/" + applicationName) + RequestHeaders.builder(HttpMethod.DELETE, "/api/v1/xds/" + groupName) .set(HttpHeaderNames.AUTHORIZATION, "Bearer anonymous") .build(); return dogma.httpClient().execute(headers).aggregate().join(); } @Test - void createAndDeleteApplicationViaStub() { - final XdsApplicationServiceBlockingStub client = + void createAndDeleteGroupViaStub() { + final XdsGroupServiceBlockingStub client = GrpcClients.builder(dogma.httpClient().uri()) .setHeader(HttpHeaderNames.AUTHORIZATION, "Bearer anonymous") - .build(XdsApplicationServiceBlockingStub.class); - final Application application = client.createApplication( - CreateApplicationRequest.newBuilder() - .setApplication(Application.newBuilder() - .setName("applications/baz")) - .build()); - assertThat(application.getName()).isEqualTo("applications/baz"); + .build(XdsGroupServiceBlockingStub.class); + final Group group = client.createGroup( + CreateGroupRequest.newBuilder() + .setGroup(Group.newBuilder() + .setName("groups/baz")) + .build()); + assertThat(group.getName()).isEqualTo("groups/baz"); // No exception is thrown. - final Empty ignored = client.deleteApplication(DeleteApplicationRequest.newBuilder() - .setName("applications/baz") - .build()); + final Empty ignored = client.deleteGroup(DeleteGroupRequest.newBuilder() + .setName("groups/baz") + .build()); } }