Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds auto-favoriting exclusion for workflow libraries #123

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,6 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git</artifactId>
<version>3.6.0</version>
<exclusions>
<!-- Upper bound dependencies error fix -->
<exclusion>
<groupId>org.jenkins-ci</groupId>
<artifactId>annotation-indexer</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
Expand Down Expand Up @@ -90,6 +82,14 @@
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>io.jenkins.plugins</groupId>
<artifactId>pipeline-groovy-lib</artifactId>
<version>656.va_a_ceeb_6ffb_f7</version>
<optional>true</optional>
</dependency>

<!-- Upper bound dependencies error fix -->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
package io.jenkins.blueocean.autofavorite;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;

import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
Expand All @@ -17,27 +29,28 @@
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.GitTool;
import hudson.plugins.git.Revision;
import hudson.plugins.git.UserRemoteConfig;
import hudson.plugins.git.util.BuildData;
import hudson.scm.SCM;
import hudson.scm.SCMRevisionState;
import hudson.util.LogTaskListener;
import io.jenkins.blueocean.autofavorite.user.FavoritingUserProperty;
import jenkins.branch.MultiBranchProject;
import jenkins.model.Jenkins;
import jenkins.plugins.git.GitSCMSource;
import jenkins.scm.api.SCMSource;
import org.apache.commons.io.IOUtils;
import org.eclipse.jgit.errors.MissingObjectException;
import org.jenkinsci.plugins.gitclient.Git;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;

import javax.annotation.CheckForNull;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jenkinsci.plugins.workflow.libs.FolderLibraries;
import org.jenkinsci.plugins.workflow.libs.GlobalLibraries;
import org.jenkinsci.plugins.workflow.libs.LibrariesAction;
import org.jenkinsci.plugins.workflow.libs.LibraryConfiguration;
import org.jenkinsci.plugins.workflow.libs.LibraryRecord;
import org.jenkinsci.plugins.workflow.libs.LibraryRetriever;
import org.jenkinsci.plugins.workflow.libs.SCMSourceRetriever;

@Extension
public class FavoritingScmListener extends SCMListener {
Expand Down Expand Up @@ -65,7 +78,23 @@ public void onCheckout(Run<?, ?> build, SCM scm, FilePath workspace, TaskListene
return;
}

BuildData buildData = build.getAction(BuildData.class);
final List<String> urls = ((GitSCM) scm).getUserRemoteConfigs().stream()
.map(UserRemoteConfig::getUrl)
.collect(Collectors.toList());

final List<BuildData> buildDataList = build.getActions(BuildData.class);
BuildData buildData = buildDataList.stream()
.filter(bd -> !Sets.intersection(Sets.newHashSet(urls), bd.remoteUrls).isEmpty())
.findFirst()
.orElse(null);

if (Jenkins.get().getPlugin("pipeline-groovy-lib") != null) {
if (shouldSkipLibrariesRepository(build, urls)) {
LOGGER.fine("Remote repository is for a Workflow Library. Skipping auto favoriting.");
return;
}
}

if (buildData == null) {
LOGGER.fine("No Git Build Data is present. Favoriting cannot be run.");
return;
Expand Down Expand Up @@ -135,6 +164,51 @@ public void onCheckout(Run<?, ?> build, SCM scm, FilePath workspace, TaskListene
}
}

private boolean shouldSkipLibrariesRepository(final Run<?, ?> build, final List<String> urls) {
final LibrariesAction librariesAction = build.getAction(LibrariesAction.class);
if (librariesAction != null && !librariesAction.getLibraries().isEmpty()) {
for (final LibraryRecord libraryRecord : librariesAction.getLibraries()) {
final String name = libraryRecord.getName();

if (libraryMatchesUrls(urls, name, GlobalLibraries.get().getLibraries())) {
return true;
}

final MultiBranchProject<?, ?> multiBranchProject = (MultiBranchProject<?, ?>) ((WorkflowRun) build).getParent().getParent();
for (Object property : multiBranchProject.getProperties()) {
if (property instanceof FolderLibraries) {
FolderLibraries folderLibraries = (FolderLibraries) property;
final List<LibraryConfiguration> libraryConfigurations = folderLibraries.getLibraries();
if (libraryMatchesUrls(urls, name, libraryConfigurations)) {
return true;
}
}
}
}
}
return false;
}

private boolean libraryMatchesUrls(final List<String> urls, final String name, final List<LibraryConfiguration> libraryConfigurations) {
for (final LibraryConfiguration library : libraryConfigurations) {
if (library.getName().equals(name)) {
final LibraryRetriever retriever = library.getRetriever();
if (retriever instanceof SCMSourceRetriever) {
final SCMSourceRetriever scmSourceRetriever = (SCMSourceRetriever) retriever;
final SCMSource scmSource = scmSourceRetriever.getScm();
if (scmSource instanceof GitSCMSource) {
final GitSCMSource gitSCMSource = (GitSCMSource) scmSource;
final String remote = gitSCMSource.getRemote();
if (urls.contains(remote)) {
return true;
}
}
}
}
}
return false;
}

private GitChangeSet getChangeSet(GitSCM scm, FilePath workspace, Revision lastBuiltRevision, TaskListener listener) throws IOException, InterruptedException {
Git gitBuilder = Git.with(listener, new EnvVars())
.in(workspace);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package io.jenkins.blueocean.autofavorite;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import hudson.model.Result;
import hudson.model.User;
import hudson.plugins.favorite.Favorites;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import hudson.plugins.git.GitSCM;
import jenkins.branch.BranchSource;
import jenkins.branch.MultiBranchProject.BranchIndexing;
import jenkins.plugins.git.GitSCMSource;
import jenkins.plugins.git.traits.BranchDiscoveryTrait;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.libs.FolderLibraries;
import org.jenkinsci.plugins.workflow.libs.GlobalLibraries;
import org.jenkinsci.plugins.workflow.libs.LibraryConfiguration;
import org.jenkinsci.plugins.workflow.libs.SCMSourceRetriever;
import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.JenkinsRule;

public class WorkFlowLibrariesTest {

@Rule
public TemporaryFolder folder = new TemporaryFolder();

@Rule
public JenkinsRule j = new JenkinsRule();

private Path jobRepository;
private Path libraryRepository;

@Before
public void setUp() throws Exception {

GitSCM.ALLOW_LOCAL_CHECKOUT = true;

jobRepository = folder.newFolder().toPath();
libraryRepository = folder.newFolder().toPath();

createJobRepository(jobRepository);
createLibraryRepository(libraryRepository);

}

@Test
public void testGlobal() throws Exception {

setupGlobalLibraries(libraryRepository);

assertNull(User.getById("name1", false));
assertNull(User.getById("name2", false));
User.getById("name1", true);
User.getById("name2", true);

final WorkflowMultiBranchProject project = createWorkflowMultiBranchProject(jobRepository);
project.addProperty(createFolderLibraries(libraryRepository));
final WorkflowJob job = runPipeline(project);

final User name1 = User.getById("name1", false);
assertNotNull(name1);
assertTrue(Favorites.isFavorite(name1, job));

final User name2 = User.getById("name2", false);
assertNotNull(name2);
assertFalse(Favorites.isFavorite(name2, job));

}


@Test
public void testFolder() throws Exception {

assertNull(User.getById("name1", false));
assertNull(User.getById("name2", false));
User.getById("name1", true);
User.getById("name2", true);

final WorkflowMultiBranchProject project = createWorkflowMultiBranchProject(jobRepository);
project.addProperty(createFolderLibraries(libraryRepository));
final WorkflowJob job = runPipeline(project);

final User name1 = User.getById("name1", false);
assertNotNull(name1);
assertTrue(Favorites.isFavorite(name1, job));

final User name2 = User.getById("name2", false);
assertNotNull(name2);
assertFalse(Favorites.isFavorite(name2, job));

}

private WorkflowMultiBranchProject createWorkflowMultiBranchProject(final Path jobRepository) throws java.io.IOException, InterruptedException {
WorkflowMultiBranchProject mbp = j.createProject(WorkflowMultiBranchProject.class, "WorkflowMultiBranchProject");
GitSCMSource gitSCMSource = new GitSCMSource(jobRepository.toString());
gitSCMSource.setCredentialsId("");
gitSCMSource.getTraits().add(new BranchDiscoveryTrait());
mbp.getSourcesList().add(new BranchSource(gitSCMSource));

return mbp;
}

private WorkflowJob runPipeline(final WorkflowMultiBranchProject mbp) throws java.io.IOException, InterruptedException {
BranchIndexing<WorkflowJob, WorkflowRun> indexing = mbp.getIndexing();
indexing.run();

while (indexing.getResult() == Result.NOT_BUILT) {
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
}

assertEquals(Result.SUCCESS, indexing.getResult());

WorkflowJob job = mbp.getItem("master");
while (job.getBuilds().isEmpty()) {
Thread.sleep(5);
}

WorkflowRun run = job.getBuildByNumber(1);
assertNotNull(run);

while (run.getResult() == null) {
/* poll faster as long as we still need to removeBuildData */
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
}

return job;
}

private void setupGlobalLibraries(final Path libraryRepository) {
List<LibraryConfiguration> libraries = new ArrayList<>();
GitSCMSource scmSource = new GitSCMSource(libraryRepository.toString());
SCMSourceRetriever scmSourceRetriever = new SCMSourceRetriever(scmSource);
final LibraryConfiguration libraryConfiguration = new LibraryConfiguration(
"shared-library",
scmSourceRetriever);
libraryConfiguration.setDefaultVersion("master");
libraries.add(libraryConfiguration);
GlobalLibraries.get().setLibraries(libraries);
}

private FolderLibraries createFolderLibraries(final Path libraryRepository) {
List<LibraryConfiguration> libraries = new ArrayList<>();
GitSCMSource scmSource = new GitSCMSource(libraryRepository.toString());
SCMSourceRetriever scmSourceRetriever = new SCMSourceRetriever(scmSource);
final LibraryConfiguration libraryConfiguration = new LibraryConfiguration(
"shared-library",
scmSourceRetriever);
libraryConfiguration.setDefaultVersion("master");
libraries.add(libraryConfiguration);
return new FolderLibraries(libraries);
}

private void createJobRepository(final Path path) throws IOException, GitAPIException {
Files.copy(getClass().getResourceAsStream("/job-repository/Jenkinsfile"),
path.resolve("Jenkinsfile"));

try (Git git = Git.init().setDirectory(path.toFile()).call()) {

git.add().addFilepattern("Jenkinsfile").call();

git.commit()
.setMessage("some commit message 1")
.setAuthor("name1", "name1@example.com")
.call();
}
}

private void createLibraryRepository(final Path path) throws IOException, GitAPIException {
final Path vars = path.resolve("vars");
vars.toFile().mkdir();

Files.copy(getClass().getResourceAsStream("/library-repository/vars/helloWorld.groovy"),
vars.resolve("helloWorld.groovy"));

try (Git git = Git.init().setDirectory(path.toFile()).call()) {

git.add().addFilepattern("vars/helloWorld.groovy").call();

git.commit()
.setMessage("some commit message 2")
.setAuthor("name2", "name2@example.com")
.call();
}
}
}
18 changes: 18 additions & 0 deletions src/test/resources/job-repository/Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@Library("shared-library") _

pipeline {
agent any

stages {
stage('Hello') {
steps {
echo 'Hello World'
}
}
stage('Hello from library') {
steps {
helloWorld()
}
}
}
}
3 changes: 3 additions & 0 deletions src/test/resources/library-repository/vars/helloWorld.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def call(Map config = [:]) {
sh "echo Hello ${config.name}. Today is ${config.dayOfWeek}."
}