mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-18 03:01:05 +01:00
Merge with 2.0.0-m3
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
package sonia.scm.update.group;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.group.Group;
|
||||
import sonia.scm.group.xml.XmlGroupDAO;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.update.properties.V1Properties;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
@Extension
|
||||
public class XmlGroupV1UpdateStep implements UpdateStep {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XmlGroupV1UpdateStep.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final XmlGroupDAO groupDAO;
|
||||
private final ConfigurationEntryStore<V1Properties> propertyStore;
|
||||
|
||||
@Inject
|
||||
public XmlGroupV1UpdateStep(
|
||||
SCMContextProvider contextProvider,
|
||||
XmlGroupDAO groupDAO,
|
||||
ConfigurationEntryStoreFactory configurationEntryStoreFactory
|
||||
) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.groupDAO = groupDAO;
|
||||
this.propertyStore = configurationEntryStoreFactory
|
||||
.withType(V1Properties.class)
|
||||
.withName("group-properties-v1")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate() throws JAXBException {
|
||||
Optional<Path> v1GroupsFile = determineV1File();
|
||||
if (!v1GroupsFile.isPresent()) {
|
||||
LOG.info("no v1 file for groups found");
|
||||
return;
|
||||
}
|
||||
XmlGroupV1UpdateStep.V1GroupDatabase v1Database = readV1Database(v1GroupsFile.get());
|
||||
if (v1Database.groupList != null && v1Database.groupList.groups != null) {
|
||||
v1Database.groupList.groups.forEach(this::update);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return parse("2.0.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAffectedDataType() {
|
||||
return "sonia.scm.group.xml";
|
||||
}
|
||||
|
||||
private void update(V1Group v1Group) {
|
||||
LOG.debug("updating group {}", v1Group.name);
|
||||
Group group = new Group(
|
||||
v1Group.type,
|
||||
v1Group.name,
|
||||
v1Group.members);
|
||||
group.setDescription(v1Group.description);
|
||||
group.setCreationDate(v1Group.creationDate);
|
||||
group.setLastModified(v1Group.lastModified);
|
||||
groupDAO.add(group);
|
||||
|
||||
propertyStore.put(v1Group.name, v1Group.properties);
|
||||
}
|
||||
|
||||
private XmlGroupV1UpdateStep.V1GroupDatabase readV1Database(Path v1GroupsFile) throws JAXBException {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(XmlGroupV1UpdateStep.V1GroupDatabase.class);
|
||||
return (XmlGroupV1UpdateStep.V1GroupDatabase) jaxbContext.createUnmarshaller().unmarshal(v1GroupsFile.toFile());
|
||||
}
|
||||
|
||||
private Optional<Path> determineV1File() {
|
||||
Path existingGroupsFile = resolveConfigFile("groups");
|
||||
Path groupsV1File = resolveConfigFile("groupsV1");
|
||||
if (existingGroupsFile.toFile().exists()) {
|
||||
try {
|
||||
Files.move(existingGroupsFile, groupsV1File);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not move old groups file to " + groupsV1File.toAbsolutePath());
|
||||
}
|
||||
LOG.info("moved old groups file to {}", groupsV1File.toAbsolutePath());
|
||||
return of(groupsV1File);
|
||||
}
|
||||
return empty();
|
||||
}
|
||||
|
||||
private Path resolveConfigFile(String name) {
|
||||
return contextProvider
|
||||
.resolve(
|
||||
Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve(name + StoreConstants.FILE_EXTENSION)
|
||||
);
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "group")
|
||||
private static class V1Group {
|
||||
private V1Properties properties;
|
||||
private long creationDate;
|
||||
private String description;
|
||||
private Long lastModified;
|
||||
private String name;
|
||||
private String type;
|
||||
@XmlElement(name = "members")
|
||||
private List<String> members;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "V1Group{" +
|
||||
"properties=" + properties +
|
||||
", creationDate=" + creationDate + '\'' +
|
||||
", description=" + description + '\'' +
|
||||
", lastModified=" + lastModified + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", type='" + type + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
private static class GroupList {
|
||||
@XmlElement(name = "group")
|
||||
private List<V1Group> groups;
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "group-db")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
private static class V1GroupDatabase {
|
||||
private long creationTime;
|
||||
private Long lastModified;
|
||||
@XmlElement(name = "groups")
|
||||
private XmlGroupV1UpdateStep.GroupList groupList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package sonia.scm.update.properties;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.List;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "properties")
|
||||
public class V1Properties {
|
||||
@XmlElement(name = "item")
|
||||
private List<V1Property> properties;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package sonia.scm.update.properties;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class V1Property {
|
||||
private String key;
|
||||
private String value;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
abstract class BaseMigrationStrategy implements MigrationStrategy.Instance {
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
|
||||
BaseMigrationStrategy(SCMContextProvider contextProvider) {
|
||||
this.contextProvider = contextProvider;
|
||||
}
|
||||
|
||||
Path getSourceDataPath(String name, String type) {
|
||||
return Arrays.stream(name.split("/"))
|
||||
.reduce(getTypeDependentPath(type), (path, namePart) -> path.resolve(namePart), (p1, p2) -> p1);
|
||||
}
|
||||
|
||||
Path getTypeDependentPath(String type) {
|
||||
return contextProvider.getBaseDirectory().toPath().resolve("repositories").resolve(type);
|
||||
}
|
||||
|
||||
Stream<Path> listSourceDirectory(Path sourceDirectory) {
|
||||
try {
|
||||
return Files.list(sourceDirectory);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not read original directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
void createDataDirectory(Path target) {
|
||||
try {
|
||||
Files.createDirectories(target);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not create data directory " + target, e);
|
||||
}
|
||||
}
|
||||
|
||||
void moveFile(Path sourceFile, Path targetFile) {
|
||||
try {
|
||||
Files.move(sourceFile, targetFile);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not move data file from " + sourceFile + " to " + targetFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
void copyFile(Path sourceFile, Path targetFile) {
|
||||
try {
|
||||
Files.copy(sourceFile, targetFile);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not copy original file from " + sourceFile + " to " + targetFile, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.RepositoryDirectoryHandler;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class CopyMigrationStrategy extends BaseMigrationStrategy {
|
||||
|
||||
private final RepositoryLocationResolver locationResolver;
|
||||
|
||||
@Inject
|
||||
public CopyMigrationStrategy(SCMContextProvider contextProvider, RepositoryLocationResolver locationResolver) {
|
||||
super(contextProvider);
|
||||
this.locationResolver = locationResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path migrate(String id, String name, String type) {
|
||||
Path repositoryBasePath = locationResolver.forClass(Path.class).getLocation(id);
|
||||
Path targetDataPath = repositoryBasePath
|
||||
.resolve(RepositoryDirectoryHandler.REPOSITORIES_NATIVE_DIRECTORY);
|
||||
Path sourceDataPath = getSourceDataPath(name, type);
|
||||
copyData(sourceDataPath, targetDataPath);
|
||||
return repositoryBasePath;
|
||||
}
|
||||
|
||||
private void copyData(Path sourceDirectory, Path targetDirectory) {
|
||||
createDataDirectory(targetDirectory);
|
||||
listSourceDirectory(sourceDirectory).forEach(
|
||||
sourceFile -> {
|
||||
Path targetFile = targetDirectory.resolve(sourceFile.getFileName());
|
||||
if (Files.isDirectory(sourceFile)) {
|
||||
copyData(sourceFile, targetFile);
|
||||
} else {
|
||||
copyFile(sourceFile, targetFile);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.RepositoryDirectoryHandler;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class InlineMigrationStrategy extends BaseMigrationStrategy {
|
||||
|
||||
@Inject
|
||||
public InlineMigrationStrategy(SCMContextProvider contextProvider) {
|
||||
super(contextProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path migrate(String id, String name, String type) {
|
||||
Path repositoryBasePath = getSourceDataPath(name, type);
|
||||
Path targetDataPath = repositoryBasePath
|
||||
.resolve(RepositoryDirectoryHandler.REPOSITORIES_NATIVE_DIRECTORY);
|
||||
moveData(repositoryBasePath, targetDataPath);
|
||||
return repositoryBasePath;
|
||||
}
|
||||
|
||||
private void moveData(Path sourceDirectory, Path targetDirectory) {
|
||||
createDataDirectory(targetDirectory);
|
||||
listSourceDirectory(sourceDirectory)
|
||||
.filter(sourceFile -> !targetDirectory.equals(sourceFile))
|
||||
.forEach(
|
||||
sourceFile -> {
|
||||
Path targetFile = targetDirectory.resolve(sourceFile.getFileName());
|
||||
if (Files.isDirectory(sourceFile)) {
|
||||
moveData(sourceFile, targetFile);
|
||||
} else {
|
||||
moveFile(sourceFile, targetFile);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.HealthCheckFailure;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryPermission;
|
||||
import sonia.scm.repository.RepositoryRole;
|
||||
import sonia.scm.repository.xml.SingleRepositoryUpdateProcessor;
|
||||
import sonia.scm.security.SystemRepositoryPermissionProvider;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Marshaller;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlElementWrapper;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@Extension
|
||||
public class MigrateVerbsToPermissionRoles extends RepositoryUpdates.RepositoryUpdateType implements UpdateStep {
|
||||
|
||||
public static final Logger LOG = LoggerFactory.getLogger(MigrateVerbsToPermissionRoles.class);
|
||||
|
||||
private final SingleRepositoryUpdateProcessor updateProcessor;
|
||||
private final SystemRepositoryPermissionProvider systemRepositoryPermissionProvider;
|
||||
|
||||
@Inject
|
||||
public MigrateVerbsToPermissionRoles(SingleRepositoryUpdateProcessor updateProcessor, SystemRepositoryPermissionProvider systemRepositoryPermissionProvider) {
|
||||
this.updateProcessor = updateProcessor;
|
||||
this.systemRepositoryPermissionProvider = systemRepositoryPermissionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate() {
|
||||
updateProcessor.doUpdate(this::update);
|
||||
}
|
||||
|
||||
void update(String repositoryId, Path path) {
|
||||
LOG.info("updating repository {}", repositoryId);
|
||||
OldRepository oldRepository = readOldRepository(path);
|
||||
Repository newRepository = createNewRepository(oldRepository);
|
||||
writeNewRepository(path, newRepository);
|
||||
}
|
||||
|
||||
private void writeNewRepository(Path path, Repository newRepository) {
|
||||
try {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(Repository.class);
|
||||
Marshaller marshaller = jaxbContext.createMarshaller();
|
||||
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
|
||||
marshaller.marshal(newRepository, path.resolve("metadata.xml").toFile());
|
||||
} catch (JAXBException e) {
|
||||
throw new UpdateException("could not read old repository structure", e);
|
||||
}
|
||||
}
|
||||
|
||||
private OldRepository readOldRepository(Path path) {
|
||||
try {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(OldRepository.class);
|
||||
return (OldRepository) jaxbContext.createUnmarshaller().unmarshal(path.resolve("metadata.xml").toFile());
|
||||
} catch (JAXBException e) {
|
||||
throw new UpdateException("could not read old repository structure", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Repository createNewRepository(OldRepository oldRepository) {
|
||||
Repository repository = new Repository(
|
||||
oldRepository.id,
|
||||
oldRepository.type,
|
||||
oldRepository.namespace,
|
||||
oldRepository.name,
|
||||
oldRepository.contact,
|
||||
oldRepository.description,
|
||||
oldRepository.permissions.stream().map(this::updatePermission).toArray(RepositoryPermission[]::new)
|
||||
);
|
||||
repository.setCreationDate(oldRepository.creationDate);
|
||||
repository.setHealthCheckFailures(oldRepository.healthCheckFailures);
|
||||
repository.setLastModified(oldRepository.lastModified);
|
||||
repository.setPublicReadable(oldRepository.publicReadable);
|
||||
repository.setArchived(oldRepository.archived);
|
||||
return repository;
|
||||
}
|
||||
|
||||
private RepositoryPermission updatePermission(RepositoryPermission repositoryPermission) {
|
||||
return findMatchingRole(repositoryPermission.getVerbs())
|
||||
.map(roleName -> copyRepositoryPermissionWithRole(repositoryPermission, roleName))
|
||||
.orElse(repositoryPermission);
|
||||
}
|
||||
|
||||
private RepositoryPermission copyRepositoryPermissionWithRole(RepositoryPermission repositoryPermission, String roleName) {
|
||||
return new RepositoryPermission(repositoryPermission.getName(), roleName, repositoryPermission.isGroupPermission());
|
||||
}
|
||||
|
||||
private Optional<String> findMatchingRole(Collection<String> verbs) {
|
||||
return systemRepositoryPermissionProvider.availableRoles()
|
||||
.stream()
|
||||
.filter(r -> roleMatchesVerbs(verbs, r))
|
||||
.map(RepositoryRole::getName)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private boolean roleMatchesVerbs(Collection<String> verbs, RepositoryRole r) {
|
||||
return verbs.size() == r.getVerbs().size() && r.getVerbs().containsAll(verbs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return Version.parse("1");
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "repositories")
|
||||
private static class OldRepository {
|
||||
private String contact;
|
||||
private Long creationDate;
|
||||
private String description;
|
||||
@XmlElement(name = "healthCheckFailure")
|
||||
@XmlElementWrapper(name = "healthCheckFailures")
|
||||
private List<HealthCheckFailure> healthCheckFailures;
|
||||
private String id;
|
||||
private Long lastModified;
|
||||
private String namespace;
|
||||
private String name;
|
||||
@XmlElement(name = "permission")
|
||||
private final Set<RepositoryPermission> permissions = new HashSet<>();
|
||||
@XmlElement(name = "public")
|
||||
private boolean publicReadable = false;
|
||||
private boolean archived = false;
|
||||
private String type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
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.update.repository;
|
||||
|
||||
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,75 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.RepositoryDirectoryHandler;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
class MoveMigrationStrategy extends BaseMigrationStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MoveMigrationStrategy.class);
|
||||
|
||||
private final RepositoryLocationResolver locationResolver;
|
||||
|
||||
@Inject
|
||||
public MoveMigrationStrategy(SCMContextProvider contextProvider, RepositoryLocationResolver locationResolver) {
|
||||
super(contextProvider);
|
||||
this.locationResolver = locationResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path migrate(String id, String name, String type) {
|
||||
Path repositoryBasePath = locationResolver.forClass(Path.class).getLocation(id);
|
||||
Path targetDataPath = repositoryBasePath
|
||||
.resolve(RepositoryDirectoryHandler.REPOSITORIES_NATIVE_DIRECTORY);
|
||||
Path sourceDataPath = getSourceDataPath(name, type);
|
||||
moveData(sourceDataPath, targetDataPath);
|
||||
deleteOldDataDir(getTypeDependentPath(type), name);
|
||||
return repositoryBasePath;
|
||||
}
|
||||
|
||||
private void deleteOldDataDir(Path rootPath, String name) {
|
||||
delete(rootPath, asList(name.split("/")));
|
||||
}
|
||||
|
||||
private void delete(Path rootPath, List<String> directories) {
|
||||
if (directories.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Path directory = rootPath.resolve(directories.get(0));
|
||||
delete(directory, directories.subList(1, directories.size()));
|
||||
try {
|
||||
Files.deleteIfExists(directory);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not delete source repository directory {}", directory);
|
||||
}
|
||||
}
|
||||
|
||||
private void moveData(Path sourceDirectory, Path targetDirectory) {
|
||||
createDataDirectory(targetDirectory);
|
||||
listSourceDirectory(sourceDirectory).forEach(
|
||||
sourceFile -> {
|
||||
Path targetFile = targetDirectory.resolve(sourceFile.getFileName());
|
||||
if (Files.isDirectory(sourceFile)) {
|
||||
moveData(sourceFile, targetFile);
|
||||
} else {
|
||||
moveFile(sourceFile, targetFile);
|
||||
}
|
||||
}
|
||||
);
|
||||
try {
|
||||
Files.delete(sourceDirectory);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not delete source repository directory {}", sourceDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
public class RepositoryUpdates {
|
||||
|
||||
static class RepositoryUpdateType {
|
||||
public String getAffectedDataType() {
|
||||
return "repository";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
||||
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
/**
|
||||
* Moves an existing <code>repositories.xml</code> file to <code>repository-paths.xml</code>.
|
||||
* Note that this has to run <em>after</em> an old v1 repository database has been migrated to v2
|
||||
* (see {@link XmlRepositoryV1UpdateStep}).
|
||||
*/
|
||||
@Extension
|
||||
public class XmlRepositoryFileNameUpdateStep implements UpdateStep {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XmlRepositoryFileNameUpdateStep.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final XmlRepositoryDAO repositoryDAO;
|
||||
|
||||
@Inject
|
||||
public XmlRepositoryFileNameUpdateStep(SCMContextProvider contextProvider, XmlRepositoryDAO repositoryDAO) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.repositoryDAO = repositoryDAO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate() throws IOException {
|
||||
Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME);
|
||||
Path oldRepositoriesFile = configDir.resolve("repositories.xml");
|
||||
Path newRepositoryPathsFile = configDir.resolve(PathBasedRepositoryLocationResolver.STORE_NAME + StoreConstants.FILE_EXTENSION);
|
||||
if (Files.exists(oldRepositoriesFile)) {
|
||||
LOG.info("moving old repositories database files to repository-paths file");
|
||||
Files.move(oldRepositoriesFile, newRepositoryPathsFile);
|
||||
repositoryDAO.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return parse("2.0.1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAffectedDataType() {
|
||||
return "sonia.scm.repository.xml";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import com.google.inject.Injector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryPermission;
|
||||
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.update.properties.V1Properties;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
/**
|
||||
* Migrates SCM-Manager v1 repository data structure to SCM-Manager v2 data structure.
|
||||
* That is:
|
||||
* <ul>
|
||||
* <li>The old <code>repositories.xml</code> file is read</li>
|
||||
* <li>For each repository in this database,
|
||||
* <ul>
|
||||
* <li>a new entry in the new <code>repository-paths.xml</code> database is written,</li>
|
||||
* <li>the data directory is moved or copied to a SCM v2 consistent directory. How this is done
|
||||
* can be specified by a strategy (@see {@link MigrationStrategy}), that has to be set in
|
||||
* a database file named <code>migration-plan.xml</code></li> (to create this file, use {@link MigrationStrategyDao}),
|
||||
* and
|
||||
* <li>the new <code>metadata.xml</code> file is created.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
@Extension
|
||||
public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
||||
|
||||
private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final XmlRepositoryDAO repositoryDao;
|
||||
private final MigrationStrategyDao migrationStrategyDao;
|
||||
private final Injector injector;
|
||||
private final ConfigurationEntryStore<V1Properties> propertyStore;
|
||||
|
||||
@Inject
|
||||
public XmlRepositoryV1UpdateStep(
|
||||
SCMContextProvider contextProvider,
|
||||
XmlRepositoryDAO repositoryDao,
|
||||
MigrationStrategyDao migrationStrategyDao,
|
||||
Injector injector,
|
||||
ConfigurationEntryStoreFactory configurationEntryStoreFactory
|
||||
) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.repositoryDao = repositoryDao;
|
||||
this.migrationStrategyDao = migrationStrategyDao;
|
||||
this.injector = injector;
|
||||
this.propertyStore = configurationEntryStoreFactory
|
||||
.withType(V1Properties.class)
|
||||
.withName("repository-properties-v1")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return parse("2.0.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAffectedDataType() {
|
||||
return "sonia.scm.repository.xml";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate() throws JAXBException {
|
||||
if (!resolveV1File().exists()) {
|
||||
LOG.info("no v1 repositories database file found");
|
||||
return;
|
||||
}
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class);
|
||||
readV1Database(jaxbContext).ifPresent(
|
||||
v1Database -> {
|
||||
v1Database.repositoryList.repositories.forEach(this::readMigrationStrategy);
|
||||
v1Database.repositoryList.repositories.forEach(this::update);
|
||||
backupOldRepositoriesFile();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void backupOldRepositoriesFile() {
|
||||
Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME);
|
||||
Path oldRepositoriesFile = configDir.resolve("repositories.xml");
|
||||
Path backupFile = configDir.resolve("repositories.xml.v1.backup");
|
||||
LOG.info("moving old repositories database files to backup file {}", backupFile);
|
||||
try {
|
||||
Files.move(oldRepositoriesFile, backupFile);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not backup old repository database file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void update(V1Repository v1Repository) {
|
||||
Path destination = handleDataDirectory(v1Repository);
|
||||
Repository repository = new Repository(
|
||||
v1Repository.id,
|
||||
v1Repository.type,
|
||||
getNamespace(v1Repository),
|
||||
getName(v1Repository),
|
||||
v1Repository.contact,
|
||||
v1Repository.description,
|
||||
createPermissions(v1Repository));
|
||||
LOG.info("creating new repository {} with id {} from old repository {} in directory {}", repository.getNamespaceAndName(), repository.getId(), v1Repository.name, destination);
|
||||
repositoryDao.add(repository, destination);
|
||||
propertyStore.put(v1Repository.id, v1Repository.properties);
|
||||
}
|
||||
|
||||
private Path handleDataDirectory(V1Repository v1Repository) {
|
||||
MigrationStrategy dataMigrationStrategy = readMigrationStrategy(v1Repository);
|
||||
return dataMigrationStrategy.from(injector).migrate(v1Repository.id, v1Repository.name, v1Repository.type);
|
||||
}
|
||||
|
||||
private MigrationStrategy readMigrationStrategy(V1Repository v1Repository) {
|
||||
return migrationStrategyDao.get(v1Repository.id)
|
||||
.orElseThrow(() -> new IllegalStateException("no strategy found for repository with id " + v1Repository.id + " and name " + v1Repository.name));
|
||||
}
|
||||
|
||||
private RepositoryPermission[] createPermissions(V1Repository v1Repository) {
|
||||
if (v1Repository.permissions == null) {
|
||||
return new RepositoryPermission[0];
|
||||
}
|
||||
return v1Repository.permissions
|
||||
.stream()
|
||||
.map(this::createPermission)
|
||||
.toArray(RepositoryPermission[]::new);
|
||||
}
|
||||
|
||||
private RepositoryPermission createPermission(V1Permission v1Permission) {
|
||||
LOG.info("creating permission {} for {}", v1Permission.type, v1Permission.name);
|
||||
return new RepositoryPermission(v1Permission.name, v1Permission.type, v1Permission.groupPermission);
|
||||
}
|
||||
|
||||
private String getNamespace(V1Repository v1Repository) {
|
||||
String[] nameParts = getNameParts(v1Repository.name);
|
||||
return nameParts.length > 1 ? nameParts[0] : v1Repository.type;
|
||||
}
|
||||
|
||||
private String getName(V1Repository v1Repository) {
|
||||
String[] nameParts = getNameParts(v1Repository.name);
|
||||
return nameParts.length == 1 ? nameParts[0] : concatPathElements(nameParts);
|
||||
}
|
||||
|
||||
private String concatPathElements(String[] nameParts) {
|
||||
return Arrays.stream(nameParts).skip(1).collect(Collectors.joining("_"));
|
||||
}
|
||||
|
||||
private String[] getNameParts(String v1Name) {
|
||||
return v1Name.split("/");
|
||||
}
|
||||
|
||||
private Optional<V1RepositoryDatabase> readV1Database(JAXBContext jaxbContext) throws JAXBException {
|
||||
Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File());
|
||||
if (unmarshal instanceof V1RepositoryDatabase) {
|
||||
return of((V1RepositoryDatabase) unmarshal);
|
||||
} else {
|
||||
return empty();
|
||||
}
|
||||
}
|
||||
|
||||
private File resolveV1File() {
|
||||
return contextProvider
|
||||
.resolve(
|
||||
Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve("repositories" + StoreConstants.FILE_EXTENSION)
|
||||
).toFile();
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "permissions")
|
||||
private static class V1Permission {
|
||||
private boolean groupPermission;
|
||||
private String name;
|
||||
private String type;
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "repositories")
|
||||
private static class V1Repository {
|
||||
private String contact;
|
||||
private long creationDate;
|
||||
private Long lastModified;
|
||||
private String description;
|
||||
private String id;
|
||||
private String name;
|
||||
private boolean isPublic;
|
||||
private boolean archived;
|
||||
private String type;
|
||||
private List<V1Permission> permissions;
|
||||
private V1Properties properties;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "V1Repository{" +
|
||||
", contact='" + contact + '\'' +
|
||||
", creationDate=" + creationDate +
|
||||
", lastModified=" + lastModified +
|
||||
", description='" + description + '\'' +
|
||||
", id='" + id + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", isPublic=" + isPublic +
|
||||
", archived=" + archived +
|
||||
", type='" + type + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
private static class RepositoryList {
|
||||
@XmlElement(name = "repository")
|
||||
private List<V1Repository> repositories;
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "repository-db")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
private static class V1RepositoryDatabase {
|
||||
private long creationTime;
|
||||
private Long lastModified;
|
||||
@XmlElement(name = "repositories")
|
||||
private RepositoryList repositoryList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package sonia.scm.update.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.security.AssignedPermission;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
@Extension
|
||||
public class XmlSecurityV1UpdateStep implements UpdateStep {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XmlSecurityV1UpdateStep.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final ConfigurationEntryStoreFactory configurationEntryStoreFactory;
|
||||
|
||||
@Inject
|
||||
public XmlSecurityV1UpdateStep(SCMContextProvider contextProvider, ConfigurationEntryStoreFactory configurationEntryStoreFactory) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.configurationEntryStoreFactory = configurationEntryStoreFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate() throws JAXBException {
|
||||
ConfigurationEntryStore<AssignedPermission> securityStore = createSecurityStore();
|
||||
|
||||
forAllAdmins(user -> createSecurityEntry(user, false, securityStore),
|
||||
group -> createSecurityEntry(group, true, securityStore));
|
||||
}
|
||||
|
||||
private void forAllAdmins(Consumer<String> userConsumer, Consumer<String> groupConsumer) throws JAXBException {
|
||||
Path configDirectory = determineConfigDirectory();
|
||||
Path existingConfigFile = configDirectory.resolve("config" + StoreConstants.FILE_EXTENSION);
|
||||
if (existingConfigFile.toFile().exists()) {
|
||||
forAllAdmins(existingConfigFile, userConsumer, groupConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
private void forAllAdmins(
|
||||
Path existingConfigFile, Consumer<String> userConsumer, Consumer<String> groupConsumer
|
||||
) throws JAXBException {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(XmlSecurityV1UpdateStep.V1Configuration.class);
|
||||
V1Configuration v1Configuration = (V1Configuration) jaxbContext.createUnmarshaller().unmarshal(existingConfigFile.toFile());
|
||||
|
||||
ofNullable(v1Configuration.adminUsers).ifPresent(users -> forAll(users, userConsumer));
|
||||
ofNullable(v1Configuration.adminGroups).ifPresent(groups -> forAll(groups, groupConsumer));
|
||||
}
|
||||
|
||||
private void forAll(String entries, Consumer<String> consumer) {
|
||||
Arrays.stream(entries.split(",")).forEach(consumer);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return parse("2.0.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAffectedDataType() {
|
||||
return "sonia.scm.security.xml";
|
||||
}
|
||||
|
||||
private void createSecurityEntry(String name, boolean group, ConfigurationEntryStore<AssignedPermission> securityStore) {
|
||||
LOG.debug("setting admin permissions for {} {}", group? "group": "user", name);
|
||||
securityStore.put(new AssignedPermission(name, group, "*"));
|
||||
}
|
||||
|
||||
private ConfigurationEntryStore<AssignedPermission> createSecurityStore() {
|
||||
return configurationEntryStoreFactory.withType(AssignedPermission.class).withName("security").build();
|
||||
}
|
||||
|
||||
private Path determineConfigDirectory() {
|
||||
return new File(contextProvider.getBaseDirectory(), StoreConstants.CONFIG_DIRECTORY_NAME).toPath();
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "scm-config")
|
||||
private static class V1Configuration {
|
||||
@XmlElement(name = "admin-users")
|
||||
private String adminUsers;
|
||||
@XmlElement(name = "admin-groups")
|
||||
private String adminGroups;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package sonia.scm.update.user;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.security.AssignedPermission;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.update.properties.V1Properties;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.xml.XmlUserDAO;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
@Extension
|
||||
public class XmlUserV1UpdateStep implements UpdateStep {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XmlUserV1UpdateStep.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final XmlUserDAO userDAO;
|
||||
private final ConfigurationEntryStoreFactory configurationEntryStoreFactory;
|
||||
private final ConfigurationEntryStore<V1Properties> propertyStore;
|
||||
|
||||
@Inject
|
||||
public XmlUserV1UpdateStep(SCMContextProvider contextProvider, XmlUserDAO userDAO, ConfigurationEntryStoreFactory configurationEntryStoreFactory) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.userDAO = userDAO;
|
||||
this.configurationEntryStoreFactory = configurationEntryStoreFactory;
|
||||
this.propertyStore = configurationEntryStoreFactory
|
||||
.withType(V1Properties.class)
|
||||
.withName("user-properties-v1")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate() throws JAXBException {
|
||||
Optional<Path> v1UsersFile = determineV1File();
|
||||
if (!v1UsersFile.isPresent()) {
|
||||
LOG.info("no v1 file for users found");
|
||||
return;
|
||||
}
|
||||
XmlUserV1UpdateStep.V1UserDatabase v1Database = readV1Database(v1UsersFile.get());
|
||||
ConfigurationEntryStore<AssignedPermission> securityStore = createSecurityStore();
|
||||
v1Database.userList.users.forEach(user -> update(user, securityStore));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return parse("2.0.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAffectedDataType() {
|
||||
return "sonia.scm.user.xml";
|
||||
}
|
||||
|
||||
private void update(V1User v1User, ConfigurationEntryStore<AssignedPermission> securityStore) {
|
||||
LOG.debug("updating user {}", v1User.name);
|
||||
User user = new User(
|
||||
v1User.name,
|
||||
v1User.displayName,
|
||||
v1User.mail,
|
||||
v1User.password,
|
||||
v1User.type,
|
||||
v1User.active);
|
||||
user.setCreationDate(v1User.creationDate);
|
||||
user.setLastModified(v1User.lastModified);
|
||||
userDAO.add(user);
|
||||
|
||||
if (v1User.admin) {
|
||||
LOG.debug("setting admin permissions for user {}", v1User.name);
|
||||
securityStore.put(new AssignedPermission(v1User.name, "*"));
|
||||
}
|
||||
|
||||
propertyStore.put(v1User.name, v1User.properties);
|
||||
}
|
||||
|
||||
private XmlUserV1UpdateStep.V1UserDatabase readV1Database(Path v1UsersFile) throws JAXBException {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(XmlUserV1UpdateStep.V1UserDatabase.class);
|
||||
return (XmlUserV1UpdateStep.V1UserDatabase) jaxbContext.createUnmarshaller().unmarshal(v1UsersFile.toFile());
|
||||
}
|
||||
|
||||
private ConfigurationEntryStore<AssignedPermission> createSecurityStore() {
|
||||
return configurationEntryStoreFactory.withType(AssignedPermission.class).withName("security").build();
|
||||
}
|
||||
|
||||
private Optional<Path> determineV1File() {
|
||||
Path existingUsersFile = resolveConfigFile("users");
|
||||
Path usersV1File = resolveConfigFile("usersV1");
|
||||
if (existingUsersFile.toFile().exists()) {
|
||||
try {
|
||||
Files.move(existingUsersFile, usersV1File);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not move old users file to " + usersV1File.toAbsolutePath());
|
||||
}
|
||||
LOG.info("moved old users file to {}", usersV1File.toAbsolutePath());
|
||||
return of(usersV1File);
|
||||
}
|
||||
return empty();
|
||||
}
|
||||
|
||||
private Path resolveConfigFile(String name) {
|
||||
return contextProvider
|
||||
.resolve(
|
||||
Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve(name + StoreConstants.FILE_EXTENSION)
|
||||
);
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "user")
|
||||
private static class V1User {
|
||||
private V1Properties properties;
|
||||
private boolean admin;
|
||||
private long creationDate;
|
||||
private String displayName;
|
||||
private Long lastModified;
|
||||
private String mail;
|
||||
private String name;
|
||||
private String password;
|
||||
private String type;
|
||||
private boolean active;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "V1User{" +
|
||||
"properties=" + properties +
|
||||
", admin='" + admin + '\'' +
|
||||
", creationDate=" + creationDate + '\'' +
|
||||
", displayName=" + displayName + '\'' +
|
||||
", lastModified=" + lastModified + '\'' +
|
||||
", mail='" + mail + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", type='" + type + '\'' +
|
||||
", active='" + active + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
private static class UserList {
|
||||
@XmlElement(name = "user")
|
||||
private List<V1User> users;
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "user-db")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
private static class V1UserDatabase {
|
||||
private long creationTime;
|
||||
private Long lastModified;
|
||||
@XmlElement(name = "users")
|
||||
private XmlUserV1UpdateStep.UserList userList;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user