mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 16:35:45 +01:00
Bootstrap migration strategies
This commit is contained in:
@@ -95,10 +95,17 @@ public class XmlRepositoryDAO implements RepositoryDAO {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(Repository repository) {
|
public void add(Repository repository) {
|
||||||
|
add(repository, repositoryLocationResolver.create(repository.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Repository repository, Object location) {
|
||||||
|
if (!(location instanceof Path)) {
|
||||||
|
throw new IllegalArgumentException("can only handle locations of type " + Path.class.getName() + ", not of type " + location.getClass().getName());
|
||||||
|
}
|
||||||
Repository clone = repository.clone();
|
Repository clone = repository.clone();
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
Path repositoryPath = repositoryLocationResolver.create(repository.getId());
|
Path repositoryPath = (Path) location;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Path metadataPath = resolveDataPath(repositoryPath);
|
Path metadataPath = resolveDataPath(repositoryPath);
|
||||||
@@ -111,10 +118,8 @@ public class XmlRepositoryDAO implements RepositoryDAO {
|
|||||||
byId.put(repository.getId(), clone);
|
byId.put(repository.getId(), clone);
|
||||||
byNamespaceAndName.put(repository.getNamespaceAndName(), clone);
|
byNamespaceAndName.put(repository.getNamespaceAndName(), clone);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(Repository repository) {
|
public boolean contains(Repository repository) {
|
||||||
return byId.containsKey(repository.getId());
|
return byId.containsKey(repository.getId());
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package sonia.scm.repository.update;
|
||||||
|
|
||||||
|
import sonia.scm.SCMContextProvider;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
class CopyMigrationStrategy implements MigrationStrategy.Instance {
|
||||||
|
|
||||||
|
private final SCMContextProvider contextProvider;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CopyMigrationStrategy(SCMContextProvider contextProvider) {
|
||||||
|
this.contextProvider = contextProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path migrate(String id, String name, String type) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package sonia.scm.repository.update;
|
||||||
|
|
||||||
|
import sonia.scm.SCMContextProvider;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
class InlineMigrationStrategy implements MigrationStrategy.Instance {
|
||||||
|
|
||||||
|
private final SCMContextProvider contextProvider;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public InlineMigrationStrategy(SCMContextProvider contextProvider) {
|
||||||
|
this.contextProvider = contextProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path migrate(String id, String name, String type) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package sonia.scm.repository.update;
|
||||||
|
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
enum MigrationStrategy {
|
||||||
|
|
||||||
|
COPY(CopyMigrationStrategy.class),
|
||||||
|
MOVE(MoveMigrationStrategy.class),
|
||||||
|
INLINE(InlineMigrationStrategy.class);
|
||||||
|
|
||||||
|
private Class<? extends Instance> implementationClass;
|
||||||
|
|
||||||
|
MigrationStrategy(Class<? extends Instance> implementationClass) {
|
||||||
|
this.implementationClass = implementationClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance from(Injector injector) {
|
||||||
|
return injector.getInstance(implementationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Instance {
|
||||||
|
Path migrate(String id, String name, String type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package sonia.scm.repository.update;
|
||||||
|
|
||||||
|
import sonia.scm.store.ConfigurationStore;
|
||||||
|
import sonia.scm.store.ConfigurationStoreFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class MigrationStrategyDao {
|
||||||
|
|
||||||
|
private final RepositoryMigrationPlan plan;
|
||||||
|
private final ConfigurationStore<RepositoryMigrationPlan> store;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MigrationStrategyDao(ConfigurationStoreFactory storeFactory) {
|
||||||
|
store = storeFactory.withType(RepositoryMigrationPlan.class).withName("migration-plan").build();
|
||||||
|
this.plan = store.getOptional().orElse(new RepositoryMigrationPlan());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<MigrationStrategy> get(String id) {
|
||||||
|
return plan.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(String repositoryId, MigrationStrategy strategy) {
|
||||||
|
plan.set(repositoryId, strategy);
|
||||||
|
store.set(plan);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package sonia.scm.repository.update;
|
||||||
|
|
||||||
|
import sonia.scm.SCMContextProvider;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
class MoveMigrationStrategy implements MigrationStrategy.Instance {
|
||||||
|
|
||||||
|
private final SCMContextProvider contextProvider;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MoveMigrationStrategy(SCMContextProvider contextProvider) {
|
||||||
|
this.contextProvider = contextProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path migrate(String id, String name, String type) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package sonia.scm.repository.update;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
@XmlRootElement(name = "repository-migration")
|
||||||
|
class RepositoryMigrationPlan {
|
||||||
|
|
||||||
|
private List<RepositoryEntry> entries;
|
||||||
|
|
||||||
|
RepositoryMigrationPlan() {
|
||||||
|
this(new RepositoryEntry[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
RepositoryMigrationPlan(RepositoryEntry... entries) {
|
||||||
|
this.entries = new ArrayList<>(asList(entries));
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<MigrationStrategy> get(String repositoryId) {
|
||||||
|
return findEntry(repositoryId)
|
||||||
|
.map(RepositoryEntry::getDataMigrationStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(String repositoryId, MigrationStrategy strategy) {
|
||||||
|
Optional<RepositoryEntry> entry = findEntry(repositoryId);
|
||||||
|
if (entry.isPresent()) {
|
||||||
|
entry.get().setStrategy(strategy);
|
||||||
|
} else {
|
||||||
|
entries.add(new RepositoryEntry(repositoryId, strategy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<RepositoryEntry> findEntry(String repositoryId) {
|
||||||
|
return entries.stream()
|
||||||
|
.filter(repositoryEntry -> repositoryId.equals(repositoryEntry.repositoryId))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlRootElement(name = "entries")
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
static class RepositoryEntry {
|
||||||
|
|
||||||
|
private String repositoryId;
|
||||||
|
private MigrationStrategy dataMigrationStrategy;
|
||||||
|
|
||||||
|
RepositoryEntry() {
|
||||||
|
}
|
||||||
|
|
||||||
|
RepositoryEntry(String repositoryId, MigrationStrategy dataMigrationStrategy) {
|
||||||
|
this.repositoryId = repositoryId;
|
||||||
|
this.dataMigrationStrategy = dataMigrationStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MigrationStrategy getDataMigrationStrategy() {
|
||||||
|
return dataMigrationStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setStrategy(MigrationStrategy strategy) {
|
||||||
|
this.dataMigrationStrategy = strategy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
package sonia.scm.repository.update;
|
package sonia.scm.repository.update;
|
||||||
|
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
import sonia.scm.migration.UpdateStep;
|
import sonia.scm.migration.UpdateStep;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
@@ -17,6 +20,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
|
|||||||
import javax.xml.bind.annotation.XmlElement;
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -27,13 +31,19 @@ import static sonia.scm.version.Version.parse;
|
|||||||
@Extension
|
@Extension
|
||||||
public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
||||||
|
|
||||||
|
private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class);
|
||||||
|
|
||||||
private final SCMContextProvider contextProvider;
|
private final SCMContextProvider contextProvider;
|
||||||
private final XmlRepositoryDAO dao;
|
private final XmlRepositoryDAO repositoryDao;
|
||||||
|
private final MigrationStrategyDao migrationStrategyDao;
|
||||||
|
private final Injector injector;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public XmlRepositoryV1UpdateStep(SCMContextProvider contextProvider, XmlRepositoryDAO dao) {
|
public XmlRepositoryV1UpdateStep(SCMContextProvider contextProvider, XmlRepositoryDAO repositoryDao, MigrationStrategyDao migrationStrategyDao, Injector injector) {
|
||||||
this.contextProvider = contextProvider;
|
this.contextProvider = contextProvider;
|
||||||
this.dao = dao;
|
this.repositoryDao = repositoryDao;
|
||||||
|
this.migrationStrategyDao = migrationStrategyDao;
|
||||||
|
this.injector = injector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -57,6 +67,7 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void update(V1Repository v1Repository) {
|
private void update(V1Repository v1Repository) {
|
||||||
|
Path destination = handleDataDirectory(v1Repository);
|
||||||
Repository repository = new Repository(
|
Repository repository = new Repository(
|
||||||
v1Repository.id,
|
v1Repository.id,
|
||||||
v1Repository.type,
|
v1Repository.type,
|
||||||
@@ -65,7 +76,14 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
|||||||
v1Repository.contact,
|
v1Repository.contact,
|
||||||
v1Repository.description,
|
v1Repository.description,
|
||||||
createPermissions(v1Repository));
|
createPermissions(v1Repository));
|
||||||
dao.add(repository);
|
repositoryDao.add(repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path handleDataDirectory(V1Repository v1Repository) {
|
||||||
|
MigrationStrategy dataMigrationStrategy =
|
||||||
|
migrationStrategyDao.get(v1Repository.id)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("no strategy found for repository with id " + v1Repository.id + " and name " + v1Repository.name));
|
||||||
|
return dataMigrationStrategy.from(injector).migrate(v1Repository.id, v1Repository.name, v1Repository.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RepositoryPermission[] createPermissions(V1Repository v1Repository) {
|
private RepositoryPermission[] createPermissions(V1Repository v1Repository) {
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package sonia.scm.repository.update;
|
||||||
|
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junitpioneer.jupiter.TempDirectory;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.SCMContextProvider;
|
||||||
|
import sonia.scm.store.ConfigurationStoreFactory;
|
||||||
|
import sonia.scm.store.JAXBConfigurationStoreFactory;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static sonia.scm.repository.update.MigrationStrategy.INLINE;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
@ExtendWith(TempDirectory.class)
|
||||||
|
class MigrationStrategyDaoTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
SCMContextProvider contextProvider;
|
||||||
|
|
||||||
|
private ConfigurationStoreFactory storeFactory;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void initStore(@TempDirectory.TempDir Path tempDir) {
|
||||||
|
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
||||||
|
storeFactory = new JAXBConfigurationStoreFactory(contextProvider, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnEmptyOptionalWhenStoreIsEmpty() throws JAXBException {
|
||||||
|
MigrationStrategyDao dao = new MigrationStrategyDao(storeFactory);
|
||||||
|
|
||||||
|
Optional<MigrationStrategy> strategy = dao.get("any");
|
||||||
|
|
||||||
|
Assertions.assertThat(strategy).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnNewValue() throws JAXBException {
|
||||||
|
MigrationStrategyDao dao = new MigrationStrategyDao(storeFactory);
|
||||||
|
|
||||||
|
dao.set("id", INLINE);
|
||||||
|
|
||||||
|
Optional<MigrationStrategy> strategy = dao.get("id");
|
||||||
|
|
||||||
|
Assertions.assertThat(strategy).contains(INLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class WithExistingDatabase {
|
||||||
|
@BeforeEach
|
||||||
|
void initExistingDatabase() throws JAXBException {
|
||||||
|
MigrationStrategyDao dao = new MigrationStrategyDao(storeFactory);
|
||||||
|
|
||||||
|
dao.set("id", INLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFindExistingValue() throws JAXBException {
|
||||||
|
MigrationStrategyDao dao = new MigrationStrategyDao(storeFactory);
|
||||||
|
|
||||||
|
Optional<MigrationStrategy> strategy = dao.get("id");
|
||||||
|
|
||||||
|
Assertions.assertThat(strategy).contains(INLINE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package sonia.scm.repository.update;
|
||||||
|
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class MigrationStrategyMock {
|
||||||
|
|
||||||
|
static Injector init() {
|
||||||
|
Map<Class, MigrationStrategy.Instance> mocks = new HashMap<>();
|
||||||
|
Injector mock = mock(Injector.class);
|
||||||
|
when(
|
||||||
|
mock.getInstance(any(Class.class)))
|
||||||
|
.thenAnswer(
|
||||||
|
invocationOnMock -> mocks.getOrDefault(invocationOnMock.getArgument(0), mock(invocationOnMock.getArgument(0)))
|
||||||
|
);
|
||||||
|
return mock;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package sonia.scm.repository.update;
|
package sonia.scm.repository.update;
|
||||||
|
|
||||||
|
import com.google.inject.Injector;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -21,21 +22,30 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static java.util.Optional.of;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.doNothing;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static sonia.scm.repository.update.MigrationStrategy.COPY;
|
||||||
|
import static sonia.scm.repository.update.MigrationStrategy.INLINE;
|
||||||
|
import static sonia.scm.repository.update.MigrationStrategy.MOVE;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@ExtendWith(TempDirectory.class)
|
@ExtendWith(TempDirectory.class)
|
||||||
class XmlRepositoryV1UpdateStepTest {
|
class XmlRepositoryV1UpdateStepTest {
|
||||||
|
|
||||||
|
Injector injectorMock = MigrationStrategyMock.init();
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
SCMContextProvider contextProvider;
|
SCMContextProvider contextProvider;
|
||||||
@Mock
|
@Mock
|
||||||
XmlRepositoryDAO dao;
|
XmlRepositoryDAO repositoryDAO;
|
||||||
|
@Mock()
|
||||||
|
MigrationStrategyDao migrationStrategyDao;
|
||||||
|
|
||||||
@Captor
|
@Captor
|
||||||
ArgumentCaptor<Repository> storeCaptor;
|
ArgumentCaptor<Repository> storeCaptor;
|
||||||
@@ -58,13 +68,20 @@ class XmlRepositoryV1UpdateStepTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void captureStoredRepositories() {
|
void captureStoredRepositories() {
|
||||||
doNothing().when(dao).add(storeCaptor.capture());
|
doNothing().when(repositoryDAO).add(storeCaptor.capture());
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void createMigrationPlan(@TempDirectory.TempDir Path tempDir) {
|
||||||
|
lenient().when(migrationStrategyDao.get("3b91caa5-59c3-448f-920b-769aaa56b761")).thenReturn(of(MOVE));
|
||||||
|
lenient().when(migrationStrategyDao.get("c1597b4f-a9f0-49f7-ad1f-37d3aae1c55f")).thenReturn(of(COPY));
|
||||||
|
lenient().when(migrationStrategyDao.get("454972da-faf9-4437-b682-dc4a4e0aa8eb")).thenReturn(of(INLINE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCreateNewRepositories() throws JAXBException {
|
void shouldCreateNewRepositories() throws JAXBException {
|
||||||
updateStep.doUpdate();
|
updateStep.doUpdate();
|
||||||
verify(dao, times(3)).add(any());
|
verify(repositoryDAO, times(3)).add(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user