mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 09:46:16 +01:00
Add the repository import and export with metadata for Subversion repositories (#1501)
* Add store exporter to collect the repository metadata * Add EnvironmentInformationXmlGenerator * Collect export data and put into compressed tar archive output stream * Create full repository export endpoint. * Add full repository export to ui * Ignore irrelevant files from config store directory * write metadata stores to file since a baos could teardown the server memory * Migrate store name for git lfs files (#1504) Changes the directory name for the git LFS blob store by removing the repository id from the store name. This is necessary for im- and exports of lfs blob stores, because the original name had the repository id as a part of it and therefore the old store would not be found when the repository is imported with another id. Existing blob files will be moved to the new store location by an update step. Co-authored-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com> * Introduce util for migrations (#1505) With this util it is more simple to rename or delete stores. * Rename files in export Co-authored-by: René Pfeuffer <rene.pfeuffer@cloudogu.com>
This commit is contained in:
52
scm-dao-xml/src/main/java/sonia/scm/store/ExportCopier.java
Normal file
52
scm-dao-xml/src/main/java/sonia/scm/store/ExportCopier.java
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import sonia.scm.repository.api.ExportFailedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.noContext;
|
||||
|
||||
final class ExportCopier {
|
||||
|
||||
private ExportCopier() {
|
||||
}
|
||||
|
||||
static void putFileContentIntoStream(Exporter exporter, Path file) {
|
||||
try (OutputStream stream = exporter.put(file.getFileName().toString(), Files.size(file))) {
|
||||
Files.copy(file, stream);
|
||||
} catch (IOException e) {
|
||||
throw new ExportFailedException(
|
||||
noContext(),
|
||||
"Could not copy file to export stream: " + file,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
|
||||
class ExportableBlobFileStore extends ExportableDirectoryBasedFileStore {
|
||||
|
||||
static Function<StoreType, Optional<Function<Path, ExportableStore>>> BLOB_FACTORY =
|
||||
storeType -> storeType == StoreType.BLOB ? of(ExportableBlobFileStore::new) : empty();
|
||||
|
||||
ExportableBlobFileStore(Path directory) {
|
||||
super(directory);
|
||||
}
|
||||
|
||||
@Override
|
||||
StoreType getStoreType() {
|
||||
return StoreType.BLOB;
|
||||
}
|
||||
|
||||
boolean shouldIncludeFile(Path file) {
|
||||
return file.getFileName().toString().endsWith(".blob");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static sonia.scm.store.ExportCopier.putFileContentIntoStream;
|
||||
|
||||
class ExportableConfigFileStore implements ExportableStore {
|
||||
|
||||
private final Path file;
|
||||
|
||||
static Function<StoreType, Optional<Function<Path, ExportableStore>>> CONFIG_FACTORY =
|
||||
storeType -> storeType == StoreType.CONFIG ? of(ExportableConfigFileStore::new) : empty();
|
||||
|
||||
ExportableConfigFileStore(Path file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreEntryMetaData getMetaData() {
|
||||
return new StoreEntryMetaData(StoreType.CONFIG, file.getFileName().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void export(Exporter exporter) throws IOException {
|
||||
if (file.getFileName().toString().endsWith(".xml")) {
|
||||
putFileContentIntoStream(exporter, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
|
||||
class ExportableDataFileStore extends ExportableDirectoryBasedFileStore {
|
||||
|
||||
static Function<StoreType, Optional<Function<Path, ExportableStore>>> DATA_FACTORY =
|
||||
storeType -> storeType == StoreType.DATA ? of(ExportableDataFileStore::new) : empty();
|
||||
|
||||
ExportableDataFileStore(Path directory) {
|
||||
super(directory);
|
||||
}
|
||||
|
||||
@Override
|
||||
StoreType getStoreType() {
|
||||
return StoreType.DATA;
|
||||
}
|
||||
|
||||
boolean shouldIncludeFile(Path file) {
|
||||
return file.getFileName().toString().endsWith(".xml");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import sonia.scm.repository.api.ExportFailedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.noContext;
|
||||
import static sonia.scm.store.ExportCopier.putFileContentIntoStream;
|
||||
|
||||
abstract class ExportableDirectoryBasedFileStore implements ExportableStore {
|
||||
|
||||
private final Path directory;
|
||||
|
||||
ExportableDirectoryBasedFileStore(Path directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreEntryMetaData getMetaData() {
|
||||
return new StoreEntryMetaData(getStoreType(), directory.getFileName().toString());
|
||||
}
|
||||
|
||||
abstract StoreType getStoreType();
|
||||
|
||||
abstract boolean shouldIncludeFile(Path file);
|
||||
|
||||
@Override
|
||||
public void export(Exporter exporter) throws IOException {
|
||||
exportDirectoryEntries(exporter, directory);
|
||||
}
|
||||
|
||||
private void exportDirectoryEntries(Exporter exporter, Path directory) {
|
||||
try (Stream<Path> fileList = Files.list(directory)) {
|
||||
fileList.forEach(fileOrDir -> exportIfRelevant(exporter, fileOrDir));
|
||||
} catch (IOException e) {
|
||||
throw new ExportFailedException(
|
||||
noContext(),
|
||||
"Could not read directory " + directory,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void exportIfRelevant(Exporter exporter, Path fileOrDir) {
|
||||
if (!Files.isDirectory(fileOrDir) && shouldIncludeFile(fileOrDir)) {
|
||||
putFileContentIntoStream(exporter, fileOrDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.repository.api.ImportFailedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class FileBasedStoreEntryImporter implements StoreEntryImporter {
|
||||
|
||||
private final Path directory;
|
||||
|
||||
FileBasedStoreEntryImporter(Path directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Path getDirectory() {
|
||||
return this.directory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importEntry(String name, InputStream stream) {
|
||||
Path filePath = directory.resolve(name);
|
||||
try {
|
||||
Files.copy(stream, filePath);
|
||||
} catch (IOException e) {
|
||||
throw new ImportFailedException(
|
||||
ContextEntry.ContextBuilder.noContext(),
|
||||
String.format("Could not import file %s for store %s", name, directory.toString()),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.repository.api.ImportFailedException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
class FileBasedStoreEntryImporterFactory implements StoreEntryImporterFactory {
|
||||
|
||||
private final Path directory;
|
||||
|
||||
FileBasedStoreEntryImporterFactory(Path directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreEntryImporter importStore(StoreEntryMetaData metaData) {
|
||||
StoreType storeType = metaData.getType();
|
||||
String storeName = metaData.getName();
|
||||
Path storeDirectory = directory.resolve(Store.STORE_DIRECTORY);
|
||||
try {
|
||||
storeDirectory = storeDirectory.resolve(resolveFilePath(storeType.getValue(), storeName));
|
||||
Files.createDirectories(storeDirectory);
|
||||
if (!Files.exists(storeDirectory)) {
|
||||
throw new ImportFailedException(
|
||||
ContextEntry.ContextBuilder.noContext(),
|
||||
String.format("Could not create store for type %s and name %s", storeType, storeName)
|
||||
);
|
||||
}
|
||||
return new FileBasedStoreEntryImporter(storeDirectory);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new ImportFailedException(
|
||||
ContextEntry.ContextBuilder.noContext(),
|
||||
String.format("Could not create store directory %s for type %s and name %s", storeDirectory, storeType, storeName)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Path resolveFilePath(String type, String name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return Paths.get(type);
|
||||
}
|
||||
return Paths.get(type, name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.update.RepositoryUpdateIterator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class FileRepositoryUpdateIterator implements RepositoryUpdateIterator {
|
||||
|
||||
private final RepositoryLocationResolver locationResolver;
|
||||
|
||||
@Inject
|
||||
public FileRepositoryUpdateIterator(RepositoryLocationResolver locationResolver) {
|
||||
this.locationResolver = locationResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachRepository(Consumer<String> repositoryIdConsumer) {
|
||||
locationResolver
|
||||
.forClass(Path.class)
|
||||
.forAllLocations((repositoryId, path) -> repositoryIdConsumer.accept(repositoryId));
|
||||
}
|
||||
}
|
||||
121
scm-dao-xml/src/main/java/sonia/scm/store/FileStoreExporter.java
Normal file
121
scm-dao-xml/src/main/java/sonia/scm/store/FileStoreExporter.java
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.repository.api.ExportFailedException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.noContext;
|
||||
import static sonia.scm.store.ExportableBlobFileStore.BLOB_FACTORY;
|
||||
import static sonia.scm.store.ExportableConfigFileStore.CONFIG_FACTORY;
|
||||
import static sonia.scm.store.ExportableDataFileStore.DATA_FACTORY;
|
||||
|
||||
public class FileStoreExporter implements StoreExporter {
|
||||
|
||||
private final RepositoryLocationResolver locationResolver;
|
||||
|
||||
private static final Collection<Function<StoreType, Optional<Function<Path, ExportableStore>>>> STORE_FACTORIES =
|
||||
asList(DATA_FACTORY, BLOB_FACTORY, CONFIG_FACTORY);
|
||||
|
||||
@Inject
|
||||
public FileStoreExporter(RepositoryLocationResolver locationResolver) {
|
||||
this.locationResolver = locationResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExportableStore> listExportableStores(Repository repository) {
|
||||
List<ExportableStore> exportableStores = new ArrayList<>();
|
||||
Path storeDirectory = resolveStoreDirectory(repository);
|
||||
if (!Files.exists(storeDirectory)) {
|
||||
return emptyList();
|
||||
}
|
||||
try (Stream<Path> storeTypeDirectories = Files.list(storeDirectory)) {
|
||||
storeTypeDirectories.forEach(storeTypeDirectory ->
|
||||
exportStoreTypeDirectories(exportableStores, storeTypeDirectory)
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new ExportFailedException(
|
||||
noContext(),
|
||||
"Could not list content of directory " + storeDirectory,
|
||||
e
|
||||
);
|
||||
}
|
||||
return exportableStores;
|
||||
}
|
||||
|
||||
private Path resolveStoreDirectory(Repository repository) {
|
||||
return locationResolver
|
||||
.forClass(Path.class)
|
||||
.getLocation(repository.getId())
|
||||
.resolve(Store.STORE_DIRECTORY);
|
||||
}
|
||||
|
||||
private void exportStoreTypeDirectories(List<ExportableStore> exportableStores, Path storeTypeDirectory) {
|
||||
try (Stream<Path> storeDirectories = Files.list(storeTypeDirectory)) {
|
||||
storeDirectories.forEach(storeDirectory ->
|
||||
getStoreFor(storeDirectory).ifPresent(exportableStores::add)
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new ExportFailedException(
|
||||
noContext(),
|
||||
"Could not list content of directory " + storeTypeDirectory,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<ExportableStore> getStoreFor(Path storePath) {
|
||||
return STORE_FACTORIES
|
||||
.stream()
|
||||
.map(factory -> factory.apply(getEnumForValue(storePath.getParent())))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.findFirst()
|
||||
.map(f -> f.apply(storePath));
|
||||
}
|
||||
|
||||
private StoreType getEnumForValue(Path storeTypeDirectory) {
|
||||
for (StoreType type : StoreType.values()) {
|
||||
if (type.getValue().equals(storeTypeDirectory.getFileName().toString())) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.update.StoreUpdateStepUtilFactory;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class FileStoreUpdateStepUtil implements StoreUpdateStepUtilFactory.StoreUpdateStepUtil {
|
||||
|
||||
private final RepositoryLocationResolver locationResolver;
|
||||
private final SCMContextProvider contextProvider;
|
||||
|
||||
private final StoreParameters parameters;
|
||||
private final StoreType type;
|
||||
|
||||
FileStoreUpdateStepUtil(RepositoryLocationResolver locationResolver, SCMContextProvider contextProvider, StoreParameters parameters, StoreType type) {
|
||||
this.locationResolver = locationResolver;
|
||||
this.contextProvider = contextProvider;
|
||||
this.parameters = parameters;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renameStore(String newName) {
|
||||
Path oldStorePath = resolveBasePath().resolve(parameters.getName());
|
||||
if (Files.exists(oldStorePath)) {
|
||||
Path newStorePath = resolveBasePath().resolve(newName);
|
||||
try {
|
||||
Files.move(oldStorePath, newStorePath);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException(String.format("Could not move store path %s to %s", oldStorePath, newStorePath), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteStore() {
|
||||
Path oldStorePath = resolveBasePath().resolve(parameters.getName());
|
||||
IOUtil.deleteSilently(oldStorePath.toFile());
|
||||
}
|
||||
|
||||
private Path resolveBasePath() {
|
||||
Path basePath;
|
||||
if (parameters.getRepositoryId() != null) {
|
||||
basePath = locationResolver.forClass(Path.class).getLocation(parameters.getRepositoryId());
|
||||
} else {
|
||||
basePath = contextProvider.getBaseDirectory().toPath();
|
||||
}
|
||||
Path storeBasePath;
|
||||
if (parameters.getRepositoryId() == null) {
|
||||
storeBasePath = basePath.resolve(Store.forStoreType(type).getGlobalStoreDirectory());
|
||||
} else {
|
||||
storeBasePath = basePath.resolve(Store.forStoreType(type).getRepositoryStoreDirectory());
|
||||
}
|
||||
return storeBasePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.update.StoreUpdateStepUtilFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class FileStoreUpdateStepUtilFactory implements StoreUpdateStepUtilFactory {
|
||||
|
||||
private final RepositoryLocationResolver locationResolver;
|
||||
private final SCMContextProvider contextProvider;
|
||||
|
||||
@Inject
|
||||
public FileStoreUpdateStepUtilFactory(RepositoryLocationResolver locationResolver, SCMContextProvider contextProvider) {
|
||||
this.locationResolver = locationResolver;
|
||||
this.contextProvider = contextProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreUpdateStepUtil build(StoreType type, StoreParameters parameters) {
|
||||
return new FileStoreUpdateStepUtil(locationResolver, contextProvider, parameters, type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class RepositoryStoreImporter implements StoreImporter {
|
||||
|
||||
private final RepositoryLocationResolver locationResolver;
|
||||
|
||||
@Inject
|
||||
public RepositoryStoreImporter(RepositoryLocationResolver locationResolver) {
|
||||
this.locationResolver = locationResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreEntryImporterFactory doImport(Repository repository) {
|
||||
Path storeLocation = locationResolver
|
||||
.forClass(Path.class)
|
||||
.getLocation(repository.getId());
|
||||
return new FileBasedStoreEntryImporterFactory(storeLocation);
|
||||
}
|
||||
}
|
||||
@@ -21,18 +21,32 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public enum Store {
|
||||
enum Store {
|
||||
CONFIG("config"),
|
||||
DATA("data"),
|
||||
BLOB("blob");
|
||||
|
||||
private static final String GLOBAL_STORE_BASE_DIRECTORY = "var";
|
||||
private static final String STORE_DIRECTORY = "store";
|
||||
static final String STORE_DIRECTORY = "store";
|
||||
|
||||
static Store forStoreType(StoreType storeType) {
|
||||
switch (storeType) {
|
||||
case BLOB:
|
||||
return BLOB;
|
||||
case DATA:
|
||||
return DATA;
|
||||
case CONFIG:
|
||||
case CONFIG_ENTRY:
|
||||
return CONFIG;
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown store type: " + storeType);
|
||||
}
|
||||
}
|
||||
|
||||
private String directory;
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ExportableFileStoreTest {
|
||||
|
||||
@Mock
|
||||
Exporter exporter;
|
||||
|
||||
@Test
|
||||
void shouldNotPutContentIfNoFilesExists(@TempDir Path temp) throws IOException {
|
||||
Path dataStoreDir = temp.resolve("some-store");
|
||||
Files.createDirectories(dataStoreDir);
|
||||
|
||||
ExportableStore exportableFileStore = new ExportableDataFileStore(dataStoreDir);
|
||||
exportableFileStore.export(exporter);
|
||||
|
||||
verify(exporter, never()).put(anyString(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPutContentIntoExporterForDataStore(@TempDir Path temp) throws IOException {
|
||||
createFile(temp, "data", "trace", "first.xml");
|
||||
createFile(temp, "data", "trace", "second.xml");
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ExportableStore exportableFileStore = new ExportableDataFileStore(temp.resolve("data").resolve("trace"));
|
||||
when(exporter.put(anyString(), anyLong())).thenReturn(os);
|
||||
|
||||
exportableFileStore.export(exporter);
|
||||
|
||||
verify(exporter).put(eq("first.xml"), anyLong());
|
||||
verify(exporter).put(eq("second.xml"), anyLong());
|
||||
assertThat(os.toString()).isNotBlank();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPutContentIntoExporterForConfigStore(@TempDir Path temp) throws IOException {
|
||||
createFile(temp, "config", "", "first.xml");
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ExportableStore exportableConfigFileStore = new ExportableConfigFileStore(temp.resolve("config").resolve("first.xml"));
|
||||
when(exporter.put(anyString(), anyLong())).thenReturn(os);
|
||||
|
||||
exportableConfigFileStore.export(exporter);
|
||||
|
||||
verify(exporter).put(eq("first.xml"), anyLong());
|
||||
assertThat(os.toString()).isNotBlank();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFilterNoneConfigFiles(@TempDir Path temp) throws IOException {
|
||||
createFile(temp, "config", "", "first.bck");
|
||||
ExportableStore exportableConfigFileStore = new ExportableConfigFileStore(temp.resolve("config").resolve("first.bck"));
|
||||
|
||||
exportableConfigFileStore.export(exporter);
|
||||
|
||||
verify(exporter, never()).put(anyString(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPutContentIntoExporterForBlobStore(@TempDir Path temp) throws IOException {
|
||||
createFile(temp, "blob", "assets", "first.blob");
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
Exporter exporter = mock(Exporter.class);
|
||||
ExportableStore exportableBlobFileStore = new ExportableBlobFileStore(temp.resolve("blob").resolve("assets"));
|
||||
when(exporter.put(anyString(), anyLong())).thenReturn(os);
|
||||
|
||||
exportableBlobFileStore.export(exporter);
|
||||
|
||||
verify(exporter).put(eq("first.blob"), anyLong());
|
||||
assertThat(os.toString()).isNotBlank();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipFilteredBlobFiles(@TempDir Path temp) throws IOException {
|
||||
createFile(temp, "blob", "security", "second.xml");
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
Exporter exporter = mock(Exporter.class);
|
||||
ExportableStore exportableBlobFileStore = new ExportableBlobFileStore(temp.resolve("blob").resolve("security"));
|
||||
|
||||
exportableBlobFileStore.export(exporter);
|
||||
|
||||
verify(exporter, never()).put(anyString(), anyLong());
|
||||
assertThat(os.toString()).isBlank();
|
||||
}
|
||||
|
||||
private File createFile(Path temp, String type, String name, String fileName) throws IOException {
|
||||
Path path = name != null ? temp.resolve(type).resolve(name) : temp.resolve(type);
|
||||
|
||||
new File(path.toUri()).mkdirs();
|
||||
File file = new File(path.toFile(), fileName);
|
||||
if (!file.exists()) {
|
||||
file.createNewFile();
|
||||
}
|
||||
FileWriter source = new FileWriter(file);
|
||||
source.write("something");
|
||||
source.close();
|
||||
return file;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package sonia.scm.store;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class FileBasedStoreEntryImporterFactoryTest {
|
||||
|
||||
@Test
|
||||
void shouldCreateStoreEntryImporterForDataStore(@TempDir Path temp) {
|
||||
FileBasedStoreEntryImporterFactory factory = new FileBasedStoreEntryImporterFactory(temp);
|
||||
|
||||
FileBasedStoreEntryImporter dataImporter = (FileBasedStoreEntryImporter) factory.importStore(new StoreEntryMetaData(StoreType.DATA, "hitchhiker"));
|
||||
assertThat(dataImporter.getDirectory()).isEqualTo(temp.resolve("store").resolve("data").resolve("hitchhiker"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateStoreEntryImporterForConfigStore(@TempDir Path temp) {
|
||||
FileBasedStoreEntryImporterFactory factory = new FileBasedStoreEntryImporterFactory(temp);
|
||||
|
||||
FileBasedStoreEntryImporter configImporter = (FileBasedStoreEntryImporter) factory.importStore(new StoreEntryMetaData(StoreType.CONFIG, ""));
|
||||
assertThat(configImporter.getDirectory()).isEqualTo(temp.resolve("store").resolve("config"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class FileBasedStoreEntryImporterTest {
|
||||
|
||||
@Test
|
||||
void shouldCreateFileFromInputStream(@TempDir Path temp) {
|
||||
FileBasedStoreEntryImporter importer = new FileBasedStoreEntryImporter(temp);
|
||||
String fileName = "testStore.xml";
|
||||
|
||||
importer.importEntry(fileName, new ByteArrayInputStream("testdata".getBytes()));
|
||||
|
||||
assertThat(Files.exists(temp.resolve(fileName))).isTrue();
|
||||
assertThat(temp.resolve(fileName)).hasContent("testdata");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.repository.RepositoryTestData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FileStoreExporterTest {
|
||||
|
||||
private static final Repository REPOSITORY = RepositoryTestData.create42Puzzle();
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private RepositoryLocationResolver resolver;
|
||||
|
||||
@InjectMocks
|
||||
private FileStoreExporter fileStoreExporter;
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyList(@TempDir Path temp) {
|
||||
when(resolver.forClass(Path.class).getLocation(REPOSITORY.getId())).thenReturn(temp);
|
||||
|
||||
List<ExportableStore> exportableStores = fileStoreExporter.listExportableStores(REPOSITORY);
|
||||
assertThat(exportableStores).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnListOfExportableStores(@TempDir Path temp) throws IOException {
|
||||
Path storePath = temp.resolve("store");
|
||||
createFile(storePath, StoreType.CONFIG.getValue(), null, "first.xml");
|
||||
createFile(storePath, StoreType.DATA.getValue(), "ci", "second.xml");
|
||||
createFile(storePath, StoreType.DATA.getValue(), "jenkins", "third.xml");
|
||||
when(resolver.forClass(Path.class).getLocation(REPOSITORY.getId())).thenReturn(temp);
|
||||
|
||||
List<ExportableStore> exportableStores = fileStoreExporter.listExportableStores(REPOSITORY);
|
||||
|
||||
assertThat(exportableStores).hasSize(3);
|
||||
assertThat(exportableStores.stream().filter(e -> e.getMetaData().getType().equals(StoreType.CONFIG))).hasSize(1);
|
||||
assertThat(exportableStores.stream().filter(e -> e.getMetaData().getType().equals(StoreType.DATA))).hasSize(2);
|
||||
}
|
||||
|
||||
private void createFile(Path storePath, String type, String name, String fileName) throws IOException {
|
||||
Path path = name != null ? storePath.resolve(type).resolve(name) : storePath.resolve(type);
|
||||
Files.createDirectories(path);
|
||||
Path file = path.resolve(fileName);
|
||||
if (!Files.exists(file)) {
|
||||
Files.createFile(file);
|
||||
}
|
||||
Files.write(file, Collections.singletonList("something"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.update.StoreUpdateStepUtilFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FileStoreUpdateStepUtilFactoryTest {
|
||||
|
||||
@Mock
|
||||
private RepositoryLocationResolver locationResolver;
|
||||
@Mock
|
||||
private RepositoryLocationResolver.RepositoryLocationResolverInstance locationResolverInstance;
|
||||
@Mock
|
||||
private SCMContextProvider contextProvider;
|
||||
|
||||
@InjectMocks
|
||||
private FileStoreUpdateStepUtilFactory factory;
|
||||
|
||||
private Path globalPath;
|
||||
private Path repositoryPath;
|
||||
|
||||
@BeforeEach
|
||||
void initPaths(@TempDir Path temp) throws IOException {
|
||||
globalPath = temp.resolve("global");
|
||||
Files.createDirectories(globalPath);
|
||||
lenient().doReturn(globalPath.toFile()).when(contextProvider).getBaseDirectory();
|
||||
|
||||
repositoryPath = temp.resolve("repo");
|
||||
Files.createDirectories(repositoryPath);
|
||||
lenient().doReturn(true).when(locationResolver).supportsLocationType(Path.class);
|
||||
lenient().doReturn(locationResolverInstance).when(locationResolver).forClass(Path.class);
|
||||
lenient().doReturn(repositoryPath).when(locationResolverInstance).getLocation("repo-id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMoveGlobalDataDirectory() throws IOException {
|
||||
Path dataPath = globalPath.resolve("var").resolve("data");
|
||||
Files.createDirectories(dataPath.resolve("something"));
|
||||
Files.createFile(dataPath.resolve("something").resolve("some.file"));
|
||||
StoreUpdateStepUtilFactory.StoreUpdateStepUtil util =
|
||||
factory
|
||||
.forType(StoreType.DATA)
|
||||
.forName("something")
|
||||
.build();
|
||||
|
||||
util.renameStore("new-name");
|
||||
|
||||
assertThat(dataPath.resolve("new-name").resolve("some.file")).exists();
|
||||
assertThat(dataPath.resolve("something")).doesNotExist();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMoveRepositoryDataDirectory() throws IOException {
|
||||
Path dataPath = repositoryPath.resolve("store").resolve("data");
|
||||
Files.createDirectories(dataPath.resolve("something"));
|
||||
Files.createFile(dataPath.resolve("something").resolve("some.file"));
|
||||
StoreUpdateStepUtilFactory.StoreUpdateStepUtil util =
|
||||
factory
|
||||
.forType(StoreType.DATA)
|
||||
.forName("something")
|
||||
.forRepository("repo-id")
|
||||
.build();
|
||||
|
||||
util.renameStore("new-name");
|
||||
|
||||
assertThat(dataPath.resolve("new-name").resolve("some.file")).exists();
|
||||
assertThat(dataPath.resolve("something")).doesNotExist();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleMissingMoveGlobalDataDirectory() throws IOException {
|
||||
StoreUpdateStepUtilFactory.StoreUpdateStepUtil util =
|
||||
factory
|
||||
.forType(StoreType.DATA)
|
||||
.forName("something")
|
||||
.build();
|
||||
|
||||
util.renameStore("new-name");
|
||||
|
||||
assertThat(globalPath.resolve("new-name")).doesNotExist();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package sonia.scm.store;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.repository.RepositoryTestData;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RepositoryStoreImporterTest {
|
||||
private static final Repository REPOSITORY = RepositoryTestData.createHeartOfGold();
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private RepositoryLocationResolver locationResolver;
|
||||
|
||||
@InjectMocks
|
||||
private RepositoryStoreImporter repositoryStoreImporter;
|
||||
|
||||
@Test
|
||||
void shouldImportStore() {
|
||||
StoreEntryImporterFactory storeEntryImporterFactory = repositoryStoreImporter.doImport(REPOSITORY);
|
||||
assertThat(storeEntryImporterFactory).isInstanceOf(StoreEntryImporterFactory.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
mock-maker-inline
|
||||
Reference in New Issue
Block a user