From 788d2ab3e8fe4946614969b4f4449f90c44990d8 Mon Sep 17 00:00:00 2001 From: minux Date: Thu, 26 Sep 2024 17:07:37 +0900 Subject: [PATCH 1/5] Remove hostnamePatterns from credential files (#1030) Motivation: We decided to repurpose the `MirrorCredential` to manage all repository credentials, not just those specific to mirroring. The hostnamePatterns property, which was specific to mirroring, is no longer necessary. With the introduction of a dedicated mirroring API, hostnamePatterns can be safely removed. Modifications: - Added a job that removes the `hostnamePatterns` property from existing credential files. This prepares the system for the complete removal of this property in the next PR. - Remove `MirroringMigrationService` to avoid conflict while executing the job. Result: - The `hostnamePatterns` property is now deprecated and will be removed entirely in the subsequent PR. --- .../MirroringMigrationServiceClusterTest.java | 263 ---------- .../mirror/MirroringMigrationServiceTest.java | 391 --------------- .../RemovingHostnamePatternsServiceTest.java | 188 +++++++ .../mirror/DefaultMirroringService.java | 7 +- .../mirror/MirroringMigrationService.java | 469 ------------------ .../RemovingHostnamePatternsService.java | 145 ++++++ 6 files changed, 336 insertions(+), 1127 deletions(-) delete mode 100644 server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringMigrationServiceClusterTest.java delete mode 100644 server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringMigrationServiceTest.java create mode 100644 server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsServiceTest.java delete mode 100644 server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/MirroringMigrationService.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsService.java diff --git a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringMigrationServiceClusterTest.java b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringMigrationServiceClusterTest.java deleted file mode 100644 index c767c98565..0000000000 --- a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringMigrationServiceClusterTest.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2023 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.mirror; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationService.PATH_LEGACY_CREDENTIALS; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationService.PATH_LEGACY_CREDENTIALS_BACKUP; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationService.PATH_LEGACY_MIRRORS; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationService.PATH_LEGACY_MIRRORS_BACKUP; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.ACCESS_TOKEN_CREDENTIAL; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.PASSWORD_CREDENTIAL; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.PUBLIC_KEY_CREDENTIAL; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.REPO0_MIRROR; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.REPO1_MIRROR; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.REPO2_MIRROR; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.REPO3_MIRROR; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.TEST_PROJ; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.TEST_REPO0; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.TEST_REPO1; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.TEST_REPO2; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.TEST_REPO3; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.assertCredential; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationServiceTest.assertMirrorConfig; -import static com.linecorp.centraldogma.testing.internal.auth.TestAuthMessageUtil.getAccessToken; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.stream.IntStream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; -import org.junit.jupiter.api.extension.RegisterExtension; - -import com.fasterxml.jackson.core.JsonParseException; -import com.spotify.futures.CompletableFutures; - -import com.linecorp.armeria.client.WebClient; -import com.linecorp.armeria.common.SessionProtocol; -import com.linecorp.armeria.internal.common.util.PortUtil; -import com.linecorp.centraldogma.client.CentralDogmaRepository; -import com.linecorp.centraldogma.client.armeria.ArmeriaCentralDogmaBuilder; -import com.linecorp.centraldogma.common.Change; -import com.linecorp.centraldogma.common.Entry; -import com.linecorp.centraldogma.common.EntryNotFoundException; -import com.linecorp.centraldogma.common.Markup; -import com.linecorp.centraldogma.common.PathPattern; -import com.linecorp.centraldogma.common.Revision; -import com.linecorp.centraldogma.server.CentralDogma; -import com.linecorp.centraldogma.server.CentralDogmaBuilder; -import com.linecorp.centraldogma.server.ZooKeeperReplicationConfig; -import com.linecorp.centraldogma.server.ZooKeeperServerConfig; -import com.linecorp.centraldogma.server.auth.AuthProviderFactory; -import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfig; -import com.linecorp.centraldogma.server.storage.project.Project; -import com.linecorp.centraldogma.testing.internal.TemporaryFolderExtension; -import com.linecorp.centraldogma.testing.internal.auth.TestAuthMessageUtil; -import com.linecorp.centraldogma.testing.internal.auth.TestAuthProviderFactory; - -class MirroringMigrationServiceClusterTest { - - @RegisterExtension - static final TemporaryFolderExtension tempDir = new TemporaryFolderExtension(); - private final AuthProviderFactory factory = new TestAuthProviderFactory(); - - @Timeout(value = 3, unit = TimeUnit.MINUTES) - @Test - void shouldMigrateMirrorConfigsWithZooKeeper() throws Exception { - final int numberReplicas = 3; - final int[] ports = unusedTcpPorts(3 * numberReplicas); - final Map serverConfigs = new HashMap<>(); - for (int i = 0; i < numberReplicas; i++) { - final int quorumPort = ports[i * 3]; - final int electionPort = ports[i * 3 + 1]; - serverConfigs.put(i + 1, new ZooKeeperServerConfig("127.0.0.1", quorumPort, electionPort, 0, - null, null)); - } - final List servers = - IntStream.range(0, numberReplicas).mapToObj(i -> { - try { - final File data = tempDir.newFolder().toFile(); - final ZooKeeperReplicationConfig replicationConfig = - new ZooKeeperReplicationConfig(i + 1, serverConfigs); - final int port = ports[i * 3 + 2]; - return new CentralDogmaBuilder(data) - .port(port, SessionProtocol.HTTP) - .pluginConfigs(new MirroringServicePluginConfig(false)) - .authProviderFactory(factory) - .replication(replicationConfig) - .administrators(TestAuthMessageUtil.USERNAME) - .build(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }) - .collect(toImmutableList()); - final List> futures = servers.stream().map(CentralDogma::start) - .collect(toImmutableList()); - CompletableFutures.allAsList(futures).join(); - - final int serverPort = servers.get(0).activePort().localAddress().getPort(); - final String accessToken = getAccessToken(WebClient.of("http://127.0.0.1:" + serverPort), - TestAuthMessageUtil.USERNAME, - TestAuthMessageUtil.PASSWORD); - final com.linecorp.centraldogma.client.CentralDogma client = - new ArmeriaCentralDogmaBuilder() - .host("127.0.0.1", serverPort) - .accessToken(accessToken) - .build(); - - client.createProject(TEST_PROJ).join(); - client.createRepository(TEST_PROJ, TEST_REPO0).join(); - client.createRepository(TEST_PROJ, TEST_REPO1).join(); - client.createRepository(TEST_PROJ, TEST_REPO2).join(); - client.createRepository(TEST_PROJ, TEST_REPO3).join(); - - final String mirrorsJson = - '[' + REPO0_MIRROR + ',' + REPO1_MIRROR + ',' + REPO2_MIRROR + ',' + REPO3_MIRROR + ']'; - client.push(TEST_PROJ, Project.REPO_META, Revision.HEAD, "Create a new mirrors.json", "", - Markup.PLAINTEXT, Change.ofJsonUpsert(PATH_LEGACY_MIRRORS, mirrorsJson)).join(); - - final String credentialJson = '[' + PUBLIC_KEY_CREDENTIAL + ',' + PASSWORD_CREDENTIAL + ',' + - ACCESS_TOKEN_CREDENTIAL + ']'; - client.push(TEST_PROJ, Project.REPO_META, Revision.HEAD, "Create a new credentials.json", "", - Markup.PLAINTEXT, Change.ofJsonUpsert(PATH_LEGACY_CREDENTIALS, credentialJson)).join(); - // Wait for the replication to complete. - Thread.sleep(5000); - - final List newServers = - servers.stream().parallel().map(server -> { - final int port = server.activePort().localAddress().getPort(); - // Restart the servers with mirroring enabled. - server.stop().join(); - return new CentralDogmaBuilder(server.config().dataDir()) - .port(port, SessionProtocol.HTTP) - .replication(server.config().replicationConfig()) - .build(); - }) - .collect(toImmutableList()); - - final List> newFutures = newServers.stream().map(CentralDogma::start) - .collect(toImmutableList()); - CompletableFutures.allAsList(newFutures).join(); - // Wait for the mirroring migration to complete. - Thread.sleep(60000); - - // Check if the mirroring migration is correctly completed. - final int newServerPort = newServers.get(0).activePort().localAddress().getPort(); - final com.linecorp.centraldogma.client.CentralDogma newClient = - new ArmeriaCentralDogmaBuilder() - .host("127.0.0.1", newServerPort) - .build(); - final CentralDogmaRepository repo = newClient.forRepo(TEST_PROJ, Project.REPO_META); - - final Map> mirrorEntries = repo.file(PathPattern.of("/mirrors/*.json")).get().join(); - assertThat(mirrorEntries).hasSize(4); - final Map>> mirrors = - mirrorEntries.entrySet().stream() - .collect(toImmutableMap(e -> { - try { - return e.getValue().contentAsJson().get("localRepo").asText(); - } catch (JsonParseException ex) { - throw new RuntimeException(ex); - } - }, Function.identity())); - - assertMirrorConfig(mirrors.get(TEST_REPO0), "mirror-" + TEST_PROJ + '-' + TEST_REPO0, - REPO0_MIRROR); - assertMirrorConfig(mirrors.get(TEST_REPO1), "mirror-1", REPO1_MIRROR); - // "-khaki" suffix is added because the mirror ID is duplicated. - assertMirrorConfig(mirrors.get(TEST_REPO2), "mirror-1-khaki", REPO2_MIRROR); - // "-speakers" suffix is added because the mirror ID is duplicated. - assertMirrorConfig(mirrors.get(TEST_REPO3), "mirror-1-speakers", REPO3_MIRROR); - - final Map> credentialEntries = repo.file(PathPattern.of("/credentials/*.json")) - .get() - .join(); - - assertThat(credentialEntries).hasSize(3); - final Map>> credentials = - credentialEntries.entrySet().stream() - .collect(toImmutableMap(e -> { - try { - return e.getValue().contentAsJson().get("type").asText(); - } catch (JsonParseException ex) { - throw new RuntimeException(ex); - } - }, Function.identity())); - - assertCredential(credentials.get("public_key"), "credential-" + TEST_PROJ + "-public_key", - PUBLIC_KEY_CREDENTIAL); - assertCredential(credentials.get("password"), "credential-1", PASSWORD_CREDENTIAL); - // "-1" suffix is added because the credential ID is duplicated. - assertCredential(credentials.get("access_token"), "credential-1-slingshot", ACCESS_TOKEN_CREDENTIAL); - - // Make sure that the legacy files are renamed. - assertThatThrownBy(() -> repo.file(PATH_LEGACY_MIRRORS).get().join()) - .isInstanceOf(CompletionException.class) - .hasCauseInstanceOf(EntryNotFoundException.class); - assertThatThrownBy(() -> repo.file(PATH_LEGACY_CREDENTIALS).get().join()) - .isInstanceOf(CompletionException.class) - .hasCauseInstanceOf(EntryNotFoundException.class); - assertThat(repo.file(PATH_LEGACY_MIRRORS_BACKUP).get().join() - .hasContent()).isTrue(); - assertThat(repo.file(PATH_LEGACY_CREDENTIALS_BACKUP).get().join() - .hasContent()).isTrue(); - } - - // Forked from ZooKeeperTestUtil in Armeria. - private static int[] unusedTcpPorts(int numPorts) { - final int[] ports = new int[numPorts]; - for (int i = 0; i < numPorts; i++) { - int mayUnusedTcpPort; - for (;;) { - mayUnusedTcpPort = PortUtil.unusedTcpPort(); - if (i == 0) { - // The first acquired port is always unique. - break; - } - boolean isAcquiredPort = false; - for (int j = 0; j < i; j++) { - isAcquiredPort = ports[j] == mayUnusedTcpPort; - if (isAcquiredPort) { - break; - } - } - - if (isAcquiredPort) { - // Duplicate port. Look up an unused port again. - continue; - } else { - // A newly acquired unique port. - break; - } - } - ports[i] = mayUnusedTcpPort; - } - return ports; - } -} diff --git a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringMigrationServiceTest.java b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringMigrationServiceTest.java deleted file mode 100644 index 8b887b2167..0000000000 --- a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringMigrationServiceTest.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright 2023 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.mirror; - -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationService.MIRROR_MIGRATION_JOB_LOG; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationService.PATH_LEGACY_CREDENTIALS; -import static com.linecorp.centraldogma.server.internal.mirror.MirroringMigrationService.PATH_LEGACY_MIRRORS; -import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson; -import static org.assertj.core.api.Assertions.assertThat; - -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonNode; - -import com.linecorp.centraldogma.common.Author; -import com.linecorp.centraldogma.common.Change; -import com.linecorp.centraldogma.common.Entry; -import com.linecorp.centraldogma.common.Revision; -import com.linecorp.centraldogma.internal.Jackson; -import com.linecorp.centraldogma.server.metadata.MetadataService; -import com.linecorp.centraldogma.server.mirror.Mirror; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; -import com.linecorp.centraldogma.server.storage.project.InternalProjectInitializer; -import com.linecorp.centraldogma.server.storage.project.Project; -import com.linecorp.centraldogma.server.storage.project.ProjectManager; -import com.linecorp.centraldogma.server.storage.repository.Repository; -import com.linecorp.centraldogma.server.storage.repository.RepositoryManager; -import com.linecorp.centraldogma.testing.internal.ProjectManagerExtension; - -class MirroringMigrationServiceTest { - - // The static fields are shared with MirroringMigrationServiceClusterTest. - static final String TEST_PROJ = "fooProj"; - static final String TEST_REPO0 = "repo0"; - static final String TEST_REPO1 = "repo1"; - static final String TEST_REPO2 = "repo2"; - static final String TEST_REPO3 = "repo3"; - - // The real key pair generated using: - // - // ssh-keygen -t rsa -b 768 -N sesame - // - static final String PUBLIC_KEY = - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCmkW9HjZE5q0EM06MUWXYFTNTi" + - "KkfYD/pH2GwJw6yi20Gi0TzjJ6YBLueU48vxkwWmw6sTOEuBxtzefTxs4kQuatev" + - "uXn7tWX9fhSIAEp+zdyQY7InyCqfHFwRwswemCM= trustin@localhost"; - - static final String PRIVATE_KEY = - "-----BEGIN RSA PRIVATE KEY-----\n" + - "Proc-Type: 4,ENCRYPTED\n" + - "DEK-Info: AES-128-CBC,C35856D3C524AA2FD32D878F4409B97E\n" + - '\n' + - "X3HRmqg2bUqfqxkWjHsr4KeN1UyN5QbypGd7Jov/nDSyiIWe4zPJD/3oji0xOK+h\n" + - "Lxq+c8DDu7ItpC6dwe5WexcyIKGF7WqlkqeEhVM3VOkQtbpbdnb7bA8mLja2unMW\n" + - "bFLgQiTF1Y8SlG4Q70N0iY638AeIG/ZUU14LSBFSQDkrtZ+f7bhIhVDDavANMF+B\n" + - "+eiQ4u3W59Cpbm83AfzqotrPXuBusfyBjH7Wfj0XRvOGRjTQT0jXIWWpLqnIy5ms\n" + - "HNGlMoJElUQuPpbQUiFvmqiMj40r9V/Wx/8+GciADOs4FsTvGFKIcouWDhjIWg0b\n" + - "DKFqV/Hw/AjkAafkySxxmk1+EIen4XfkghtlWLwT2Xp4RtJXYiVC9q9483jDv3+Z\n" + - "iTa5rjFuro4WJkDZp6/N6l+/HcbBXL8L6y66xsJwP+6GLuDLpXjGZrneV1ip2dtG\n" + - "BQzvlgCOr9pTAa4Ar7MC3E2C6+qPhOwO4B/f1cigwRaEB92MHz5gJsITU3xVfTjV\n" + - "yf4THKipBDxqnET6F2FMZJFolVzFEXDaCFNC1TjBqS0+A8KaMcO/lXjJxtfvV37l\n" + - "zmB/ey0dZ8WBCazCp9OX3dYgNkVR1yYNlJWOGJS8Cwc=\n" + - "-----END RSA PRIVATE KEY-----"; - - static final String PASSPHRASE = "sesame"; - - // A mirror config without ID - static final String REPO0_MIRROR = - "{\n" + - " \"type\": \"single\",\n" + - " \"enabled\": true,\n" + - " \"schedule\": \"0 * * * * ?\",\n" + - " \"direction\": \"REMOTE_TO_LOCAL\",\n" + - " \"localRepo\": \"" + TEST_REPO0 + "\",\n" + - " \"localPath\": \"/\",\n" + - " \"remoteUri\": \"git+ssh://git.foo.com/foo.git/settings#release\",\n" + - " \"gitignore\": [\n" + - " \"/credential.txt\",\n" + - " \"private_dir\"\n" + - " ]\n" + - '}'; - - // A mirror config with ID - static final String REPO1_MIRROR = - "{\n" + - " \"id\": \"mirror-1\",\n" + - " \"type\": \"single\",\n" + - " \"enabled\": true,\n" + - " \"schedule\": \"0 * * * * ?\",\n" + - " \"direction\": \"REMOTE_TO_LOCAL\",\n" + - " \"localRepo\": \"" + TEST_REPO1 + "\",\n" + - " \"localPath\": \"/\",\n" + - " \"remoteUri\": \"git+ssh://git.bar.com/foo.git/settings#release\",\n" + - " \"credentialId\": \"credential-1\",\n" + - " \"gitignore\": [\n" + - " \"/credential.txt\",\n" + - " \"private_dir\"\n" + - " ]\n" + - '}'; - - // An invalid mirror config that has no localRepo. - static final String INVALID_MIRROR = - "{\n" + - " \"type\": \"single\",\n" + - " \"enabled\": true,\n" + - " \"schedule\": \"0 * * * * ?\",\n" + - " \"direction\": \"REMOTE_TO_LOCAL\",\n" + - " \"localPath\": \"/\",\n" + - " \"remoteUri\": \"git+ssh://git.bar.com/foo.git/settings#release\",\n" + - " \"credentialId\": \"credential-1\",\n" + - " \"gitignore\": [\n" + - " \"/credential.txt\",\n" + - " \"private_dir\"\n" + - " ]\n" + - '}'; - - // A mirror config with duplicate ID - static final String REPO2_MIRROR = - "{\n" + - " \"id\": \"mirror-1\",\n" + - " \"type\": \"single\",\n" + - " \"enabled\": true,\n" + - " \"schedule\": \"0 * * * * ?\",\n" + - " \"direction\": \"REMOTE_TO_LOCAL\",\n" + - " \"localRepo\": \"" + TEST_REPO2 + "\",\n" + - " \"localPath\": \"/\",\n" + - " \"remoteUri\": \"git+ssh://git.qux.com/foo.git/settings#release\",\n" + - " \"gitignore\": [\n" + - " \"/credential.txt\",\n" + - " \"private_dir\"\n" + - " ]\n" + - '}'; - - // A mirror config with duplicate ID - static final String REPO3_MIRROR = - "{\n" + - " \"id\": \"mirror-1\",\n" + - " \"type\": \"single\",\n" + - " \"enabled\": true,\n" + - " \"schedule\": \"0 * * * * ?\",\n" + - " \"direction\": \"REMOTE_TO_LOCAL\",\n" + - " \"localRepo\": \"" + TEST_REPO3 + "\",\n" + - " \"localPath\": \"/\",\n" + - " \"remoteUri\": \"git+ssh://git.qux.com/foo.git/settings#release\",\n" + - " \"gitignore\": [\n" + - " \"/credential.txt\",\n" + - " \"private_dir\"\n" + - " ]\n" + - '}'; - - // A credential without ID - static final String PUBLIC_KEY_CREDENTIAL = - '{' + - " \"type\": \"public_key\"," + - " \"hostnamePatterns\": [" + - " \"^git\\\\.foo\\\\.com$\"" + - " ]," + - " \"username\": \"trustin\"," + - " \"publicKey\": \"" + Jackson.escapeText(PUBLIC_KEY) + "\"," + - " \"privateKey\": \"" + Jackson.escapeText(PRIVATE_KEY) + "\"," + - " \"passphrase\": \"" + Jackson.escapeText(PASSPHRASE) + '"' + - '}'; - - // A credential with ID - static final String PASSWORD_CREDENTIAL = - '{' + - " \"id\": \"credential-1\"," + - " \"type\": \"password\"," + - " \"hostnamePatterns\": [" + - " \".*.bar\\\\.com$\"" + - " ]," + - " \"username\": \"trustin\"," + - " \"password\": \"sesame\"" + - '}'; - - // An invalid credential - static final String INVALID_CREDENTIAL = - '{' + - " \"hostnamePatterns\": [" + - " \".*.bar\\\\.com$\"" + - " ]," + - " \"username\": \"trustin\"," + - " \"password\": \"sesame\"" + - '}'; - - // A credential with duplicate ID - static final String ACCESS_TOKEN_CREDENTIAL = - '{' + - " \"id\": \"credential-1\"," + - " \"type\": \"access_token\"," + - " \"hostnamePatterns\": [" + - " \"^bar\\\\.com$\"" + - " ]," + - " \"accessToken\": \"sesame\"" + - '}'; - - @RegisterExtension - static ProjectManagerExtension projectManagerExtension = new ProjectManagerExtension() { - @Override - protected boolean runForEachTest() { - return true; - } - - @Override - protected void afterExecutorStarted() { - final ProjectManager projectManager = projectManagerExtension.projectManager(); - final Project project = projectManager.create(TEST_PROJ, Author.SYSTEM); - final RepositoryManager repoManager = project.repos(); - repoManager.create(TEST_REPO0, Author.SYSTEM); - repoManager.create(TEST_REPO1, Author.SYSTEM); - repoManager.create(TEST_REPO2, Author.SYSTEM); - repoManager.create(TEST_REPO3, Author.SYSTEM); - - final MetadataService mds = new MetadataService(projectManager, projectManagerExtension.executor()); - mds.addRepo(Author.SYSTEM, TEST_PROJ, TEST_REPO0).join(); - mds.addRepo(Author.SYSTEM, TEST_PROJ, TEST_REPO1).join(); - mds.addRepo(Author.SYSTEM, TEST_PROJ, TEST_REPO2).join(); - mds.addRepo(Author.SYSTEM, TEST_PROJ, TEST_REPO3).join(); - } - }; - - @Test - void shouldMigrateMirrorsJson() throws Exception { - final ProjectManager projectManager = projectManagerExtension.projectManager(); - final Project project = projectManager.get(TEST_PROJ); - - final String mirrorsJson = - '[' + REPO0_MIRROR + ',' + REPO1_MIRROR + ',' + INVALID_MIRROR + - ',' + REPO2_MIRROR + ',' + REPO3_MIRROR + ']'; - project.metaRepo().commit(Revision.HEAD, System.currentTimeMillis(), Author.SYSTEM, - "Create a new mirrors.json", - Change.ofJsonUpsert(PATH_LEGACY_MIRRORS, mirrorsJson)).join(); - final MirroringMigrationService migrationService = new MirroringMigrationService( - projectManager, projectManagerExtension.executor()); - migrationService.migrate(); - - final Map> entries = project.metaRepo() - .find(Revision.HEAD, "/mirrors/*.json") - .join(); - - // The invalid mirror config should be ignored. - assertThat(entries).hasSize(4); - final Map>> mirrors = - entries.entrySet().stream() - .collect(toImmutableMap(e -> { - try { - return e.getValue().contentAsJson().get("localRepo").asText(); - } catch (JsonParseException ex) { - throw new RuntimeException(ex); - } - }, Function.identity())); - - assertMirrorConfig(mirrors.get(TEST_REPO0), "mirror-" + TEST_PROJ + '-' + TEST_REPO0, - REPO0_MIRROR); - assertMirrorConfig(mirrors.get(TEST_REPO1), "mirror-1", REPO1_MIRROR); - // "-khaki" suffix is added because the mirror ID is duplicated. - assertMirrorConfig(mirrors.get(TEST_REPO2), "mirror-1-khaki", REPO2_MIRROR); - // "-speakers" suffix is added because the mirror ID is duplicated. - assertMirrorConfig(mirrors.get(TEST_REPO3), "mirror-1-speakers", REPO3_MIRROR); - } - - static void assertMirrorConfig(Map.Entry> actualMirrorConfig, String mirrorId, - String expectedMirrorConfig) throws JsonParseException { - assertThat(actualMirrorConfig.getKey()).matches("/mirrors/" + mirrorId + "\\.json"); - final JsonNode mirrorConfig = actualMirrorConfig.getValue().contentAsJson(); - assertThat(mirrorConfig.get("id").asText()).matches(mirrorId); - assertThatJson(mirrorConfig).whenIgnoringPaths("id", "credentialId") - .isEqualTo(expectedMirrorConfig); - } - - @Test - void shouldMigrateCredential() throws Exception { - final ProjectManager projectManager = projectManagerExtension.projectManager(); - final Project project = projectManager.get(TEST_PROJ); - - final String credentialJson = '[' + PUBLIC_KEY_CREDENTIAL + ',' + PASSWORD_CREDENTIAL + ',' + - INVALID_CREDENTIAL + ',' + ACCESS_TOKEN_CREDENTIAL + ']'; - - project.metaRepo().commit(Revision.HEAD, System.currentTimeMillis(), Author.SYSTEM, - "Create a new credentials.json", - Change.ofJsonUpsert(PATH_LEGACY_CREDENTIALS, credentialJson)).join(); - final MirroringMigrationService migrationService = new MirroringMigrationService( - projectManager, projectManagerExtension.executor()); - migrationService.migrate(); - - final Map> entries = project.metaRepo() - .find(Revision.HEAD, "/credentials/*.json") - .join(); - - assertThat(entries).hasSize(3); - final Map>> credentials = - entries.entrySet().stream() - .collect(toImmutableMap(e -> { - try { - return e.getValue().contentAsJson().get("type").asText(); - } catch (JsonParseException ex) { - throw new RuntimeException(ex); - } - }, Function.identity())); - - assertCredential(credentials.get("public_key"), "credential-" + TEST_PROJ + "-public_key", - PUBLIC_KEY_CREDENTIAL); - assertCredential(credentials.get("password"), "credential-1", PASSWORD_CREDENTIAL); - // "-slingshot" suffix is added because the credential ID is duplicated. - assertCredential(credentials.get("access_token"), "credential-1-slingshot", ACCESS_TOKEN_CREDENTIAL); - - // Make sure that the migration log is written. - final Repository dogmaRepo = projectManager.get(InternalProjectInitializer.INTERNAL_PROJECT_DOGMA) - .repos().get(Project.REPO_DOGMA); - final Map> log = dogmaRepo.find(Revision.HEAD, MIRROR_MIGRATION_JOB_LOG).join(); - final JsonNode data = log.get(MIRROR_MIGRATION_JOB_LOG).contentAsJson(); - assertThat(Jackson.readValue(data.get("timestamp").asText(), Instant.class)).isBefore(Instant.now()); - } - - @Test - void shouldUpdateCredentialIdToMirrorConfig() throws Exception { - final ProjectManager projectManager = projectManagerExtension.projectManager(); - final Project project = projectManager.get(TEST_PROJ); - final String mirrorsJson = '[' + REPO0_MIRROR + ',' + REPO1_MIRROR + ',' + REPO2_MIRROR + ']'; - project.metaRepo().commit(Revision.HEAD, System.currentTimeMillis(), Author.SYSTEM, - "Create a new mirrors.json", - Change.ofJsonUpsert(PATH_LEGACY_MIRRORS, mirrorsJson)).join(); - - final String credentialJson = '[' + PUBLIC_KEY_CREDENTIAL + ',' + PASSWORD_CREDENTIAL + ',' + - ACCESS_TOKEN_CREDENTIAL + ']'; - - project.metaRepo().commit(Revision.HEAD, System.currentTimeMillis(), Author.SYSTEM, - "Create a new credentials.json", - Change.ofJsonUpsert(PATH_LEGACY_CREDENTIALS, credentialJson)).join(); - - final MirroringMigrationService migrationService = new MirroringMigrationService( - projectManager, projectManagerExtension.executor()); - migrationService.migrate(); - - final List mirrors = project.metaRepo().mirrors().join(); - assertThat(mirrors).hasSize(3); - for (Mirror mirror : mirrors) { - if ("mirror-1".equals(mirror.id())) { - assertThat(mirror.credential().id()).isEqualTo("credential-1"); - } else if (mirror.id().startsWith("mirror-" + TEST_PROJ + '-' + TEST_REPO0)) { - assertThat(mirror.credential().id()).matches("credential-" + TEST_PROJ + "-public_key"); - } else if (mirror.id().startsWith("mirror-1-")) { - // No matched credential was found. - assertThat(mirror.credential().id()).matches(""); - assertThat(mirror.credential()).isSameAs(MirrorCredential.FALLBACK); - } else { - throw new AssertionError("Unexpected mirror ID: " + mirror.id()); - } - } - } - - @Test - void shouldBuildShortWords() { - final List words = MirroringMigrationService.buildShortWords(); - final int expectedSize = 1296; - assertThat(words).hasSize(expectedSize); - assertThat(words.get(0)).isEqualTo("aardvark"); - assertThat(words.get(expectedSize - 1)).isEqualTo("zucchini"); - } - - static void assertCredential(Map.Entry> actualCredential, String credentialId, - String expectedCredential) throws JsonParseException { - assertThat(actualCredential.getKey()).matches("/credentials/" + credentialId + "\\.json"); - final JsonNode credential = actualCredential.getValue().contentAsJson(); - assertThat(credential.get("id").asText()).matches(credentialId); - assertThatJson(credential).whenIgnoringPaths("id") - .isEqualTo(expectedCredential); - } -} diff --git a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsServiceTest.java b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsServiceTest.java new file mode 100644 index 0000000000..e3bee1f019 --- /dev/null +++ b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsServiceTest.java @@ -0,0 +1,188 @@ +/* + * Copyright 2023 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.mirror; + +import static com.linecorp.centraldogma.server.internal.mirror.RemovingHostnamePatternsService.REMOVING_HOSTNAME_JOB_LOG; +import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Instant; +import java.util.Map; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import com.linecorp.centraldogma.common.Author; +import com.linecorp.centraldogma.common.Change; +import com.linecorp.centraldogma.common.Entry; +import com.linecorp.centraldogma.common.Revision; +import com.linecorp.centraldogma.internal.Jackson; +import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.storage.project.InternalProjectInitializer; +import com.linecorp.centraldogma.server.storage.project.Project; +import com.linecorp.centraldogma.server.storage.project.ProjectManager; +import com.linecorp.centraldogma.server.storage.repository.Repository; +import com.linecorp.centraldogma.testing.internal.ProjectManagerExtension; + +class RemovingHostnamePatternsServiceTest { + + private static final String TEST_PROJ = "fooProj"; + + // The real key pair generated using: + // + // ssh-keygen -t rsa -b 768 -N sesame + // + private static final String PUBLIC_KEY = + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCmkW9HjZE5q0EM06MUWXYFTNTi" + + "KkfYD/pH2GwJw6yi20Gi0TzjJ6YBLueU48vxkwWmw6sTOEuBxtzefTxs4kQuatev" + + "uXn7tWX9fhSIAEp+zdyQY7InyCqfHFwRwswemCM= trustin@localhost"; + + private static final String PRIVATE_KEY = + "-----BEGIN RSA PRIVATE KEY-----\n" + + "Proc-Type: 4,ENCRYPTED\n" + + "DEK-Info: AES-128-CBC,C35856D3C524AA2FD32D878F4409B97E\n" + + '\n' + + "X3HRmqg2bUqfqxkWjHsr4KeN1UyN5QbypGd7Jov/nDSyiIWe4zPJD/3oji0xOK+h\n" + + "Lxq+c8DDu7ItpC6dwe5WexcyIKGF7WqlkqeEhVM3VOkQtbpbdnb7bA8mLja2unMW\n" + + "bFLgQiTF1Y8SlG4Q70N0iY638AeIG/ZUU14LSBFSQDkrtZ+f7bhIhVDDavANMF+B\n" + + "+eiQ4u3W59Cpbm83AfzqotrPXuBusfyBjH7Wfj0XRvOGRjTQT0jXIWWpLqnIy5ms\n" + + "HNGlMoJElUQuPpbQUiFvmqiMj40r9V/Wx/8+GciADOs4FsTvGFKIcouWDhjIWg0b\n" + + "DKFqV/Hw/AjkAafkySxxmk1+EIen4XfkghtlWLwT2Xp4RtJXYiVC9q9483jDv3+Z\n" + + "iTa5rjFuro4WJkDZp6/N6l+/HcbBXL8L6y66xsJwP+6GLuDLpXjGZrneV1ip2dtG\n" + + "BQzvlgCOr9pTAa4Ar7MC3E2C6+qPhOwO4B/f1cigwRaEB92MHz5gJsITU3xVfTjV\n" + + "yf4THKipBDxqnET6F2FMZJFolVzFEXDaCFNC1TjBqS0+A8KaMcO/lXjJxtfvV37l\n" + + "zmB/ey0dZ8WBCazCp9OX3dYgNkVR1yYNlJWOGJS8Cwc=\n" + + "-----END RSA PRIVATE KEY-----"; + + private static final String PASSPHRASE = "sesame"; + + private static final String PUBLIC_KEY_CREDENTIAL_FORMAT = + '{' + + " \"id\": \"%s\"," + + " \"type\": \"public_key\"," + + " %s" + + " \"username\": \"trustin\"," + + " \"publicKey\": \"" + Jackson.escapeText(PUBLIC_KEY) + "\"," + + " \"privateKey\": \"" + Jackson.escapeText(PRIVATE_KEY) + "\"," + + " \"passphrase\": \"" + Jackson.escapeText(PASSPHRASE) + '"' + + '}'; + + private static final String PASSWORD_CREDENTIAL_FORMAT = + '{' + + " \"id\": \"%s\"," + + " \"type\": \"password\"," + + " %s" + + " \"username\": \"trustin\"," + + " \"password\": \"sesame\"" + + '}'; + + // A credential with duplicate ID + static final String ACCESS_TOKEN_CREDENTIAL_FORMAT = + '{' + + " \"id\": \"%s\"," + + " \"type\": \"access_token\"," + + " %s" + + " \"accessToken\": \"sesame\"" + + '}'; + + private static final String HOSTNAME_PATTERNS = + " \"hostnamePatterns\": [" + + " \"^git\\\\.foo\\\\.com$\"" + + " ],"; + + @RegisterExtension + static ProjectManagerExtension projectManagerExtension = new ProjectManagerExtension() { + @Override + protected boolean runForEachTest() { + return true; + } + + @Override + protected void afterExecutorStarted() { + final ProjectManager projectManager = projectManagerExtension.projectManager(); + projectManager.create(TEST_PROJ, Author.SYSTEM); + } + }; + + @CsvSource(value = { + HOSTNAME_PATTERNS, "''", "\"hostnamePatterns\": [],", "\"hostnamePatterns\": null," + }, delimiter = ';') // string contains ',' so change the delimiter. + @ParameterizedTest + void removeHostnamePatterns(String hostnamePatterns) throws Exception { + final ProjectManager projectManager = projectManagerExtension.projectManager(); + final Project project = projectManager.get(TEST_PROJ); + + final String publicKeyCredential = + String.format(PUBLIC_KEY_CREDENTIAL_FORMAT, "credential-1", hostnamePatterns); + final String passwordCredential = + String.format(PASSWORD_CREDENTIAL_FORMAT, "credential-2", hostnamePatterns); + final String accessTokenCredential = + String.format(ACCESS_TOKEN_CREDENTIAL_FORMAT, "credential-3", hostnamePatterns); + final Change change1 = + Change.ofJsonUpsert("/credentials/credential-1.json", publicKeyCredential); + final Change change2 = + Change.ofJsonUpsert("/credentials/credential-2.json", passwordCredential); + final Change change3 = + Change.ofJsonUpsert("/credentials/credential-3.json", accessTokenCredential); + + project.metaRepo().commit(Revision.HEAD, System.currentTimeMillis(), Author.SYSTEM, + "Create credentials.", change1, change2, change3).join(); + final RemovingHostnamePatternsService service = new RemovingHostnamePatternsService( + projectManager, projectManagerExtension.executor()); + service.start(); + + final Map> entries = project.metaRepo() + .find(Revision.HEAD, "/credentials/*.json") + .join(); + + assertThat(entries).hasSize(3); + final Entry entry1 = entries.get("/credentials/credential-1.json"); + assertCredential(entry1, String.format(PUBLIC_KEY_CREDENTIAL_FORMAT, "credential-1", "")); + MirrorCredential mirrorCredential = Jackson.treeToValue(entry1.contentAsJson(), MirrorCredential.class); + assertThat(mirrorCredential.hostnamePatterns()).isEmpty(); + assertThat(mirrorCredential.id()).isEqualTo("credential-1"); + + final Entry entry2 = entries.get("/credentials/credential-2.json"); + assertCredential(entry2, String.format(PASSWORD_CREDENTIAL_FORMAT, "credential-2", "")); + mirrorCredential = Jackson.treeToValue(entry2.contentAsJson(), MirrorCredential.class); + assertThat(mirrorCredential.hostnamePatterns()).isEmpty(); + assertThat(mirrorCredential.id()).isEqualTo("credential-2"); + + final Entry entry3 = entries.get("/credentials/credential-3.json"); + assertCredential(entry3, String.format(ACCESS_TOKEN_CREDENTIAL_FORMAT, "credential-3", "")); + mirrorCredential = Jackson.treeToValue(entry3.contentAsJson(), MirrorCredential.class); + assertThat(mirrorCredential.hostnamePatterns()).isEmpty(); + assertThat(mirrorCredential.id()).isEqualTo("credential-3"); + + // Make sure that the log is written. + final Repository dogmaRepo = projectManager.get(InternalProjectInitializer.INTERNAL_PROJECT_DOGMA) + .repos().get(Project.REPO_DOGMA); + final Map> log = dogmaRepo.find(Revision.HEAD, REMOVING_HOSTNAME_JOB_LOG).join(); + final JsonNode data = log.get(REMOVING_HOSTNAME_JOB_LOG).contentAsJson(); + assertThat(Jackson.readValue(data.get("timestamp").asText(), Instant.class)).isBefore(Instant.now()); + } + + private static void assertCredential(Entry entry, String credential) throws JsonParseException { + assertThatJson(entry.contentAsJson()).isEqualTo( + ((ObjectNode) Jackson.readTree(credential)).without("hostnamePatterns")); + } +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringService.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringService.java index cd85b35906..e789093fad 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringService.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringService.java @@ -131,12 +131,11 @@ public synchronized void start(CommandExecutor commandExecutor) { } })); - // Migrate the old mirrors.json to the new format if exists. try { - new MirroringMigrationService(projectManager, commandExecutor).migrate(); + new RemovingHostnamePatternsService(projectManager, commandExecutor).start(); } catch (Throwable e) { - logger.error("Git mirroring stopped due to an unexpected exception while migrating mirrors.json:", - e); + logger.error("Git mirroring stopped due to an unexpected exception while removing" + + "hostnamePatterns :", e); return; } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/MirroringMigrationService.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/MirroringMigrationService.java deleted file mode 100644 index dab98cbfbb..0000000000 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/MirroringMigrationService.java +++ /dev/null @@ -1,469 +0,0 @@ -/* - * Copyright 2023 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.mirror; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.linecorp.centraldogma.server.internal.storage.repository.DefaultMetaRepository.PATH_CREDENTIALS; -import static com.linecorp.centraldogma.server.internal.storage.repository.DefaultMetaRepository.PATH_MIRRORS; -import static com.linecorp.centraldogma.server.internal.storage.repository.DefaultMetaRepository.credentialFile; -import static com.linecorp.centraldogma.server.internal.storage.repository.DefaultMetaRepository.mirrorFile; -import static com.linecorp.centraldogma.server.internal.storage.repository.MirrorConfig.DEFAULT_SCHEDULE; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.UncheckedIOException; -import java.net.URI; -import java.time.Instant; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import javax.annotation.Nullable; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Stopwatch; -import com.google.common.collect.ImmutableMap; - -import com.linecorp.centraldogma.common.Author; -import com.linecorp.centraldogma.common.Change; -import com.linecorp.centraldogma.common.Entry; -import com.linecorp.centraldogma.common.Markup; -import com.linecorp.centraldogma.common.Revision; -import com.linecorp.centraldogma.internal.Jackson; -import com.linecorp.centraldogma.server.command.Command; -import com.linecorp.centraldogma.server.command.CommandExecutor; -import com.linecorp.centraldogma.server.command.CommitResult; -import com.linecorp.centraldogma.server.internal.replication.ZooKeeperCommandExecutor; -import com.linecorp.centraldogma.server.internal.storage.repository.MirrorConfig; -import com.linecorp.centraldogma.server.internal.storage.repository.RepositoryMetadataException; -import com.linecorp.centraldogma.server.management.ServerStatus; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; -import com.linecorp.centraldogma.server.storage.project.InternalProjectInitializer; -import com.linecorp.centraldogma.server.storage.project.Project; -import com.linecorp.centraldogma.server.storage.project.ProjectManager; -import com.linecorp.centraldogma.server.storage.repository.MetaRepository; -import com.linecorp.centraldogma.server.storage.repository.Repository; - -final class MirroringMigrationService { - - private static final Logger logger = LoggerFactory.getLogger(MirroringMigrationService.class); - - @VisibleForTesting - static final String PATH_LEGACY_MIRRORS = "/mirrors.json"; - @VisibleForTesting - static final String PATH_LEGACY_CREDENTIALS = "/credentials.json"; - @VisibleForTesting - static final String PATH_LEGACY_MIRRORS_BACKUP = PATH_LEGACY_MIRRORS + ".bak"; - @VisibleForTesting - static final String PATH_LEGACY_CREDENTIALS_BACKUP = PATH_LEGACY_CREDENTIALS + ".bak"; - @VisibleForTesting - static final String MIRROR_MIGRATION_JOB_LOG = "/mirror-migration-job.json"; - - private final ProjectManager projectManager; - private final CommandExecutor commandExecutor; - - @Nullable - private List shortWords; - - MirroringMigrationService(ProjectManager projectManager, CommandExecutor commandExecutor) { - this.projectManager = projectManager; - this.commandExecutor = commandExecutor; - } - - void migrate() throws Exception { - if (hasMigrationLog()) { - logger.debug("Mirrors and credentials have already been migrated. Skipping auto migration..."); - return; - } - - // Enter read-only mode. - commandExecutor.execute(Command.updateServerStatus(ServerStatus.REPLICATION_ONLY)) - .get(1, TimeUnit.MINUTES); - logger.info("Starting Mirrors and credentials migration ..."); - if (commandExecutor instanceof ZooKeeperCommandExecutor) { - logger.debug("Waiting for 30 seconds to make sure that all cluster have been notified of the " + - "read-only mode ..."); - Thread.sleep(30000); - } - - final Stopwatch stopwatch = Stopwatch.createStarted(); - int numMigratedProjects = 0; - try { - for (Project project : projectManager.list().values()) { - logger.info("Migrating mirrors and credentials in the project: {} ...", project.name()); - boolean processed = false; - final MetaRepository repository = project.metaRepo(); - processed |= migrateCredentials(repository); - // Update the credential IDs in the mirrors.json file. - processed |= migrateMirrors(repository); - if (processed) { - numMigratedProjects++; - logger.info("Mirrors and credentials in the project: {} have been migrated.", - project.name()); - } else { - logger.info("No legacy configurations of mirrors and credentials found in the project: {}.", - project.name()); - } - } - logMigrationJob(numMigratedProjects); - logger.info("Mirrors and credentials migration has been completed. (took: {} ms.)", - stopwatch.elapsed().toMillis()); - } catch (Exception ex) { - final MirrorMigrationException mirrorException = new MirrorMigrationException( - "Failed to migrate mirrors and credentials. Rollback to the legacy configurations", ex); - try { - rollbackMigration(); - } catch (Exception ex0) { - ex0.addSuppressed(mirrorException); - throw new MirrorMigrationException("Failed to rollback the mirror migration:", ex0); - } - throw mirrorException; - } finally { - // Exit read-only mode. - commandExecutor.execute(Command.updateServerStatus(ServerStatus.WRITABLE)) - .get(1, TimeUnit.MINUTES); - } - - shortWords = null; - } - - private void logMigrationJob(int numMigratedProjects) throws Exception { - final ImmutableMap data = - ImmutableMap.of("timestamp", Instant.now(), - "projects", numMigratedProjects); - final Change change = Change.ofJsonUpsert(MIRROR_MIGRATION_JOB_LOG, - Jackson.writeValueAsString(data)); - final Command command = - Command.push(Author.SYSTEM, InternalProjectInitializer.INTERNAL_PROJECT_DOGMA, - Project.REPO_DOGMA, Revision.HEAD, - "Migration of mirrors and credentials has been done", "", - Markup.PLAINTEXT, change); - executeCommand(command); - } - - private void removeMigrationJobLog() throws Exception { - if (!hasMigrationLog()) { - // Maybe the migration job was failed before writing the log. - return; - } - final Change change = Change.ofRemoval(MIRROR_MIGRATION_JOB_LOG); - final Command command = - Command.push(Author.SYSTEM, InternalProjectInitializer.INTERNAL_PROJECT_DOGMA, - Project.REPO_DOGMA, Revision.HEAD, - "Remove the migration job log", "", - Markup.PLAINTEXT, change); - executeCommand(command); - } - - private boolean hasMigrationLog() throws Exception { - final Project internalProj = projectManager.get(InternalProjectInitializer.INTERNAL_PROJECT_DOGMA); - final Repository repository = internalProj.repos().get(Project.REPO_DOGMA); - final Map> entries = repository.find(Revision.HEAD, MIRROR_MIGRATION_JOB_LOG).get(); - final Entry entry = entries.get(MIRROR_MIGRATION_JOB_LOG); - return entry != null; - } - - private boolean migrateMirrors(MetaRepository repository) throws Exception { - final ArrayNode mirrors = getLegacyMetaData(repository, PATH_LEGACY_MIRRORS); - if (mirrors == null) { - return false; - } - - final List credentials = repository.credentials() - .get(30, TimeUnit.SECONDS); - - final Set mirrorIds = new HashSet<>(); - for (JsonNode mirror : mirrors) { - if (!mirror.isObject()) { - logger.warn("A mirror config must be an object: {} (project: {})", mirror, - repository.parent().name()); - continue; - } - try { - migrateMirror(repository, (ObjectNode) mirror, mirrorIds, credentials); - } catch (Exception e) { - // Log the error and continue to migrate the next mirror. - logger.warn("Failed to migrate a mirror config: {} (project: {})", mirror, - repository.parent().name(), e); - } - } - // Back up the old mirrors.json file and don't use it anymore. - rename(repository, PATH_LEGACY_MIRRORS, PATH_LEGACY_MIRRORS_BACKUP, false); - - return true; - } - - private void migrateMirror(MetaRepository repository, ObjectNode mirror, Set mirrorIds, - List credentials) throws Exception { - String id; - final JsonNode idNode = mirror.get("id"); - if (idNode == null) { - // Fill the 'id' field with a random value if not exists. - id = generateIdForMirror(repository.parent().name(), mirror); - } else { - id = idNode.asText(); - } - id = uniquify(id, mirrorIds); - mirror.put("id", id); - - fillCredentialId(repository, mirror, credentials); - if (mirror.get("schedule") == null) { - mirror.put("schedule", DEFAULT_SCHEDULE); - } - mirrorIds.add(id); - - final String jsonFile = mirrorFile(id); - final Command command = - Command.push(Author.SYSTEM, repository.parent().name(), repository.name(), Revision.HEAD, - "Migrate the mirror " + id + " in '" + PATH_LEGACY_MIRRORS + "' into '" + - jsonFile + "'.", "", Markup.PLAINTEXT, Change.ofJsonUpsert(jsonFile, mirror)); - - executeCommand(command); - } - - private void rollbackMigration() throws Exception { - for (Project project : projectManager.list().values()) { - logger.info("Rolling back the migration of mirrors and credentials in the project: {} ...", - project.name()); - final MetaRepository metaRepository = project.metaRepo(); - rollbackMigration(metaRepository, PATH_MIRRORS, PATH_LEGACY_MIRRORS, - PATH_LEGACY_MIRRORS_BACKUP); - rollbackMigration(metaRepository, PATH_CREDENTIALS, PATH_LEGACY_CREDENTIALS, - PATH_LEGACY_CREDENTIALS_BACKUP); - removeMigrationJobLog(); - } - } - - private void rollbackMigration(MetaRepository repository, String targetDirectory, String originalFile, - String backupFile) throws Exception { - // Delete all files in the target directory - final Map> entries = repository.find(Revision.HEAD, targetDirectory + "**") - .get(); - if (!entries.isEmpty()) { - final List> changes = entries.keySet().stream().map(Change::ofRemoval) - .collect(toImmutableList()); - - final Command command = - Command.push(Author.SYSTEM, repository.parent().name(), - repository.name(), Revision.HEAD, - "Rollback the migration of " + targetDirectory, "", - Markup.PLAINTEXT, changes); - try { - executeCommand(command); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new MirrorMigrationException("Failed to rollback the migration of " + targetDirectory, e); - } - } - - // Revert the backup file to the original file if exists. - final Entry backup = repository.getOrNull(Revision.HEAD, backupFile).get(); - if (backup != null) { - rename(repository, backupFile, originalFile, true); - } - } - - private CommitResult executeCommand(Command command) - throws InterruptedException, ExecutionException, TimeoutException { - return commandExecutor.execute(Command.forcePush(command)).get(1, TimeUnit.MINUTES); - } - - private static void fillCredentialId(MetaRepository repository, ObjectNode mirror, - List credentials) { - final JsonNode credentialId = mirror.get("credentialId"); - if (credentialId != null) { - return; - } - final JsonNode remoteUri = mirror.get("remoteUri"); - if (remoteUri == null) { - // An invalid mirror config. - return; - } - - final String remoteUriText = remoteUri.asText(); - final MirrorCredential credential = MirrorConfig.findCredential(credentials, URI.create(remoteUriText), - null); - if (credential == MirrorCredential.FALLBACK) { - logger.warn("Failed to find a credential for the mirror: {}, project: {}. " + - "Using the fallback credential.", mirror, repository.parent().name()); - } - mirror.put("credentialId", credential.id()); - } - - /** - * Migrate the legacy {@code credentials.json} file into the {@code /credentials/.json} directory. - * While migrating, the {@code id} field of each credential is filled with a random value if absent. - */ - private boolean migrateCredentials(MetaRepository repository) throws Exception { - final ArrayNode credentials = getLegacyMetaData(repository, PATH_LEGACY_CREDENTIALS); - if (credentials == null) { - return false; - } - - final Set credentialIds = new HashSet<>(); - int index = 0; - for (JsonNode credential : credentials) { - if (!credential.isObject()) { - logger.warn("A credential config at {} must be an object: {} (project: {})", index, - credential.getNodeType(), - repository.parent().name()); - } else { - try { - migrateCredential(repository, (ObjectNode) credential, credentialIds); - } catch (Exception e) { - // Log the error and continue to migrate the next credential. - logger.warn("Failed to migrate the credential config in project {}", - repository.parent().name(), e); - } - } - index++; - } - - // Back up the old credentials.json file and don't use it anymore. - rename(repository, PATH_LEGACY_CREDENTIALS, PATH_LEGACY_CREDENTIALS_BACKUP, false); - return true; - } - - private void migrateCredential(MetaRepository repository, ObjectNode credential, Set credentialIds) - throws Exception { - String id; - final JsonNode idNode = credential.get("id"); - final String projectName = repository.parent().name(); - if (idNode == null) { - final JsonNode typeNode = credential.get("type"); - final String type = typeNode.isTextual() ? typeNode.asText() : ""; - // Fill the 'id' field with a random value if not exists. - id = generateIdForCredential(projectName, type); - } else { - id = idNode.asText(); - } - id = uniquify(id, credentialIds); - credential.put("id", id); - credentialIds.add(id); - - final String jsonFile = credentialFile(id); - final Command command = - Command.push(Author.SYSTEM, projectName, repository.name(), Revision.HEAD, - "Migrate the credential '" + id + "' in '" + PATH_LEGACY_CREDENTIALS + - "' into '" + jsonFile + "'.", "", Markup.PLAINTEXT, - Change.ofJsonUpsert(jsonFile, credential)); - executeCommand(command); - } - - @Nullable - private static ArrayNode getLegacyMetaData(MetaRepository repository, String path) - throws InterruptedException, ExecutionException { - final Map> entries = repository.find(Revision.HEAD, path, ImmutableMap.of()) - .get(); - final Entry entry = entries.get(path); - if (entry == null) { - return null; - } - - final JsonNode content = (JsonNode) entry.content(); - if (!content.isArray()) { - throw new RepositoryMetadataException( - path + " must be an array: " + content.getNodeType()); - } - return (ArrayNode) content; - } - - private CommitResult rename(MetaRepository repository, String oldPath, String newPath, boolean rollback) - throws Exception { - final String summary; - if (rollback) { - summary = "Rollback the migration of " + newPath; - } else { - summary = "Back up the legacy " + oldPath + " into " + newPath; - } - final Command command = Command.push(Author.SYSTEM, repository.parent().name(), - repository.name(), Revision.HEAD, - summary, - "", - Markup.PLAINTEXT, - Change.ofRename(oldPath, newPath)); - return executeCommand(command); - } - - /** - * Generates a reproducible ID for the given mirror. - * Pattern: {@code mirror--}. - */ - private static String generateIdForMirror(String projectName, ObjectNode mirror) { - return "mirror-" + projectName + '-' + mirror.get("localRepo").asText(); - } - - private String getShortWord(String id) { - if (shortWords == null) { - shortWords = buildShortWords(); - } - final int index = Math.abs(id.hashCode()) % shortWords.size(); - return shortWords.get(index); - } - - /** - * Generates a reproducible ID for the given credential. - * Pattern: {@code credential--}. - */ - private static String generateIdForCredential(String projectName, String type) { - String id = "credential-" + projectName; - if (!type.isEmpty()) { - id += '-' + type; - } - return id; - } - - private String uniquify(String id, Set existingIds) { - String maybeUnique = id; - while (existingIds.contains(maybeUnique)) { - maybeUnique = id + '-' + getShortWord(maybeUnique); - } - return maybeUnique; - } - - @VisibleForTesting - static List buildShortWords() { - // TODO(ikhoon) Remove 'short_wordlist.txt' if Central Dogma version has been updated enough and - // we can assume that all users have already migrated. - final InputStream is = MirroringMigrationService.class.getResourceAsStream("short_wordlist.txt"); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { - return reader.lines().collect(toImmutableList()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static class MirrorMigrationException extends RuntimeException { - - private static final long serialVersionUID = -3924318204193024460L; - - MirrorMigrationException(String message, Throwable cause) { - super(message, cause); - } - } -} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsService.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsService.java new file mode 100644 index 0000000000..3ef1658aa8 --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsService.java @@ -0,0 +1,145 @@ +/* + * Copyright 2023 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.mirror; + +import static com.linecorp.centraldogma.server.internal.storage.repository.DefaultMetaRepository.PATH_CREDENTIALS; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableMap; + +import com.linecorp.centraldogma.common.Author; +import com.linecorp.centraldogma.common.Change; +import com.linecorp.centraldogma.common.Entry; +import com.linecorp.centraldogma.common.EntryType; +import com.linecorp.centraldogma.common.Markup; +import com.linecorp.centraldogma.common.Revision; +import com.linecorp.centraldogma.internal.Jackson; +import com.linecorp.centraldogma.server.command.Command; +import com.linecorp.centraldogma.server.command.CommandExecutor; +import com.linecorp.centraldogma.server.command.CommitResult; +import com.linecorp.centraldogma.server.storage.project.InternalProjectInitializer; +import com.linecorp.centraldogma.server.storage.project.Project; +import com.linecorp.centraldogma.server.storage.project.ProjectManager; +import com.linecorp.centraldogma.server.storage.repository.MetaRepository; +import com.linecorp.centraldogma.server.storage.repository.Repository; + +final class RemovingHostnamePatternsService { + + private static final Logger logger = LoggerFactory.getLogger(RemovingHostnamePatternsService.class); + + @VisibleForTesting + static final String REMOVING_HOSTNAME_JOB_LOG = "/removing-hostname-job.json"; + + private final ProjectManager projectManager; + private final CommandExecutor commandExecutor; + + RemovingHostnamePatternsService(ProjectManager projectManager, CommandExecutor commandExecutor) { + this.projectManager = projectManager; + this.commandExecutor = commandExecutor; + } + + void start() throws Exception { + if (hasJobLog()) { + return; + } + logger.info("Start removing hostnamePatterns in credential ..."); + + final Stopwatch stopwatch = Stopwatch.createStarted(); + int numProjects = 0; + for (Project project : projectManager.list().values()) { + if (InternalProjectInitializer.INTERNAL_PROJECT_DOGMA.equals(project.name())) { + continue; + } + try { + logger.info("Removing hostnamePatterns in credential files in the project: {} ...", + project.name()); + final List> changes = new ArrayList<>(); + final MetaRepository repository = project.metaRepo(); + for (Entry entry : repository.find(Revision.HEAD, PATH_CREDENTIALS + "**") + .get().values()) { + if (entry.type() != EntryType.JSON) { + continue; + } + final JsonNode content = (JsonNode) entry.content(); + if (content.get("hostnamePatterns") == null) { + continue; + } + changes.add(Change.ofJsonUpsert(entry.path(), + ((ObjectNode) content).without("hostnamePatterns"))); + } + + if (changes.isEmpty()) { + continue; + } + + numProjects++; + logger.info("hostnamePatterns in credentials are removed in the project: {}", + project.name()); + + executeCommand(Command.push( + Author.SYSTEM, project.name(), Project.REPO_META, Revision.HEAD, + "Remove hostnamePatterns in credentials.", "", Markup.PLAINTEXT, + changes)); + } catch (Throwable t) { + logger.warn("Failed to remove hostnamePatterns in credential files in the project: {}", + project.name(), t); + } + } + logger.info("hostnamePatterns are removed in {} projects. (took: {} ms.)", + numProjects, stopwatch.elapsed().toMillis()); + logRemovingJob(numProjects); + } + + private boolean hasJobLog() throws Exception { + final Project internalProj = projectManager.get(InternalProjectInitializer.INTERNAL_PROJECT_DOGMA); + final Repository repository = internalProj.repos().get(Project.REPO_DOGMA); + final Map> entries = repository.find(Revision.HEAD, REMOVING_HOSTNAME_JOB_LOG).get(); + final Entry entry = entries.get(REMOVING_HOSTNAME_JOB_LOG); + return entry != null; + } + + private CommitResult executeCommand(Command command) + throws InterruptedException, ExecutionException, TimeoutException { + return commandExecutor.execute(command).get(1, TimeUnit.MINUTES); + } + + private void logRemovingJob(int numProjects) throws Exception { + final ImmutableMap data = + ImmutableMap.of("timestamp", Instant.now(), + "projects", numProjects); + final Change change = Change.ofJsonUpsert(REMOVING_HOSTNAME_JOB_LOG, + Jackson.writeValueAsString(data)); + executeCommand(Command.push(Author.SYSTEM, InternalProjectInitializer.INTERNAL_PROJECT_DOGMA, + Project.REPO_DOGMA, Revision.HEAD, + "Removing hostnamePatterns from " + numProjects + + " projects has been done.", "", Markup.PLAINTEXT, change)); + } +} From 99943298d3b2507db14b9aa91d350d5823dbbac8 Mon Sep 17 00:00:00 2001 From: minwoox Date: Thu, 26 Sep 2024 17:08:39 +0900 Subject: [PATCH 2/5] Use actions/upload-artifact@v4 --- .github/workflows/actions_build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index f6ef1915ac..8074d37b7b 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -89,7 +89,7 @@ jobs: - name: Upload Artifact if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: reports-JVM-${{ matrix.java }} path: reports-JVM-${{ matrix.java }}.tar From c4c647d110ba4821f53bdd784f3fbce2ff9834f2 Mon Sep 17 00:00:00 2001 From: minwoox Date: Fri, 27 Sep 2024 09:56:01 +0900 Subject: [PATCH 3/5] Fix flaky test in RemovingHostnamePatternsServiceTest --- .../internal/mirror/RemovingHostnamePatternsServiceTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsServiceTest.java b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsServiceTest.java index e3bee1f019..10af800ff3 100644 --- a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsServiceTest.java +++ b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsServiceTest.java @@ -178,7 +178,8 @@ void removeHostnamePatterns(String hostnamePatterns) throws Exception { .repos().get(Project.REPO_DOGMA); final Map> log = dogmaRepo.find(Revision.HEAD, REMOVING_HOSTNAME_JOB_LOG).join(); final JsonNode data = log.get(REMOVING_HOSTNAME_JOB_LOG).contentAsJson(); - assertThat(Jackson.readValue(data.get("timestamp").asText(), Instant.class)).isBefore(Instant.now()); + assertThat(Jackson.readValue(data.get("timestamp").asText(), Instant.class)) + .isBeforeOrEqualTo(Instant.now()); } private static void assertCredential(Entry entry, String credential) throws JsonParseException { From 6ee10295eb65511eb96fd62f90665a7225f91ca5 Mon Sep 17 00:00:00 2001 From: Armeria Date: Fri, 27 Sep 2024 00:57:25 +0000 Subject: [PATCH 4/5] Update the project version to 0.70.1-SNAPSHOT --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e80e075858..e4545c497b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=com.linecorp.centraldogma -version=0.69.2-SNAPSHOT +version=0.70.1-SNAPSHOT projectName=Central Dogma projectUrl=https://line.github.io/centraldogma/ projectDescription=Highly-available version-controlled service configuration repository based on Git, ZooKeeper and HTTP/2 From b550f2763e85531d6e858583c983f0528764da8f Mon Sep 17 00:00:00 2001 From: minux Date: Fri, 27 Sep 2024 16:59:40 +0900 Subject: [PATCH 5/5] Rename `MirroringCredential` to `Credential` (#1031) Motivation: We decided to repurpose the `MirrorCredential` to manage all repository credentials, not just those specific to mirroring. To reflect this role, we should remove `Mirror` prefix from the class name. Caveat: This commit must be deployed to central dogma replicas after applying the changes from #1030. Modifications: - Renamed `MirroringCredential` to `Credential` and moved its package. - Removed `hostnamePatterns` property in `Credential`. Result: - The renamed `Credential` class can now be used for managing various types of repository credentials, beyond just mirroring. --- .../it/mirror/git/ForceRefUpdateTest.java | 4 +- .../it/mirror/git/GitMirrorAuthTest.java | 6 +- .../internal/mirror/AbstractGitMirror.java | 4 +- .../internal/mirror/DefaultGitMirror.java | 18 +- .../server/internal/mirror/SshGitMirror.java | 28 +-- .../DefaultMetaRepositoryWithMirrorTest.java | 78 ++------ .../mirror/DefaultMirroringServiceTest.java | 19 +- .../MirroringAndCredentialServiceV1Test.java | 48 ++--- .../internal/mirror/MirroringTestUtils.java | 6 +- .../RemovingHostnamePatternsServiceTest.java | 189 ------------------ .../server/credential/Credential.java | 63 ++++++ .../mirror => }/credential/package-info.java | 2 +- .../internal/api/CredentialServiceV1.java | 24 +-- .../AbstractCredential.java} | 59 +----- .../AccessTokenCredential.java} | 33 ++- .../CredentialUtil.java} | 6 +- .../NoneCredential.java} | 20 +- .../PasswordCredential.java} | 35 ++-- .../PublicKeyCredential.java} | 40 ++-- .../internal/credential/package-info.java | 22 ++ .../internal/mirror/AbstractMirror.java | 8 +- .../internal/mirror/CentralDogmaMirror.java | 4 +- .../mirror/DefaultMirroringService.java | 8 - .../RemovingHostnamePatternsService.java | 145 -------------- .../repository/DefaultMetaRepository.java | 20 +- .../storage/repository/MirrorConfig.java | 26 +-- .../centraldogma/server/mirror/Mirror.java | 3 +- .../server/mirror/MirrorContext.java | 7 +- .../server/mirror/MirrorCredential.java | 80 -------- .../storage/repository/MetaRepository.java | 10 +- .../AccessTokenCredentialTest.java} | 29 +-- .../NoneCredentialTest.java} | 26 +-- .../PasswordCredentialTest.java} | 34 +--- .../PublicKeyCredentialTest.java} | 95 ++++----- .../mirror/CentralDogmaMirrorTest.java | 6 +- .../credential/MirrorCredentialTest.java | 76 ------- ...p.centraldogma.server.ConfigValueConverter | 2 +- site/src/sphinx/mirroring.rst | 21 +- .../settings/credentials/CredentialDto.ts | 1 - .../settings/credentials/CredentialForm.tsx | 71 +------ .../settings/credentials/CredentialList.tsx | 13 +- .../settings/credentials/CredentialView.tsx | 15 -- .../[projectName]/credentials/index.ts | 4 - .../settings/credentials/new/index.tsx | 1 - .../xds/group/v1/XdsGroupServiceTest.java | 10 +- 45 files changed, 340 insertions(+), 1079 deletions(-) delete mode 100644 server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsServiceTest.java create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/credential/Credential.java rename server/src/main/java/com/linecorp/centraldogma/server/{internal/mirror => }/credential/package-info.java (91%) rename server/src/main/java/com/linecorp/centraldogma/server/internal/{mirror/credential/AbstractMirrorCredential.java => credential/AbstractCredential.java} (50%) rename server/src/main/java/com/linecorp/centraldogma/server/internal/{mirror/credential/AccessTokenMirrorCredential.java => credential/AccessTokenCredential.java} (63%) rename server/src/main/java/com/linecorp/centraldogma/server/internal/{mirror/credential/MirrorCredentialUtil.java => credential/CredentialUtil.java} (89%) rename server/src/main/java/com/linecorp/centraldogma/server/internal/{mirror/credential/NoneMirrorCredential.java => credential/NoneCredential.java} (56%) rename server/src/main/java/com/linecorp/centraldogma/server/internal/{mirror/credential/PasswordMirrorCredential.java => credential/PasswordCredential.java} (65%) rename server/src/main/java/com/linecorp/centraldogma/server/internal/{mirror/credential/PublicKeyMirrorCredential.java => credential/PublicKeyCredential.java} (73%) create mode 100644 server/src/main/java/com/linecorp/centraldogma/server/internal/credential/package-info.java delete mode 100644 server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsService.java delete mode 100644 server/src/main/java/com/linecorp/centraldogma/server/mirror/MirrorCredential.java rename server/src/test/java/com/linecorp/centraldogma/server/internal/{mirror/credential/AccessTokenMirrorCredentialTest.java => credential/AccessTokenCredentialTest.java} (51%) rename server/src/test/java/com/linecorp/centraldogma/server/internal/{mirror/credential/NoneMirrorCredentialTest.java => credential/NoneCredentialTest.java} (50%) rename server/src/test/java/com/linecorp/centraldogma/server/internal/{mirror/credential/PasswordMirrorCredentialTest.java => credential/PasswordCredentialTest.java} (52%) rename server/src/test/java/com/linecorp/centraldogma/server/internal/{mirror/credential/PublicKeyMirrorCredentialTest.java => credential/PublicKeyCredentialTest.java} (61%) delete mode 100644 server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/MirrorCredentialTest.java diff --git a/it/mirror/src/test/java/com/linecorp/centraldogma/it/mirror/git/ForceRefUpdateTest.java b/it/mirror/src/test/java/com/linecorp/centraldogma/it/mirror/git/ForceRefUpdateTest.java index 7d6db768de..9d473afa5d 100644 --- a/it/mirror/src/test/java/com/linecorp/centraldogma/it/mirror/git/ForceRefUpdateTest.java +++ b/it/mirror/src/test/java/com/linecorp/centraldogma/it/mirror/git/ForceRefUpdateTest.java @@ -205,7 +205,6 @@ private void pushCredentials(String pubKey, String privKey) { '{' + " \"id\": \"public-key-id\"," + " \"type\": \"public_key\"," + - " \"hostnamePatterns\": [ \"^.*$\" ]," + " \"username\": \"" + "git" + "\"," + " \"publicKey\": \"" + pubKey + "\"," + " \"privateKey\": \"" + privKey + '"' + @@ -225,7 +224,8 @@ private void pushMirror(String gitUri, MirrorDirection mirrorDirection) { " \"localRepo\": \"" + REPO_FOO + "\"," + " \"localPath\": \"/\"," + " \"remoteUri\": \"" + gitUri + "\"," + - " \"schedule\": \"0 0 0 1 1 ? 2099\"" + + " \"schedule\": \"0 0 0 1 1 ? 2099\"," + + " \"credentialId\": \"public-key-id\"" + '}')) .push().join(); } diff --git a/it/mirror/src/test/java/com/linecorp/centraldogma/it/mirror/git/GitMirrorAuthTest.java b/it/mirror/src/test/java/com/linecorp/centraldogma/it/mirror/git/GitMirrorAuthTest.java index a54bf408a6..f10880bf86 100644 --- a/it/mirror/src/test/java/com/linecorp/centraldogma/it/mirror/git/GitMirrorAuthTest.java +++ b/it/mirror/src/test/java/com/linecorp/centraldogma/it/mirror/git/GitMirrorAuthTest.java @@ -118,7 +118,8 @@ void auth(String projName, String gitUri, JsonNode credential) { " \"direction\": \"REMOTE_TO_LOCAL\"," + " \"localRepo\": \"main\"," + " \"localPath\": \"/\"," + - " \"remoteUri\": \"" + gitUri + '"' + + " \"remoteUri\": \"" + gitUri + "\"," + + " \"credentialId\": \"" + credentialId + '"' + '}')) .push().join(); @@ -138,7 +139,6 @@ private static Collection arguments() throws Exception { " \"id\": \"password-id\"," + " \"enabled\": true," + " \"type\": \"password\"," + - " \"hostnamePatterns\": [ \"^.*$\" ]," + " \"username\": \"" + GITHUB_USERNAME + "\"," + " \"password\": \"" + Jackson.escapeText(GITHUB_PASSWORD) + '"' + '}'))); @@ -153,7 +153,6 @@ private static Collection arguments() throws Exception { " \"id\": \"access-token-id\"," + " \"enabled\": true," + " \"type\": \"access_token\"," + - " \"hostnamePatterns\": [ \"^.*$\" ]," + " \"accessToken\": \"" + Jackson.escapeText(GITHUB_ACCESS_TOKEN) + '"' + '}'))); } @@ -212,7 +211,6 @@ private static void sshAuth(Builder builder, String privateKeyFile, S " \"id\": \"" + privateKeyFile + "\"," + " \"enabled\": true," + " \"type\": \"public_key\"," + - " \"hostnamePatterns\": [ \"^.*$\" ]," + " \"username\": \"git\"," + " \"publicKey\": \"" + Jackson.escapeText(publicKey) + "\"," + " \"privateKey\": \"" + Jackson.escapeText(privateKey) + "\"," + diff --git a/server-mirror-git/src/main/java/com/linecorp/centraldogma/server/internal/mirror/AbstractGitMirror.java b/server-mirror-git/src/main/java/com/linecorp/centraldogma/server/internal/mirror/AbstractGitMirror.java index d64d72f0cf..19b84e04b8 100644 --- a/server-mirror-git/src/main/java/com/linecorp/centraldogma/server/internal/mirror/AbstractGitMirror.java +++ b/server-mirror-git/src/main/java/com/linecorp/centraldogma/server/internal/mirror/AbstractGitMirror.java @@ -86,7 +86,7 @@ import com.linecorp.centraldogma.server.MirrorException; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.command.CommandExecutor; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; import com.linecorp.centraldogma.server.mirror.MirrorDirection; import com.linecorp.centraldogma.server.storage.StorageException; import com.linecorp.centraldogma.server.storage.repository.Repository; @@ -116,7 +116,7 @@ abstract class AbstractGitMirror extends AbstractMirror { private IgnoreNode ignoreNode; AbstractGitMirror(String id, boolean enabled, Cron schedule, MirrorDirection direction, - MirrorCredential credential, Repository localRepo, String localPath, + Credential credential, Repository localRepo, String localPath, URI remoteRepoUri, String remotePath, String remoteBranch, @Nullable String gitignore) { super(id, enabled, schedule, direction, credential, localRepo, localPath, remoteRepoUri, remotePath, diff --git a/server-mirror-git/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultGitMirror.java b/server-mirror-git/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultGitMirror.java index 9e171b5bae..069fe1375d 100644 --- a/server-mirror-git/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultGitMirror.java +++ b/server-mirror-git/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultGitMirror.java @@ -32,9 +32,9 @@ import com.cronutils.model.Cron; import com.linecorp.centraldogma.server.command.CommandExecutor; -import com.linecorp.centraldogma.server.internal.mirror.credential.AccessTokenMirrorCredential; -import com.linecorp.centraldogma.server.internal.mirror.credential.PasswordMirrorCredential; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; +import com.linecorp.centraldogma.server.internal.credential.AccessTokenCredential; +import com.linecorp.centraldogma.server.internal.credential.PasswordCredential; import com.linecorp.centraldogma.server.mirror.MirrorDirection; import com.linecorp.centraldogma.server.storage.repository.Repository; @@ -43,7 +43,7 @@ final class DefaultGitMirror extends AbstractGitMirror { private static final Consumer> NOOP_CONFIGURATOR = command -> {}; DefaultGitMirror(String id, boolean enabled, Cron schedule, MirrorDirection direction, - MirrorCredential credential, Repository localRepo, String localPath, + Credential credential, Repository localRepo, String localPath, URI remoteRepoUri, String remotePath, String remoteBranch, @Nullable String gitignore) { super(id, enabled, schedule, direction, credential, localRepo, localPath, remoteRepoUri, remotePath, @@ -58,17 +58,17 @@ protected void mirrorLocalToRemote(File workDir, int maxNumFiles, long maxNumByt } private Consumer> transportCommandConfigurator() { - final MirrorCredential c = credential(); + final Credential c = credential(); switch (remoteRepoUri().getScheme()) { case SCHEME_GIT_HTTP: case SCHEME_GIT_HTTPS: - if (c instanceof PasswordMirrorCredential) { - final PasswordMirrorCredential cred = (PasswordMirrorCredential) c; + if (c instanceof PasswordCredential) { + final PasswordCredential cred = (PasswordCredential) c; return command -> command.setCredentialsProvider( new UsernamePasswordCredentialsProvider(cred.username(), cred.password())); } - if (c instanceof AccessTokenMirrorCredential) { - final AccessTokenMirrorCredential cred = (AccessTokenMirrorCredential) c; + if (c instanceof AccessTokenCredential) { + final AccessTokenCredential cred = (AccessTokenCredential) c; return command -> command.setCredentialsProvider( new UsernamePasswordCredentialsProvider("token", cred.accessToken())); } diff --git a/server-mirror-git/src/main/java/com/linecorp/centraldogma/server/internal/mirror/SshGitMirror.java b/server-mirror-git/src/main/java/com/linecorp/centraldogma/server/internal/mirror/SshGitMirror.java index ded2eb0ede..690fd51f6b 100644 --- a/server-mirror-git/src/main/java/com/linecorp/centraldogma/server/internal/mirror/SshGitMirror.java +++ b/server-mirror-git/src/main/java/com/linecorp/centraldogma/server/internal/mirror/SshGitMirror.java @@ -16,7 +16,7 @@ package com.linecorp.centraldogma.server.internal.mirror; -import static com.linecorp.centraldogma.server.internal.mirror.credential.PublicKeyMirrorCredential.publicKeyPreview; +import static com.linecorp.centraldogma.server.internal.credential.PublicKeyCredential.publicKeyPreview; import java.io.File; import java.io.IOException; @@ -57,9 +57,9 @@ import com.linecorp.centraldogma.server.MirrorException; import com.linecorp.centraldogma.server.command.CommandExecutor; -import com.linecorp.centraldogma.server.internal.mirror.credential.PasswordMirrorCredential; -import com.linecorp.centraldogma.server.internal.mirror.credential.PublicKeyMirrorCredential; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; +import com.linecorp.centraldogma.server.internal.credential.PasswordCredential; +import com.linecorp.centraldogma.server.internal.credential.PublicKeyCredential; import com.linecorp.centraldogma.server.mirror.MirrorDirection; import com.linecorp.centraldogma.server.storage.repository.Repository; @@ -80,7 +80,7 @@ final class SshGitMirror extends AbstractGitMirror { private static final BouncyCastleRandom bounceCastleRandom = new BouncyCastleRandom(); SshGitMirror(String id, boolean enabled, Cron schedule, MirrorDirection direction, - MirrorCredential credential, Repository localRepo, String localPath, + Credential credential, Repository localRepo, String localPath, URI remoteRepoUri, String remotePath, String remoteBranch, @Nullable String gitignore) { super(id, enabled, schedule, direction, credential, localRepo, localPath, remoteRepoUri, remotePath, @@ -118,10 +118,10 @@ protected void mirrorRemoteToLocal(File workDir, CommandExecutor executor, private URIish remoteUri() throws URISyntaxException { // Requires the username to be included in the URI. final String username; - if (credential() instanceof PasswordMirrorCredential) { - username = ((PasswordMirrorCredential) credential()).username(); - } else if (credential() instanceof PublicKeyMirrorCredential) { - username = ((PublicKeyMirrorCredential) credential()).username(); + if (credential() instanceof PasswordCredential) { + username = ((PasswordCredential) credential()).username(); + } else if (credential() instanceof PublicKeyCredential) { + username = ((PublicKeyCredential) credential()).username(); } else { username = null; } @@ -181,11 +181,11 @@ static ClientSession createSession(SshClient sshClient, URIish uri) { } private void configureCredential(SshClient client) { - final MirrorCredential c = credential(); - if (c instanceof PasswordMirrorCredential) { - client.setFilePasswordProvider(passwordProvider(((PasswordMirrorCredential) c).password())); - } else if (c instanceof PublicKeyMirrorCredential) { - final PublicKeyMirrorCredential cred = (PublicKeyMirrorCredential) credential(); + final Credential c = credential(); + if (c instanceof PasswordCredential) { + client.setFilePasswordProvider(passwordProvider(((PasswordCredential) c).password())); + } else if (c instanceof PublicKeyCredential) { + final PublicKeyCredential cred = (PublicKeyCredential) credential(); final Collection keyPairs; try { keyPairs = keyPairResourceParser.loadKeyPairs(null, NamedResource.ofName(cred.username()), diff --git a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMetaRepositoryWithMirrorTest.java b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMetaRepositoryWithMirrorTest.java index 3fb966ee32..3dcd58cbc7 100644 --- a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMetaRepositoryWithMirrorTest.java +++ b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMetaRepositoryWithMirrorTest.java @@ -24,7 +24,6 @@ import java.util.Comparator; import java.util.List; import java.util.concurrent.CompletionException; -import java.util.regex.Pattern; import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeAll; @@ -45,13 +44,11 @@ import com.linecorp.centraldogma.common.Revision; import com.linecorp.centraldogma.internal.api.v1.MirrorDto; import com.linecorp.centraldogma.server.command.Command; -import com.linecorp.centraldogma.server.command.CommandExecutor; import com.linecorp.centraldogma.server.command.CommitResult; -import com.linecorp.centraldogma.server.internal.mirror.credential.NoneMirrorCredential; -import com.linecorp.centraldogma.server.internal.mirror.credential.PasswordMirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; +import com.linecorp.centraldogma.server.internal.credential.PasswordCredential; import com.linecorp.centraldogma.server.internal.storage.repository.RepositoryMetadataException; import com.linecorp.centraldogma.server.mirror.Mirror; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; import com.linecorp.centraldogma.server.mirror.MirrorDirection; import com.linecorp.centraldogma.server.storage.project.Project; import com.linecorp.centraldogma.server.storage.project.ProjectManager; @@ -67,7 +64,6 @@ class DefaultMetaRepositoryWithMirrorTest { '{' + " \"id\": \"alice\"," + " \"type\": \"password\"," + - " \"hostnamePatterns\": [ \"^foo\\\\.com$\" ]," + " \"username\": \"alice\"," + " \"password\": \"secret_a\"" + '}'), @@ -76,18 +72,15 @@ class DefaultMetaRepositoryWithMirrorTest { '{' + " \"id\": \"bob\"," + " \"type\": \"password\"," + - " \"hostnamePatterns\": [ \"^.*\\\\.com$\" ]," + " \"username\": \"bob\"," + " \"password\": \"secret_b\"" + '}')); - private static final List CREDENTIALS = ImmutableList.of( - new PasswordMirrorCredential( - "alice", true, ImmutableList.of(Pattern.compile("^foo\\.com$")), - "alice", "secret_a"), - new PasswordMirrorCredential( - "bob", true, ImmutableList.of(Pattern.compile("^.*\\.com$")), - "bob", "secret_b")); + private static final List CREDENTIALS = ImmutableList.of( + new PasswordCredential( + "alice", true, "alice", "secret_a"), + new PasswordCredential( + "bob", true, "bob", "secret_b")); private static final CronParser cronParser = new CronParser( CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ)); @@ -169,43 +162,18 @@ void testMirror(boolean useRawApi) { " \"schedule\": \"0 */10 * * * ?\"," + " \"direction\": \"REMOTE_TO_LOCAL\"," + " \"localRepo\": \"bar\"," + - " \"remoteUri\": \"git+ssh://bar.com/bar.git/some-path\"," + + " \"remoteUri\": \"git+ssh://bar.com/bar.git/some-path#develop\"," + " \"credentialId\": \"bob\"" + - '}'), - Change.ofJsonUpsert( - "/mirrors/qux.json", - '{' + - " \"id\": \"qux\"," + - " \"direction\": \"LOCAL_TO_REMOTE\"," + - " \"localRepo\": \"qux\"," + - " \"remoteUri\": \"git+ssh://qux.net/qux.git#develop\"" + - // No credential will be chosen. - '}'), - Change.ofJsonUpsert( - "/mirrors/foo-bar.json", - '{' + - " \"id\": \"foo-bar\"," + - " \"enabled\": false," + // Disabled - " \"direction\": \"LOCAL_TO_REMOTE\"," + - " \"localRepo\": \"foo\"," + - " \"localPath\": \"/mirrors/bar\"," + - " \"remoteUri\": \"git+ssh://bar.com/bar.git\"" + - // credentialId 'bob' will be chosen. '}')); metaRepo.commit(Revision.HEAD, 0L, Author.SYSTEM, "", mirrors).join(); metaRepo.commit(Revision.HEAD, 0L, Author.SYSTEM, "", UPSERT_RAW_CREDENTIALS).join(); } else { - final CommandExecutor commandExecutor = pmExtension.executor(); final List mirrors = ImmutableList.of( new MirrorDto("foo", true, project.name(), DEFAULT_SCHEDULE, "LOCAL_TO_REMOTE", "foo", "/mirrors/foo", "git+ssh", "foo.com/foo.git", "", "", null, "alice"), new MirrorDto("bar", true, project.name(), "0 */10 * * * ?", "REMOTE_TO_LOCAL", "bar", - "", "git+ssh", "bar.com/bar.git", "/some-path", "", null, "bob"), - new MirrorDto("qux", true, project.name(), DEFAULT_SCHEDULE, "LOCAL_TO_REMOTE", "qux", - "", "git+ssh", "qux.net/qux.git", "", "develop", null, ""), - new MirrorDto("foo-bar", false, project.name(), DEFAULT_SCHEDULE, "LOCAL_TO_REMOTE", "foo", - "/mirrors/bar", "git+ssh", "bar.com/bar.git", "", "", null, "bob")); - for (MirrorCredential credential : CREDENTIALS) { + "", "git+ssh", "bar.com/bar.git", "/some-path", "develop", null, "bob")); + for (Credential credential : CREDENTIALS) { final Command command = metaRepo.createPushCommand(credential, Author.SYSTEM, false).join(); pmExtension.executor().execute(command).join(); @@ -222,52 +190,42 @@ void testMirror(boolean useRawApi) { project.repos().create("foo", Author.SYSTEM); project.repos().create("bar", Author.SYSTEM); - project.repos().create("qux", Author.SYSTEM); final List mirrors = findMirrors(); assertThat(mirrors.stream() .map(m -> m.localRepo().name()) - .collect(Collectors.toList())).containsExactly("bar", "foo", "qux"); + .collect(Collectors.toList())).containsExactly("bar", "foo"); final Mirror foo = mirrors.get(1); final Mirror bar = mirrors.get(0); - final Mirror qux = mirrors.get(2); assertThat(foo.direction()).isEqualTo(MirrorDirection.LOCAL_TO_REMOTE); assertThat(bar.direction()).isEqualTo(MirrorDirection.REMOTE_TO_LOCAL); - assertThat(qux.direction()).isEqualTo(MirrorDirection.LOCAL_TO_REMOTE); assertThat(foo.schedule().equivalent(cronParser.parse("0 * * * * ?"))).isTrue(); assertThat(bar.schedule().equivalent(cronParser.parse("0 */10 * * * ?"))).isTrue(); - assertThat(qux.schedule().equivalent(cronParser.parse("0 * * * * ?"))).isTrue(); assertThat(foo.localPath()).isEqualTo("/mirrors/foo/"); assertThat(bar.localPath()).isEqualTo("/"); - assertThat(qux.localPath()).isEqualTo("/"); assertThat(foo.remoteRepoUri().toString()).isEqualTo("git+ssh://foo.com/foo.git"); assertThat(bar.remoteRepoUri().toString()).isEqualTo("git+ssh://bar.com/bar.git"); - assertThat(qux.remoteRepoUri().toString()).isEqualTo("git+ssh://qux.net/qux.git"); assertThat(foo.remotePath()).isEqualTo("/"); assertThat(bar.remotePath()).isEqualTo("/some-path/"); - assertThat(qux.remotePath()).isEqualTo("/"); assertThat(foo.remoteBranch()).isEmpty(); - assertThat(bar.remoteBranch()).isEmpty(); - assertThat(qux.remoteBranch()).isEqualTo("develop"); + assertThat(bar.remoteBranch()).isEqualTo("develop"); // Ensure the credentials are loaded correctly. //// Should be matched by 'alice' credential. - assertThat(foo.credential()).isInstanceOf(PasswordMirrorCredential.class); + assertThat(foo.credential()).isInstanceOf(PasswordCredential.class); //// Should be matched by 'bob' credential. - assertThat(bar.credential()).isInstanceOf(PasswordMirrorCredential.class); - //// Should be matched by no credential. - assertThat(qux.credential()).isInstanceOf(NoneMirrorCredential.class); + assertThat(bar.credential()).isInstanceOf(PasswordCredential.class); - final PasswordMirrorCredential fooCredential = (PasswordMirrorCredential) foo.credential(); - final PasswordMirrorCredential barCredential = (PasswordMirrorCredential) bar.credential(); + final PasswordCredential fooCredential = (PasswordCredential) foo.credential(); + final PasswordCredential barCredential = (PasswordCredential) bar.credential(); assertThat(fooCredential.username()).isEqualTo("alice"); assertThat(fooCredential.password()).isEqualTo("secret_a"); @@ -302,8 +260,8 @@ void testMirrorWithCredentialId() { final Mirror m = mirrors.get(0); assertThat(m.localRepo().name()).isEqualTo("qux"); - assertThat(m.credential()).isInstanceOf(PasswordMirrorCredential.class); - assertThat(((PasswordMirrorCredential) m.credential()).username()).isEqualTo("alice"); + assertThat(m.credential()).isInstanceOf(PasswordCredential.class); + assertThat(((PasswordCredential) m.credential()).username()).isEqualTo("alice"); } private List findMirrors() { diff --git a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringServiceTest.java b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringServiceTest.java index 8bf2dce232..8751a2704e 100644 --- a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringServiceTest.java +++ b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringServiceTest.java @@ -17,10 +17,6 @@ package com.linecorp.centraldogma.server.internal.mirror; import static org.awaitility.Awaitility.await; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -39,12 +35,9 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.linecorp.armeria.common.util.UnmodifiableFuture; -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.credential.Credential; import com.linecorp.centraldogma.server.mirror.Mirror; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; import com.linecorp.centraldogma.server.mirror.MirrorDirection; import com.linecorp.centraldogma.server.storage.project.Project; import com.linecorp.centraldogma.server.storage.project.ProjectManager; @@ -71,21 +64,14 @@ void mirroringTaskShouldNeverBeRejected() { final RepositoryManager rm = mock(RepositoryManager.class); final Repository r = mock(Repository.class); when(pm.list()).thenReturn(ImmutableMap.of("foo", p)); - when(pm.get(anyString())).thenReturn(p); when(p.name()).thenReturn("foo"); - when(p.repos()).thenReturn(rm); - when(rm.get(anyString())).thenReturn(r); when(p.metaRepo()).thenReturn(mr); - when(mr.find(eq(Revision.HEAD), anyString(), anyMap())) - .thenReturn(UnmodifiableFuture.completedFuture(ImmutableMap.of())); - when(r.find(eq(Revision.HEAD), anyString())) - .thenReturn(UnmodifiableFuture.completedFuture(ImmutableMap.of())); when(r.parent()).thenReturn(p); when(r.name()).thenReturn("bar"); final Mirror mirror = new AbstractMirror("my-mirror-1", true, EVERY_SECOND, MirrorDirection.REMOTE_TO_LOCAL, - MirrorCredential.FALLBACK, r, "/", + Credential.FALLBACK, r, "/", URI.create("unused://uri"), "/", "", null) { @Override protected void mirrorLocalToRemote(File workDir, int maxNumFiles, long maxNumBytes) {} @@ -104,7 +90,6 @@ protected void mirrorRemoteToLocal(File workDir, CommandExecutor executor, final DefaultMirroringService service = new DefaultMirroringService( temporaryFolder, pm, new SimpleMeterRegistry(), 1, 1, 1); final CommandExecutor executor = mock(CommandExecutor.class); - when(executor.execute(any(Command.class))).thenReturn(UnmodifiableFuture.completedFuture(null)); service.start(executor); try { diff --git a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringAndCredentialServiceV1Test.java b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringAndCredentialServiceV1Test.java index 7785942b56..ef26a2b0d9 100644 --- a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringAndCredentialServiceV1Test.java +++ b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringAndCredentialServiceV1Test.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Map; -import java.util.regex.Pattern; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -46,11 +45,11 @@ import com.linecorp.centraldogma.internal.api.v1.MirrorDto; import com.linecorp.centraldogma.internal.api.v1.PushResultDto; import com.linecorp.centraldogma.server.CentralDogmaBuilder; -import com.linecorp.centraldogma.server.internal.mirror.credential.AccessTokenMirrorCredential; -import com.linecorp.centraldogma.server.internal.mirror.credential.NoneMirrorCredential; -import com.linecorp.centraldogma.server.internal.mirror.credential.PasswordMirrorCredential; -import com.linecorp.centraldogma.server.internal.mirror.credential.PublicKeyMirrorCredential; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; +import com.linecorp.centraldogma.server.internal.credential.AccessTokenCredential; +import com.linecorp.centraldogma.server.internal.credential.NoneCredential; +import com.linecorp.centraldogma.server.internal.credential.PasswordCredential; +import com.linecorp.centraldogma.server.internal.credential.PublicKeyCredential; import com.linecorp.centraldogma.testing.internal.auth.TestAuthProviderFactory; import com.linecorp.centraldogma.testing.junit.CentralDogmaExtension; @@ -75,8 +74,6 @@ protected void scaffold(CentralDogma client) { } }; - private final List hostnamePatterns = ImmutableList.of("github.com"); - private BlockingWebClient adminClient; private BlockingWebClient userClient; @@ -145,15 +142,14 @@ private void setUpRole() { private void createAndReadCredential() { final List> credentials = ImmutableList.of( ImmutableMap.of("type", "password", "id", "password-credential", - "hostnamePatterns", hostnamePatterns, "username", "username-0", "password", "password-0"), - ImmutableMap.of("type", "access_token", "id", "access-token-credential", "hostnamePatterns", - hostnamePatterns, "accessToken", "secret-token-abc-1"), + ImmutableMap.of("type", "access_token", "id", "access-token-credential", + "accessToken", "secret-token-abc-1"), ImmutableMap.of("type", "public_key", "id", "public-key-credential", - "hostnamePatterns", hostnamePatterns, "username", "username-2", + "username", "username-2", "publicKey", "public-key-2", "privateKey", "private-key-2", "passphrase", "password-0"), - ImmutableMap.of("type", "none", "id", "non-credential", "hostnamePatterns", hostnamePatterns)); + ImmutableMap.of("type", "none", "id", "non-credential")); for (int i = 0; i < credentials.size(); i++) { final Map credential = credentials.get(i); @@ -171,21 +167,19 @@ private void createAndReadCredential() { for (BlockingWebClient client : ImmutableList.of(adminClient, userClient)) { final boolean isAdmin = client == adminClient; - final ResponseEntity fetchResponse = + final ResponseEntity fetchResponse = client.prepare() .get("/api/v1/projects/{proj}/credentials/{id}") .pathParam("proj", FOO_PROJ) .pathParam("id", credentialId) .responseTimeoutMillis(0) - .asJson(MirrorCredential.class) + .asJson(Credential.class) .execute(); - final MirrorCredential credentialDto = fetchResponse.content(); + final Credential credentialDto = fetchResponse.content(); assertThat(credentialDto.id()).isEqualTo(credentialId); - assertThat(credentialDto.hostnamePatterns().stream().map(Pattern::pattern)).isEqualTo( - credential.get("hostnamePatterns")); final String credentialType = (String) credential.get("type"); if ("password".equals(credentialType)) { - final PasswordMirrorCredential actual = (PasswordMirrorCredential) credentialDto; + final PasswordCredential actual = (PasswordCredential) credentialDto; assertThat(actual.username()).isEqualTo(credential.get("username")); if (isAdmin) { assertThat(actual.password()).isEqualTo(credential.get("password")); @@ -193,14 +187,14 @@ private void createAndReadCredential() { assertThat(actual.password()).isEqualTo("****"); } } else if ("access_token".equals(credentialType)) { - final AccessTokenMirrorCredential actual = (AccessTokenMirrorCredential) credentialDto; + final AccessTokenCredential actual = (AccessTokenCredential) credentialDto; if (isAdmin) { assertThat(actual.accessToken()).isEqualTo(credential.get("accessToken")); } else { assertThat(actual.accessToken()).isEqualTo("****"); } } else if ("public_key".equals(credentialType)) { - final PublicKeyMirrorCredential actual = (PublicKeyMirrorCredential) credentialDto; + final PublicKeyCredential actual = (PublicKeyCredential) credentialDto; assertThat(actual.username()).isEqualTo(credential.get("username")); assertThat(actual.publicKey()).isEqualTo(credential.get("publicKey")); if (isAdmin) { @@ -211,7 +205,7 @@ private void createAndReadCredential() { assertThat(actual.rawPassphrase()).isEqualTo("****"); } } else if ("none".equals(credentialType)) { - assertThat(credentialDto).isInstanceOf(NoneMirrorCredential.class); + assertThat(credentialDto).isInstanceOf(NoneCredential.class); } else { throw new AssertionError("Unexpected credential type: " + credential.getClass().getName()); } @@ -220,12 +214,10 @@ private void createAndReadCredential() { } private void updateCredential() { - final List hostnamePatterns = ImmutableList.of("gitlab.com"); final String credentialId = "public-key-credential"; final Map credential = ImmutableMap.of("type", "public_key", "id", credentialId, - "hostnamePatterns", hostnamePatterns, "username", "updated-username-2", "publicKey", "updated-public-key-2", "privateKey", "updated-private-key-2", @@ -242,17 +234,15 @@ private void updateCredential() { for (BlockingWebClient client : ImmutableList.of(adminClient, userClient)) { final boolean isAdmin = client == adminClient; - final ResponseEntity fetchResponse = + final ResponseEntity fetchResponse = client.prepare() .get("/api/v1/projects/{proj}/credentials/{id}") .pathParam("proj", FOO_PROJ) .pathParam("id", credentialId) - .asJson(MirrorCredential.class) + .asJson(Credential.class) .execute(); - final PublicKeyMirrorCredential actual = (PublicKeyMirrorCredential) fetchResponse.content(); + final PublicKeyCredential actual = (PublicKeyCredential) fetchResponse.content(); assertThat(actual.id()).isEqualTo((String) credential.get("id")); - assertThat(actual.hostnamePatterns().stream().map(Pattern::pattern)) - .containsExactlyElementsOf(hostnamePatterns); assertThat(actual.username()).isEqualTo(credential.get("username")); assertThat(actual.publicKey()).isEqualTo(credential.get("publicKey")); if (isAdmin) { diff --git a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringTestUtils.java b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringTestUtils.java index 486c055927..2df3911b32 100644 --- a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringTestUtils.java +++ b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringTestUtils.java @@ -27,9 +27,9 @@ import com.cronutils.model.definition.CronDefinitionBuilder; import com.cronutils.parser.CronParser; +import com.linecorp.centraldogma.server.credential.Credential; import com.linecorp.centraldogma.server.mirror.Mirror; import com.linecorp.centraldogma.server.mirror.MirrorContext; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; import com.linecorp.centraldogma.server.mirror.MirrorDirection; import com.linecorp.centraldogma.server.storage.project.Project; import com.linecorp.centraldogma.server.storage.repository.Repository; @@ -55,7 +55,7 @@ static T newMirror(String remoteUri, Class mirrorType, static T newMirror(String remoteUri, Cron schedule, Repository repository, Class mirrorType) { - final MirrorCredential credential = mock(MirrorCredential.class); + final Credential credential = mock(Credential.class); final Mirror mirror = new GitMirrorProvider().newMirror( new MirrorContext("mirror-id", true, schedule, MirrorDirection.LOCAL_TO_REMOTE, @@ -73,7 +73,7 @@ static T newMirror(String remoteUri, Cron schedule, } static void assertMirrorNull(String remoteUri) { - final MirrorCredential credential = mock(MirrorCredential.class); + final Credential credential = mock(Credential.class); final Mirror mirror = new GitMirrorProvider().newMirror( new MirrorContext("mirror-id", true, EVERY_MINUTE, MirrorDirection.LOCAL_TO_REMOTE, credential, mock(Repository.class), "/", URI.create(remoteUri), null)); diff --git a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsServiceTest.java b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsServiceTest.java deleted file mode 100644 index 10af800ff3..0000000000 --- a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsServiceTest.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2023 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.mirror; - -import static com.linecorp.centraldogma.server.internal.mirror.RemovingHostnamePatternsService.REMOVING_HOSTNAME_JOB_LOG; -import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson; -import static org.assertj.core.api.Assertions.assertThat; - -import java.time.Instant; -import java.util.Map; - -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import com.linecorp.centraldogma.common.Author; -import com.linecorp.centraldogma.common.Change; -import com.linecorp.centraldogma.common.Entry; -import com.linecorp.centraldogma.common.Revision; -import com.linecorp.centraldogma.internal.Jackson; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; -import com.linecorp.centraldogma.server.storage.project.InternalProjectInitializer; -import com.linecorp.centraldogma.server.storage.project.Project; -import com.linecorp.centraldogma.server.storage.project.ProjectManager; -import com.linecorp.centraldogma.server.storage.repository.Repository; -import com.linecorp.centraldogma.testing.internal.ProjectManagerExtension; - -class RemovingHostnamePatternsServiceTest { - - private static final String TEST_PROJ = "fooProj"; - - // The real key pair generated using: - // - // ssh-keygen -t rsa -b 768 -N sesame - // - private static final String PUBLIC_KEY = - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCmkW9HjZE5q0EM06MUWXYFTNTi" + - "KkfYD/pH2GwJw6yi20Gi0TzjJ6YBLueU48vxkwWmw6sTOEuBxtzefTxs4kQuatev" + - "uXn7tWX9fhSIAEp+zdyQY7InyCqfHFwRwswemCM= trustin@localhost"; - - private static final String PRIVATE_KEY = - "-----BEGIN RSA PRIVATE KEY-----\n" + - "Proc-Type: 4,ENCRYPTED\n" + - "DEK-Info: AES-128-CBC,C35856D3C524AA2FD32D878F4409B97E\n" + - '\n' + - "X3HRmqg2bUqfqxkWjHsr4KeN1UyN5QbypGd7Jov/nDSyiIWe4zPJD/3oji0xOK+h\n" + - "Lxq+c8DDu7ItpC6dwe5WexcyIKGF7WqlkqeEhVM3VOkQtbpbdnb7bA8mLja2unMW\n" + - "bFLgQiTF1Y8SlG4Q70N0iY638AeIG/ZUU14LSBFSQDkrtZ+f7bhIhVDDavANMF+B\n" + - "+eiQ4u3W59Cpbm83AfzqotrPXuBusfyBjH7Wfj0XRvOGRjTQT0jXIWWpLqnIy5ms\n" + - "HNGlMoJElUQuPpbQUiFvmqiMj40r9V/Wx/8+GciADOs4FsTvGFKIcouWDhjIWg0b\n" + - "DKFqV/Hw/AjkAafkySxxmk1+EIen4XfkghtlWLwT2Xp4RtJXYiVC9q9483jDv3+Z\n" + - "iTa5rjFuro4WJkDZp6/N6l+/HcbBXL8L6y66xsJwP+6GLuDLpXjGZrneV1ip2dtG\n" + - "BQzvlgCOr9pTAa4Ar7MC3E2C6+qPhOwO4B/f1cigwRaEB92MHz5gJsITU3xVfTjV\n" + - "yf4THKipBDxqnET6F2FMZJFolVzFEXDaCFNC1TjBqS0+A8KaMcO/lXjJxtfvV37l\n" + - "zmB/ey0dZ8WBCazCp9OX3dYgNkVR1yYNlJWOGJS8Cwc=\n" + - "-----END RSA PRIVATE KEY-----"; - - private static final String PASSPHRASE = "sesame"; - - private static final String PUBLIC_KEY_CREDENTIAL_FORMAT = - '{' + - " \"id\": \"%s\"," + - " \"type\": \"public_key\"," + - " %s" + - " \"username\": \"trustin\"," + - " \"publicKey\": \"" + Jackson.escapeText(PUBLIC_KEY) + "\"," + - " \"privateKey\": \"" + Jackson.escapeText(PRIVATE_KEY) + "\"," + - " \"passphrase\": \"" + Jackson.escapeText(PASSPHRASE) + '"' + - '}'; - - private static final String PASSWORD_CREDENTIAL_FORMAT = - '{' + - " \"id\": \"%s\"," + - " \"type\": \"password\"," + - " %s" + - " \"username\": \"trustin\"," + - " \"password\": \"sesame\"" + - '}'; - - // A credential with duplicate ID - static final String ACCESS_TOKEN_CREDENTIAL_FORMAT = - '{' + - " \"id\": \"%s\"," + - " \"type\": \"access_token\"," + - " %s" + - " \"accessToken\": \"sesame\"" + - '}'; - - private static final String HOSTNAME_PATTERNS = - " \"hostnamePatterns\": [" + - " \"^git\\\\.foo\\\\.com$\"" + - " ],"; - - @RegisterExtension - static ProjectManagerExtension projectManagerExtension = new ProjectManagerExtension() { - @Override - protected boolean runForEachTest() { - return true; - } - - @Override - protected void afterExecutorStarted() { - final ProjectManager projectManager = projectManagerExtension.projectManager(); - projectManager.create(TEST_PROJ, Author.SYSTEM); - } - }; - - @CsvSource(value = { - HOSTNAME_PATTERNS, "''", "\"hostnamePatterns\": [],", "\"hostnamePatterns\": null," - }, delimiter = ';') // string contains ',' so change the delimiter. - @ParameterizedTest - void removeHostnamePatterns(String hostnamePatterns) throws Exception { - final ProjectManager projectManager = projectManagerExtension.projectManager(); - final Project project = projectManager.get(TEST_PROJ); - - final String publicKeyCredential = - String.format(PUBLIC_KEY_CREDENTIAL_FORMAT, "credential-1", hostnamePatterns); - final String passwordCredential = - String.format(PASSWORD_CREDENTIAL_FORMAT, "credential-2", hostnamePatterns); - final String accessTokenCredential = - String.format(ACCESS_TOKEN_CREDENTIAL_FORMAT, "credential-3", hostnamePatterns); - final Change change1 = - Change.ofJsonUpsert("/credentials/credential-1.json", publicKeyCredential); - final Change change2 = - Change.ofJsonUpsert("/credentials/credential-2.json", passwordCredential); - final Change change3 = - Change.ofJsonUpsert("/credentials/credential-3.json", accessTokenCredential); - - project.metaRepo().commit(Revision.HEAD, System.currentTimeMillis(), Author.SYSTEM, - "Create credentials.", change1, change2, change3).join(); - final RemovingHostnamePatternsService service = new RemovingHostnamePatternsService( - projectManager, projectManagerExtension.executor()); - service.start(); - - final Map> entries = project.metaRepo() - .find(Revision.HEAD, "/credentials/*.json") - .join(); - - assertThat(entries).hasSize(3); - final Entry entry1 = entries.get("/credentials/credential-1.json"); - assertCredential(entry1, String.format(PUBLIC_KEY_CREDENTIAL_FORMAT, "credential-1", "")); - MirrorCredential mirrorCredential = Jackson.treeToValue(entry1.contentAsJson(), MirrorCredential.class); - assertThat(mirrorCredential.hostnamePatterns()).isEmpty(); - assertThat(mirrorCredential.id()).isEqualTo("credential-1"); - - final Entry entry2 = entries.get("/credentials/credential-2.json"); - assertCredential(entry2, String.format(PASSWORD_CREDENTIAL_FORMAT, "credential-2", "")); - mirrorCredential = Jackson.treeToValue(entry2.contentAsJson(), MirrorCredential.class); - assertThat(mirrorCredential.hostnamePatterns()).isEmpty(); - assertThat(mirrorCredential.id()).isEqualTo("credential-2"); - - final Entry entry3 = entries.get("/credentials/credential-3.json"); - assertCredential(entry3, String.format(ACCESS_TOKEN_CREDENTIAL_FORMAT, "credential-3", "")); - mirrorCredential = Jackson.treeToValue(entry3.contentAsJson(), MirrorCredential.class); - assertThat(mirrorCredential.hostnamePatterns()).isEmpty(); - assertThat(mirrorCredential.id()).isEqualTo("credential-3"); - - // Make sure that the log is written. - final Repository dogmaRepo = projectManager.get(InternalProjectInitializer.INTERNAL_PROJECT_DOGMA) - .repos().get(Project.REPO_DOGMA); - final Map> log = dogmaRepo.find(Revision.HEAD, REMOVING_HOSTNAME_JOB_LOG).join(); - final JsonNode data = log.get(REMOVING_HOSTNAME_JOB_LOG).contentAsJson(); - assertThat(Jackson.readValue(data.get("timestamp").asText(), Instant.class)) - .isBeforeOrEqualTo(Instant.now()); - } - - private static void assertCredential(Entry entry, String credential) throws JsonParseException { - assertThatJson(entry.contentAsJson()).isEqualTo( - ((ObjectNode) Jackson.readTree(credential)).without("hostnamePatterns")); - } -} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/credential/Credential.java b/server/src/main/java/com/linecorp/centraldogma/server/credential/Credential.java new file mode 100644 index 0000000000..3394c6368f --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/credential/Credential.java @@ -0,0 +1,63 @@ +/* + * Copyright 2017 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.credential; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import com.linecorp.centraldogma.server.internal.credential.AccessTokenCredential; +import com.linecorp.centraldogma.server.internal.credential.NoneCredential; +import com.linecorp.centraldogma.server.internal.credential.PasswordCredential; +import com.linecorp.centraldogma.server.internal.credential.PublicKeyCredential; + +/** + * A credential used to access external resources such as Git repositories or the Kubernetes control plane. + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type") +@JsonSubTypes({ + @Type(value = NoneCredential.class, name = "none"), + @Type(value = PasswordCredential.class, name = "password"), + @Type(value = PublicKeyCredential.class, name = "public_key"), + @Type(value = AccessTokenCredential.class, name = "access_token") +}) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public interface Credential { + + Credential FALLBACK = new NoneCredential("", true); + + /** + * Returns the ID of the credential. + */ + @JsonProperty("id") + String id(); + + /** + * Returns whether this {@link Credential} is enabled. + */ + @JsonProperty("enabled") + boolean enabled(); + + /** + * Returns a new {@link Credential} that does not contain any sensitive information. + */ + Credential withoutSecret(); +} diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/package-info.java b/server/src/main/java/com/linecorp/centraldogma/server/credential/package-info.java similarity index 91% rename from server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/package-info.java rename to server/src/main/java/com/linecorp/centraldogma/server/credential/package-info.java index 737dfd1fff..621a1abb9e 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/package-info.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/credential/package-info.java @@ -17,6 +17,6 @@ * Credential classes for mirroring. */ @NonNullByDefault -package com.linecorp.centraldogma.server.internal.mirror.credential; +package com.linecorp.centraldogma.server.credential; import com.linecorp.centraldogma.common.util.NonNullByDefault; diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1.java index f75f08746d..dccc6e01db 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1.java @@ -32,11 +32,11 @@ import com.linecorp.centraldogma.common.Author; import com.linecorp.centraldogma.internal.api.v1.PushResultDto; import com.linecorp.centraldogma.server.command.CommandExecutor; +import com.linecorp.centraldogma.server.credential.Credential; import com.linecorp.centraldogma.server.internal.api.auth.RequiresReadPermission; import com.linecorp.centraldogma.server.internal.api.auth.RequiresWritePermission; import com.linecorp.centraldogma.server.internal.storage.project.ProjectApiManager; import com.linecorp.centraldogma.server.metadata.User; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; import com.linecorp.centraldogma.server.storage.project.Project; import com.linecorp.centraldogma.server.storage.repository.MetaRepository; @@ -60,16 +60,16 @@ public CredentialServiceV1(ProjectApiManager projectApiManager, CommandExecutor */ @RequiresReadPermission(repository = Project.REPO_META) @Get("/projects/{projectName}/credentials") - public CompletableFuture> listCredentials(User loginUser, - @Param String projectName) { - final CompletableFuture> future = metaRepo(projectName).credentials(); + public CompletableFuture> listCredentials(User loginUser, + @Param String projectName) { + final CompletableFuture> future = metaRepo(projectName).credentials(); if (loginUser.isAdmin()) { return future; } return future.thenApply(credentials -> { return credentials .stream() - .map(MirrorCredential::withoutSecret) + .map(Credential::withoutSecret) .collect(toImmutableList()); }); } @@ -81,13 +81,13 @@ public CompletableFuture> listCredentials(User loginUser, */ @RequiresReadPermission(repository = Project.REPO_META) @Get("/projects/{projectName}/credentials/{id}") - public CompletableFuture getCredentialById(User loginUser, - @Param String projectName, @Param String id) { - final CompletableFuture future = metaRepo(projectName).credential(id); + public CompletableFuture getCredentialById(User loginUser, + @Param String projectName, @Param String id) { + final CompletableFuture future = metaRepo(projectName).credential(id); if (loginUser.isAdmin()) { return future; } - return future.thenApply(MirrorCredential::withoutSecret); + return future.thenApply(Credential::withoutSecret); } /** @@ -100,7 +100,7 @@ public CompletableFuture getCredentialById(User loginUser, @ConsumesJson @StatusCode(201) public CompletableFuture createCredential(@Param String projectName, - MirrorCredential credential, Author author) { + Credential credential, Author author) { return createOrUpdate(projectName, credential, author, false); } @@ -114,13 +114,13 @@ public CompletableFuture createCredential(@Param String projectNa @ConsumesJson public CompletableFuture updateCredential(@Param String projectName, @Param String id, - MirrorCredential credential, Author author) { + Credential credential, Author author) { checkArgument(id.equals(credential.id()), "The credential ID (%s) can't be updated", id); return createOrUpdate(projectName, credential, author, true); } private CompletableFuture createOrUpdate(String projectName, - MirrorCredential credential, + Credential credential, Author author, boolean update) { return metaRepo(projectName).createPushCommand(credential, author, update).thenCompose(command -> { return executor().execute(command).thenApply(result -> { diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/AbstractMirrorCredential.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/AbstractCredential.java similarity index 50% rename from server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/AbstractMirrorCredential.java rename to server/src/main/java/com/linecorp/centraldogma/server/internal/credential/AbstractCredential.java index 4e11d08cc9..cdbd946e91 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/AbstractMirrorCredential.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/AbstractCredential.java @@ -14,54 +14,32 @@ * under the License. */ -package com.linecorp.centraldogma.server.internal.mirror.credential; +package com.linecorp.centraldogma.server.internal.credential; import static com.google.common.base.MoreObjects.firstNonNull; -import static com.linecorp.centraldogma.internal.Util.requireNonNullElements; import static java.util.Objects.requireNonNull; -import java.net.URI; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; -public abstract class AbstractMirrorCredential implements MirrorCredential { +abstract class AbstractCredential implements Credential { private final String id; private final boolean enabled; // TODO(ikhoon): Consider changing 'type' to an enum. private final String type; - private final Set hostnamePatterns; - private final Set hostnamePatternStrings; - AbstractMirrorCredential(String id, @Nullable Boolean enabled, String type, - @Nullable Iterable hostnamePatterns) { + AbstractCredential(String id, @Nullable Boolean enabled, String type) { this.id = requireNonNull(id, "id"); this.enabled = firstNonNull(enabled, true); // JsonTypeInfo is ignored when serializing collections. // As a workaround, manually set the type hint to serialize. this.type = requireNonNull(type, "type"); - this.hostnamePatterns = validateHostnamePatterns(hostnamePatterns); - hostnamePatternStrings = this.hostnamePatterns.stream().map(Pattern::pattern) - .collect(Collectors.toSet()); - } - - private static Set validateHostnamePatterns(@Nullable Iterable hostnamePatterns) { - if (hostnamePatterns == null || Iterables.isEmpty(hostnamePatterns)) { - return ImmutableSet.of(); - } - return ImmutableSet.copyOf( - requireNonNullElements(hostnamePatterns, "hostnamePatterns")); } @Override @@ -74,24 +52,11 @@ public final String type() { return type; } - @Override - public final Set hostnamePatterns() { - return hostnamePatterns; - } - @Override public final boolean enabled() { return enabled; } - @Override - public final boolean matches(URI uri) { - requireNonNull(uri, "uri"); - - final String host = uri.getHost(); - return host != null && hostnamePatterns.stream().anyMatch(p -> p.matcher(host).matches()); - } - @Override public boolean equals(Object o) { if (this == o) { @@ -102,25 +67,21 @@ public boolean equals(Object o) { return false; } - final AbstractMirrorCredential that = (AbstractMirrorCredential) o; - return hostnamePatternStrings.equals(that.hostnamePatternStrings); + final AbstractCredential that = (AbstractCredential) o; + return enabled == that.enabled && + id.equals(that.id); } @Override public int hashCode() { - return hostnamePatternStrings.hashCode(); + return id.hashCode() * 31 + Boolean.hashCode(enabled); } @Override public final String toString() { final ToStringHelper helper = MoreObjects.toStringHelper(this); - if (id != null) { - helper.add("id", id); - } - if (!hostnamePatterns.isEmpty()) { - helper.add("hostnamePatterns", hostnamePatterns); - } - + helper.add("id", id); + helper.add("enabled", enabled); addProperties(helper); return helper.toString(); } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/AccessTokenMirrorCredential.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/AccessTokenCredential.java similarity index 63% rename from server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/AccessTokenMirrorCredential.java rename to server/src/main/java/com/linecorp/centraldogma/server/internal/credential/AccessTokenCredential.java index 205dc51bd7..b1eaeb2016 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/AccessTokenMirrorCredential.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/AccessTokenCredential.java @@ -14,12 +14,10 @@ * under the License. */ -package com.linecorp.centraldogma.server.internal.mirror.credential; +package com.linecorp.centraldogma.server.internal.credential; import static com.linecorp.centraldogma.server.CentralDogmaConfig.convertValue; -import static com.linecorp.centraldogma.server.internal.mirror.credential.MirrorCredentialUtil.requireNonEmpty; - -import java.util.regex.Pattern; +import static com.linecorp.centraldogma.server.internal.credential.CredentialUtil.requireNonEmpty; import javax.annotation.Nullable; @@ -28,26 +26,21 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.common.base.MoreObjects.ToStringHelper; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; -public final class AccessTokenMirrorCredential extends AbstractMirrorCredential { +public final class AccessTokenCredential extends AbstractCredential { - private static final Logger logger = LoggerFactory.getLogger(AccessTokenMirrorCredential.class); + private static final Logger logger = LoggerFactory.getLogger(AccessTokenCredential.class); private final String accessToken; @JsonCreator - public AccessTokenMirrorCredential(@JsonProperty("id") String id, - @JsonProperty("enabled") @Nullable Boolean enabled, - @JsonProperty("hostnamePatterns") @Nullable - @JsonDeserialize(contentAs = Pattern.class) - Iterable hostnamePatterns, - @JsonProperty("accessToken") String accessToken) { - super(id, enabled, "access_token", hostnamePatterns); - + public AccessTokenCredential(@JsonProperty("id") String id, + @JsonProperty("enabled") @Nullable Boolean enabled, + @JsonProperty("accessToken") String accessToken) { + super(id, enabled, "access_token"); this.accessToken = requireNonEmpty(accessToken, "accessToken"); } @@ -79,7 +72,7 @@ public boolean equals(Object o) { return true; } - if (!(o instanceof AccessTokenMirrorCredential)) { + if (!(o instanceof AccessTokenCredential)) { return false; } @@ -87,7 +80,7 @@ public boolean equals(Object o) { return false; } - final AccessTokenMirrorCredential that = (AccessTokenMirrorCredential) o; + final AccessTokenCredential that = (AccessTokenCredential) o; return accessToken.equals(that.accessToken); } @@ -97,7 +90,7 @@ void addProperties(ToStringHelper helper) { } @Override - public MirrorCredential withoutSecret() { - return new AccessTokenMirrorCredential(id(), enabled(), hostnamePatterns(), "****"); + public Credential withoutSecret() { + return new AccessTokenCredential(id(), enabled(), "****"); } } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/MirrorCredentialUtil.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/CredentialUtil.java similarity index 89% rename from server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/MirrorCredentialUtil.java rename to server/src/main/java/com/linecorp/centraldogma/server/internal/credential/CredentialUtil.java index bf8d97f654..227443cffe 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/MirrorCredentialUtil.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/CredentialUtil.java @@ -14,11 +14,11 @@ * under the License. */ -package com.linecorp.centraldogma.server.internal.mirror.credential; +package com.linecorp.centraldogma.server.internal.credential; import static java.util.Objects.requireNonNull; -final class MirrorCredentialUtil { +final class CredentialUtil { static String requireNonEmpty(String value, String name) { requireNonNull(value, name); @@ -37,5 +37,5 @@ static byte[] requireNonEmpty(byte[] value, String name) { return value; } - private MirrorCredentialUtil() {} + private CredentialUtil() {} } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/NoneMirrorCredential.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/NoneCredential.java similarity index 56% rename from server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/NoneMirrorCredential.java rename to server/src/main/java/com/linecorp/centraldogma/server/internal/credential/NoneCredential.java index a91be6f5fe..426e412787 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/NoneMirrorCredential.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/NoneCredential.java @@ -14,28 +14,22 @@ * under the License. */ -package com.linecorp.centraldogma.server.internal.mirror.credential; - -import java.util.regex.Pattern; +package com.linecorp.centraldogma.server.internal.credential; import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.common.base.MoreObjects.ToStringHelper; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; -public final class NoneMirrorCredential extends AbstractMirrorCredential { +public final class NoneCredential extends AbstractCredential { @JsonCreator - public NoneMirrorCredential(@JsonProperty("id") String id, - @JsonProperty("enabled") @Nullable Boolean enabled, - @JsonProperty("hostnamePatterns") @Nullable - @JsonDeserialize(contentAs = Pattern.class) - Iterable hostnamePatterns) { - super(id, enabled, "none", hostnamePatterns); + public NoneCredential(@JsonProperty("id") String id, + @JsonProperty("enabled") @Nullable Boolean enabled) { + super(id, enabled, "none"); } @Override @@ -44,7 +38,7 @@ void addProperties(ToStringHelper helper) { } @Override - public MirrorCredential withoutSecret() { + public Credential withoutSecret() { return this; } } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/PasswordMirrorCredential.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/PasswordCredential.java similarity index 65% rename from server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/PasswordMirrorCredential.java rename to server/src/main/java/com/linecorp/centraldogma/server/internal/credential/PasswordCredential.java index cfe80428cf..1c29db89b6 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/PasswordMirrorCredential.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/PasswordCredential.java @@ -14,14 +14,12 @@ * under the License. */ -package com.linecorp.centraldogma.server.internal.mirror.credential; +package com.linecorp.centraldogma.server.internal.credential; import static com.linecorp.centraldogma.server.CentralDogmaConfig.convertValue; -import static com.linecorp.centraldogma.server.internal.mirror.credential.MirrorCredentialUtil.requireNonEmpty; +import static com.linecorp.centraldogma.server.internal.credential.CredentialUtil.requireNonEmpty; import static java.util.Objects.requireNonNull; -import java.util.regex.Pattern; - import javax.annotation.Nullable; import org.slf4j.Logger; @@ -29,28 +27,23 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.common.base.MoreObjects.ToStringHelper; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; -public final class PasswordMirrorCredential extends AbstractMirrorCredential { +public final class PasswordCredential extends AbstractCredential { - private static final Logger logger = LoggerFactory.getLogger(PasswordMirrorCredential.class); + private static final Logger logger = LoggerFactory.getLogger(PasswordCredential.class); private final String username; private final String password; @JsonCreator - public PasswordMirrorCredential(@JsonProperty("id") String id, - @JsonProperty("enabled") @Nullable Boolean enabled, - @JsonProperty("hostnamePatterns") @Nullable - @JsonDeserialize(contentAs = Pattern.class) - Iterable hostnamePatterns, - @JsonProperty("username") String username, - @JsonProperty("password") String password) { - super(id, enabled, "password", hostnamePatterns); - + public PasswordCredential(@JsonProperty("id") String id, + @JsonProperty("enabled") @Nullable Boolean enabled, + @JsonProperty("username") String username, + @JsonProperty("password") String password) { + super(id, enabled, "password"); this.username = requireNonEmpty(username, "username"); this.password = requireNonNull(password, "password"); } @@ -90,7 +83,7 @@ public boolean equals(Object o) { return true; } - if (!(o instanceof PasswordMirrorCredential)) { + if (!(o instanceof PasswordCredential)) { return false; } @@ -98,7 +91,7 @@ public boolean equals(Object o) { return false; } - final PasswordMirrorCredential that = (PasswordMirrorCredential) o; + final PasswordCredential that = (PasswordCredential) o; return username.equals(that.username) && password.equals(that.password); } @@ -108,7 +101,7 @@ void addProperties(ToStringHelper helper) { } @Override - public MirrorCredential withoutSecret() { - return new PasswordMirrorCredential(id(), enabled(), hostnamePatterns(), username(), "****"); + public Credential withoutSecret() { + return new PasswordCredential(id(), enabled(), username(), "****"); } } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/PublicKeyMirrorCredential.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/PublicKeyCredential.java similarity index 73% rename from server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/PublicKeyMirrorCredential.java rename to server/src/main/java/com/linecorp/centraldogma/server/internal/credential/PublicKeyCredential.java index 2eb91bab74..75f896fc68 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/credential/PublicKeyMirrorCredential.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/PublicKeyCredential.java @@ -14,14 +14,13 @@ * under the License. */ -package com.linecorp.centraldogma.server.internal.mirror.credential; +package com.linecorp.centraldogma.server.internal.credential; import static com.linecorp.centraldogma.server.CentralDogmaConfig.convertValue; -import static com.linecorp.centraldogma.server.internal.mirror.credential.MirrorCredentialUtil.requireNonEmpty; +import static com.linecorp.centraldogma.server.internal.credential.CredentialUtil.requireNonEmpty; import java.util.List; import java.util.Objects; -import java.util.regex.Pattern; import javax.annotation.Nullable; @@ -30,17 +29,16 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.common.base.CharMatcher; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; -public final class PublicKeyMirrorCredential extends AbstractMirrorCredential { +public final class PublicKeyCredential extends AbstractCredential { - private static final Logger logger = LoggerFactory.getLogger(PublicKeyMirrorCredential.class); + private static final Logger logger = LoggerFactory.getLogger(PublicKeyCredential.class); private static final Splitter NEWLINE_SPLITTER = Splitter.on(CharMatcher.anyOf("\n\r")) .omitEmptyStrings() @@ -55,17 +53,13 @@ public final class PublicKeyMirrorCredential extends AbstractMirrorCredential { private final String passphrase; @JsonCreator - public PublicKeyMirrorCredential(@JsonProperty("id") String id, - @JsonProperty("enabled") @Nullable Boolean enabled, - @JsonProperty("hostnamePatterns") @Nullable - @JsonDeserialize(contentAs = Pattern.class) - Iterable hostnamePatterns, - @JsonProperty("username") String username, - @JsonProperty("publicKey") String publicKey, - @JsonProperty("privateKey") String privateKey, - @JsonProperty("passphrase") @Nullable String passphrase) { - - super(id, enabled, "public_key", hostnamePatterns); + public PublicKeyCredential(@JsonProperty("id") String id, + @JsonProperty("enabled") @Nullable Boolean enabled, + @JsonProperty("username") String username, + @JsonProperty("publicKey") String publicKey, + @JsonProperty("privateKey") String privateKey, + @JsonProperty("passphrase") @Nullable String passphrase) { + super(id, enabled, "public_key"); this.username = requireNonEmpty(username, "username"); this.publicKey = requireNonEmpty(publicKey, "publicKey"); @@ -128,7 +122,7 @@ public boolean equals(Object o) { return true; } - if (!(o instanceof PublicKeyMirrorCredential)) { + if (!(o instanceof PublicKeyCredential)) { return false; } @@ -136,7 +130,7 @@ public boolean equals(Object o) { return false; } - final PublicKeyMirrorCredential that = (PublicKeyMirrorCredential) o; + final PublicKeyCredential that = (PublicKeyCredential) o; return username.equals(that.username) && Objects.equals(publicKey, that.publicKey) && @@ -163,8 +157,8 @@ public static String publicKeyPreview(String publicKey) { } @Override - public MirrorCredential withoutSecret() { - return new PublicKeyMirrorCredential(id(), enabled(), hostnamePatterns(), username(), publicKey(), - "****", "****"); + public Credential withoutSecret() { + return new PublicKeyCredential(id(), enabled(), username(), publicKey(), + "****", "****"); } } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/package-info.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/package-info.java new file mode 100644 index 0000000000..21aec639ad --- /dev/null +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/credential/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2021 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. + */ +/** + * Credential classes for mirroring. + */ +@NonNullByDefault +package com.linecorp.centraldogma.server.internal.credential; + +import com.linecorp.centraldogma.common.util.NonNullByDefault; diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/AbstractMirror.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/AbstractMirror.java index 6901486376..155a8dbaa6 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/AbstractMirror.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/AbstractMirror.java @@ -38,8 +38,8 @@ import com.linecorp.centraldogma.common.Author; import com.linecorp.centraldogma.server.MirrorException; import com.linecorp.centraldogma.server.command.CommandExecutor; +import com.linecorp.centraldogma.server.credential.Credential; import com.linecorp.centraldogma.server.mirror.Mirror; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; import com.linecorp.centraldogma.server.mirror.MirrorDirection; import com.linecorp.centraldogma.server.storage.repository.Repository; @@ -53,7 +53,7 @@ public abstract class AbstractMirror implements Mirror { private final boolean enabled; private final Cron schedule; private final MirrorDirection direction; - private final MirrorCredential credential; + private final Credential credential; private final Repository localRepo; private final String localPath; private final URI remoteRepoUri; @@ -65,7 +65,7 @@ public abstract class AbstractMirror implements Mirror { private final long jitterMillis; protected AbstractMirror(String id, boolean enabled, Cron schedule, MirrorDirection direction, - MirrorCredential credential, Repository localRepo, String localPath, + Credential credential, Repository localRepo, String localPath, URI remoteRepoUri, String remotePath, String remoteBranch, @Nullable String gitignore) { this.id = requireNonNull(id, "id"); @@ -124,7 +124,7 @@ public MirrorDirection direction() { } @Override - public final MirrorCredential credential() { + public final Credential credential() { return credential; } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/CentralDogmaMirror.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/CentralDogmaMirror.java index 1d06055707..f0a84c7a5e 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/CentralDogmaMirror.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/CentralDogmaMirror.java @@ -26,7 +26,7 @@ import com.cronutils.model.Cron; import com.linecorp.centraldogma.server.command.CommandExecutor; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; import com.linecorp.centraldogma.server.mirror.MirrorDirection; import com.linecorp.centraldogma.server.storage.repository.Repository; @@ -36,7 +36,7 @@ public final class CentralDogmaMirror extends AbstractMirror { private final String remoteRepo; public CentralDogmaMirror(String id, boolean enabled, Cron schedule, MirrorDirection direction, - MirrorCredential credential, Repository localRepo, String localPath, + Credential credential, Repository localRepo, String localPath, URI remoteRepoUri, String remoteProject, String remoteRepo, String remotePath, @Nullable String gitignore) { // Central Dogma has no notion of 'branch', so we just pass an empty string as a placeholder. diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringService.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringService.java index e789093fad..6076ed77a1 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringService.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirroringService.java @@ -131,14 +131,6 @@ public synchronized void start(CommandExecutor commandExecutor) { } })); - try { - new RemovingHostnamePatternsService(projectManager, commandExecutor).start(); - } catch (Throwable e) { - logger.error("Git mirroring stopped due to an unexpected exception while removing" + - "hostnamePatterns :", e); - return; - } - final ListenableScheduledFuture future = scheduler.scheduleWithFixedDelay( this::schedulePendingMirrors, TICK.getSeconds(), TICK.getSeconds(), TimeUnit.SECONDS); diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsService.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsService.java deleted file mode 100644 index 3ef1658aa8..0000000000 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/mirror/RemovingHostnamePatternsService.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2023 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.mirror; - -import static com.linecorp.centraldogma.server.internal.storage.repository.DefaultMetaRepository.PATH_CREDENTIALS; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Stopwatch; -import com.google.common.collect.ImmutableMap; - -import com.linecorp.centraldogma.common.Author; -import com.linecorp.centraldogma.common.Change; -import com.linecorp.centraldogma.common.Entry; -import com.linecorp.centraldogma.common.EntryType; -import com.linecorp.centraldogma.common.Markup; -import com.linecorp.centraldogma.common.Revision; -import com.linecorp.centraldogma.internal.Jackson; -import com.linecorp.centraldogma.server.command.Command; -import com.linecorp.centraldogma.server.command.CommandExecutor; -import com.linecorp.centraldogma.server.command.CommitResult; -import com.linecorp.centraldogma.server.storage.project.InternalProjectInitializer; -import com.linecorp.centraldogma.server.storage.project.Project; -import com.linecorp.centraldogma.server.storage.project.ProjectManager; -import com.linecorp.centraldogma.server.storage.repository.MetaRepository; -import com.linecorp.centraldogma.server.storage.repository.Repository; - -final class RemovingHostnamePatternsService { - - private static final Logger logger = LoggerFactory.getLogger(RemovingHostnamePatternsService.class); - - @VisibleForTesting - static final String REMOVING_HOSTNAME_JOB_LOG = "/removing-hostname-job.json"; - - private final ProjectManager projectManager; - private final CommandExecutor commandExecutor; - - RemovingHostnamePatternsService(ProjectManager projectManager, CommandExecutor commandExecutor) { - this.projectManager = projectManager; - this.commandExecutor = commandExecutor; - } - - void start() throws Exception { - if (hasJobLog()) { - return; - } - logger.info("Start removing hostnamePatterns in credential ..."); - - final Stopwatch stopwatch = Stopwatch.createStarted(); - int numProjects = 0; - for (Project project : projectManager.list().values()) { - if (InternalProjectInitializer.INTERNAL_PROJECT_DOGMA.equals(project.name())) { - continue; - } - try { - logger.info("Removing hostnamePatterns in credential files in the project: {} ...", - project.name()); - final List> changes = new ArrayList<>(); - final MetaRepository repository = project.metaRepo(); - for (Entry entry : repository.find(Revision.HEAD, PATH_CREDENTIALS + "**") - .get().values()) { - if (entry.type() != EntryType.JSON) { - continue; - } - final JsonNode content = (JsonNode) entry.content(); - if (content.get("hostnamePatterns") == null) { - continue; - } - changes.add(Change.ofJsonUpsert(entry.path(), - ((ObjectNode) content).without("hostnamePatterns"))); - } - - if (changes.isEmpty()) { - continue; - } - - numProjects++; - logger.info("hostnamePatterns in credentials are removed in the project: {}", - project.name()); - - executeCommand(Command.push( - Author.SYSTEM, project.name(), Project.REPO_META, Revision.HEAD, - "Remove hostnamePatterns in credentials.", "", Markup.PLAINTEXT, - changes)); - } catch (Throwable t) { - logger.warn("Failed to remove hostnamePatterns in credential files in the project: {}", - project.name(), t); - } - } - logger.info("hostnamePatterns are removed in {} projects. (took: {} ms.)", - numProjects, stopwatch.elapsed().toMillis()); - logRemovingJob(numProjects); - } - - private boolean hasJobLog() throws Exception { - final Project internalProj = projectManager.get(InternalProjectInitializer.INTERNAL_PROJECT_DOGMA); - final Repository repository = internalProj.repos().get(Project.REPO_DOGMA); - final Map> entries = repository.find(Revision.HEAD, REMOVING_HOSTNAME_JOB_LOG).get(); - final Entry entry = entries.get(REMOVING_HOSTNAME_JOB_LOG); - return entry != null; - } - - private CommitResult executeCommand(Command command) - throws InterruptedException, ExecutionException, TimeoutException { - return commandExecutor.execute(command).get(1, TimeUnit.MINUTES); - } - - private void logRemovingJob(int numProjects) throws Exception { - final ImmutableMap data = - ImmutableMap.of("timestamp", Instant.now(), - "projects", numProjects); - final Change change = Change.ofJsonUpsert(REMOVING_HOSTNAME_JOB_LOG, - Jackson.writeValueAsString(data)); - executeCommand(Command.push(Author.SYSTEM, InternalProjectInitializer.INTERNAL_PROJECT_DOGMA, - Project.REPO_DOGMA, Revision.HEAD, - "Removing hostnamePatterns from " + numProjects + - " projects has been done.", "", Markup.PLAINTEXT, change)); - } -} 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 f4674b32cf..58db07d632 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,8 +46,8 @@ import com.linecorp.centraldogma.internal.api.v1.MirrorDto; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.command.CommitResult; +import com.linecorp.centraldogma.server.credential.Credential; import com.linecorp.centraldogma.server.mirror.Mirror; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; import com.linecorp.centraldogma.server.mirror.MirrorDirection; import com.linecorp.centraldogma.server.mirror.MirrorUtil; import com.linecorp.centraldogma.server.storage.repository.MetaRepository; @@ -117,7 +117,7 @@ public CompletableFuture mirror(String id) { throw new RepositoryMetadataException("failed to load the mirror configuration", e); } - final CompletableFuture> credentials; + final CompletableFuture> credentials; if (Strings.isNullOrEmpty(c.credentialId())) { credentials = credentials(); } else { @@ -150,7 +150,7 @@ private CompletableFuture> allMirrors() { }); } - private List parseMirrors(Map> entries, List credentials) + private List parseMirrors(Map> entries, List credentials) throws JsonProcessingException { return entries.entrySet().stream().map(entry -> { @@ -171,7 +171,7 @@ private List parseMirrors(Map> entries, List> credentials() { + public CompletableFuture> credentials() { return find(PATH_CREDENTIALS + "*.json").thenApply(entries -> { if (entries.isEmpty()) { return ImmutableList.of(); @@ -185,7 +185,7 @@ public CompletableFuture> credentials() { } @Override - public CompletableFuture credential(String credentialId) { + public CompletableFuture credential(String credentialId) { final String credentialFile = credentialFile(credentialId); return find(credentialFile).thenApply(entries -> { @SuppressWarnings("unchecked") @@ -203,7 +203,7 @@ public CompletableFuture credential(String credentialId) { }); } - private List parseCredentials(Map> entries) + private List parseCredentials(Map> entries) throws JsonProcessingException { return entries.entrySet().stream() .map(entry -> { @@ -217,13 +217,13 @@ private List parseCredentials(Map> entries) .collect(toImmutableList()); } - private MirrorCredential parseCredential(String credentialFile, Entry entry) + private Credential parseCredential(String credentialFile, Entry entry) throws JsonProcessingException { final JsonNode credentialJson = entry.content(); if (!credentialJson.isObject()) { throw newInvalidJsonTypeException(credentialFile, credentialJson); } - return Jackson.treeToValue(credentialJson, MirrorCredential.class); + return Jackson.treeToValue(credentialJson, Credential.class); } private RepositoryMetadataException newInvalidJsonTypeException(String fileName, JsonNode credentialJson) { @@ -259,7 +259,7 @@ public CompletableFuture> createPushCommand(MirrorDto mirr } @Override - public CompletableFuture> createPushCommand(MirrorCredential credential, + public CompletableFuture> createPushCommand(Credential credential, Author author, boolean update) { checkArgument(!credential.id().isEmpty(), "Credential ID should not be empty"); @@ -283,7 +283,7 @@ private Command newCommand(MirrorDto mirrorDto, Author author, Str change); } - private Command newCommand(MirrorCredential credential, Author author, String summary) { + private Command newCommand(Credential credential, Author author, String summary) { final JsonNode jsonNode = Jackson.valueToTree(credential); final Change change = Change.ofJsonUpsert(credentialFile(credential.id()), jsonNode); return Command.push(author, parent().name(), name(), Revision.HEAD, summary, "", Markup.PLAINTEXT, diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/MirrorConfig.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/MirrorConfig.java index f083f03f74..a553dac741 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/MirrorConfig.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/MirrorConfig.java @@ -40,13 +40,12 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.MoreObjects; -import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Streams; +import com.linecorp.centraldogma.server.credential.Credential; import com.linecorp.centraldogma.server.mirror.Mirror; import com.linecorp.centraldogma.server.mirror.MirrorContext; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; import com.linecorp.centraldogma.server.mirror.MirrorDirection; import com.linecorp.centraldogma.server.mirror.MirrorProvider; import com.linecorp.centraldogma.server.storage.project.Project; @@ -118,17 +117,17 @@ public MirrorConfig(@JsonProperty("id") String id, } else { this.gitignore = null; } - this.credentialId = Strings.emptyToNull(credentialId); + this.credentialId = credentialId; } @Nullable - Mirror toMirror(Project parent, Iterable credentials) { + Mirror toMirror(Project parent, Iterable credentials) { if (localRepo == null || !parent.repos().exists(localRepo)) { return null; } final MirrorContext mirrorContext = new MirrorContext( - id, enabled, schedule, direction, findCredential(credentials, remoteUri, credentialId), + id, enabled, schedule, direction, findCredential(credentials, credentialId), parent.repos().get(localRepo), localPath, remoteUri, gitignore); for (MirrorProvider mirrorProvider : MIRROR_PROVIDERS) { final Mirror mirror = mirrorProvider.newMirror(mirrorContext); @@ -140,26 +139,18 @@ id, enabled, schedule, direction, findCredential(credentials, remoteUri, credent throw new IllegalArgumentException("could not find a mirror provider for " + mirrorContext); } - public static MirrorCredential findCredential(Iterable credentials, URI remoteUri, - @Nullable String credentialId) { + public static Credential findCredential(Iterable credentials, + @Nullable String credentialId) { if (credentialId != null) { - // Find by credential ID. - for (MirrorCredential c : credentials) { + for (Credential c : credentials) { final String id = c.id(); if (credentialId.equals(id)) { return c; } } - } else { - // Find by host name. - for (MirrorCredential c : credentials) { - if (c.matches(remoteUri)) { - return c; - } - } } - return MirrorCredential.FALLBACK; + return Credential.FALLBACK; } @JsonProperty("id") @@ -199,7 +190,6 @@ public String gitignore() { return gitignore; } - @Nullable @JsonProperty("credentialId") public String credentialId() { return credentialId; diff --git a/server/src/main/java/com/linecorp/centraldogma/server/mirror/Mirror.java b/server/src/main/java/com/linecorp/centraldogma/server/mirror/Mirror.java index 0c92be44c7..4732a57b3f 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/mirror/Mirror.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/mirror/Mirror.java @@ -25,6 +25,7 @@ import com.linecorp.centraldogma.server.MirrorException; import com.linecorp.centraldogma.server.command.CommandExecutor; +import com.linecorp.centraldogma.server.credential.Credential; import com.linecorp.centraldogma.server.storage.repository.Repository; /** @@ -57,7 +58,7 @@ public interface Mirror { /** * Returns the authentication credentials which are required when accessing the Git repositories. */ - MirrorCredential credential(); + Credential credential(); /** * Returns the Central Dogma repository where is supposed to keep the mirrored files. diff --git a/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirrorContext.java b/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirrorContext.java index e1a0d4eca8..a5ea678dfe 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirrorContext.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirrorContext.java @@ -25,6 +25,7 @@ import com.cronutils.model.Cron; import com.google.common.base.MoreObjects; +import com.linecorp.centraldogma.server.credential.Credential; import com.linecorp.centraldogma.server.storage.repository.Repository; /** @@ -36,7 +37,7 @@ public final class MirrorContext { private final boolean enabled; private final Cron schedule; private final MirrorDirection direction; - private final MirrorCredential credential; + private final Credential credential; private final Repository localRepo; private final String localPath; private final URI remoteUri; @@ -47,7 +48,7 @@ public final class MirrorContext { * Creates a new instance. */ public MirrorContext(String id, boolean enabled, Cron schedule, MirrorDirection direction, - MirrorCredential credential, Repository localRepo, String localPath, URI remoteUri, + Credential credential, Repository localRepo, String localPath, URI remoteUri, @Nullable String gitignore) { this.id = requireNonNull(id, "id"); this.enabled = enabled; @@ -91,7 +92,7 @@ public MirrorDirection direction() { /** * Returns the credential of this mirror. */ - public MirrorCredential credential() { + public Credential credential() { return credential; } diff --git a/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirrorCredential.java b/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirrorCredential.java deleted file mode 100644 index 5b7c6b55db..0000000000 --- a/server/src/main/java/com/linecorp/centraldogma/server/mirror/MirrorCredential.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2017 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 java.net.URI; -import java.util.Collections; -import java.util.Set; -import java.util.regex.Pattern; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonSubTypes.Type; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -import com.linecorp.centraldogma.server.internal.mirror.credential.AccessTokenMirrorCredential; -import com.linecorp.centraldogma.server.internal.mirror.credential.NoneMirrorCredential; -import com.linecorp.centraldogma.server.internal.mirror.credential.PasswordMirrorCredential; -import com.linecorp.centraldogma.server.internal.mirror.credential.PublicKeyMirrorCredential; - -/** - * The authentication credentials which are required when accessing the Git repositories. - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type") -@JsonSubTypes({ - @Type(value = NoneMirrorCredential.class, name = "none"), - @Type(value = PasswordMirrorCredential.class, name = "password"), - @Type(value = PublicKeyMirrorCredential.class, name = "public_key"), - @Type(value = AccessTokenMirrorCredential.class, name = "access_token") -}) -@JsonInclude(JsonInclude.Include.NON_NULL) -public interface MirrorCredential { - - MirrorCredential FALLBACK = - new NoneMirrorCredential("", true, Collections.singleton(Pattern.compile("^.*$"))); - - /** - * Returns the ID of the credential. - */ - @JsonProperty("id") - String id(); - - /** - * Returns the {@link Pattern}s compiled from the regular expressions that match a host name. - */ - @JsonProperty("hostnamePatterns") - Set hostnamePatterns(); - - /** - * Returns whether this {@link MirrorCredential} is enabled. - */ - @JsonProperty("enabled") - boolean enabled(); - - /** - * Returns {@code true} if the specified {@code uri} is matched by one of the host name patterns. - * - * @param uri a URI of a Git repository - */ - boolean matches(URI uri); - - /** - * Returns a new {@link MirrorCredential} that does not contain any sensitive information. - */ - MirrorCredential withoutSecret(); -} 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 1f54a1f41e..49526fdc68 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,8 +23,8 @@ import com.linecorp.centraldogma.internal.api.v1.MirrorDto; import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.command.CommitResult; +import com.linecorp.centraldogma.server.credential.Credential; import com.linecorp.centraldogma.server.mirror.Mirror; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; /** * A Revision-controlled filesystem-like repository which is named {@code "meta"}. @@ -52,12 +52,12 @@ default CompletableFuture> mirrors() { /** * Returns a list of mirroring credentials. */ - CompletableFuture> credentials(); + CompletableFuture> credentials(); /** * Returns a mirroring credential of the specified {@code id}. */ - CompletableFuture credential(String id); + CompletableFuture credential(String id); /** * Create a push {@link Command} for the {@link MirrorDto}. @@ -66,8 +66,8 @@ CompletableFuture> createPushCommand(MirrorDto mirrorDto, boolean update); /** - * Create a push {@link Command} for the {@link MirrorCredential}. + * Create a push {@link Command} for the {@link Credential}. */ - CompletableFuture> createPushCommand(MirrorCredential credential, Author author, + CompletableFuture> createPushCommand(Credential credential, Author author, boolean update); } diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/AccessTokenMirrorCredentialTest.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/credential/AccessTokenCredentialTest.java similarity index 51% rename from server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/AccessTokenMirrorCredentialTest.java rename to server/src/test/java/com/linecorp/centraldogma/server/internal/credential/AccessTokenCredentialTest.java index f144aecf25..e90a57c39f 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/AccessTokenMirrorCredentialTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/credential/AccessTokenCredentialTest.java @@ -14,54 +14,41 @@ * under the License. */ -package com.linecorp.centraldogma.server.internal.mirror.credential; +package com.linecorp.centraldogma.server.internal.credential; -import static com.linecorp.centraldogma.server.internal.mirror.credential.MirrorCredentialTest.HOSTNAME_PATTERNS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; import com.linecorp.centraldogma.internal.Jackson; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; -class AccessTokenMirrorCredentialTest { +class AccessTokenCredentialTest { @Test void testConstruction() throws Exception { // null checks - assertThatThrownBy(() -> new AccessTokenMirrorCredential("foo", true, null, null)) + assertThatThrownBy(() -> new AccessTokenCredential("foo", true, null)) .isInstanceOf(NullPointerException.class); // emptiness checks - assertThatThrownBy(() -> new AccessTokenMirrorCredential("foo", true, null, "")) + assertThatThrownBy(() -> new AccessTokenCredential("foo", true, "")) .isInstanceOf(IllegalArgumentException.class); // successful construction - final AccessTokenMirrorCredential c = new AccessTokenMirrorCredential("foo", true, null, "sesame"); + final AccessTokenCredential c = new AccessTokenCredential("foo", true, "sesame"); assertThat(c.id()).isEqualTo("foo"); assertThat(c.accessToken()).isEqualTo("sesame"); } @Test void testDeserialization() throws Exception { - // With hostnamePatterns - assertThat(Jackson.readValue('{' + - " \"id\": \"access-token-id\"," + - " \"type\": \"access_token\"," + - " \"hostnamePatterns\": [" + - " \"^foo\\\\.com$\"" + - " ]," + - " \"accessToken\": \"sesame\"" + - '}', MirrorCredential.class)) - .isEqualTo(new AccessTokenMirrorCredential("access-token-id", true, HOSTNAME_PATTERNS, - "sesame")); - // Without hostnamePatterns assertThat(Jackson.readValue('{' + " \"type\": \"access_token\"," + " \"id\": \"foo\"," + " \"accessToken\": \"sesame\"" + - '}', MirrorCredential.class)) - .isEqualTo(new AccessTokenMirrorCredential("foo", true, null, "sesame")); + '}', Credential.class)) + .isEqualTo(new AccessTokenCredential("foo", true, "sesame")); } } diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/NoneMirrorCredentialTest.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/credential/NoneCredentialTest.java similarity index 50% rename from server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/NoneMirrorCredentialTest.java rename to server/src/test/java/com/linecorp/centraldogma/server/internal/credential/NoneCredentialTest.java index 60555ca49e..589717307e 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/NoneMirrorCredentialTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/credential/NoneCredentialTest.java @@ -14,40 +14,24 @@ * under the License. */ -package com.linecorp.centraldogma.server.internal.mirror.credential; +package com.linecorp.centraldogma.server.internal.credential; import static org.assertj.core.api.Assertions.assertThat; -import java.util.regex.Pattern; - import org.junit.jupiter.api.Test; -import com.google.common.collect.ImmutableSet; - import com.linecorp.centraldogma.internal.Jackson; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; -class NoneMirrorCredentialTest { +class NoneCredentialTest { @Test void testDeserialization() throws Exception { - // With hostnamePatterns assertThat(Jackson.readValue('{' + " \"id\": \"none\"," + " \"type\": \"none\"," + - " \"hostnamePatterns\": [" + - " \"^foo\\\\.com$\"" + - " ]," + " \"enabled\": true" + - '}', MirrorCredential.class)) - .isEqualTo(new NoneMirrorCredential("none", true, - ImmutableSet.of(Pattern.compile("^foo\\.com$")) - )); - // Without hostnamePatterns - assertThat(Jackson.readValue('{' + - " \"type\": \"none\"," + - " \"id\": \"foo\"" + - '}', MirrorCredential.class)) - .isEqualTo(new NoneMirrorCredential("foo", true, null)); + '}', Credential.class)) + .isEqualTo(new NoneCredential("none", true)); } } diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/PasswordMirrorCredentialTest.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/credential/PasswordCredentialTest.java similarity index 52% rename from server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/PasswordMirrorCredentialTest.java rename to server/src/test/java/com/linecorp/centraldogma/server/internal/credential/PasswordCredentialTest.java index b4669acef7..d3168ec0bc 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/PasswordMirrorCredentialTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/credential/PasswordCredentialTest.java @@ -14,37 +14,36 @@ * under the License. */ -package com.linecorp.centraldogma.server.internal.mirror.credential; +package com.linecorp.centraldogma.server.internal.credential; -import static com.linecorp.centraldogma.server.internal.mirror.credential.MirrorCredentialTest.HOSTNAME_PATTERNS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; import com.linecorp.centraldogma.internal.Jackson; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; -class PasswordMirrorCredentialTest { +class PasswordCredentialTest { @Test void testConstruction() throws Exception { // null checks - assertThatThrownBy(() -> new PasswordMirrorCredential("foo", true, null, null, "sesame")) + assertThatThrownBy(() -> new PasswordCredential("foo", true, null, "sesame")) .isInstanceOf(NullPointerException.class); - assertThatThrownBy(() -> new PasswordMirrorCredential("foo", true, null, "trustin", null)) + assertThatThrownBy(() -> new PasswordCredential("foo", true, "trustin", null)) .isInstanceOf(NullPointerException.class); // emptiness checks - assertThatThrownBy(() -> new PasswordMirrorCredential("foo", true, null, "", "sesame")) + assertThatThrownBy(() -> new PasswordCredential("foo", true, "", "sesame")) .isInstanceOf(IllegalArgumentException.class); // An empty password must be allowed because some servers uses password authentication // as token-based authentication whose username is the token and password is an empty string. - assertThat(new PasswordMirrorCredential("foo", true, null, "trustin", "").password()).isEmpty(); + assertThat(new PasswordCredential("foo", true, "trustin", "").password()).isEmpty(); // successful construction - final PasswordMirrorCredential c = new PasswordMirrorCredential("foo", true, null, "trustin", "sesame"); + final PasswordCredential c = new PasswordCredential("foo", true, "trustin", "sesame"); assertThat(c.id()).isEqualTo("foo"); assertThat(c.username()).isEqualTo("trustin"); assertThat(c.password()).isEqualTo("sesame"); @@ -52,25 +51,12 @@ void testConstruction() throws Exception { @Test void testDeserialization() throws Exception { - // With hostnamePatterns - assertThat(Jackson.readValue('{' + - " \"id\": \"password-id\"," + - " \"type\": \"password\"," + - " \"hostnamePatterns\": [" + - " \"^foo\\\\.com$\"" + - " ]," + - " \"username\": \"trustin\"," + - " \"password\": \"sesame\"" + - '}', MirrorCredential.class)) - .isEqualTo(new PasswordMirrorCredential("password-id", true, HOSTNAME_PATTERNS, - "trustin", "sesame")); - // Without hostnamePatterns assertThat(Jackson.readValue('{' + " \"type\": \"password\"," + " \"id\": \"foo\"," + " \"username\": \"trustin\"," + " \"password\": \"sesame\"" + - '}', MirrorCredential.class)) - .isEqualTo(new PasswordMirrorCredential("foo", true, null, "trustin", "sesame")); + '}', Credential.class)) + .isEqualTo(new PasswordCredential("foo", true, "trustin", "sesame")); } } diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/PublicKeyMirrorCredentialTest.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/credential/PublicKeyCredentialTest.java similarity index 61% rename from server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/PublicKeyMirrorCredentialTest.java rename to server/src/test/java/com/linecorp/centraldogma/server/internal/credential/PublicKeyCredentialTest.java index a69ead6474..e44269c81c 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/PublicKeyMirrorCredentialTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/credential/PublicKeyCredentialTest.java @@ -14,9 +14,8 @@ * under the License. */ -package com.linecorp.centraldogma.server.internal.mirror.credential; +package com.linecorp.centraldogma.server.internal.credential; -import static com.linecorp.centraldogma.server.internal.mirror.credential.MirrorCredentialTest.HOSTNAME_PATTERNS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -28,9 +27,9 @@ import com.linecorp.centraldogma.internal.Jackson; import com.linecorp.centraldogma.server.ConfigValueConverter; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; +import com.linecorp.centraldogma.server.credential.Credential; -public class PublicKeyMirrorCredentialTest { +public class PublicKeyCredentialTest { private static final String USERNAME = "trustin"; @@ -66,38 +65,38 @@ public class PublicKeyMirrorCredentialTest { @Test void testConstruction() { // null checks - assertThatThrownBy(() -> new PublicKeyMirrorCredential( - "id", true, null, null, PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE)) + assertThatThrownBy(() -> new PublicKeyCredential( + "id", true, null, PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE)) .isInstanceOf(NullPointerException.class); - assertThatThrownBy(() -> new PublicKeyMirrorCredential( - "id", true, null, USERNAME, null, PRIVATE_KEY, PASSPHRASE)) + assertThatThrownBy(() -> new PublicKeyCredential( + "id", true, USERNAME, null, PRIVATE_KEY, PASSPHRASE)) .isInstanceOf(NullPointerException.class); - assertThatThrownBy(() -> new PublicKeyMirrorCredential( - "id", true, null, USERNAME, PUBLIC_KEY, null, PASSPHRASE)) + assertThatThrownBy(() -> new PublicKeyCredential( + "id", true, USERNAME, PUBLIC_KEY, null, PASSPHRASE)) .isInstanceOf(NullPointerException.class); // null passphrase must be accepted. - assertThat(new PublicKeyMirrorCredential( - "id", true, null, USERNAME, PUBLIC_KEY, PRIVATE_KEY, null).passphrase()).isNull(); + assertThat(new PublicKeyCredential( + "id", true, USERNAME, PUBLIC_KEY, PRIVATE_KEY, null).passphrase()).isNull(); // emptiness checks - assertThatThrownBy(() -> new PublicKeyMirrorCredential( - "id", true, null, "", PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE)) + assertThatThrownBy(() -> new PublicKeyCredential( + "id", true, "", PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE)) .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new PublicKeyMirrorCredential( - "id", true, null, USERNAME, "", PRIVATE_KEY, PASSPHRASE)) + assertThatThrownBy(() -> new PublicKeyCredential( + "id", true, USERNAME, "", PRIVATE_KEY, PASSPHRASE)) .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new PublicKeyMirrorCredential( - "id", true, null, USERNAME, PUBLIC_KEY, "", PASSPHRASE)) + assertThatThrownBy(() -> new PublicKeyCredential( + "id", true, USERNAME, PUBLIC_KEY, "", PASSPHRASE)) .isInstanceOf(IllegalArgumentException.class); // empty passphrase must be accepted, because an empty password is still a password. - assertThat(new PublicKeyMirrorCredential( - "id", true, null, USERNAME, PUBLIC_KEY, PRIVATE_KEY, "").passphrase()).isEmpty(); + assertThat(new PublicKeyCredential( + "id", true, USERNAME, PUBLIC_KEY, PRIVATE_KEY, "").passphrase()).isEmpty(); // successful construction - final PublicKeyMirrorCredential c = new PublicKeyMirrorCredential( - "id", true, null, USERNAME, PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE); + final PublicKeyCredential c = new PublicKeyCredential( + "id", true, USERNAME, PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE); assertThat(c.username()).isEqualTo(USERNAME); assertThat(c.publicKey()).isEqualTo(PUBLIC_KEY); @@ -107,8 +106,8 @@ void testConstruction() { @Test void testBase64Passphrase() { - final PublicKeyMirrorCredential c = new PublicKeyMirrorCredential( - "id", true, null, USERNAME, PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE_BASE64); + final PublicKeyCredential c = new PublicKeyCredential( + "id", true, USERNAME, PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE_BASE64); assertThat(c.passphrase()).isEqualTo(PASSPHRASE); } @@ -118,36 +117,29 @@ void testDeserialization() throws Exception { assertThat(Jackson.readValue('{' + " \"id\": \"foo\"," + " \"type\": \"public_key\"," + - " \"hostnamePatterns\": [" + - " \"^foo\\\\.com$\"" + - " ]," + " \"username\": \"trustin\"," + " \"publicKey\": \"" + Jackson.escapeText(PUBLIC_KEY) + "\"," + " \"privateKey\": \"" + Jackson.escapeText(PRIVATE_KEY) + "\"," + " \"passphrase\": \"" + Jackson.escapeText(PASSPHRASE) + '"' + - '}', MirrorCredential.class)) - .isEqualTo(new PublicKeyMirrorCredential("foo", true, HOSTNAME_PATTERNS, USERNAME, - PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE)); + '}', Credential.class)) + .isEqualTo(new PublicKeyCredential("foo", true, USERNAME, + PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE)); // base64 passphrase - final PublicKeyMirrorCredential base64Expected = - new PublicKeyMirrorCredential("bar", null, HOSTNAME_PATTERNS, USERNAME, - PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE_BASE64); + final PublicKeyCredential base64Expected = + new PublicKeyCredential("bar", null, USERNAME, + PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE_BASE64); assertThat(Jackson.readValue('{' + " \"id\": \"bar\"," + " \"type\": \"public_key\"," + - " \"hostnamePatterns\": [" + - " \"^foo\\\\.com$\"" + - " ]," + " \"username\": \"trustin\"," + " \"publicKey\": \"" + Jackson.escapeText(PUBLIC_KEY) + "\"," + " \"privateKey\": \"" + Jackson.escapeText(PRIVATE_KEY) + "\"," + " \"passphrase\": \"" + Jackson.escapeText(PASSPHRASE_BASE64) + '"' + - '}', MirrorCredential.class)) + '}', Credential.class)) .isEqualTo(base64Expected); assertThat(base64Expected.passphrase()).isEqualTo(PASSPHRASE); - // Without hostnamePatterns assertThat(Jackson.readValue('{' + " \"type\": \"public_key\"," + " \"id\": \"foo\"," + @@ -155,13 +147,13 @@ void testDeserialization() throws Exception { " \"publicKey\": \"" + Jackson.escapeText(PUBLIC_KEY) + "\"," + " \"privateKey\": \"" + Jackson.escapeText(PRIVATE_KEY) + "\"," + " \"passphrase\": \"" + Jackson.escapeText(PASSPHRASE) + '"' + - '}', MirrorCredential.class)) - .isEqualTo(new PublicKeyMirrorCredential("foo", true, null, USERNAME, - PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE)); + '}', Credential.class)) + .isEqualTo(new PublicKeyCredential("foo", true, USERNAME, + PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE)); - final PublicKeyMirrorCredential converterExpected = - new PublicKeyMirrorCredential("foo", true, null, USERNAME, - PUBLIC_KEY, PRIVATE_KEY, "mirror_encryption:foo"); + final PublicKeyCredential converterExpected = + new PublicKeyCredential("foo", true, USERNAME, + PUBLIC_KEY, PRIVATE_KEY, "mirror_encryption:foo"); assertThat(Jackson.readValue('{' + " \"type\": \"public_key\"," + " \"id\": \"foo\"," + @@ -169,22 +161,9 @@ void testDeserialization() throws Exception { " \"publicKey\": \"" + Jackson.escapeText(PUBLIC_KEY) + "\"," + " \"privateKey\": \"" + Jackson.escapeText(PRIVATE_KEY) + "\"," + " \"passphrase\": \"mirror_encryption:foo\"" + - '}', MirrorCredential.class)) + '}', Credential.class)) .isEqualTo(converterExpected); assertThat(converterExpected.passphrase()).isEqualTo("bar"); - - // Empty hostnamePatterns - assertThat(Jackson.readValue('{' + - " \"type\": \"public_key\"," + - " \"id\": \"foo\"," + - " \"hostnamePatterns\": []," + - " \"username\": \"trustin\"," + - " \"publicKey\": \"" + Jackson.escapeText(PUBLIC_KEY) + "\"," + - " \"privateKey\": \"" + Jackson.escapeText(PRIVATE_KEY) + "\"," + - " \"passphrase\": \"" + Jackson.escapeText(PASSPHRASE) + '"' + - '}', MirrorCredential.class)) - .isEqualTo(new PublicKeyMirrorCredential("foo", true, ImmutableList.of(), USERNAME, - PUBLIC_KEY, PRIVATE_KEY, PASSPHRASE)); } public static class PasswordConfigValueConverter implements ConfigValueConverter { diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/CentralDogmaMirrorTest.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/CentralDogmaMirrorTest.java index 9133b605c9..d47fbf0215 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/CentralDogmaMirrorTest.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/CentralDogmaMirrorTest.java @@ -29,10 +29,10 @@ import com.cronutils.model.definition.CronDefinitionBuilder; import com.cronutils.parser.CronParser; +import com.linecorp.centraldogma.server.credential.Credential; import com.linecorp.centraldogma.server.internal.storage.repository.CentralDogmaMirrorProvider; import com.linecorp.centraldogma.server.mirror.Mirror; import com.linecorp.centraldogma.server.mirror.MirrorContext; -import com.linecorp.centraldogma.server.mirror.MirrorCredential; import com.linecorp.centraldogma.server.mirror.MirrorDirection; import com.linecorp.centraldogma.server.storage.project.Project; import com.linecorp.centraldogma.server.storage.repository.Repository; @@ -118,7 +118,7 @@ static T newMirror(String remoteUri, Class mirrorType) { static T newMirror(String remoteUri, Cron schedule, Repository repository, Class mirrorType) { - final MirrorCredential credential = mock(MirrorCredential.class); + final Credential credential = mock(Credential.class); final String mirrorId = "mirror-id"; final Mirror mirror = new CentralDogmaMirrorProvider().newMirror( @@ -138,7 +138,7 @@ static T newMirror(String remoteUri, Cron schedule, } static void assertMirrorNull(String remoteUri) { - final MirrorCredential credential = mock(MirrorCredential.class); + final Credential credential = mock(Credential.class); final Mirror mirror = new CentralDogmaMirrorProvider().newMirror( new MirrorContext("mirror-id", true, EVERY_MINUTE, MirrorDirection.LOCAL_TO_REMOTE, credential, mock(Repository.class), "/", URI.create(remoteUri), null)); diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/MirrorCredentialTest.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/MirrorCredentialTest.java deleted file mode 100644 index 2fd4268294..0000000000 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/mirror/credential/MirrorCredentialTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2020 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.mirror.credential; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.Arrays; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import javax.annotation.Nullable; - -import org.junit.jupiter.api.Test; - -import com.google.common.base.MoreObjects.ToStringHelper; -import com.google.common.collect.ImmutableSet; - -import com.linecorp.centraldogma.server.mirror.MirrorCredential; - -class MirrorCredentialTest { - - static final Iterable HOSTNAME_PATTERNS = ImmutableSet.of(Pattern.compile("^foo\\.com$")); - - private static final Iterable INVALID_PATTERNS = Arrays.asList( - Pattern.compile("^foo\\.com$"), - null, - Pattern.compile("^bar\\.com$")); - - @Test - void testConstruction() { - // null hostnamePatterns. - assertThat(new MirrorCredentialImpl("foo", null).hostnamePatterns()).isEmpty(); - - // hostnamePatterns that contain null. - assertThatThrownBy(() -> new MirrorCredentialImpl("foo", INVALID_PATTERNS)) - .isInstanceOf(NullPointerException.class); - - // An empty hostnamePatterns. - assertThat(new MirrorCredentialImpl("foo", ImmutableSet.of()).hostnamePatterns()).isEmpty(); - - // With ID and non-empty hostnamePatterns. - final MirrorCredential c = new MirrorCredentialImpl("foo", HOSTNAME_PATTERNS); - assertThat(c.id()).contains("foo"); - assertThat(c.hostnamePatterns().stream().map(Pattern::pattern) - .collect(Collectors.toSet())).containsExactly("^foo\\.com$"); - } - - private static final class MirrorCredentialImpl extends AbstractMirrorCredential { - MirrorCredentialImpl(String id, @Nullable Iterable hostnamePatterns) { - super(id, true, "custom", hostnamePatterns); - } - - @Override - void addProperties(ToStringHelper helper) {} - - @Override - public MirrorCredential withoutSecret() { - return this; - } - } -} diff --git a/server/src/test/resources/META-INF/services/com.linecorp.centraldogma.server.ConfigValueConverter b/server/src/test/resources/META-INF/services/com.linecorp.centraldogma.server.ConfigValueConverter index 0da82ad464..9b59e03746 100644 --- a/server/src/test/resources/META-INF/services/com.linecorp.centraldogma.server.ConfigValueConverter +++ b/server/src/test/resources/META-INF/services/com.linecorp.centraldogma.server.ConfigValueConverter @@ -1,2 +1,2 @@ com.linecorp.centraldogma.server.ConfigDeserializationTest$KeyConfigValueConverter -com.linecorp.centraldogma.server.internal.mirror.credential.PublicKeyMirrorCredentialTest$PasswordConfigValueConverter +com.linecorp.centraldogma.server.internal.credential.PublicKeyCredentialTest$PasswordConfigValueConverter diff --git a/site/src/sphinx/mirroring.rst b/site/src/sphinx/mirroring.rst index 41da90b2b2..a611e86c8d 100644 --- a/site/src/sphinx/mirroring.rst +++ b/site/src/sphinx/mirroring.rst @@ -106,11 +106,9 @@ Setting up a mirroring task - Fragment represents a branch name. e.g. ``#release`` will mirror the branch ``release``. If unspecified, the repository's default branch is mirrored. -- ``credentialId`` (string, optional) +- ``credentialId`` (string) - the ID of the credential to use for authentication, as defined in ``/credentials/{credential-id}.json``. - If unspecified, the credential whose ``hostnamePattern`` is matched by the host name part of the - ``remoteUri`` value will be selected automatically. - ``gitignore`` (string or array of strings, optional) @@ -131,10 +129,7 @@ the Git repositories defined in ``/mirrors/{mirror-id}.json``: { "id": "no_auth", - "type": "none", - "hostnamePatterns": [ - "^git\.insecure\.com$" - ] + "type": "none" } * Password-based authentication @@ -144,9 +139,6 @@ the Git repositories defined in ``/mirrors/{mirror-id}.json``: { "id": "my_password", "type": "password", - "hostnamePatterns": [ - "^git\.password-protected\.com$" - ], "username": "alice", "password": "secret!" } @@ -158,9 +150,6 @@ the Git repositories defined in ``/mirrors/{mirror-id}.json``: { "id": "my_private_key", "type": "public_key", - "hostnamePatterns": [ - "^.*\.secure\.com$" - ], "username": "git", "publicKey": "ssh-ed25519 ... user@host", "privateKey": "-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----\n", @@ -188,12 +177,6 @@ the Git repositories defined in ``/mirrors/{mirror-id}.json``: - the type of authentication mechanism: ``none``, ``password``, ``public_key`` or ``access_token``. -- ``hostnamePatterns`` (array of strings, optional) - - - the regular expression that matches a host name. The credential whose hostname pattern matches first will - be used when accessing a host. You may want to omit this field if you do not want the credential to be - selected automatically, i.e. a mirror has to specify the ``credentialId`` field. - - ``username`` (string) - the user name. You must specify this field if you use a credential whose type is ``password`` or diff --git a/webapp/src/dogma/features/project/settings/credentials/CredentialDto.ts b/webapp/src/dogma/features/project/settings/credentials/CredentialDto.ts index 5107bbf854..b6ea44e460 100644 --- a/webapp/src/dogma/features/project/settings/credentials/CredentialDto.ts +++ b/webapp/src/dogma/features/project/settings/credentials/CredentialDto.ts @@ -17,7 +17,6 @@ export interface CredentialDto { id: string; type: 'password' | 'public_key' | 'access_token' | 'none'; - hostnamePatterns: string[]; enabled: boolean; // Password-based credential diff --git a/webapp/src/dogma/features/project/settings/credentials/CredentialForm.tsx b/webapp/src/dogma/features/project/settings/credentials/CredentialForm.tsx index ab212017a6..94598acf31 100644 --- a/webapp/src/dogma/features/project/settings/credentials/CredentialForm.tsx +++ b/webapp/src/dogma/features/project/settings/credentials/CredentialForm.tsx @@ -23,10 +23,7 @@ import { FormLabel, Heading, HStack, - IconButton, Input, - InputGroup, - InputLeftElement, Radio, RadioGroup, Spacer, @@ -36,12 +33,10 @@ import { VStack, } from '@chakra-ui/react'; import { HiOutlineIdentification, HiOutlineUser } from 'react-icons/hi'; -import { VscRegex } from 'react-icons/vsc'; -import { AddIcon } from '@chakra-ui/icons'; -import { MdDelete, MdPublic } from 'react-icons/md'; +import { MdPublic } from 'react-icons/md'; import { GoKey, GoLock } from 'react-icons/go'; import { RiGitRepositoryPrivateLine } from 'react-icons/ri'; -import { useFieldArray, useForm } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; import React, { useState } from 'react'; import { LabelledIcon } from 'dogma/common/components/LabelledIcon'; import FieldErrorMessage from 'dogma/common/components/form/FieldErrorMessage'; @@ -76,20 +71,8 @@ const CredentialForm = ({ projectName, defaultValue, onSubmit, isWaitingResponse handleSubmit, reset, formState: { errors }, - control, } = useForm({ - defaultValues: { - hostnamePatterns: defaultValue.hostnamePatterns, - }, - }); - const { - fields: hostnamePatterns, - append, - remove, - } = useFieldArray({ - control, - // @ts-expect-error The type of the field array is not correctly inferred. - name: 'hostnamePatterns', + defaultValues: {}, }); /** @@ -288,54 +271,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX )} - - {credentialType === 'none' && <>{/* No additional information is required */}} - - - - - - } - ml={1} - mb={0.5} - size={'sm'} - onClick={() => append('')} - /> - - {hostnamePatterns.map((item, index) => { - return ( - <> - - - - } - mr={1} - size={'sm'} - onClick={() => remove(index)} - > - - - - ); - })} - - The credential whose hostname pattern matches first will be used when accessing a host. - - {isNew ? (