mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-11-04 00:28:54 +00:00 
			
		
		
		
	Add updateDependencies
Allows for updating the depencencies of the project in an automated fashion. Closes gh-9542
This commit is contained in:
		
							parent
							
								
									8af36c9ef6
								
							
						
					
					
						commit
						f6a5b723cb
					
				
							
								
								
									
										22
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								build.gradle
									
									
									
									
									
								
							@ -16,6 +16,7 @@ apply plugin: 'io.spring.nohttp'
 | 
			
		||||
apply plugin: 'locks'
 | 
			
		||||
apply plugin: 'io.spring.convention.root'
 | 
			
		||||
apply plugin: 'org.jetbrains.kotlin.jvm'
 | 
			
		||||
apply plugin: 'org.springframework.security.update-dependencies'
 | 
			
		||||
 | 
			
		||||
group = 'org.springframework.security'
 | 
			
		||||
description = 'Spring Security'
 | 
			
		||||
@ -28,6 +29,26 @@ repositories {
 | 
			
		||||
	mavenCentral()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
updateDependenciesSettings {
 | 
			
		||||
	gitHub {
 | 
			
		||||
		organization = "rwinch"
 | 
			
		||||
		repository = "spring-security"
 | 
			
		||||
	}
 | 
			
		||||
	addFiles({
 | 
			
		||||
		return [
 | 
			
		||||
				project.file("buildSrc/src/main/java/io/spring/gradle/convention/AsciidoctorConventionPlugin.java"),
 | 
			
		||||
				project.file("buildSrc/src/main/groovy/io/spring/gradle/convention/CheckstylePlugin.groovy")
 | 
			
		||||
		]
 | 
			
		||||
	})
 | 
			
		||||
	dependencyExcludes {
 | 
			
		||||
		majorVersionBump()
 | 
			
		||||
		alphaBetaVersions()
 | 
			
		||||
		releaseCandidatesVersions()
 | 
			
		||||
		milestoneVersions()
 | 
			
		||||
		snapshotVersions()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
subprojects {
 | 
			
		||||
	plugins.withType(JavaPlugin) {
 | 
			
		||||
		project.sourceCompatibility='1.8'
 | 
			
		||||
@ -37,6 +58,7 @@ subprojects {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
allprojects {
 | 
			
		||||
	if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) {
 | 
			
		||||
		apply plugin: 'io.spring.javaformat'
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,10 @@
 | 
			
		||||
apply plugin: "java-gradle-plugin"
 | 
			
		||||
apply plugin: 'java'
 | 
			
		||||
apply plugin: 'groovy'
 | 
			
		||||
plugins {
 | 
			
		||||
	id "java-gradle-plugin"
 | 
			
		||||
	id "java"
 | 
			
		||||
	id "groovy"
 | 
			
		||||
	id 'com.apollographql.apollo' version '2.4.5'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sourceCompatibility = 1.8
 | 
			
		||||
 | 
			
		||||
@ -25,6 +29,10 @@ gradlePlugin {
 | 
			
		||||
			id = "io.spring.convention.management-configuration"
 | 
			
		||||
			implementationClass = "io.spring.gradle.convention.ManagementConfigurationPlugin"
 | 
			
		||||
		}
 | 
			
		||||
		updateDependencies {
 | 
			
		||||
			id = "org.springframework.security.update-dependencies"
 | 
			
		||||
			implementationClass = "org.springframework.security.convention.versions.UpdateDependenciesPlugin"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -39,7 +47,10 @@ dependencies {
 | 
			
		||||
	implementation 'net.sourceforge.saxon:saxon:9.1.0.8'
 | 
			
		||||
	implementation localGroovy()
 | 
			
		||||
 | 
			
		||||
	implementation 'com.github.ben-manes:gradle-versions-plugin:0.25.0'
 | 
			
		||||
	implementation 'io.projectreactor:reactor-core:3.4.4'
 | 
			
		||||
	implementation 'gradle.plugin.org.gretty:gretty:3.0.1'
 | 
			
		||||
	implementation 'com.apollographql.apollo:apollo-runtime:2.4.5'
 | 
			
		||||
	implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
 | 
			
		||||
	implementation 'io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.21.1'
 | 
			
		||||
	implementation 'io.spring.gradle:docbook-reference-plugin:0.3.1'
 | 
			
		||||
	implementation 'io.spring.gradle:propdeps-plugin:0.0.10.RELEASE'
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,7 @@
 | 
			
		||||
mutation CreateIssueInput($assigneeId: ID!, $labelIds: [ID!], $milestoneId: ID!, $repositoryId: ID!, $title: String!) {
 | 
			
		||||
    createIssue(input: {assigneeIds: [$assigneeId], labelIds: $labelIds, milestoneId: $milestoneId, projectIds: [], repositoryId: $repositoryId, title: $title}) {
 | 
			
		||||
        issue {
 | 
			
		||||
            number
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
query FindCreateIssueInput($owner: String!, $name: String!, $labelQuery: String, $milestoneName: String) {
 | 
			
		||||
  repository(owner: $owner, name: $name) {
 | 
			
		||||
      id
 | 
			
		||||
      labels(query: $labelQuery, first: 1) {
 | 
			
		||||
          nodes {
 | 
			
		||||
              id
 | 
			
		||||
              name
 | 
			
		||||
          }
 | 
			
		||||
      }
 | 
			
		||||
      milestones(query: $milestoneName, states: [OPEN], first: 1) {
 | 
			
		||||
          nodes {
 | 
			
		||||
              id
 | 
			
		||||
              title
 | 
			
		||||
          }
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
  viewer {
 | 
			
		||||
      id
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,8 @@
 | 
			
		||||
query RateLimit {
 | 
			
		||||
    rateLimit {
 | 
			
		||||
        limit
 | 
			
		||||
        cost
 | 
			
		||||
        remaining
 | 
			
		||||
        resetAt
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -59,7 +59,6 @@ public abstract class AbstractSpringJavaPlugin implements Plugin<Project> {
 | 
			
		||||
		pluginManager.apply("io.spring.convention.dependency-set");
 | 
			
		||||
		pluginManager.apply("io.spring.convention.javadoc-options");
 | 
			
		||||
		pluginManager.apply("io.spring.convention.checkstyle");
 | 
			
		||||
		pluginManager.apply('com.github.ben-manes.versions');
 | 
			
		||||
 | 
			
		||||
		copyPropertyFromRootProjectTo("group", project);
 | 
			
		||||
		copyPropertyFromRootProjectTo("version", project);
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,179 @@
 | 
			
		||||
package org.springframework.security.convention.versions;
 | 
			
		||||
 | 
			
		||||
import com.apollographql.apollo.ApolloCall;
 | 
			
		||||
import com.apollographql.apollo.ApolloClient;
 | 
			
		||||
import com.apollographql.apollo.api.Input;
 | 
			
		||||
import com.apollographql.apollo.api.Response;
 | 
			
		||||
import com.apollographql.apollo.exception.ApolloException;
 | 
			
		||||
import okhttp3.Interceptor;
 | 
			
		||||
import okhttp3.OkHttpClient;
 | 
			
		||||
import okhttp3.Request;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import reactor.core.publisher.Mono;
 | 
			
		||||
import reactor.util.retry.Retry;
 | 
			
		||||
import reactor.util.retry.RetrySpec;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
public class GitHubApi {
 | 
			
		||||
 | 
			
		||||
	private final ApolloClient apolloClient;
 | 
			
		||||
 | 
			
		||||
	public GitHubApi(String githubToken) {
 | 
			
		||||
		if (githubToken == null) {
 | 
			
		||||
			throw new IllegalArgumentException("githubToken is required. You can set it using -PgitHubAccessToken=");
 | 
			
		||||
		}
 | 
			
		||||
		OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
 | 
			
		||||
		clientBuilder.addInterceptor(new AuthorizationInterceptor(githubToken));
 | 
			
		||||
		this.apolloClient = ApolloClient.builder()
 | 
			
		||||
				.serverUrl("https://api.github.com/graphql")
 | 
			
		||||
				.okHttpClient(clientBuilder.build())
 | 
			
		||||
				.build();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Mono<FindCreateIssueResult> findCreateIssueInput(String owner, String name, String milestone) {
 | 
			
		||||
		String label = "\"type: dependency-upgrade\"";
 | 
			
		||||
		FindCreateIssueInputQuery findCreateIssueInputQuery = new FindCreateIssueInputQuery(owner, name, Input.optional(label), Input.optional(milestone));
 | 
			
		||||
		return Mono.create( sink -> this.apolloClient.query(findCreateIssueInputQuery)
 | 
			
		||||
				.enqueue(new ApolloCall.Callback<FindCreateIssueInputQuery.Data>() {
 | 
			
		||||
					@Override
 | 
			
		||||
					public void onResponse(@NotNull Response<FindCreateIssueInputQuery.Data> response) {
 | 
			
		||||
						if (response.hasErrors()) {
 | 
			
		||||
							sink.error(new RuntimeException(response.getErrors().stream().map(e -> e.getMessage()).collect(Collectors.joining(" "))));
 | 
			
		||||
						} else {
 | 
			
		||||
							FindCreateIssueInputQuery.Data data = response.getData();
 | 
			
		||||
							FindCreateIssueInputQuery.Repository repository = data.repository();
 | 
			
		||||
							List<String> labels = repository.labels().nodes().stream().map(FindCreateIssueInputQuery.Node::id).collect(Collectors.toList());
 | 
			
		||||
							if (labels.isEmpty()) {
 | 
			
		||||
								sink.error(new IllegalArgumentException("Could not find label for " + label));
 | 
			
		||||
								return;
 | 
			
		||||
							}
 | 
			
		||||
							Optional<String> firstMilestoneId = repository.milestones().nodes().stream().map(FindCreateIssueInputQuery.Node1::id).findFirst();
 | 
			
		||||
							if (!firstMilestoneId.isPresent()) {
 | 
			
		||||
								sink.error(new IllegalArgumentException("Could not find OPEN milestone id for " + milestone));
 | 
			
		||||
								return;
 | 
			
		||||
							}
 | 
			
		||||
							String milestoneId = firstMilestoneId.get();
 | 
			
		||||
							String repositoryId = repository.id();
 | 
			
		||||
							String assigneeId = data.viewer().id();
 | 
			
		||||
							sink.success(new FindCreateIssueResult(repositoryId, labels, milestoneId, assigneeId));
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					@Override
 | 
			
		||||
					public void onFailure(@NotNull ApolloException e) {
 | 
			
		||||
						sink.error(e);
 | 
			
		||||
					}
 | 
			
		||||
				}));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static class FindCreateIssueResult {
 | 
			
		||||
		private final String repositoryId;
 | 
			
		||||
		private final List<String> labelIds;
 | 
			
		||||
		private final String milestoneId;
 | 
			
		||||
		private final String assigneeId;
 | 
			
		||||
 | 
			
		||||
		public FindCreateIssueResult(String repositoryId, List<String> labelIds, String milestoneId, String assigneeId) {
 | 
			
		||||
			this.repositoryId = repositoryId;
 | 
			
		||||
			this.labelIds = labelIds;
 | 
			
		||||
			this.milestoneId = milestoneId;
 | 
			
		||||
			this.assigneeId = assigneeId;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public String getRepositoryId() {
 | 
			
		||||
			return repositoryId;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public List<String> getLabelIds() {
 | 
			
		||||
			return labelIds;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public String getMilestoneId() {
 | 
			
		||||
			return milestoneId;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public String getAssigneeId() {
 | 
			
		||||
			return assigneeId;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Mono<RateLimitQuery.RateLimit> findRateLimit() {
 | 
			
		||||
		return Mono.create( sink -> this.apolloClient.query(new RateLimitQuery())
 | 
			
		||||
			.enqueue(new ApolloCall.Callback<RateLimitQuery.Data>() {
 | 
			
		||||
				@Override
 | 
			
		||||
				public void onResponse(@NotNull Response<RateLimitQuery.Data> response) {
 | 
			
		||||
					if (response.hasErrors()) {
 | 
			
		||||
						sink.error(new RuntimeException(response.getErrors().stream().map(e -> e.getMessage()).collect(Collectors.joining(" "))));
 | 
			
		||||
					} else {
 | 
			
		||||
						sink.success(response.getData().rateLimit());
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				@Override
 | 
			
		||||
				public void onFailure(@NotNull ApolloException e) {
 | 
			
		||||
					sink.error(e);
 | 
			
		||||
				}
 | 
			
		||||
			}));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Mono<Integer> createIssue(String repositoryId, String title, List<String> labelIds, String milestoneId, String assigneeId) {
 | 
			
		||||
		CreateIssueInputMutation createIssue = new CreateIssueInputMutation.Builder()
 | 
			
		||||
				.repositoryId(repositoryId)
 | 
			
		||||
				.title(title)
 | 
			
		||||
				.labelIds(labelIds)
 | 
			
		||||
				.milestoneId(milestoneId)
 | 
			
		||||
				.assigneeId(assigneeId)
 | 
			
		||||
				.build();
 | 
			
		||||
		return Mono.create( sink -> this.apolloClient.mutate(createIssue)
 | 
			
		||||
				.enqueue(new ApolloCall.Callback<CreateIssueInputMutation.Data>() {
 | 
			
		||||
					@Override
 | 
			
		||||
					public void onResponse(@NotNull Response<CreateIssueInputMutation.Data> response) {
 | 
			
		||||
						if (response.hasErrors()) {
 | 
			
		||||
							String message = response.getErrors().stream().map(e -> e.getMessage() + " " + e.getCustomAttributes() + " " + e.getLocations()).collect(Collectors.joining(" "));
 | 
			
		||||
							if (message.contains("was submitted too quickly")) {
 | 
			
		||||
								sink.error(new SubmittedTooQuick(message));
 | 
			
		||||
							} else {
 | 
			
		||||
								sink.error(new RuntimeException(message));
 | 
			
		||||
							}
 | 
			
		||||
						} else {
 | 
			
		||||
							sink.success(response.getData().createIssue().issue().number());
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					@Override
 | 
			
		||||
					public void onFailure(@NotNull ApolloException e) {
 | 
			
		||||
						sink.error(e);
 | 
			
		||||
					}
 | 
			
		||||
				}))
 | 
			
		||||
				.retryWhen(
 | 
			
		||||
					RetrySpec.fixedDelay(3, Duration.ofMinutes(1))
 | 
			
		||||
						.filter(SubmittedTooQuick.class::isInstance)
 | 
			
		||||
						.doBeforeRetry(r -> System.out.println("Pausing for 1 minute and then retrying due to receiving \"submitted too quickly\" error from GitHub API"))
 | 
			
		||||
				)
 | 
			
		||||
				.cast(Integer.class);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static class SubmittedTooQuick extends RuntimeException {
 | 
			
		||||
		public SubmittedTooQuick(String message) {
 | 
			
		||||
			super(message);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static class AuthorizationInterceptor implements Interceptor {
 | 
			
		||||
 | 
			
		||||
		private final String token;
 | 
			
		||||
 | 
			
		||||
		public AuthorizationInterceptor(String token) {
 | 
			
		||||
			this.token = token;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public okhttp3.Response intercept(Chain chain) throws IOException {
 | 
			
		||||
			Request request = chain.request().newBuilder()
 | 
			
		||||
					.addHeader("Authorization", "Bearer " + this.token).build();
 | 
			
		||||
			return chain.proceed(request);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,168 @@
 | 
			
		||||
package org.springframework.security.convention.versions;
 | 
			
		||||
 | 
			
		||||
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionWithCurrent;
 | 
			
		||||
import org.gradle.api.Action;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
public class UpdateDependenciesExtension {
 | 
			
		||||
	private Supplier<List<File>> files;
 | 
			
		||||
 | 
			
		||||
	private UpdateMode updateMode = UpdateMode.COMMIT;
 | 
			
		||||
 | 
			
		||||
	private DependencyExcludes dependencyExcludes = new DependencyExcludes();
 | 
			
		||||
 | 
			
		||||
	private GitHub gitHub = new GitHub();
 | 
			
		||||
 | 
			
		||||
	public UpdateDependenciesExtension(Supplier<List<File>> files) {
 | 
			
		||||
		this.files = files;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void setUpdateMode(UpdateMode updateMode) {
 | 
			
		||||
		this.updateMode = updateMode;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public UpdateMode getUpdateMode() {
 | 
			
		||||
		return updateMode;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	GitHub getGitHub() {
 | 
			
		||||
		return this.gitHub;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	DependencyExcludes getExcludes() {
 | 
			
		||||
		return dependencyExcludes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Supplier<List<File>> getFiles() {
 | 
			
		||||
		return files;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void setFiles(Supplier<List<File>> files) {
 | 
			
		||||
		this.files = files;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void addFiles(Supplier<List<File>> files) {
 | 
			
		||||
		Supplier<List<File>> original = this.files;
 | 
			
		||||
		setFiles(() -> {
 | 
			
		||||
			List<File> result = new ArrayList<>(original.get());
 | 
			
		||||
			result.addAll(files.get());
 | 
			
		||||
			return result;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void dependencyExcludes(Action<DependencyExcludes> excludes) {
 | 
			
		||||
		excludes.execute(this.dependencyExcludes);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void gitHub(Action<GitHub> gitHub) {
 | 
			
		||||
		gitHub.execute(this.gitHub);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public enum UpdateMode {
 | 
			
		||||
		COMMIT,
 | 
			
		||||
		GITHUB_ISSUE
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public class GitHub {
 | 
			
		||||
		private String organization;
 | 
			
		||||
 | 
			
		||||
		private String repository;
 | 
			
		||||
 | 
			
		||||
		private String accessToken;
 | 
			
		||||
 | 
			
		||||
		private String milestone;
 | 
			
		||||
 | 
			
		||||
		public String getOrganization() {
 | 
			
		||||
			return organization;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void setOrganization(String organization) {
 | 
			
		||||
			this.organization = organization;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public String getRepository() {
 | 
			
		||||
			return repository;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void setRepository(String repository) {
 | 
			
		||||
			this.repository = repository;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public String getAccessToken() {
 | 
			
		||||
			return accessToken;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void setAccessToken(String accessToken) {
 | 
			
		||||
			this.accessToken = accessToken;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public String getMilestone() {
 | 
			
		||||
			return milestone;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void setMilestone(String milestone) {
 | 
			
		||||
			this.milestone = milestone;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Consider creating some Predicates instead since they are composible
 | 
			
		||||
	 */
 | 
			
		||||
	public class DependencyExcludes {
 | 
			
		||||
		private List<Action<ComponentSelectionWithCurrent>> actions = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
		List<Action<ComponentSelectionWithCurrent>> getActions() {
 | 
			
		||||
			return actions;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public DependencyExcludes alphaBetaVersions() {
 | 
			
		||||
			this.actions.add(excludeVersionWithRegex("(?i).*?(alpha|beta).*", "an alpha or beta version"));
 | 
			
		||||
			return this;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public DependencyExcludes majorVersionBump() {
 | 
			
		||||
			this.actions.add((selection) -> {
 | 
			
		||||
				String currentVersion = selection.getCurrentVersion();
 | 
			
		||||
				int separator = currentVersion.indexOf(".");
 | 
			
		||||
				String major = separator > 0 ? currentVersion.substring(0, separator) : currentVersion;
 | 
			
		||||
				String candidateVersion = selection.getCandidate().getVersion();
 | 
			
		||||
				Pattern calVerPattern = Pattern.compile("\\d\\d\\d\\d.*");
 | 
			
		||||
				boolean isCalVer = calVerPattern.matcher(candidateVersion).matches();
 | 
			
		||||
				if (!isCalVer && !candidateVersion.startsWith(major)) {
 | 
			
		||||
					selection.reject("Cannot upgrade to new Major Version");
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			return this;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public DependencyExcludes releaseCandidatesVersions() {
 | 
			
		||||
			this.actions.add(excludeVersionWithRegex("(?i).*?rc\\d+.*", "a release candidate version"));
 | 
			
		||||
			return this;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public DependencyExcludes milestoneVersions() {
 | 
			
		||||
			this.actions.add(excludeVersionWithRegex("(?i).*?m\\d+.*", "a milestone version"));
 | 
			
		||||
			return this;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public DependencyExcludes snapshotVersions() {
 | 
			
		||||
			this.actions.add(excludeVersionWithRegex(".*?-SNAPSHOT.*", "a SNAPSHOT version"));
 | 
			
		||||
			return this;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private Action<ComponentSelectionWithCurrent> excludeVersionWithRegex(String regex, String reason) {
 | 
			
		||||
			Pattern pattern = Pattern.compile(regex);
 | 
			
		||||
			return (selection) -> {
 | 
			
		||||
				String candidateVersion = selection.getCandidate().getVersion();
 | 
			
		||||
				if (pattern.matcher(candidateVersion).matches()) {
 | 
			
		||||
					selection.reject(candidateVersion + " is not allowed because it is " + reason);
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,254 @@
 | 
			
		||||
package org.springframework.security.convention.versions;
 | 
			
		||||
 | 
			
		||||
import com.github.benmanes.gradle.versions.reporter.result.DependencyOutdated;
 | 
			
		||||
import com.github.benmanes.gradle.versions.reporter.result.Result;
 | 
			
		||||
import com.github.benmanes.gradle.versions.reporter.result.VersionAvailable;
 | 
			
		||||
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask;
 | 
			
		||||
import com.github.benmanes.gradle.versions.updates.gradle.GradleUpdateResult;
 | 
			
		||||
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionRulesWithCurrent;
 | 
			
		||||
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionWithCurrent;
 | 
			
		||||
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ResolutionStrategyWithCurrent;
 | 
			
		||||
import groovy.lang.Closure;
 | 
			
		||||
import org.gradle.api.Action;
 | 
			
		||||
import org.gradle.api.Plugin;
 | 
			
		||||
import org.gradle.api.Project;
 | 
			
		||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 | 
			
		||||
import reactor.core.publisher.Mono;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
public class UpdateDependenciesPlugin implements Plugin<Project> {
 | 
			
		||||
	private GitHubApi gitHubApi;
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void apply(Project project) {
 | 
			
		||||
		UpdateDependenciesExtension updateDependenciesSettings = project.getExtensions().create("updateDependenciesSettings", UpdateDependenciesExtension.class, defaultFiles(project));
 | 
			
		||||
		if (project.hasProperty("updateMode")) {
 | 
			
		||||
			String updateMode = String.valueOf(project.findProperty("updateMode"));
 | 
			
		||||
			updateDependenciesSettings.setUpdateMode(UpdateDependenciesExtension.UpdateMode.valueOf(updateMode));
 | 
			
		||||
		}
 | 
			
		||||
		if (project.hasProperty("nextVersion")) {
 | 
			
		||||
			String nextVersion = String.valueOf(project.findProperty("nextVersion"));
 | 
			
		||||
			updateDependenciesSettings.getGitHub().setMilestone(nextVersion);
 | 
			
		||||
		}
 | 
			
		||||
		if (project.hasProperty("gitHubAccessToken")) {
 | 
			
		||||
			String gitHubAccessToken = String.valueOf(project.findProperty("gitHubAccessToken"));
 | 
			
		||||
			updateDependenciesSettings.getGitHub().setAccessToken(gitHubAccessToken);
 | 
			
		||||
		}
 | 
			
		||||
		project.getTasks().register("updateDependencies", DependencyUpdatesTask.class, new Action<DependencyUpdatesTask>() {
 | 
			
		||||
			@Override
 | 
			
		||||
			public void execute(DependencyUpdatesTask updateDependencies) {
 | 
			
		||||
				updateDependencies.setDescription("Update the dependencies");
 | 
			
		||||
				updateDependencies.setCheckConstraints(true);
 | 
			
		||||
				updateDependencies.setOutputFormatter(new Closure<Void>(null) {
 | 
			
		||||
					@Override
 | 
			
		||||
					public Void call(Object argument) {
 | 
			
		||||
						Result result = (Result) argument;
 | 
			
		||||
						if (gitHubApi == null && updateDependenciesSettings.getUpdateMode() != UpdateDependenciesExtension.UpdateMode.COMMIT) {
 | 
			
		||||
							gitHubApi = new GitHubApi(updateDependenciesSettings.getGitHub().getAccessToken());
 | 
			
		||||
						}
 | 
			
		||||
						updateDependencies(result, project, updateDependenciesSettings);
 | 
			
		||||
						updateGradleVersion(result, project, updateDependenciesSettings);
 | 
			
		||||
						return null;
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
				updateDependencies.resolutionStrategy(new Action<ResolutionStrategyWithCurrent>() {
 | 
			
		||||
					@Override
 | 
			
		||||
					public void execute(ResolutionStrategyWithCurrent resolution) {
 | 
			
		||||
						resolution.componentSelection(new Action<ComponentSelectionRulesWithCurrent>() {
 | 
			
		||||
							@Override
 | 
			
		||||
							public void execute(ComponentSelectionRulesWithCurrent components) {
 | 
			
		||||
								updateDependenciesSettings.getExcludes().getActions().forEach((action) -> {
 | 
			
		||||
									components.all(action);
 | 
			
		||||
								});
 | 
			
		||||
								components.all((selection) -> {
 | 
			
		||||
									ModuleComponentIdentifier candidate = selection.getCandidate();
 | 
			
		||||
									if (candidate.getGroup().startsWith("org.apache.directory.") && !candidate.getVersion().equals(selection.getCurrentVersion())) {
 | 
			
		||||
										selection.reject("org.apache.directory.* has breaking changes in newer versions");
 | 
			
		||||
									}
 | 
			
		||||
								});
 | 
			
		||||
								String jaxbBetaRegex = ".*?b\\d+.*";
 | 
			
		||||
								components.withModule("javax.xml.bind:jaxb-api", excludeWithRegex(jaxbBetaRegex, "Reject jaxb-api beta versions"));
 | 
			
		||||
								components.withModule("com.sun.xml.bind:jaxb-impl", excludeWithRegex(jaxbBetaRegex, "Reject jaxb-api beta versions"));
 | 
			
		||||
								components.withModule("commons-collections:commons-collections", excludeWithRegex("^\\d{3,}.*", "Reject commons-collections date based releases"));
 | 
			
		||||
							}
 | 
			
		||||
						});
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void updateDependencies(Result result, Project project, UpdateDependenciesExtension updateDependenciesSettings) {
 | 
			
		||||
		SortedSet<DependencyOutdated> dependencies = result.getOutdated().getDependencies();
 | 
			
		||||
		if (dependencies.isEmpty()) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		Map<String, List<DependencyOutdated>> groups = new LinkedHashMap<>();
 | 
			
		||||
		dependencies.forEach(outdated -> {
 | 
			
		||||
			groups.computeIfAbsent(outdated.getGroup(), (key) -> new ArrayList<>()).add(outdated);
 | 
			
		||||
		});
 | 
			
		||||
		File gradlePropertiesFile = project.getRootProject().file(Project.GRADLE_PROPERTIES);
 | 
			
		||||
		Mono<GitHubApi.FindCreateIssueResult> createIssueResult = createIssueResultMono(updateDependenciesSettings);
 | 
			
		||||
		List<File> filesWithDependencies = updateDependenciesSettings.getFiles().get();
 | 
			
		||||
		groups.forEach((group, outdated) -> {
 | 
			
		||||
			outdated.forEach((dependency) -> {
 | 
			
		||||
				String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
 | 
			
		||||
				String originalDependency = ga + dependency.getVersion();
 | 
			
		||||
				String replacementDependency = ga + updatedVersion(dependency);
 | 
			
		||||
				System.out.println("Update " + originalDependency + " to " + replacementDependency);
 | 
			
		||||
				filesWithDependencies.forEach((fileWithDependency) -> {
 | 
			
		||||
					updateDependencyInlineVersion(fileWithDependency, dependency);
 | 
			
		||||
					updateDependencyWithVersionVariable(fileWithDependency, gradlePropertiesFile, dependency);
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			// commit
 | 
			
		||||
			DependencyOutdated firstDependency = outdated.get(0);
 | 
			
		||||
			String updatedVersion = updatedVersion(firstDependency);
 | 
			
		||||
			String title = outdated.size() == 1 ? "Update " + firstDependency.getName() + " to " + updatedVersion : "Update " + firstDependency.getGroup() + " to " + updatedVersion;
 | 
			
		||||
			afterGroup(updateDependenciesSettings, project.getRootDir(), title, createIssueResult);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void afterGroup(UpdateDependenciesExtension updateDependenciesExtension, File rootDir, String title, Mono<GitHubApi.FindCreateIssueResult> createIssueResultMono) {
 | 
			
		||||
 | 
			
		||||
		String commitMessage = title;
 | 
			
		||||
		if (updateDependenciesExtension.getUpdateMode() == UpdateDependenciesExtension.UpdateMode.GITHUB_ISSUE) {
 | 
			
		||||
			GitHubApi.FindCreateIssueResult createIssueResult = createIssueResultMono.block();
 | 
			
		||||
			RateLimitQuery.RateLimit rateLimit = gitHubApi.findRateLimit().block();
 | 
			
		||||
			rateLimit = gitHubApi.findRateLimit().block();
 | 
			
		||||
			System.out.println("remaining " + rateLimit.remaining() + " reset at " + rateLimit.resetAt());
 | 
			
		||||
			Integer issueNumber = gitHubApi.createIssue(createIssueResult.getRepositoryId(), title, createIssueResult.getLabelIds(), createIssueResult.getMilestoneId(), createIssueResult.getAssigneeId()).delayElement(Duration.ofSeconds(1)).block();
 | 
			
		||||
			commitMessage += "\n\nCloses gh-" + issueNumber;
 | 
			
		||||
		}
 | 
			
		||||
		runCommand(rootDir, "git", "commit", "-am", commitMessage);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private Mono<GitHubApi.FindCreateIssueResult> createIssueResultMono(UpdateDependenciesExtension updateDependenciesExtension) {
 | 
			
		||||
		return Mono.defer(() -> {
 | 
			
		||||
			UpdateDependenciesExtension.GitHub gitHub = updateDependenciesExtension.getGitHub();
 | 
			
		||||
			return gitHubApi.findCreateIssueInput(gitHub.getOrganization(), gitHub.getRepository(), gitHub.getMilestone()).cache();
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void updateGradleVersion(Result result, Project project, UpdateDependenciesExtension updateDependenciesSettings) {
 | 
			
		||||
		GradleUpdateResult current = result.getGradle().getCurrent();
 | 
			
		||||
		GradleUpdateResult running = result.getGradle().getRunning();
 | 
			
		||||
		if (current.compareTo(running) > 0) {
 | 
			
		||||
			String title = "Update Gradle to " + current.getVersion();
 | 
			
		||||
			System.out.println(title);
 | 
			
		||||
			runCommand(project.getRootDir(), "./gradlew", "wrapper", "--gradle-version", current.getVersion(), "--no-daemon");
 | 
			
		||||
			afterGroup(updateDependenciesSettings, project.getRootDir(), title, createIssueResultMono(updateDependenciesSettings));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static Supplier<List<File>> defaultFiles(Project project) {
 | 
			
		||||
		return () -> {
 | 
			
		||||
			List<File> result = new ArrayList<>();
 | 
			
		||||
			result.add(project.getBuildFile());
 | 
			
		||||
			project.getChildProjects().values().forEach((childProject) ->
 | 
			
		||||
					result.add(childProject.getBuildFile())
 | 
			
		||||
			);
 | 
			
		||||
			result.add(project.getRootProject().file("buildSrc/build.gradle"));
 | 
			
		||||
			return result;
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static void runCommand(File dir, String... args) {
 | 
			
		||||
		try {
 | 
			
		||||
			Process process = new ProcessBuilder()
 | 
			
		||||
					.directory(dir)
 | 
			
		||||
					.command(args)
 | 
			
		||||
					.start();
 | 
			
		||||
			writeLinesTo(process.getInputStream(), System.out);
 | 
			
		||||
			writeLinesTo(process.getErrorStream(), System.out);
 | 
			
		||||
			if (process.waitFor() != 0) {
 | 
			
		||||
				new RuntimeException("Failed to run " + Arrays.toString(args));
 | 
			
		||||
			}
 | 
			
		||||
		} catch (IOException | InterruptedException e) {
 | 
			
		||||
			throw new RuntimeException("Failed to run " + Arrays.toString(args), e);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static void writeLinesTo(InputStream input, PrintStream out) {
 | 
			
		||||
		Scanner scanner = new Scanner(input);
 | 
			
		||||
		while(scanner.hasNextLine()) {
 | 
			
		||||
			out.println(scanner.nextLine());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	static Action<ComponentSelectionWithCurrent> excludeWithRegex(String regex, String reason) {
 | 
			
		||||
		Pattern pattern = Pattern.compile(regex);
 | 
			
		||||
		return (selection) -> {
 | 
			
		||||
			String candidateVersion = selection.getCandidate().getVersion();
 | 
			
		||||
			if (pattern.matcher(candidateVersion).matches()) {
 | 
			
		||||
				selection.reject(candidateVersion + " is not allowed because it is " + reason);
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static void updateDependencyInlineVersion(File buildFile, DependencyOutdated dependency){
 | 
			
		||||
		String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
 | 
			
		||||
		String originalDependency = ga + dependency.getVersion();
 | 
			
		||||
		String replacementDependency = ga + updatedVersion(dependency);
 | 
			
		||||
		replaceFileText(buildFile, buildFileText -> buildFileText.replace(originalDependency, replacementDependency));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static void replaceFileText(File file, Function<String, String> replaceText) {
 | 
			
		||||
		String buildFileText = readString(file);
 | 
			
		||||
		String updatedBuildFileText = replaceText.apply(buildFileText);
 | 
			
		||||
		writeString(file, updatedBuildFileText);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static String readString(File file) {
 | 
			
		||||
		try {
 | 
			
		||||
			byte[] bytes = Files.readAllBytes(file.toPath());
 | 
			
		||||
			return new String(bytes);
 | 
			
		||||
		} catch (IOException e) {
 | 
			
		||||
			throw new RuntimeException(e);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void writeString(File file, String text) {
 | 
			
		||||
		try {
 | 
			
		||||
			Files.write(file.toPath(), text.getBytes());
 | 
			
		||||
		} catch (IOException e) {
 | 
			
		||||
			throw new RuntimeException(e);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static void updateDependencyWithVersionVariable(File scanFile, File gradlePropertiesFile, DependencyOutdated dependency) {
 | 
			
		||||
		if (!gradlePropertiesFile.exists()) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
 | 
			
		||||
			String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
 | 
			
		||||
			Pattern pattern = Pattern.compile("\"" + ga + "\\$\\{?([^'\"]+?)\\}?\"");
 | 
			
		||||
			String buildFileText = readString(scanFile);
 | 
			
		||||
			Matcher matcher = pattern.matcher(buildFileText);
 | 
			
		||||
			while (matcher.find()) {
 | 
			
		||||
				String versionVariable = matcher.group(1);
 | 
			
		||||
				gradlePropertiesText = gradlePropertiesText.replace(versionVariable + "=" + dependency.getVersion(), versionVariable + "=" + updatedVersion(dependency));
 | 
			
		||||
			}
 | 
			
		||||
			return gradlePropertiesText;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static String updatedVersion(DependencyOutdated dependency) {
 | 
			
		||||
		VersionAvailable available = dependency.getAvailable();
 | 
			
		||||
		String release = available.getRelease();
 | 
			
		||||
		if (release != null) {
 | 
			
		||||
			return release;
 | 
			
		||||
		}
 | 
			
		||||
		return available.getMilestone();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user