From 07e86c05fe0c56a1ba173fa77e5a2ea2cf0d2352 Mon Sep 17 00:00:00 2001 From: mrudula-gs <89876341+mrudula-gs@users.noreply.github.com> Date: Mon, 21 Aug 2023 17:53:36 -0400 Subject: [PATCH] SDLC Server with File System Backend (#688) * SDLC Server with File System Backend * SDLC Server with File System Backend - Revision 2 * Update getFilesInCanonicalDirectories() * Remove unnecessary dependency * Update FileSystemEntityApi.java * SDLC Server with File System Backend - Revision 3 * SDLC Server with File System Backend - Revision 4 * Fix access and exception msg * Add docker setup --- legend-sdlc-server-fs/Dockerfile | 9 + legend-sdlc-server-fs/pom.xml | 272 +++++++ .../legend/sdlc/server/api/BaseFSApi.java | 145 ++++ .../api/backup/FileSystemBackupApi.java | 41 + .../server/api/build/FileSystemBuildApi.java | 50 ++ .../comparison/FileSystemComparisonApi.java | 54 ++ .../FileSystemConflictResolutionApi.java | 48 ++ .../entity/FileSystemApiWithFileAccess.java | 365 +++++++++ .../api/entity/FileSystemEntityApi.java | 740 ++++++++++++++++++ .../server/api/issue/FileSystemIssueApi.java | 54 ++ .../server/api/patch/FileSystemPatchApi.java | 56 ++ .../api/project/FileSystemProjectApi.java | 277 +++++++ .../FileSystemProjectConfigurationApi.java | 106 +++ .../api/review/FileSystemReviewApi.java | 121 +++ .../api/revision/FileSystemRevisionApi.java | 167 ++++ .../server/api/user/FileSystemUserApi.java | 61 ++ .../api/version/FileSystemVersionApi.java | 58 ++ .../api/workflow/FileSystemWorkflowApi.java | 42 + .../workflow/FileSystemWorkflowJobApi.java | 42 + .../api/workspace/FileSystemWorkspaceApi.java | 342 ++++++++ .../server/depot/FileSystemMetadataApi.java | 45 ++ .../FileSystemProjectConfiguration.java | 75 ++ .../model/revision/FileSystemRevision.java | 101 +++ .../domain/model/user/FileSystemUser.java | 42 + .../sdlc/server/exception/FSException.java | 31 + .../FileSystemAuthCheckResource.java | 47 ++ .../resources/FileSystemAuthResource.java | 66 ++ .../sdlc/server/startup/FSConfiguration.java | 39 + .../legend/sdlc/server/startup/FSModule.java | 546 +++++++++++++ .../server/startup/LegendSDLCServerFS.java | 128 +++ .../LegendSDLCServerFSConfiguration.java | 29 + .../main/resources/docker/config/config.json | 120 +++ .../src/test/resources/config.yml | 90 +++ pom.xml | 6 + 34 files changed, 4415 insertions(+) create mode 100644 legend-sdlc-server-fs/Dockerfile create mode 100644 legend-sdlc-server-fs/pom.xml create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/BaseFSApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/backup/FileSystemBackupApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/build/FileSystemBuildApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/comparison/FileSystemComparisonApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/conflictresolution/FileSystemConflictResolutionApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemApiWithFileAccess.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemEntityApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/issue/FileSystemIssueApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/patch/FileSystemPatchApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectConfigurationApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/review/FileSystemReviewApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/revision/FileSystemRevisionApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/user/FileSystemUserApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/version/FileSystemVersionApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowJobApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workspace/FileSystemWorkspaceApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/depot/FileSystemMetadataApi.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/project/configuration/FileSystemProjectConfiguration.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/revision/FileSystemRevision.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/user/FileSystemUser.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/exception/FSException.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthCheckResource.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthResource.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSConfiguration.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSModule.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFS.java create mode 100644 legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFSConfiguration.java create mode 100644 legend-sdlc-server-fs/src/main/resources/docker/config/config.json create mode 100644 legend-sdlc-server-fs/src/test/resources/config.yml diff --git a/legend-sdlc-server-fs/Dockerfile b/legend-sdlc-server-fs/Dockerfile new file mode 100644 index 0000000000..0a6e98e8c1 --- /dev/null +++ b/legend-sdlc-server-fs/Dockerfile @@ -0,0 +1,9 @@ +FROM eclipse-temurin:11.0.17_8-jdk-jammy +COPY target/legend-sdlc-server-fs-*-shaded.jar /app/bin/ +COPY src/main/resources/docker/config /config +CMD java -cp /app/bin/*.jar \ +-XX:+ExitOnOutOfMemoryError \ +-XX:MaxRAMPercentage=60 \ +-Xss4M \ +-Dfile.encoding=UTF8 \ +org.finos.legend.sdlc.server.startup.LegendSDLCServerFS server /config/config.json \ No newline at end of file diff --git a/legend-sdlc-server-fs/pom.xml b/legend-sdlc-server-fs/pom.xml new file mode 100644 index 0000000000..c24b4d547a --- /dev/null +++ b/legend-sdlc-server-fs/pom.xml @@ -0,0 +1,272 @@ + + + + + + org.finos.legend.sdlc + legend-sdlc + 0.140.1-SNAPSHOT + + 4.0.0 + + legend-sdlc-server-fs + Legend SDLC Server - FS + jar + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + + org.slf4j:jcl-over-slf4j:${slf4j.version} + org.slf4j:jul-to-slf4j:${slf4j.version} + ch.qos.logback:logback-access + ch.qos.logback:logback-classic + ch.qos.logback:logback-core + + + + + + + maven-shade-plugin + + + package + + shade + + + false + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + true + + + + org.finos.legend.sdlc.server.startup.LegendSDLCServerFS + + + + + + + + + + + + + org.finos.legend.sdlc + legend-sdlc-model + + + org.finos.legend.sdlc + legend-sdlc-server-shared + + + org.glassfish.hk2.external + * + + + + + org.finos.legend.sdlc + legend-sdlc-extensions-collection-entity-serializer + runtime + + + org.finos.legend.sdlc + legend-sdlc-server + + + jakarta.servlet + * + + + jakarta.ws.rs + * + + + org.glassfish.hk2.external + * + + + + + + + + org.finos.legend.engine + legend-engine-protocol-pure + + + org.finos.legend.engine + legend-engine-extensions-collection-generation + runtime + + + + + + org.eclipse.collections + eclipse-collections-api + + + org.eclipse.collections + eclipse-collections + + + org.eclipse.jgit + org.eclipse.jgit + 6.3.0.202209071007-r + + + + + com.fasterxml.jackson.core + jackson-annotations + + + + org.apache.httpcomponents + httpclient + + + + com.google.inject + guice + + + + io.dropwizard + dropwizard-core + + + io.dropwizard + dropwizard-lifecycle + + + + com.hubspot.dropwizard + dropwizard-guicier + + + + io.swagger + swagger-annotations + + + + javax.inject + javax.inject + + + + javax.servlet + javax.servlet-api + + + javax.ws.rs + javax.ws.rs-api + + + + org.slf4j + slf4j-api + + + + + junit + junit + test + + + + + + docker + + + + com.spotify + dockerfile-maven-plugin + false + + + deploy + + build + tag + push + + + + + ${project.version} + ${env.DOCKER_USERNAME} + ${env.DOCKER_PASSWORD} + registry.hub.docker.com/${env.DOCKER_USERNAME}/${project.artifactId} + + + + + + + + docker-snapshot + + + + com.spotify + dockerfile-maven-plugin + false + + + deploy + + build + tag + push + + + + + snapshot + ${env.DOCKER_USERNAME} + ${env.DOCKER_PASSWORD} + registry.hub.docker.com/${env.DOCKER_USERNAME}/${project.artifactId} + + + + + + + diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/BaseFSApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/BaseFSApi.java new file mode 100644 index 0000000000..6b74ad5a29 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/BaseFSApi.java @@ -0,0 +1,145 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api; + +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.finos.legend.sdlc.domain.model.revision.Revision; +import org.finos.legend.sdlc.domain.model.revision.RevisionAlias; +import org.finos.legend.sdlc.server.api.workspace.FileSystemWorkspaceApi; +import org.finos.legend.sdlc.server.domain.api.project.source.ProjectSourceSpecification; +import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification; +import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecificationVisitor; +import org.finos.legend.sdlc.server.domain.api.project.source.WorkspaceSourceSpecification; +import org.finos.legend.sdlc.server.exception.FSException; +import org.finos.legend.sdlc.server.project.ProjectFileAccessProvider; +import org.finos.legend.sdlc.server.startup.FSConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + +public class BaseFSApi +{ + private static final Logger LOGGER = LoggerFactory.getLogger(BaseFSApi.class); + private final String rootDirectory; + + protected BaseFSApi(FSConfiguration fsConfiguration) + { + this.rootDirectory = fsConfiguration.getRootDirectory(); + } + + protected String getRootDirectory() + { + return this.rootDirectory; + } + + public Repository retrieveRepo(String projectId) + { + try + { + String repoDirPath = rootDirectory + File.separator + projectId + File.separator + ".git"; + File repoDir = new File(repoDirPath); + if (repoDir.exists() && repoDir.isDirectory()) + { + return FileRepositoryBuilder.create(repoDir); + } + } + catch (Exception e) + { + throw FSException.getLegendSDLCServerException("Repository " + projectId + " can't be found ", e); + } + return null; + } + + public Ref getGitBranch(String projectId, String branchName) + { + String refBranchName = branchName; + try + { + Repository repository = retrieveRepo(projectId); + return repository.getRefDatabase().findRef(refBranchName); + } + catch (Exception e) + { + LOGGER.error("Error occurred during branch list operation for workspace {} in project {}", refBranchName, projectId, e); + throw FSException.getLegendSDLCServerException("Branch " + projectId + " does not exist", e); + } + } + + public String getRefBranchName(SourceSpecification sourceSpecification) + { + return sourceSpecification.visit(new SourceSpecificationVisitor() + { + public String visit(ProjectSourceSpecification sourceSpec) + { + return "master"; + } + + public String visit(WorkspaceSourceSpecification workspaceSourceSpecification) + { + return FileSystemWorkspaceApi.getWorkspaceBranchName(workspaceSourceSpecification.getWorkspaceSpecification()); + } + }); + } + + protected String resolveRevisionId(String revisionId, ProjectFileAccessProvider.RevisionAccessContext revisionAccessContext) + { + if (revisionId == null) + { + throw new IllegalArgumentException("Resolving revision alias does not work with null revisionId, null handling must be done before using this method"); + } + RevisionAlias revisionAlias = getRevisionAlias(revisionId); + switch (revisionAlias) + { + case BASE: + { + Revision revision = revisionAccessContext.getBaseRevision(); + return (revision == null) ? null : revision.getId(); + } + case HEAD: + { + Revision revision = revisionAccessContext.getCurrentRevision(); + return (revision == null) ? null : revision.getId(); + } + case REVISION_ID: + { + return revisionId; + } + default: + { + throw new IllegalArgumentException("Unknown revision alias type: " + revisionAlias); + } + } + } + + protected RevisionAlias getRevisionAlias(String revisionId) + { + if (revisionId == null) + { + return null; + } + if (RevisionAlias.BASE.getValue().equalsIgnoreCase(revisionId)) + { + return RevisionAlias.BASE; + } + if (RevisionAlias.HEAD.getValue().equalsIgnoreCase(revisionId) || RevisionAlias.CURRENT.getValue().equalsIgnoreCase(revisionId) || RevisionAlias.LATEST.getValue().equalsIgnoreCase(revisionId)) + { + return RevisionAlias.HEAD; + } + return RevisionAlias.REVISION_ID; + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/backup/FileSystemBackupApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/backup/FileSystemBackupApi.java new file mode 100644 index 0000000000..808ceabe73 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/backup/FileSystemBackupApi.java @@ -0,0 +1,41 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.backup; + +import org.finos.legend.sdlc.server.domain.api.backup.BackupApi; +import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceSpecification; +import org.finos.legend.sdlc.server.exception.FSException; + +import javax.inject.Inject; + +public class FileSystemBackupApi implements BackupApi +{ + @Inject + public FileSystemBackupApi() + { + } + + @Override + public void discardBackupWorkspace(String projectId, WorkspaceSpecification workspaceSpecification) + { + FSException.unavailableFeature(); + } + + @Override + public void recoverBackupWorkspace(String projectId, WorkspaceSpecification workspaceSpecification, boolean forceRecovery) + { + FSException.unavailableFeature(); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/build/FileSystemBuildApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/build/FileSystemBuildApi.java new file mode 100644 index 0000000000..2e7d029a35 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/build/FileSystemBuildApi.java @@ -0,0 +1,50 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.build; + +import org.finos.legend.sdlc.domain.model.project.workspace.WorkspaceType; +import org.finos.legend.sdlc.domain.model.version.VersionId; +import org.finos.legend.sdlc.server.exception.FSException; +import org.finos.legend.sdlc.server.domain.api.build.BuildAccessContext; +import org.finos.legend.sdlc.server.domain.api.build.BuildApi; +import org.finos.legend.sdlc.server.project.ProjectFileAccessProvider; + +import javax.inject.Inject; + +public class FileSystemBuildApi implements BuildApi +{ + @Inject + public FileSystemBuildApi() + { + } + + @Override + public BuildAccessContext getProjectBuildAccessContext(String projectId) + { + throw FSException.unavailableFeature(); + } + + @Override + public BuildAccessContext getWorkspaceBuildAccessContext(String projectId, String workspaceId, WorkspaceType workspaceType, ProjectFileAccessProvider.WorkspaceAccessType workspaceAccessType) + { + throw FSException.unavailableFeature(); + } + + @Override + public BuildAccessContext getVersionBuildAccessContext(String projectId, VersionId versionId) + { + throw FSException.unavailableFeature(); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/comparison/FileSystemComparisonApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/comparison/FileSystemComparisonApi.java new file mode 100644 index 0000000000..1611896666 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/comparison/FileSystemComparisonApi.java @@ -0,0 +1,54 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.comparison; + +import org.finos.legend.sdlc.domain.model.comparison.Comparison; +import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceSpecification; +import org.finos.legend.sdlc.server.exception.FSException; +import org.finos.legend.sdlc.server.domain.api.comparison.ComparisonApi; + +import javax.inject.Inject; + +public class FileSystemComparisonApi implements ComparisonApi +{ + @Inject + public FileSystemComparisonApi() + { + } + + @Override + public Comparison getWorkspaceCreationComparison(String projectId, WorkspaceSpecification workspaceSpecification) + { + throw FSException.unavailableFeature(); + } + + @Override + public Comparison getWorkspaceSourceComparison(String projectId, WorkspaceSpecification workspaceSpecification) + { + throw FSException.unavailableFeature(); + } + + @Override + public Comparison getReviewComparison(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } + + @Override + public Comparison getReviewWorkspaceCreationComparison(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } +} \ No newline at end of file diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/conflictresolution/FileSystemConflictResolutionApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/conflictresolution/FileSystemConflictResolutionApi.java new file mode 100644 index 0000000000..ed1fcba732 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/conflictresolution/FileSystemConflictResolutionApi.java @@ -0,0 +1,48 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.conflictresolution; + +import org.finos.legend.sdlc.server.application.entity.PerformChangesCommand; +import org.finos.legend.sdlc.server.domain.api.conflictResolution.ConflictResolutionApi; +import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceSpecification; +import org.finos.legend.sdlc.server.exception.FSException; + +import javax.inject.Inject; + +public class FileSystemConflictResolutionApi implements ConflictResolutionApi +{ + @Inject + public FileSystemConflictResolutionApi() + { + } + + @Override + public void discardConflictResolution(String projectId, WorkspaceSpecification workspaceSpecification) + { + throw FSException.unavailableFeature(); + } + + @Override + public void discardChangesConflictResolution(String projectId, WorkspaceSpecification workspaceSpecification) + { + throw FSException.unavailableFeature(); + } + + @Override + public void acceptConflictResolution(String projectId, WorkspaceSpecification workspaceSpecification, PerformChangesCommand command) + { + throw FSException.unavailableFeature(); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemApiWithFileAccess.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemApiWithFileAccess.java new file mode 100644 index 0000000000..f683154015 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemApiWithFileAccess.java @@ -0,0 +1,365 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.entity; + +import org.eclipse.collections.api.list.MutableList; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.*; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.finos.legend.sdlc.domain.model.revision.Revision; +import org.finos.legend.sdlc.server.api.BaseFSApi; +import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification; +import org.finos.legend.sdlc.server.domain.model.revision.FileSystemRevision; +import org.finos.legend.sdlc.server.error.LegendSDLCServerException; +import org.finos.legend.sdlc.server.exception.FSException; +import org.finos.legend.sdlc.server.project.AbstractFileAccessContext; +import org.finos.legend.sdlc.server.project.ProjectFileAccessProvider; +import org.finos.legend.sdlc.server.project.ProjectFileOperation; +import org.finos.legend.sdlc.server.project.ProjectFiles; +import org.finos.legend.sdlc.server.startup.FSConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.core.Response; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public abstract class FileSystemApiWithFileAccess extends BaseFSApi +{ + private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemApiWithFileAccess.class); + + protected FileSystemApiWithFileAccess(FSConfiguration fsConfiguration) + { + super(fsConfiguration); + } + + private class FileSystemFileAccessContext extends AbstractFileAccessContext + { + protected final String projectId; + private final SourceSpecification sourceSpecification; + private final String revisionId; + + public FileSystemFileAccessContext(String projectId, SourceSpecification sourceSpecification, String revisionId) + { + this.projectId = projectId; + this.sourceSpecification = sourceSpecification; + this.revisionId = revisionId; + } + + @Override + protected Stream getFilesInCanonicalDirectories(MutableList directories) + { + List files = new ArrayList<>(); + Repository repo = retrieveRepo(this.projectId); + try + { + ObjectId commitId = ObjectId.fromString(revisionId); + RevCommit commit = repo.parseCommit(commitId); + RevTree tree = commit.getTree(); + TreeWalk treeWalk = new TreeWalk(repo); + treeWalk.addTree(tree); + treeWalk.setRecursive(true); + + while (treeWalk.next()) + { + String path = treeWalk.getPathString(); + if (directories.anySatisfy(d -> path.startsWith(d))) + { + files.add(getFile(path)); + } + } + } + catch (Exception e) + { + throw FSException.getLegendSDLCServerException("Error getting files in directories for " + projectId, e); + } + return files.stream(); + } + + @Override + public ProjectFileAccessProvider.ProjectFile getFile(String path) + { + String branchName = getRefBranchName(sourceSpecification); + try + { + Repository repo = retrieveRepo(this.projectId); + RevWalk revWalk = new RevWalk(repo); + RevCommit branchCommit = revWalk.parseCommit(repo.resolve(branchName)); + RevTree branchTree = branchCommit.getTree(); + path = path.startsWith("/") ? path.substring(1) : path; + try (TreeWalk treeWalk = TreeWalk.forPath(repo, path, branchTree)) + { + if (treeWalk != null) + { + ObjectId objectId = treeWalk.getObjectId(0); + ObjectReader objectReader = repo.newObjectReader(); + byte[] fileBytes = objectReader.open(objectId).getBytes(); + return ProjectFiles.newByteArrayProjectFile(path, fileBytes); + } + } + } + catch (Exception e) + { + throw FSException.getLegendSDLCServerException("Error getting file " + path, e); + } + return null; + } + + @Override + public boolean fileExists(String path) + { + String workspaceId = getRefBranchName(sourceSpecification); + try + { + Repository repo = retrieveRepo(this.projectId); + RevWalk revWalk = new RevWalk(repo); + RevCommit branchCommit = revWalk.parseCommit(repo.resolve(workspaceId)); + RevTree branchTree = branchCommit.getTree(); + path = path.startsWith("/") ? path.substring(1) : path; + try (TreeWalk treeWalk = TreeWalk.forPath(repo, path, branchTree)) + { + return treeWalk != null; + } + } + catch (Exception e) + { + throw FSException.getLegendSDLCServerException("Error occurred while parsing Git commit for workspace " + workspaceId, e); + } + } + } + + private class FileSystemRevisionAccessContext implements ProjectFileAccessProvider.RevisionAccessContext + { + private final String projectId; + private final SourceSpecification sourceSpecification; + + public FileSystemRevisionAccessContext(String projectId, SourceSpecification sourceSpecification) + { + this.projectId = projectId; + this.sourceSpecification = Objects.requireNonNull(sourceSpecification, "source specification may not be null"); + } + + @Override + public Revision getCurrentRevision() + { + String branchName = getRefBranchName(sourceSpecification); + Repository repo = retrieveRepo(this.projectId); + try + { + ObjectId commitId = repo.resolve(branchName); + RevWalk revWalk = new RevWalk(repo); + RevCommit commit = revWalk.parseCommit(commitId); + revWalk.dispose(); + return getRevisionInfo(commit); + } + catch (Exception e) + { + throw FSException.getLegendSDLCServerException("Failed to get current revision for branch " + branchName + " in project " + this.projectId, e); + } + } + + @Override + public Revision getBaseRevision() + { + String branchName = getRefBranchName(sourceSpecification); + Repository repo = retrieveRepo(this.projectId); + try + { + Ref branchRef = repo.exactRef(Constants.R_HEADS + branchName); + ObjectId branchCommitId = branchRef.getObjectId(); + ObjectId masterCommitId = repo.resolve(Constants.R_HEADS + "master"); + RevWalk revWalk = new RevWalk(repo); + RevCommit branchCommit = revWalk.parseCommit(branchCommitId); + if (masterCommitId.equals(branchCommitId)) // If branch is master, return the first commit + { + RevCommit baseCommit = revWalk.parseCommit(masterCommitId); + revWalk.dispose(); + return getRevisionInfo(baseCommit); + } + else + { + revWalk.markStart(branchCommit); + revWalk.markStart(revWalk.parseCommit(masterCommitId)); + RevCommit baseCommit = revWalk.next(); // Finds the common commit which is merge base + revWalk.dispose(); + return getRevisionInfo(baseCommit); + } + } + catch (Exception e) + { + throw FSException.getLegendSDLCServerException("Failed to get base revision for branch " + branchName + " in project " + this.projectId, e); + } + } + + @Override + public Revision getRevision(String revisionId) + { + LegendSDLCServerException.validateNonNull(revisionId, "revisionId may not be null"); + String resolvedRevisionId = resolveRevisionId(revisionId, this); + if (resolvedRevisionId == null) + { + throw new LegendSDLCServerException("Failed to resolve revision " + revisionId + " of project " + this.projectId, Response.Status.NOT_FOUND); + } + String branchName = getRefBranchName(sourceSpecification); + Repository repo = retrieveRepo(this.projectId); + try + { + ObjectId commitId = ObjectId.fromString(resolvedRevisionId); + RevWalk revWalk = new RevWalk(repo); + RevCommit commit = revWalk.parseCommit(commitId); + revWalk.dispose(); + return getRevisionInfo(commit); + } + catch (Exception e) + { + throw FSException.getLegendSDLCServerException("Failed to get " + resolvedRevisionId + " revision for branch " + branchName + " in project " + this.projectId, e); + } + } + + @Override + public Stream getAllRevisions(Predicate predicate, Instant since, Instant until, Integer limit) + { + throw new UnsupportedOperationException("Not implemented"); + } + + private Revision getRevisionInfo(RevCommit commit) + { + String revisionID = commit.getId().getName(); + String authorName = commit.getAuthorIdent().getName(); + Instant authoredTimeStamp = commit.getAuthorIdent().getWhenAsInstant(); + String committerName = commit.getCommitterIdent().getName(); + Instant committedTimeStamp = commit.getCommitterIdent().getWhenAsInstant(); + String message = commit.getFullMessage(); + return new FileSystemRevision(revisionID, authorName, authoredTimeStamp, committerName, committedTimeStamp, message); + } + } + + public class FileSystemFileFileModificationContext implements ProjectFileAccessProvider.FileModificationContext + { + private final String projectId; + private final String revisionId; + private final SourceSpecification sourceSpecification; + + public FileSystemFileFileModificationContext(String projectId, SourceSpecification sourceSpecification, String revisionId) + { + this.projectId = projectId; + this.revisionId = revisionId; + this.sourceSpecification = Objects.requireNonNull(sourceSpecification, "source specification may not be null"); + } + + @Override + public Revision submit(String message, List operations) + { + String branchName = getRefBranchName(sourceSpecification); + try + { + Repository repo = retrieveRepo(this.projectId); + String referenceRevisionId = this.revisionId; + Ref branch = getGitBranch(projectId, branchName); + if (referenceRevisionId != null) + { + String targetBranchRevision = FileSystemRevision.getFileSystemRevision(this.projectId, branchName, repo, branch).getId(); + if (!referenceRevisionId.equals(targetBranchRevision)) + { + String msg = "Expected " + sourceSpecification + " to be at revision " + referenceRevisionId + "; instead it was at revision " + targetBranchRevision; + LOGGER.info(msg); + throw new LegendSDLCServerException(msg, Response.Status.CONFLICT); + } + } + Git git = new Git(repo); + git.checkout().setName(branchName).call(); + for (ProjectFileOperation fileOperation : operations) + { + if (fileOperation instanceof ProjectFileOperation.AddFile) + { + File newFile = new File(repo.getDirectory().getParent(), fileOperation.getPath()); + Files.createDirectories(newFile.toPath().getParent()); + Files.write(newFile.toPath(), ((ProjectFileOperation.AddFile) fileOperation).getContent(), StandardOpenOption.CREATE_NEW); + git.add().addFilepattern(".").call(); + } + else if (fileOperation instanceof ProjectFileOperation.ModifyFile) + { + File file = new File(repo.getDirectory().getParent(), fileOperation.getPath()); + if (file.exists()) + { + Files.write(file.toPath(), ((ProjectFileOperation.ModifyFile) fileOperation).getNewContent()); + } + else + { + throw new LegendSDLCServerException("File " + file + " does not exist"); + } + git.add().addFilepattern(".").call(); + } + else if (fileOperation instanceof ProjectFileOperation.DeleteFile) + { + File fileToRemove = new File(repo.getWorkTree(), fileOperation.getPath().substring(1)); + if (!fileToRemove.exists()) + { + throw new LegendSDLCServerException("File " + fileToRemove + " does not exist"); + } + fileToRemove.delete(); + git.rm().addFilepattern(fileOperation.getPath().substring(1)).call(); + } + else + { + throw new LegendSDLCServerException(fileOperation + "operation is not yet supported"); + } + } + git.commit().setMessage(message).call(); + repo.close(); + return FileSystemRevision.getFileSystemRevision(projectId, branchName, repo, branch); + } + catch (Exception e) + { + throw FSException.getLegendSDLCServerException("Error occurred while committing changes to " + branchName + " of project " + projectId, e); + } + } + } + + protected ProjectFileAccessProvider getProjectFileAccessProvider() + { + return new ProjectFileAccessProvider() + { + @Override + public FileAccessContext getFileAccessContext(String projectId, SourceSpecification sourceSpecification, String revisionId) + { + return new FileSystemFileAccessContext(projectId, sourceSpecification, revisionId); + } + + @Override + public RevisionAccessContext getRevisionAccessContext(String projectId, SourceSpecification sourceSpecification, Iterable paths) + { + return new FileSystemRevisionAccessContext(projectId, sourceSpecification); + } + + @Override + public FileModificationContext getFileModificationContext(String projectId, SourceSpecification sourceSpecification, String revisionId) + { + return new FileSystemFileFileModificationContext(projectId, sourceSpecification, revisionId); + } + }; + } + +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemEntityApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemEntityApi.java new file mode 100644 index 0000000000..be46bb00f2 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemEntityApi.java @@ -0,0 +1,740 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.entity; + +import org.eclipse.collections.api.factory.Lists; +import org.eclipse.collections.api.factory.Maps; +import org.eclipse.collections.api.list.MutableList; +import org.eclipse.collections.api.map.MutableMap; +import org.eclipse.collections.impl.utility.ListIterate; +import org.eclipse.jgit.lib.*; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.finos.legend.sdlc.domain.model.entity.Entity; +import org.finos.legend.sdlc.domain.model.entity.change.EntityChange; +import org.finos.legend.sdlc.domain.model.entity.change.EntityChangeType; +import org.finos.legend.sdlc.domain.model.revision.Revision; +import org.finos.legend.sdlc.server.domain.api.entity.EntityAccessContext; +import org.finos.legend.sdlc.server.domain.api.entity.EntityApi; +import org.finos.legend.sdlc.server.domain.api.entity.EntityModificationContext; +import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification; +import org.finos.legend.sdlc.server.domain.api.project.source.WorkspaceSourceSpecification; +import org.finos.legend.sdlc.server.error.LegendSDLCServerException; +import org.finos.legend.sdlc.server.exception.FSException; +import org.finos.legend.sdlc.server.project.ProjectFileAccessProvider; +import org.finos.legend.sdlc.server.project.ProjectFileOperation; +import org.finos.legend.sdlc.server.project.ProjectFiles; +import org.finos.legend.sdlc.server.project.ProjectStructure; +import org.finos.legend.sdlc.server.project.CachingFileAccessContext; +import org.finos.legend.sdlc.server.startup.FSConfiguration; +import org.finos.legend.sdlc.server.tools.StringTools; +import org.finos.legend.sdlc.tools.entity.EntityPaths; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.ws.rs.core.Response; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class FileSystemEntityApi extends FileSystemApiWithFileAccess implements EntityApi +{ + private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemEntityApi.class); + + @Inject + public FileSystemEntityApi(FSConfiguration fsConfiguration) + { + super(fsConfiguration); + } + + @Override + public EntityAccessContext getEntityAccessContext(String projectId, SourceSpecification sourceSpecification, String revisionId) + { + String branchName = getRefBranchName(sourceSpecification); + Repository repo = retrieveRepo(projectId); + return new FileSystemEntityAccessContext(branchName, repo) + { + @Override + protected ProjectFileAccessProvider.FileAccessContext getFileAccessContext(ProjectFileAccessProvider projectFileAccessProvider) + { + return projectFileAccessProvider.getFileAccessContext(projectId, sourceSpecification, null); + } + }; + } + + @Override + public EntityAccessContext getReviewFromEntityAccessContext(String projectId, String reviewId) + { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public EntityAccessContext getReviewToEntityAccessContext(String projectId, String reviewId) + { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public EntityModificationContext getEntityModificationContext(String projectId, WorkspaceSourceSpecification sourceSpecification) + { + return new FileSystemEntityModificationContext(projectId, sourceSpecification); + } + + + public abstract class FileSystemEntityAccessContext implements EntityAccessContext + { + private final String branchName; + private final Repository repo; + + protected FileSystemEntityAccessContext(String branchName, Repository repo) + { + this.branchName = branchName; + this.repo = repo; + } + + @Override + public Entity getEntity(String path) + { + ProjectFileAccessProvider.FileAccessContext fileAccessContext = getFileAccessContext(getProjectFileAccessProvider()); + ProjectStructure projectStructure = ProjectStructure.getProjectStructure(fileAccessContext); + for (ProjectStructure.EntitySourceDirectory sourceDirectory : projectStructure.getEntitySourceDirectories()) + { + String filePath = sourceDirectory.entityPathToFilePath(path); + ProjectFileAccessProvider.ProjectFile file = fileAccessContext.getFile(filePath); + if (file != null) + { + try + { + Entity localEntity = sourceDirectory.deserialize(file); + if (!Objects.equals(localEntity.getPath(), path)) + { + throw new RuntimeException("Expected entity path " + path + ", found " + localEntity.getPath()); + } + return localEntity; + } + catch (Exception e) + { + StringBuilder builder = new StringBuilder("Error deserializing entity \"").append(path).append("\" from file \"").append(filePath).append('"'); + StringTools.appendThrowableMessageIfPresent(builder, e); + throw new LegendSDLCServerException(builder.toString(), e); + } + } + } + throw new LegendSDLCServerException("Unknown entity " + path, Response.Status.NOT_FOUND); + } + + @Override + public List getEntities(Predicate entityPathPredicate, Predicate classifierPathPredicate, Predicate> entityContentPredicate, boolean excludeInvalid) + { + try (Stream stream = getEntityProjectFiles(getFileAccessContext(getProjectFileAccessProvider()), entityPathPredicate, classifierPathPredicate, entityContentPredicate, excludeInvalid, branchName, repo)) + { + return stream.map(excludeInvalid ? epf -> + { + try + { + return epf.getEntity(); + } + catch (Exception ignore) + { + return null; + } + } : EntityProjectFile::getEntity).filter(Objects::nonNull).collect(Collectors.toList()); + } + } + + @Override + public List getEntityPaths(Predicate entityPathPredicate, Predicate classifierPathPredicate, Predicate> entityContentPredicate) + { + try (Stream stream = getEntityProjectFiles(getFileAccessContext(getProjectFileAccessProvider()), entityPathPredicate, classifierPathPredicate, entityContentPredicate)) + { + return stream.map(EntityProjectFile::getEntityPath).collect(Collectors.toList()); + } + } + + protected abstract ProjectFileAccessProvider.FileAccessContext getFileAccessContext(ProjectFileAccessProvider projectFileAccessProvider); + } + + public class FileSystemEntityModificationContext implements EntityModificationContext + { + private final String projectId; + private final WorkspaceSourceSpecification sourceSpecification; + + private FileSystemEntityModificationContext(String projectId, WorkspaceSourceSpecification sourceSpecification) + { + this.projectId = projectId; + this.sourceSpecification = Objects.requireNonNull(sourceSpecification, "source specification may not be null"); + } + + @Override + public Revision updateEntities(Iterable entities, boolean replace, String message) + { + LegendSDLCServerException.validateNonNull(entities, "entities may not be null"); + LegendSDLCServerException.validateNonNull(message, "message may not be null"); + return FileSystemEntityApi.this.updateEntities(this.projectId, this.sourceSpecification, entities, replace, message); + } + + @Override + public Revision performChanges(List changes, String revisionId, String message) + { + LegendSDLCServerException.validateNonNull(changes, "changes may not be null"); + LegendSDLCServerException.validateNonNull(message, "message may not be null"); + validateEntityChanges(changes); + return FileSystemEntityApi.this.performChanges(this.projectId, this.sourceSpecification, revisionId, message, changes); + } + } + + private static void validateEntityChanges(List entityChanges) + { + StringBuilder builder = new StringBuilder(); + List errorMessages = Lists.mutable.ofInitialCapacity(4); + int i = 0; + for (EntityChange change : entityChanges) + { + collectErrorsForEntityChange(change, errorMessages); + if (!errorMessages.isEmpty()) + { + if (builder.length() == 0) + { + builder.append("There are entity change errors:"); + } + builder.append("\n\tEntity change #").append(i + 1).append(" (").append(change).append("):"); + errorMessages.forEach(m -> builder.append("\n\t\t").append(m)); + errorMessages.clear(); + } + i++; + } + if (builder.length() > 0) + { + throw new LegendSDLCServerException(builder.toString(), Response.Status.BAD_REQUEST); + } + } + + private static void collectErrorsForEntityChange(EntityChange entityChange, Collection errorMessages) + { + if (entityChange == null) + { + errorMessages.add("Invalid entity change: null"); + return; + } + + EntityChangeType type = entityChange.getType(); + if (type == null) + { + errorMessages.add("Missing entity change type"); + } + + String path = entityChange.getEntityPath(); + String classifierPath = entityChange.getClassifierPath(); + Map content = entityChange.getContent(); + String newPath = entityChange.getNewEntityPath(); + + if (path == null) + { + errorMessages.add("Missing entity path"); + } + else if (!EntityPaths.isValidEntityPath(path)) + { + errorMessages.add("Invalid entity path: " + path); + } + else if (content != null) + { + Object pkg = content.get("package"); + Object name = content.get("name"); + if (!(pkg instanceof String) || !(name instanceof String) || !path.equals(pkg + EntityPaths.PACKAGE_SEPARATOR + name)) + { + StringBuilder builder = new StringBuilder("Mismatch between entity path (\"").append(path).append("\") and package ("); + if (pkg instanceof String) + { + builder.append('"').append(pkg).append('"'); + } + else + { + builder.append(pkg); + } + builder.append(") and name ("); + if (name instanceof String) + { + builder.append('"').append(name).append('"'); + } + else + { + builder.append(name); + } + builder.append(") properties"); + errorMessages.add(builder.toString()); + } + } + if (type != null) + { + switch (type) + { + case CREATE: + case MODIFY: + { + if (classifierPath == null) + { + errorMessages.add("Missing classifier path"); + } + else if (!EntityPaths.isValidClassifierPath(classifierPath)) + { + errorMessages.add("Invalid classifier path: " + classifierPath); + } + if (content == null) + { + errorMessages.add("Missing content"); + } + if (newPath != null) + { + errorMessages.add("Unexpected new entity path: " + newPath); + } + break; + } + case RENAME: + { + if (classifierPath != null) + { + errorMessages.add("Unexpected classifier path: " + classifierPath); + } + if (content != null) + { + errorMessages.add("Unexpected content"); + } + if (newPath == null) + { + errorMessages.add("Missing new entity path"); + } + else if (!EntityPaths.isValidEntityPath(newPath)) + { + errorMessages.add("Invalid new entity path: " + newPath); + } + break; + } + case DELETE: + { + if (classifierPath != null) + { + errorMessages.add("Unexpected classifier path: " + classifierPath); + } + if (content != null) + { + errorMessages.add("Unexpected content"); + } + if (newPath != null) + { + errorMessages.add("Unexpected new entity path: " + newPath); + } + break; + } + default: + { + errorMessages.add("Unexpected entity change type: " + type); + } + } + } + } + + public Revision updateEntities(String projectId, WorkspaceSourceSpecification sourceSpecification, Iterable newEntities, boolean replace, String message) + { + MutableMap newEntityDefinitions = Maps.mutable.empty(); + MutableList errorMessages = Lists.mutable.empty(); + newEntities.forEach(entity -> + { + if (entity == null) + { + errorMessages.add("Invalid entity: null"); + return; + } + + String path = entity.getPath(); + if (!EntityPaths.isValidEntityPath(path)) + { + errorMessages.add("Invalid entity path: \"" + path + "\""); + return; + } + + String classifierPath = entity.getClassifierPath(); + if (!EntityPaths.isValidClassifierPath(classifierPath)) + { + errorMessages.add("Entity: " + path + "; error: invalid classifier path \"" + classifierPath + "\""); + } + + Map content = entity.getContent(); + if (content == null) + { + errorMessages.add("Entity: " + path + "; error: missing content"); + } + else if (path != null) + { + Object pkg = content.get("package"); + Object name = content.get("name"); + if (!(pkg instanceof String) || !(name instanceof String) || !path.equals(pkg + EntityPaths.PACKAGE_SEPARATOR + name)) + { + StringBuilder builder = new StringBuilder("Entity: ").append(path).append("; mismatch between entity path and package ("); + if (pkg instanceof String) + { + builder.append('"').append(pkg).append('"'); + } + else + { + builder.append(pkg); + } + builder.append(") and name ("); + if (name instanceof String) + { + builder.append('"').append(name).append('"'); + } + else + { + builder.append(name); + } + builder.append(") properties"); + errorMessages.add(builder.toString()); + } + } + + Entity oldDefinition = newEntityDefinitions.put(path, entity); + if (oldDefinition != null) + { + errorMessages.add("Entity: " + path + "; error: multiple definitions"); + } + }); + if (errorMessages.notEmpty()) + { + throw new LegendSDLCServerException((errorMessages.size() == 1) ? errorMessages.get(0) : "There are errors with entity definitions:\n\t" + String.join("\n\t", errorMessages), Response.Status.BAD_REQUEST); + } + + ProjectFileAccessProvider fileProvider = getProjectFileAccessProvider(); + Revision currentWorkspaceRevision = fileProvider.getRevisionAccessContext(projectId, sourceSpecification, null).getCurrentRevision(); + if (currentWorkspaceRevision == null) + { + throw new LegendSDLCServerException("Could not find current revision for " + sourceSpecification + ": it may be corrupt"); + } + String revisionId = currentWorkspaceRevision.getId(); + LOGGER.debug("Using revision {} for reference in entity update in {} in project {}", revisionId, sourceSpecification, projectId); + List entityChanges = Lists.mutable.ofInitialCapacity(newEntityDefinitions.size()); + if (newEntityDefinitions.isEmpty()) + { + if (replace) + { + try (Stream stream = getEntityProjectFiles(fileProvider.getFileAccessContext(projectId, sourceSpecification, revisionId))) + { + stream.map(EntityProjectFile::getEntityPath).map(EntityChange::newDeleteEntity).forEach(entityChanges::add); + } + } + } + else + { + try (Stream stream = getEntityProjectFiles(fileProvider.getFileAccessContext(projectId, sourceSpecification, revisionId))) + { + stream.forEach(epf -> + { + String path = epf.getEntityPath(); + Entity newDefinition = newEntityDefinitions.remove(path); + if (newDefinition == null) + { + if (replace) + { + entityChanges.add(EntityChange.newDeleteEntity(path)); + } + } + else + { + Entity entity = epf.getEntity(); + String newClassifierPath = newDefinition.getClassifierPath(); + Map newContent = newDefinition.getContent(); + if (!newClassifierPath.equals(entity.getClassifierPath()) || !newContent.equals(entity.getContent())) + { + entityChanges.add(EntityChange.newModifyEntity(path, newClassifierPath, newContent)); + } + } + }); + } + newEntityDefinitions.forEachValue(definition -> entityChanges.add(EntityChange.newCreateEntity(definition.getPath(), definition.getClassifierPath(), definition.getContent()))); + } + + return performChanges(projectId, sourceSpecification, revisionId, message, entityChanges); + } + + private Stream getEntityProjectFiles(ProjectFileAccessProvider.FileAccessContext accessContext, Predicate entityPathPredicate, Predicate classifierPathPredicate, Predicate> contentPredicate) + { + return getEntityProjectFiles(accessContext, entityPathPredicate, classifierPathPredicate, contentPredicate, false, null, null); + } + + private Stream getEntityProjectFiles(ProjectFileAccessProvider.FileAccessContext accessContext, Predicate entityPathPredicate, Predicate classifierPathPredicate, Predicate> contentPredicate, boolean excludeInvalid, String branchName, Repository repo) + { + Stream stream = (repo != null && branchName != null) ? getEntityProjectFiles(branchName, repo, accessContext) : getEntityProjectFiles(accessContext); + if (entityPathPredicate != null) + { + stream = stream.filter(epf -> entityPathPredicate.test(epf.getEntityPath())); + } + if (classifierPathPredicate != null) + { + stream = stream.filter(excludeInvalid ? epf -> + { + Entity entity; + try + { + entity = epf.getEntity(); + } + catch (Exception ignore) + { + return false; + } + return classifierPathPredicate.test(entity.getClassifierPath()); + } : epf -> classifierPathPredicate.test(epf.getEntity().getClassifierPath())); + } + if (contentPredicate != null) + { + stream = stream.filter(excludeInvalid ? epf -> + { + Entity entity; + try + { + entity = epf.getEntity(); + } + catch (Exception ignore) + { + return false; + } + return contentPredicate.test(entity.getContent()); + } : epf -> contentPredicate.test(epf.getEntity().getContent())); + } + return stream; + } + + private Stream getEntityProjectFiles(String branchName, Repository repo, ProjectFileAccessProvider.FileAccessContext fileAccessContext) + { + ProjectStructure projectStructure = ProjectStructure.getProjectStructure(fileAccessContext); + List sourceDirectories = projectStructure.getEntitySourceDirectories(); + return sourceDirectories.stream().flatMap(sd -> getSourceDirectoryProjectFiles(sd, branchName, repo, projectStructure.getProjectConfiguration().getProjectId())); + } + + private Stream getSourceDirectoryProjectFiles(ProjectStructure.EntitySourceDirectory sourceDirectory, String workspaceId, Repository repo, String projectID) + { + List files = new ArrayList<>(); + try + { + ObjectId headCommitId = repo.findRef(workspaceId).getObjectId(); + TreeWalk treeWalk = new TreeWalk(repo); + treeWalk.addTree(repo.parseCommit(headCommitId).getTree()); + treeWalk.setRecursive(true); + while (treeWalk.next()) + { + File file = new File(repo.getWorkTree(), treeWalk.getPathString()); + ObjectId entityId = treeWalk.getObjectId(0); + ObjectLoader loader = repo.open(entityId); + byte[] entityContentBytes = loader.getBytes(); + String entityContent = new String(entityContentBytes, StandardCharsets.UTF_8); + ProjectFileAccessProvider.ProjectFile projectFile = ProjectFiles.newStringProjectFile(file.getCanonicalPath(), entityContent); + if (sourceDirectory.isPossiblyEntityFilePath(getEntityPath(file, projectID))) + { + files.add(new EntityProjectFile(sourceDirectory, projectFile, projectID, getRootDirectory())); + } + } + } + catch (Exception e) + { + LOGGER.error("Error occurred while parsing Git commit for workspace {}", workspaceId, e); + throw FSException.getLegendSDLCServerException("Failed to get project files for workspace " + workspaceId + " of project " + projectID, e); + } + return files.stream(); + } + + private String getEntityPath(File file, String projectID) + { + Path filePath = Paths.get(file.getPath()); + Path pathToProject = Paths.get(getRootDirectory() + "/" + projectID); + if (!filePath.startsWith(pathToProject)) + { + throw new LegendSDLCServerException("Paths " + filePath + " and " + pathToProject + " are not related"); + } + Path relativePath = filePath.subpath(pathToProject.getNameCount(), filePath.getNameCount()); + return "/" + relativePath; + } + + private Stream getEntityProjectFiles(ProjectFileAccessProvider.FileAccessContext accessContext) + { + ProjectStructure projectStructure = ProjectStructure.getProjectStructure(accessContext); + List sourceDirectories = projectStructure.getEntitySourceDirectories(); + ProjectFileAccessProvider.FileAccessContext cachingAccessContext = (sourceDirectories.size() > 1) ? CachingFileAccessContext.wrap(accessContext) : accessContext; + return sourceDirectories.stream().flatMap(sd -> getSourceDirectoryProjectFiles(cachingAccessContext, sd, projectStructure.getProjectConfiguration().getProjectId())); + } + + private Stream getSourceDirectoryProjectFiles(ProjectFileAccessProvider.FileAccessContext accessContext, ProjectStructure.EntitySourceDirectory sourceDirectory, String projectID) + { + return accessContext.getFilesInDirectory(sourceDirectory.getDirectory()) + .filter(f -> sourceDirectory.isPossiblyEntityFilePath(f.getPath())) + .map(f -> new EntityProjectFile(sourceDirectory, f, projectID, getRootDirectory())); + } + + private Revision performChanges(String projectId, WorkspaceSourceSpecification sourceSpecification, String referenceRevisionId, String message, List changes) + { + int changeCount = changes.size(); + if (changeCount == 0) + { + LOGGER.debug("No changes for {} in project {}", sourceSpecification, projectId); + return null; + } + LOGGER.debug("Committing {} changes to {} in project {}: {}", changeCount, sourceSpecification, projectId, message); + ProjectFileAccessProvider.FileAccessContext fileAccessContext = getProjectFileAccessProvider().getFileAccessContext(projectId, sourceSpecification, referenceRevisionId); + ProjectStructure projectStructure = ProjectStructure.getProjectStructure(fileAccessContext); + MutableList fileOperations = ListIterate.collect(changes, c -> entityChangeToFileOperation(c, projectStructure, fileAccessContext)); + fileOperations.removeIf(Objects::isNull); + if (fileOperations.isEmpty()) + { + LOGGER.debug("No changes for {} in project {}", sourceSpecification, projectId); + return null; + } + return getProjectFileAccessProvider().getFileModificationContext(projectId, sourceSpecification, referenceRevisionId).submit(message, fileOperations); + } + + private ProjectFileOperation entityChangeToFileOperation(EntityChange change, ProjectStructure projectStructure, ProjectFileAccessProvider.FileAccessContext fileAccessContext) + { + switch (change.getType()) + { + case CREATE: + { + String entityPath = change.getEntityPath(); + + // check if a file already exists for this entity + if (projectStructure.findEntityFile(entityPath, fileAccessContext) != null) + { + throw new LegendSDLCServerException("Unable to handle operation " + change + ": entity \"" + entityPath + "\" already exists"); + } + + Entity entity = Entity.newEntity(entityPath, change.getClassifierPath(), change.getContent()); + ProjectStructure.EntitySourceDirectory sourceDirectory = projectStructure.findSourceDirectoryForEntity(entity); + if (sourceDirectory == null) + { + throw new LegendSDLCServerException("Unable to handle operation " + change + ": cannot serialize entity \"" + entityPath + "\""); + } + return ProjectFileOperation.addFile(sourceDirectory.entityPathToFilePath(change.getEntityPath()), sourceDirectory.serializeToBytes(entity)); + } + case DELETE: + { + String entityPath = change.getEntityPath(); + String filePath = projectStructure.findEntityFile(entityPath, fileAccessContext); + if (filePath == null) + { + throw new LegendSDLCServerException("Unable to handle operation " + change + ": could not find entity \"" + entityPath + "\""); + } + return ProjectFileOperation.deleteFile(filePath); + } + case MODIFY: + { + String entityPath = change.getEntityPath(); + Entity entity = Entity.newEntity(entityPath, change.getClassifierPath(), change.getContent()); + + // find current file + String currentFilePath = projectStructure.findEntityFile(entityPath, fileAccessContext); + if (currentFilePath == null) + { + throw new LegendSDLCServerException("Unable to handle operation " + change + ": could not find entity \"" + entityPath + "\""); + } + + ProjectStructure.EntitySourceDirectory newSourceDirectory = projectStructure.findSourceDirectoryForEntity(entity); + if (newSourceDirectory == null) + { + throw new LegendSDLCServerException("Unable to handle operation " + change + ": cannot serialize entity \"" + entityPath + "\""); + } + + String newFilePath = newSourceDirectory.entityPathToFilePath(entityPath); + byte[] serialized = newSourceDirectory.serializeToBytes(entity); + + if (!currentFilePath.equals(newFilePath)) + { + return ProjectFileOperation.moveFile(currentFilePath, newFilePath, serialized); + } + if (!Arrays.equals(serialized, fileAccessContext.getFile(currentFilePath).getContentAsBytes())) + { + return ProjectFileOperation.modifyFile(currentFilePath, serialized); + } + return null; + } + case RENAME: + { + String currentEntityPath = change.getEntityPath(); + for (ProjectStructure.EntitySourceDirectory sourceDirectory : projectStructure.getEntitySourceDirectories()) + { + String filePath = sourceDirectory.entityPathToFilePath(currentEntityPath); + if (fileAccessContext.fileExists(filePath)) + { + String newFilePath = sourceDirectory.entityPathToFilePath(change.getNewEntityPath()); + return ProjectFileOperation.moveFile(filePath, newFilePath); + } + } + throw new LegendSDLCServerException("Unable to handle operation " + change + ": could not find entity \"" + currentEntityPath + "\""); + } + default: + { + throw new RuntimeException("Unknown entity change type: " + change.getType()); + } + } + } + + static class EntityProjectFile + { + private final ProjectStructure.EntitySourceDirectory sourceDirectory; + private final ProjectFileAccessProvider.ProjectFile file; + private String path; + private Entity entity; + private String projectID; + private String rootDirectory; + + private EntityProjectFile(ProjectStructure.EntitySourceDirectory sourceDirectory, ProjectFileAccessProvider.ProjectFile file, String projectID, String rootDirectory) + { + this.sourceDirectory = sourceDirectory; + this.file = file; + this.projectID = projectID; + this.rootDirectory = rootDirectory; + } + + synchronized String getEntityPath() + { + if (this.path == null) + { + Path filePath = Paths.get(this.file.getPath()); + Path pathToProject = Paths.get(rootDirectory + "/" + this.projectID); + if (!filePath.startsWith(pathToProject)) + { + throw new LegendSDLCServerException("Paths " + filePath + " and " + pathToProject + " are not related"); + } + Path relativePath = filePath.subpath(pathToProject.getNameCount(), filePath.getNameCount()); + this.path = this.sourceDirectory.filePathToEntityPath("/" + relativePath); + } + return this.path; + } + + synchronized Entity getEntity() + { + if (this.entity == null) + { + Entity localEntity = this.sourceDirectory.deserialize(this.file); + if (!Objects.equals(localEntity.getPath(), getEntityPath())) + { + throw new RuntimeException("Expected entity path " + getEntityPath() + ", found " + localEntity.getPath()); + } + this.entity = localEntity; + } + return this.entity; + } + } + +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/issue/FileSystemIssueApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/issue/FileSystemIssueApi.java new file mode 100644 index 0000000000..ba6d9f8b48 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/issue/FileSystemIssueApi.java @@ -0,0 +1,54 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.issue; + +import org.finos.legend.sdlc.domain.model.issue.Issue; +import org.finos.legend.sdlc.server.domain.api.issue.IssueApi; +import org.finos.legend.sdlc.server.exception.FSException; + +import javax.inject.Inject; +import java.util.List; + +public class FileSystemIssueApi implements IssueApi +{ + @Inject + public FileSystemIssueApi() + { + } + + @Override + public Issue getIssue(String projectId, String issueId) + { + throw FSException.unavailableFeature(); + } + + @Override + public List getIssues(String projectId) + { + throw FSException.unavailableFeature(); + } + + @Override + public Issue createIssue(String projectId, String title, String description) + { + throw FSException.unavailableFeature(); + } + + @Override + public void deleteIssue(String projectId, String issueId) + { + throw FSException.unavailableFeature(); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/patch/FileSystemPatchApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/patch/FileSystemPatchApi.java new file mode 100644 index 0000000000..612afc6015 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/patch/FileSystemPatchApi.java @@ -0,0 +1,56 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.patch; + +import org.finos.legend.sdlc.domain.model.patch.Patch; +import org.finos.legend.sdlc.domain.model.version.Version; +import org.finos.legend.sdlc.domain.model.version.VersionId; +import org.finos.legend.sdlc.server.domain.api.patch.PatchApi; +import org.finos.legend.sdlc.server.exception.FSException; + +import javax.inject.Inject; +import java.util.List; + +public class FileSystemPatchApi implements PatchApi +{ + @Inject + public FileSystemPatchApi() + { + } + + @Override + public Patch newPatch(String projectId, VersionId sourceVersion) + { + throw FSException.unavailableFeature(); + } + + @Override + public List getPatches(String projectId, Integer minMajorVersion, Integer maxMajorVersion, Integer minMinorVersion, Integer maxMinorVersion, Integer minPatchVersion, Integer maxPatchVersion) + { + throw FSException.unavailableFeature(); + } + + @Override + public void deletePatch(String projectId, VersionId patchReleaseVersionId) + { + throw FSException.unavailableFeature(); + } + + @Override + public Version releasePatch(String projectId, VersionId patchReleaseVersionId) + { + throw FSException.unavailableFeature(); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectApi.java new file mode 100644 index 0000000000..cea10c45a1 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectApi.java @@ -0,0 +1,277 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.project; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.finos.legend.sdlc.domain.model.project.Project; +import org.finos.legend.sdlc.domain.model.project.ProjectType; +import org.finos.legend.sdlc.domain.model.project.accessRole.AccessRole; +import org.finos.legend.sdlc.domain.model.project.accessRole.AuthorizableProjectAction; +import org.finos.legend.sdlc.server.api.entity.FileSystemApiWithFileAccess; +import org.finos.legend.sdlc.server.domain.api.project.ProjectApi; +import org.finos.legend.sdlc.server.domain.api.project.ProjectConfigurationUpdater; +import org.finos.legend.sdlc.server.error.LegendSDLCServerException; +import org.finos.legend.sdlc.server.exception.FSException; +import org.finos.legend.sdlc.server.project.ProjectStructure; +import org.finos.legend.sdlc.server.project.ProjectStructurePlatformExtensions; +import org.finos.legend.sdlc.server.project.config.ProjectCreationConfiguration; +import org.finos.legend.sdlc.server.project.config.ProjectStructureConfiguration; +import org.finos.legend.sdlc.server.project.extension.ProjectStructureExtensionProvider; + +import org.eclipse.jgit.api.Git; +import org.finos.legend.sdlc.server.startup.FSConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +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.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +public class FileSystemProjectApi extends FileSystemApiWithFileAccess implements ProjectApi +{ + private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemProjectApi.class); + private final ProjectStructureConfiguration projectStructureConfig; + private final ProjectStructureExtensionProvider projectStructureExtensionProvider; + private final ProjectStructurePlatformExtensions projectStructurePlatformExtensions; + + @Inject + public FileSystemProjectApi(FSConfiguration fsConfiguration, ProjectStructureConfiguration projectStructureConfig, ProjectStructureExtensionProvider projectStructureExtensionProvider, ProjectStructurePlatformExtensions projectStructurePlatformExtensions) + { + super(fsConfiguration); + this.projectStructureConfig = projectStructureConfig; + this.projectStructureExtensionProvider = projectStructureExtensionProvider; + this.projectStructurePlatformExtensions = projectStructurePlatformExtensions; + } + + @Override + public Project getProject(String id) + { + Repository repository = retrieveRepo(id); + Git git = new Git(repository); + return gitProjectToProject(git); + } + + @Override + public List getProjects(boolean user, String search, Iterable tags, Integer limit) + { + List gitRepos = new ArrayList<>(); + try (Stream paths = Files.list(Paths.get(getRootDirectory()))) + { + paths.filter(path -> Files.isDirectory(path)).forEach(repoDir -> + { + try + { + FileRepositoryBuilder repoBuilder = new FileRepositoryBuilder(); + Repository repository = repoBuilder.setGitDir(new File(repoDir.toFile(), ".git")).readEnvironment().findGitDir().build(); + if (repository != null) + { + gitRepos.add(gitProjectToProject(new Git(repository))); + } + } + catch (IOException e) + { + LOGGER.error("Repository {} could not be accessed", repoDir, e); + } + }); + } + catch (IOException e) + { + LOGGER.error("Exception occurred when opening the directory {}", getRootDirectory(), e); + throw FSException.getLegendSDLCServerException("Failed to fetch projects", e); + } + return gitRepos; + } + + @Override + public Project createProject(String name, String description, ProjectType type, String groupId, String artifactId, Iterable tags) + { + LegendSDLCServerException.validate(name, n -> (n != null) && !n.isEmpty(), "name may not be null or empty"); + LegendSDLCServerException.validateNonNull(description, "description may not be null"); + + String projectPath = getRootDirectory() + "/" + name; + Git gitProject; + String projectId = name; + try + { + Repository repository = FileRepositoryBuilder.create(new File(projectPath, ".git")); + repository.create(); + + gitProject = new Git(repository); + gitProject.commit().setMessage("Initial Commit").call(); + + repository.getConfig().setString("project", null, "id", projectId); + repository.getConfig().setString("project", null, "name", name); + repository.getConfig().setString("project", null, "description", description); + repository.getConfig().save(); + } + catch (Exception e) + { + throw FSException.getLegendSDLCServerException("Failed to create project: " + name, e); + } + Project project = gitProjectToProject(gitProject); + + // Build project structure + int projectStructureVersion = getDefaultProjectStructureVersion(); + ProjectConfigurationUpdater configUpdater = ProjectConfigurationUpdater.newUpdater() + .withProjectId(project.getProjectId()) + .withProjectType(type) + .withGroupId(groupId) + .withArtifactId(artifactId) + .withProjectStructureVersion(projectStructureVersion); + if (this.projectStructureExtensionProvider != null && type != ProjectType.EMBEDDED) + { + configUpdater.setProjectStructureExtensionVersion(this.projectStructureExtensionProvider.getLatestVersionForProjectStructureVersion(projectStructureVersion)); + } + ProjectStructure.newUpdateBuilder(getProjectFileAccessProvider(), project.getProjectId(), configUpdater) + .withMessage("Build project structure") + .withProjectStructureExtensionProvider(this.projectStructureExtensionProvider) + .withProjectStructurePlatformExtensions(this.projectStructurePlatformExtensions) + .build(); + return project; + } + + private int getDefaultProjectStructureVersion() + { + ProjectCreationConfiguration projectCreationConfig = getProjectCreationConfiguration(); + if (projectCreationConfig != null) + { + Integer defaultProjectStructureVersion = projectCreationConfig.getDefaultProjectStructureVersion(); + if (defaultProjectStructureVersion != null) + { + return defaultProjectStructureVersion; + } + } + return ProjectStructure.getLatestProjectStructureVersion(); + } + + private ProjectCreationConfiguration getProjectCreationConfiguration() + { + return (this.projectStructureConfig == null) ? null : this.projectStructureConfig.getProjectCreationConfiguration(); + } + + private Project gitProjectToProject(Git project) + { + return (project == null) ? null : new FileSystemProjectApi.ProjectWrapper(project); + } + + private static class ProjectWrapper implements Project + { + private final Git gitProject; + + public ProjectWrapper(Git gitProject) + { + this.gitProject = gitProject; + } + + @Override + public String getProjectId() + { + return this.gitProject.getRepository().getConfig().getString("project", null, "id"); + } + + @Override + public String getName() + { + return this.gitProject.getRepository().getConfig().getString("project", null, "name"); + } + + @Override + public String getDescription() + { + return this.gitProject.getRepository().getConfig().getString("project", null, "description"); + } + + @Override + public List getTags() + { + return null; + } + + @Override + public ProjectType getProjectType() + { + return null; + } + + @Override + public String getWebUrl() + { + return null; + } + } + + @Override + public void deleteProject(String id) + { + throw FSException.unavailableFeature(); + } + + @Override + public void changeProjectName(String id, String newName) + { + throw FSException.unavailableFeature(); + } + + @Override + public void changeProjectDescription(String id, String newDescription) + { + throw FSException.unavailableFeature(); + } + + @Override + public void updateProjectTags(String id, Iterable tagsToRemove, Iterable tagsToAdd) + { + throw FSException.unavailableFeature(); + } + + @Override + public void setProjectTags(String id, Iterable tags) + { + throw FSException.unavailableFeature(); + } + + @Override + public AccessRole getCurrentUserAccessRole(String id) + { + throw FSException.unavailableFeature(); + } + + @Override + public Set checkUserAuthorizedActions(String id, Set actions) + { + throw FSException.unavailableFeature(); + } + + @Override + public boolean checkUserAuthorizedAction(String id, AuthorizableProjectAction action) + { + throw FSException.unavailableFeature(); + } + + @Override + public ImportReport importProject(String id, ProjectType type, String groupId, String artifactId) + { + throw FSException.unavailableFeature(); + } + +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectConfigurationApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectConfigurationApi.java new file mode 100644 index 0000000000..a07025fbc2 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectConfigurationApi.java @@ -0,0 +1,106 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.project; + +import org.finos.legend.sdlc.domain.model.project.configuration.ArtifactTypeGenerationConfiguration; +import org.finos.legend.sdlc.domain.model.project.configuration.ProjectConfiguration; +import org.finos.legend.sdlc.domain.model.project.configuration.ProjectStructureVersion; +import org.finos.legend.sdlc.domain.model.revision.Revision; +import org.finos.legend.sdlc.server.api.entity.FileSystemApiWithFileAccess; +import org.finos.legend.sdlc.server.domain.api.project.ProjectConfigurationApi; +import org.finos.legend.sdlc.server.domain.api.project.ProjectConfigurationUpdater; +import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification; +import org.finos.legend.sdlc.server.domain.api.project.source.WorkspaceSourceSpecification; +import org.finos.legend.sdlc.server.error.LegendSDLCServerException; +import org.finos.legend.sdlc.server.project.ProjectConfigurationStatusReport; +import org.finos.legend.sdlc.server.project.ProjectStructure; +import org.finos.legend.sdlc.server.project.extension.ProjectStructureExtensionProvider; +import org.finos.legend.sdlc.server.startup.FSConfiguration; + +import java.util.Collections; +import java.util.List; +import javax.inject.Inject; + +public class FileSystemProjectConfigurationApi extends FileSystemApiWithFileAccess implements ProjectConfigurationApi +{ + private final ProjectStructureExtensionProvider projectStructureExtensionProvider; + + @Inject + public FileSystemProjectConfigurationApi(FSConfiguration fsConfiguration, ProjectStructureExtensionProvider projectStructureExtensionProvider) + { + super(fsConfiguration); + this.projectStructureExtensionProvider = projectStructureExtensionProvider; + } + + @Override + public ProjectConfiguration getProjectConfiguration(String projectId, SourceSpecification sourceSpecification, String revisionId) + { + LegendSDLCServerException.validateNonNull(projectId, "projectId may not be null"); + LegendSDLCServerException.validateNonNull(sourceSpecification, "sourceSpecification may not be null"); + ProjectConfiguration config = ProjectStructure.getProjectConfiguration(projectId, sourceSpecification, revisionId, getProjectFileAccessProvider()); + return (config == null) ? ProjectStructure.getDefaultProjectConfiguration(projectId) : config; + } + + @Override + public ProjectConfiguration getReviewFromProjectConfiguration(String projectId, String reviewId) + { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public ProjectConfiguration getReviewToProjectConfiguration(String projectId, String reviewId) + { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public Revision updateProjectConfiguration(String projectId, WorkspaceSourceSpecification sourceSpecification, String message, ProjectConfigurationUpdater updater) + { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public List getAvailableArtifactGenerations(String projectId, SourceSpecification sourceSpecification, String revisionId) + { + return Collections.emptyList(); + } + + @Override + public ProjectConfigurationStatusReport getProjectConfigurationStatus(String projectId) + { + boolean isProjectConfigured = (ProjectStructure.getProjectConfiguration(projectId, SourceSpecification.projectSourceSpecification(), null, getProjectFileAccessProvider()) != null); + return new ProjectConfigurationStatusReport() + { + @Override + public boolean isProjectConfigured() + { + return isProjectConfigured; + } + + @Override + public List getReviewIds() + { + return Collections.emptyList(); + } + }; + } + + @Override + public ProjectStructureVersion getLatestProjectStructureVersion() + { + int latestProjectStructureVersion = ProjectStructure.getLatestProjectStructureVersion(); + return ProjectStructureVersion.newProjectStructureVersion(latestProjectStructureVersion, this.projectStructureExtensionProvider.getLatestVersionForProjectStructureVersion(latestProjectStructureVersion)); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/review/FileSystemReviewApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/review/FileSystemReviewApi.java new file mode 100644 index 0000000000..ff7bf2d246 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/review/FileSystemReviewApi.java @@ -0,0 +1,121 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.review; + +import org.finos.legend.sdlc.domain.model.project.workspace.WorkspaceType; +import org.finos.legend.sdlc.domain.model.review.Approval; +import org.finos.legend.sdlc.domain.model.review.Review; +import org.finos.legend.sdlc.domain.model.review.ReviewState; +import org.finos.legend.sdlc.server.domain.api.review.ReviewApi; +import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceSpecification; +import org.finos.legend.sdlc.server.exception.FSException; + +import javax.inject.Inject; +import java.time.Instant; +import java.util.Collections; +import java.util.List; +import java.util.function.BiPredicate; + +public class FileSystemReviewApi implements ReviewApi +{ + @Inject + public FileSystemReviewApi() + { + } + + @Override + public Review getReview(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } + + @Override + public List getReviews(String projectId, ReviewState state, Iterable revisionIds, BiPredicate workspaceIdAndTypePredicate, Instant since, Instant until, Integer limit) + { + return Collections.emptyList(); + } + + @Override + public List getReviews(boolean assignedToMe, boolean authoredByMe, List labels, BiPredicate workspaceIdAndTypePredicate, ReviewState state, Instant since, Instant until, Integer limit) + { + return Collections.emptyList(); + } + + @Override + public Review createReview(String projectId, WorkspaceSpecification workspaceSpecification, String title, String description, List labels) + { + throw FSException.unavailableFeature(); + } + + @Override + public Review editReview(String projectId, String reviewId, String title, String description, List labels) + { + throw FSException.unavailableFeature(); + } + + @Override + public Review closeReview(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } + + @Override + public Review reopenReview(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } + + @Override + public Review approveReview(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } + + @Override + public Review revokeReviewApproval(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } + + @Override + public Review rejectReview(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } + + @Override + public Approval getReviewApproval(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } + + @Override + public Review commitReview(String projectId, String reviewId, String message) + { + throw FSException.unavailableFeature(); + } + + @Override + public ReviewUpdateStatus getReviewUpdateStatus(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } + + @Override + public ReviewUpdateStatus updateReview(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/revision/FileSystemRevisionApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/revision/FileSystemRevisionApi.java new file mode 100644 index 0000000000..76cd194bf4 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/revision/FileSystemRevisionApi.java @@ -0,0 +1,167 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.revision; + +import org.finos.legend.sdlc.domain.model.revision.Revision; +import org.finos.legend.sdlc.domain.model.revision.RevisionStatus; +import org.finos.legend.sdlc.server.api.entity.FileSystemApiWithFileAccess; +import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification; +import org.finos.legend.sdlc.server.domain.api.revision.RevisionAccessContext; +import org.finos.legend.sdlc.server.domain.api.revision.RevisionApi; +import org.finos.legend.sdlc.server.error.LegendSDLCServerException; +import org.finos.legend.sdlc.server.exception.FSException; +import org.finos.legend.sdlc.server.project.ProjectFileAccessProvider; +import org.finos.legend.sdlc.server.startup.FSConfiguration; + +import javax.inject.Inject; +import java.time.Instant; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class FileSystemRevisionApi extends FileSystemApiWithFileAccess implements RevisionApi +{ + @Inject + public FileSystemRevisionApi(FSConfiguration fsConfiguration) + { + super(fsConfiguration); + } + + @Override + public RevisionAccessContext getRevisionContext(String projectId, SourceSpecification sourceSpec) + { + return new ProjectFileRevisionAccessContextWrapper(getProjectFileAccessProvider().getRevisionAccessContext(projectId, sourceSpec)); + } + + @Override + public RevisionAccessContext getPackageRevisionContext(String projectId, SourceSpecification sourceSpec, String packagePath) + { + throw FSException.unavailableFeature(); + } + + @Override + public RevisionAccessContext getEntityRevisionContext(String projectId, SourceSpecification sourceSpec, String entityPath) + { + throw FSException.unavailableFeature(); + } + + @Override + public RevisionStatus getRevisionStatus(String projectId, String revisionId) + { + throw FSException.unavailableFeature(); + } + + private static class ProjectFileRevisionAccessContextWrapper implements RevisionAccessContext + { + private final ProjectFileAccessProvider.RevisionAccessContext revisionAccessContext; + private final Function exceptionProcessor; + + private ProjectFileRevisionAccessContextWrapper(ProjectFileAccessProvider.RevisionAccessContext revisionAccessContext, Function exceptionProcessor) + { + this.revisionAccessContext = revisionAccessContext; + this.exceptionProcessor = exceptionProcessor; + } + + private ProjectFileRevisionAccessContextWrapper(ProjectFileAccessProvider.RevisionAccessContext revisionAccessContext) + { + this(revisionAccessContext, null); + } + + @Override + public Revision getRevision(String revisionId) + { + try + { + return this.revisionAccessContext.getRevision(revisionId); + } + catch (LegendSDLCServerException e) + { + if (this.exceptionProcessor != null) + { + LegendSDLCServerException processedException = this.exceptionProcessor.apply(e); + if (processedException != null) + { + throw processedException; + } + } + throw e; + } + } + + @Override + public Revision getBaseRevision() + { + try + { + return this.revisionAccessContext.getBaseRevision(); + } + catch (LegendSDLCServerException e) + { + if (this.exceptionProcessor != null) + { + LegendSDLCServerException processedException = this.exceptionProcessor.apply(e); + if (processedException != null) + { + throw processedException; + } + } + throw e; + } + } + + @Override + public Revision getCurrentRevision() + { + try + { + return this.revisionAccessContext.getCurrentRevision(); + } + catch (LegendSDLCServerException e) + { + if (this.exceptionProcessor != null) + { + LegendSDLCServerException processedException = this.exceptionProcessor.apply(e); + if (processedException != null) + { + throw processedException; + } + } + throw e; + } + } + + @Override + public List getRevisions(Predicate predicate, Instant since, Instant until, Integer limit) + { + try + { + return this.revisionAccessContext.getAllRevisions(predicate, since, until, limit).collect(Collectors.toList()); + } + catch (LegendSDLCServerException e) + { + if (this.exceptionProcessor != null) + { + LegendSDLCServerException processedException = this.exceptionProcessor.apply(e); + if (processedException != null) + { + throw processedException; + } + } + throw e; + } + } + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/user/FileSystemUserApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/user/FileSystemUserApi.java new file mode 100644 index 0000000000..b76cb7afbb --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/user/FileSystemUserApi.java @@ -0,0 +1,61 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.user; + +import org.eclipse.collections.api.factory.Lists; +import org.eclipse.collections.api.factory.Maps; +import org.eclipse.collections.api.map.ImmutableMap; +import org.finos.legend.sdlc.domain.model.user.User; +import org.finos.legend.sdlc.server.domain.api.user.UserApi; +import org.finos.legend.sdlc.server.domain.model.user.FileSystemUser; + +import javax.inject.Inject; +import java.util.List; + +public class FileSystemUserApi implements UserApi +{ + public static final FileSystemUser LOCAL_USER = new FileSystemUser("local_user", "local_user"); + + private static final ImmutableMap users = Maps.immutable.of(LOCAL_USER.getUserId(), LOCAL_USER); + + @Inject + public FileSystemUserApi() + { + } + + @Override + public List getUsers() + { + return Lists.mutable.withAll(users.valuesView()); + } + + @Override + public User getUserById(String userId) + { + return users.get(userId); + } + + @Override + public List findUsers(String searchString) + { + return Lists.mutable.withAll(users.select(user -> user.getUserId().contains(searchString) || user.getName().contains(searchString))); + } + + @Override + public User getCurrentUserInfo() + { + return LOCAL_USER; + } +} \ No newline at end of file diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/version/FileSystemVersionApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/version/FileSystemVersionApi.java new file mode 100644 index 0000000000..cd1760f54b --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/version/FileSystemVersionApi.java @@ -0,0 +1,58 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.version; + +import org.finos.legend.sdlc.domain.model.version.Version; +import org.finos.legend.sdlc.server.domain.api.version.NewVersionType; +import org.finos.legend.sdlc.server.domain.api.version.VersionApi; +import org.finos.legend.sdlc.server.exception.FSException; + +import javax.inject.Inject; +import java.util.Collections; +import java.util.List; + +public class FileSystemVersionApi implements VersionApi +{ + + @Inject + public FileSystemVersionApi() + { + } + + @Override + public List getVersions(String projectId, Integer minMajorVersion, Integer maxMajorVersion, Integer minMinorVersion, Integer maxMinorVersion, Integer minPatchVersion, Integer maxPatchVersion) + { + return Collections.emptyList(); + } + + @Override + public Version getLatestVersion(String projectId, Integer minMajorVersion, Integer maxMajorVersion, Integer minMinorVersion, Integer maxMinorVersion, Integer minPatchVersion, Integer maxPatchVersion) + { + throw FSException.unavailableFeature(); + } + + @Override + public Version getVersion(String projectId, int majorVersion, int minorVersion, int patchVersion) + { + throw FSException.unavailableFeature(); + } + + @Override + public Version newVersion(String projectId, NewVersionType type, String revisionId, String notes) + { + throw FSException.unavailableFeature(); + } + +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowApi.java new file mode 100644 index 0000000000..b44ae72cca --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowApi.java @@ -0,0 +1,42 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.workflow; + +import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification; +import org.finos.legend.sdlc.server.exception.FSException; +import org.finos.legend.sdlc.server.domain.api.workflow.WorkflowAccessContext; +import org.finos.legend.sdlc.server.domain.api.workflow.WorkflowApi; + +import javax.inject.Inject; + +public class FileSystemWorkflowApi implements WorkflowApi +{ + @Inject + public FileSystemWorkflowApi() + { + } + + @Override + public WorkflowAccessContext getWorkflowAccessContext(String projectId, SourceSpecification sourceSpecification) + { + throw FSException.unavailableFeature(); + } + + @Override + public WorkflowAccessContext getReviewWorkflowAccessContext(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowJobApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowJobApi.java new file mode 100644 index 0000000000..9475c54023 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowJobApi.java @@ -0,0 +1,42 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.workflow; + +import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification; +import org.finos.legend.sdlc.server.exception.FSException; +import org.finos.legend.sdlc.server.domain.api.workflow.WorkflowJobAccessContext; +import org.finos.legend.sdlc.server.domain.api.workflow.WorkflowJobApi; + +import javax.inject.Inject; + +public class FileSystemWorkflowJobApi implements WorkflowJobApi +{ + @Inject + public FileSystemWorkflowJobApi() + { + } + + @Override + public WorkflowJobAccessContext getWorkflowJobAccessContext(String projectId, SourceSpecification sourceSpecification) + { + throw FSException.unavailableFeature(); + } + + @Override + public WorkflowJobAccessContext getReviewWorkflowJobAccessContext(String projectId, String reviewId) + { + throw FSException.unavailableFeature(); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workspace/FileSystemWorkspaceApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workspace/FileSystemWorkspaceApi.java new file mode 100644 index 0000000000..da6353fcf9 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workspace/FileSystemWorkspaceApi.java @@ -0,0 +1,342 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.api.workspace; + +import org.eclipse.collections.api.factory.Lists; +import org.eclipse.collections.api.list.MutableList; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.ListBranchCommand; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.finos.legend.sdlc.domain.model.project.workspace.Workspace; +import org.finos.legend.sdlc.domain.model.project.workspace.WorkspaceType; +import org.finos.legend.sdlc.server.api.entity.FileSystemApiWithFileAccess; +import org.finos.legend.sdlc.server.api.user.FileSystemUserApi; +import org.finos.legend.sdlc.server.domain.api.workspace.PatchWorkspaceSource; +import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceApi; +import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceSource; +import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceSpecification; +import org.finos.legend.sdlc.server.error.LegendSDLCServerException; +import org.finos.legend.sdlc.server.exception.FSException; +import org.finos.legend.sdlc.server.project.ProjectFileAccessProvider; +import org.finos.legend.sdlc.server.startup.FSConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import javax.inject.Inject; +import javax.ws.rs.core.Response; + +import static org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification.workspaceSourceSpecification; + +public class FileSystemWorkspaceApi extends FileSystemApiWithFileAccess implements WorkspaceApi +{ + private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemWorkspaceApi.class); + + private static final String WORKSPACE_BRANCH_PREFIX = "workspace"; + private static final String CONFLICT_RESOLUTION_BRANCH_PREFIX = "resolution"; + private static final String BACKUP_BRANCH_PREFIX = "backup"; + private static final String GROUP_WORKSPACE_BRANCH_PREFIX = "group"; + private static final String GROUP_CONFLICT_RESOLUTION_BRANCH_PREFIX = "group-resolution"; + private static final String GROUP_BACKUP_BRANCH_PREFIX = "group-backup"; + private static final String PATCH_WORKSPACE_BRANCH_PREFIX = "patch"; + + protected static final char BRANCH_DELIMITER = '/'; + + @Inject + public FileSystemWorkspaceApi(FSConfiguration fsConfiguration) + { + super(fsConfiguration); + } + + @Override + public Workspace getWorkspace(String projectId, WorkspaceSpecification workspaceSpecification) + { + String refBranchName = Constants.R_HEADS + getRefBranchName(workspaceSourceSpecification(workspaceSpecification)); + Ref branch = getGitBranch(projectId, refBranchName); + return workspaceBranchToWorkspace(projectId, branch, workspaceSpecification.getType()); + } + + @Override + public List getWorkspaces(String projectId, Set types, Set accessTypes, Set sources) + { + if (sources == null) + { + throw new UnsupportedOperationException("Not implemented"); + } + + Set resolvedTypes = (types == null) ? EnumSet.allOf(WorkspaceType.class) : types; + Set resolvedAccessTypes = (accessTypes == null) ? EnumSet.allOf(ProjectFileAccessProvider.WorkspaceAccessType.class) : accessTypes; + + Repository repository = retrieveRepo(projectId); + MutableList result = Lists.mutable.empty(); + // currently only WORKSPACE access type is supported + if (resolvedAccessTypes.contains(ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE)) + { + if (resolvedTypes.contains(WorkspaceType.GROUP)) + { + Collection UserWS = getBranchesByType(repository, "user", projectId, WorkspaceType.USER); + result.addAllIterable(UserWS); + } + if (resolvedTypes.contains(WorkspaceType.USER)) + { + Collection GroupWS = getBranchesByType(repository, "group", projectId, WorkspaceType.GROUP); + result.addAllIterable(GroupWS); + } + } + return result; + } + + @Override + public List getAllWorkspaces(String projectId, Set types, Set accessTypes, Set sources) + { + return this.getWorkspaces(projectId, types, accessTypes, sources); + } + + @Override + public Workspace newWorkspace(String projectId, String workspaceId, WorkspaceType type, WorkspaceSource source) + { + LegendSDLCServerException.validateNonNull(projectId, "projectId may not be null"); + LegendSDLCServerException.validateNonNull(workspaceId, "workspaceId may not be null"); + LegendSDLCServerException.validateNonNull(type, "workspace type may not be null"); + LegendSDLCServerException.validateNonNull(source, "workspace source may not be null"); + validateWorkspaceId(workspaceId); + + WorkspaceSpecification workspaceSpecification = WorkspaceSpecification.newWorkspaceSpecification(workspaceId, type, ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE, source); + String workspaceBranchName = getWorkspaceBranchName(workspaceSpecification); + + Ref branchRef; + try + { + Repository repository = retrieveRepo(projectId); + Git git = new Git(repository); + git.checkout().setName("master").call(); + branchRef = git.branchCreate().setName(workspaceBranchName).call(); + git.getRepository().getConfig().setString("branch", workspaceBranchName, "type", type.getLabel()); + git.getRepository().getConfig().save(); + } + catch (Exception e) + { + throw FSException.getLegendSDLCServerException("Failed to create workspace " + workspaceBranchName + " for project " + projectId, e); + } + return workspaceBranchToWorkspace(projectId, branchRef, workspaceSpecification.getType()); + } + + private static void validateWorkspaceId(String idString) + { + validateWorkspaceId(idString, null); + } + + private static void validateWorkspaceId(String idString, Response.Status errorStatus) + { + if (!isValidWorkspaceId(idString)) + { + throw new LegendSDLCServerException("Invalid workspace id: \"" + idString + "\". A workspace id must be a non-empty string consisting of characters from the following set: {a-z, A-Z, 0-9, _, ., -}. The id may not contain \"..\" and may not start or end with '.' or '-'.", (errorStatus == null) ? Response.Status.BAD_REQUEST : errorStatus); + } + } + + private static boolean isValidWorkspaceId(String string) + { + if ((string == null) || string.isEmpty()) + { + return false; + } + + if (!isValidWorkspaceStartEndChar(string.charAt(0))) + { + return false; + } + int lastIndex = string.length() - 1; + for (int i = 1; i < lastIndex; i++) + { + char c = string.charAt(i); + boolean isValid = isValidWorkspaceStartEndChar(c) || (c == '-') || ((c == '.') && (string.charAt(i - 1) != '.')); + if (!isValid) + { + return false; + } + } + return isValidWorkspaceStartEndChar(string.charAt(lastIndex)); + } + + private static boolean isValidWorkspaceStartEndChar(char c) + { + return (c == '_') || (('a' <= c) && (c <= 'z')) || (('A' <= c) && (c <= 'Z')) || (('0' <= c) && (c <= '9')); + } + + public static String getWorkspaceBranchName(WorkspaceSpecification workspaceSpec) + { + String userId = (workspaceSpec.getType() == WorkspaceType.GROUP) ? null : FileSystemUserApi.LOCAL_USER.getUserId(); + return getWorkspaceBranchName(workspaceSpec, userId); + } + + protected static String getWorkspaceBranchName(WorkspaceSpecification workspaceSpec, String currentUser) + { + StringBuilder builder = new StringBuilder(); + WorkspaceSource source = workspaceSpec.getSource(); + if (source instanceof PatchWorkspaceSource) + { + builder.append(PATCH_WORKSPACE_BRANCH_PREFIX).append(BRANCH_DELIMITER); + ((PatchWorkspaceSource) source).getPatchVersionId().appendVersionIdString(builder).append(BRANCH_DELIMITER); + } + builder.append(getWorkspaceBranchNamePrefix(workspaceSpec.getType(), workspaceSpec.getAccessType())).append(BRANCH_DELIMITER); + if (workspaceSpec.getType() == WorkspaceType.USER) + { + String userId = workspaceSpec.getUserId(); + builder.append((userId == null) ? currentUser : userId).append(BRANCH_DELIMITER); + } + return builder.append(workspaceSpec.getId()).toString(); + } + + protected static String getWorkspaceBranchNamePrefix(WorkspaceType workspaceType, ProjectFileAccessProvider.WorkspaceAccessType workspaceAccessType) + { + assert workspaceAccessType != null : "Workspace access type has been used but it is null, which should not be the case"; + + switch (workspaceType) + { + case GROUP: + { + switch (workspaceAccessType) + { + case WORKSPACE: + { + return GROUP_WORKSPACE_BRANCH_PREFIX; + } + case CONFLICT_RESOLUTION: + { + return GROUP_CONFLICT_RESOLUTION_BRANCH_PREFIX; + } + case BACKUP: + { + return GROUP_BACKUP_BRANCH_PREFIX; + } + default: + { + throw new RuntimeException("Unknown workspace access type: " + workspaceAccessType); + } + } + } + case USER: + { + switch (workspaceAccessType) + { + case WORKSPACE: + { + return WORKSPACE_BRANCH_PREFIX; + } + case CONFLICT_RESOLUTION: + { + return CONFLICT_RESOLUTION_BRANCH_PREFIX; + } + case BACKUP: + { + return BACKUP_BRANCH_PREFIX; + } + default: + { + throw new RuntimeException("Unknown workspace access type: " + workspaceAccessType); + } + } + } + default: + { + throw new RuntimeException("Unknown workspace type: " + workspaceType); + } + } + } + + @Override + public void deleteWorkspace(String projectId, WorkspaceSpecification workspaceSpecification) + { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public boolean isWorkspaceOutdated(String projectId, WorkspaceSpecification workspaceSpecification) + { + return false; + } + + @Override + public boolean isWorkspaceInConflictResolutionMode(String projectId, WorkspaceSpecification workspaceSpecification) + { + return false; + } + + @Override + public WorkspaceUpdateReport updateWorkspace(String projectId, WorkspaceSpecification workspaceSpecification) + { + throw FSException.unavailableFeature(); + } + + private Collection getBranchesByType(Repository repository, String branchType, String projectId, WorkspaceType wType) + { + List branchesOfType = new ArrayList<>(); + try + { + List allBranches = Git.wrap(repository).branchList().setListMode(ListBranchCommand.ListMode.ALL).call(); + for (Ref branch : allBranches) + { + String type = repository.getConfig().getString("branch", Repository.shortenRefName(branch.getName()), "type"); + if (type != null && type.equals(branchType)) + { + branchesOfType.add(workspaceBranchToWorkspace(projectId, branch, wType)); + } + } + } + catch (Exception e) + { + LOGGER.error("Error occurred during branch list operation for project {}", projectId, e); + throw FSException.getLegendSDLCServerException("Failed to fetch " + branchType + "workspaces for project " + projectId, e); + } + return branchesOfType; + } + + private static Workspace workspaceBranchToWorkspace(String projectId, Ref branch, WorkspaceType workspaceType) + { + String workspaceIdWithType = Repository.shortenRefName(branch.getName()); + String workspaceId = workspaceIdWithType.substring(workspaceIdWithType.lastIndexOf('/') + 1); + + WorkspaceSpecification workspaceSpecification = WorkspaceSpecification.newWorkspaceSpecification(workspaceId, workspaceType, ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE, null); + return (branch == null) ? null : fromWorkspaceBranchName(projectId, workspaceSpecification); + } + + protected static Workspace fromWorkspaceBranchName(String projectId, WorkspaceSpecification workspaceSpecification) + { + String userId = workspaceSpecification.getType() == WorkspaceType.GROUP ? null : FileSystemUserApi.LOCAL_USER.getUserId(); + String workspaceId = workspaceSpecification.getId(); + return new Workspace() + { + @Override + public String getProjectId() + { + return projectId; + } + + @Override + public String getUserId() + { + return userId; + } + + @Override + public String getWorkspaceId() + { + return workspaceId; + } + }; + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/depot/FileSystemMetadataApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/depot/FileSystemMetadataApi.java new file mode 100644 index 0000000000..b8c5a0dd51 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/depot/FileSystemMetadataApi.java @@ -0,0 +1,45 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.depot; + +import org.finos.legend.sdlc.domain.model.entity.Entity; +import org.finos.legend.sdlc.server.depot.api.MetadataApi; +import org.finos.legend.sdlc.server.depot.model.DepotProjectId; +import org.finos.legend.sdlc.server.depot.model.DepotProjectVersion; +import org.finos.legend.sdlc.server.exception.FSException; + +import java.util.List; +import java.util.Set; +import javax.inject.Inject; + +public class FileSystemMetadataApi implements MetadataApi +{ + @Inject + public FileSystemMetadataApi() + { + } + + @Override + public List getEntities(DepotProjectId projectId, String versionId) + { + throw FSException.unavailableFeature(); + } + + @Override + public Set getProjectDependencies(DepotProjectId projectId, String versionId, boolean transitive) + { + throw FSException.unavailableFeature(); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/project/configuration/FileSystemProjectConfiguration.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/project/configuration/FileSystemProjectConfiguration.java new file mode 100644 index 0000000000..7e183eb8c4 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/project/configuration/FileSystemProjectConfiguration.java @@ -0,0 +1,75 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.domain.model.project.configuration; + +import org.finos.legend.sdlc.domain.model.project.configuration.MetamodelDependency; +import org.finos.legend.sdlc.domain.model.project.configuration.ProjectConfiguration; +import org.finos.legend.sdlc.domain.model.project.configuration.ProjectDependency; +import org.finos.legend.sdlc.domain.model.project.configuration.ProjectStructureVersion; + +import java.util.Collections; +import java.util.List; + +public class FileSystemProjectConfiguration implements ProjectConfiguration +{ + private final String projectId; + private final ProjectStructureVersion projectStructureVersion; + private final String groupId; + private final String artifactId; + + public FileSystemProjectConfiguration(String projectId, ProjectStructureVersion projectStructureVersion, String groupId, String artifactId) + { + this.projectId = projectId; + this.projectStructureVersion = projectStructureVersion; + this.groupId = groupId; + this.artifactId = artifactId; + } + + @Override + public String getProjectId() + { + return this.projectId; + } + + @Override + public ProjectStructureVersion getProjectStructureVersion() + { + return this.projectStructureVersion; + } + + @Override + public String getGroupId() + { + return this.groupId; + } + + @Override + public String getArtifactId() + { + return this.artifactId; + } + + @Override + public List getProjectDependencies() + { + return Collections.emptyList(); + } + + @Override + public List getMetamodelDependencies() + { + return Collections.emptyList(); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/revision/FileSystemRevision.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/revision/FileSystemRevision.java new file mode 100644 index 0000000000..75dac817d0 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/revision/FileSystemRevision.java @@ -0,0 +1,101 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.domain.model.revision; + +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.finos.legend.sdlc.domain.model.revision.Revision; +import org.finos.legend.sdlc.server.exception.FSException; + +import java.time.Instant; + +public class FileSystemRevision implements Revision +{ + private final String id; + private final String authorName; + private final Instant authoredTimestamp; + private final String committerName; + private final Instant committedTimestamp; + private final String message; + + public FileSystemRevision(String revisionId, String authorName, Instant authoredTimeStamp, String committerName, Instant committedTimeStamp, String message) + { + this.id = revisionId; + this.authorName = authorName; + this.authoredTimestamp = authoredTimeStamp; + this.committerName = committerName; + this.committedTimestamp = committedTimeStamp; + this.message = message; + } + + public static FileSystemRevision getFileSystemRevision(String projectId, String workspaceId, Repository repo, Ref branchRef) + { + try + { + RevWalk revWalk = new RevWalk(repo); + RevCommit commit = revWalk.parseCommit(branchRef.getObjectId()); + revWalk.dispose(); + String revisionId = commit.getId().getName(); + String authorName = commit.getAuthorIdent().getName(); + Instant authoredTimeStamp = commit.getAuthorIdent().getWhenAsInstant(); + String committerName = commit.getCommitterIdent().getName(); + Instant committedTimeStamp = commit.getCommitterIdent().getWhenAsInstant(); + String message = commit.getFullMessage(); + return new FileSystemRevision(revisionId, authorName, authoredTimeStamp, committerName, committedTimeStamp, message); + } + catch (Exception e) + { + throw FSException.getLegendSDLCServerException("Failed to get revision for workspace " + workspaceId + " in project " + projectId, e); + } + } + + @Override + public String getId() + { + return id; + } + + @Override + public String getAuthorName() + { + return authorName; + } + + @Override + public Instant getAuthoredTimestamp() + { + return authoredTimestamp; + } + + @Override + public String getCommitterName() + { + return committerName; + } + + @Override + public Instant getCommittedTimestamp() + { + return committedTimestamp; + } + + @Override + public String getMessage() + { + return message; + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/user/FileSystemUser.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/user/FileSystemUser.java new file mode 100644 index 0000000000..4727af9fd1 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/user/FileSystemUser.java @@ -0,0 +1,42 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.domain.model.user; + +import org.finos.legend.sdlc.domain.model.user.User; + +public class FileSystemUser implements User +{ + private final String userId; + private final String name; + + public FileSystemUser(String userId, String name) + { + this.userId = userId; + this.name = name; + } + + @Override + public String getUserId() + { + return userId; + } + + @Override + public String getName() + { + return name; + } + +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/exception/FSException.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/exception/FSException.java new file mode 100644 index 0000000000..571164dcb4 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/exception/FSException.java @@ -0,0 +1,31 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.exception; + +import org.finos.legend.sdlc.server.error.LegendSDLCServerException; + +public class FSException +{ + public static UnsupportedOperationException unavailableFeature() + { + return new UnsupportedOperationException("Feature unavailable"); + } + + public static LegendSDLCServerException getLegendSDLCServerException(String errorMessage, Exception e) + { + String exceptionMessage = e.getMessage(); + return new LegendSDLCServerException(exceptionMessage != null ? errorMessage + " : " + exceptionMessage : errorMessage, e); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthCheckResource.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthCheckResource.java new file mode 100644 index 0000000000..06ec4c57b2 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthCheckResource.java @@ -0,0 +1,47 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.resources; + +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/auth") +public class FileSystemAuthCheckResource extends BaseResource +{ + private final HttpServletRequest httpRequest; + private final HttpServletResponse httpResponse; + + @Inject + public FileSystemAuthCheckResource(HttpServletRequest httpRequest, HttpServletResponse httpResponse) + { + super(); + this.httpRequest = httpRequest; + this.httpResponse = httpResponse; + } + + @GET + @Path("authorized") + @Produces(MediaType.APPLICATION_JSON) + public boolean isAuthorized() + { + return true; + } + +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthResource.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthResource.java new file mode 100644 index 0000000000..d0188a78a9 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthResource.java @@ -0,0 +1,66 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.resources; + +import io.swagger.annotations.ApiParam; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import java.util.Collections; +import java.util.Set; +import java.util.regex.Pattern; + +@Path("/auth") +public class FileSystemAuthResource extends BaseResource +{ + private static final Pattern TERMS_OF_SERVICE_MESSAGE_PATTERN = Pattern.compile("terms\\s++of\\s++service", Pattern.CASE_INSENSITIVE); + + @Inject + public FileSystemAuthResource() + { + super(); + } + + @GET + @Path("callback") + public Object callback(@QueryParam("code") String code, @QueryParam("state") String state) + { + throw new UnsupportedOperationException("Not implemented"); + } + + @GET + @Path("authorize") + @Produces(MediaType.TEXT_HTML) + public String authorize(@QueryParam("redirect_uri") @ApiParam("URI to redirect to when authorization is complete") String redirectUri) + { + return executeWithLogging("authorizing", () -> + { + return "

Success

"; + }); + } + + @GET + @Path("termsOfServiceAcceptance") + @Produces(MediaType.APPLICATION_JSON) + // NOTE: we have to return a set for backward compatibility reason + public Set termsOfServiceAcceptance() + { + return Collections.emptySet(); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSConfiguration.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSConfiguration.java new file mode 100644 index 0000000000..ccedaf26c4 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSConfiguration.java @@ -0,0 +1,39 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.startup; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class FSConfiguration +{ + public final String rootDirectory; + + private FSConfiguration(String rootDirectory) + { + this.rootDirectory = rootDirectory; + } + + public String getRootDirectory() + { + return rootDirectory; + } + + @JsonCreator + public static FSConfiguration newConfiguration(@JsonProperty("rootDirectory") String rootDirectory) + { + return new FSConfiguration(rootDirectory); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSModule.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSModule.java new file mode 100644 index 0000000000..fd0d124ec6 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSModule.java @@ -0,0 +1,546 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.startup; + +import com.google.inject.Binder; +import com.hubspot.dropwizard.guicier.DropwizardAwareModule; +import org.finos.legend.sdlc.server.BaseServer; +import org.finos.legend.sdlc.server.api.backup.FileSystemBackupApi; +import org.finos.legend.sdlc.server.api.build.FileSystemBuildApi; +import org.finos.legend.sdlc.server.api.comparison.FileSystemComparisonApi; +import org.finos.legend.sdlc.server.api.conflictresolution.FileSystemConflictResolutionApi; +import org.finos.legend.sdlc.server.api.entity.FileSystemEntityApi; +import org.finos.legend.sdlc.server.api.issue.FileSystemIssueApi; +import org.finos.legend.sdlc.server.api.patch.FileSystemPatchApi; +import org.finos.legend.sdlc.server.api.project.FileSystemProjectApi; +import org.finos.legend.sdlc.server.api.project.FileSystemProjectConfigurationApi; +import org.finos.legend.sdlc.server.api.review.FileSystemReviewApi; +import org.finos.legend.sdlc.server.api.revision.FileSystemRevisionApi; +import org.finos.legend.sdlc.server.api.user.FileSystemUserApi; +import org.finos.legend.sdlc.server.api.version.FileSystemVersionApi; +import org.finos.legend.sdlc.server.api.workflow.FileSystemWorkflowApi; +import org.finos.legend.sdlc.server.api.workflow.FileSystemWorkflowJobApi; +import org.finos.legend.sdlc.server.api.workspace.FileSystemWorkspaceApi; +import org.finos.legend.sdlc.server.config.LegendSDLCServerFeaturesConfiguration; +import org.finos.legend.sdlc.server.depot.DepotConfiguration; +import org.finos.legend.sdlc.server.depot.FileSystemMetadataApi; +import org.finos.legend.sdlc.server.depot.api.MetadataApi; +import org.finos.legend.sdlc.server.depot.auth.AuthClientInjector; +import org.finos.legend.sdlc.server.domain.api.backup.BackupApi; +import org.finos.legend.sdlc.server.domain.api.build.BuildApi; +import org.finos.legend.sdlc.server.domain.api.comparison.ComparisonApi; +import org.finos.legend.sdlc.server.domain.api.conflictResolution.ConflictResolutionApi; +import org.finos.legend.sdlc.server.domain.api.dependency.DependenciesApi; +import org.finos.legend.sdlc.server.domain.api.dependency.DependenciesApiImpl; +import org.finos.legend.sdlc.server.domain.api.entity.EntityApi; +import org.finos.legend.sdlc.server.domain.api.issue.IssueApi; +import org.finos.legend.sdlc.server.domain.api.patch.PatchApi; +import org.finos.legend.sdlc.server.domain.api.project.ProjectApi; +import org.finos.legend.sdlc.server.domain.api.project.ProjectConfigurationApi; +import org.finos.legend.sdlc.server.domain.api.review.ReviewApi; +import org.finos.legend.sdlc.server.domain.api.revision.RevisionApi; +import org.finos.legend.sdlc.server.domain.api.test.TestModelBuilder; +import org.finos.legend.sdlc.server.domain.api.user.UserApi; +import org.finos.legend.sdlc.server.domain.api.version.VersionApi; +import org.finos.legend.sdlc.server.domain.api.workflow.WorkflowApi; +import org.finos.legend.sdlc.server.domain.api.workflow.WorkflowJobApi; +import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceApi; +import org.finos.legend.sdlc.server.guice.UserContext; +import org.finos.legend.sdlc.server.project.ProjectStructurePlatformExtensions; +import org.finos.legend.sdlc.server.project.config.ProjectPlatformsConfiguration; +import org.finos.legend.sdlc.server.project.config.ProjectStructureConfiguration; +import org.finos.legend.sdlc.server.project.extension.DefaultProjectStructureExtensionProvider; +import org.finos.legend.sdlc.server.project.extension.ProjectStructureExtension; +import org.finos.legend.sdlc.server.project.extension.ProjectStructureExtensionProvider; +import org.finos.legend.sdlc.server.project.extension.VoidProjectStructureExtensionProvider; +import org.finos.legend.sdlc.server.resources.FileSystemAuthCheckResource; +import org.finos.legend.sdlc.server.resources.FileSystemAuthResource; +import org.finos.legend.sdlc.server.resources.InfoResource; +import org.finos.legend.sdlc.server.resources.ServerResource; +import org.finos.legend.sdlc.server.resources.backup.patch.group.*; +import org.finos.legend.sdlc.server.resources.backup.patch.user.*; +import org.finos.legend.sdlc.server.resources.backup.project.BackupProjectResource; +import org.finos.legend.sdlc.server.resources.backup.project.group.*; +import org.finos.legend.sdlc.server.resources.backup.project.user.*; +import org.finos.legend.sdlc.server.resources.build.ProjectBuildsResource; +import org.finos.legend.sdlc.server.resources.build.VersionBuildsResource; +import org.finos.legend.sdlc.server.resources.build.WorkspaceBuildsResource; +import org.finos.legend.sdlc.server.resources.comparison.patch.ComparisonPatchReviewEntitiesResource; +import org.finos.legend.sdlc.server.resources.comparison.patch.ComparisonPatchReviewProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.comparison.patch.ComparisonPatchReviewResource; +import org.finos.legend.sdlc.server.resources.comparison.patch.group.PatchesGroupComparisonWorkspaceResource; +import org.finos.legend.sdlc.server.resources.comparison.patch.user.ComparisonPatchesWorkspaceResource; +import org.finos.legend.sdlc.server.resources.comparison.project.ComparisonReviewEntitiesResource; +import org.finos.legend.sdlc.server.resources.comparison.project.ComparisonReviewProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.comparison.project.ComparisonReviewResource; +import org.finos.legend.sdlc.server.resources.comparison.project.group.GroupComparisonWorkspaceResource; +import org.finos.legend.sdlc.server.resources.comparison.project.user.ComparisonWorkspaceResource; +import org.finos.legend.sdlc.server.resources.conflictResolution.patch.ConflictResolutionPatchResource; +import org.finos.legend.sdlc.server.resources.conflictResolution.patch.group.*; +import org.finos.legend.sdlc.server.resources.conflictResolution.patch.user.*; +import org.finos.legend.sdlc.server.resources.conflictResolution.project.ConflictResolutionProjectResource; +import org.finos.legend.sdlc.server.resources.conflictResolution.project.group.*; +import org.finos.legend.sdlc.server.resources.conflictResolution.project.user.*; +import org.finos.legend.sdlc.server.resources.dependency.patch.PatchRevisionDependenciesResource; +import org.finos.legend.sdlc.server.resources.dependency.patch.group.PatchesGroupWorkspaceRevisionDependenciesResource; +import org.finos.legend.sdlc.server.resources.dependency.patch.user.PatchesWorkspaceRevisionDependenciesResource; +import org.finos.legend.sdlc.server.resources.dependency.project.DownstreamDependenciesResource; +import org.finos.legend.sdlc.server.resources.dependency.project.ProjectRevisionDependenciesResource; +import org.finos.legend.sdlc.server.resources.dependency.project.ProjectVersionDependenciesResource; +import org.finos.legend.sdlc.server.resources.dependency.project.group.GroupWorkspaceRevisionDependenciesResource; +import org.finos.legend.sdlc.server.resources.dependency.project.user.WorkspaceRevisionDependenciesResource; +import org.finos.legend.sdlc.server.resources.entity.VersionEntitiesResource; +import org.finos.legend.sdlc.server.resources.entity.VersionEntityPathsResource; +import org.finos.legend.sdlc.server.resources.entity.patch.PatchesProjectEntitiesResource; +import org.finos.legend.sdlc.server.resources.entity.patch.PatchesProjectEntityPathsResource; +import org.finos.legend.sdlc.server.resources.entity.patch.group.*; +import org.finos.legend.sdlc.server.resources.entity.patch.user.*; +import org.finos.legend.sdlc.server.resources.entity.project.ProjectEntitiesResource; +import org.finos.legend.sdlc.server.resources.entity.project.ProjectEntityPathsResource; +import org.finos.legend.sdlc.server.resources.entity.project.ProjectRevisionEntitiesResource; +import org.finos.legend.sdlc.server.resources.entity.project.ProjectRevisionEntityPathsResource; +import org.finos.legend.sdlc.server.resources.entity.project.group.*; +import org.finos.legend.sdlc.server.resources.entity.project.user.*; +import org.finos.legend.sdlc.server.resources.issue.IssuesResource; +import org.finos.legend.sdlc.server.resources.patch.PatchesResource; +import org.finos.legend.sdlc.server.resources.pmcd.VersionPureModelContextDataResource; +import org.finos.legend.sdlc.server.resources.pmcd.patch.PatchPureModelContextDataResource; +import org.finos.legend.sdlc.server.resources.pmcd.patch.PatchRevisionPureModelContextDataResource; +import org.finos.legend.sdlc.server.resources.pmcd.patch.group.PatchesGroupWorkspacePureModelContextDataResource; +import org.finos.legend.sdlc.server.resources.pmcd.patch.group.PatchesGroupWorkspaceRevisionPureModelContextDataResource; +import org.finos.legend.sdlc.server.resources.pmcd.patch.user.PatchesWorkspacePureModelContextDataResource; +import org.finos.legend.sdlc.server.resources.pmcd.project.ProjectPureModelContextDataResource; +import org.finos.legend.sdlc.server.resources.pmcd.project.group.GroupWorkspacePureModelContextDataResource; +import org.finos.legend.sdlc.server.resources.pmcd.project.group.GroupWorkspaceRevisionPureModelContextDataResource; +import org.finos.legend.sdlc.server.resources.pmcd.project.user.WorkspacePureModelContextDataResource; +import org.finos.legend.sdlc.server.resources.pmcd.project.user.WorkspaceRevisionPureModelContextDataResource; +import org.finos.legend.sdlc.server.resources.project.ConfigurationResource; +import org.finos.legend.sdlc.server.resources.project.VersionProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.project.patch.PatchProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.project.patch.group.PatchesGroupWorkspaceProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.project.patch.group.PatchesGroupWorkspaceRevisionProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.project.patch.user.PatchesWorkspaceProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.project.patch.user.PatchesWorkspaceRevisionProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.project.project.ProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.project.project.ProjectRevisionProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.project.project.ProjectRevisionPureModelContextDataResource; +import org.finos.legend.sdlc.server.resources.project.project.ProjectsResource; +import org.finos.legend.sdlc.server.resources.project.project.group.GroupWorkspaceProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.project.project.group.GroupWorkspaceRevisionProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.project.project.user.WorkspaceProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.project.project.user.WorkspaceRevisionProjectConfigurationResource; +import org.finos.legend.sdlc.server.resources.review.ReviewsOnlyResource; +import org.finos.legend.sdlc.server.resources.review.patch.PatchReviewsResource; +import org.finos.legend.sdlc.server.resources.review.project.ReviewsResource; +import org.finos.legend.sdlc.server.resources.revision.patch.PatchEntityRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.patch.PatchPackageRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.patch.PatchRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.patch.group.PatchesGroupWorkspaceEntityRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.patch.group.PatchesGroupWorkspacePackageRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.patch.group.PatchesGroupWorkspaceRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.patch.user.PatchesWorkspaceRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.project.ProjectEntityRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.project.ProjectPackageRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.project.ProjectRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.project.group.GroupWorkspaceEntityRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.project.group.GroupWorkspacePackageRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.project.group.GroupWorkspaceRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.project.user.WorkspaceEntityRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.project.user.WorkspacePackageRevisionsResource; +import org.finos.legend.sdlc.server.resources.revision.project.user.WorkspaceRevisionsResource; +import org.finos.legend.sdlc.server.resources.user.CurrentUserResource; +import org.finos.legend.sdlc.server.resources.user.UsersResource; +import org.finos.legend.sdlc.server.resources.version.VersionsResource; +import org.finos.legend.sdlc.server.resources.workflow.ReviewWorkflowJobsResource; +import org.finos.legend.sdlc.server.resources.workflow.ReviewWorkflowsResource; +import org.finos.legend.sdlc.server.resources.workflow.VersionWorkflowJobsResource; +import org.finos.legend.sdlc.server.resources.workflow.VersionWorkflowsResource; +import org.finos.legend.sdlc.server.resources.workflow.patch.PatchReviewWorkflowJobsResource; +import org.finos.legend.sdlc.server.resources.workflow.patch.PatchReviewWorkflowsResource; +import org.finos.legend.sdlc.server.resources.workflow.patch.PatchWorkflowJobsResource; +import org.finos.legend.sdlc.server.resources.workflow.patch.PatchWorkflowsResource; +import org.finos.legend.sdlc.server.resources.workflow.patch.group.PatchesGroupWorkspaceWorkflowJobsResource; +import org.finos.legend.sdlc.server.resources.workflow.patch.group.PatchesGroupWorkspaceWorkflowsResource; +import org.finos.legend.sdlc.server.resources.workflow.patch.user.PatchesWorkspaceWorkflowJobsResource; +import org.finos.legend.sdlc.server.resources.workflow.patch.user.PatchesWorkspaceWorkflowsResource; +import org.finos.legend.sdlc.server.resources.workflow.project.ProjectWorkflowJobsResource; +import org.finos.legend.sdlc.server.resources.workflow.project.ProjectWorkflowsResource; +import org.finos.legend.sdlc.server.resources.workflow.project.group.GroupWorkspaceWorkflowJobsResource; +import org.finos.legend.sdlc.server.resources.workflow.project.group.GroupWorkspaceWorkflowsResource; +import org.finos.legend.sdlc.server.resources.workflow.project.user.WorkspaceWorkflowJobsResource; +import org.finos.legend.sdlc.server.resources.workflow.project.user.WorkspaceWorkflowsResource; +import org.finos.legend.sdlc.server.resources.workspace.patch.group.PatchesGroupWorkspacesResource; +import org.finos.legend.sdlc.server.resources.workspace.patch.user.PatchesWorkspacesResource; +import org.finos.legend.sdlc.server.resources.workspace.project.group.GroupWorkspacesResource; +import org.finos.legend.sdlc.server.resources.workspace.project.user.WorkspacesResource; +import org.finos.legend.sdlc.server.tools.BackgroundTaskProcessor; + +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class FSModule extends DropwizardAwareModule +{ + protected final LegendSDLCServerFS server; + protected ProjectStructureExtensionProvider extensionProvider; + private AuthClientInjector authClientInjector; + + public FSModule(LegendSDLCServerFS server) + { + this.server = server; + } + + @Override + public void configure(Binder binder) + { + configureCommonApis(binder); + configureApis(binder); + initRootDirectory(getFSConfiguration()); + + binder.bind(UserContext.class); + binder.bind(TestModelBuilder.class); + binder.bind(FSConfiguration.class).toProvider(this::getFSConfiguration); + binder.bind(ProjectStructureConfiguration.class).toProvider(this::getProjectStructureConfiguration); + binder.bind(ProjectStructureExtensionProvider.class).toProvider(this::getProjectStructureExtensionProvider); + binder.bind(DepotConfiguration.class).toProvider(this::getDepotConfiguration); + binder.bind(AuthClientInjector.class).toProvider(this::getAuthClientInjector); + binder.bind(BaseServer.ServerInfo.class).toProvider(this.server::getServerInfo); + binder.bind(LegendSDLCServerFeaturesConfiguration.class).toProvider(this::getFeaturesConfiguration); + binder.bind(BackgroundTaskProcessor.class).toProvider(this.server::getBackgroundTaskProcessor); + binder.bind(ProjectStructurePlatformExtensions.class).toInstance(buildProjectStructurePlatformExtensions()); + + bindResources(binder); + } + + private void bindResources(Binder binder) + { + binder.bind(InfoResource.class); + binder.bind(ServerResource.class); + binder.bind(ProjectsResource.class); + binder.bind(WorkspacesResource.class); + binder.bind(ProjectConfigurationResource.class); + binder.bind(ConfigurationResource.class); + binder.bind(ProjectRevisionProjectConfigurationResource.class); + binder.bind(WorkspaceProjectConfigurationResource.class); + binder.bind(WorkspaceRevisionProjectConfigurationResource.class); + binder.bind(ProjectEntitiesResource.class); + binder.bind(ProjectPureModelContextDataResource.class); + binder.bind(ProjectEntityPathsResource.class); + binder.bind(ProjectRevisionEntitiesResource.class); + binder.bind(ProjectRevisionPureModelContextDataResource.class); + binder.bind(ProjectRevisionEntityPathsResource.class); + binder.bind(WorkspaceEntitiesResource.class); + binder.bind(WorkspacePureModelContextDataResource.class); + binder.bind(WorkspaceEntityPathsResource.class); + binder.bind(WorkspaceEntityChangesResource.class); + binder.bind(WorkspaceRevisionEntitiesResource.class); + binder.bind(WorkspaceRevisionPureModelContextDataResource.class); + binder.bind(WorkspaceRevisionEntityPathsResource.class); + binder.bind(VersionEntitiesResource.class); + binder.bind(VersionPureModelContextDataResource.class); + binder.bind(VersionEntityPathsResource.class); + binder.bind(VersionProjectConfigurationResource.class); + binder.bind(ProjectRevisionsResource.class); + binder.bind(ProjectEntityRevisionsResource.class); + binder.bind(ProjectPackageRevisionsResource.class); + binder.bind(WorkspaceRevisionsResource.class); + binder.bind(WorkspaceEntityRevisionsResource.class); + binder.bind(WorkspacePackageRevisionsResource.class); + binder.bind(IssuesResource.class); + binder.bind(UsersResource.class); + binder.bind(CurrentUserResource.class); + binder.bind(ReviewsOnlyResource.class); + binder.bind(ReviewsResource.class); + binder.bind(ProjectBuildsResource.class); + binder.bind(WorkspaceBuildsResource.class); + binder.bind(VersionBuildsResource.class); + binder.bind(VersionsResource.class); + binder.bind(ComparisonWorkspaceResource.class); + binder.bind(ComparisonReviewResource.class); + binder.bind(ComparisonReviewEntitiesResource.class); + binder.bind(ComparisonReviewProjectConfigurationResource.class); + binder.bind(ConflictResolutionProjectResource.class); + binder.bind(ConflictResolutionWorkspaceResource.class); + binder.bind(ConflictResolutionWorkspaceEntitiesResource.class); + binder.bind(ConflictResolutionWorkspaceEntityPathsResource.class); + binder.bind(ConflictResolutionWorkspaceRevisionsResource.class); + binder.bind(ConflictResolutionWorkspaceRevisionEntitiesResource.class); + binder.bind(ConflictResolutionWorkspaceRevisionEntityPathsResource.class); + binder.bind(ConflictResolutionWorkspaceProjectConfigurationResource.class); + binder.bind(ConflictResolutionWorkspaceRevisionProjectConfigurationResource.class); + binder.bind(BackupProjectResource.class); + binder.bind(BackupWorkspaceResource.class); + binder.bind(BackupWorkspaceEntitiesResource.class); + binder.bind(BackupWorkspaceEntityPathsResource.class); + binder.bind(BackupWorkspaceRevisionsResource.class); + binder.bind(BackupWorkspaceRevisionEntitiesResource.class); + binder.bind(BackupWorkspaceRevisionEntityPathsResource.class); + binder.bind(BackupWorkspaceProjectConfigurationResource.class); + binder.bind(BackupWorkspaceRevisionProjectConfigurationResource.class); + binder.bind(DownstreamDependenciesResource.class); + binder.bind(ProjectRevisionDependenciesResource.class); + binder.bind(ProjectVersionDependenciesResource.class); + binder.bind(WorkspaceRevisionDependenciesResource.class); + binder.bind(ProjectWorkflowsResource.class); + binder.bind(VersionWorkflowsResource.class); + binder.bind(WorkspaceWorkflowsResource.class); + binder.bind(ReviewWorkflowsResource.class); + binder.bind(ProjectWorkflowJobsResource.class); + binder.bind(VersionWorkflowJobsResource.class); + binder.bind(WorkspaceWorkflowJobsResource.class); + binder.bind(ReviewWorkflowJobsResource.class); + binder.bind(GroupBackupWorkspaceEntitiesResource.class); + binder.bind(GroupBackupWorkspaceEntityPathsResource.class); + binder.bind(GroupBackupWorkspaceProjectConfigurationResource.class); + binder.bind(GroupBackupWorkspaceResource.class); + binder.bind(GroupBackupWorkspaceRevisionEntitiesResource.class); + binder.bind(GroupBackupWorkspaceRevisionEntityPathsResource.class); + binder.bind(GroupBackupWorkspaceRevisionProjectConfigurationResource.class); + binder.bind(GroupBackupWorkspaceRevisionsResource.class); + binder.bind(GroupComparisonWorkspaceResource.class); + binder.bind(GroupConflictResolutionWorkspaceEntitiesResource.class); + binder.bind(GroupConflictResolutionWorkspaceEntityPathsResource.class); + binder.bind(GroupConflictResolutionWorkspaceProjectConfigurationResource.class); + binder.bind(GroupConflictResolutionWorkspaceResource.class); + binder.bind(GroupConflictResolutionWorkspaceRevisionEntitiesResource.class); + binder.bind(GroupConflictResolutionWorkspaceRevisionEntityPathsResource.class); + binder.bind(GroupConflictResolutionWorkspaceRevisionProjectConfigurationResource.class); + binder.bind(GroupConflictResolutionWorkspaceRevisionsResource.class); + binder.bind(GroupWorkspaceEntitiesResource.class); + binder.bind(GroupWorkspacePureModelContextDataResource.class); + binder.bind(GroupWorkspaceEntityChangesResource.class); + binder.bind(GroupWorkspaceEntityPathsResource.class); + binder.bind(GroupWorkspaceEntityRevisionsResource.class); + binder.bind(GroupWorkspacePackageRevisionsResource.class); + binder.bind(GroupWorkspaceProjectConfigurationResource.class); + binder.bind(GroupWorkspaceRevisionDependenciesResource.class); + binder.bind(GroupWorkspaceRevisionEntitiesResource.class); + binder.bind(GroupWorkspaceRevisionPureModelContextDataResource.class); + binder.bind(GroupWorkspaceRevisionEntityPathsResource.class); + binder.bind(GroupWorkspaceRevisionProjectConfigurationResource.class); + binder.bind(GroupWorkspaceRevisionsResource.class); + binder.bind(GroupWorkspacesResource.class); + binder.bind(GroupWorkspaceWorkflowJobsResource.class); + binder.bind(GroupWorkspaceWorkflowsResource.class); + binder.bind(PatchProjectConfigurationResource.class); + binder.bind(PatchesResource.class); + binder.bind(PatchesWorkspacesResource.class); + binder.bind(PatchesGroupWorkspacesResource.class); + binder.bind(PatchesGroupWorkspaceProjectConfigurationResource.class); + binder.bind(PatchesProjectEntitiesResource.class); + binder.bind(PatchesProjectEntityPathsResource.class); + binder.bind(PatchesGroupWorkspaceEntitiesResource.class); + binder.bind(PatchesWorkspaceEntitiesResource.class); + binder.bind(PatchRevisionDependenciesResource.class); + binder.bind(PatchesGroupWorkspaceRevisionDependenciesResource.class); + binder.bind(PatchesWorkspaceRevisionDependenciesResource.class); + binder.bind(ConflictResolutionPatchResource.class); + binder.bind(ConflictResolutionPatchesWorkspaceEntitiesResource.class); + binder.bind(ConflictResolutionPatchesWorkspaceEntityPathsResource.class); + binder.bind(ConflictResolutionPatchesWorkspaceProjectConfigurationResource.class); + binder.bind(ConflictResolutionPatchesWorkspaceResource.class); + binder.bind(ConflictResolutionPatchesWorkspaceRevisionEntitiesResource.class); + binder.bind(ConflictResolutionPatchesWorkspaceRevisionEntityPathsResource.class); + binder.bind(ConflictResolutionPatchesWorkspaceRevisionProjectConfigurationResource.class); + binder.bind(ConflictResolutionPatchesWorkspaceRevisionsResource.class); + binder.bind(ConflictResolutionPatchesGroupWorkspaceEntitiesResource.class); + binder.bind(ConflictResolutionPatchesGroupWorkspaceEntityPathsResource.class); + binder.bind(ConflictResolutionPatchesGroupWorkspaceProjectConfigurationResource.class); + binder.bind(ConflictResolutionPatchesGroupWorkspaceResource.class); + binder.bind(ConflictResolutionPatchesGroupWorkspaceRevisionEntitiesResource.class); + binder.bind(ConflictResolutionPatchesGroupWorkspaceRevisionEntityPathsResource.class); + binder.bind(ConflictResolutionPatchesGroupWorkspaceRevisionProjectConfigurationResource.class); + binder.bind(ConflictResolutionPatchesGroupWorkspaceRevisionsResource.class); + binder.bind(PatchRevisionsResource.class); + binder.bind(PatchPackageRevisionsResource.class); + binder.bind(PatchEntityRevisionsResource.class); + binder.bind(PatchesGroupWorkspaceEntityRevisionsResource.class); + binder.bind(PatchesGroupWorkspaceRevisionsResource.class); + binder.bind(PatchesGroupWorkspacePackageRevisionsResource.class); + binder.bind(BackupPatchesWorkspaceEntitiesResource.class); + binder.bind(BackupPatchesWorkspaceEntityPathsResource.class); + binder.bind(BackupPatchesWorkspaceProjectConfigurationResource.class); + binder.bind(BackupPatchesWorkspaceResource.class); + binder.bind(BackupPatchesWorkspaceRevisionEntitiesResource.class); + binder.bind(BackupPatchesWorkspaceRevisionEntityPathsResource.class); + binder.bind(BackupPatchesWorkspaceRevisionProjectConfigurationResource.class); + binder.bind(BackupPatchesWorkspaceRevisionsResource.class); + binder.bind(BackupPatchesGroupWorkspaceEntitiesResource.class); + binder.bind(BackupPatchesGroupWorkspaceEntityPathsResource.class); + binder.bind(BackupPatchesGroupWorkspaceProjectConfigurationResource.class); + binder.bind(BackupPatchesGroupWorkspaceResource.class); + binder.bind(BackupPatchesGroupWorkspaceRevisionEntitiesResource.class); + binder.bind(BackupPatchesGroupWorkspaceRevisionEntityPathsResource.class); + binder.bind(BackupPatchesGroupWorkspaceRevisionProjectConfigurationResource.class); + binder.bind(BackupPatchesGroupWorkspaceRevisionsResource.class); + binder.bind(PatchesGroupWorkspaceRevisionPureModelContextDataResource.class); + binder.bind(PatchesGroupWorkspacePureModelContextDataResource.class); + binder.bind(PatchPureModelContextDataResource.class); + binder.bind(PatchRevisionPureModelContextDataResource.class); + binder.bind(PatchesWorkspacePureModelContextDataResource.class); + binder.bind(PatchReviewsResource.class); + binder.bind(ComparisonPatchReviewEntitiesResource.class); + binder.bind(ComparisonPatchReviewProjectConfigurationResource.class); + binder.bind(ComparisonPatchReviewResource.class); + binder.bind(ComparisonPatchesWorkspaceResource.class); + binder.bind(PatchesGroupComparisonWorkspaceResource.class); + binder.bind(PatchesGroupWorkspaceWorkflowJobsResource.class); + binder.bind(PatchesGroupWorkspaceWorkflowsResource.class); + binder.bind(PatchesWorkspaceWorkflowsResource.class); + binder.bind(PatchesWorkspaceWorkflowJobsResource.class); + binder.bind(PatchWorkflowJobsResource.class); + binder.bind(PatchWorkflowsResource.class); + binder.bind(PatchReviewWorkflowJobsResource.class); + binder.bind(PatchReviewWorkflowsResource.class); + binder.bind(PatchesGroupWorkspaceEntityPathsResource.class); + binder.bind(PatchesGroupWorkspaceEntityChangesResource.class); + binder.bind(PatchesGroupWorkspaceRevisionEntitiesResource.class); + binder.bind(PatchesGroupWorkspaceRevisionEntityPathsResource.class); + binder.bind(PatchesWorkspaceEntityChangesResource.class); + binder.bind(PatchesWorkspaceEntityPathsResource.class); + binder.bind(PatchesWorkspaceRevisionEntitiesResource.class); + binder.bind(PatchesWorkspaceRevisionEntityPathsResource.class); + binder.bind(PatchesWorkspaceRevisionsResource.class); + binder.bind(PatchesGroupWorkspaceRevisionProjectConfigurationResource.class); + binder.bind(PatchesWorkspaceProjectConfigurationResource.class); + binder.bind(PatchesWorkspaceRevisionProjectConfigurationResource.class); + } + + public void initRootDirectory(FSConfiguration fsConfiguration) + { + // Check if rootDirectory exists, and create if not + File localFile = new File(fsConfiguration.getRootDirectory()); + if (!localFile.exists() && !localFile.mkdirs()) + { + throw new RuntimeException("Failed to create directories for rootDirectory"); + } + } + + private void configureCommonApis(Binder binder) + { + binder.bind(DependenciesApi.class).to(DependenciesApiImpl.class); + } + + protected void configureApis(Binder binder) + { + configureLegendApis(binder); + } + + public void configureLegendApis(Binder binder) + { + binder.bind(MetadataApi.class).to(FileSystemMetadataApi.class); + binder.bind(ProjectApi.class).to(FileSystemProjectApi.class); + binder.bind(ProjectConfigurationApi.class).to(FileSystemProjectConfigurationApi.class); + binder.bind(UserApi.class).to(FileSystemUserApi.class); + binder.bind(IssueApi.class).to(FileSystemIssueApi.class); + binder.bind(EntityApi.class).to(FileSystemEntityApi.class); + binder.bind(WorkspaceApi.class).to(FileSystemWorkspaceApi.class); + binder.bind(RevisionApi.class).to(FileSystemRevisionApi.class); + binder.bind(ReviewApi.class).to(FileSystemReviewApi.class); + binder.bind(BuildApi.class).to(FileSystemBuildApi.class); + binder.bind(VersionApi.class).to(FileSystemVersionApi.class); + binder.bind(ComparisonApi.class).to(FileSystemComparisonApi.class); + binder.bind(ConflictResolutionApi.class).to(FileSystemConflictResolutionApi.class); + binder.bind(BackupApi.class).to(FileSystemBackupApi.class); + binder.bind(WorkflowApi.class).to(FileSystemWorkflowApi.class); + binder.bind(WorkflowJobApi.class).to(FileSystemWorkflowJobApi.class); + binder.bind(PatchApi.class).to(FileSystemPatchApi.class); + binder.bind(FileSystemAuthCheckResource.class); + binder.bind(FileSystemAuthResource.class); + } + + private FSConfiguration getFSConfiguration() + { + return getConfiguration().getFileSystemConfiguration(); + } + + private ProjectStructureConfiguration getProjectStructureConfiguration() + { + ProjectStructureConfiguration projectStructureConfiguration = getConfiguration().getProjectStructureConfiguration(); + return (projectStructureConfiguration == null) ? ProjectStructureConfiguration.emptyConfiguration() : projectStructureConfiguration; + } + + private ProjectStructurePlatformExtensions buildProjectStructurePlatformExtensions() + { + return Optional.ofNullable(getConfiguration().getProjectStructureConfiguration()) + .map(ProjectStructureConfiguration::getProjectPlatformsConfiguration) + .map(ProjectPlatformsConfiguration::buildProjectStructurePlatformExtensions) + .orElseGet(() -> ProjectStructurePlatformExtensions.newPlatformExtensions(Collections.emptyList(), Collections.emptyList())); + } + + private ProjectStructureExtensionProvider getProjectStructureExtensionProvider() + { + if (this.extensionProvider == null) + { + this.extensionProvider = resolveProjectStructureExtensionProvider(); + } + return this.extensionProvider; + } + + private ProjectStructureExtensionProvider resolveProjectStructureExtensionProvider() + { + ProjectStructureConfiguration projectStructureConfiguration = getConfiguration().getProjectStructureConfiguration(); + if (projectStructureConfiguration != null) + { + ProjectStructureExtensionProvider configuredProvider = projectStructureConfiguration.getProjectStructureExtensionProvider(); + if (configuredProvider != null) + { + return configuredProvider; + } + List extensions = projectStructureConfiguration.getProjectStructureExtensions(); + if ((extensions != null) && !extensions.isEmpty()) + { + return DefaultProjectStructureExtensionProvider.fromExtensions(extensions); + } + } + return new VoidProjectStructureExtensionProvider(); + } + + private DepotConfiguration getDepotConfiguration() + { + DepotConfiguration depotConfiguration = getConfiguration().getDepotConfiguration(); + return (depotConfiguration == null) ? DepotConfiguration.emptyConfiguration() : depotConfiguration; + } + + private LegendSDLCServerFeaturesConfiguration getFeaturesConfiguration() + { + LegendSDLCServerFeaturesConfiguration featuresConfiguration = getConfiguration().getFeaturesConfiguration(); + return (featuresConfiguration == null) ? LegendSDLCServerFeaturesConfiguration.emptyConfiguration() : featuresConfiguration; + } + + private AuthClientInjector getAuthClientInjector() + { + if (this.authClientInjector == null) + { + this.authClientInjector = resolveAuthClientInjector(); + } + return this.authClientInjector; + } + + private AuthClientInjector resolveAuthClientInjector() + { + DepotConfiguration depotConfiguration = getConfiguration().getDepotConfiguration(); + if (depotConfiguration != null) + { + AuthClientInjector authClientInjector = depotConfiguration.getAuthClientInjector(); + if (authClientInjector != null) + { + return authClientInjector; + } + } + return builder -> builder; + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFS.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFS.java new file mode 100644 index 0000000000..e39692f3ec --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFS.java @@ -0,0 +1,128 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.startup; + +import com.hubspot.dropwizard.guicier.GuiceBundle; +import io.dropwizard.lifecycle.Managed; +import io.dropwizard.lifecycle.setup.LifecycleEnvironment; +import io.dropwizard.setup.Bootstrap; +import io.dropwizard.setup.Environment; +import org.finos.legend.engine.protocol.pure.v1.PureProtocolObjectMapperFactory; +import org.finos.legend.sdlc.server.BaseServer; +import org.finos.legend.sdlc.server.depot.DepotConfiguration; +import org.finos.legend.sdlc.server.project.config.ProjectStructureConfiguration; +import org.finos.legend.sdlc.server.tools.BackgroundTaskProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +public class LegendSDLCServerFS extends BaseServer +{ + private static final Logger LOGGER = LoggerFactory.getLogger(LegendSDLCServerFS.class); + + private BackgroundTaskProcessor backgroundTaskProcessor; + + public LegendSDLCServerFS() + { + } + + @Override + public String getName() + { + return "Metadata SDLC"; + } + + @Override + public void initialize(Bootstrap bootstrap) + { + super.initialize(bootstrap); + + configureApis(bootstrap); + + // SDLC specific initialization + ProjectStructureConfiguration.configureObjectMapper(bootstrap.getObjectMapper()); + DepotConfiguration.configureObjectMapper(bootstrap.getObjectMapper()); + PureProtocolObjectMapperFactory.withPureProtocolExtensions(bootstrap.getObjectMapper()); + } + + protected void configureApis(Bootstrap bootstrap) + { + // Guice bootstrapping.. + bootstrap.addBundle(buildGuiceBundle()); + } + + protected GuiceBundle buildGuiceBundle() + { + return GuiceBundle.defaultBuilder(LegendSDLCServerFSConfiguration.class) + .modules(buildBaseModule()) + .build(); + } + + protected FSModule buildBaseModule() + { + return new FSModule(this); + } + + @Override + public void run(T configuration, Environment environment) + { + super.run(configuration, environment); + LifecycleEnvironment lifecycleEnvironment = environment.lifecycle(); + LOGGER.debug("Creating background task processor"); + BackgroundTaskProcessor taskProcessor = new BackgroundTaskProcessor(1); + lifecycleEnvironment.manage(new Managed() + { + @Override + public void start() + { + // nothing to do + } + + @Override + public void stop() throws Exception + { + LOGGER.debug("Shutting down background task processor"); + taskProcessor.shutdown(); + if (taskProcessor.awaitTermination(30, TimeUnit.SECONDS)) + { + LOGGER.debug("Done shutting down background task processor"); + } + else + { + LOGGER.debug("Background task processor did not terminate within the timeout"); + } + } + }); + this.backgroundTaskProcessor = taskProcessor; + } + + public BackgroundTaskProcessor getBackgroundTaskProcessor() + { + return this.backgroundTaskProcessor; + } + + @Override + protected ServerPlatformInfo newServerPlatformInfo() + { + return new ServerPlatformInfo(null, null, null); + } + + public static void main(String... args) throws Exception + { + LOGGER.info("Starting SDLC server with File System as backend"); + new LegendSDLCServerFS().run(args); + } +} diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFSConfiguration.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFSConfiguration.java new file mode 100644 index 0000000000..1c5efeb014 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFSConfiguration.java @@ -0,0 +1,29 @@ +// Copyright 2023 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.sdlc.server.startup; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.finos.legend.sdlc.server.config.LegendSDLCServerConfiguration; + +public class LegendSDLCServerFSConfiguration extends LegendSDLCServerConfiguration +{ + @JsonProperty("fileSystem") + private FSConfiguration fileSystemConfiguration; + + public FSConfiguration getFileSystemConfiguration() + { + return this.fileSystemConfiguration; + } +} diff --git a/legend-sdlc-server-fs/src/main/resources/docker/config/config.json b/legend-sdlc-server-fs/src/main/resources/docker/config/config.json new file mode 100644 index 0000000000..b3b63fa761 --- /dev/null +++ b/legend-sdlc-server-fs/src/main/resources/docker/config/config.json @@ -0,0 +1,120 @@ +{ + "applicationName": "Legend SDLC", + "sessionCookie": "LEGEND_SDLC_JSESSIONID", + "server": { + "adminContextPath": "/admin", + "applicationConnectors": [ + { + "type": "http", + "port": ${SDLC_PORT}, + "maxRequestHeaderSize": "128KiB" + } + ], + "adminConnectors": [ + { + "type": "http", + "port": ${SDLC_ADMIN_PORT} + } + ], + "gzip": { + "includedMethods": [ + "GET", + "POST" + ] + }, + "requestLog": { + "type": "classic", + "level": "OFF", + "appenders": [ + { + "type": "console", + "logFormat": "OFF" + } + ] + }, + "rootPath": "/api" + }, + "projectStructure": { + "projectCreation": { + }, + "extensionProvider": { + "org.finos.legend.sdlc.server.gitlab.finos.FinosGitlabProjectStructureExtensionProvider": {} + }, + "platforms": { + "legend-engine": { + "groupId": "org.finos.legend.engine", + "platformVersion": { + "version": "${ENGINE_MAVEN_VERSION}" + } + }, + "legend-sdlc": { + "groupId": "org.finos.legend.sdlc", + "platformVersion":{ + "version": "${SDLC_MAVEN_VERSION}" + } + } + } + }, + "filterPriorities": { + "GitLab": 1, + "org.pac4j.j2e.filter.CallbackFilter": 2, + "org.pac4j.j2e.filter.SecurityFilter": 3, + "CORS": 4 + }, + "pac4j": { + "callbackPrefix": "/api/pac4j/login", + "clients": [ + { + "org.finos.legend.server.pac4j.gitlab.GitlabClient": { + "name": "gitlab", + "clientId": "${GITLAB_APP_ID}", + "secret": "${GITLAB_APP_SECRET}", + "discoveryUri": "https://${GITLAB_HOST}/.well-known/openid-configuration", + "scope": "openid profile api" + } + } + ], + "mongoSession": { + "enabled": "${MONGO_SESSION_ENABLED}", + "collection": "userSessions" + }, + "bypassPaths": [ + "/api/info" + ] + }, + "fileSystem": { + "rootDirectory": ${FS_ROOT_DIR} + }, + "features": { + "canCreateProject": true, + "canCreateVersion": true + }, + "gitLab": { + "newProjectVisibility": "public", + "projectTag": "${SDLC_PROJECT_TAG}", + "server": { + "scheme": "https", + "host": "${GITLAB_HOST}" + }, + "app": { + "id": "${GITLAB_APP_ID}", + "secret": "${GITLAB_APP_SECRET}", + "redirectURI": "${SDLC_REDIRECT_URI}" + } + }, + "logging": { + "level": "INFO", + "appenders": [ + { + "type": "console", + "logFormat": "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%thread] %c - %m%n" + } + ] + }, + "swagger": { + "resourcePackage": "org.finos.legend.sdlc.server.resources", + "title": "Legend LEGEND_SDLC", + "version": "local-snapshot", + "schemes": [] + } +} \ No newline at end of file diff --git a/legend-sdlc-server-fs/src/test/resources/config.yml b/legend-sdlc-server-fs/src/test/resources/config.yml new file mode 100644 index 0000000000..391f6cf34c --- /dev/null +++ b/legend-sdlc-server-fs/src/test/resources/config.yml @@ -0,0 +1,90 @@ +# Copyright 2023 Goldman Sachs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +applicationName: Legend SDLC + +sessionCookie: LEGEND_SDLC_JSESSIONID + +cors: + allowedHeaders: + - X-Requested-With + - Content-Type + - Accept + - Origin + - Access-Control-Allow-Credentials + - x-b3-parentspanid + - x-b3-sampled + - x-b3-spanid + - x-b3-traceid + - legend-test-pat + +server: + applicationConnectors: + - type: http + port: 3000 + maxRequestHeaderSize: 128KiB + adminConnectors: + - type: http + port: 8889 + gzip: + includedMethods: + - GET + - POST + requestLog: + type: classic + level: OFF + appenders: + - type: console + logFormat: "OFF" + rootPath: /api + +filterPriorities: + GitLab: 1 + org.pac4j.j2e.filter.CallbackFilter: 2 + org.pac4j.j2e.filter.SecurityFilter: 3 + CORS: 4 + +pac4j: + clients: + - org.pac4j.core.client.direct.AnonymousClient: {} + bypassPaths: + - /api/info + - /api/server/info + - /api/server/platforms + - /api/auth/authorized + +fileSystem: + rootDirectory: /root/AlloyProjects + +features: + canCreateProject: true + canCreateVersion: false + +projectStructure: + projectCreation: + groupIdPattern: ^org\.finos\.legend\..+ + +logging: + # Change this to affect library class logging + level: INFO + appenders: + - type: console + logFormat: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%thread] %c - %m%n" + +swagger: + resourcePackage: org.finos.legend.sdlc.server.resources + title: Legend SDLC + version: local-snapshot + schemes: [] \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7ba333bb8d..f2136170d7 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ legend-sdlc-language-pure-compiler legend-sdlc-server-shared legend-sdlc-server + legend-sdlc-server-fs legend-sdlc-generation-shared legend-sdlc-generation-file legend-sdlc-generation-service @@ -541,6 +542,11 @@ test-jar test + + org.finos.legend.sdlc + legend-sdlc-server-fs + ${project.version} +