This is an automated email from the ASF dual-hosted git repository. marat pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
commit ec37f5fa1337f3fc7407a4e1afc3cda04c97b4ff Author: Marat Gubaidullin <marat.gubaidul...@gmail.com> AuthorDate: Wed Mar 1 16:44:40 2023 -0500 Fix #645 --- karavan-app/pom.xml | 17 +- .../{GitResource.java => ProjectGitResource.java} | 21 +- .../org/apache/camel/karavan/model/CommitInfo.java | 39 ++++ .../apache/camel/karavan/service/GitService.java | 214 ++++++++++++++++----- .../camel/karavan/service/InfinispanService.java | 22 ++- .../camel/karavan/service/KaravanService.java | 5 +- .../camel/karavan/service/KubernetesService.java | 4 +- .../{ImportService.java => ProjectService.java} | 139 +++++++------ .../apache/camel/karavan/service/ServiceUtil.java | 66 +++++++ .../src/main/resources/application.properties | 6 +- karavan-app/src/main/webui/src/api/KaravanApi.tsx | 12 +- .../main/webui/src/projects/ProjectPageToolbar.tsx | 7 +- 12 files changed, 403 insertions(+), 149 deletions(-) diff --git a/karavan-app/pom.xml b/karavan-app/pom.xml index 402b8cde..151cfb34 100644 --- a/karavan-app/pom.xml +++ b/karavan-app/pom.xml @@ -30,8 +30,9 @@ <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id> <quarkus.platform.version>2.16.0.Final</quarkus.platform.version> - <surefire-plugin.version>3.0.0-M5</surefire-plugin.version> + <camel-quarkus.version>2.16.0</camel-quarkus.version> <camel.version>3.20.2</camel.version> + <surefire-plugin.version>3.0.0-M5</surefire-plugin.version> <infinispan.version>14.0.5.Final</infinispan.version> <tekton.version>6.2.0</tekton.version> <jgit.version>2.3.1</jgit.version> @@ -114,6 +115,10 @@ <groupId>io.quarkus</groupId> <artifactId>quarkus-qute</artifactId> </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-scheduler</artifactId> + </dependency> <dependency> <groupId>io.fabric8</groupId> <artifactId>tekton-client</artifactId> @@ -152,11 +157,11 @@ <artifactId>rest-assured</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>io.quarkiverse.quinoa</groupId> - <artifactId>quarkus-quinoa</artifactId> - <version>${quinoa.version}</version> - </dependency> +<!-- <dependency>--> +<!-- <groupId>io.quarkiverse.quinoa</groupId>--> +<!-- <artifactId>quarkus-quinoa</artifactId>--> +<!-- <version>${quinoa.version}</version>--> +<!-- </dependency>--> </dependencies> <build> <resources> diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectGitResource.java similarity index 72% rename from karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java rename to karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectGitResource.java index 3e9de9c2..f2d0196f 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectGitResource.java @@ -16,12 +16,8 @@ */ package org.apache.camel.karavan.api; -import org.apache.camel.karavan.model.GitRepo; import org.apache.camel.karavan.model.Project; -import org.apache.camel.karavan.model.ProjectFile; -import org.apache.camel.karavan.service.GitService; -import org.apache.camel.karavan.service.ImportService; -import org.jboss.logging.Logger; +import org.apache.camel.karavan.service.ProjectService; import javax.inject.Inject; import javax.ws.rs.Consumes; @@ -33,24 +29,17 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import java.util.HashMap; - @Path("/api/git") -public class GitResource { - - @Inject - GitService gitService; +public class ProjectGitResource { @Inject - ImportService importService; - - private static final Logger LOGGER = Logger.getLogger(GitResource.class.getName()); - + ProjectService projectService; @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Project push(HashMap<String, String> params) throws Exception { - return gitService.commitAndPushProject(params.get("projectId"), params.get("message")); + return projectService.commitAndPushProject(params.get("projectId"), params.get("message")); } @GET @@ -58,6 +47,6 @@ public class GitResource { @Consumes(MediaType.APPLICATION_JSON) @Path("/{projectId}") public Project pull(@PathParam("projectId") String projectId) throws Exception { - return importService.importProject(projectId); + return projectService.importProject(projectId); } } \ No newline at end of file diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/CommitInfo.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/CommitInfo.java new file mode 100644 index 00000000..2affc488 --- /dev/null +++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/CommitInfo.java @@ -0,0 +1,39 @@ +package org.apache.camel.karavan.model; + +import java.util.List; + +public class CommitInfo { + private String commitId; + private Integer time; + private List<GitRepo> repos; + + public CommitInfo(String commitId, Integer time, List<GitRepo> repos) { + this.commitId = commitId; + this.time = time; + this.repos = repos; + } + + public String getCommitId() { + return commitId; + } + + public void setCommitId(String commitId) { + this.commitId = commitId; + } + + public Integer getTime() { + return time; + } + + public void setTime(Integer time) { + this.time = time; + } + + public List<GitRepo> getRepos() { + return repos; + } + + public void setRepos(List<GitRepo> repos) { + this.repos = repos; + } +} diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java index 57765e4d..2b459861 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java @@ -17,27 +17,35 @@ package org.apache.camel.karavan.service; import io.fabric8.kubernetes.api.model.Secret; -import io.quarkus.runtime.StartupEvent; import io.smallrye.mutiny.tuples.Tuple2; import io.vertx.core.Vertx; +import org.apache.camel.karavan.model.CommitInfo; import org.apache.camel.karavan.model.GitConfig; import org.apache.camel.karavan.model.GitRepo; import org.apache.camel.karavan.model.GitRepoFile; import org.apache.camel.karavan.model.Project; import org.apache.camel.karavan.model.ProjectFile; -import org.eclipse.jgit.api.*; +import org.eclipse.jgit.api.CheckoutCommand; +import org.eclipse.jgit.api.CloneCommand; +import org.eclipse.jgit.api.FetchCommand; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.RemoteAddCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.RefNotFoundException; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.microprofile.config.ConfigProvider; import org.jboss.logging.Logger; import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.event.Observes; import javax.inject.Inject; import java.io.File; import java.io.IOException; @@ -46,16 +54,19 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.time.Instant; -import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; @ApplicationScoped public class GitService { @@ -66,25 +77,69 @@ public class GitService { @Inject KubernetesService kubernetesService; - @Inject - InfinispanService infinispanService; + private Git git; private static final Logger LOGGER = Logger.getLogger(GitService.class.getName()); - void onStart(@Observes StartupEvent ev) { - LOGGER.info("Git service for repo: " + getGitConfig().getUri()); + public Git getPollGit(){ + if (git == null) { + try { + git = getGit(true, vertx.fileSystem().createTempDirectoryBlocking("poll")); + } catch (Exception e) { + LOGGER.error("Error", e); + } + } + return git; + } + + public Map<String, Integer> getAllCommits() { + Map<String, Integer> result = new HashMap(); + try { + Git pollGit = getPollGit(); + if (pollGit != null) { + StreamSupport.stream(pollGit.log().all().call().spliterator(), false) + .sorted(Comparator.comparingInt(RevCommit::getCommitTime)) + .forEach(commit -> result.put(commit.getName(), commit.getCommitTime())); + } + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + return result; + } + + public List<CommitInfo> getCommitsAfterCommit(int commitTime) { + List<CommitInfo> result = new ArrayList<>(); + try { + Git pollGit = getPollGit(); + if (pollGit != null) { + GitConfig gitConfig = getGitConfig(); + CredentialsProvider cred = new UsernamePasswordCredentialsProvider(gitConfig.getUsername(), gitConfig.getPassword()); + fetch(pollGit, cred); + List<RevCommit> commits = StreamSupport.stream(pollGit.log().all().call().spliterator(), false) + .filter(commit -> commit.getCommitTime() > commitTime) + .sorted(Comparator.comparingInt(RevCommit::getCommitTime)).collect(Collectors.toList()); + for (RevCommit commit: commits) { + List<String> projects = new ArrayList<>(getChangedProjects(commit)); + List<GitRepo> repo = readProjectsFromRepository(pollGit, projects.toArray(new String[projects.size()])); + result.add(new CommitInfo(commit.getName(), commit.getCommitTime(), repo)); + } + } + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + return result; } - private GitConfig getGitConfig() { + public GitConfig getGitConfig() { String propertiesPrefix = "karavan."; String branch = ConfigProvider.getConfig().getValue(propertiesPrefix + "git-branch", String.class); - if (kubernetesService.inKubernetes()){ + if (kubernetesService.inKubernetes()) { LOGGER.info("inKubernetes " + kubernetesService.getNamespace()); - Secret secret = kubernetesService.getKaravanSecret(); + Secret secret = kubernetesService.getKaravanSecret(); String uri = new String(Base64.getDecoder().decode(secret.getData().get("git-repository").getBytes(StandardCharsets.UTF_8))); String username = new String(Base64.getDecoder().decode(secret.getData().get("git-username").getBytes(StandardCharsets.UTF_8))); String password = new String(Base64.getDecoder().decode(secret.getData().get("git-password").getBytes(StandardCharsets.UTF_8))); - if (secret.getData().containsKey("git-branch")){ + if (secret.getData().containsKey("git-branch")) { branch = new String(Base64.getDecoder().decode(secret.getData().get("git-branch").getBytes(StandardCharsets.UTF_8))); } return new GitConfig(uri, username, password, branch); @@ -96,18 +151,6 @@ public class GitService { } } - public Project commitAndPushProject(String projectId, String message) throws Exception { - Project p = infinispanService.getProject(projectId); - List<ProjectFile> files = infinispanService.getProjectFiles(projectId); - RevCommit commit = commitAndPushProject(p, files, message); - String commitId = commit.getId().getName(); - Long lastUpdate = commit.getCommitTime() * 1000L; - p.setLastCommit(commitId); - p.setLastCommitTimestamp(lastUpdate); - infinispanService.saveProject(p, false); - return p; - } - public RevCommit commitAndPushProject(Project project, List<ProjectFile> files, String message) throws GitAPIException, IOException, URISyntaxException { LOGGER.info("Commit and push project " + project.getProjectId()); GitConfig gitConfig = getGitConfig(); @@ -131,29 +174,35 @@ public class GitService { } public List<GitRepo> readProjectsFromRepository() { - return readProjectsFromRepository(null); + Git git = null; + try { + git = getGit(true, vertx.fileSystem().createTempDirectoryBlocking(UUID.randomUUID().toString())); + } catch (Exception e) { + LOGGER.error("Error", e); + } + return readProjectsFromRepository(git, null); } public GitRepo readProjectFromRepository(String projectId) { - return readProjectsFromRepository(projectId).get(0); + Git git = null; + try { + git = getGit(true, vertx.fileSystem().createTempDirectoryBlocking(UUID.randomUUID().toString())); + } catch (Exception e) { + LOGGER.error("Error", e); + } + return readProjectsFromRepository(git, projectId).get(0); } - private List<GitRepo> readProjectsFromRepository(String filter) { + private List<GitRepo> readProjectsFromRepository(Git git, String... filter) { LOGGER.info("Read projects..."); - GitConfig gitConfig = getGitConfig(); - LOGGER.info("Read projects from repository " + gitConfig.getUri()); - CredentialsProvider cred = new UsernamePasswordCredentialsProvider(gitConfig.getUsername(), gitConfig.getPassword()); - String uuid = UUID.randomUUID().toString(); - String folder = vertx.fileSystem().createTempDirectoryBlocking(uuid); - LOGGER.infof("Temp folder created: %s", folder); List<GitRepo> result = new ArrayList<>(); - Git git = null; try { - git = clone(folder, gitConfig.getUri(), gitConfig.getBranch(), cred); - checkout(git, false, null, null, gitConfig.getBranch()); + String folder = git.getRepository().getDirectory().getAbsolutePath().replace("/.git", ""); List<String> projects = readProjectsFromFolder(folder); if (filter != null) { - projects = projects.stream().filter(s -> s.equals(filter)).collect(Collectors.toList()); + projects = projects.stream().filter(s -> Arrays.stream(filter).filter(f -> f.equals(s)).findFirst().isPresent()).collect(Collectors.toList()); + } else { + projects = projects.stream().filter(s -> !s.startsWith(".")).collect(Collectors.toList()); // do not import hidden folders } for (String project : projects) { Map<String, String> filesRead = readProjectFilesFromFolder(folder, project); @@ -178,6 +227,26 @@ public class GitService { } } + public Git getGit(boolean checkout, String folder) throws GitAPIException, IOException, URISyntaxException { + LOGGER.info("Git checkout"); + GitConfig gitConfig = getGitConfig(); + CredentialsProvider cred = new UsernamePasswordCredentialsProvider(gitConfig.getUsername(), gitConfig.getPassword()); + LOGGER.info("Temp folder created " + folder); + Git git = null; + try { + git = clone(folder, gitConfig.getUri(), gitConfig.getBranch(), cred); + if (checkout) { + checkout(git, false, null, null, gitConfig.getBranch()); + } + } catch (RefNotFoundException e) { + LOGGER.error("New repository"); + git = init(folder, gitConfig.getUri(), gitConfig.getBranch()); + } catch (Exception e) { + LOGGER.error("Error", e); + } + return git; + } + private List<Tuple2<String, String>> readKameletsFromFolder(String folder) { LOGGER.info("Read kamelets from " + folder); List<Tuple2<String, String>> kamelets = new ArrayList<>(); @@ -198,7 +267,7 @@ public class GitService { List<String> files = new ArrayList<>(); vertx.fileSystem().readDirBlocking(folder).forEach(f -> { String[] filenames = f.split(File.separator); - String folderName = filenames[filenames.length -1]; + String folderName = filenames[filenames.length - 1]; if (!folderName.startsWith(".") && Files.isDirectory(Paths.get(f))) { LOGGER.info("Importing project from folder " + folderName); files.add(folderName); @@ -208,11 +277,11 @@ public class GitService { } private Map<String, String> readProjectFilesFromFolder(String repoFolder, String projectFolder) { - LOGGER.infof("Read files from %s/%s", repoFolder, projectFolder ); + LOGGER.infof("Read files from %s/%s", repoFolder, projectFolder); Map<String, String> files = new HashMap<>(); vertx.fileSystem().readDirBlocking(repoFolder + File.separator + projectFolder).forEach(f -> { String[] filenames = f.split(File.separator); - String filename = filenames[filenames.length -1]; + String filename = filenames[filenames.length - 1]; Path path = Paths.get(f); if (!filename.startsWith(".") && !Files.isDirectory(path)) { LOGGER.info("Importing file " + filename); @@ -244,9 +313,9 @@ public class GitService { LOGGER.info("Add deleted files to git index for project " + project.getProjectId()); vertx.fileSystem().readDirBlocking(path.toString()).forEach(f -> { String[] filenames = f.split(File.separator); - String filename = filenames[filenames.length -1]; + String filename = filenames[filenames.length - 1]; LOGGER.info("Checking file " + filename); - if (files.stream().filter(pf -> Objects.equals(pf.getName(), filename)).count() == 0){ + if (files.stream().filter(pf -> Objects.equals(pf.getName(), filename)).count() == 0) { try { LOGGER.info("Add deleted file " + filename); git.rm().addFilepattern(project.getProjectId() + File.separator + filename).call(); @@ -257,7 +326,7 @@ public class GitService { }); } - public RevCommit commitAddedAndPush(Git git, String branch, CredentialsProvider cred, String message) throws GitAPIException, IOException, URISyntaxException { + public RevCommit commitAddedAndPush(Git git, String branch, CredentialsProvider cred, String message) throws GitAPIException { LOGGER.info("Commit and push changes"); LOGGER.info("Git add: " + git.add().addFilepattern(".").call()); RevCommit commit = git.commit().setMessage(message).call(); @@ -273,16 +342,16 @@ public class GitService { return git; } - private void addDeletedFolderToIndex(Git git, String folder, String projectId, List<ProjectFile> files) throws IOException { + private void addDeletedFolderToIndex(Git git, String folder, String projectId, List<ProjectFile> files) { LOGGER.infof("Add folder %s to git index.", projectId); try { - git.rm().addFilepattern(projectId + File.separator).call(); + git.rm().addFilepattern(projectId + File.separator).call(); } catch (GitAPIException e) { - throw new RuntimeException(e); + throw new RuntimeException(e); } } - - public void deleteProject(String projectId, List<ProjectFile> files) throws GitAPIException, IOException, URISyntaxException { + + public void deleteProject(String projectId, List<ProjectFile> files) { LOGGER.info("Delete and push project " + projectId); GitConfig gitConfig = getGitConfig(); CredentialsProvider cred = new UsernamePasswordCredentialsProvider(gitConfig.getUsername(), gitConfig.getPassword()); @@ -298,7 +367,7 @@ public class GitService { commitAddedAndPush(git, gitConfig.getBranch(), cred, commitMessage); LOGGER.info("Delete Temp folder " + folder); vertx.fileSystem().deleteRecursiveBlocking(folder, true); - LOGGER.infof("Project %s deleted from Git" , projectId); + LOGGER.infof("Project %s deleted from Git", projectId); } catch (RefNotFoundException e) { LOGGER.error("Repository not found"); } catch (Exception e) { @@ -307,14 +376,16 @@ public class GitService { } } - private Git clone(String dir, String uri, String branch, CredentialsProvider cred) throws GitAPIException { + private Git clone(String dir, String uri, String branch, CredentialsProvider cred) throws GitAPIException, URISyntaxException { CloneCommand cloneCommand = Git.cloneRepository(); cloneCommand.setCloneAllBranches(false); cloneCommand.setDirectory(Paths.get(dir).toFile()); cloneCommand.setURI(uri); cloneCommand.setBranch(branch); cloneCommand.setCredentialsProvider(cred); - return cloneCommand.call(); + Git git = cloneCommand.call(); + addRemote(git, uri); + return git; } private void addRemote(Git git, String uri) throws URISyntaxException, GitAPIException { @@ -325,12 +396,19 @@ public class GitService { remoteAddCommand.call(); } + private void fetch(Git git, CredentialsProvider cred) throws GitAPIException { + // fetch: + FetchCommand fetchCommand = git.fetch(); + fetchCommand.setCredentialsProvider(cred); + FetchResult result = fetchCommand.call(); + } + private void checkout(Git git, boolean create, String path, String startPoint, String branch) throws GitAPIException { // create branch: CheckoutCommand checkoutCommand = git.checkout(); checkoutCommand.setName(branch); checkoutCommand.setCreateBranch(create); - if (startPoint != null){ + if (startPoint != null) { checkoutCommand.setStartPoint(startPoint); } if (path != null) { @@ -346,4 +424,34 @@ public class GitService { } return null; } + + public Set<String> getChangedProjects(RevCommit commit) { + Set<String> files = new HashSet<>(); + Git git = getPollGit(); + if (git != null) { + TreeWalk walk = new TreeWalk(git.getRepository()); + walk.setRecursive(true); + walk.setFilter(TreeFilter.ANY_DIFF); + + ObjectId a = commit.getTree().getId(); + RevCommit parent = commit.getParent(0); + ObjectId b = parent.getTree().getId(); + try { + walk.reset(b, a); + List<DiffEntry> changes = DiffEntry.scan(walk); + changes.stream().forEach(de -> { + String path = de.getNewPath(); + if (path != null) { + String[] parts = path.split(File.separator); + if (parts.length > 0) { + files.add(parts[0]); + } + } + }); + } catch (IOException e) { + LOGGER.error("Error", e); + } + } + return files; + } } diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java index e7d49982..c85bde94 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java @@ -16,6 +16,7 @@ */ package org.apache.camel.karavan.service; +import io.smallrye.mutiny.tuples.Tuple2; import org.apache.camel.karavan.model.CamelStatus; import org.apache.camel.karavan.model.DeploymentStatus; import org.apache.camel.karavan.model.Environment; @@ -59,6 +60,7 @@ public class InfinispanService { BasicCache<GroupedKey, CamelStatus> camelStatuses; BasicCache<GroupedKey, ServiceStatus> serviceStatuses; BasicCache<String, Environment> environments; + BasicCache<String, String> commits; @Inject RemoteCacheManager cacheManager; @@ -95,7 +97,7 @@ public class InfinispanService { podStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(PodStatus.CACHE, builder.build()); serviceStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(ServiceStatus.CACHE, builder.build()); camelStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(CamelStatus.CACHE, builder.build()); - + commits = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache("commits", builder.build()); cleanData(); } else { LOGGER.info("InfinispanService is starting in remote mode"); @@ -107,6 +109,7 @@ public class InfinispanService { podStatuses = cacheManager.administration().getOrCreateCache(PodStatus.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, PodStatus.CACHE))); serviceStatuses = cacheManager.administration().getOrCreateCache(ServiceStatus.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, ServiceStatus.CACHE))); camelStatuses = cacheManager.administration().getOrCreateCache(CamelStatus.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, CamelStatus.CACHE))); + commits = cacheManager.administration().getOrCreateCache("commits", new XMLStringConfiguration(String.format(CACHE_CONFIG, "commits"))); } } @@ -291,6 +294,23 @@ public class InfinispanService { environments.put(environment.getName(), environment); } + public void saveCommit(String commitId, int time) { + commits.put(commitId, String.valueOf(time)); + } + + public void saveLastCommit(String commitId) { + commits.put("lastCommitId", commitId); + } + + public Tuple2<String, Integer> getLastCommit() { + String lastCommitId = commits.get("lastCommitId"); + String time = commits.get(lastCommitId); + return Tuple2.of(lastCommitId, Integer.parseInt(time)); + } + + public boolean hasCommit(String commitId) { + return commits.get(commitId) != null; + } protected void clearAllStatuses() { CompletableFuture.allOf( diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java index d235d178..6f5d7c33 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java @@ -67,10 +67,11 @@ public class KaravanService { void initialImport() { if (infinispanService.getProjects().isEmpty()) { LOGGER.info("No projects found in the Data Grid"); - bus.publish(ImportService.IMPORT_PROJECTS, ""); + bus.publish(ProjectService.IMPORT_PROJECTS, ""); } else { - bus.publish(ImportService.IMPORT_TEMPLATES, ""); + bus.publish(ProjectService.IMPORT_TEMPLATES, ""); } + bus.publish(ProjectService.IMPORT_COMMITS, ""); } void startInformers() { diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java index 62175b2d..796dbfc3 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java @@ -137,8 +137,8 @@ public class KubernetesService { return "karavan-pipeline-" + environment + "-" + project.getRuntime(); } - public String createPipelineRun(Project project) throws Exception { - String pipeline = getPipelineName(project); + public String createPipelineRun(Project project) { + String pipeline = getPipelineName(project); LOGGER.info("Pipeline " + pipeline + " is creating for " + project.getProjectId()); Map<String, String> labels = Map.of( diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java similarity index 60% rename from karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java rename to karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java index 3ebc09c4..9d8f174b 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java @@ -16,31 +16,36 @@ */ package org.apache.camel.karavan.service; +import io.quarkus.scheduler.Scheduled; import io.quarkus.vertx.ConsumeEvent; +import io.smallrye.mutiny.tuples.Tuple2; import org.apache.camel.karavan.model.GitRepo; -import org.apache.camel.karavan.model.GitRepoFile; import org.apache.camel.karavan.model.Project; import org.apache.camel.karavan.model.ProjectFile; +import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import java.time.Instant; -import java.util.Arrays; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; @ApplicationScoped -public class ImportService { +public class ProjectService { - private static final Logger LOGGER = Logger.getLogger(ImportService.class.getName()); + private static final Logger LOGGER = Logger.getLogger(ProjectService.class.getName()); public static final String IMPORT_TEMPLATES = "import-templates"; public static final String IMPORT_PROJECTS = "import-projects"; - + public static final String IMPORT_COMMITS = "import-commits"; @Inject InfinispanService infinispanService; + @Inject + KubernetesService kubernetesService; + @Inject GitService gitService; @@ -50,8 +55,37 @@ public class ImportService { @ConfigProperty(name = "karavan.default-runtime") String runtime; + private AtomicBoolean readyToPull = new AtomicBoolean(false); + + @Scheduled(every = "{karavan.git-pull-interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP) + void pullCommits() { + if (readyToPull.get()) { + Tuple2<String, Integer> lastCommit = infinispanService.getLastCommit(); + gitService.getCommitsAfterCommit(lastCommit.getItem2()).forEach(commitInfo -> { + if (!infinispanService.hasCommit(commitInfo.getCommitId())) { + commitInfo.getRepos().forEach(repo -> { + Project project = importProjectFromRepo(repo); + kubernetesService.createPipelineRun(project); + }); + infinispanService.saveCommit(commitInfo.getCommitId(), commitInfo.getTime()); + } + infinispanService.saveLastCommit(commitInfo.getCommitId()); + }); + } + } + + @ConsumeEvent(value = IMPORT_COMMITS, blocking = true) + void importCommits(String data) { + LOGGER.info("Import commits"); + gitService.getAllCommits().forEach((commitId, time) -> { + infinispanService.saveCommit(commitId, time); + infinispanService.saveLastCommit(commitId); + }); + readyToPull.set(true); + } + @ConsumeEvent(value = IMPORT_PROJECTS, blocking = true) - void importProjects(String data) { + void importAllProjects(String data) { LOGGER.info("Import projects from Git"); try { List<GitRepo> repos = gitService.readProjectsFromRepository(); @@ -63,11 +97,7 @@ public class ImportService { } else if (folderName.equals(Project.NAME_KAMELETS)){ project = new Project(Project.NAME_KAMELETS, "Custom Kamelets", "Custom Kamelets", "quarkus", repo.getCommitId(), repo.getLastCommitTimestamp()); } else { - String propertiesFile = getPropertiesFile(repo); - String projectName = getProjectName(propertiesFile); - String projectDescription = getProjectDescription(propertiesFile); - String runtime = getProjectRuntime(propertiesFile); - project = new Project(folderName, projectName, projectDescription, runtime, repo.getCommitId(), repo.getLastCommitTimestamp()); + project = getProjectFromRepo(repo); } infinispanService.saveProject(project, true); @@ -87,16 +117,20 @@ public class ImportService { LOGGER.info("Import project from Git " + projectId); try { GitRepo repo = gitService.readProjectFromRepository(projectId); - Project project; - String folderName = repo.getName(); - String propertiesFile = getPropertiesFile(repo); - String projectName = getProjectName(propertiesFile); - String projectDescription = getProjectDescription(propertiesFile); - String runtime = getProjectRuntime(propertiesFile); - project = new Project(folderName, projectName, projectDescription, runtime, repo.getCommitId(), repo.getLastCommitTimestamp()); + return importProjectFromRepo(repo); + } catch (Exception e) { + LOGGER.error("Error during project import", e); + return null; + } + } + + private Project importProjectFromRepo(GitRepo repo) { + LOGGER.info("Import project from GitRepo " + repo.getName()); + try { + Project project = getProjectFromRepo(repo); infinispanService.saveProject(project, true); repo.getFiles().forEach(repoFile -> { - ProjectFile file = new ProjectFile(repoFile.getName(), repoFile.getBody(), folderName, repoFile.getLastCommitTimestamp()); + ProjectFile file = new ProjectFile(repoFile.getName(), repoFile.getBody(), repo.getName(), repoFile.getLastCommitTimestamp()); infinispanService.saveProjectFile(file); }); return project; @@ -106,6 +140,28 @@ public class ImportService { } } + public Project getProjectFromRepo(GitRepo repo) { + String folderName = repo.getName(); + String propertiesFile = ServiceUtil.getPropertiesFile(repo); + String projectName = ServiceUtil.getProjectName(propertiesFile); + String projectDescription = ServiceUtil.getProjectDescription(propertiesFile); + String runtime = ServiceUtil.getProjectRuntime(propertiesFile); + return new Project(folderName, projectName, projectDescription, runtime, repo.getCommitId(), repo.getLastCommitTimestamp()); + } + + public Project commitAndPushProject(String projectId, String message) throws Exception { + Project p = infinispanService.getProject(projectId); + List<ProjectFile> files = infinispanService.getProjectFiles(projectId); + RevCommit commit = gitService.commitAndPushProject(p, files, message); + String commitId = commit.getId().getName(); + Long lastUpdate = commit.getCommitTime() * 1000L; + p.setLastCommit(commitId); + p.setLastCommitTimestamp(lastUpdate); + infinispanService.saveProject(p, false); + infinispanService.saveCommit(commitId, commit.getCommitTime()); + return p; + } + void addKameletsProject(String data) { LOGGER.info("Add custom kamelets project if not exists"); try { @@ -113,7 +169,7 @@ public class ImportService { if (kamelets == null) { kamelets = new Project(Project.NAME_KAMELETS, "Custom Kamelets", "Custom Kamelets", "quarkus", "", Instant.now().toEpochMilli()); infinispanService.saveProject(kamelets, true); - gitService.commitAndPushProject("kamelets", "Add custom kamelets"); + commitAndPushProject("kamelets", "Add custom kamelets"); } } catch (Exception e) { LOGGER.error("Error during custom kamelets project creation", e); @@ -133,51 +189,10 @@ public class ImportService { ProjectFile file = new ProjectFile(name, value, Project.NAME_TEMPLATES, Instant.now().toEpochMilli()); infinispanService.saveProjectFile(file); }); - gitService.commitAndPushProject("templates", "Add default templates"); + commitAndPushProject("templates", "Add default templates"); } } catch (Exception e) { LOGGER.error("Error during templates project creation", e); } } - - private String getPropertiesFile(GitRepo repo) { - try { - for (GitRepoFile e : repo.getFiles()){ - if (e.getName().equalsIgnoreCase("application.properties")) { - return e.getBody(); - } - } - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - return null; - } - - private static String capitalize(String str) { - if(str == null || str.isEmpty()) { - return str; - } - return str.substring(0, 1).toUpperCase() + str.substring(1); - } - - private static String getProperty(String file, String property) { - String prefix = property + "="; - return Arrays.stream(file.split(System.lineSeparator())).filter(s -> s.startsWith(prefix)) - .findFirst().orElseGet(() -> "") - .replace(prefix, ""); - } - - private static String getProjectDescription(String file) { - String description = getProperty(file, "camel.jbang.project-description"); - return description != null && !description.isBlank() ? description : getProperty(file, "camel.karavan.project-description"); - } - - private static String getProjectName(String file) { - String name = getProperty(file, "camel.jbang.project-name"); - return name != null && !name.isBlank() ? name : getProperty(file, "camel.karavan.project-name"); - } - - private static String getProjectRuntime(String file) { - return getProperty(file, "camel.jbang.runtime"); - } } diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/ServiceUtil.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/ServiceUtil.java new file mode 100644 index 00000000..d6d95c40 --- /dev/null +++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/ServiceUtil.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.karavan.service; + +import org.apache.camel.karavan.model.GitRepo; +import org.apache.camel.karavan.model.GitRepoFile; + +import java.util.Arrays; + +public class ServiceUtil { + + public static String getPropertiesFile(GitRepo repo) { + try { + for (GitRepoFile e : repo.getFiles()){ + if (e.getName().equalsIgnoreCase("application.properties")) { + return e.getBody(); + } + } + } catch (Exception e) { + + } + return null; + } + + public static String capitalize(String str) { + if(str == null || str.isEmpty()) { + return str; + } + return str.substring(0, 1).toUpperCase() + str.substring(1); + } + + public static String getProperty(String file, String property) { + String prefix = property + "="; + return Arrays.stream(file.split(System.lineSeparator())).filter(s -> s.startsWith(prefix)) + .findFirst().orElseGet(() -> "") + .replace(prefix, ""); + } + + public static String getProjectDescription(String file) { + String description = getProperty(file, "camel.jbang.project-description"); + return description != null && !description.isBlank() ? description : getProperty(file, "camel.karavan.project-description"); + } + + public static String getProjectName(String file) { + String name = getProperty(file, "camel.jbang.project-name"); + return name != null && !name.isBlank() ? name : getProperty(file, "camel.karavan.project-name"); + } + + public static String getProjectRuntime(String file) { + return getProperty(file, "camel.jbang.runtime"); + } +} diff --git a/karavan-app/src/main/resources/application.properties b/karavan-app/src/main/resources/application.properties index 8a27af6a..3c5236aa 100644 --- a/karavan-app/src/main/resources/application.properties +++ b/karavan-app/src/main/resources/application.properties @@ -9,6 +9,8 @@ karavan.git-repository=${GIT_REPOSITORY} karavan.git-username=${GIT_USERNAME} karavan.git-password=${GIT_TOKEN} karavan.git-branch=main +karavan.git-pull-interval=5s +quarkus.scheduler.enabled=true # Infinispan Server address #quarkus.infinispan-client.server-list=localhost:12345 @@ -16,8 +18,8 @@ quarkus.infinispan-client.devservices.enabled=false quarkus.infinispan-client.devservices.port=12345 quarkus.infinispan-client.devservices.service-name=karavan # Authentication -quarkus.infinispan-client.auth-username=admin -quarkus.infinispan-client.auth-password=password +quarkus.infinispan-client.username=admin +quarkus.infinispan-client.password=password # Infinispan client intelligence # Use BASIC as a Docker for Mac workaround diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-app/src/main/webui/src/api/KaravanApi.tsx index 53af69f5..d16e1083 100644 --- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx +++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx @@ -274,7 +274,6 @@ export class KaravanApi { } static async push(params: {}, after: (res: AxiosResponse<any>) => void) { - console.log(params) instance.post('/api/git', params) .then(res => { after(res); @@ -283,6 +282,17 @@ export class KaravanApi { }); } + static async pull(projectId: string, after: (res: AxiosResponse<any>) => void) { + instance.get('/api/git/' + projectId) + .then(res => { + if (res.status === 200) { + after(res.data); + } + }).catch(err => { + console.log(err); + }); + } + static async getTemplatesFiles( after: (files: []) => void) { instance.get('/api/file/templates') .then(res => { diff --git a/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx b/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx index 35facb46..dc38966b 100644 --- a/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx +++ b/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx @@ -7,10 +7,10 @@ import { FlexItem, ToggleGroup, ToggleGroupItem, - Checkbox, Tooltip, ToolbarItem, Modal, ModalVariant, Form, FormGroup, TextInput, FormHelperText, HelperText, HelperTextItem + Checkbox, Tooltip, ToolbarItem, Modal, ModalVariant, Form, FormGroup, TextInput, FormHelperText } from '@patternfly/react-core'; import '../designer/karavan.css'; -import {Project, ProjectFile, ProjectFileTypes} from "./ProjectModels"; +import {Project, ProjectFile} from "./ProjectModels"; import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon"; import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon"; import DownloadImageIcon from "@patternfly/react-icons/dist/esm/icons/image-icon"; @@ -18,7 +18,6 @@ import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml"; import PushIcon from "@patternfly/react-icons/dist/esm/icons/code-branch-icon"; import {KaravanApi} from "../api/KaravanApi"; -import {CamelUi} from "../designer/utils/CamelUi"; interface Props { project: Project, @@ -164,7 +163,7 @@ export class ProjectPageToolbar extends React.Component<Props> { commitMessageIsOpen: true, commitMessage : commitMessage === '' ? new Date().toLocaleString() : commitMessage })}> - {isPushing ? "..." : "Commit"} + {isPushing ? "..." : "Push"} </Button> </Tooltip> </FlexItem>}