Search in sources :

Example 1 with Revision

use of org.finos.legend.sdlc.domain.model.revision.Revision in project legend-sdlc by finos.

the class GitLabComparisonApiTestResource method runUserWorkspaceComparisonTest.

public void runUserWorkspaceComparisonTest() {
    String projectName = "ComparisonTestProjectOne";
    String description = "A test project.";
    ProjectType projectType = ProjectType.PRODUCTION;
    String groupId = "org.finos.sdlc.test";
    String artifactId = "comptestprojone";
    List<String> tags = Lists.mutable.with("doe", "moffitt", AbstractGitLabServerApiTest.INTEGRATION_TEST_PROJECT_TAG);
    String workspaceOneId = "testworkspaceone";
    Project createdProject = gitLabProjectApi.createProject(projectName, description, projectType, groupId, artifactId, tags);
    Assert.assertNotNull(createdProject);
    Assert.assertEquals(projectName, createdProject.getName());
    Assert.assertEquals(description, createdProject.getDescription());
    Assert.assertEquals(projectType, createdProject.getProjectType());
    Assert.assertEquals(Sets.mutable.withAll(tags), Sets.mutable.withAll(createdProject.getTags()));
    String projectId = createdProject.getProjectId();
    Workspace createdWorkspaceOne = gitLabWorkspaceApi.newUserWorkspace(projectId, workspaceOneId);
    Revision fromRevision = gitLabRevisionApi.getUserWorkspaceRevisionContext(projectId, workspaceOneId).getCurrentRevision();
    Assert.assertNotNull(createdWorkspaceOne);
    Assert.assertEquals(workspaceOneId, createdWorkspaceOne.getWorkspaceId());
    Assert.assertEquals(projectId, createdWorkspaceOne.getProjectId());
    Assert.assertNotNull(createdWorkspaceOne.getUserId());
    String entityPath = "test::entity";
    String classifierPath = "meta::test::mathematicsDepartment";
    Map<String, String> entityContentMap = Maps.mutable.with("package", "test", "name", "entity", "math-113", "abstract-algebra", "math-185", "complex-analysis");
    gitLabEntityApi.getUserWorkspaceEntityModificationContext(projectId, workspaceOneId).createEntity(entityPath, classifierPath, entityContentMap, "initial entity");
    List<Entity> modifiedWorkspaceEntities = gitLabEntityApi.getUserWorkspaceEntityAccessContext(projectId, workspaceOneId).getEntities(null, null, null);
    Assert.assertNotNull(modifiedWorkspaceEntities);
    Assert.assertEquals(1, modifiedWorkspaceEntities.size());
    Entity initalEntity = modifiedWorkspaceEntities.get(0);
    Assert.assertEquals(initalEntity.getPath(), entityPath);
    Assert.assertEquals(initalEntity.getClassifierPath(), classifierPath);
    Assert.assertEquals(initalEntity.getContent(), entityContentMap);
    Revision toRevision = gitLabRevisionApi.getUserWorkspaceRevisionContext(projectId, workspaceOneId).getCurrentRevision();
    List<EntityDiff> entityDiffs = gitLabComparisonApi.getUserWorkspaceCreationComparison(projectId, workspaceOneId).getEntityDiffs();
    String fromRevisionId = gitLabComparisonApi.getUserWorkspaceCreationComparison(projectId, workspaceOneId).getFromRevisionId();
    String toRevisionId = gitLabComparisonApi.getUserWorkspaceCreationComparison(projectId, workspaceOneId).getToRevisionId();
    Assert.assertNotNull(fromRevision);
    Assert.assertNotNull(toRevision);
    Assert.assertEquals(fromRevision.getId(), fromRevisionId);
    Assert.assertEquals(toRevision.getId(), toRevisionId);
    Assert.assertNotNull(entityDiffs);
    Assert.assertEquals(1, entityDiffs.size());
    Assert.assertEquals(EntityChangeType.CREATE, entityDiffs.get(0).getEntityChangeType());
    Revision projectFromRevision = gitLabRevisionApi.getProjectRevisionContext(projectId).getCurrentRevision();
    Revision projectToRevision = gitLabRevisionApi.getUserWorkspaceRevisionContext(projectId, workspaceOneId).getCurrentRevision();
    List<EntityDiff> projectEntityDiffs = gitLabComparisonApi.getUserWorkspaceProjectComparison(projectId, workspaceOneId).getEntityDiffs();
    String projectFromRevisionId = gitLabComparisonApi.getUserWorkspaceProjectComparison(projectId, workspaceOneId).getFromRevisionId();
    String projectToRevisionId = gitLabComparisonApi.getUserWorkspaceProjectComparison(projectId, workspaceOneId).getToRevisionId();
    Assert.assertNotNull(projectFromRevision);
    Assert.assertEquals(projectFromRevision.getId(), projectFromRevisionId);
    Assert.assertEquals(projectToRevision.getId(), projectToRevisionId);
    Assert.assertNotNull(projectEntityDiffs);
    Assert.assertEquals(1, projectEntityDiffs.size());
    Assert.assertEquals(EntityChangeType.CREATE, projectEntityDiffs.get(0).getEntityChangeType());
}
Also used : Project(org.finos.legend.sdlc.domain.model.project.Project) Entity(org.finos.legend.sdlc.domain.model.entity.Entity) Revision(org.finos.legend.sdlc.domain.model.revision.Revision) ProjectType(org.finos.legend.sdlc.domain.model.project.ProjectType) EntityDiff(org.finos.legend.sdlc.domain.model.comparison.EntityDiff) Workspace(org.finos.legend.sdlc.domain.model.project.workspace.Workspace)

Example 2 with Revision

use of org.finos.legend.sdlc.domain.model.revision.Revision in project legend-sdlc by finos.

the class ProjectStructure method updateProjectConfiguration.

private static Revision updateProjectConfiguration(ProjectConfigurationUpdateBuilder updateBuilder, boolean requireRevisionId) {
    ProjectFileAccessProvider projectFileAccessProvider = CachingProjectFileAccessProvider.wrap(updateBuilder.getProjectFileAccessProvider());
    ProjectFileAccessProvider.WorkspaceAccessType workspaceAccessType = updateBuilder.getWorkspaceAccessType();
    WorkspaceType workspaceType = updateBuilder.getWorkspaceType();
    if (updateBuilder.hasGroupId() && !isValidGroupId(updateBuilder.getGroupId())) {
        throw new LegendSDLCServerException("Invalid groupId: " + updateBuilder.getGroupId(), Status.BAD_REQUEST);
    }
    if (updateBuilder.hasArtifactId() && !isValidArtifactId(updateBuilder.getArtifactId())) {
        throw new LegendSDLCServerException("Invalid artifactId: " + updateBuilder.getArtifactId(), Status.BAD_REQUEST);
    }
    ProjectType projectType = updateBuilder.getProjectType();
    String projectId = updateBuilder.getProjectId();
    String workspaceId = updateBuilder.getWorkspaceId();
    String revisionId = updateBuilder.getRevisionId();
    // if revisionId not specified, get the current revision
    if (revisionId == null) {
        Revision currentRevision = projectFileAccessProvider.getRevisionAccessContext(projectId, workspaceId, workspaceType, workspaceAccessType).getCurrentRevision();
        if (currentRevision != null) {
            revisionId = currentRevision.getId();
        } else if (requireRevisionId) {
            StringBuilder builder = new StringBuilder("Could not find current revision for ");
            if (workspaceId != null) {
                builder.append(workspaceType.getLabel() + " " + workspaceAccessType.getLabel()).append(" ").append(workspaceId).append("in ");
            }
            builder.append("project ").append(projectId).append(": it may be corrupt");
            throw new LegendSDLCServerException(builder.toString());
        }
    }
    // find out what we need to update for project structure
    FileAccessContext fileAccessContext = CachingFileAccessContext.wrap(projectFileAccessProvider.getFileAccessContext(projectId, workspaceId, workspaceType, workspaceAccessType, revisionId));
    ProjectFile configFile = getProjectConfigurationFile(fileAccessContext);
    ProjectConfiguration currentConfig = (configFile == null) ? getDefaultProjectConfiguration(projectId, projectType) : readProjectConfiguration(configFile);
    if (projectType != currentConfig.getProjectType()) {
        throw new LegendSDLCServerException("Project type mismatch for project " + projectId + ": got " + projectType + ", found " + currentConfig.getProjectType(), Status.BAD_REQUEST);
    }
    boolean updateProjectStructureVersion = updateBuilder.hasProjectStructureVersion() && (updateBuilder.getProjectStructureVersion() != currentConfig.getProjectStructureVersion().getVersion());
    boolean updateProjectStructureExtensionVersion = updateBuilder.hasProjectStructureExtensionVersion() && !updateBuilder.getProjectStructureExtensionVersion().equals(currentConfig.getProjectStructureVersion().getExtensionVersion());
    boolean updateGroupId = updateBuilder.hasGroupId() && !updateBuilder.getGroupId().equals(currentConfig.getGroupId());
    boolean updateArtifactId = updateBuilder.hasArtifactId() && !updateBuilder.getArtifactId().equals(currentConfig.getArtifactId());
    // find out which dependencies we need to update
    boolean updateProjectDependencies = false;
    Set<ProjectDependency> projectDependencies = Sets.mutable.withAll(currentConfig.getProjectDependencies());
    Set<ProjectDependency> toUpdateProjectDependencies = projectDependencies.stream().filter(dep -> ProjectDependency.isLegacyProjectDependency(dep) && !(updateBuilder.hasProjectDependenciesToRemove() && updateBuilder.getProjectDependenciesToRemove().contains(dep))).collect(Collectors.toSet());
    if (toUpdateProjectDependencies.size() > 0) {
        updateProjectDependencies = true;
        updateOrAddDependencies(toUpdateProjectDependencies, projectFileAccessProvider, projectDependencies, true);
    }
    if (updateBuilder.hasProjectDependenciesToRemove()) {
        updateProjectDependencies |= projectDependencies.removeAll(updateBuilder.getProjectDependenciesToRemove());
    }
    // add new dependencies to the list of dependencies while also validate that there are no unknown/non-prod dependencies
    if (updateBuilder.hasProjectDependenciesToAdd()) {
        updateProjectDependencies = true;
        updateOrAddDependencies(updateBuilder.getProjectDependenciesToAdd(), projectFileAccessProvider, projectDependencies, false);
    }
    // validate if there are any conflicts between the dependencies
    if (updateProjectDependencies) {
        validateDependencyConflicts(projectDependencies, ProjectDependency::getProjectId, (id, deps) -> {
            if ((deps.size() <= 1) || deps.stream().allMatch(dep -> getProjectStructure(projectFileAccessProvider.getFileAccessContext(dep.getProjectId(), dep.getVersionId())).isSupportedArtifactType(ArtifactType.versioned_entities))) {
                return null;
            }
            List<ProjectDependency> supported = Lists.mutable.empty();
            List<ProjectDependency> unsupported = Lists.mutable.empty();
            deps.forEach(dep -> (getProjectStructure(projectFileAccessProvider.getFileAccessContext(dep.getProjectId(), dep.getVersionId())).isSupportedArtifactType(ArtifactType.versioned_entities) ? supported : unsupported).add(dep));
            StringBuilder message = new StringBuilder();
            unsupported.forEach(dep -> dep.appendVersionIdString((message.length() == 0) ? message : message.append(", ")));
            message.append((unsupported.size() == 1) ? " does" : " do").append(" not support multi-version dependency");
            if (!supported.isEmpty()) {
                int startLen = message.length();
                supported.forEach(dep -> dep.appendVersionIdString(message.append((message.length() == startLen) ? "; " : ", ")));
                message.append((supported.size() == 1) ? " does" : " do");
            }
            return message.toString();
        }, "projects");
    }
    // check if we need to update any metamodel dependencies
    boolean updateMetamodelDependencies = false;
    Set<MetamodelDependency> metamodelDependencies = Sets.mutable.withAll(currentConfig.getMetamodelDependencies());
    if (updateBuilder.hasMetamodelDependenciesToRemove()) {
        updateMetamodelDependencies |= metamodelDependencies.removeAll(updateBuilder.getMetamodelDependenciesToRemove());
    }
    // add new metamodel dependencies to the list of metamodel dependencies while also validate that there are no unknown metamodel dependencies
    if (updateBuilder.hasMetamodelDependenciesToAdd()) {
        List<MetamodelDependency> unknownDependencies = Lists.mutable.empty();
        for (MetamodelDependency metamodelDependency : updateBuilder.getMetamodelDependenciesToAdd()) {
            if (metamodelDependencies.add(metamodelDependency)) {
                updateMetamodelDependencies = true;
                if (!isKnownMetamodel(metamodelDependency)) {
                    unknownDependencies.add(metamodelDependency);
                }
            }
        }
        if (!unknownDependencies.isEmpty()) {
            StringBuilder builder = new StringBuilder("There were issues with one or more added metamodel dependencies");
            builder.append("; unknown ").append((unknownDependencies.size() == 1) ? "dependency" : "dependencies").append(": ");
            unknownDependencies.sort(Comparator.naturalOrder());
            unknownDependencies.forEach(d -> d.appendDependencyString((d == unknownDependencies.get(0)) ? builder : builder.append(", ")));
            throw new LegendSDLCServerException(builder.toString(), Status.BAD_REQUEST);
        }
    }
    // validate that there are no conflicts between the metamodel dependencies
    if (updateMetamodelDependencies) {
        validateDependencyConflicts(metamodelDependencies, MetamodelDependency::getMetamodel, (id, deps) -> (deps.size() > 1) ? deps.stream().collect(StringBuilder::new, (builder, dep) -> dep.appendVersionIdString(builder.append((builder.length() == 0) ? "multiple versions not allowed: " : ", ")), StringBuilder::append).toString() : null, "metamodels");
    }
    boolean updateGeneration = false;
    Map<String, ArtifactGeneration> generationsByName = currentConfig.getArtifactGenerations().stream().collect(Collectors.toMap(ArtifactGeneration::getName, Function.identity()));
    if (updateBuilder.hasArtifactGenerationsToRemove()) {
        updateGeneration = generationsByName.keySet().stream().anyMatch(updateBuilder.getArtifactGenerationToRemove()::contains);
        updateBuilder.getArtifactGenerationToRemove().forEach(generationsByName::remove);
    }
    if (updateBuilder.hasArtifactGenerationsToAdd()) {
        validateArtifactGenerations(generationsByName, updateBuilder.getArtifactGenerationToAdd());
        updateGeneration = updateBuilder.getArtifactGenerationToAdd().stream().noneMatch(generationsByName.values()::contains);
        updateBuilder.getArtifactGenerationToAdd().forEach(art -> generationsByName.put(art.getName(), art));
    }
    // abort if there is no change to make
    if (!updateProjectStructureVersion && !updateProjectStructureExtensionVersion && !updateGroupId && !updateArtifactId && !updateProjectDependencies && !updateMetamodelDependencies && !updateGeneration) {
        return null;
    }
    // Collect operations
    List<ProjectFileOperation> operations = Lists.mutable.empty();
    // New configuration
    SimpleProjectConfiguration newConfig = new SimpleProjectConfiguration(currentConfig);
    if (updateProjectStructureVersion) {
        if (updateBuilder.hasProjectStructureExtensionVersion()) {
            newConfig.setProjectStructureVersion(updateBuilder.getProjectStructureVersion(), updateBuilder.getProjectStructureExtensionVersion());
        } else if (updateBuilder.hasProjectStructureExtensionProvider()) {
            newConfig.setProjectStructureVersion(updateBuilder.getProjectStructureVersion(), updateBuilder.getProjectStructureExtensionProvider().getLatestVersionForProjectStructureVersion(updateBuilder.getProjectStructureVersion()));
        } else {
            newConfig.setProjectStructureVersion(updateBuilder.getProjectStructureVersion(), null);
        }
    } else if (updateProjectStructureExtensionVersion) {
        newConfig.setProjectStructureVersion(currentConfig.getProjectStructureVersion().getVersion(), updateBuilder.getProjectStructureExtensionVersion());
    }
    if (updateGroupId) {
        newConfig.setGroupId(updateBuilder.getGroupId());
    }
    if (updateArtifactId) {
        newConfig.setArtifactId(updateBuilder.getArtifactId());
    }
    if (updateProjectDependencies) {
        List<ProjectDependency> projectDependencyList = Lists.mutable.withAll(projectDependencies);
        projectDependencyList.sort(Comparator.naturalOrder());
        newConfig.setProjectDependencies(projectDependencyList);
    }
    if (updateMetamodelDependencies) {
        List<MetamodelDependency> metamodelDependencyList = Lists.mutable.withAll(metamodelDependencies);
        metamodelDependencyList.sort(Comparator.naturalOrder());
        newConfig.setMetamodelDependencies(metamodelDependencyList);
    }
    if (updateGeneration) {
        List<ArtifactGeneration> artifactGenerationsList = Lists.mutable.withAll(generationsByName.values());
        artifactGenerationsList.sort(Comparator.comparing(ArtifactGeneration::getName));
        newConfig.setArtifactGeneration(artifactGenerationsList);
    }
    // prevent downgrading project
    if (newConfig.getProjectStructureVersion().compareTo(currentConfig.getProjectStructureVersion()) < 0) {
        throw new LegendSDLCServerException("Cannot change project " + projectId + " from project structure version " + currentConfig.getProjectStructureVersion().toVersionString() + " to version " + newConfig.getProjectStructureVersion().toVersionString(), Status.BAD_REQUEST);
    }
    String serializedNewConfig = serializeProjectConfiguration(newConfig);
    operations.add((configFile == null) ? ProjectFileOperation.addFile(PROJECT_CONFIG_PATH, serializedNewConfig) : ProjectFileOperation.modifyFile(PROJECT_CONFIG_PATH, serializedNewConfig));
    ProjectStructure currentProjectStructure = getProjectStructure(currentConfig, updateBuilder.getProjectStructurePlatformExtensions());
    ProjectStructure newProjectStructure = getProjectStructure(newConfig, updateBuilder.getProjectStructurePlatformExtensions());
    // Move or re-serialize entities if necessary
    List<EntitySourceDirectory> currentEntityDirectories = currentProjectStructure.getEntitySourceDirectories();
    List<EntitySourceDirectory> newEntityDirectories = newProjectStructure.getEntitySourceDirectories();
    if (!currentEntityDirectories.equals(newEntityDirectories)) {
        currentEntityDirectories.forEach(currentSourceDirectory -> fileAccessContext.getFilesInDirectory(currentSourceDirectory.getDirectory()).forEach(file -> {
            String currentPath = file.getPath();
            if (currentSourceDirectory.isPossiblyEntityFilePath(currentPath)) {
                byte[] currentBytes = file.getContentAsBytes();
                Entity entity;
                try {
                    entity = currentSourceDirectory.deserialize(currentBytes);
                } catch (Exception e) {
                    StringBuilder builder = new StringBuilder("Error deserializing entity from file \"").append(currentPath).append('"');
                    StringTools.appendThrowableMessageIfPresent(builder, e);
                    throw new LegendSDLCServerException(builder.toString(), e);
                }
                EntitySourceDirectory newSourceDirectory = Iterate.detectWith(newEntityDirectories, EntitySourceDirectory::canSerialize, entity);
                if (newSourceDirectory == null) {
                    throw new LegendSDLCServerException("Could not find a new source directory for entity " + entity.getPath() + ", currently in " + currentPath);
                }
                if (!currentSourceDirectory.equals(newSourceDirectory)) {
                    String newPath = newSourceDirectory.entityPathToFilePath(entity.getPath());
                    byte[] newBytes = newSourceDirectory.serializeToBytes(entity);
                    if (!newPath.equals(currentPath)) {
                        operations.add(ProjectFileOperation.moveFile(currentPath, newPath, newBytes));
                    } else if (!Arrays.equals(currentBytes, newBytes)) {
                        operations.add(ProjectFileOperation.modifyFile(currentPath, newBytes));
                    }
                }
            }
        }));
    }
    // Collect any further update operations
    newProjectStructure.collectUpdateProjectConfigurationOperations(currentProjectStructure, fileAccessContext, projectFileAccessProvider::getFileAccessContext, operations::add);
    // Collect update operations from any project structure extension
    if (updateBuilder.hasProjectStructureExtensionProvider() && (newConfig.getProjectStructureVersion().getExtensionVersion() != null)) {
        ProjectStructureExtension projectStructureExtension = updateBuilder.getProjectStructureExtensionProvider().getProjectStructureExtension(newConfig.getProjectStructureVersion().getVersion(), newConfig.getProjectStructureVersion().getExtensionVersion());
        projectStructureExtension.collectUpdateProjectConfigurationOperations(currentConfig, newConfig, fileAccessContext, operations::add);
    }
    // Submit changes
    return projectFileAccessProvider.getFileModificationContext(projectId, workspaceId, workspaceType, workspaceAccessType, revisionId).submit(updateBuilder.getMessage(), operations);
}
Also used : ProjectType(org.finos.legend.sdlc.domain.model.project.ProjectType) Arrays(java.util.Arrays) ArtifactType(org.finos.legend.sdlc.domain.model.project.configuration.ArtifactType) SortedSet(java.util.SortedSet) BiFunction(java.util.function.BiFunction) MutableList(org.eclipse.collections.api.list.MutableList) StringTools(org.finos.legend.sdlc.server.tools.StringTools) LegendSDLCServerException(org.finos.legend.sdlc.server.error.LegendSDLCServerException) Map(java.util.Map) EnumSet(java.util.EnumSet) Dependency(org.finos.legend.sdlc.domain.model.project.configuration.Dependency) ArtifactTypeGenerationConfiguration(org.finos.legend.sdlc.domain.model.project.configuration.ArtifactTypeGenerationConfiguration) VersionId(org.finos.legend.sdlc.domain.model.version.VersionId) MetamodelDependency(org.finos.legend.sdlc.domain.model.project.configuration.MetamodelDependency) Collection(java.util.Collection) Set(java.util.Set) StreamWriteFeature(com.fasterxml.jackson.core.StreamWriteFeature) Reader(java.io.Reader) Iterate(org.eclipse.collections.impl.utility.Iterate) StreamReadFeature(com.fasterxml.jackson.core.StreamReadFeature) JsonMapper(com.fasterxml.jackson.databind.json.JsonMapper) Collectors(java.util.stream.Collectors) ListAdapter(org.eclipse.collections.impl.list.mutable.ListAdapter) SourceVersion(javax.lang.model.SourceVersion) List(java.util.List) Stream(java.util.stream.Stream) Pattern(java.util.regex.Pattern) ArtifactGeneration(org.finos.legend.sdlc.domain.model.project.configuration.ArtifactGeneration) SortedMap(java.util.SortedMap) ProjectStructureExtension(org.finos.legend.sdlc.server.project.extension.ProjectStructureExtension) Lists(org.eclipse.collections.api.factory.Lists) Function(java.util.function.Function) TreeSet(java.util.TreeSet) FileAccessContext(org.finos.legend.sdlc.server.project.ProjectFileAccessProvider.FileAccessContext) MapperFeature(com.fasterxml.jackson.databind.MapperFeature) EntitySerializer(org.finos.legend.sdlc.serialization.EntitySerializer) Entity(org.finos.legend.sdlc.domain.model.entity.Entity) Status(javax.ws.rs.core.Response.Status) Sets(org.eclipse.collections.api.factory.Sets) IOException(java.io.IOException) EntitySerializers(org.finos.legend.sdlc.serialization.EntitySerializers) WorkspaceType(org.finos.legend.sdlc.domain.model.project.workspace.WorkspaceType) ProjectStructureVersion(org.finos.legend.sdlc.domain.model.project.configuration.ProjectStructureVersion) Consumer(java.util.function.Consumer) ProjectFile(org.finos.legend.sdlc.server.project.ProjectFileAccessProvider.ProjectFile) ProjectDependency(org.finos.legend.sdlc.domain.model.project.configuration.ProjectDependency) TreeMap(java.util.TreeMap) ProjectConfiguration(org.finos.legend.sdlc.domain.model.project.configuration.ProjectConfiguration) SerializationFeature(com.fasterxml.jackson.databind.SerializationFeature) Revision(org.finos.legend.sdlc.domain.model.revision.Revision) Comparator(java.util.Comparator) Collections(java.util.Collections) InputStream(java.io.InputStream) Entity(org.finos.legend.sdlc.domain.model.entity.Entity) ArtifactGeneration(org.finos.legend.sdlc.domain.model.project.configuration.ArtifactGeneration) ProjectStructureExtension(org.finos.legend.sdlc.server.project.extension.ProjectStructureExtension) ProjectFile(org.finos.legend.sdlc.server.project.ProjectFileAccessProvider.ProjectFile) LegendSDLCServerException(org.finos.legend.sdlc.server.error.LegendSDLCServerException) FileAccessContext(org.finos.legend.sdlc.server.project.ProjectFileAccessProvider.FileAccessContext) ProjectDependency(org.finos.legend.sdlc.domain.model.project.configuration.ProjectDependency) MetamodelDependency(org.finos.legend.sdlc.domain.model.project.configuration.MetamodelDependency) LegendSDLCServerException(org.finos.legend.sdlc.server.error.LegendSDLCServerException) IOException(java.io.IOException) WorkspaceType(org.finos.legend.sdlc.domain.model.project.workspace.WorkspaceType) Revision(org.finos.legend.sdlc.domain.model.revision.Revision) ProjectType(org.finos.legend.sdlc.domain.model.project.ProjectType) ProjectConfiguration(org.finos.legend.sdlc.domain.model.project.configuration.ProjectConfiguration)

Example 3 with Revision

use of org.finos.legend.sdlc.domain.model.revision.Revision in project legend-sdlc by finos.

the class GitLabProjectApi method importProject.

@Override
public ImportReport importProject(String id, ProjectType type, String groupId, String artifactId) {
    LegendSDLCServerException.validateNonNull(id, "id may not be null");
    LegendSDLCServerException.validateNonNull(type, "type may not be null");
    LegendSDLCServerException.validate(groupId, ProjectStructure::isValidGroupId, g -> "Invalid groupId: " + g);
    LegendSDLCServerException.validate(artifactId, ProjectStructure::isValidArtifactId, a -> "Invalid artifactId: " + a);
    // Get project id
    GitLabProjectId projectId;
    if (id.chars().allMatch(Character::isDigit)) {
        projectId = GitLabProjectId.newProjectId(getGitLabModeFromProjectType(type), Integer.parseInt(id));
    } else {
        projectId = parseProjectId(id);
        if (projectId.getGitLabMode() != getGitLabModeFromProjectType(type)) {
            throw new LegendSDLCServerException("Invalid project id \"" + id + "\" for project type " + type, Status.BAD_REQUEST);
        }
    }
    // Find project
    GitLabApi gitLabApi = getGitLabApi(projectId.getGitLabMode());
    org.gitlab4j.api.ProjectApi gitLabProjectApi = gitLabApi.getProjectApi();
    org.gitlab4j.api.models.Project currentProject;
    try {
        currentProject = withRetries(() -> gitLabProjectApi.getProject(projectId.getGitLabId()));
    } catch (Exception e) {
        throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to access project " + id + " of type " + type, () -> "Could not find project " + id + " of type " + type, () -> "Failed to access project " + id + " of type " + type);
    }
    // Create a workspace for project configuration
    RepositoryApi repositoryApi = gitLabApi.getRepositoryApi();
    String workspaceId = "ProjectConfiguration_" + getRandomIdString();
    Branch workspaceBranch;
    try {
        workspaceBranch = GitLabApiTools.createBranchFromSourceBranchAndVerify(repositoryApi, projectId.getGitLabId(), getWorkspaceBranchName(workspaceId, WorkspaceType.USER, ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE), MASTER_BRANCH, 30, 1_000);
    } catch (Exception e) {
        throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to create a workspace for initial configuration of project " + id + " of type " + type, () -> "Could not find project " + id + " of type " + type, () -> "Failed to create workspace for initial configuration of project " + id + " of type " + type);
    }
    if (workspaceBranch == null) {
        throw new LegendSDLCServerException("Failed to create workspace " + workspaceId + " in project " + projectId);
    }
    // Configure project in workspace
    ProjectFileAccessProvider projectFileAccessProvider = getProjectFileAccessProvider();
    Revision configRevision;
    try {
        ProjectConfiguration currentConfig = ProjectStructure.getProjectConfiguration(projectId.toString(), null, null, projectFileAccessProvider, WorkspaceType.USER, ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE);
        ProjectConfigurationUpdateBuilder builder = ProjectConfigurationUpdateBuilder.newBuilder(projectFileAccessProvider, type, projectId.toString()).withWorkspace(workspaceId, WorkspaceType.USER, ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE).withGroupId(groupId).withArtifactId(artifactId).withProjectStructureExtensionProvider(this.projectStructureExtensionProvider).withProjectStructurePlatformExtensions(this.projectStructurePlatformExtensions);
        int defaultProjectStructureVersion = getDefaultProjectStructureVersion();
        if (currentConfig == null) {
            // No current project structure: build a new one
            configRevision = builder.withProjectStructureVersion(defaultProjectStructureVersion).withMessage("Build project structure").buildProjectStructure();
        } else {
            // Existing project structure: update
            if (currentConfig.getProjectType() != type) {
                throw new LegendSDLCServerException("Mismatch between requested project type (" + type + ") and found project type (" + currentConfig.getProjectType() + ")", Status.BAD_REQUEST);
            }
            ProjectStructureVersion currentVersion = currentConfig.getProjectStructureVersion();
            if ((currentVersion == null) || (currentVersion.getVersion() < defaultProjectStructureVersion)) {
                builder.withProjectStructureVersion(defaultProjectStructureVersion).withProjectStructureExtensionVersion(null);
            }
            configRevision = builder.withMessage("Update project structure").updateProjectConfiguration();
        }
    } catch (Exception e) {
        // Try to delete the branch in case of exception
        deleteWorkspace(projectId, repositoryApi, workspaceId);
        throw e;
    }
    // Submit workspace changes, if any, for review
    String reviewId;
    if (configRevision == null) {
        // No changes: nothing to submit
        reviewId = null;
        // Try to delete the branch
        deleteWorkspace(projectId, repositoryApi, workspaceId);
    } else {
        MergeRequest mergeRequest;
        try {
            mergeRequest = gitLabApi.getMergeRequestApi().createMergeRequest(projectId.getGitLabId(), getWorkspaceBranchName(workspaceId, WorkspaceType.USER, ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE), MASTER_BRANCH, "Project structure", "Set up project structure", null, null, null, null, true, false);
        } catch (Exception e) {
            // Try to delete the branch in case of exception
            deleteWorkspace(projectId, repositoryApi, workspaceId);
            throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to submit project configuration changes create a workspace for initial configuration of project " + id + " of type " + type, () -> "Could not find workspace " + workspaceId + " project " + id + " of type " + type, () -> "Failed to create a review for configuration of project " + id + " of type " + type);
        }
        reviewId = toStringIfNotNull(mergeRequest.getIid());
    }
    // Add tags
    Project finalProject;
    List<String> currentTags = currentProject.getTagList();
    if ((currentTags != null) && currentTags.stream().anyMatch(this::isLegendSDLCProjectTag)) {
        // already has the necessary tag
        finalProject = fromGitLabProject(currentProject, projectId.getGitLabMode());
    } else {
        List<String> updatedTags = Lists.mutable.ofInitialCapacity((currentTags == null) ? 1 : (currentTags.size() + 1));
        if (currentTags != null) {
            updatedTags.addAll(currentTags);
        }
        updatedTags.add(getLegendSDLCProjectTag());
        org.gitlab4j.api.models.Project updatedProject;
        try {
            updatedProject = gitLabProjectApi.updateProject(new org.gitlab4j.api.models.Project().withId(currentProject.getId()).withTagList(updatedTags));
        } catch (Exception e) {
            // Try to delete the branch in case of exception
            deleteWorkspace(projectId, repositoryApi, workspaceId);
            throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to import project " + id + " of type " + type, () -> "Could not find project " + id + " of type " + type, () -> "Failed to import project " + id + " of type " + type);
        }
        finalProject = fromGitLabProject(updatedProject, projectId.getGitLabMode());
    }
    return new ImportReport() {

        @Override
        public Project getProject() {
            return finalProject;
        }

        @Override
        public String getReviewId() {
            return reviewId;
        }
    };
}
Also used : GitLabApi(org.gitlab4j.api.GitLabApi) ProjectStructureVersion(org.finos.legend.sdlc.domain.model.project.configuration.ProjectStructureVersion) LegendSDLCServerException(org.finos.legend.sdlc.server.error.LegendSDLCServerException) MergeRequest(org.gitlab4j.api.models.MergeRequest) GitLabProjectId(org.finos.legend.sdlc.server.gitlab.GitLabProjectId) Branch(org.gitlab4j.api.models.Branch) RepositoryApi(org.gitlab4j.api.RepositoryApi) ProjectStructure(org.finos.legend.sdlc.server.project.ProjectStructure) LegendSDLCServerException(org.finos.legend.sdlc.server.error.LegendSDLCServerException) ProjectFileAccessProvider(org.finos.legend.sdlc.server.project.ProjectFileAccessProvider) Project(org.finos.legend.sdlc.domain.model.project.Project) Revision(org.finos.legend.sdlc.domain.model.revision.Revision) ProjectConfigurationUpdateBuilder(org.finos.legend.sdlc.server.project.ProjectConfigurationUpdateBuilder) ProjectConfiguration(org.finos.legend.sdlc.domain.model.project.configuration.ProjectConfiguration)

Example 4 with Revision

use of org.finos.legend.sdlc.domain.model.revision.Revision in project legend-sdlc by finos.

the class GitLabProjectConfigurationApi method updateProjectConfigurationByWorkspaceAccessType.

private Revision updateProjectConfigurationByWorkspaceAccessType(String projectId, String workspaceId, WorkspaceType workspaceType, ProjectFileAccessProvider.WorkspaceAccessType workspaceAccessType, String message, Integer projectStructureVersion, Integer projectStructureExtensionVersion, String groupId, String artifactId, Iterable<? extends ProjectDependency> projectDependenciesToAdd, Iterable<? extends ProjectDependency> projectDependenciesToRemove, List<? extends ArtifactGeneration> artifactGenerationsToAdd, List<String> artifactGenerationsToRemove) {
    LegendSDLCServerException.validateNonNull(projectId, "projectId may not be null");
    LegendSDLCServerException.validateNonNull(workspaceId, "workspaceId may not be null");
    LegendSDLCServerException.validateNonNull(workspaceType, "workspaceType may not be null");
    LegendSDLCServerException.validateNonNull(workspaceAccessType, "workspaceAccessType may not be null");
    LegendSDLCServerException.validateNonNull(message, "message may not be null");
    if ((groupId != null) && !ProjectStructure.isValidGroupId(groupId)) {
        throw new LegendSDLCServerException("Invalid groupId: " + groupId, Status.BAD_REQUEST);
    }
    if ((artifactId != null) && !ProjectStructure.isValidArtifactId(artifactId)) {
        throw new LegendSDLCServerException("Invalid artifactId: " + artifactId, Status.BAD_REQUEST);
    }
    if ((projectStructureVersion != null) && (this.projectStructureConfig != null) && this.projectStructureConfig.getDemisedVersions().contains(projectStructureVersion)) {
        throw new LegendSDLCServerException("Project structure version " + projectStructureVersion + " is demised", Status.BAD_REQUEST);
    }
    try {
        ProjectFileAccessProvider fileAccessProvider = getProjectFileAccessProvider();
        Revision currentRevision = fileAccessProvider.getRevisionAccessContext(projectId, workspaceId, workspaceType, workspaceAccessType).getCurrentRevision();
        if (currentRevision == null) {
            throw new LegendSDLCServerException("Could not find current revision for " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " " + workspaceId + " in project " + projectId + ": " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " may be corrupt");
        }
        return ProjectConfigurationUpdateBuilder.newBuilder(fileAccessProvider, getProjectTypeFromMode(GitLabProjectId.getGitLabMode(projectId)), projectId).withWorkspace(workspaceId, workspaceType, workspaceAccessType).withRevisionId(currentRevision.getId()).withMessage(message).withProjectStructureVersion(projectStructureVersion).withProjectStructureExtensionVersion(projectStructureExtensionVersion).withGroupId(groupId).withArtifactId(artifactId).withProjectDependenciesToAdd(projectDependenciesToAdd).withProjectDependenciesToRemove(projectDependenciesToRemove).withArtifactGenerationsToAdd(artifactGenerationsToAdd).withArtifactGenerationsToRemove(artifactGenerationsToRemove).withProjectStructureExtensionProvider(this.projectStructureExtensionProvider).withProjectStructurePlatformExtensions(this.projectStructurePlatformExtensions).updateProjectConfiguration();
    } catch (Exception e) {
        throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to update project configuration for project " + projectId + " in " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " " + workspaceId, () -> "Unknown project (" + projectId + ") or " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " (" + workspaceId + ")", () -> "Failed to update project configuration for project " + projectId + " in " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " " + workspaceId);
    }
}
Also used : ProjectFileAccessProvider(org.finos.legend.sdlc.server.project.ProjectFileAccessProvider) LegendSDLCServerException(org.finos.legend.sdlc.server.error.LegendSDLCServerException) Revision(org.finos.legend.sdlc.domain.model.revision.Revision) LegendSDLCServerException(org.finos.legend.sdlc.server.error.LegendSDLCServerException)

Example 5 with Revision

use of org.finos.legend.sdlc.domain.model.revision.Revision in project legend-sdlc by finos.

the class GitLabWorkspaceApi method updateWorkspace.

/**
 * There are 4 possible outcome for this method:
 * 1. NO_OP: If the workspace is already branching from the HEAD of master, nothing is needed.
 * 2. UPDATED: If the workspace is not branching from the HEAD of master, and we successfully rebase the branch to master HEAD.
 * 3. CONFLICT: If the workspace is not branching from the HEAD of master, and we failed to rebase the branch to master HEAD due to merge conflicts.
 * 4. ERROR
 * <p>
 * The procedure goes like the followings:
 * 1. Check if the current workspace is already up to date:
 * - If yes, return NO_OP
 * - If no, proceed
 * 2. Create a temp branch to attempt to rebase:
 * - If rebase succeeded, return UPDATED
 * - If rebase failed, further check if we need to enter conflict resolution mode.
 * This check makes sure the conflict that causes rebase to fail does not come from intermediate
 * commits by squashing these commits and attempt to do another rebase there. If this still fails
 * it means the workspace in overall truly has merge conflicts while updating, so entering conflict resolution mode
 */
@Override
public WorkspaceUpdateReport updateWorkspace(String projectId, String workspaceId, WorkspaceType workspaceType) {
    LegendSDLCServerException.validateNonNull(projectId, "projectId may not be null");
    LegendSDLCServerException.validateNonNull(workspaceId, "workspaceId may not be null");
    LOGGER.info("Updating workspace {} in project {} to latest revision", workspaceId, projectId);
    GitLabProjectId gitLabProjectId = parseProjectId(projectId);
    ProjectFileAccessProvider.WorkspaceAccessType workspaceAccessType = ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE;
    String workspaceBranchName = getBranchName(workspaceId, workspaceType, workspaceAccessType);
    GitLabApi gitLabApi = getGitLabApi(gitLabProjectId.getGitLabMode());
    RepositoryApi repositoryApi = gitLabApi.getRepositoryApi();
    // Get the workspace branch
    Branch workspaceBranch;
    try {
        workspaceBranch = withRetries(() -> repositoryApi.getBranch(gitLabProjectId.getGitLabId(), workspaceBranchName));
    } catch (Exception e) {
        throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to access " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " " + workspaceId + " in project " + projectId, () -> "Unknown " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " in project " + projectId + ": " + workspaceId, () -> "Error accessing " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " " + workspaceId + " in project " + projectId);
    }
    String currentWorkspaceRevisionId = workspaceBranch.getCommit().getId();
    LOGGER.info("Found latest revision of {} {} in project {}: {}", workspaceType.getLabel() + " " + workspaceAccessType.getLabel(), workspaceId, projectId, currentWorkspaceRevisionId);
    // Determine the revision to update to
    Branch masterBranch;
    try {
        masterBranch = withRetries(() -> repositoryApi.getBranch(gitLabProjectId.getGitLabId(), MASTER_BRANCH));
    } catch (Exception e) {
        throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to access the latest revision in project " + projectId, () -> "Unknown project: " + projectId, () -> "Error accessing latest revision for project " + projectId);
    }
    String masterRevisionId = masterBranch.getCommit().getId();
    LOGGER.info("Found latest revision of project {}: {}", projectId, masterRevisionId);
    CommitsApi commitsApi = gitLabApi.getCommitsApi();
    // Check if the workspace already has the latest revision
    try {
        boolean isAlreadyLatest = false;
        // This will check if the branch contains the master HEAD commit by looking up the list of references the commit is pushed to
        if (masterRevisionId.equals(currentWorkspaceRevisionId)) {
            isAlreadyLatest = true;
        } else {
            Pager<CommitRef> masterHeadCommitRefPager = withRetries(() -> commitsApi.getCommitRefs(gitLabProjectId.getGitLabId(), masterRevisionId, RefType.BRANCH, ITEMS_PER_PAGE));
            Stream<CommitRef> masterHeadCommitRefs = PagerTools.stream(masterHeadCommitRefPager);
            if (masterHeadCommitRefs.anyMatch(cr -> workspaceBranchName.equals(cr.getName()))) {
                isAlreadyLatest = true;
            }
        }
        if (isAlreadyLatest) {
            // revision is already in the workspace, no update necessary, hence NO_OP
            LOGGER.info("Workspace {} in project {} already has revision {}, no update necessary", workspaceId, projectId, masterRevisionId);
            return createWorkspaceUpdateReport(WorkspaceUpdateReportStatus.NO_OP, masterRevisionId, currentWorkspaceRevisionId);
        }
    } catch (Exception e) {
        throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to access revision " + masterRevisionId + " in project " + projectId, () -> "Unknown revision in project " + projectId + ": " + masterRevisionId, () -> "Error accessing revision " + masterRevisionId + " of project " + projectId);
    }
    // Temp branch for checking for merge conflicts
    String tempBranchName = newUserTemporaryBranchName();
    Branch tempBranch;
    try {
        tempBranch = GitLabApiTools.createBranchAndVerify(repositoryApi, gitLabProjectId.getGitLabId(), tempBranchName, currentWorkspaceRevisionId, 30, 1_000);
    } catch (Exception e) {
        throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to create temporary workspace " + tempBranchName + " in project " + projectId, () -> "Unknown project: " + projectId, () -> "Error creating temporary workspace " + tempBranchName + " in project " + projectId);
    }
    if (tempBranch == null) {
        throw new LegendSDLCServerException("Failed to create temporary workspace " + tempBranchName + " in project " + projectId + " from revision " + currentWorkspaceRevisionId);
    }
    // Attempt to rebase the temporary branch on top of master
    boolean rebaseSucceeded = this.attemptToRebaseWorkspaceUsingTemporaryBranch(projectId, workspaceId, workspaceType, tempBranchName, masterRevisionId);
    // 2. There are merge conflicts, so we enter conflict resolution route
    if (!rebaseSucceeded) {
        String workspaceCreationRevisionId;
        try {
            workspaceCreationRevisionId = withRetries(() -> repositoryApi.getMergeBase(gitLabProjectId.getGitLabId(), Arrays.asList(MASTER_BRANCH, currentWorkspaceRevisionId)).getId());
        } catch (Exception e) {
            throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to get merged base revision for workspace " + workspaceId + " from project " + projectId, () -> "Could not find revision " + currentWorkspaceRevisionId + " from project " + projectId, () -> "Failed to fetch merged base revision for workspace " + workspaceId + " from project " + projectId);
        }
        // Small optimization step to make sure we need squashing.
        // If there are less than 2 commits (not including the base commit), there is no point in squashing
        List<Revision> latestTwoRevisionsOnWorkspaceBranch = this.revisionApi.getWorkspaceRevisionContext(projectId, workspaceId, workspaceType).getRevisions(null, null, null, 2);
        Set<String> latestTwoRevisionOnWorkspaceBranchIds = latestTwoRevisionsOnWorkspaceBranch.stream().map(Revision::getId).collect(Collectors.toSet());
        if (latestTwoRevisionOnWorkspaceBranchIds.contains(workspaceCreationRevisionId)) {
            LOGGER.debug("Failed to rebase branch {}, but the branch does not have enough commits to perform squashing. Proceeding to conflict resolution...", workspaceBranchName);
            return this.createConflictResolution(projectId, workspaceId, workspaceType, masterRevisionId);
        } else {
            LOGGER.debug("Failed to rebase branch {}. Performing squashing commits and re-attempting rebase...", workspaceBranchName);
        }
        WorkspaceUpdateReport rebaseUpdateAttemptReport = this.attemptToSquashAndRebaseWorkspace(projectId, workspaceId, workspaceType, masterRevisionId, currentWorkspaceRevisionId, workspaceCreationRevisionId);
        return WorkspaceUpdateReportStatus.UPDATED.equals(rebaseUpdateAttemptReport.getStatus()) ? rebaseUpdateAttemptReport : this.createConflictResolution(projectId, workspaceId, workspaceType, masterRevisionId);
    }
    String updatedCurrentWorkspaceRevisionId = this.revisionApi.getWorkspaceRevisionContext(projectId, workspaceId, workspaceType).getCurrentRevision().getId();
    return createWorkspaceUpdateReport(WorkspaceUpdateReportStatus.UPDATED, masterRevisionId, updatedCurrentWorkspaceRevisionId);
}
Also used : GitLabApi(org.gitlab4j.api.GitLabApi) CommitRef(org.gitlab4j.api.models.CommitRef) LegendSDLCServerException(org.finos.legend.sdlc.server.error.LegendSDLCServerException) GitLabApiException(org.gitlab4j.api.GitLabApiException) CommitsApi(org.gitlab4j.api.CommitsApi) ProjectFileAccessProvider(org.finos.legend.sdlc.server.project.ProjectFileAccessProvider) LegendSDLCServerException(org.finos.legend.sdlc.server.error.LegendSDLCServerException) Revision(org.finos.legend.sdlc.domain.model.revision.Revision) GitLabProjectId(org.finos.legend.sdlc.server.gitlab.GitLabProjectId) Branch(org.gitlab4j.api.models.Branch) RepositoryApi(org.gitlab4j.api.RepositoryApi)

Aggregations

Revision (org.finos.legend.sdlc.domain.model.revision.Revision)26 Entity (org.finos.legend.sdlc.domain.model.entity.Entity)15 ProjectConfiguration (org.finos.legend.sdlc.domain.model.project.configuration.ProjectConfiguration)10 Workspace (org.finos.legend.sdlc.domain.model.project.workspace.Workspace)9 LegendSDLCServerException (org.finos.legend.sdlc.server.error.LegendSDLCServerException)7 Project (org.finos.legend.sdlc.domain.model.project.Project)6 ProjectType (org.finos.legend.sdlc.domain.model.project.ProjectType)6 Instant (java.time.Instant)5 List (java.util.List)5 Test (org.junit.Test)5 Response (javax.ws.rs.core.Response)4 HttpResponseException (org.apache.http.client.HttpResponseException)4 Collections (java.util.Collections)3 Comparator (java.util.Comparator)3 Function (java.util.function.Function)3 Collectors (java.util.stream.Collectors)3 Lists (org.eclipse.collections.api.factory.Lists)3 Iterate (org.eclipse.collections.impl.utility.Iterate)3 ArtifactGeneration (org.finos.legend.sdlc.domain.model.project.configuration.ArtifactGeneration)3 MetamodelDependency (org.finos.legend.sdlc.domain.model.project.configuration.MetamodelDependency)3