Repository export read-only lock (#1519)

* Lock repository for read-only access only while exporting
* Create read-only check api

Co-authored-by: René Pfeuffer <rene.pfeuffer@cloudogu.com>
This commit is contained in:
Eduard Heimbuch
2021-02-04 15:29:49 +01:00
committed by GitHub
parent 04c6243f64
commit ac5d145266
54 changed files with 1104 additions and 182 deletions

View File

@@ -58,6 +58,7 @@ public class RepositoryDto extends HalRepresentation implements CreateRepository
@NotEmpty
private String type;
private boolean archived;
private boolean exporting;
RepositoryDto(Links links, Embedded embedded) {
super(links, embedded);

View File

@@ -161,12 +161,7 @@ public class RepositoryExportResource {
@PathParam("name") String name
) {
Repository repository = getVerifiedRepository(namespace, name);
StreamingOutput output = os -> fullScmRepositoryExporter.export(repository, os);
return Response
.ok(output, "application/x-gzip")
.header("content-disposition", createContentDispositionHeaderValue(repository, "tar.gz"))
.build();
return exportFullRepository(repository);
}
private Repository getVerifiedRepository(String namespace, String name) {
@@ -186,6 +181,15 @@ public class RepositoryExportResource {
return repository;
}
private Response exportFullRepository(Repository repository) {
StreamingOutput output = os -> fullScmRepositoryExporter.export(repository, os);
return Response
.ok(output, "application/x-gzip")
.header("content-disposition", createContentDispositionHeaderValue(repository, "tar.gz"))
.build();
}
private Response exportRepository(Repository repository, boolean compressed) {
StreamingOutput output;
String fileExtension;

View File

@@ -27,9 +27,12 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.ObjectFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.DefaultRepositoryExportingCheck;
import sonia.scm.repository.Feature;
import sonia.scm.repository.HealthCheckFailure;
import sonia.scm.repository.NamespaceStrategy;
@@ -70,6 +73,11 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
@Override
public abstract RepositoryDto map(Repository modelObject);
@AfterMapping
void setExporting(Repository repository, @MappingTarget RepositoryDto repositoryDto) {
repositoryDto.setExporting(DefaultRepositoryExportingCheck.isRepositoryExporting(repository.getId()));
}
@ObjectFactory
RepositoryDto createDto(Repository repository) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.repository().self(repository.getNamespace(), repository.getName()));

View File

@@ -29,6 +29,7 @@ import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import sonia.scm.ContextEntry;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryExportingCheck;
import sonia.scm.repository.api.ExportFailedException;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
@@ -54,25 +55,36 @@ public class FullScmRepositoryExporter {
private final RepositoryServiceFactory serviceFactory;
private final TarArchiveRepositoryStoreExporter storeExporter;
private final WorkdirProvider workdirProvider;
private final RepositoryExportingCheck repositoryExportingCheck;
@Inject
public FullScmRepositoryExporter(EnvironmentInformationXmlGenerator environmentGenerator,
RepositoryMetadataXmlGenerator metadataGenerator,
RepositoryServiceFactory serviceFactory,
TarArchiveRepositoryStoreExporter storeExporter, WorkdirProvider workdirProvider) {
TarArchiveRepositoryStoreExporter storeExporter,
WorkdirProvider workdirProvider,
RepositoryExportingCheck repositoryExportingCheck) {
this.environmentGenerator = environmentGenerator;
this.metadataGenerator = metadataGenerator;
this.serviceFactory = serviceFactory;
this.storeExporter = storeExporter;
this.workdirProvider = workdirProvider;
this.repositoryExportingCheck = repositoryExportingCheck;
}
public void export(Repository repository, OutputStream outputStream) {
repositoryExportingCheck.withExportingLock(repository, () -> {
exportInLock(repository, outputStream);
return null;
});
}
private void exportInLock(Repository repository, OutputStream outputStream) {
try (
RepositoryService service = serviceFactory.create(repository);
BufferedOutputStream bos = new BufferedOutputStream(outputStream);
GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(bos);
TarArchiveOutputStream taos = new TarArchiveOutputStream(gzos);
TarArchiveOutputStream taos = new TarArchiveOutputStream(gzos)
) {
writeEnvironmentData(taos);
writeMetadata(repository, taos);

View File

@@ -36,8 +36,10 @@ import sonia.scm.io.FileSystem;
import sonia.scm.lifecycle.DefaultRestarter;
import sonia.scm.lifecycle.Restarter;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.DefaultRepositoryExportingCheck;
import sonia.scm.repository.EventDrivenRepositoryArchiveCheck;
import sonia.scm.repository.RepositoryArchivedCheck;
import sonia.scm.repository.RepositoryExportingCheck;
import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.repository.xml.MetadataStore;
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
@@ -100,6 +102,7 @@ public class BootstrapModule extends AbstractModule {
// bind core
bind(RepositoryArchivedCheck.class, EventDrivenRepositoryArchiveCheck.class);
bind(RepositoryExportingCheck.class, DefaultRepositoryExportingCheck.class);
bind(ConfigurationStoreFactory.class, JAXBConfigurationStoreFactory.class);
bind(ConfigurationEntryStoreFactory.class, JAXBConfigurationEntryStoreFactory.class);
bind(DataStoreFactory.class, JAXBDataStoreFactory.class);

View File

@@ -39,7 +39,6 @@ import sonia.scm.NoChangesMadeException;
import sonia.scm.NotFoundException;
import sonia.scm.SCMContextProvider;
import sonia.scm.Type;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.event.ScmEventBus;
import sonia.scm.security.AuthorizationChangedEvent;
import sonia.scm.security.KeyGenerator;
@@ -79,7 +78,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
private static final String THREAD_NAME = "Hook-%s";
private static final Logger logger =
LoggerFactory.getLogger(DefaultRepositoryManager.class);
private final ScmConfiguration configuration;
private final ExecutorService executorService;
private final Map<String, RepositoryHandler> handlerMap;
private final KeyGenerator keyGenerator;
@@ -89,11 +87,9 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
private final ManagerDaoAdapter<Repository> managerDaoAdapter;
@Inject
public DefaultRepositoryManager(ScmConfiguration configuration,
SCMContextProvider contextProvider, KeyGenerator keyGenerator,
public DefaultRepositoryManager(SCMContextProvider contextProvider, KeyGenerator keyGenerator,
RepositoryDAO repositoryDAO, Set<RepositoryHandler> handlerSet,
Provider<NamespaceStrategy> namespaceStrategyProvider) {
this.configuration = configuration;
this.keyGenerator = keyGenerator;
this.repositoryDAO = repositoryDAO;
this.namespaceStrategyProvider = namespaceStrategyProvider;